diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..b2a76c2 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,22 @@ +# Commits that changed file headers, copyright lines or version constants across +# the whole tree. They carry no behavioural change, so `git blame` should look +# through them to the commit that last touched the code itself. +# +# GitHub applies this file automatically. To use it locally, run once: +# +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# Add a commit here only when it is mechanical and repository-wide. A commit +# that changes behaviour does not belong in this file, however large it is. + +# 2024-01-29 Update copyright. +# 229 files +8c3b71832c6650e022cac3c824b8cda4b728248e + +# 2026-03-05 Updated copyright headers and removed revision history +# 372 files +61b0b06c3eca2690c2b80cadb09321d1479bd013 + +# 2026-06-06 Added copyright headers to files missing them +# 143 files +30e1d491a92c03cf8f52d1ef28fd017eb9bfb268 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ff41be5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + target-branch: dev diff --git a/.github/workflows/regression_test.yml b/.github/workflows/regression_test.yml index 5aab97d..613b2e3 100644 --- a/.github/workflows/regression_test.yml +++ b/.github/workflows/regression_test.yml @@ -1,25 +1,100 @@ -# This is a basic workflow that is manually triggered - name: regression_test -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch on: workflow_dispatch: push: - branches: [ master ] + branches: [dev, master] pull_request: - branches: [ master ] + branches: [dev, master] + +concurrency: + group: filex-regression-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} -# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # This workflow contains a single job called "linux_job" run_tests: permissions: contents: read issues: read checks: write pull-requests: write + # GitHub validates the called workflow's skipped deploy job permissions. + # Its test job narrows its own token permissions. + pages: write + id-token: write + uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@80dfbe7797f4e03bb878f06b9557ddc4041cf212 + with: + coverage_name: merged + coverage_thresholds: '99 100' + skip_deploy: true + + win64: + runs-on: windows-2022 + timeout-minutes: 90 + permissions: + contents: read + steps: + - name: Check out FileX + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Check out ThreadX + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: eclipse-threadx/threadx + ref: 80dfbe7797f4e03bb878f06b9557ddc4041cf212 + path: threadx + - name: Build Win64 configurations + timeout-minutes: 35 + shell: pwsh + run: | + Start-Transcript -Path win64-build.log + try { + ./scripts/build_fx.ps1 -Arch win64 -Configuration all -ThreadXDir "$env:GITHUB_WORKSPACE/threadx" -BuildTimeoutSeconds 300 + } finally { + Stop-Transcript + } + - name: Test Win64 configurations + timeout-minutes: 50 + shell: pwsh + run: | + Start-Transcript -Path win64-test.log + try { + ./scripts/test_fx.ps1 -Arch win64 -Configuration all -Parallel 1 -TestTimeoutSeconds 600 + } finally { + Stop-Transcript + } + - name: Upload failure diagnostics + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: win64-diagnostics + path: | + win64-build.log + win64-test.log + build/tests/win64/**/Testing/Temporary/* + build/tests/win64/**/CMakeFiles/CMakeConfigureLog.yaml + if-no-files-found: warn + + deploy: + if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' }} + needs: [run_tests, win64] + runs-on: ubuntu-24.04 + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + permissions: pages: write id-token: write - uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@master + steps: + - name: Download merged coverage + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage_report-* + merge-multiple: true + path: coverage_report + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: coverage_report/merged + - name: Deploy coverage + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.github/workflows/repo_checks.yml b/.github/workflows/repo_checks.yml new file mode 100644 index 0000000..955e6dd --- /dev/null +++ b/.github/workflows/repo_checks.yml @@ -0,0 +1,63 @@ +############################################################################### +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################### + +name: repo_checks + +# Repository-wide text checks. +# +# THIS LIVES IN ITS OWN WORKFLOW BECAUSE IT MUST NOT BE PATH-FILTERED, and a +# path filter is a property of a workflow rather than of a job. The check +# selects its input with `git ls-files` and scans every tracked file, so any +# file at all can carry a finding. +# +# That is not hypothetical here. The pass that introduced the fixed +# disclosure line covered source files only, and the drift it was meant to end +# survived for months afterwards in CMake files, toolchain files, shell and +# PowerShell scripts, a GDB script and a Visual Studio manifest -- precisely +# the files a source-path filter would have skipped. A workflow that gates no +# pull request anybody opens is worse than no workflow, because it looks like +# coverage. +# +# Cheap enough that running it on everything costs nothing worth measuring: it +# is grep over a repository this size, with no toolchain, no build and no +# cache. + +on: + # No `paths:` on either trigger, deliberately. See above. + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +# A second push to the same branch makes the first answer irrelevant. +concurrency: + group: repo-checks-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + checks: + runs-on: ubuntu-24.04 + steps: + # Actions are pinned to a commit SHA with the version in the trailing + # comment. A tag can be moved; a SHA cannot, which is what makes "which + # code ran in our CI" answerable from the repository. + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Check the AI disclosure comments + run: scripts/check_ai_disclosure.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index f3ace51..89c53a4 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.13 FATAL_ERROR) # Set up the project project(filex - LANGUAGES C ASM + LANGUAGES C ) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a327a0b..f7d2eae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,72 +1,359 @@ -# Contributing to Eclipse ThreadX +# Contributing to Eclipse ThreadX FileX Thanks for your interest in this project. ## Project description -Eclipse ThreadX provides a vendor-neutral, open source, safety certified OS for -real-time applications published on under a permissive license. The Eclipse -ThreadX suite encompasses: -* ThreadX - advanced real-time operating system (RTOS) designed specifically for deeply embedded applications -* NetX Duo - advanced, industrial-grade TCP/IP network stack designed specifically for deeply embedded real-time and IoT applications -* FileX - high-performance, FAT-compatible file system that’s fully integrated with ThreadX kernel -* GUIX - provides a complete, embedded graphical user interface (GUI) library -* USBX - high-performance USB host, device, and on-the-go (OTG) embedded stack, that is fully integrated with ThreadX kernel -* LevelX - Flash Wear Leveling for FileX and stand-alone purposes -* GuiX Studio - design environment, facilitating the creation and maintenance of all graphical elements for GUIX -* TraceX - analysis tool that provides a graphical view of real-time system events to better understand the behavior of real-time systems +FileX is the Eclipse ThreadX FAT-compatible file system for deeply embedded applications. It provides file management over media such as RAM, SD cards, USB storage, and NAND or NOR flash through LevelX. The repository contains the core file system, media drivers, samples, and processor ports. FileX integrates with ThreadX and also supports a standalone build. -Project site: https://projects.eclipse.org/projects/iot.threadx +Eclipse ThreadX provides a vendor-neutral, open source, safety-certified OS for real-time applications, published under a permissive license. The Eclipse ThreadX suite encompasses: + +* **ThreadX** - advanced real-time operating system (RTOS) designed specifically for deeply embedded applications +* **NetX Duo** - advanced, industrial-grade TCP/IP network stack designed specifically for deeply embedded real-time and IoT applications +* **FileX** - high-performance, FAT-compatible file system that is fully integrated with the ThreadX kernel +* **GUIX** - complete, embedded graphical user interface (GUI) library +* **GUIX Studio** - design environment, facilitating the creation and maintenance of all graphical elements for GUIX +* **USBX** - high-performance USB host, device, and on-the-go (OTG) embedded stack, fully integrated with the ThreadX kernel +* **LevelX** - flash wear levelling for FileX and stand-alone purposes +* **TraceX** - analysis tool that provides a graphical view of real-time system events to better understand the behaviour of real-time systems +* **ZoneX** - deterministic partitioning hypervisor for mixed-criticality embedded systems +* **SampleX** - samples and demos for the components above + +Project websites: + +* https://threadx.io +* https://projects.eclipse.org/projects/iot.threadx + +This file describes how to contribute to **FileX**. General contribution conventions are shared across the Eclipse ThreadX repositories; build and test instructions differ by component. ## Terms of Use -This repository is subject to the Terms of Use of the Eclipse Foundation -https://www.eclipse.org/legal/termsofuse.php +This repository is subject to the Terms of Use of the Eclipse Foundation: https://www.eclipse.org/legal/termsofuse.php -## Developer resources +## New contributors -Information regarding source code management, builds, coding standards, and more. -https://projects.eclipse.org/projects/iot.threadx/developer +Welcome. Here is the shortest path from zero to a merged pull request. -The project maintains the following source code repositories +1. **Create an Eclipse Foundation account.** https://accounts.eclipse.org/user/register -* https://github.com/eclipse-threadx/.github -* https://github.com/eclipse-threadx/cmsis-packs -* https://github.com/eclipse-threadx/filex -* https://github.com/eclipse-threadx/getting-started -* https://github.com/eclipse-threadx/guix -* https://github.com/eclipse-threadx/levelx -* https://github.com/eclipse-threadx/netxduo -* https://github.com/eclipse-threadx/rtos-docs -* https://github.com/eclipse-threadx/samples -* https://github.com/eclipse-threadx/threadx -* https://github.com/eclipse-threadx/threadx-learn-samples -* https://github.com/eclipse-threadx/tracex -* https://github.com/eclipse-threadx/usbx + The email address on that account matters: it must be the same address you use as the `Author` of your Git commits. If the two do not match, the ECA check on your pull request will fail. + +2. **Sign the Eclipse Contributor Agreement (ECA).** See the section below. This is a one-time step and covers every Eclipse Foundation project. + +
+ +3. **Configure Git accordingly.** + + ``` + git config user.name "Your Name" + git config user.email "the-address-on-your-eclipse-account@example.org" + ``` + +4. **Pick something to work on.** Open issues are fair game, including ones nobody has assigned. Please leave a comment on the issue saying you intend to work on it, so two people do not solve the same problem twice. If an issue carries a `good first issue` label, it is a reasonable starting point. + +5. **Have an idea for a new feature? Discuss it first.** Open a thread in [GitHub Discussions](https://github.com/orgs/eclipse-threadx/discussions) or raise it on the [developer mailing list](https://accounts.eclipse.org/mailing-list/threadx-dev) before writing code. File system and media behaviour can affect stored data across many applications, so discuss the shape of a feature before submitting a finished implementation. + +Bug fixes and documentation corrections need no prior discussion. Send them straight in. ## Eclipse Development Process -This Eclipse Foundation open project is governed by the Eclipse Foundation -Development Process and operates under the terms of the Eclipse IP Policy. +This Eclipse Foundation open project is governed by the Eclipse Foundation Development Process and operates under the terms of the Eclipse IP Policy. * https://eclipse.org/projects/dev_process * https://www.eclipse.org/org/documents/Eclipse_IP_Policy.pdf ## Eclipse Contributor Agreement -In order to be able to contribute to Eclipse Foundation projects you must electronically sign the Eclipse Contributor Agreement (ECA). -https://www.eclipse.org/legal/ECA.php +In order to be able to contribute to Eclipse Foundation projects you must electronically sign the Eclipse Contributor Agreement (ECA): https://www.eclipse.org/legal/ECA.php + +The ECA provides the Eclipse Foundation with a permanent record that you agree that each of your contributions will comply with the commitments documented in the Developer Certificate of Origin (DCO). Having an ECA on file associated with the email address matching the "Author" field of your contribution's Git commits fulfills the DCO's requirement that you sign-off on your contributions. + +For more information, please see the Eclipse Committer Handbook: https://www.eclipse.org/projects/handbook/#resources-commit + +## Required tooling + +Build and test FileX with CMake and Ninja. The root and regression projects require CMake 3.13 or later. The regression CMake project sets C99 with compiler extensions off. GCC 14 is the project reference compiler on Linux. The Windows simulator uses MSVC from the Visual Studio Build Tools; it does not require the Visual Studio IDE. + +On Ubuntu, `scripts/install.sh` installs the host test tools and pins `gcovr` 8.6 in a Python virtual environment; activate that environment before collecting coverage locally. Use GCC 14 with matching gcov for the Linux regression suite. Embedded ports use the appropriate target compiler and toolchain file. Use GCC syntax for assembly built with GNU toolchains. + +A non-standalone FileX build links ThreadX. The Linux regression runner obtains the pinned ThreadX revision under `test/cmake/threadx/` when needed and checks an existing checkout against that pin. On Windows, pass the checkout explicitly to `scripts/build_fx.ps1` with `-ThreadXDir`. + +## Building and testing + +The regression suite lives under `test/cmake/` and defines nine configurations covering the normal build, disabled cache, disabled error checking, fault tolerance, and standalone combinations. The Linux wrapper scripts build and test all configurations: + +```sh +CC=gcc-14 GCOV=gcov-14 TX_COVERAGE=ON scripts/build.sh +CC=gcc-14 GCOV=gcov-14 TX_COVERAGE=ON scripts/test.sh +``` + +For a focused Linux run, call the CMake suite runner with the same configuration for the build and test steps: + +```sh +test/cmake/run.sh build fault_tolerant_build_coverage +test/cmake/run.sh test fault_tolerant_build_coverage +``` + +The Windows simulator has `win32` and `win64` ports. Its PowerShell scripts configure CMake with Ninja and use MSVC from the Build Tools. For a non-standalone run, supply a ThreadX checkout: + +```powershell +scripts/build_fx.ps1 -Arch win64 -ThreadXDir 'path/to/threadx' +scripts/test_fx.ps1 -Arch win64 +``` + +Use `-Arch win32` for the 32-bit simulator. Both PowerShell scripts accept `-Configuration` to select configurations; the Windows tests run serially because the simulator is timing-sensitive. The Linux test wrapper uses the repository's CTest runner. Report the architecture, configurations, and results that exercised your change. + +With `TX_COVERAGE=ON`, all nine Linux configurations instrument the FileX library. `test/cmake/coverage.sh` writes per-configuration JSON, XML and HTML reports and merges them across the full suite. The merged report excludes the sample RAM driver and must meet 99.9% line and 99.4% branch coverage in CI. Coverage is not collected by the MSVC Windows build. The project goal is 100% test coverage: add or update regression tests for new behaviour and explain any relevant gaps. File-system changes involving a media driver also need testing on the affected media or hardware when available. + +## Continuous integration + +`regression_test.yml` runs on pushes and pull requests to `dev` and `master`, and by manual dispatch. Its Linux job builds and tests all nine configurations and merges their coverage through the pinned ThreadX reusable regression workflow. A separate Win64 job builds and tests all nine configurations with the PowerShell scripts. The workflow does not run Win32 or hardware tests. + +Run relevant Win32 or hardware tests locally and report their results in the pull request. Do not describe an unrun configuration or hardware target as verified. + +## Pull request acceptance criteria + +**Pull requests must target the `dev` branch.** `master` holds the latest release; see [Release model and support](#release-model-and-support) below. A pull request opened against `master` will be asked to retarget. + +Before requesting a review, check your contribution against this list. + +**Process** + +* The branch is a feature branch based on `dev`. Never commit directly to `master` or `dev`. +* Your ECA is signed and the commit `Author` email matches your Eclipse account. +* The pull request is one logical change. Unrelated fixes belong in separate pull requests. +* Commit subject lines start with a past-tense verb, for example `Fixed the memory allocator`. +* The pull request explains what changed and why, and how you verified it. + +**Code** + +* The code is C99-compatible. +* It follows the coding style of the surrounding code. +* New functions and structures are documented in comments, as in existing code. +* MISRA C rules are followed as closely as practical, taking MISRA C 2004, 2012 and 2023 into account. Any deviation is explicit, names the rule being circumvented, and justifies it in a comment. +* `goto` is not used. +* Do not add new external dependencies. This is a hard rule. ThreadX is an existing component relationship for non-standalone builds. +* When implementing an industry standard, code is not copied from an existing implementation. Existing implementations may inform your work, but you must identify those sources clearly. +* Code is written with the suite's priorities in mind: runtime speed and small code size. +* New C and assembly files, and edited C and assembly files, carry the appropriate header and attribution (see below). + +**Verification** + +* All applicable CI checks are green, including the Linux and Win64 regression jobs on a pull request to `dev`. +* The change builds without new warnings on the reference toolchains. +* Regression tests covering the change are added or updated. The project targets 100% test coverage; a pull request that lowers coverage needs a stated reason. State which FileX configurations ran and any relevant coverage gap. +* API or behaviour changes come with a matching documentation pull request against [rtos-docs-asciidoc](https://github.com/eclipse-threadx/rtos-docs-asciidoc). + +**Security** + +If you discover a security issue while working on a change, do not describe it in a public pull request. Follow [SECURITY.md](SECURITY.md) instead. + +## AI-assisted contributions + +**AI-assisted contributions are welcome**, provided they are attributed. + +Two things are non-negotiable: + +1. **Attribution.** Mark AI-assisted C and assembly files as described below, and identify the tool and model in the commit message. +2. **Human responsibility.** The human contributor submitting the pull request is responsible for the contribution - technically and legally. Signing the ECA means *you* certify the contribution's provenance. An AI tool cannot sign the ECA and cannot hold that responsibility. Review what the tool produced, understand it, and verify that it is correct and that you have the right to contribute it. "The model wrote it" is not a defence. + +This is consistent with the Eclipse Foundation's [Generative AI Usage Guidelines](https://www.eclipse.org/projects/guidelines/genai/) and the [Eclipse Project Handbook](https://www.eclipse.org/projects/handbook/#genai). Please read them before submitting AI-assisted work. + +### Using a coding agent + +Give a coding agent this contribution guide and the build instructions for the area it changes. Review its output against the same acceptance criteria as any other contribution. In particular, check C99 compatibility, surrounding style, test coverage, and the relevant media and standalone or ThreadX configuration for runtime verification. + +### Header for new files + +Add this header when creating a new C or assembly (`.S`) file: + +```c +/*************************************************************************** + * Copyright (c) Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by (). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ +``` + +Substitute the current year, product name, and model and version. If a file was written without AI assistance, omit the AI Disclosure paragraph and use `SPDX-License-Identifier: MIT`. + +### Header for existing files + +When editing an existing C or assembly file whose copyright is from 2025 or earlier, and which does not already mention Eclipse ThreadX contributors, add this line to the header: + +```c + * Copyright (c) Eclipse ThreadX contributors +``` + +It goes *below* the older copyright. All copyright lines must stay in chronological order. For example: + +```c +/*************************************************************************** + * Copyright (c) 2024 Microsoft Corporation + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + **************************************************************************/ +``` + +If the edit was AI-assisted and the file does not already carry an AI disclosure line, add this line just under the header: + +```c +// Portions of this file were generated with AI assistance. +``` -The ECA provides the Eclipse Foundation with a permanent record that you agree -that each of your contributions will comply with the commitments documented in -the Developer Certificate of Origin (DCO). Having an ECA on file associated with -the email address matching the "Author" field of your contribution's Git commits -fulfills the DCO's requirement that you sign-off on your contributions. +Use the comment marker already used by the file. Keep an existing disclosure line unchanged and never add a second one. If you copy an existing file to get started on a new one, treat the result as a new file: use the new-file header, do not carry the old one over. -For more information, please see the Eclipse Committer Handbook: -https://www.eclipse.org/projects/handbook/#resources-commit +### Commit attribution + +Attribute AI assistance with an `Assisted-by` trailer in each commit that contains AI-assisted changes: + +```text +Fixed the file write status + +A completed write could report an out-of-date status to the caller. + +The write path now returns the status set by the completed operation. + +The affected FileX regression configurations passed all tests. + +Assisted-by: () +``` + +Use the product's accepted name (`Claude Code`, `Copilot`, `Codex`, or `Gemini`) and the model string reported by the tool. Do not use `Co-Authored-By` for AI attribution. Commit subjects and pull request titles start with a past-tense verb and stay within 72 characters. The message body states the cause, fix, and test result in that order; wrap commit bodies at 88 characters. Pull request descriptions use one line per paragraph. + +## Release model and support + +This section summarises the project's [Release Model and Support Policy](https://github.com/eclipse-threadx/rtos-docs-asciidoc/blob/main/rtos-docs/home/modules/ROOT/pages/releases-and-support.adoc), which is the authoritative version. + +### Version numbers + +Eclipse ThreadX releases generally follow [Semantic Versioning](https://semver.org/). Given a version number **X.Y.Z.Bh**, for example `6.5.0.202601a`: + +* **X** increases for a *milestone release*. Components currently stay at version 6. +* **Y** increases for a *feature release* adding a major feature. +* **Z** increases for a *maintenance release* of minor fixes and improvements. +* **B** is a *build number* identifying the quarter of publication - `202601` is Q1 2026. +* **h** denotes a *hotfix release*, identified by a letter. Hotfix releases are component-specific. + +### Release cadence + +The project adopted a predictable quarterly release model in September 2025 and has published a release every quarter since. + +Quarterly releases ship new versions of every component, whether or not that component saw code changes, so that version numbers do not drift apart between components. A release shipping no code changes for a component says so in its release notes. + +Urgent fixes between quarterly releases ship as *hotfix* releases. Those contain only security fixes, or fixes for serious problems that prevent building or testing applications. + +### Branches + +The project runs a time-based release train on trunk-based development. There are no long-term support branches and no backports to older releases. + +* `master` always contains the latest quarterly or hotfix release. +* `dev` is the integration branch. Pull requests are merged there during the quarter. Track `dev` if you want to test new features and fixes ahead of a release. +* A quarterly release is performed by merging `dev` into `master`. + +### Support policy + +Issues in the codebase are fixed on a best-effort basis, and pull requests fixing existing or new issues are gladly accepted. + +Security vulnerabilities disclosed confidentially are handled under the [Eclipse Foundation's security policy](https://www.eclipse.org/security/policy/). Once a candidate vulnerability is confirmed, the team works to deliver a fix as soon as possible, shipping it in the next quarterly release or as a hotfix depending on timing. Resolved vulnerabilities are disclosed immediately after a release containing the fix becomes available. The team aims to resolve newly confirmed vulnerabilities within three months; that period may be extended by the Project Leadership Chain together with the Eclipse Foundation Security team where appropriate. + +See [SECURITY.md](SECURITY.md) for how to report a vulnerability. + +### Roadmap + +The project team plans its work on a [public GitHub project board](https://github.com/orgs/eclipse-threadx/projects/2/views/2). + +### A note on older releases + +Microsoft contributed only the Azure RTOS codebase v6.x to the Eclipse Foundation. Older ThreadX releases (v5.x and lower) sold by Express Logic were never made open source, and Microsoft has discontinued sales and support for them. Users of ThreadX 5.x and lower should upgrade to the latest release of Eclipse ThreadX as soon as possible. + +## Documentation + +The documentation is published at **https://threadx.io**. PDF manuals for every component, in A4 and US Letter formats, are attached as assets to each GitHub release. + +### The stack + +Documentation is written in [AsciiDoc](https://asciidoc.org/) and built with [Antora](https://antora.org/). PDF manuals are produced by the [Antora Assembler](https://docs.antora.org/assembler/latest/) with `asciidoctor-pdf`. + +### The workflow + +1. The single source of truth is [rtos-docs-asciidoc](https://github.com/eclipse-threadx/rtos-docs-asciidoc). All documentation changes are made there, as pull requests, following the same branch rules as the code repositories. +2. At release time the site is generated from that source into [rtos-docs-html](https://github.com/eclipse-threadx/rtos-docs-html), which holds the rendered HTML and exists solely for website integration. Do not edit it by hand - your changes will be overwritten by the next build. +3. The website then serves the generated content from `rtos-docs-html`. + +The older `rtos-docs` repository, which held the documentation in Markdown, is archived and superseded by `rtos-docs-asciidoc`. Do not send changes there. + +If your contribution adds or changes an API or a feature, open a matching pull request against `rtos-docs-asciidoc`. + +## Developer resources + +Information regarding source code management, builds, coding standards, and more: https://projects.eclipse.org/projects/iot.threadx/developer + +The project maintains the following repositories: + +**Components** + +* https://github.com/eclipse-threadx/threadx +* https://github.com/eclipse-threadx/netxduo +* https://github.com/eclipse-threadx/filex +* https://github.com/eclipse-threadx/guix +* https://github.com/eclipse-threadx/usbx +* https://github.com/eclipse-threadx/levelx +* https://github.com/eclipse-threadx/tracex +* https://github.com/eclipse-threadx/zonex + +**Samples and platforms** + +* https://github.com/eclipse-threadx/samplex +* https://github.com/eclipse-threadx/supported-platforms + +**Documentation** + +* https://github.com/eclipse-threadx/rtos-docs-asciidoc +* https://github.com/eclipse-threadx/rtos-docs-html + +**Community and process** + +* https://github.com/eclipse-threadx/discussions +* https://github.com/eclipse-threadx/trustedx +* https://github.com/eclipse-threadx/.github ## Contact -Contact the project developers via the project's "dev" list. +### GitHub Discussions + +https://github.com/orgs/eclipse-threadx/discussions + +Q&A, feedback, and announcements. Decisions taken by the project team are documented here as well. This is usually the fastest way to reach both the team and other users. + +### Main ThreadX mailing list + +https://accounts.eclipse.org/mailing-list/threadx + +News and updates about the ThreadX project and the ThreadX Alliance. + +### Developer mailing list + https://accounts.eclipse.org/mailing-list/threadx-dev + +Project team conversations. Feel free to jump in and ask non-technical questions there. + +### User mailing list + +https://accounts.eclipse.org/mailing-list/threadx-users + +Ask your technical questions and discuss issues here. diff --git a/README.md b/README.md index 745371f..2200980 100755 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ FileX as part of Eclipse ThreadX has been integrated to the semiconductor's SDKs We also provide [samples](https://github.com/eclipse-threadx/samples) using hero development boards from semiconductors you can build and test with. -See [Overview of Eclipse ThreadX FileX](https://github.com/eclipse-threadx/rtos-docs/blob/main/rtos-docs/filex/overview-filex.md) for the high-level overview. +See [Overview of Eclipse ThreadX FileX](https://threadx.io/releases/6.5.1/filex/main/overview-filex.html) for the high-level overview. ## Repository Structure and Usage @@ -129,7 +129,7 @@ License terms for using Eclipse ThreadX are defined in the LICENSE.txt file of t The following are references to additional Eclipse ThreadX resources: -- **Product introduction**: https://github.com/eclipse-threadx/rtos-docs +- **Product introduction**: https://threadx.io/releases/latest - **Product issues and bugs, or feature requests**: https://github.com/eclipse-threadx/filex/issues - **TraceX Installer**: https://aka.ms/azrtos-tracex-installer diff --git a/common/inc/fx_api.h b/common/inc/fx_api.h index 9c25594..42d759d 100644 --- a/common/inc/fx_api.h +++ b/common/inc/fx_api.h @@ -86,8 +86,8 @@ extern "C" { #define AZURE_RTOS_FILEX #define FILEX_MAJOR_VERSION 6 #define FILEX_MINOR_VERSION 5 -#define FILEX_PATCH_VERSION 1 -#define FILEX_BUILD_VERSION 202602 +#define FILEX_PATCH_VERSION 2 +#define FILEX_BUILD_VERSION 202603 #define FILEX_HOTFIX_VERSION ' ' /* Define the following symbols for backward compatibility */ diff --git a/common/src/fx_directory_long_name_get.c b/common/src/fx_directory_long_name_get.c index d7b397d..dfd6fac 100644 --- a/common/src/fx_directory_long_name_get.c +++ b/common/src/fx_directory_long_name_get.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -29,6 +31,17 @@ #include "fx_directory.h" +/* DEPRECATION NOTICE + * fx_directory_long_name_get() is deprecated. Do not use it in new code. + * + * WHY: Does not accept a destination buffer length; writes up to FX_MAX_LONG_NAME_LEN bytes regardless of the caller's buffer size, risking a buffer overrun. + * + * WHAT TO DO: replace calls with fx_directory_long_name_get_extended(), passing the actual + * destination buffer size as an additional argument. + */ +#pragma message("fx_directory_long_name_get() is deprecated. " \ + "Use fx_directory_long_name_get_extended() and pass the actual buffer size.") + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -45,9 +58,10 @@ /* name. If there is no long file name, the short file name will be */ /* returned. */ /* */ -/* Note, this API is deprecated as fx_directory_long_name_get_extended */ -/* should be used. The maximum written size to long_file_name could be */ -/* FX_MAX_LONG_NAME_LEN. */ +/* DEPRECATED. Use fx_directory_long_name_get_extended() instead, */ +/* passing the actual destination buffer length. It takes no buffer */ +/* length and writes up to FX_MAX_LONG_NAME_LEN bytes whatever the size*/ +/* of the caller's buffer, risking an overrun. */ /* */ /* INPUT */ /* */ diff --git a/common/src/fx_directory_short_name_get.c b/common/src/fx_directory_short_name_get.c index a5ec217..e3fbe46 100644 --- a/common/src/fx_directory_short_name_get.c +++ b/common/src/fx_directory_short_name_get.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -29,6 +31,17 @@ #include "fx_directory.h" +/* DEPRECATION NOTICE + * fx_directory_short_name_get() is deprecated. Do not use it in new code. + * + * WHY: Does not accept a destination buffer length; writes up to FX_MAX_SHORT_NAME_LEN bytes regardless of the caller's buffer size, risking a buffer overrun. + * + * WHAT TO DO: replace calls with fx_directory_short_name_get_extended(), passing the actual + * destination buffer size as an additional argument. + */ +#pragma message("fx_directory_short_name_get() is deprecated. " \ + "Use fx_directory_short_name_get_extended() and pass the actual buffer size.") + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -45,9 +58,10 @@ /* name. If the long file name is really a short file name, the short */ /* file name will be returned. */ /* */ -/* Note, this API is deprecated as fx_directory_short_name_get_extended*/ -/* should be used. The maximum written size to short_file_name could */ -/* be FX_MAX_SHORT_NAME_LEN. */ +/* DEPRECATED. Use fx_directory_short_name_get_extended() instead, */ +/* passing the actual destination buffer length. This function takes no*/ +/* buffer length and writes up to FX_MAX_SHORT_NAME_LEN bytes whatever */ +/* the size of the caller's buffer, risking an overrun. */ /* */ /* INPUT */ /* */ diff --git a/common/src/fx_fault_tolerant_apply_logs.c b/common/src/fx_fault_tolerant_apply_logs.c index 75fb578..628b189 100644 --- a/common/src/fx_fault_tolerant_apply_logs.c +++ b/common/src/fx_fault_tolerant_apply_logs.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -101,7 +103,16 @@ FX_FAULT_TOLERANT_DIR_LOG *dir_log; /* Extended port-specific processing macro, which is by default defined to white space. */ FX_FAULT_TOLERANT_APPLY_LOGS_EXTENSION - size -= FX_FAULT_TOLERANT_LOG_CONTENT_HEADER_SIZE; + /* The log entries start after the header, the FAT chain and the content header, + so only the bytes past all three are available to them. */ + if (size < (FX_FAULT_TOLERANT_LOG_CONTENT_OFFSET + FX_FAULT_TOLERANT_LOG_CONTENT_HEADER_SIZE)) + { + + /* Something wrong with log file. */ + return(FX_FILE_CORRUPT); + } + + size -= (FX_FAULT_TOLERANT_LOG_CONTENT_OFFSET + FX_FAULT_TOLERANT_LOG_CONTENT_HEADER_SIZE); /* Find the number of log entries. */ remaining_logs = _fx_utility_16_unsigned_read((UCHAR *)&log_content -> fx_fault_tolerant_log_content_count); @@ -121,7 +132,11 @@ FX_FAULT_TOLERANT_DIR_LOG *dir_log; /* Validate log entry size. */ - if (log_len > size) + if (((log_type == FX_FAULT_TOLERANT_FAT_LOG_TYPE) && + (log_len < FX_FAULT_TOLERANT_FAT_LOG_ENTRY_SIZE)) || + ((log_type == FX_FAULT_TOLERANT_DIR_LOG_TYPE) && + (log_len < FX_FAULT_TOLERANT_DIR_LOG_ENTRY_SIZE)) || + (log_len > size)) { /* Something wrong with log file. */ @@ -178,7 +193,7 @@ FX_FAULT_TOLERANT_DIR_LOG *dir_log; copy_size = log_len - FX_FAULT_TOLERANT_DIR_LOG_ENTRY_SIZE; - if (((ULONG64)copy_offset + (ULONG64)copy_size) > (ULONG64)(media_ptr -> fx_media_memory_size)) + if (((ULONG64)copy_offset + (ULONG64)copy_size) > (ULONG64)(media_ptr -> fx_media_bytes_per_sector)) { return(FX_FILE_CORRUPT); } diff --git a/common/src/fx_fault_tolerant_enable.c b/common/src/fx_fault_tolerant_enable.c index 21b4d1e..9c436f2 100644 --- a/common/src/fx_fault_tolerant_enable.c +++ b/common/src/fx_fault_tolerant_enable.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -368,6 +370,18 @@ ULONG bytes_per_cluster; /* Fault tolerant log file is valid. */ /* Initialize file size. */ total_size = _fx_utility_16_unsigned_read((UCHAR *)&log_header -> fx_fault_tolerant_log_header_total_size); + + /* A reset log file carries just the header and the FAT chain, so that + is the smallest size on record. The log must also fit both the format + maximum and the buffer the caller supplied. */ + if ((total_size < FX_FAULT_TOLERANT_LOG_CONTENT_OFFSET) || + (total_size > FX_FAULT_TOLERANT_MAXIMUM_LOG_FILE_SIZE) || + (total_size > media_ptr -> fx_media_fault_tolerant_memory_buffer_size)) + { + FX_UNPROTECT + return(FX_FILE_CORRUPT); + } + media_ptr -> fx_media_fault_tolerant_file_size = total_size; diff --git a/common/src/fx_media_volume_get.c b/common/src/fx_media_volume_get.c index 8a9de71..de15326 100644 --- a/common/src/fx_media_volume_get.c +++ b/common/src/fx_media_volume_get.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -32,6 +34,17 @@ #include "fx_utility.h" +/* DEPRECATION NOTICE + * fx_media_volume_get() is deprecated. Do not use it in new code. + * + * WHY: Does not accept a volume-name buffer length; writes up to 12 bytes regardless of the caller's buffer size, risking a buffer overrun. + * + * WHAT TO DO: replace calls with fx_media_volume_get_extended(), passing the actual + * destination buffer size as an additional argument. + */ +#pragma message("fx_media_volume_get() is deprecated. " \ + "Use fx_media_volume_get_extended() and pass the actual buffer size.") + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -47,8 +60,10 @@ /* This function reads the volume name stored in the media's boot */ /* record or root directory. */ /* */ -/* Note, this API is deprecated as fx_media_volume_get_extended should */ -/* be used. The maximum written size to volume_name could be 12. */ +/* DEPRECATED. Use fx_media_volume_get_extended() instead, passing the */ +/* actual destination buffer length. This function takes no buffer */ +/* length and writes up to 12 bytes whatever the size of the caller's */ +/* buffer, risking an overrun. */ /* */ /* INPUT */ /* */ diff --git a/common/src/fx_unicode_length_get.c b/common/src/fx_unicode_length_get.c index 35ee544..971c4b8 100644 --- a/common/src/fx_unicode_length_get.c +++ b/common/src/fx_unicode_length_get.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -29,6 +31,17 @@ #include "fx_unicode.h" +/* DEPRECATION NOTICE + * fx_unicode_length_get() is deprecated. Do not use it in new code. + * + * WHY: Does not accept a buffer length; scans up to 256 bytes regardless of the actual buffer size, risking an overread. + * + * WHAT TO DO: replace calls with fx_unicode_length_get_extended(), passing the actual + * destination buffer size as an additional argument. + */ +#pragma message("fx_unicode_length_get() is deprecated. " \ + "Use fx_unicode_length_get_extended() and pass the actual buffer size.") + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -43,8 +56,10 @@ /* */ /* This function returns the length of the supplied unicode name. */ /* */ -/* Note, this API is deprecated as _fx_unicode_length_get_extended */ -/* should be used. The maximum buffer size of unicode_name is 256. */ +/* DEPRECATED. Use fx_unicode_length_get_extended() instead, passing */ +/* the actual buffer length. It takes no buffer length and scans */ +/* up to 256 bytes whatever the size of the actual buffer, risking an */ +/* over-read. */ /* */ /* INPUT */ /* */ diff --git a/common/src/fx_unicode_name_get.c b/common/src/fx_unicode_name_get.c index 50435af..cc9b6f3 100644 --- a/common/src/fx_unicode_name_get.c +++ b/common/src/fx_unicode_name_get.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -29,6 +31,17 @@ #include "fx_unicode.h" +/* DEPRECATION NOTICE + * fx_unicode_name_get() is deprecated. Do not use it in new code. + * + * WHY: Does not accept a destination buffer length; writes up to FX_MAX_LONG_NAME_LEN*2 bytes regardless of the caller's buffer size, risking a buffer overrun. + * + * WHAT TO DO: replace calls with fx_unicode_name_get_extended(), passing the actual + * destination buffer size as an additional argument. + */ +#pragma message("fx_unicode_name_get() is deprecated. " \ + "Use fx_unicode_name_get_extended() and pass the actual buffer size.") + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -44,9 +57,10 @@ /* This function finds the unicode name associated with the supplied */ /* short 8.3 name. */ /* */ -/* Note, this API is deprecated as fx_unicode_name_get_extended should */ -/* be used. The maximum written size to destination_unicode_name could */ -/* be FX_MAX_LONG_NAME_LEN * 2. */ +/* DEPRECATED. Use fx_unicode_name_get_extended() instead, passing the */ +/* actual destination buffer length. This function takes no buffer */ +/* length and writes up to FX_MAX_LONG_NAME_LEN * 2 bytes whatever the */ +/* size of the caller's buffer, risking an overrun. */ /* */ /* INPUT */ /* */ diff --git a/common/src/fx_unicode_short_name_get.c b/common/src/fx_unicode_short_name_get.c index b0a6fa3..927f9f3 100644 --- a/common/src/fx_unicode_short_name_get.c +++ b/common/src/fx_unicode_short_name_get.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -29,6 +31,17 @@ #include "fx_unicode.h" +/* DEPRECATION NOTICE + * fx_unicode_short_name_get() is deprecated. Do not use it in new code. + * + * WHY: Does not accept a destination buffer length; writes up to 13 bytes regardless of the caller's buffer size, risking a buffer overrun. + * + * WHAT TO DO: replace calls with fx_unicode_short_name_get_extended(), passing the actual + * destination buffer size as an additional argument. + */ +#pragma message("fx_unicode_short_name_get() is deprecated. " \ + "Use fx_unicode_short_name_get_extended() and pass the actual buffer size.") + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -44,9 +57,10 @@ /* This function finds the short name associated with the supplied */ /* unicode name. */ /* */ -/* Note, this API is deprecated as fx_unicode_short_name_get_extended */ -/* should be used. The maximum written size to destination_short_name */ -/* could be 13. */ +/* DEPRECATED. Use fx_unicode_short_name_get_extended() instead, */ +/* passing the actual destination buffer length. It takes no buffer */ +/* length and writes up to 13 bytes whatever the size of the caller's */ +/* buffer, risking an overrun. */ /* */ /* INPUT */ /* */ diff --git a/common/src/fx_utility_logical_sector_write.c b/common/src/fx_utility_logical_sector_write.c index 9da0ffc..825c64c 100644 --- a/common/src/fx_utility_logical_sector_write.c +++ b/common/src/fx_utility_logical_sector_write.c @@ -306,8 +306,6 @@ UCHAR cache_found = FX_FALSE; return(FX_SECTOR_INVALID); } - /* Just write the buffer to the media. */ - #ifndef FX_MEDIA_STATISTICS_DISABLE /* Increment the number of driver write sector(s) requests. */ diff --git a/ports/generic/inc/fx_port.h b/ports/generic/inc/fx_port.h index b3e8de7..73453ed 100644 --- a/ports/generic/inc/fx_port.h +++ b/ports/generic/inc/fx_port.h @@ -206,7 +206,7 @@ typedef unsigned long long ULONG64; #ifdef FX_SYSTEM_INIT CHAR _fx_version_id[] = - "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * FileX Generic Version 6.5.1.202602 *"; + "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * FileX Generic Version 6.5.2.202603 *"; #else extern CHAR _fx_version_id[]; #endif diff --git a/ports/linux/gnu/inc/fx_port.h b/ports/linux/gnu/inc/fx_port.h index fa420f4..170a3d2 100644 --- a/ports/linux/gnu/inc/fx_port.h +++ b/ports/linux/gnu/inc/fx_port.h @@ -331,7 +331,7 @@ extern VOID fault_tolerant_apply_log_callback(struct FX_MEDIA_STRUCT *media_ptr, #ifdef FX_SYSTEM_INIT CHAR _fx_version_id[] = - "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * FileX Linux/GCC Version 6.5.1.202602 *"; + "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * FileX Linux/GCC Version 6.5.2.202603 *"; #else extern CHAR _fx_version_id[]; #endif diff --git a/ports/win32/vs_2019/CMakeLists.txt b/ports/win32/vs_2019/CMakeLists.txt new file mode 100644 index 0000000..d5b601d --- /dev/null +++ b/ports/win32/vs_2019/CMakeLists.txt @@ -0,0 +1,12 @@ +# Portions of this file were generated with AI assistance. + +target_sources(${PROJECT_NAME} PRIVATE + # {{BEGIN_TARGET_SOURCES}} + + # {{END_TARGET_SOURCES}} +) + +target_include_directories(${PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/inc +) diff --git a/ports/win32/vs_2019/inc/fx_port.h b/ports/win32/vs_2019/inc/fx_port.h index 3fa5d2a..c69ef76 100644 --- a/ports/win32/vs_2019/inc/fx_port.h +++ b/ports/win32/vs_2019/inc/fx_port.h @@ -1,14 +1,19 @@ /*************************************************************************** - * Copyright (c) 2024 Microsoft Corporation - * Copyright (c) 2026-present Eclipse ThreadX contributors + * Copyright (C) 2026 Eclipse ThreadX contributors * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at * https://opensource.org/licenses/MIT. * - * SPDX-License-Identifier: MIT + * AI Disclosure: This file was largely AI-generated by Copilot (Claude Opus 4.8). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ @@ -26,11 +31,11 @@ /* PORT SPECIFIC C INFORMATION RELEASE */ /* */ /* fx_port.h Win32/Visual */ -/* 6.3.0 */ +/* 6.5.1.202602 */ /* */ /* AUTHOR */ /* */ -/* William E. Lamie, Microsoft Corporation */ +/* Eclipse ThreadX contributors */ /* */ /* DESCRIPTION */ /* */ @@ -57,6 +62,8 @@ #include "fx_user.h" #endif +#include + /* Include the ThreadX api file. */ @@ -98,7 +105,8 @@ typedef unsigned long long ULONG64; /* Define basic alignment type used in block and byte pool operations. This data type must - be at least 32-bits in size and also be large enough to hold a pointer type. */ + be at least 32-bits in size and also be large enough to hold a pointer type. + On Win32 (ILP32) ULONG is 32 bits and holds a pointer, so ULONG is sufficient. */ #ifndef ALIGN_TYPE_DEFINED #define ALIGN_TYPE_DEFINED @@ -106,19 +114,173 @@ typedef unsigned long long ULONG64; #endif -#endif +#endif /* FX_STANDALONE_ENABLE */ + + +#ifdef FX_REGRESSION_TEST + +/* Define parameters for regression test suite. */ + +#define FX_MAX_SECTOR_CACHE 256 +#define FX_MAX_FAT_CACHE 64 +#define FX_FAT_MAP_SIZE 1 + + +/* Define variables and macros used to introduce errors for the regression test suite. */ + +extern ULONG _fx_ram_driver_io_error_request; +extern ULONG _fx_ram_driver_io_request_count; +extern ULONG _fx_file_open_max_file_size_request; +extern ULONG _fx_directory_entry_read_count; +extern ULONG _fx_directory_entry_read_error_request; +extern ULONG _fx_directory_entry_write_count; +extern ULONG _fx_directory_entry_write_error_request; +extern ULONG _fx_utility_fat_entry_write_count; +extern ULONG _fx_utility_fat_entry_write_error_request; +extern ULONG _fx_utility_fat_entry_read_count; +extern ULONG _fx_utility_fat_entry_read_error_request; +extern ULONG _fx_utility_logical_sector_flush_count; +extern ULONG _fx_utility_logical_sector_flush_error_request; +extern ULONG _fx_utility_logical_sector_write_count; +extern ULONG _fx_utility_logical_sector_write_error_request; +extern ULONG _fx_utility_logical_sector_read_count; +extern ULONG _fx_utility_logical_sector_read_error_request; +extern ULONG _fx_utility_logical_sector_read_1_count; +extern ULONG _fx_utility_logical_sector_read_1_error_request; + +#ifdef FX_ENABLE_FAULT_TOLERANT +struct FX_MEDIA_STRUCT; +extern VOID fault_tolerant_enable_callback(struct FX_MEDIA_STRUCT *media_ptr, + UCHAR *fault_tolerant_memory_buffer, + ULONG log_size); +extern VOID fault_tolerant_apply_log_callback(struct FX_MEDIA_STRUCT *media_ptr, + UCHAR *fault_tolerant_memory_buffer, + ULONG log_size); +#endif /* FX_ENABLE_FAULT_TOLERANT */ + + +#define FX_DIRECTORY_ENTRY_READ_EXTENSION _fx_directory_entry_read_count++; \ + if (_fx_directory_entry_read_error_request) \ + { \ + _fx_directory_entry_read_error_request--; \ + if (_fx_directory_entry_read_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_DIRECTORY_ENTRY_WRITE_EXTENSION _fx_directory_entry_write_count++; \ + if (_fx_directory_entry_write_error_request) \ + { \ + _fx_directory_entry_write_error_request--; \ + if (_fx_directory_entry_write_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_UTILITY_FAT_ENTRY_READ_EXTENSION _fx_utility_fat_entry_read_count++; \ + if (_fx_utility_fat_entry_read_error_request) \ + { \ + _fx_utility_fat_entry_read_error_request--; \ + if (_fx_utility_fat_entry_read_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + if (_fx_utility_fat_entry_read_error_request == 10000) \ + { \ + *entry_ptr = 1; \ + _fx_utility_fat_entry_read_error_request = 0; \ + return(FX_SUCCESS); \ + } \ + if (_fx_utility_fat_entry_read_error_request == 20000) \ + { \ + *entry_ptr = media_ptr -> fx_media_fat_reserved; \ + _fx_utility_fat_entry_read_error_request = 0; \ + return(FX_SUCCESS); \ + } \ + if (_fx_utility_fat_entry_read_error_request == 30000) \ + { \ + *entry_ptr = cluster; \ + _fx_utility_fat_entry_read_error_request = 0; \ + return(FX_SUCCESS); \ + } \ + if (_fx_utility_fat_entry_read_error_request == 40000) \ + { \ + media_ptr -> fx_media_total_clusters = 0; \ + _fx_utility_fat_entry_read_error_request = 0; \ + return(FX_SUCCESS); \ + } \ + } + +#define FX_UTILITY_FAT_ENTRY_WRITE_EXTENSION _fx_utility_fat_entry_write_count++; \ + if (_fx_utility_fat_entry_write_error_request) \ + { \ + _fx_utility_fat_entry_write_error_request--; \ + if (_fx_utility_fat_entry_write_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_UTILITY_LOGICAL_SECTOR_FLUSH_EXTENSION _fx_utility_logical_sector_flush_count++; \ + if (_fx_utility_logical_sector_flush_error_request) \ + { \ + _fx_utility_logical_sector_flush_error_request--; \ + if (_fx_utility_logical_sector_flush_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_UTILITY_LOGICAL_SECTOR_READ_EXTENSION _fx_utility_logical_sector_read_count++; \ + if (_fx_utility_logical_sector_read_error_request) \ + { \ + _fx_utility_logical_sector_read_error_request--; \ + if (_fx_utility_logical_sector_read_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_UTILITY_LOGICAL_SECTOR_READ_EXTENSION_1 _fx_utility_logical_sector_read_1_count++; \ + if (_fx_utility_logical_sector_read_1_error_request) \ + { \ + _fx_utility_logical_sector_read_1_error_request--; \ + if (_fx_utility_logical_sector_read_1_error_request == 0) \ + { \ + cache_entry = FX_NULL; \ + } \ + } + +#define FX_UTILITY_LOGICAL_SECTOR_WRITE_EXTENSION _fx_utility_logical_sector_write_count++; \ + if (_fx_utility_logical_sector_write_error_request) \ + { \ + _fx_utility_logical_sector_write_error_request--; \ + if (_fx_utility_logical_sector_write_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_FAULT_TOLERANT_ENABLE_EXTENSION fault_tolerant_enable_callback(media_ptr, media_ptr -> fx_media_fault_tolerant_memory_buffer, total_size); +#define FX_FAULT_TOLERANT_APPLY_LOGS_EXTENSION fault_tolerant_apply_log_callback(media_ptr, media_ptr -> fx_media_fault_tolerant_memory_buffer, size); + +#endif /* FX_REGRESSION_TEST */ + /* Define FileX internal protection macros. If FX_SINGLE_THREAD is defined, these protection macros are effectively disabled. However, for multi-thread uses, the macros are setup to utilize a ThreadX mutex for multiple thread access control into an open media. */ +/* Reduce the mutex error checking for testing purpose. */ + #if defined(FX_SINGLE_THREAD) || defined(FX_STANDALONE_ENABLE) #define FX_PROTECT #define FX_UNPROTECT #else -#define FX_PROTECT if (media_ptr -> fx_media_id != FX_MEDIA_ID) return(FX_MEDIA_NOT_OPEN); \ - else if (tx_mutex_get(&(media_ptr -> fx_media_protect), TX_WAIT_FOREVER) != TX_SUCCESS) return(FX_MEDIA_NOT_OPEN); +#define FX_PROTECT tx_mutex_get(&(media_ptr -> fx_media_protect), TX_WAIT_FOREVER); #define FX_UNPROTECT tx_mutex_put(&(media_ptr -> fx_media_protect)); #endif @@ -131,6 +293,9 @@ typedef unsigned long long ULONG64; #define FX_DISABLE_INTS old_interrupt_posture = tx_interrupt_control(TX_INT_DISABLE); #define FX_RESTORE_INTS tx_interrupt_control(old_interrupt_posture); #else +#ifndef FX_LEGACY_INTERRUPT_PROTECTION +#define FX_LEGACY_INTERRUPT_PROTECTION +#endif #define FX_INT_SAVE_AREA #define FX_DISABLE_INTS #define FX_RESTORE_INTS @@ -166,15 +331,15 @@ typedef unsigned long long ULONG64; #endif /* Define the update rate of the system timer. These values may also be defined at the command - line when compiling the fx_system_initialize.c module in the FileX library build. Alternatively, they can - be modified in this file or fx_user.h. Note: the update rate must be an even number of seconds greater - than or equal to 2, which is the minimal update rate for FAT time. */ + line when compiling the fx_system_initialize.c module in the FileX library build. Alternatively, + they can be modified in this file or fx_user.h. Note: the update rate must be an even number of + seconds greater than or equal to 2, which is the minimal update rate for FAT time. */ /* Define the number of seconds the timer parameters are updated in FileX. The default value is 10 seconds. This value can be overwritten externally. */ #ifndef FX_UPDATE_RATE_IN_SECONDS -#define FX_UPDATE_RATE_IN_SECONDS 10 +#define FX_UPDATE_RATE_IN_SECONDS 10 #endif @@ -196,11 +361,9 @@ typedef unsigned long long ULONG64; #ifdef FX_SYSTEM_INIT CHAR _fx_version_id[] = - "Copyright (c) 2024 Microsoft Corporation. * FileX Win32/Version 6.5.1.202602 *"; - * Copyright (c) 2026-present Eclipse ThreadX contributors + "Copyright (c) 2026 Eclipse ThreadX contributors. * FileX Win32/Visual Version 6.5.2.202603 *"; #else extern CHAR _fx_version_id[]; #endif -#endif - +#endif /* FX_PORT_H */ diff --git a/ports/win64/vs_2022/CMakeLists.txt b/ports/win64/vs_2022/CMakeLists.txt new file mode 100644 index 0000000..6bc1b86 --- /dev/null +++ b/ports/win64/vs_2022/CMakeLists.txt @@ -0,0 +1,10 @@ +target_sources(${PROJECT_NAME} PRIVATE + # {{BEGIN_TARGET_SOURCES}} + + # {{END_TARGET_SOURCES}} +) + +target_include_directories(${PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/inc +) diff --git a/ports/win64/vs_2022/inc/fx_port.h b/ports/win64/vs_2022/inc/fx_port.h new file mode 100644 index 0000000..504a7b5 --- /dev/null +++ b/ports/win64/vs_2022/inc/fx_port.h @@ -0,0 +1,368 @@ +/*************************************************************************** + * Copyright (C) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Copilot (Claude Sonnet 4.6). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** FileX Component */ +/** */ +/** Port Specific */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + + +/**************************************************************************/ +/* */ +/* PORT SPECIFIC C INFORMATION RELEASE */ +/* */ +/* fx_port.h Win64/Visual */ +/* 6.5.1.202602 */ +/* */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This file contains data type definitions that make the FileX FAT */ +/* compatible file system function identically on a variety of */ +/* different processor architectures. For example, the byte offset of */ +/* various entries in the boot record, and directory entries are */ +/* defined in this file. */ +/* */ +/**************************************************************************/ + +#ifndef FX_PORT_H +#define FX_PORT_H + + +/* Determine if the optional FileX user define file should be used. */ + +#ifdef FX_INCLUDE_USER_DEFINE_FILE + + +/* Yes, include the user defines in fx_user.h. The defines in this file may + alternately be defined on the command line. */ + +#include "fx_user.h" +#endif + +#include + + +/* Include the ThreadX api file. */ + +#ifndef FX_STANDALONE_ENABLE +#include "tx_api.h" + + +/* Define ULONG64 typedef, if not already defined. */ + +#ifndef ULONG64_DEFINED +#define ULONG64_DEFINED +typedef unsigned long long ULONG64; +#endif + +#else + +/* Define compiler library include files. */ + +#include +#include + +#ifndef VOID +#define VOID void +typedef char CHAR; +typedef char BOOL; +typedef unsigned char UCHAR; +typedef int INT; +typedef unsigned int UINT; +typedef long LONG; +typedef unsigned long ULONG; +typedef short SHORT; +typedef unsigned short USHORT; +#endif + +#ifndef ULONG64_DEFINED +#define ULONG64_DEFINED +typedef unsigned long long ULONG64; +#endif + + +/* Define basic alignment type used in block and byte pool operations. This data type must + be at least 32-bits in size and also be large enough to hold a pointer type. + On Win64, pointers are 64 bits so ULONG (32-bit on Windows LLP64) is insufficient. */ + +#ifndef ALIGN_TYPE_DEFINED +#define ALIGN_TYPE_DEFINED +#define ALIGN_TYPE ULONG64 +#endif + + +#endif /* FX_STANDALONE_ENABLE */ + + +#ifdef FX_REGRESSION_TEST + +/* Define parameters for regression test suite. */ + +#define FX_MAX_SECTOR_CACHE 256 +#define FX_MAX_FAT_CACHE 64 +#define FX_FAT_MAP_SIZE 1 + + +/* Define variables and macros used to introduce errors for the regression test suite. */ + +extern ULONG _fx_ram_driver_io_error_request; +extern ULONG _fx_ram_driver_io_request_count; +extern ULONG _fx_file_open_max_file_size_request; +extern ULONG _fx_directory_entry_read_count; +extern ULONG _fx_directory_entry_read_error_request; +extern ULONG _fx_directory_entry_write_count; +extern ULONG _fx_directory_entry_write_error_request; +extern ULONG _fx_utility_fat_entry_write_count; +extern ULONG _fx_utility_fat_entry_write_error_request; +extern ULONG _fx_utility_fat_entry_read_count; +extern ULONG _fx_utility_fat_entry_read_error_request; +extern ULONG _fx_utility_logical_sector_flush_count; +extern ULONG _fx_utility_logical_sector_flush_error_request; +extern ULONG _fx_utility_logical_sector_write_count; +extern ULONG _fx_utility_logical_sector_write_error_request; +extern ULONG _fx_utility_logical_sector_read_count; +extern ULONG _fx_utility_logical_sector_read_error_request; +extern ULONG _fx_utility_logical_sector_read_1_count; +extern ULONG _fx_utility_logical_sector_read_1_error_request; + +#ifdef FX_ENABLE_FAULT_TOLERANT +struct FX_MEDIA_STRUCT; +extern VOID fault_tolerant_enable_callback(struct FX_MEDIA_STRUCT *media_ptr, + UCHAR *fault_tolerant_memory_buffer, + ULONG log_size); +extern VOID fault_tolerant_apply_log_callback(struct FX_MEDIA_STRUCT *media_ptr, + UCHAR *fault_tolerant_memory_buffer, + ULONG log_size); +#endif /* FX_ENABLE_FAULT_TOLERANT */ + + +#define FX_DIRECTORY_ENTRY_READ_EXTENSION _fx_directory_entry_read_count++; \ + if (_fx_directory_entry_read_error_request) \ + { \ + _fx_directory_entry_read_error_request--; \ + if (_fx_directory_entry_read_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_DIRECTORY_ENTRY_WRITE_EXTENSION _fx_directory_entry_write_count++; \ + if (_fx_directory_entry_write_error_request) \ + { \ + _fx_directory_entry_write_error_request--; \ + if (_fx_directory_entry_write_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_UTILITY_FAT_ENTRY_READ_EXTENSION _fx_utility_fat_entry_read_count++; \ + if (_fx_utility_fat_entry_read_error_request) \ + { \ + _fx_utility_fat_entry_read_error_request--; \ + if (_fx_utility_fat_entry_read_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + if (_fx_utility_fat_entry_read_error_request == 10000) \ + { \ + *entry_ptr = 1; \ + _fx_utility_fat_entry_read_error_request = 0; \ + return(FX_SUCCESS); \ + } \ + if (_fx_utility_fat_entry_read_error_request == 20000) \ + { \ + *entry_ptr = media_ptr -> fx_media_fat_reserved; \ + _fx_utility_fat_entry_read_error_request = 0; \ + return(FX_SUCCESS); \ + } \ + if (_fx_utility_fat_entry_read_error_request == 30000) \ + { \ + *entry_ptr = cluster; \ + _fx_utility_fat_entry_read_error_request = 0; \ + return(FX_SUCCESS); \ + } \ + if (_fx_utility_fat_entry_read_error_request == 40000) \ + { \ + media_ptr -> fx_media_total_clusters = 0; \ + _fx_utility_fat_entry_read_error_request = 0; \ + return(FX_SUCCESS); \ + } \ + } + +#define FX_UTILITY_FAT_ENTRY_WRITE_EXTENSION _fx_utility_fat_entry_write_count++; \ + if (_fx_utility_fat_entry_write_error_request) \ + { \ + _fx_utility_fat_entry_write_error_request--; \ + if (_fx_utility_fat_entry_write_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_UTILITY_LOGICAL_SECTOR_FLUSH_EXTENSION _fx_utility_logical_sector_flush_count++; \ + if (_fx_utility_logical_sector_flush_error_request) \ + { \ + _fx_utility_logical_sector_flush_error_request--; \ + if (_fx_utility_logical_sector_flush_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_UTILITY_LOGICAL_SECTOR_READ_EXTENSION _fx_utility_logical_sector_read_count++; \ + if (_fx_utility_logical_sector_read_error_request) \ + { \ + _fx_utility_logical_sector_read_error_request--; \ + if (_fx_utility_logical_sector_read_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_UTILITY_LOGICAL_SECTOR_READ_EXTENSION_1 _fx_utility_logical_sector_read_1_count++; \ + if (_fx_utility_logical_sector_read_1_error_request) \ + { \ + _fx_utility_logical_sector_read_1_error_request--; \ + if (_fx_utility_logical_sector_read_1_error_request == 0) \ + { \ + cache_entry = FX_NULL; \ + } \ + } + +#define FX_UTILITY_LOGICAL_SECTOR_WRITE_EXTENSION _fx_utility_logical_sector_write_count++; \ + if (_fx_utility_logical_sector_write_error_request) \ + { \ + _fx_utility_logical_sector_write_error_request--; \ + if (_fx_utility_logical_sector_write_error_request == 0) \ + { \ + return(FX_IO_ERROR); \ + } \ + } + +#define FX_FAULT_TOLERANT_ENABLE_EXTENSION fault_tolerant_enable_callback(media_ptr, media_ptr -> fx_media_fault_tolerant_memory_buffer, total_size); +#define FX_FAULT_TOLERANT_APPLY_LOGS_EXTENSION fault_tolerant_apply_log_callback(media_ptr, media_ptr -> fx_media_fault_tolerant_memory_buffer, size); + +#endif /* FX_REGRESSION_TEST */ + + +/* Define FileX internal protection macros. If FX_SINGLE_THREAD is defined, + these protection macros are effectively disabled. However, for multi-thread + uses, the macros are setup to utilize a ThreadX mutex for multiple thread + access control into an open media. */ + +/* Reduce the mutex error checking for testing purpose. */ + +#if defined(FX_SINGLE_THREAD) || defined(FX_STANDALONE_ENABLE) +#define FX_PROTECT +#define FX_UNPROTECT +#else +#define FX_PROTECT tx_mutex_get(&(media_ptr -> fx_media_protect), TX_WAIT_FOREVER); +#define FX_UNPROTECT tx_mutex_put(&(media_ptr -> fx_media_protect)); +#endif + + +/* Define interrupt lockout constructs to protect the system date/time from being updated + while they are being read. */ + +#ifndef FX_STANDALONE_ENABLE +#define FX_INT_SAVE_AREA unsigned int old_interrupt_posture; +#define FX_DISABLE_INTS old_interrupt_posture = tx_interrupt_control(TX_INT_DISABLE); +#define FX_RESTORE_INTS tx_interrupt_control(old_interrupt_posture); +#else +#ifndef FX_LEGACY_INTERRUPT_PROTECTION +#define FX_LEGACY_INTERRUPT_PROTECTION +#endif +#define FX_INT_SAVE_AREA +#define FX_DISABLE_INTS +#define FX_RESTORE_INTS +#endif + +/* Define the error checking logic to determine if there is a caller error in the FileX API. + The default definitions assume ThreadX is being used. This code can be completely turned + off by just defining these macros to white space. */ + +#ifndef FX_STANDALONE_ENABLE +#ifndef TX_TIMER_PROCESS_IN_ISR + +#define FX_CALLER_CHECKING_EXTERNS extern TX_THREAD *_tx_thread_current_ptr; \ + extern TX_THREAD _tx_timer_thread; \ + extern volatile ULONG _tx_thread_system_state; + +#define FX_CALLER_CHECKING_CODE if ((_tx_thread_system_state) || \ + (_tx_thread_current_ptr == TX_NULL) || \ + (_tx_thread_current_ptr == &_tx_timer_thread)) \ + return(FX_CALLER_ERROR); + +#else +#define FX_CALLER_CHECKING_EXTERNS extern TX_THREAD *_tx_thread_current_ptr; \ + extern volatile ULONG _tx_thread_system_state; + +#define FX_CALLER_CHECKING_CODE if ((_tx_thread_system_state) || \ + (_tx_thread_current_ptr == TX_NULL)) \ + return(FX_CALLER_ERROR); +#endif +#else +#define FX_CALLER_CHECKING_EXTERNS +#define FX_CALLER_CHECKING_CODE +#endif + +/* Define the update rate of the system timer. These values may also be defined at the command + line when compiling the fx_system_initialize.c module in the FileX library build. Alternatively, + they can be modified in this file or fx_user.h. Note: the update rate must be an even number of + seconds greater than or equal to 2, which is the minimal update rate for FAT time. */ + +/* Define the number of seconds the timer parameters are updated in FileX. The default + value is 10 seconds. This value can be overwritten externally. */ + +#ifndef FX_UPDATE_RATE_IN_SECONDS +#define FX_UPDATE_RATE_IN_SECONDS 10 +#endif + + +/* Defines the number of ThreadX timer ticks required to achieve the update rate specified by + FX_UPDATE_RATE_IN_SECONDS defined previously. By default, the ThreadX timer tick is 10ms, + so the default value for this constant is 1000. If TX_TIMER_TICKS_PER_SECOND is defined, + this value is derived from TX_TIMER_TICKS_PER_SECOND. */ + +#ifndef FX_UPDATE_RATE_IN_TICKS +#if (defined(TX_TIMER_TICKS_PER_SECOND) && (!defined(FX_STANDALONE_ENABLE))) +#define FX_UPDATE_RATE_IN_TICKS (TX_TIMER_TICKS_PER_SECOND * FX_UPDATE_RATE_IN_SECONDS) +#else +#define FX_UPDATE_RATE_IN_TICKS 1000 +#endif +#endif + + +/* Define the version ID of FileX. This may be utilized by the application. */ + +#ifdef FX_SYSTEM_INIT +CHAR _fx_version_id[] = + "Copyright (c) 2026 Eclipse ThreadX contributors. * FileX Win64/Visual Version 6.5.2.202603 *"; +#else +extern CHAR _fx_version_id[]; +#endif + +#endif /* FX_PORT_H */ diff --git a/scripts/build.sh b/scripts/build.sh index 736b73f..a7f75e3 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -10,4 +10,4 @@ # SPDX-License-Identifier: MIT ############################################################################## -$(dirname `realpath $0`)/../test/cmake/run.sh build all +"$(dirname "$(realpath "$0")")/../test/cmake/run.sh" build all diff --git a/scripts/build_fx.ps1 b/scripts/build_fx.ps1 new file mode 100644 index 0000000..40f241e --- /dev/null +++ b/scripts/build_fx.ps1 @@ -0,0 +1,75 @@ +# Portions of this file were generated with AI assistance. + +[CmdletBinding()] +param( + [ValidateSet('win64', 'win32')] + [string]$Arch = 'win64', + + [AllowNull()] + [object]$Configuration = 'all', + + [int]$Parallel = [Math]::Max(1, [Environment]::ProcessorCount), + + [int]$BuildTimeoutSeconds = 120, + + [string]$BuildDir, + + [string]$ThreadXDir, + + [switch]$Clean +) + +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'fx_windows_common.ps1') + +$repoRoot = Split-Path -Parent $PSScriptRoot +$settings = Get-PortSettings -SelectedArch $Arch + +if (-not $BuildDir) { + $BuildDir = Join-Path $repoRoot "build\tests\$Arch" +} + +if (-not $ThreadXDir) { + $ThreadXDir = Join-Path (Split-Path -Parent $repoRoot) 'threadx' +} + +if (-not (Test-Path (Join-Path $ThreadXDir 'common\inc\tx_api.h'))) { + throw "No ThreadX source tree at $ThreadXDir. Clone eclipse-threadx/threadx beside this repository, or pass -ThreadXDir ." +} + +$selectedConfigurations = Resolve-RegressionConfigurations -RequestedConfigurations $Configuration +Write-Host "Selected configurations: $($selectedConfigurations -join ', ')" + +Enter-VisualStudioDevShell -VsArch $settings.VsArch + +foreach ($currentConfiguration in $selectedConfigurations) { + $currentBuildDirName = Get-RegressionBuildDirectoryName -ConfigurationName $currentConfiguration + $currentBuildDir = Join-Path $BuildDir $currentBuildDirName + + if ($Clean) { + Remove-BuildDirectory -Path $currentBuildDir -RepoRoot $repoRoot + } + + Remove-NinjaLock -Path $currentBuildDir + + Write-Host "Configuring $Arch / $currentConfiguration" + Invoke-NativeCommand -FilePath 'cmake' -Arguments @( + '-S', (Join-Path $repoRoot 'test\cmake'), + '-B', $currentBuildDir, + '-G', 'Ninja', + '-DCMAKE_C_COMPILER_FORCED=TRUE', + '-DCMAKE_C_COMPILER_WORKS=TRUE', + '-DCMAKE_C_ABI_COMPILED=TRUE', + # FileX's root CMakeLists.txt declares LANGUAGES C ASM; suppress the + # ASM compiler probe because the win64 port has no assembly sources. + '-DCMAKE_ASM_COMPILER_FORCED=TRUE', + '-DCMAKE_ASM_COMPILER_WORKS=TRUE', + "-DCMAKE_BUILD_TYPE=$currentConfiguration", + "-DTHREADX_ARCH=$($settings.FileXArch)", + "-DTHREADX_TOOLCHAIN=$($settings.FileXToolchain)", + "-DTHREADX_SOURCE_DIR=$ThreadXDir" + ) + + Write-Host "Building $Arch / $currentConfiguration" + Invoke-CMakeBuild -BuildDir $currentBuildDir -Parallel $Parallel -TimeoutSeconds $BuildTimeoutSeconds +} diff --git a/scripts/check_ai_disclosure.sh b/scripts/check_ai_disclosure.sh new file mode 100755 index 0000000..0a76976 --- /dev/null +++ b/scripts/check_ai_disclosure.sh @@ -0,0 +1,129 @@ +#!/bin/bash +############################################################################### +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################### +# +# Fail the build if a file's AI disclosure comment has drifted from the one +# accepted form. +# +# A file that was edited with AI assistance carries exactly one line: +# +# Portions of this file were generated with AI assistance. +# +# written with the comment character that file already uses. It names no +# product, no model and no version, and a file carries at most one of it, ever. +# +# The text is fixed for a reason that is easy to miss. An earlier convention +# named the product and the model -- "Some portions generated by +# ()" -- and the result was not attribution but accumulation: each tool +# that touched a file failed to recognise the line another tool had left, and +# appended its own. Files reached three stacked lines, and one product ended +# up spelled four different ways across the tree, which made the record +# unusable for the one question it was meant to answer. +# +# Precise attribution lives on the commit instead, where the Assisted-by +# trailer is dated and attached to the diff it describes: +# +# git log --format='%h %(trailers:key=Assisted-by,valueonly)' -- +# +# A file-level flag answers WHETHER; the history answers WHO. A header line +# cannot hold the second honestly, because the code it names gets rewritten +# and the line stays. +# +# The AI Disclosure paragraph in a new file's copyright header is different and +# is not checked here: it keeps its product and model version, because a file +# is created once and that record cannot grow. +# +# This script is excluded from its own scan. It has to spell the rejected +# forms in order to look for them. + +set -euo pipefail + +readonly ROOT="$(cd "$(dirname "$(realpath "$0")")/.." && pwd)" +readonly SELF='scripts/check_ai_disclosure.sh' +readonly FIXED='Portions of this file were generated with AI assistance.' + +cd "${ROOT}" + +# Tracked files only. A build tree is not this repository's text to police, +# and scanning one would make the check depend on whether somebody had built. +mapfile -d '' -t FILES < <(git ls-files -z | grep -zZv "^${SELF}$") + +status=0 + +report() { + printf '%s\n\n' "$1" >&2 + printf '%s\n\n' "$2" >&2 + status=1 +} + +# 1. The superseded per-edit form, which names a product and a model. +hits="$(grep -nI 'Some portions generated by' -- "${FILES[@]}" 2>/dev/null || true)" +if [ -n "${hits}" ]; then + report "AI disclosure check FAILED: superseded per-edit form. + +Replace each of these with the fixed line, keeping the file's comment +character: + + ${FIXED}" "${hits}" +fi + +# 2. A doubled comment marker, such as '; //' or '@ //'. Assembly dialects +# differ -- armasm and IAR use ';', GNU as uses '@' or '//' -- and writing +# both is a symptom of a tool guessing rather than reading the file. +hits="$(grep -nIE '(//|[;@#])[[:space:]]*//[[:space:]]*Portions of this file were generated' \ + -- "${FILES[@]}" 2>/dev/null || true)" +if [ -n "${hits}" ]; then + report "AI disclosure check FAILED: doubled comment marker. + +Use the single comment character the rest of the file uses." "${hits}" +fi + +# 3. More than one disclosure line in a file. This is the failure the fixed +# text exists to prevent, so it is worth catching directly rather than +# inferring it from the form. +hits="$(grep -cIF "${FIXED}" -- "${FILES[@]}" 2>/dev/null | awk -F: '$NF > 1' || true)" +if [ -n "${hits}" ]; then + report "AI disclosure check FAILED: more than one disclosure line. + +A file carries at most one, ever. Keep the first and delete the rest; the +commit trailer, not the header, records which agents have touched the file." "${hits}" +fi + +# 4. A near miss. A line that is clearly meant to be the disclosure but is +# not spelled exactly right defeats every deduplication that follows it. +# +# Markdown is exempt from this one check. The contribution guide has to +# quote the accepted text and explain when it applies, so matching the bare +# phrase there reports prose rather than drift. A superseded form in a +# Markdown file is still caught by check 1. +mapfile -d '' -t SOURCES < <(printf '%s\0' "${FILES[@]}" | grep -zZv '\.md$' || true) +hits='' +if [ "${#SOURCES[@]}" -gt 0 ]; then + hits="$(grep -nIF 'AI assistance' -- "${SOURCES[@]}" 2>/dev/null \ + | grep -vF "${FIXED}" || true)" +fi +if [ -n "${hits}" ]; then + report "AI disclosure check FAILED: the text is not spelled exactly. + +The accepted text, character for character, is: + + ${FIXED}" "${hits}" +fi + +if [ "${status}" -eq 0 ]; then + echo "AI disclosure check passed." +fi + +exit "${status}" diff --git a/scripts/fx_windows_common.ps1 b/scripts/fx_windows_common.ps1 new file mode 100644 index 0000000..b063438 --- /dev/null +++ b/scripts/fx_windows_common.ps1 @@ -0,0 +1,939 @@ +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' + +function Invoke-NativeCommand { + param( + [Parameter(Mandatory = $true)] + [string]$FilePath, + + [Parameter()] + [string[]]$Arguments = @() + ) + + & $FilePath @Arguments + if ($LASTEXITCODE -ne 0) { + throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $($Arguments -join ' ')" + } +} + +function Get-CommandPathIfAvailable { + param( + [Parameter(Mandatory = $true)] + [string]$CommandName + ) + + $command = Get-Command $CommandName -ErrorAction SilentlyContinue + if ($null -eq $command) { + return $null + } + + return $command.Source +} + +function Get-PortSettings { + param( + [Parameter(Mandatory = $true)] + [string]$SelectedArch + ) + + switch ($SelectedArch) { + 'win32' { + return @{ + FileXArch = 'win32' + FileXToolchain = 'vs_2019' + VsArch = 'x86' + } + } + 'win64' { + return @{ + FileXArch = 'win64' + FileXToolchain = 'vs_2022' + VsArch = 'amd64' + } + } + default { + throw "Unsupported architecture: $SelectedArch" + } + } +} + +function Get-RegressionConfigurations { + return @( + 'default_build_coverage', + 'no_cache_build', + 'no_cache_standalone_build', + 'fault_tolerant_build_coverage', + 'no_check_build', + 'no_cache_fault_tolerant_build', + 'standalone_build_coverage', + 'standalone_fault_tolerant_build_coverage', + 'standalone_no_cache_fault_tolerant_build' + ) +} + +function Resolve-RegressionConfigurations { + param( + [Parameter(Mandatory = $false)] + [AllowNull()] + [AllowEmptyCollection()] + [object]$RequestedConfigurations = 'all' + ) + + $allConfigurations = Get-RegressionConfigurations + $resolvedConfigurations = @() + + if ($null -eq $RequestedConfigurations) { + $resolvedConfigurations = @('all') + } + elseif ($RequestedConfigurations -is [System.Array]) { + foreach ($requestedConfiguration in $RequestedConfigurations) { + if ($null -ne $requestedConfiguration) { + $resolvedConfigurations += [string]$requestedConfiguration + } + } + } + else { + $resolvedConfigurations = @([string]$RequestedConfigurations) + } + + $normalizedConfigurations = @() + foreach ($requestedConfiguration in $resolvedConfigurations) { + foreach ($configurationPart in ($requestedConfiguration -split ',')) { + $trimmedConfiguration = $configurationPart.Trim() + if ($trimmedConfiguration.Length -gt 0) { + $normalizedConfigurations += $trimmedConfiguration + } + } + } + + if (($normalizedConfigurations.Count -eq 0) -or ($normalizedConfigurations -contains 'all')) { + return $allConfigurations + } + + foreach ($normalizedConfiguration in $normalizedConfigurations) { + if ($allConfigurations -notcontains $normalizedConfiguration) { + throw "Unsupported configuration: $normalizedConfiguration" + } + } + + return $normalizedConfigurations +} + +function Get-RegressionBuildDirectoryName { + param( + [Parameter(Mandatory = $true)] + [string]$ConfigurationName + ) + + switch ($ConfigurationName) { + 'default_build_coverage' { return 'dbc' } + 'no_cache_build' { return 'nc' } + 'no_cache_standalone_build' { return 'ncs' } + 'fault_tolerant_build_coverage' { return 'ft' } + 'no_check_build' { return 'nck' } + 'no_cache_fault_tolerant_build' { return 'ncft' } + 'standalone_build_coverage' { return 'sa' } + 'standalone_fault_tolerant_build_coverage' { return 'saft' } + 'standalone_no_cache_fault_tolerant_build' { return 'sancft' } + default { + throw "Unsupported configuration: $ConfigurationName" + } + } +} + +function Enter-VisualStudioDevShell { + param( + [Parameter(Mandatory = $true)] + [string]$VsArch + ) + + $targetArch = switch ($VsArch) { + 'amd64' { 'x64' } + 'x86' { 'x86' } + default { $VsArch } + } + + if ((Get-Command cl -ErrorAction SilentlyContinue) -and ($env:VSCMD_ARG_TGT_ARCH -eq $targetArch)) { + return + } + + $vsWherePath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' + if (-not (Test-Path -LiteralPath $vsWherePath)) { + throw "Unable to locate vswhere.exe at $vsWherePath" + } + + $installationPath = & $vsWherePath -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if (-not $installationPath) { + throw 'Unable to locate a Visual Studio 2022 installation with MSVC build tools.' + } + + $launchScript = Join-Path $installationPath 'Common7\Tools\Launch-VsDevShell.ps1' + if (-not (Test-Path -LiteralPath $launchScript)) { + throw "Unable to locate Launch-VsDevShell.ps1 at $launchScript" + } + + $env:VSCMD_SKIP_SENDTELEMETRY = '1' + & $launchScript -VsInstallationPath $installationPath -Arch $VsArch -HostArch amd64 -SkipAutomaticLocation | Out-Null + + if (-not (Get-Command cl -ErrorAction SilentlyContinue)) { + throw 'MSVC compiler environment was not activated successfully.' + } +} + +function Remove-BuildDirectory { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + + [Parameter(Mandatory = $true)] + [string]$RepoRoot + ) + + $fullRepoRoot = [System.IO.Path]::GetFullPath($RepoRoot) + $fullPath = [System.IO.Path]::GetFullPath($Path) + + if (-not $fullPath.StartsWith($fullRepoRoot, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "Refusing to remove a directory outside the repository: $fullPath" + } + + if (Test-Path -LiteralPath $fullPath) { + try { + Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction Stop + return + } catch { + Write-Warning "Failed to remove build directory '$fullPath': $($_.Exception.Message)" + } + + Get-ChildItem -LiteralPath $fullPath -Force -Recurse -ErrorAction SilentlyContinue | ForEach-Object { + try { + if (($_.Attributes -band [System.IO.FileAttributes]::ReadOnly) -ne 0) { + $_.Attributes = ($_.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly)) + } + } catch { + } + } + + try { + Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction Stop + } catch { + Write-Warning "Proceeding with partially cleaned build directory '$fullPath': $($_.Exception.Message)" + } + } +} + +function Remove-CtestTestingDirectory { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + if (-not (Test-Path -LiteralPath $Path)) { + return + } + + try { + Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop + return + } catch { + Write-Warning "Failed to remove CTest directory '$Path': $($_.Exception.Message)" + } + + Get-ChildItem -LiteralPath $Path -Force -Recurse -ErrorAction SilentlyContinue | ForEach-Object { + try { + if (($_.Attributes -band [System.IO.FileAttributes]::ReadOnly) -ne 0) { + $_.Attributes = ($_.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly)) + } + } catch { + } + } + + try { + Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop + } catch { + Write-Warning "Proceeding with partially cleaned CTest directory '$Path': $($_.Exception.Message)" + } +} + +function Remove-NinjaLock { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + $ninjaLockPath = Join-Path $Path '.ninja_lock' + if (Test-Path -LiteralPath $ninjaLockPath) { + Remove-Item -LiteralPath $ninjaLockPath -Force + } +} + +function Get-WindowsDebuggerPath { + $debuggerPath = Get-CommandPathIfAvailable -CommandName 'cdb.exe' + if ($debuggerPath) { + return $debuggerPath + } + + $candidatePaths = @( + (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Debuggers\x64\cdb.exe'), + (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Debuggers\x86\cdb.exe') + ) + + foreach ($candidatePath in $candidatePaths) { + if (Test-Path -LiteralPath $candidatePath) { + return $candidatePath + } + } + + return $null +} + +function Get-SanitizedFileName { + param( + [Parameter(Mandatory = $true)] + [string]$Name + ) + + $safeName = [regex]::Replace($Name, '[<>:"/\\|?*]', '_') + $safeName = $safeName -replace '\s+', '_' + return $safeName +} + +function Initialize-MinidumpSupport { + if ($null -ne ('FileX.WindowsMiniDump' -as [type])) { + return + } + + Add-Type -TypeDefinition @' +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace FileX +{ + public static class WindowsMiniDump + { + [DllImport("Dbghelp.dll", SetLastError = true)] + private static extern bool MiniDumpWriteDump( + IntPtr hProcess, + uint processId, + IntPtr hFile, + uint dumpType, + IntPtr exceptionParam, + IntPtr userStreamParam, + IntPtr callbackParam); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess(uint desiredAccess, bool inheritHandle, int processId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr handle); + + private const uint ProcessQueryInformation = 0x0400U; + private const uint ProcessVmRead = 0x0010U; + private const uint ProcessDupHandle = 0x0040U; + + public static bool WriteDump(int processId, string dumpPath, uint dumpType, out int errorCode) + { + IntPtr processHandle = OpenProcess(ProcessQueryInformation | ProcessVmRead | ProcessDupHandle, false, processId); + if (processHandle == IntPtr.Zero) + { + errorCode = Marshal.GetLastWin32Error(); + return false; + } + + try + { + using (FileStream dumpStream = new FileStream(dumpPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) + { + bool success = MiniDumpWriteDump( + processHandle, + unchecked((uint)processId), + dumpStream.SafeFileHandle.DangerousGetHandle(), + dumpType, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero); + + errorCode = success ? 0 : Marshal.GetLastWin32Error(); + return success; + } + } + finally + { + CloseHandle(processHandle); + } + } + } +} +'@ +} + +function Wait-FileReadable { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + + [Parameter()] + [int]$TimeoutSeconds = 10 + ) + + $deadline = (Get-Date).AddSeconds($TimeoutSeconds) + while ((Get-Date) -lt $deadline) { + if (-not (Test-Path -LiteralPath $Path)) { + Start-Sleep -Milliseconds 200 + continue + } + + try { + $fileStream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) + $fileStream.Dispose() + return $true + } + catch { + Start-Sleep -Milliseconds 200 + } + } + + return $false +} + +function Get-CtestTestMetadata { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir + ) + + $ctestOutput = & ctest --test-dir $BuildDir --show-only=json-v1 + if ($LASTEXITCODE -ne 0) { + throw "Unable to enumerate ctest metadata in $BuildDir" + } + + return (($ctestOutput -join [Environment]::NewLine) | ConvertFrom-Json).tests +} + +function Get-CtestFailedTestNames { + param( + [Parameter(Mandatory = $true)] + [string]$TestingTemporaryDir + ) + + $lastFailedPath = Join-Path $TestingTemporaryDir 'LastTestsFailed.log' + if (-not (Test-Path -LiteralPath $lastFailedPath)) { + return @() + } + + $failedTestNames = @() + foreach ($logLine in (Get-Content -LiteralPath $lastFailedPath)) { + if ([string]::IsNullOrWhiteSpace($logLine)) { + continue + } + + if ($logLine -match '^\s*\d+:(?.+)\s*$') { + $failedTestNames += $Matches['name'].Trim() + } + else { + $failedTestNames += $logLine.Trim() + } + } + + return $failedTestNames +} + +function Invoke-ProcessDumpCapture { + param( + [Parameter(Mandatory = $true)] + [int]$ProcessId, + + [Parameter(Mandatory = $true)] + [string]$DumpPath, + + [Parameter()] + [int]$TimeoutSeconds = 15 + ) + + $outputDirectory = Split-Path -Parent $DumpPath + if (-not (Test-Path -LiteralPath $outputDirectory)) { + New-Item -ItemType Directory -Path $outputDirectory | Out-Null + } + + Remove-Item -LiteralPath $DumpPath -Force -ErrorAction SilentlyContinue + + Initialize-MinidumpSupport + $dumpType = [uint32]0x00001006 + $errorCode = 0 + $dumpCaptured = [FileX.WindowsMiniDump]::WriteDump($ProcessId, $DumpPath, $dumpType, [ref]$errorCode) + + if (-not $dumpCaptured) { + Write-Warning "MiniDumpWriteDump failed for PID ${ProcessId} with Win32 error $errorCode" + return $false + } + + return (Test-Path -LiteralPath $DumpPath) +} + +function Invoke-DumpStackAnalysis { + param( + [Parameter(Mandatory = $true)] + [string]$DumpPath, + + [Parameter(Mandatory = $true)] + [string]$OutputBasePath, + + [Parameter()] + [string]$SymbolPath, + + [Parameter()] + [int]$TimeoutSeconds = 15 + ) + + if (-not (Test-Path -LiteralPath $DumpPath)) { + Write-Warning "Skipping dump analysis because the dump file was not created: $DumpPath" + return $false + } + + if (-not (Wait-FileReadable -Path $DumpPath)) { + Write-Warning "Skipping dump analysis because the dump file is not readable yet: $DumpPath" + return $false + } + + $debuggerPath = Get-WindowsDebuggerPath + if (-not $debuggerPath) { + Write-Warning 'Skipping dump analysis because cdb.exe is not available.' + return $false + } + + $outputDirectory = Split-Path -Parent $OutputBasePath + if (-not (Test-Path -LiteralPath $outputDirectory)) { + New-Item -ItemType Directory -Path $outputDirectory | Out-Null + } + + $stdoutPath = "${OutputBasePath}.stdout.txt" + $stderrPath = "${OutputBasePath}.stderr.txt" + $commandFilePath = "${OutputBasePath}.commands.txt" + Set-Content -LiteralPath $commandFilePath -Value @( + '!runaway 7' + '~* kb 200' + 'q' + ) -Encoding Ascii + $cdbArguments = @( + '-lines', + '-z', $DumpPath + ) + + if ($SymbolPath) { + $cdbArguments += @('-y', $SymbolPath) + } + + $cdbArguments += @('-cf', $commandFilePath) + $cdbProcess = Start-Process -FilePath $debuggerPath -ArgumentList $cdbArguments -PassThru -NoNewWindow ` + -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath + + try { + $cdbProcess | Wait-Process -Timeout $TimeoutSeconds -ErrorAction Stop + } + catch { + if (-not $cdbProcess.HasExited) { + $null = Start-Process -FilePath 'taskkill.exe' -ArgumentList @('/PID', $cdbProcess.Id.ToString(), '/T', '/F') ` + -WindowStyle Hidden -Wait -PassThru + } + } + + return $true +} + +function Invoke-ProcessWithTimeout { + param( + [Parameter(Mandatory = $true)] + [string]$FilePath, + + [Parameter()] + [string[]]$Arguments = @(), + + [Parameter()] + [int]$TimeoutSeconds = 0, + + [Parameter()] + [string]$WorkingDirectory, + + [Parameter()] + [string]$RedirectStandardOutputPath, + + [Parameter()] + [string]$RedirectStandardErrorPath, + + [Parameter()] + [scriptblock]$OnTimeout, + + [Parameter()] + [scriptblock]$PostTimeout + ) + + $argumentList = @() + foreach ($argument in $Arguments) { + if ($argument -match '\s|"') { + $argumentList += '"' + ($argument -replace '"', '\"') + '"' + } + else { + $argumentList += $argument + } + } + + $startProcessParameters = @{ + FilePath = $FilePath + NoNewWindow = $true + PassThru = $true + } + + if ($argumentList.Count -gt 0) { + $startProcessParameters['ArgumentList'] = $argumentList + } + + if ($WorkingDirectory) { + $startProcessParameters['WorkingDirectory'] = $WorkingDirectory + } + + if ($RedirectStandardOutputPath) { + $startProcessParameters['RedirectStandardOutput'] = $RedirectStandardOutputPath + } + + if ($RedirectStandardErrorPath) { + $startProcessParameters['RedirectStandardError'] = $RedirectStandardErrorPath + } + + $process = Start-Process @startProcessParameters + if ($TimeoutSeconds -le 0) { + $process | Wait-Process + $completed = $true + } + else { + try { + $process | Wait-Process -Timeout $TimeoutSeconds -ErrorAction Stop + $completed = $true + } + catch { + $completed = $false + } + } + + if (-not $completed) { + if ($null -ne $OnTimeout) { + & $OnTimeout $process + } + + $null = Start-Process -FilePath 'taskkill.exe' -ArgumentList @('/PID', $process.Id.ToString(), '/T', '/F') -WindowStyle Hidden -Wait -PassThru + + if ($null -ne $PostTimeout) { + & $PostTimeout $process + } + + return @{ + Completed = $false + ExitCode = $null + ProcessId = $process.Id + } + } + + $process.Refresh() + return @{ + Completed = $true + ExitCode = $process.ExitCode + ProcessId = $process.Id + } +} + +function Invoke-CtestFailureDiagnostics { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir, + + [Parameter(Mandatory = $true)] + [string]$TestingTemporaryDir, + + [Parameter(Mandatory = $true)] + [int]$TimeoutSeconds + ) + + $failedTestNames = Get-CtestFailedTestNames -TestingTemporaryDir $TestingTemporaryDir + if ($failedTestNames.Count -eq 0) { + Write-Warning "No failed tests were recorded in $TestingTemporaryDir" + return + } + + $testMetadataList = Get-CtestTestMetadata -BuildDir $BuildDir + $testMetadataMap = @{} + foreach ($testMetadata in $testMetadataList) { + $testMetadataMap[$testMetadata.name] = $testMetadata + } + + $diagnosticsRoot = Join-Path $TestingTemporaryDir 'FailureDiagnostics' + if (-not (Test-Path -LiteralPath $diagnosticsRoot)) { + New-Item -ItemType Directory -Path $diagnosticsRoot | Out-Null + } + + foreach ($failedTestName in $failedTestNames) { + if (-not $testMetadataMap.ContainsKey($failedTestName)) { + Write-Warning "Unable to locate ctest metadata for failed test: $failedTestName" + continue + } + + $testMetadata = $testMetadataMap[$failedTestName] + if (($null -eq $testMetadata.command) -or ($testMetadata.command.Count -eq 0)) { + Write-Warning "No executable command was recorded for failed test: $failedTestName" + continue + } + + $testArguments = @() + if ($testMetadata.command.Count -gt 1) { + $testArguments = @($testMetadata.command[1..($testMetadata.command.Count - 1)]) + } + + $safeTestName = Get-SanitizedFileName -Name $failedTestName + $stdoutPath = Join-Path $diagnosticsRoot "${safeTestName}.stdout.txt" + $stderrPath = Join-Path $diagnosticsRoot "${safeTestName}.stderr.txt" + $debugOutputBasePath = Join-Path $diagnosticsRoot "${safeTestName}.cdb" + $workingDirectory = $null + $symbolDirectory = Split-Path -Parent $testMetadata.command[0] + + if ($null -ne $testMetadata.properties) { + foreach ($testProperty in $testMetadata.properties) { + if ($testProperty.name -eq 'WORKING_DIRECTORY') { + $workingDirectory = $testProperty.value + break + } + } + } + + Write-Warning "Collecting failure diagnostics for $failedTestName" + $dumpPath = '{0}.{1}.dmp' -f $debugOutputBasePath, ([DateTime]::UtcNow.ToString('yyyyMMddHHmmssfff')) + $testResult = Invoke-ProcessWithTimeout -FilePath $testMetadata.command[0] -Arguments $testArguments ` + -TimeoutSeconds $TimeoutSeconds -WorkingDirectory $workingDirectory -RedirectStandardOutputPath $stdoutPath ` + -RedirectStandardErrorPath $stderrPath -OnTimeout { + param($timedOutProcess) + Invoke-ProcessDumpCapture -ProcessId $timedOutProcess.Id -DumpPath $dumpPath | Out-Null + } -PostTimeout { + param($timedOutProcess) + if (Test-Path -LiteralPath $dumpPath) { + Invoke-DumpStackAnalysis -DumpPath $dumpPath -OutputBasePath $debugOutputBasePath -SymbolPath $symbolDirectory | Out-Null + } + } + + if (-not $testResult.Completed) { + Write-Warning "Timeout diagnostics were captured for $failedTestName under $diagnosticsRoot" + continue + } + + Write-Warning "Replay finished for $failedTestName with exit code $($testResult.ExitCode). Output was saved under $diagnosticsRoot" + } +} + +function Test-IsNinjaBuildDirectory { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir + ) + + return (Test-Path -LiteralPath (Join-Path $BuildDir 'build.ninja')) +} + +function Get-NinjaBuildStatements { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir + ) + + $buildNinjaPath = Join-Path $BuildDir 'build.ninja' + if (-not (Test-Path -LiteralPath $buildNinjaPath)) { + throw "Unable to locate build.ninja in $BuildDir" + } + + return Get-Content -LiteralPath $buildNinjaPath +} + +function New-NinjaRspFile { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir, + + [Parameter(Mandatory = $true)] + [string]$RspRelativePath + ) + + $buildStatements = Get-NinjaBuildStatements -BuildDir $BuildDir + $rspLine = ' RSP_FILE = ' + $RspRelativePath + $rspIndex = -1 + + for ($index = 0; $index -lt $buildStatements.Count; $index++) { + if ($buildStatements[$index] -eq $rspLine) { + $rspIndex = $index + break + } + } + + if ($rspIndex -lt 0) { + throw "Unable to locate RSP_FILE entry for $RspRelativePath in build.ninja." + } + + $buildIndex = -1 + for ($index = $rspIndex; $index -ge 0; $index--) { + if ($buildStatements[$index].StartsWith('build ')) { + $buildIndex = $index + break + } + } + + if ($buildIndex -lt 0) { + throw "Unable to locate the build statement that owns $RspRelativePath." + } + + $buildLine = $buildStatements[$buildIndex] + if ($buildLine -notmatch '^build\s+\S+:\s+\S+\s+(.+)$') { + throw "Unable to parse build statement for $RspRelativePath." + } + + $rspContents = ($Matches[1] -split '\s+') -join [Environment]::NewLine + $rspPath = Join-Path $BuildDir $RspRelativePath + $rspParent = Split-Path -Parent $rspPath + + if (-not (Test-Path -LiteralPath $rspParent)) { + New-Item -ItemType Directory -Path $rspParent | Out-Null + } + + Set-Content -LiteralPath $rspPath -Value $rspContents +} + +function Ensure-NinjaRspFiles { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir, + + [Parameter(Mandatory = $true)] + [string]$CommandLine + ) + + $rspMatches = [regex]::Matches($CommandLine, '@(?[^\s"]+\.rsp)') + foreach ($rspMatch in $rspMatches) { + $rspRelativePath = $rspMatch.Groups['path'].Value + $rspPath = Join-Path $BuildDir $rspRelativePath + if (-not (Test-Path -LiteralPath $rspPath)) { + New-NinjaRspFile -BuildDir $BuildDir -RspRelativePath $rspRelativePath + } + } +} + +function Get-PendingNinjaCommands { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir + ) + + $commandLines = & ninja -C $BuildDir -t commands + if ($LASTEXITCODE -ne 0) { + throw "Unable to enumerate pending Ninja commands in $BuildDir" + } + + return $commandLines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } +} + +function Invoke-NinjaFallbackBuild { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir + ) + + $pendingCommands = Get-PendingNinjaCommands -BuildDir $BuildDir + if ($pendingCommands.Count -eq 0) { + return + } + + Push-Location $BuildDir + try { + foreach ($pendingCommand in $pendingCommands) { + Ensure-NinjaRspFiles -BuildDir $BuildDir -CommandLine $pendingCommand + + $commandToRun = $pendingCommand -replace '\s/showIncludes(?=\s|$)', '' + + if ($commandToRun -match '^[^ ]*cmd(?:\.exe)?\s+/C\s+"(?.*)"\s*$') { + & cmd.exe /C $Matches['inner'] + } + else { + & cmd.exe /C $commandToRun + } + + if ($LASTEXITCODE -ne 0) { + throw "Fallback Ninja command failed with exit code ${LASTEXITCODE}: $pendingCommand" + } + } + } + finally { + Pop-Location + } +} + +function Invoke-CMakeBuild { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir, + + [Parameter(Mandatory = $true)] + [int]$Parallel, + + [Parameter()] + [int]$TimeoutSeconds = 0 + ) + + Remove-NinjaLock -Path $BuildDir + $isNinjaBuild = Test-IsNinjaBuildDirectory -BuildDir $BuildDir + + if ($TimeoutSeconds -le 0) { + if ($isNinjaBuild) { + Invoke-NativeCommand -FilePath 'ninja' -Arguments @( + '-C', $BuildDir, + '-j', $Parallel.ToString() + ) + } + else { + Invoke-NativeCommand -FilePath 'cmake' -Arguments @( + '--build', $BuildDir, + '--parallel', $Parallel.ToString() + ) + } + return + } + + if ($isNinjaBuild) { + $buildToolName = 'Ninja' + $buildResult = Invoke-ProcessWithTimeout -FilePath 'ninja' -Arguments @( + '-C', $BuildDir, + '-j', $Parallel.ToString() + ) -TimeoutSeconds $TimeoutSeconds + } + else { + $buildToolName = 'CMake' + $buildResult = Invoke-ProcessWithTimeout -FilePath 'cmake' -Arguments @( + '--build', $BuildDir, + '--parallel', $Parallel.ToString() + ) -TimeoutSeconds $TimeoutSeconds + } + + if ($buildResult.Completed -and ($buildResult.ExitCode -eq 0)) { + return + } + + if (-not $isNinjaBuild) { + if (-not $buildResult.Completed) { + throw "$buildToolName build timed out after $TimeoutSeconds seconds in $BuildDir" + } + + throw "$buildToolName build failed with exit code $($buildResult.ExitCode) in $BuildDir" + } + + if ($buildResult.Completed) { + throw "$buildToolName build failed with exit code $($buildResult.ExitCode) in $BuildDir" + } + + Write-Warning "$buildToolName build timed out after $TimeoutSeconds seconds in $BuildDir. Replaying pending Ninja commands from PowerShell." + + Remove-NinjaLock -Path $BuildDir + Invoke-NinjaFallbackBuild -BuildDir $BuildDir +} diff --git a/scripts/install.sh b/scripts/install.sh index bcf4a0c..a5ddcb9 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -10,30 +10,48 @@ # SPDX-License-Identifier: MIT ############################################################################## -# +set -euo pipefail + +retry() { + local attempt + for attempt in 1 2 3; do + if "$@"; then + return 0 + fi + if [ "$attempt" -lt 3 ]; then + sleep $((attempt * 5)) + fi + done + return 1 +} -# Remove large folder -rm -rf /opt/hostedtoolcache +apt_options=(-o Acquire::Retries=3 -o DPkg::Lock::Timeout=60) +if ! retry sudo timeout 150 apt-get "${apt_options[@]}" update; then + echo "Package index update failed; package installation will verify availability." >&2 +fi +retry sudo timeout 150 apt-get "${apt_options[@]}" install -y \ + cmake gcc-14 gcc-14-multilib git ninja-build python3-venv \ + unifdef p7zip-full tofrodos gawk -# Install necessary softwares for Ubuntu. +venv_dir="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/filex-ci-venv" +python3 -m venv "$venv_dir" +retry timeout 120 "$venv_dir/bin/python" -m pip install \ + --retries 3 --timeout 30 gcovr==8.6 -sudo apt-get update -sudo apt-get install -y \ - gcc-multilib \ - git \ - g++ \ - python3-pip \ - ninja-build \ - unifdef \ - p7zip-full \ - tofrodos \ - gawk \ - software-properties-common +cc="${CC:-gcc-14}" +gcov="${GCOV:-gcov-14}" +cc_version=$("$cc" -dumpfullversion) +gcov_version=$("$gcov" --version | sed -n '1{s/.* \([0-9][0-9]*\.[0-9][0-9]*\(\.[0-9][0-9]*\)\?\).*/\1/p;}') +if [ -z "$gcov_version" ] || [ "$cc_version" != "$gcov_version" ]; then + echo "Compiler $cc and coverage tool $gcov have different versions." >&2 + exit 1 +fi -wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add - -CODENAME=$(lsb_release -c | cut -f2 -d':' | sed 's/\t//') -apt-add-repository "deb https://apt.kitware.com/ubuntu/ $CODENAME main" +if [ -n "${GITHUB_ENV:-}" ]; then + printf 'CC=%s\nGCOV=%s\n' "$cc" "$gcov" >> "$GITHUB_ENV" + printf '%s\n' "$venv_dir/bin" >> "$GITHUB_PATH" +fi -python3 -m pip install --upgrade pip -pip3 install gcovr==4.1 -pip install --upgrade cmake \ No newline at end of file +"$venv_dir/bin/gcovr" --version | head -1 +"$cc" --version | head -1 +"$gcov" --version | head -1 diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index f9b60c5..2681940 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -15,6 +15,7 @@ # # Copyright (C) 2026 Eclipse ThreadX contributors # SPDX-License-Identifier: MIT +# Portions of this file were generated with AI assistance. set -eu @@ -120,8 +121,6 @@ sed -i -E "s|(#define FILEX_HOTFIX_VERSION[[:space:]]+)'[^']*'|\1${HOTFIX_DEFINE git -C "${REPO_ROOT}" add "${API_HEADER}" git -C "${REPO_ROOT}" commit -F - <<'COMMIT_EOF' Updated version number constants - -Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> COMMIT_EOF printf "Committed version constant updates.\n" @@ -146,23 +145,48 @@ PORT_FILES=$(printf "%s" "${PORT_FILES}" | grep -v '^[[:space:]]*$' || true) if [ -z "${PORT_FILES}" ]; then printf "Warning: No port header files found. Skipping port version string commit.\n" else + # A port advertises its release in the string the library reports at run time. + # The pattern accepts three or four dotted numbers and tolerates a stray + # letter before the first one, so a port that writes its version slightly + # differently is still reached. + VERSION_RE='Version[[:space:]]+[A-Za-z]?[0-9]+(\.[0-9]+)+[a-z]*' while IFS= read -r port_file; do - if [ -n "${port_file}" ] && grep -qE "Version [0-9]" "${port_file}" 2>/dev/null; then - sed -i -E "s/Version [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[a-z]*/Version ${VERSION}/g" "${port_file}" + if [ -n "${port_file}" ] && grep -qE "${VERSION_RE}" "${port_file}" 2>/dev/null; then + sed -i -E "s/${VERSION_RE}/Version ${VERSION}/g" "${port_file}" printf " Updated: %s\n" "${port_file#${REPO_ROOT}/}" fi done </dev/null || true) + [ -n "${advertised}" ] || continue + if ! printf "%s" "${advertised}" | grep -qF "${VERSION}"; then + STALE="${STALE} ${port_file#${REPO_ROOT}/} +" + fi + done <&2 + printf "Correct them so the substitution above reaches them, then re-run.\n" >&2 + exit 1 + fi + git -C "${REPO_ROOT}" add -u if git -C "${REPO_ROOT}" diff --cached --quiet; then printf "No port version string changes staged. Skipping commit.\n" else git -C "${REPO_ROOT}" commit -F - <<'COMMIT_EOF' Updated port version strings - -Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> COMMIT_EOF printf "Committed port version string updates.\n" fi diff --git a/scripts/test.sh b/scripts/test.sh index 49fe8a3..cfe112d 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -10,4 +10,6 @@ # SPDX-License-Identifier: MIT ############################################################################## -$(dirname `realpath $0`)/../test/cmake/run.sh test all +export CTEST_PARALLEL_LEVEL="${CTEST_PARALLEL_LEVEL:-1}" +export CTEST_REPEAT_FAIL="${CTEST_REPEAT_FAIL:-1}" +"$(dirname "$(realpath "$0")")/../test/cmake/run.sh" test all diff --git a/scripts/test_fx.ps1 b/scripts/test_fx.ps1 new file mode 100644 index 0000000..f60fdae --- /dev/null +++ b/scripts/test_fx.ps1 @@ -0,0 +1,121 @@ +[CmdletBinding()] +param( + [ValidateSet('win64', 'win32')] + [string]$Arch = 'win64', + + [AllowNull()] + [object]$Configuration = 'all', + + [int]$Parallel = [Math]::Max(1, [Environment]::ProcessorCount), + + [int]$RepeatFailCount = 1, + + [int]$TestTimeoutSeconds = 60, + + [switch]$CollectFailureDiagnostics = $true, + + [string]$TestRegex, + + [switch]$RerunFailedOnly, + + [string]$BuildDir, + + [switch]$Clean +) + +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'fx_windows_common.ps1') + +$repoRoot = Split-Path -Parent $PSScriptRoot +$settings = Get-PortSettings -SelectedArch $Arch + +if (-not $BuildDir) { + $BuildDir = Join-Path $repoRoot "build\tests\$Arch" +} + +$selectedConfigurations = Resolve-RegressionConfigurations -RequestedConfigurations $Configuration +Write-Host "Selected configurations: $($selectedConfigurations -join ', ')" + +Enter-VisualStudioDevShell -VsArch $settings.VsArch + +if (($settings.FileXArch -eq 'win32') -or ($settings.FileXArch -eq 'win64')) { + if ($Parallel -ne 1) { + Write-Warning "Windows simulator regression tests are timing-sensitive under concurrent ctest execution. Forcing -Parallel 1." + $Parallel = 1 + } +} + +$failedConfigurations = @() + +foreach ($currentConfiguration in $selectedConfigurations) { + $currentBuildDirName = Get-RegressionBuildDirectoryName -ConfigurationName $currentConfiguration + $currentBuildDir = Join-Path $BuildDir $currentBuildDirName + $currentTestingTemporaryDir = Join-Path $currentBuildDir 'Testing\Temporary' + + try { + if ($Clean) { + $currentTestingDir = Join-Path $currentBuildDir 'Testing' + Remove-CtestTestingDirectory -Path $currentTestingDir + } + + if (-not (Test-Path -LiteralPath $currentBuildDir)) { + throw "Build directory does not exist for $Arch / ${currentConfiguration}: $currentBuildDir" + } + + Remove-NinjaLock -Path $currentBuildDir + if (Test-Path -LiteralPath $currentTestingTemporaryDir) { + Remove-Item -LiteralPath (Join-Path $currentTestingTemporaryDir 'LastTest.log') -Force -ErrorAction SilentlyContinue + Remove-Item -LiteralPath (Join-Path $currentTestingTemporaryDir 'LastTestsFailed.log') -Force -ErrorAction SilentlyContinue + } + + Write-Host "Testing $Arch / $currentConfiguration" + $ctestArguments = @( + '--test-dir', $currentBuildDir, + '--output-on-failure', + '--timeout', $TestTimeoutSeconds.ToString(), + '-j', $Parallel.ToString() + ) + + if ($RepeatFailCount -gt 1) { + $ctestArguments += @('--repeat', "until-pass:$RepeatFailCount") + } + + if ($TestRegex) { + $ctestArguments += @('-R', $TestRegex) + } + + if ($RerunFailedOnly) { + $ctestArguments += '--rerun-failed' + } + + Invoke-NativeCommand -FilePath 'ctest' -Arguments $ctestArguments + } + catch { + if ($CollectFailureDiagnostics -and (Test-Path -LiteralPath $currentBuildDir)) { + try { + Invoke-CtestFailureDiagnostics -BuildDir $currentBuildDir -TestingTemporaryDir $currentTestingTemporaryDir ` + -TimeoutSeconds $TestTimeoutSeconds + } + catch { + Write-Warning "Failure diagnostics collection failed for ${currentConfiguration}: $($_.Exception.Message)" + } + } + + $failedConfigurations += @{ + Configuration = $currentConfiguration + Message = $_.Exception.Message + } + + Write-Warning "Configuration failed: $currentConfiguration" + } +} + +if ($failedConfigurations.Count -gt 0) { + Write-Host '' + Write-Host 'Configuration failure summary:' + foreach ($failedConfiguration in $failedConfigurations) { + Write-Host "- $($failedConfiguration.Configuration): $($failedConfiguration.Message)" + } + + throw "One or more configurations failed: $($failedConfigurations.Configuration -join ', ')" +} diff --git a/test/cmake/CMakeLists.txt b/test/cmake/CMakeLists.txt index a81cc17..6b8bf86 100644 --- a/test/cmake/CMakeLists.txt +++ b/test/cmake/CMakeLists.txt @@ -1,14 +1,28 @@ +# Portions of this file were generated with AI assistance. + cmake_minimum_required(VERSION 3.13 FATAL_ERROR) cmake_policy(SET CMP0054 NEW) cmake_policy(SET CMP0057 NEW) -project(threadx_test LANGUAGES C) +# Suppress try_compile link step when MSVC cannot produce a runnable executable +# (e.g. when the VS environment is not fully initialised for a given target arch). +if((DEFINED THREADX_ARCH) AND ((THREADX_ARCH STREQUAL "win32") OR (THREADX_ARCH STREQUAL "win64"))) + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +endif() + +project(filex_test LANGUAGES C) + +option(FX_COVERAGE "Instrument FileX in every Linux test configuration" OFF) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) # Set build configurations set(BUILD_CONFIGURATIONS default_build_coverage no_cache_build no_cache_standalone_build fault_tolerant_build_coverage no_check_build no_cache_fault_tolerant_build - standalone_build_coverage standalone_fault_tolerant_build_coverage + standalone_build_coverage standalone_fault_tolerant_build_coverage standalone_no_cache_fault_tolerant_build) set(CMAKE_CONFIGURATION_TYPES ${BUILD_CONFIGURATIONS} @@ -31,25 +45,38 @@ set(FX_FAULT_TOLERANT_DEFINITIONS set(default_build_coverage "") set(no_cache_build -DFX_DISABLE_CACHE -DFX_DISABLE_FAT_ENTRY_REFRESH) set(fault_tolerant_build_coverage ${FX_FAULT_TOLERANT_DEFINITIONS}) -set(standalone_build_coverage -DFX_STANDALONE_ENABLE) +set(standalone_build_coverage -DFX_STANDALONE_ENABLE) set(standalone_fault_tolerant_build_coverage ${FX_FAULT_TOLERANT_DEFINITIONS} - -DFX_STANDALONE_ENABLE) + -DFX_STANDALONE_ENABLE) set(no_cache_standalone_build -DFX_DISABLE_CACHE -DFX_STANDALONE_ENABLE) -set(no_check_build ${FX_COMPILE_DEFINITIONS} -DFX_DISABLE_ERROR_CHECKING) +set(no_check_build -DFX_DISABLE_ERROR_CHECKING) set(no_cache_fault_tolerant_build ${no_cache_build} ${FX_FAULT_TOLERANT_DEFINITIONS}) set(standalone_no_cache_fault_tolerant_build ${no_cache_build} ${FX_FAULT_TOLERANT_DEFINITIONS} -DFX_STANDALONE_ENABLE) -add_compile_options( - -m32 - -std=c99 - -ggdb - -g3 - -gdwarf-2 - -fdiagnostics-color - -Werror - -DFX_REGRESSION_TEST - ${${CMAKE_BUILD_TYPE}}) -add_link_options(-m32) +if(MSVC) + add_compile_options(/W3 /Zi) + # /LARGEADDRESSAWARE lets the 32-bit (win32) test executables use the full + # 4 GB user address space available to a 32-bit process on 64-bit Windows, + # instead of the default 2 GB. The large-media regression tests allocate + # ~1.7 GB of RAM-disk + data buffers (e.g. filex_file_allocate_test), which + # overflows the 2 GB limit and makes calloc return NULL -> crash. This flag + # is the default for x64, so it is a no-op for the win64 build. + add_link_options(/DEBUG /INCREMENTAL:NO /LARGEADDRESSAWARE) + # Both cl.exe and gcc accept -DFOO; use it to keep the per-configuration + # definitions consistent with the Linux path. + add_compile_options(-DFX_REGRESSION_TEST ${${CMAKE_BUILD_TYPE}}) +else() + add_compile_options( + -m32 + -ggdb + -g3 + -gdwarf-2 + -fdiagnostics-color + -Werror + -DFX_REGRESSION_TEST + ${${CMAKE_BUILD_TYPE}}) + add_link_options(-m32) +endif() if(CMAKE_BUILD_TYPE MATCHES ".*standalone.*") set(FX_STANDALONE_ENABLE @@ -59,19 +86,35 @@ endif() enable_testing() +# ThreadX dependency: on Windows build from source; on Linux use run.sh. +if(MSVC) + if(NOT FX_STANDALONE_ENABLE) + if(NOT DEFINED THREADX_SOURCE_DIR) + message(FATAL_ERROR + "THREADX_SOURCE_DIR must point to a ThreadX source tree for non-standalone Windows builds. " + "Pass -DTHREADX_SOURCE_DIR= to cmake, or use build_fx.ps1 which sets it automatically.") + endif() + add_subdirectory(${THREADX_SOURCE_DIR} threadx_build) + endif() +endif() + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../.. filex) add_subdirectory(regression) add_subdirectory(samples) -# Coverage -if(CMAKE_BUILD_TYPE MATCHES ".*_coverage") +# TX_COVERAGE is supplied by the ThreadX regression runner. +if(NOT MSVC AND (FX_COVERAGE OR TX_COVERAGE OR CMAKE_BUILD_TYPE MATCHES ".*_coverage")) target_compile_options(filex PRIVATE -fprofile-arcs -ftest-coverage) target_link_options(filex PRIVATE -fprofile-arcs -ftest-coverage) endif() -# Build ThreadX library once -if(NOT FX_STANDALONE_ENABLE) - execute_process(COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs) +# Build ThreadX shared library for Linux +if(NOT MSVC AND NOT FX_STANDALONE_ENABLE) + execute_process(COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs + RESULT_VARIABLE threadx_build_result) + if(NOT threadx_build_result STREQUAL "0") + message(FATAL_ERROR "ThreadX dependency build failed: ${threadx_build_result}") + endif() add_custom_target(build_libs ALL COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs) add_dependencies(filex build_libs) @@ -83,23 +126,26 @@ if(NOT FX_STANDALONE_ENABLE) ${CMAKE_BINARY_DIR}/../libs/threadx/libthreadx.so) endif() -target_compile_options( - filex - PRIVATE -Werror - -Wall - -Wextra - -pedantic - -fmessage-length=0 - -fsigned-char - -ffunction-sections - -fdata-sections - -Wunused - -Wuninitialized - -Wmissing-declarations - -Wconversion - -Wpointer-arith - -Wshadow - -Wlogical-op - -Waggregate-return - -Wfloat-equal) - +if(MSVC) + target_compile_options(filex PRIVATE /W3) +else() + target_compile_options( + filex + PRIVATE -Werror + -Wall + -Wextra + -pedantic + -fmessage-length=0 + -fsigned-char + -ffunction-sections + -fdata-sections + -Wunused + -Wuninitialized + -Wmissing-declarations + -Wconversion + -Wpointer-arith + -Wshadow + -Wlogical-op + -Waggregate-return + -Wfloat-equal) +endif() diff --git a/test/cmake/coverage.sh b/test/cmake/coverage.sh index ce81e77..07f9ab2 100755 --- a/test/cmake/coverage.sh +++ b/test/cmake/coverage.sh @@ -10,10 +10,139 @@ # SPDX-License-Identifier: MIT ############################################################################## +set -euo pipefail -set -e +cd "$(dirname "$0")" +repo_root=$(cd ../.. && pwd) +report_dir=coverage_report +configurations=( + default_build_coverage no_cache_build no_cache_standalone_build + fault_tolerant_build_coverage no_check_build no_cache_fault_tolerant_build + standalone_build_coverage standalone_fault_tolerant_build_coverage + standalone_no_cache_fault_tolerant_build +) -cd $(dirname $0) -mkdir -p coverage_report/$1 -gcovr --object-directory=build/$1/filex/CMakeFiles/filex.dir/common/src -r ../../common/src -e ".*driver.*" --xml-pretty --output coverage_report/$1.xml -gcovr --object-directory=build/$1/filex/CMakeFiles/filex.dir/common/src -r ../../common/src -e ".*driver.*" --html --html-details --output coverage_report/$1/index.html +check_report() { + python3 - "$@" <<'PY' +import json +import pathlib +import sys +import xml.etree.ElementTree as ET + +report = pathlib.Path(sys.argv[1]) +root = ET.parse(report).getroot() +classes = root.findall('.//class') +if int(root.get('lines-valid', '0')) == 0 or not classes: + raise SystemExit(f'{report}: report contains no measured FileX files') +if any(not item.get('filename', '').startswith('common/src/') for item in classes): + raise SystemExit(f'{report}: report contains a source outside common/src') +if len(sys.argv) > 2: + with open(sys.argv[2], encoding='utf-8') as stream: + data = json.load(stream) + if not data.get('files'): + raise SystemExit(f'{sys.argv[2]}: tracefile contains no measured files') + if any(not item.get('file', '').startswith('common/src/') for item in data['files']): + raise SystemExit(f'{sys.argv[2]}: tracefile contains a source outside common/src') +print(f"{report}: lines {root.get('lines-covered')}/{root.get('lines-valid')} " + f"({float(root.get('line-rate', '0')) * 100:.2f}%), branches " + f"{root.get('branches-covered')}/{root.get('branches-valid')} " + f"({float(root.get('branch-rate', '0')) * 100:.2f}%)") +PY +} + +check_merged_floor() { + python3 - "$1" <<'PY' +import sys +import xml.etree.ElementTree as ET + +root = ET.parse(sys.argv[1]).getroot() +for label, attribute, minimum in ( + ('line', 'lines', 999), + ('branch', 'branches', 993), +): + covered = int(root.get(f'{attribute}-covered', '0')) + valid = int(root.get(f'{attribute}-valid', '0')) + if valid == 0 or covered * 1000 < valid * minimum: + raise SystemExit( + f'{sys.argv[1]}: {label} coverage {covered}/{valid} ' + f'is below {minimum / 10:.1f}%' + ) +PY +} + +if [ "${1:-}" = --clean ]; then + if [ -d "$report_dir" ]; then + rm -r -- "$report_dir" + fi + if [ -d build ]; then + find build -type f -name '*.gcda' -delete + fi + exit 0 +fi + +if [ "${1:-}" = --merge ]; then + trace_args=() + for configuration in "${configurations[@]}"; do + base="$report_dir/per_configuration/$configuration" + for path in "$base.json" "$base.xml" "$base/index.html"; do + if [ ! -s "$path" ]; then + echo "Missing or empty coverage report: $path" >&2 + exit 1 + fi + done + check_report "$base.xml" "$base.json" + trace_args+=(--add-tracefile "$base.json") + done + + mkdir -p "$report_dir/merged" + gcovr -r "$repo_root" "${trace_args[@]}" --xml-pretty \ + --output "$report_dir/merged.xml" + gcovr -r "$repo_root" "${trace_args[@]}" --html --html-details \ + --output "$report_dir/merged/index.html" + check_report "$report_dir/merged.xml" + check_merged_floor "$report_dir/merged.xml" + exit 0 +fi + +configuration="${1:-}" +valid=0 +for item in "${configurations[@]}"; do + if [ "$configuration" = "$item" ]; then + valid=1 + break + fi +done +if [ "$valid" -ne 1 ]; then + echo "Unknown coverage configuration: $configuration" >&2 + exit 1 +fi + +cc_name=$(basename "${CC:-gcc}") +if [ -n "${GCOV:-}" ]; then + gcov="$GCOV" +elif [[ "$cc_name" = gcc* ]]; then + gcov="gcov${cc_name#gcc}" +else + gcov=gcov +fi +if ! command -v "$gcov" >/dev/null 2>&1; then + echo "Coverage tool $gcov is unavailable." >&2 + exit 1 +fi + +objects="$PWD/build/$configuration/filex/CMakeFiles/filex.dir/common/src" +if [ ! -d "$objects" ] || [ -z "$(find "$objects" -name '*.gcda' -print -quit)" ]; then + echo "No FileX coverage data for $configuration." >&2 + exit 1 +fi + +base="$report_dir/per_configuration/$configuration" +mkdir -p "$base" +# Regression tests use a separate driver with fault injection. +gcovr --gcov-executable "$gcov" -r "$repo_root" -f "$repo_root/common/src" \ + -e "$repo_root/common/src/fx_ram_driver.c" \ + "$objects" --json "$base.json" --xml-pretty --output "$base.xml" +gcovr --gcov-executable "$gcov" -r "$repo_root" -f "$repo_root/common/src" \ + -e "$repo_root/common/src/fx_ram_driver.c" \ + "$objects" --html --html-details --output "$base/index.html" +check_report "$base.xml" "$base.json" diff --git a/test/cmake/regression/CMakeLists.txt b/test/cmake/regression/CMakeLists.txt index 2674ef6..e72989f 100644 --- a/test/cmake/regression/CMakeLists.txt +++ b/test/cmake/regression/CMakeLists.txt @@ -15,6 +15,7 @@ set(regression_test_cases ${SOURCE_DIR}/filex_directory_long_short_get_test.c ${SOURCE_DIR}/filex_directory_naming_test.c ${SOURCE_DIR}/filex_directory_rename_test.c + ${SOURCE_DIR}/filex_fault_tolerant_apply_logs_bounds_test.c ${SOURCE_DIR}/filex_fault_tolerant_corrupted_log_file_test.c ${SOURCE_DIR}/filex_fault_tolerant_delete_large_data_test.c ${SOURCE_DIR}/filex_fault_tolerant_directory_attributes_set_directory_interrupt_test.c @@ -141,16 +142,43 @@ set(regression_test_cases ${SOURCE_DIR}/filex_file_write_notify_test.c ${SOURCE_DIR}/filex_file_write_available_cluster_test.c ${SOURCE_DIR}/filex_utility_test.c + ${SOURCE_DIR}/filex_partition_offset_extended_test.c ${SOURCE_DIR}/filex_utility_fat_flush_test.c) add_library(test_utility ${SOURCE_DIR}/fx_ram_driver_test.c ${SOURCE_DIR}/filextestcontrol.c) target_link_libraries(test_utility PUBLIC azrtos::filex) target_compile_definitions(test_utility PUBLIC BATCH_TEST CTEST) +if(MSVC AND FX_STANDALONE_ENABLE) + target_include_directories(test_utility PUBLIC ${SOURCE_DIR}/win64_compat) +endif() foreach(test_case ${regression_test_cases}) get_filename_component(test_name ${test_case} NAME_WE) - add_executable(${test_name} ${test_case}) - target_link_libraries(${test_name} PRIVATE test_utility) - add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name}) + if(MSVC) + # CMake repeats the target name in its object directory. Several descriptive + # FileX test names otherwise exceed the legacy path limit used by cl.exe in a + # normally nested Windows worktree. + string(MD5 test_target_hash ${test_name}) + string(SUBSTRING ${test_target_hash} 0 12 test_target_hash) + set(test_target fx_test_${test_target_hash}) + else() + set(test_target ${test_name}) + endif() + + add_executable(${test_target} ${test_case}) + target_link_libraries(${test_target} PRIVATE test_utility) + set_target_properties(${test_target} PROPERTIES OUTPUT_NAME ${test_name}) + add_test(NAME ${CMAKE_BUILD_TYPE}::${test_name} COMMAND ${test_target}) endforeach() + +# These two tests write or corrupt large amounts of data on a 900 MB RAM disk and are +# inherently slow (≈95 s and ≈330 s on Windows). Override the default 60-second ctest +# timeout so that they are not mis-reported as failures. +if(MSVC) + set_tests_properties( + ${CMAKE_BUILD_TYPE}::filex_fault_tolerant_file_corruption_test + ${CMAKE_BUILD_TYPE}::filex_fault_tolerant_media_full_test + PROPERTIES TIMEOUT 600 + ) +endif() diff --git a/test/cmake/run.sh b/test/cmake/run.sh index 4d7950a..e32ab61 100755 --- a/test/cmake/run.sh +++ b/test/cmake/run.sh @@ -10,10 +10,33 @@ # SPDX-License-Identifier: MIT ############################################################################## +set -euo pipefail -cd $(dirname $0) +cd "$(dirname "$0")" +revision=$(cat threadx-revision.txt) -# if threadx repo does not exist, clone it -[ -d threadx ] || git clone https://github.com/eclipse-threadx/threadx.git --depth 1 -[ -f .run.sh ] || ln -sf threadx/scripts/cmake_bootstrap.sh .run.sh -./.run.sh $* \ No newline at end of file +if [ ! -e threadx ]; then + git init -q threadx + git -C threadx remote add origin https://github.com/eclipse-threadx/threadx.git + timeout 180 git -C threadx fetch --depth 1 origin "$revision" + git -C threadx checkout -q --detach FETCH_HEAD +fi + +actual_revision=$(git -C threadx rev-parse HEAD) +if [ "$actual_revision" != "$revision" ]; then + echo "ThreadX checkout does not match threadx-revision.txt." >&2 + exit 1 +fi + +bootstrap=threadx/scripts/cmake_bootstrap.sh +if [ ! -f "$bootstrap" ]; then + echo "ThreadX bootstrap script is missing." >&2 + exit 1 +fi + +if [ "${1:-}" = test ] && [ "${2:-}" = all ] && [ "${TX_COVERAGE:-OFF}" = ON ]; then + ./coverage.sh --clean +fi + +ln -sfn "$bootstrap" .run.sh +exec ./.run.sh "$@" diff --git a/test/cmake/samples/CMakeLists.txt b/test/cmake/samples/CMakeLists.txt index 17530dc..b9824f0 100644 --- a/test/cmake/samples/CMakeLists.txt +++ b/test/cmake/samples/CMakeLists.txt @@ -12,6 +12,6 @@ foreach(sample_file ${sample_files}) get_filename_component(sample_file_name ${sample_file} NAME_WE) add_executable(${sample_file_name} ${sample_file} ${CMAKE_CURRENT_LIST_DIR}/../../regression_test/fx_ram_driver_test.c) - target_compile_options(${sample_file_name} PRIVATE -DSAMPLE_BUILD) + target_compile_definitions(${sample_file_name} PRIVATE SAMPLE_BUILD) target_link_libraries(${sample_file_name} PRIVATE azrtos::filex) endforeach() diff --git a/test/cmake/threadx-revision.txt b/test/cmake/threadx-revision.txt new file mode 100644 index 0000000..aaa9e4d --- /dev/null +++ b/test/cmake/threadx-revision.txt @@ -0,0 +1 @@ +80dfbe7797f4e03bb878f06b9557ddc4041cf212 diff --git a/test/regression_test/filex_directory_create_delete_test.c b/test/regression_test/filex_directory_create_delete_test.c index d092469..9ad4ca7 100644 --- a/test/regression_test/filex_directory_create_delete_test.c +++ b/test/regression_test/filex_directory_create_delete_test.c @@ -9,6 +9,8 @@ /* SPDX-License-Identifier: MIT */ /***************************************************************************/ +// Portions of this file were generated with AI assistance. + /* This FileX test concentrates on the basic directory create/delete operations. */ #ifndef FX_STANDALONE_ENABLE @@ -111,8 +113,10 @@ static UCHAR *cache_buffer; static UCHAR *fault_tolerant_buffer; #else static UCHAR cache_buffer[CACHE_SIZE]; +#ifdef FX_ENABLE_FAULT_TOLERANT static UCHAR fault_tolerant_buffer[FAULT_TOLERANT_SIZE]; #endif +#endif extern ULONG _fx_ram_driver_copy_default_format; @@ -3064,4 +3068,3 @@ FX_DIR_ENTRY search_directory; test_control_return(0); } } - diff --git a/test/regression_test/filex_directory_duplicate_entries_test.c b/test/regression_test/filex_directory_duplicate_entries_test.c index 5266d66..1ff2379 100644 --- a/test/regression_test/filex_directory_duplicate_entries_test.c +++ b/test/regression_test/filex_directory_duplicate_entries_test.c @@ -9,6 +9,8 @@ /* SPDX-License-Identifier: MIT */ /***************************************************************************/ +// Portions of this file were generated with AI assistance. + /* This FileX test concentrates on recovery operation when there are duplicate entries. */ #ifndef FX_STANDALONE_ENABLE @@ -49,8 +51,10 @@ static UCHAR *cache_buffer; static UCHAR *fault_tolerant_buffer; #else static UCHAR cache_buffer[CACHE_SIZE]; +#ifdef FX_ENABLE_FAULT_TOLERANT static UCHAR fault_tolerant_buffer[FAULT_TOLERANT_SIZE]; #endif +#endif extern ULONG _fx_ram_driver_copy_default_format; diff --git a/test/regression_test/filex_directory_local_path_test.c b/test/regression_test/filex_directory_local_path_test.c index 71ea3db..99ba41a 100644 --- a/test/regression_test/filex_directory_local_path_test.c +++ b/test/regression_test/filex_directory_local_path_test.c @@ -9,6 +9,8 @@ /* SPDX-License-Identifier: MIT */ /***************************************************************************/ +/* Portions of this file were generated with AI assistance. */ + /* This FileX test concentrates on the local path operations. */ #ifndef FX_STANDALONE_ENABLE @@ -803,12 +805,36 @@ void filex_directory_local_path_application_define(void *first_unused_memory) #endif { +FX_MEDIA media = {0}; +FX_LOCAL_PATH local_path = {0}; +CHAR *path_name = FX_NULL; +UINT status; + FX_PARAMETER_NOT_USED(first_unused_memory); - /* Print out some test information banners. */ - printf("FileX Test: Directory local path test..............................N/A\n"); + /* Local paths are unavailable in standalone mode. */ + media.fx_media_id = FX_MEDIA_ID; + status = fx_directory_local_path_clear(&media); + if (status != FX_NOT_IMPLEMENTED) + { + test_control_return(1); + return; + } + status = fx_directory_local_path_get(&media, &path_name); + if (status != FX_NOT_IMPLEMENTED) + { + test_control_return(2); + return; + } + status = fx_directory_local_path_restore(&media, &local_path); + if (status != FX_NOT_IMPLEMENTED) + { + test_control_return(3); + return; + } - test_control_return(255); + printf("FileX Test: Directory local path test..............................SUCCESS\n"); + test_control_return(0); } #endif diff --git a/test/regression_test/filex_directory_rename_test.c b/test/regression_test/filex_directory_rename_test.c index 36fd181..f76e8fb 100644 --- a/test/regression_test/filex_directory_rename_test.c +++ b/test/regression_test/filex_directory_rename_test.c @@ -9,6 +9,8 @@ /* SPDX-License-Identifier: MIT */ /***************************************************************************/ +// Portions of this file were generated with AI assistance. + /* This FileX test concentrates on the directory rename operations. */ #ifndef FX_STANDALONE_ENABLE @@ -47,8 +49,10 @@ static UCHAR *cache_buffer; static UCHAR *fault_tolerant_buffer; #else static UCHAR cache_buffer[CACHE_SIZE]; +#ifdef FX_ENABLE_FAULT_TOLERANT static UCHAR fault_tolerant_buffer[FAULT_TOLERANT_SIZE]; #endif +#endif /* Define thread prototypes. */ diff --git a/test/regression_test/filex_fault_tolerant_apply_logs_bounds_test.c b/test/regression_test/filex_fault_tolerant_apply_logs_bounds_test.c new file mode 100644 index 0000000..1b12a31 --- /dev/null +++ b/test/regression_test/filex_fault_tolerant_apply_logs_bounds_test.c @@ -0,0 +1,476 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/* This FileX test concentrates on the bounds applied to fault-tolerant log replay. + + A directory log entry names the offset and the size of a copy into a single + sector staged at fx_media_memory_buffer. Both values come off the media, so + the replay path has to bound them against the sector size rather than against + the whole cache allocation, and a re-mounted clean log has to keep working. */ + +#ifndef FX_STANDALONE_ENABLE +#include "tx_api.h" +#include "tx_thread.h" +#include "tx_timer.h" +#endif +#include "fx_api.h" +#include "fx_utility.h" +#include "fx_fault_tolerant.h" +#include +#include "fx_ram_driver_test.h" +extern void test_control_return(UINT status); +void filex_fault_tolerant_apply_logs_bounds_test_application_define(void *first_unused_memory); + +#if defined (FX_ENABLE_FAULT_TOLERANT) && defined (FX_FAULT_TOLERANT) + +#define DEMO_STACK_SIZE 4096 +#define CACHE_SIZE 2048 +#define FAULT_TOLERANT_SIZE FX_FAULT_TOLERANT_MINIMAL_BUFFER_SIZE +#define FAULT_TOLERANT_BUFFER (FX_FAULT_TOLERANT_MAXIMUM_LOG_FILE_SIZE + 1024) +#define BYTES_PER_SECTOR 256 + + +/* Define the ThreadX and FileX object control blocks... */ + +#ifndef FX_STANDALONE_ENABLE +static TX_THREAD ftest_0; +static UCHAR *cache_buffer; +static UCHAR *fault_tolerant_buffer; +#else +static UCHAR cache_buffer[CACHE_SIZE]; +static UCHAR fault_tolerant_buffer[FAULT_TOLERANT_BUFFER]; +#endif +static FX_MEDIA ram_disk; +static UCHAR *pointer; + +/* Define thread prototypes. */ + +static void ftest_0_entry(ULONG thread_input); +extern void _fx_ram_driver(FX_MEDIA *media_ptr); +extern void test_control_return(UINT status); + + +/* Format and open the media, then enable fault tolerance. */ +static UINT media_prepare(VOID) +{ + +UINT status; + + status = fx_media_format(&ram_disk, + _fx_ram_driver, // Driver entry + ram_disk_memory_large, // RAM disk memory pointer + cache_buffer, // Media buffer pointer + CACHE_SIZE, // Media buffer size + "MY_RAM_DISK", // Volume Name + 1, // Number of FATs + 32, // Directory Entries + 0, // Hidden sectors + 256, // Total sectors + BYTES_PER_SECTOR, // Sector size + 8, // Sectors per cluster + 1, // Heads + 1); // Sectors per track + if (status != FX_SUCCESS) + { + return(status); + } + + status = fx_media_open(&ram_disk, "RAM DISK", _fx_ram_driver, ram_disk_memory_large, + cache_buffer, CACHE_SIZE); + if (status != FX_SUCCESS) + { + return(status); + } + + return(fx_fault_tolerant_enable(&ram_disk, fault_tolerant_buffer, FAULT_TOLERANT_SIZE)); +} + + +/* Return a logical sector inside the fault-tolerant log file, past its header. */ +static ULONG64 log_data_sector(VOID) +{ + + return(((ULONG64)ram_disk.fx_media_data_sector_start) + + (((ULONG64)(ram_disk.fx_media_fault_tolerant_start_cluster + 1 - FX_FAT_ENTRY_START)) * + ((ULONG64)ram_disk.fx_media_sectors_per_cluster))); +} + + +/* Return the logical sector holding the fault-tolerant log file's header. */ +static ULONG64 log_header_sector(VOID) +{ + + return(((ULONG64)ram_disk.fx_media_data_sector_start) + + (((ULONG64)(ram_disk.fx_media_fault_tolerant_start_cluster - FX_FAT_ENTRY_START)) * + ((ULONG64)ram_disk.fx_media_sectors_per_cluster))); +} + + +/* Stage one directory log entry and force its copy offset, then replay the log. */ +static UINT replay_dir_log_at_offset(ULONG offset) +{ + +UINT status; +UINT payload = 0; +FX_FAULT_TOLERANT_DIR_LOG *dir_log; + + status = media_prepare(); + if (status != FX_SUCCESS) + { + return(status); + } + + _fx_fault_tolerant_transaction_start(&ram_disk); + + /* The next log entry lands at the current end of the log file. */ + dir_log = (FX_FAULT_TOLERANT_DIR_LOG *)(ram_disk.fx_media_fault_tolerant_memory_buffer + + ram_disk.fx_media_fault_tolerant_file_size); + + status = _fx_fault_tolerant_add_dir_log(&ram_disk, log_data_sector(), 1, (UCHAR *)&payload, 1); + if (status != FX_SUCCESS) + { + return(status); + } + + /* Point the copy past the end of the destination sector. */ + _fx_utility_32_unsigned_write((UCHAR *)&dir_log -> fx_fault_tolerant_dir_log_offset, offset); + + return(_fx_fault_tolerant_transaction_end(&ram_disk)); +} + + +/* Stage one directory log entry, then shrink the recorded log size so the entry + claims more than the log file holds, and replay the log. */ +static UINT replay_dir_log_short_by(ULONG shortfall) +{ + +UINT status; +UINT payload = 0; + + status = media_prepare(); + if (status != FX_SUCCESS) + { + return(status); + } + + _fx_fault_tolerant_transaction_start(&ram_disk); + + status = _fx_fault_tolerant_add_dir_log(&ram_disk, log_data_sector(), 1, (UCHAR *)&payload, 1); + if (status != FX_SUCCESS) + { + return(status); + } + + ram_disk.fx_media_fault_tolerant_file_size -= shortfall; + + return(_fx_fault_tolerant_transaction_end(&ram_disk)); +} + + +/* Stage one directory log entry, then record a log size that leaves no room for + the entries at all, and replay the log. */ +static UINT replay_dir_log_recording(ULONG recorded) +{ + +UINT status; +UINT payload = 0; + + status = media_prepare(); + if (status != FX_SUCCESS) + { + return(status); + } + + _fx_fault_tolerant_transaction_start(&ram_disk); + + status = _fx_fault_tolerant_add_dir_log(&ram_disk, log_data_sector(), 1, (UCHAR *)&payload, 1); + if (status != FX_SUCCESS) + { + return(status); + } + + ram_disk.fx_media_fault_tolerant_file_size = recorded; + + return(_fx_fault_tolerant_transaction_end(&ram_disk)); +} + + +/* Stage one FAT log entry, then declare it shorter than a FAT log entry, and + replay the log. */ +static UINT replay_short_fat_log(VOID) +{ + +UINT status; +FX_FAULT_TOLERANT_FAT_LOG *fat_log; + + status = media_prepare(); + if (status != FX_SUCCESS) + { + return(status); + } + + _fx_fault_tolerant_transaction_start(&ram_disk); + + /* The next log entry lands at the current end of the log file. */ + fat_log = (FX_FAULT_TOLERANT_FAT_LOG *)(ram_disk.fx_media_fault_tolerant_memory_buffer + + ram_disk.fx_media_fault_tolerant_file_size); + + status = _fx_fault_tolerant_add_FAT_log(&ram_disk, ram_disk.fx_media_fault_tolerant_start_cluster, + ram_disk.fx_media_fat_last); + if (status != FX_SUCCESS) + { + return(status); + } + + /* Declare one word less than the entry occupies. */ + _fx_utility_16_unsigned_write((UCHAR *)&fat_log -> fx_fault_tolerant_FAT_log_size, + FX_FAULT_TOLERANT_FAT_LOG_ENTRY_SIZE - 4); + + return(_fx_fault_tolerant_transaction_end(&ram_disk)); +} + + +/* Rewrite the log file header on the media so it records total_size, keeping the + header checksum valid, then enable fault tolerance over a buffer of + buffer_size bytes. */ +static UINT enable_over_log_recording(USHORT total_size, UINT buffer_size) +{ + +UINT status; +USHORT checksum; +ULONG64 header_sector; +FX_FAULT_TOLERANT_LOG_HEADER *log_header; + + status = media_prepare(); + if (status != FX_SUCCESS) + { + return(status); + } + + /* The log file's location is only known while fault tolerance is enabled. */ + header_sector = log_header_sector(); + + status = fx_media_close(&ram_disk); + if (status != FX_SUCCESS) + { + return(status); + } + + status = fx_media_open(&ram_disk, "RAM DISK", _fx_ram_driver, ram_disk_memory_large, + cache_buffer, CACHE_SIZE); + if (status != FX_SUCCESS) + { + return(status); + } + + /* Read the first sector of the log file, which carries the header. */ + status = _fx_utility_logical_sector_read(&ram_disk, header_sector, + ram_disk.fx_media_memory_buffer, + ((ULONG) 1), FX_DATA_SECTOR); + if (status != FX_SUCCESS) + { + return(status); + } + + log_header = (FX_FAULT_TOLERANT_LOG_HEADER *)ram_disk.fx_media_memory_buffer; + + /* The header checksum covers its own field, so zero it before computing. */ + _fx_utility_16_unsigned_write((UCHAR *)&log_header -> fx_fault_tolerant_log_header_total_size, total_size); + _fx_utility_16_unsigned_write((UCHAR *)&log_header -> fx_fault_tolerant_log_header_checksum, 0); + checksum = _fx_fault_tolerant_calculate_checksum((UCHAR *)log_header, FX_FAULT_TOLERANT_LOG_HEADER_SIZE); + _fx_utility_16_unsigned_write((UCHAR *)&log_header -> fx_fault_tolerant_log_header_checksum, checksum); + + status = _fx_utility_logical_sector_write(&ram_disk, header_sector, + ram_disk.fx_media_memory_buffer, + ((ULONG) 1), FX_DIRECTORY_SECTOR); + if (status != FX_SUCCESS) + { + return(status); + } + + status = _fx_utility_logical_sector_flush(&ram_disk, ((ULONG64) 0), ram_disk.fx_media_total_sectors, FX_TRUE); + if (status != FX_SUCCESS) + { + return(status); + } + + return(fx_fault_tolerant_enable(&ram_disk, fault_tolerant_buffer, buffer_size)); +} + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void filex_fault_tolerant_apply_logs_bounds_test_application_define(void *first_unused_memory) +#endif +{ + + +#ifndef FX_STANDALONE_ENABLE + /* Setup the working pointer. */ + pointer = (UCHAR *) first_unused_memory; + + /* Create the main thread. */ + + tx_thread_create(&ftest_0, "thread 0", ftest_0_entry, 0, + pointer, DEMO_STACK_SIZE, + 4, 4, TX_NO_TIME_SLICE, TX_AUTO_START); + + pointer = pointer + DEMO_STACK_SIZE; + + /* Setup memory for the RAM disk and the sector cache. */ + cache_buffer = pointer; + pointer += CACHE_SIZE; + fault_tolerant_buffer = pointer; + pointer += FAULT_TOLERANT_BUFFER; +#endif + + /* Initialize the FileX system. */ + fx_system_initialize(); +#ifdef FX_STANDALONE_ENABLE + ftest_0_entry(0); +#endif +} + + +/* Define the test threads. */ + +static void ftest_0_entry(ULONG thread_input) +{ + +UINT status; +USHORT total_size; + + FX_PARAMETER_NOT_USED(thread_input); + + /* Print out some test information banners. */ + printf("FileX Test: Fault Tolerant Apply Logs Bounds test.................."); + + /* A copy that runs past the destination sector must be refused. The sector + staged at fx_media_memory_buffer is one sector wide, so an offset inside the + cache allocation but past the sector is the case that used to be accepted. */ + status = replay_dir_log_at_offset(BYTES_PER_SECTOR + 44); + return_if_fail( status == FX_FILE_CORRUPT); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + /* A copy that ends exactly on the sector boundary is still legal. */ + status = replay_dir_log_at_offset(BYTES_PER_SECTOR - 1); + return_if_fail( status == FX_SUCCESS); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + /* A clean log file records no content beyond the header and the FAT chain, and + enabling fault tolerance over one on a re-mounted media has to succeed. */ + status = media_prepare(); + return_if_fail( status == FX_SUCCESS); + + total_size = (USHORT)_fx_utility_16_unsigned_read(ram_disk.fx_media_fault_tolerant_memory_buffer + + sizeof(ULONG)); + return_if_fail( total_size == FX_FAULT_TOLERANT_LOG_CONTENT_OFFSET); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + status = fx_media_open(&ram_disk, "RAM DISK", _fx_ram_driver, ram_disk_memory_large, + cache_buffer, CACHE_SIZE); + return_if_fail( status == FX_SUCCESS); + + status = fx_fault_tolerant_enable(&ram_disk, fault_tolerant_buffer, FAULT_TOLERANT_SIZE); + return_if_fail( status == FX_SUCCESS); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + /* A log entry may claim only the bytes that follow the header, the FAT chain + and the content header. The bound was computed from the content header + alone, which left every entry free to claim the 36 bytes in front of it. */ + status = replay_dir_log_short_by(1); + return_if_fail( status == FX_FILE_CORRUPT); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + /* A FAT log entry that declares less than one entry desynchronises the walk: + the entry is consumed, the cursor advances by less than the entry occupies, + and every entry behind it is then read from the middle of this one. */ + status = replay_short_fat_log(); + return_if_fail( status == FX_FILE_CORRUPT); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + /* A log file recording more bytes than the caller's buffer holds has to be + refused. fx_fault_tolerant_enable accepts any buffer of at least one + sector, so a small buffer under a full-size log is reachable through the + public API, and everything past this point indexes the buffer by the + recorded size. */ + status = enable_over_log_recording(FX_FAULT_TOLERANT_MAXIMUM_LOG_FILE_SIZE, BYTES_PER_SECTOR * 2); + return_if_fail( status == FX_FILE_CORRUPT); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + /* A log file that records less than the header, the FAT chain and the content + header together leaves no room for any entry, and the replay has to refuse it + before subtracting them from the recorded size. */ + status = replay_dir_log_recording(FX_FAULT_TOLERANT_LOG_CONTENT_OFFSET + 2); + return_if_fail( status == FX_FILE_CORRUPT); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + /* A log recording less than a reset one writes is corrupt whatever the buffer. */ + status = enable_over_log_recording(FX_FAULT_TOLERANT_LOG_CONTENT_OFFSET - 1, FAULT_TOLERANT_SIZE); + return_if_fail( status == FX_FILE_CORRUPT); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + /* So is one recording more than the format allows. The buffer here is larger + than the maximum on purpose: with a smaller one the buffer test refuses the + log first and this condition is never reached. */ + status = enable_over_log_recording(FX_FAULT_TOLERANT_MAXIMUM_LOG_FILE_SIZE + 1, FAULT_TOLERANT_BUFFER); + return_if_fail( status == FX_FILE_CORRUPT); + + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + + /* Output successful. */ + printf("SUCCESS!\n"); + test_control_return(0); +} + +#else + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void filex_fault_tolerant_apply_logs_bounds_test_application_define(void *first_unused_memory) +#endif +{ + + FX_PARAMETER_NOT_USED(first_unused_memory); + + /* Print out some test information banners. */ + printf("FileX Test: Fault Tolerant Apply Logs Bounds test..................N/A\n"); + + test_control_return(255); +} +#endif diff --git a/test/regression_test/filex_file_create_delete_test.c b/test/regression_test/filex_file_create_delete_test.c index d296ba4..43d01bb 100644 --- a/test/regression_test/filex_file_create_delete_test.c +++ b/test/regression_test/filex_file_create_delete_test.c @@ -9,6 +9,8 @@ /* SPDX-License-Identifier: MIT */ /***************************************************************************/ +// Portions of this file were generated with AI assistance. + /* This FileX test concentrates on the file create/delete operations. */ #ifndef FX_STANDALONE_ENABLE @@ -54,8 +56,10 @@ static UCHAR *cache_buffer; static UCHAR *fault_tolerant_buffer; #else static UCHAR cache_buffer[CACHE_SIZE]; +#ifdef FX_ENABLE_FAULT_TOLERANT static UCHAR fault_tolerant_buffer[FAULT_TOLERANT_SIZE]; #endif +#endif static UCHAR fat_buffer[128]; static UCHAR name_buffer[FX_MAX_LONG_NAME_LEN+1]; diff --git a/test/regression_test/filex_file_rename_test.c b/test/regression_test/filex_file_rename_test.c index c6c104e..3ff5bbd 100644 --- a/test/regression_test/filex_file_rename_test.c +++ b/test/regression_test/filex_file_rename_test.c @@ -9,6 +9,8 @@ /* SPDX-License-Identifier: MIT */ /***************************************************************************/ +// Portions of this file were generated with AI assistance. + /* This FileX test concentrates on the file rename operations. */ #ifndef FX_STANDALONE_ENABLE @@ -46,8 +48,10 @@ static UCHAR *cache_buffer; static UCHAR *fault_tolerant_buffer; #else static UCHAR cache_buffer[CACHE_SIZE]; +#ifdef FX_ENABLE_FAULT_TOLERANT static UCHAR fault_tolerant_buffer[FAULT_TOLERANT_SIZE]; #endif +#endif /* Define thread prototypes. */ diff --git a/test/regression_test/filex_partition_offset_extended_test.c b/test/regression_test/filex_partition_offset_extended_test.c new file mode 100644 index 0000000..8087e75 --- /dev/null +++ b/test/regression_test/filex_partition_offset_extended_test.c @@ -0,0 +1,224 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (GPT-6). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#include "fx_api.h" +#include + +#define TEST_SECTOR_SIZE 512u +#define TEST_PARTITION_OFFSET 446u +#define TEST_PARTITION_TYPE_OFFSET 4u +#define TEST_PARTITION_START_OFFSET 8u +#define TEST_PARTITION_SIZE_OFFSET 12u + +extern UINT _fx_partition_offset_calculate_extended(FX_MEDIA *media_ptr, void *partition_sector, + UINT partition, ULONG *partition_start, + ULONG *partition_size); +void test_control_return(UINT status); +void test_application_define(void *first_unused_memory); + +typedef enum TEST_DRIVER_MODE_ENUM +{ + TEST_DRIVER_SUCCESS, + TEST_DRIVER_READ_ERROR, + TEST_DRIVER_BAD_SIGNATURE, + TEST_DRIVER_EMPTY_PARTITION, + TEST_DRIVER_CHAIN +} TEST_DRIVER_MODE; + +static TEST_DRIVER_MODE test_driver_mode; + +/* Supply an extended boot record or a controlled read error. */ +static void test_partition_driver(FX_MEDIA *media_ptr) +{ +UCHAR *sector; + + if (test_driver_mode == TEST_DRIVER_READ_ERROR) + { + media_ptr->fx_media_driver_status = FX_IO_ERROR; + return; + } + + sector = (UCHAR *)media_ptr->fx_media_driver_buffer; + (void)memset(sector, 0, TEST_SECTOR_SIZE); + sector[510] = (UCHAR)0x55; + sector[511] = (UCHAR)0xAA; + if (test_driver_mode == TEST_DRIVER_BAD_SIGNATURE) + { + sector[510] = (UCHAR)0; + } + if (test_driver_mode != TEST_DRIVER_EMPTY_PARTITION) + { + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0x0B; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_START_OFFSET] = (UCHAR)3; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_SIZE_OFFSET] = (UCHAR)9; + } + if (test_driver_mode == TEST_DRIVER_CHAIN) + { + if (media_ptr->fx_media_driver_logical_sector == (ULONG)7) + { + sector[TEST_PARTITION_OFFSET + 16u + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0x05; + sector[TEST_PARTITION_OFFSET + 16u + TEST_PARTITION_START_OFFSET] = (UCHAR)20; + } + else + { + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_START_OFFSET] = (UCHAR)4; + } + } + media_ptr->fx_media_driver_status = FX_SUCCESS; +} + +/* Verify boot records, primary partitions, and extended partition reads. */ +void test_application_define(void *first_unused_memory) +{ +FX_MEDIA media = {0}; +UCHAR sector[TEST_SECTOR_SIZE] = {0}; +ULONG start = 0; +ULONG size = 0; +UINT status; + + FX_PARAMETER_NOT_USED(first_unused_memory); + + sector[0] = (UCHAR)0xEB; + sector[2] = (UCHAR)0x90; + sector[0x16] = (UCHAR)1; + sector[0x13] = (UCHAR)0x34; + sector[0x14] = (UCHAR)0x12; + status = _fx_partition_offset_calculate_extended(&media, sector, 0, &start, &size); + if ((status != FX_SUCCESS) || (start != (ULONG)0) || (size != (ULONG)0x1234)) + { + test_control_return((UINT)1); + return; + } + + sector[0x13] = (UCHAR)0; + sector[0x14] = (UCHAR)0; + sector[0x20] = (UCHAR)0x78; + sector[0x21] = (UCHAR)0x56; + status = _fx_partition_offset_calculate_extended(&media, sector, 0, FX_NULL, &size); + if ((status != FX_SUCCESS) || (size != (ULONG)0x5678)) + { + test_control_return((UINT)2); + return; + } + + (void)memset(sector, 0, sizeof(sector)); + status = _fx_partition_offset_calculate_extended(&media, sector, 0, &start, &size); + if (status != FX_NOT_FOUND) + { + test_control_return((UINT)12); + return; + } + sector[510] = (UCHAR)0x55; + sector[511] = (UCHAR)0xAA; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0x0B; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_START_OFFSET] = (UCHAR)7; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_SIZE_OFFSET] = (UCHAR)9; + status = _fx_partition_offset_calculate_extended(&media, sector, 0, &start, &size); + if ((status != FX_SUCCESS) || (start != (ULONG)7) || (size != (ULONG)9)) + { + test_control_return((UINT)3); + return; + } + status = _fx_partition_offset_calculate_extended(&media, sector, 0, FX_NULL, FX_NULL); + if (status != FX_SUCCESS) + { + test_control_return((UINT)4); + return; + } + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0; + status = _fx_partition_offset_calculate_extended(&media, sector, 0, &start, &size); + if (status != FX_NOT_FOUND) + { + test_control_return((UINT)13); + return; + } + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0x0B; + status = _fx_partition_offset_calculate_extended(&media, sector, 17, &start, &size); + if (status != FX_NOT_FOUND) + { + test_control_return((UINT)5); + return; + } + status = _fx_partition_offset_calculate_extended(&media, sector, 4, &start, &size); + if (status != FX_NOT_FOUND) + { + test_control_return((UINT)6); + return; + } + + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0x05; + media.fx_media_driver_entry = test_partition_driver; + test_driver_mode = TEST_DRIVER_SUCCESS; + status = _fx_partition_offset_calculate_extended(&media, sector, 5, &start, &size); + if (status != FX_NOT_FOUND) + { + test_control_return((UINT)14); + return; + } + (void)memset(sector, 0, sizeof(sector)); + sector[510] = (UCHAR)0x55; + sector[511] = (UCHAR)0xAA; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0x05; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_START_OFFSET] = (UCHAR)7; + status = _fx_partition_offset_calculate_extended(&media, sector, 4, &start, &size); + if ((status != FX_SUCCESS) || (start != (ULONG)10) || (size != (ULONG)9)) + { + test_control_return((UINT)7); + return; + } + + test_driver_mode = TEST_DRIVER_READ_ERROR; + (void)memset(sector, 0, sizeof(sector)); + sector[510] = (UCHAR)0x55; + sector[511] = (UCHAR)0xAA; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0x05; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_START_OFFSET] = (UCHAR)7; + status = _fx_partition_offset_calculate_extended(&media, sector, 4, &start, &size); + if (status != FX_IO_ERROR) + { + test_control_return((UINT)8); + return; + } + test_driver_mode = TEST_DRIVER_BAD_SIGNATURE; + status = _fx_partition_offset_calculate_extended(&media, sector, 4, &start, &size); + if (status != FX_NOT_FOUND) + { + test_control_return((UINT)9); + return; + } + test_driver_mode = TEST_DRIVER_EMPTY_PARTITION; + sector[510] = (UCHAR)0x55; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0x05; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_START_OFFSET] = (UCHAR)7; + status = _fx_partition_offset_calculate_extended(&media, sector, 4, &start, &size); + if (status != FX_NOT_FOUND) + { + test_control_return((UINT)10); + return; + } + + test_driver_mode = TEST_DRIVER_CHAIN; + sector[510] = (UCHAR)0x55; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_TYPE_OFFSET] = (UCHAR)0x05; + sector[TEST_PARTITION_OFFSET + TEST_PARTITION_START_OFFSET] = (UCHAR)7; + status = _fx_partition_offset_calculate_extended(&media, sector, 5, &start, &size); + if ((status != FX_SUCCESS) || (start != (ULONG)31) || (size != (ULONG)9)) + { + test_control_return((UINT)11); + return; + } + + test_control_return((UINT)0); +} diff --git a/test/regression_test/fx_ram_driver_test.c b/test/regression_test/fx_ram_driver_test.c index 63eb4a5..938624f 100644 --- a/test/regression_test/fx_ram_driver_test.c +++ b/test/regression_test/fx_ram_driver_test.c @@ -1,4 +1,4 @@ -/**************************************************************************/ +/**************************************************************************/ /* */ /* Copyright (c) 1996-2017 by Express Logic Inc. */ /* */ @@ -81,6 +81,36 @@ ULONG64 media_size; UCHAR large_file_name_format[]; /* Define memory for tests to be run in standalone mode (without Azure RTOS: ThreadX) */ +/* On MSVC, BSS arrays cause demand-zero page faults on first access, which serialises + with test execution and makes large-disk tests significantly slower than on Linux. + Pre-allocating with calloc commits and zero-fills all pages before any test runs, + eliminating page-fault overhead at runtime. This also avoids LNK1248 (PE image > 2 GB) + for configurations that would otherwise exceed the hard linker limit. + On other compilers keep the BSS arrays as before. */ +#ifdef _MSC_VER +#include +UCHAR *ram_disk_memory_large = NULL; +UCHAR *large_data_buffer = NULL; + +#if defined(FX_STANDALONE_ENABLE) && !defined(SAMPLE_BUILD) +UCHAR *ram_disk_memory = NULL; +UCHAR *ram_disk_memory1 = NULL; +#endif + +static void _fx_alloc_test_buffers(void) +{ + ram_disk_memory_large = (UCHAR *)calloc(900000000UL, 1U); + large_data_buffer = (UCHAR *)calloc(900000000UL, 1U); +#if defined(FX_STANDALONE_ENABLE) && !defined(SAMPLE_BUILD) + ram_disk_memory = (UCHAR *)calloc(300000000UL, 1U); + ram_disk_memory1 = (UCHAR *)calloc( 30000000UL, 1U); +#endif +} +#pragma section(".CRT$XCU", read) +__declspec(allocate(".CRT$XCU")) static void (*_p_fx_alloc)(void) = _fx_alloc_test_buffers; + +#else /* !_MSC_VER */ + #if defined(FX_STANDALONE_ENABLE) && !defined(SAMPLE_BUILD) UCHAR ram_disk_memory[300000000]; UCHAR ram_disk_memory1[30000000]; @@ -88,6 +118,7 @@ UCHAR ram_disk_memory1[30000000]; UCHAR ram_disk_memory_large[900000000]; UCHAR large_data_buffer[900000000]; +#endif /* _MSC_VER */ /* Define the callback function. */ @@ -298,8 +329,16 @@ UINT offset; /* Calculate the RAM disk sector offset. Note the RAM disk memory is pointed to by the fx_media_driver_info pointer, which is supplied by the application in the call to fx_media_open. */ - source_buffer = ((UCHAR *) media_ptr -> fx_media_driver_info) + - ((media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors) * media_ptr -> fx_media_bytes_per_sector); + { + /* Compute effective sector and guard against pointer arithmetic overflow on 64-bit + platforms when sector is near ULONG64_MAX. Out-of-range accesses are redirected + to large_data_buffer so the test can verify return codes without crashing. */ + ULONG64 _sector = media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors; + if (media_ptr -> fx_media_total_sectors > 0 && _sector >= media_ptr -> fx_media_total_sectors) + source_buffer = large_data_buffer; /* harmless scratch — test does not verify data */ + else + source_buffer = ((UCHAR *) media_ptr -> fx_media_driver_info) + (_sector * media_ptr -> fx_media_bytes_per_sector); + } /* Copy the RAM sector into the destination. */ _fx_utility_memory_copy(source_buffer, media_ptr -> fx_media_driver_buffer, @@ -318,8 +357,17 @@ UINT offset; /* Calculate the RAM disk sector offset. Note the RAM disk memory is pointed to by the fx_media_driver_info pointer, which is supplied by the application in the call to fx_media_open. */ - data_start = ((UCHAR *) media_ptr -> fx_media_driver_info) + - ((media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors) * media_ptr -> fx_media_bytes_per_sector); + { + /* Compute effective sector and guard against pointer arithmetic overflow on 64-bit + platforms when sector is near ULONG64_MAX. Out-of-range writes are discarded. */ + ULONG64 _sector = media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors; + if (media_ptr -> fx_media_total_sectors > 0 && _sector >= media_ptr -> fx_media_total_sectors) + { + media_ptr -> fx_media_driver_status = FX_SUCCESS; + return; + } + data_start = ((UCHAR *) media_ptr -> fx_media_driver_info) + (_sector * media_ptr -> fx_media_bytes_per_sector); + } /* Driver write callback function entry for calling. */ if (driver_write_callback != FX_NULL) diff --git a/test/regression_test/fx_ram_driver_test.h b/test/regression_test/fx_ram_driver_test.h index df9e610..be264e3 100644 --- a/test/regression_test/fx_ram_driver_test.h +++ b/test/regression_test/fx_ram_driver_test.h @@ -9,6 +9,8 @@ /* SPDX-License-Identifier: MIT */ /***************************************************************************/ +// Portions of this file were generated with AI assistance. + #ifndef _FX_RAM_DRIVER_TEST_H_ #define _FX_RAM_DRIVER_TEST_H_ @@ -24,15 +26,28 @@ /* Define the memory buffer to format the media if enable fault tolerant feature.*/ +/* On MSVC these are heap-allocated (see fx_ram_driver_test.c) to avoid committing + huge BSS arrays and exceeding the 2 GB PE image limit, so declare them as + pointers there; other compilers keep them as BSS arrays. */ +#ifdef _MSC_VER +extern UCHAR *ram_disk_memory_large; +extern UCHAR *large_data_buffer; +#else extern UCHAR ram_disk_memory_large[900000000]; extern UCHAR large_data_buffer[900000000]; +#endif #define large_data_buffer_size 900000000 /* Define memory for tests to be run in standalone mode (without Azure RTOS: ThreadX) */ #ifdef FX_STANDALONE_ENABLE +#ifdef _MSC_VER +extern UCHAR *ram_disk_memory; +extern UCHAR *ram_disk_memory1; +#else extern UCHAR ram_disk_memory[300000000]; extern UCHAR ram_disk_memory1[30000000]; #endif +#endif /* Define a macro for test. */ /* We do not need error code anymore but still define return_value_if_fail for compatibility backward. */ diff --git a/test/regression_test/win64_compat/pthread.h b/test/regression_test/win64_compat/pthread.h new file mode 100644 index 0000000..2ae24b3 --- /dev/null +++ b/test/regression_test/win64_compat/pthread.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2025 Contributors to the Eclipse Foundation */ +/* AI-generated with GitHub Copilot */ + +/* Minimal pthreads shim for FileX regression tests on Windows/x64. */ + +#ifndef WIN64_COMPAT_PTHREAD_H +#define WIN64_COMPAT_PTHREAD_H + +#include + +typedef HANDLE pthread_t; +typedef void pthread_attr_t; + +#define PTHREAD_CANCEL_ENABLE 0 +#define PTHREAD_CANCEL_DISABLE 1 +#define PTHREAD_CANCEL_DEFERRED 0 +#define PTHREAD_CANCEL_ASYNCHRONOUS 1 + +static inline int pthread_create(pthread_t *tid, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *arg) +{ + (void)attr; + *tid = CreateThread(NULL, 0U, (LPTHREAD_START_ROUTINE)(void *)start_routine, arg, 0U, NULL); + return (*tid == NULL) ? -1 : 0; +} + +static inline int pthread_join(pthread_t tid, void **retval) +{ + (void)retval; + WaitForSingleObject(tid, INFINITE); + CloseHandle(tid); + return 0; +} + +static inline int pthread_cancel(pthread_t tid) +{ + TerminateThread(tid, 0U); + return 0; +} + +/* No-op: allows test_control_thread_entry to reach exit(test_control_failed_tests). */ +static inline void pthread_exit(void *retval) +{ + (void)retval; +} + +static inline int pthread_setcancelstate(int state, int *oldstate) +{ + (void)state; + if (oldstate != NULL) + { + *oldstate = PTHREAD_CANCEL_ENABLE; + } + return 0; +} + +static inline int pthread_setcanceltype(int type, int *oldtype) +{ + (void)type; + if (oldtype != NULL) + { + *oldtype = PTHREAD_CANCEL_DEFERRED; + } + return 0; +} + +#endif /* WIN64_COMPAT_PTHREAD_H */ diff --git a/test/regression_test/win64_compat/unistd.h b/test/regression_test/win64_compat/unistd.h new file mode 100644 index 0000000..89a99ee --- /dev/null +++ b/test/regression_test/win64_compat/unistd.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2025 Contributors to the Eclipse Foundation */ +/* AI-generated with GitHub Copilot */ + +/* Minimal POSIX unistd shim for FileX regression tests on Windows/x64. */ + +#ifndef WIN64_COMPAT_UNISTD_H +#define WIN64_COMPAT_UNISTD_H + +#include + +typedef unsigned int useconds_t; + +/* Sleep for `usec` microseconds. Windows resolution is ~1 ms; round up. */ +static inline int usleep(useconds_t usec) +{ + DWORD ms = (DWORD)((usec + 999U) / 1000U); + Sleep((ms > 0U) ? ms : 1U); + return 0; +} + +#endif /* WIN64_COMPAT_UNISTD_H */