diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..f1194d55 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,26 @@ +# Use an official Rust image as a parent image +FROM ubuntu:latest +ARG DEBIAN_FRONTEND=noninteractive +ARG arch=x86_64 + +# Install tools and dependencies for building Rust projects +RUN apt-get update && apt-get install -y \ + build-essential \ + pkg-config \ + curl \ + libssl-dev \ + git +# Install Rust from source +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y +# Add cargo bin to PATH +ENV PATH="/root/.cargo/bin:${PATH}" + +# Install the nightly Rust toolchain +RUN rustup toolchain install nightly + +# Set the default toolchain to nightly +RUN rustup default nightly + +# Verify installation +RUN rustc --version + diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..7ecbda16 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "Rust (Unstable)", + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + "extensions": [ + "rust-lang.rust" + ] + } + }, + "forwardPorts": [], + "postCreateCommand": "cargo check", + "remoteUser": "root" +} + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..36be3b9a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,35 @@ +## Description + +Fixes # (issue) + + + + + +### Type of change + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## How Has This Been Tested? + + + + + +- Test A - `lind_project/tests/test_cases/test_a.c` +- Test B - `lind_project/tests/test_cases/test_b.c` + +## Checklist: + + + +- [ ] My code follows the style guidelines of this project +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] Any dependent changes have been added to a pull request and/or merged in other modules (native-client, lind-glibc, lind-project) diff --git a/.github/workflows/lind-selfhost.yml b/.github/workflows/lind-selfhost.yml new file mode 100644 index 00000000..7ff4a0f0 --- /dev/null +++ b/.github/workflows/lind-selfhost.yml @@ -0,0 +1,63 @@ +name: RustPOSIX Build + +# Controls when the workflow will run +on: + push: + branches: + - develop + - main + pull_request: + branches: + - develop + - main + # Triggers the workflow on push or pull request events but only for the develop branch + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build: + runs-on: self-hosted + if: github.event.pull_request.draft == false + steps: + - name: Echo out branch values + run: | + echo github.base_ref: ${{ github.base_ref }} + echo github.head_ref: ${{ github.head_ref }} + echo github.ref: ${{ github.ref }} + cd /home/lind/lind_project/ + + - name: Checkout lind-project + run: | + git --git-dir /home/lind/lind_project/.git remote update origin --prune; + git --git-dir /home/lind/lind_project/.git checkout --force remotes/origin/develop; + + - name: In the land of RUSTPOSIX where the shadows lie (PR request) + if: github.head_ref != '' + run: | + git --git-dir /home/lind/lind_project/src/safeposix-rust/.git remote update origin --prune; + git --git-dir /home/lind/lind_project/src/safeposix-rust/.git checkout remotes/origin/${{ github.head_ref }}; + make rustposix; + + - name: In the land of RUSTPOSIX where the shadows lie (Develop/Push) + if: github.head_ref == '' + run: | + git --git-dir /home/lind/lind_project/src/safeposix-rust/.git remote update origin --prune; + git --git-dir /home/lind/lind_project/src/safeposix-rust/.git checkout remotes/origin/develop; + make rustposix; + + - name: One NACL to rule them all + run: | + git --git-dir /home/lind/lind_project/src/native_client/.git remote update origin --prune; + git --git-dir /home/lind/lind_project/src/native_client/.git checkout remotes/origin/develop; + make nacl; + - name: One GLIBC to find them + run: | + git --git-dir /home/lind/lind_project/src/lind_glibc/.git remote update origin --prune; + git --git-dir /home/lind/lind_project/src/lind_glibc/.git checkout remotes/origin/develop; + make glibc; + - name: One ring to INSTALL them all + run: | + make install; + - name: And in darkness TEST them + run: | + make test-verbose; diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..6f0a29b0 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,26 @@ +name: Rust + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + + build_and_test: + name: Rust project - latest + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - nightly + steps: + - uses: actions/checkout@v4 + - run: ./gen_netdevs.sh + - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - run: cargo build --verbose + - run: cargo test --lib --verbose diff --git a/.gitignore b/.gitignore index d01bd1a9..6512f6e0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,15 @@ Cargo.lock # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ + +# Added by cargo + +/target + +**/net_devices + +linddata* +gen_netdevs +lind.metadata +lind.md.log diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 00000000..8a961074 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,5 @@ +ignore = [ + "src/safeposix/syscalls/net_constants.rs", + "src/safeposix/syscalls/sys_constants.rs", + "src/safeposix/syscalls/fs_constants.rs", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..b0dd080c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "rustposix" +version = "0.1.0" +authors = ["Nicholas Smith Renner ", "Jonathan Eli Singer ", "Tristan J. Brigham "] +edition = "2018" + +[lib] +path = "src/lib.rs" +# cdylib is a dynamically linkable library, which is great for linking into +# C programs and similar. rlib is needed for the criterion benchmarking libary +# and creates one of Rust's static libraries. We are currently not generating +# dylib files which are Rust's internal (non-stable) ABI. +# Source: https://users.rust-lang.org/t/what-is-the-difference-between-dylib-and-cdylib/28847/3 +crate-type = ["cdylib","rlib"] +test = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0", features = ["derive", "rc"] } +serde_cbor = "0.10" +libc = "0.2" +ringbuf = "0.2.6" +dashmap = { version = "5.1", features=["serde"] } +parking_lot = "0.12" + +[dev-dependencies] +criterion = { version = "0.3", features = ["html_reports"]} +tempfile = "3.2.0" +grcov="0.8.19" # code coverage + +[[bin]] +name = "lind_fs_utils" +path = "src/tools/fs_utils.rs" + +# many benchmarks follow. Don't put any non-benchmarks below this... +[[bench]] +name = "gen_getid" +path = "benches/gen_getid.rs" +harness= false + +[[bench]] +name = "fs_open_close" +path = "benches/fs_open_close.rs" +harness= false + +[[bench]] +name = "fs_read_write" +path = "benches/fs_read_write.rs" +harness= false + +[[bench]] +name = "fs_read_write_seek" +path = "benches/fs_read_write_seek.rs" +harness= false + + +# Don't put any thing below this... benchmarks above! diff --git a/LICENSE b/LICENSE index 261eeb9e..ed9327c0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,176 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..0537cfc8 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +.PHONY: test clean + +test: clean + chmod +x gen_netdevs.sh + ./gen_netdevs.sh + cargo test --lib + +clean: + rm -f gen_netdevs net_devices + rm -f linddata.* + rm -f lind.metadata + +format: + cargo fmt -- --check diff --git a/README.md b/README.md index 2aaba7c0..8855c085 100644 --- a/README.md +++ b/README.md @@ -1 +1,48 @@ -# RawPOSIX \ No newline at end of file +# RustPOSIX [![Build Status](https://github.com/Lind-Project/safeposix-rust/actions/workflows/lind-selfhost.yml/badge.svg?branch=develop)](https://github.com/Lind-Project/safeposix-rust/actions/workflows/lind-selfhost.yml) + +More implementation details could be found at [wiki](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Home.md). + +## Contents + +* [Home](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Home.md) +* [Architecture](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Architecture.md) +* [Interface](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Interface.md) +* [Run Independently](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Run-Independently.md) +* [Security Model](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Security-Model.md) +* [Style Guide](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Style-Guide.md) +* [Testing and Debugging](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Testing-and-Debugging.md) + +## Run RustPOSIX-Rust + +Quick start +Use Develop branch for the most stable behaviour. + +```bash +docker build -t --platform .devcontainer +docker run -it + +``` + +This will create a quick container with rustposix build at your local changes. +helpful for exploration and easy testing. + +See reference at [Run RustPOSIX Independently](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Run-Independently.md) + +## Test RustPOSIX-Rust + +Use main branch for the most stable behaviour. + +```bash +cargo build +cargo test --lib +``` + +See reference at [Testing and Debugging](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Testing-and-Debugging.md) + +## Development Guideline + +* All PRs should be merged to the Develop branch + +* Any imports from the standard library or any crates should be done in an interface file + +More detailed guideline will be in [RustPOSIX's wiki](https://github.com/Lind-Project/lind-docs/blob/main/docs/RustPOSIX/Style-Guide.md) \ No newline at end of file diff --git a/benches/fs_open_close.rs b/benches/fs_open_close.rs new file mode 100644 index 00000000..b0d2ec73 --- /dev/null +++ b/benches/fs_open_close.rs @@ -0,0 +1,72 @@ +/* Benchmarks for the microvisor implementation. In general, I'm not doing + * results checking / assertations to avoid adding bias to the results. */ + +// I hate allowing this, but this is apparently a known issue for a lot of +// code with CStrings. https://github.com/rust-lang/rust/issues/78691 +// I've tried to sanity check where this occurs, but please, please, please +// double check these parts of the code! +#![allow(temporary_cstring_as_ptr)] + +use criterion::{criterion_group, criterion_main, Criterion}; + +use rustposix::interface; + +use std::ffi::*; + +use rustposix::safeposix::cage::*; + +// Using this to include my criterion settings from a single shared file. +// I did not use "use" or "mod" because benches/ isn't in the crate's usual +// namespace and I didn't want to either make a separate crate with a single, +// tiny file or add this file to the rustposix crate. +mod global_criterion_settings; + +pub fn run_benchmark(c: &mut Criterion) { + // I'm following the initialization workflow from the unit tests here. + // + // I'm using the lindrustinit to set up cages and the file system. + rustposix::safeposix::dispatcher::lindrustinit(0); + + // Since all system calls are a method of a cage object, I also need this + // reference. + let cage = interface::cagetable_getref(1); + + // --- COMPARING open / close CALLS ACROSS Lind + Native OS kernel --- + let mut group = c.benchmark_group("Compare fs:open+close"); + + // Should be similar. Use a linear scale... + group.plot_config( + criterion::PlotConfiguration::default().summary_scale(criterion::AxisScale::Linear), + ); + + // Let's see how fast various file system calls are + group.bench_function("TF01: Lind open+close", |b| { + b.iter(|| { + let fd = cage.open_syscall("foo", O_CREAT | O_TRUNC | O_WRONLY, S_IRWXA); + cage.close_syscall(fd); + }) + }); + + // For comparison let's time the native OS... + group.bench_function("TF01: Native OS kernel open+close", |b| { + b.iter(|| unsafe { + let fd = libc::open( + CString::new("/tmp/foo").unwrap().as_ptr(), + O_CREAT | O_TRUNC | O_WRONLY, + S_IRWXA, + ); + libc::close(fd); + }) + }); + group.finish(); + + // This cleans up in ways I do not fully understand. I think it ensures + // the file system is cleaned up + rustposix::safeposix::dispatcher::lindrustfinalize(); +} + +criterion_group!(name=benches; + // Add the global settings here so we don't type it everywhere + config=global_criterion_settings::get_criterion(); + targets=run_benchmark); +criterion_main!(benches); diff --git a/benches/fs_read_write.rs b/benches/fs_read_write.rs new file mode 100644 index 00000000..dcf73b91 --- /dev/null +++ b/benches/fs_read_write.rs @@ -0,0 +1,148 @@ +/* Benchmarks for the microvisor implementation. In general, I'm not doing + * results checking / assertations to avoid adding bias to the results. */ + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; + +use rustposix::interface; + +use std::ffi::*; + +use std::time::Duration; + +use rustposix::safeposix::cage::*; + +use rustposix::tests; + +// Using this to include my criterion settings from a single shared file. +// I did not use "use" or "mod" because benches/ isn't in the crate's usual +// namespace and I didn't want to either make a separate crate with a single, +// tiny file or add this file to the rustposix crate. +mod global_criterion_settings; + +pub fn run_benchmark(c: &mut Criterion) { + // I'm following the initialization workflow from the unit tests here. + // + // I'm using the lindrustinit to set up cages and the file system. + rustposix::safeposix::dispatcher::lindrustinit(0); + + // Since all system calls are a method of a cage object, I also need this + // reference. + let cage = interface::cagetable_getref(1); + + // --- COMPARING read + write w/o lseek CALLS ACROSS Lind + Native OS kernel --- + // This is separated because writeat and readat do a lot of seeking. It's + // useful to have a comparison which does not. + let mut group = c.benchmark_group("Compare fs:write+read"); + + // Should be similar. Use a linear scale... + group.plot_config( + criterion::PlotConfiguration::default().summary_scale(criterion::AxisScale::Linear), + ); + + // First do this for Lind + + // Reduce the time to reduce disk space needed and go faster. + // Default is 5s... + group.measurement_time(Duration::from_secs(2)); + + // Shorten the warm up time as well from 3s to this... + group.warm_up_time(Duration::from_secs(1)); + + // Iterate for different buffer sizes... + for buflen in [1, 64, 1024, 65536].iter() { + let deststring = tests::str2cbuf( + &String::from_utf8(vec![b'X'; *buflen]).expect("error building string"), + ); + + let fd = cage.open_syscall("foo", O_CREAT | O_TRUNC | O_WRONLY, S_IRWXA); + // Let's see how fast various file system calls are + group.bench_with_input( + BenchmarkId::new("TF02:Lind write", buflen), + buflen, + |b, buflen| { + b.iter(|| { + let _ = cage.write_syscall(fd, deststring, *buflen); + }) + }, + ); + + cage.lseek_syscall(fd, 0, SEEK_SET); + + let mut read_buffer = tests::sizecbuf(*buflen); + + group.bench_with_input( + BenchmarkId::new("TF02:Lind read", buflen), + buflen, + |b, buflen| { + b.iter(|| { + cage.read_syscall(fd, read_buffer.as_mut_ptr(), *buflen); + }) + }, + ); + + cage.close_syscall(fd); + cage.unlink_syscall("foo"); + } + + // Now do this for Native + + // Iterate for different buffer sizes... + for buflen in [1, 64, 1024, 65536].iter() { + let fd: c_int; + let c_str = CString::new("/tmp/foo").unwrap(); + + let path = c_str.into_raw() as *const u8; + + unsafe { + fd = libc::open(path, O_CREAT | O_TRUNC | O_WRONLY, S_IRWXA); + } + + let deststring = tests::str2cbuf( + &String::from_utf8(vec![b'X'; *buflen]).expect("error building string"), + ); + + // For comparison let's time the native OS... + group.bench_with_input( + BenchmarkId::new("TF02:Native write", buflen), + buflen, + |b, buflen| { + b.iter(|| unsafe { + let _ = libc::write(fd, deststring as *const c_void, *buflen); + }) + }, + ); + + unsafe { + libc::lseek(fd, 0, SEEK_SET); + } + + let mut read_buffer = tests::sizecbuf(*buflen); + + // For comparison let's time the native OS... + group.bench_with_input( + BenchmarkId::new("TF02:Native read", buflen), + buflen, + |b, buflen| { + b.iter(|| unsafe { + libc::read(fd, read_buffer.as_mut_ptr() as *mut c_void, *buflen); + }) + }, + ); + + unsafe { + libc::close(fd); + libc::unlink(path); + } + } + group.finish(); + + // This cleans up in ways I do not fully understand. I think it ensures + // the file system is cleaned up + rustposix::safeposix::dispatcher::lindrustfinalize(); +} + +criterion_group!(name=benches; + // Add the global settings here so we don't type it everywhere + config=global_criterion_settings::get_criterion(); + targets=run_benchmark); +criterion_main!(benches); diff --git a/benches/fs_read_write_seek.rs b/benches/fs_read_write_seek.rs new file mode 100644 index 00000000..bc7eec58 --- /dev/null +++ b/benches/fs_read_write_seek.rs @@ -0,0 +1,125 @@ +/* Benchmarks for the microvisor implementation. In general, I'm not doing + * results checking / assertations to avoid adding bias to the results. */ + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; + +use rustposix::interface; + +use std::ffi::*; + +use std::time::Duration; + +use rustposix::safeposix::cage::*; + +use rustposix::tests; + +// Using this to include my criterion settings from a single shared file. +// I did not use "use" or "mod" because benches/ isn't in the crate's usual +// namespace and I didn't want to either make a separate crate with a single, +// tiny file or add this file to the rustposix crate. +mod global_criterion_settings; + +pub fn run_benchmark(c: &mut Criterion) { + // I'm following the initialization workflow from the unit tests here. + // + // I'm using the lindrustinit to set up cages and the file system. + rustposix::safeposix::dispatcher::lindrustinit(0); + + // Since all system calls are a method of a cage object, I also need this + // reference. + let cage = interface::cagetable_getref(1); + + // --- COMPARING read + write + lseek CALLS ACROSS Lind + Native OS kernel --- + let mut group = c.benchmark_group("Compare fs:write+read+lseek"); + + // Should be similar. Use a linear scale... + group.plot_config( + criterion::PlotConfiguration::default().summary_scale(criterion::AxisScale::Linear), + ); + + // Reduce the time to go faster! Default is 5s... + group.measurement_time(Duration::from_secs(2)); + + // Shorten the warm up time as well from 3s to this... + group.warm_up_time(Duration::from_secs(1)); + + // --- Lind --- + + // Iterate for different buffer sizes... + for buflen in [1, 64, 1024, 65536].iter() { + let fd = cage.open_syscall("foo", O_CREAT | O_TRUNC | O_WRONLY, S_IRWXA); + + let deststring = tests::str2cbuf( + &String::from_utf8(vec![b'X'; *buflen]).expect("error building string"), + ); + + let read_buffer = tests::sizecbuf(*buflen).as_mut_ptr(); + // Let's see how fast various file system calls are + group.bench_with_input( + BenchmarkId::new("TF03:Lind write+read+lseek", buflen), + buflen, + |b, buflen| { + b.iter(|| { + let _ = cage.write_syscall(fd, deststring, *buflen); + cage.lseek_syscall(fd, 0, SEEK_SET); + cage.read_syscall(fd, read_buffer, *buflen); + cage.lseek_syscall(fd, 0, SEEK_SET); + }) + }, + ); + + cage.close_syscall(fd); + cage.unlink_syscall("foo"); + } + + // --- Native --- + + // Iterate for different buffer sizes... + for buflen in [1, 64, 1024, 65536].iter() { + let fd: c_int; + + let c_str = CString::new("/tmp/foo").unwrap(); + + let path = c_str.into_raw() as *const u8; + + unsafe { + fd = libc::open(path, O_CREAT | O_TRUNC | O_WRONLY, S_IRWXA); + } + + let deststring = tests::str2cbuf( + &String::from_utf8(vec![b'X'; *buflen]).expect("error building string"), + ); + + let read_buffer = tests::sizecbuf(*buflen).as_mut_ptr(); + + // For comparison let's time the native OS... + group.bench_with_input( + BenchmarkId::new("TF03:Native write+read+lseek", buflen), + buflen, + |b, buflen| { + b.iter(|| unsafe { + let _ = libc::write(fd, deststring as *const c_void, *buflen); + libc::lseek(fd, 0, SEEK_SET); + libc::read(fd, read_buffer as *mut c_void, *buflen); + libc::lseek(fd, 0, SEEK_SET); + }) + }, + ); + unsafe { + libc::close(fd); + libc::unlink(path); + } + } + + group.finish(); + + // This cleans up in ways I do not fully understand. I think it ensures + // the file system is cleaned up + rustposix::safeposix::dispatcher::lindrustfinalize(); +} + +criterion_group!(name=benches; + // Add the global settings here so we don't type it everywhere + config=global_criterion_settings::get_criterion(); + targets=run_benchmark); +criterion_main!(benches); diff --git a/benches/gen_getid.rs b/benches/gen_getid.rs new file mode 100644 index 00000000..688757a2 --- /dev/null +++ b/benches/gen_getid.rs @@ -0,0 +1,67 @@ +/* Benchmarks for the microvisor implementation. In general, I'm not doing + * results checking / assertations to avoid adding bias to the results. */ + +use criterion::{criterion_group, criterion_main, Criterion}; + +use rustposix::interface; + +// Using this to include my criterion settings from a single shared file. +// I did not use "use" or "mod" because benches/ isn't in the crate's usual +// namespace and I didn't want to either make a separate crate with a single, +// tiny file or add this file to the rustposix crate. +mod global_criterion_settings; + +pub fn run_benchmark(c: &mut Criterion) { + // I'm following the initialization workflow from the unit tests here. + // + // I'm using the lindrustinit to set up cages and the file system. + rustposix::safeposix::dispatcher::lindrustinit(0); + + // Since all system calls are a method of a cage object, I also need this + // reference. + let cage = interface::cagetable_getref(1); + + // --- COMPARING get*id CALLS ACROSS Lind + Native OS kernel --- + let mut group = c.benchmark_group("Compare get*ids"); + + // These should be quite different, so use a log axis.. + group.plot_config( + criterion::PlotConfiguration::default().summary_scale(criterion::AxisScale::Logarithmic), + ); + + // let's have a combined benchmark of all of the get*id* system calls + // in RustPOSIX... I'm not running these separately, because they should + // not vary too much. + group.bench_function("TG01: Lind get*ids", |b| { + b.iter(|| { + cage.getpid_syscall(); + cage.getppid_syscall(); + cage.getgid_syscall(); + cage.getegid_syscall(); + cage.getuid_syscall(); + cage.geteuid_syscall(); + }) + }); + // For comparison let's time the native OS... + group.bench_function("TG01: Native OS kernel get*ids", |b| { + b.iter(|| unsafe { + libc::getpid(); + libc::getppid(); + libc::getgid(); + libc::getegid(); + libc::getuid(); + libc::geteuid(); + }) + }); + group.finish(); + + // This cleans up in ways I do not fully understand. I think it ensures + // the file system is cleaned up + rustposix::safeposix::dispatcher::lindrustfinalize(); +} + +criterion_group!(name=benches; + // Add the global settings here so we don't type it everywhere + config=global_criterion_settings::get_criterion(); + targets=run_benchmark); +criterion_main!(benches); diff --git a/benches/global_criterion_settings.rs b/benches/global_criterion_settings.rs new file mode 100644 index 00000000..b294fd58 --- /dev/null +++ b/benches/global_criterion_settings.rs @@ -0,0 +1,5 @@ +use criterion::Criterion; + +pub fn get_criterion() -> Criterion { + Criterion::default().noise_threshold(0.10) +} diff --git a/docs/RustPOSIX-README.jpg b/docs/RustPOSIX-README.jpg new file mode 100644 index 00000000..0f5f0168 Binary files /dev/null and b/docs/RustPOSIX-README.jpg differ diff --git a/docs/SafePOSIX Rust Diagram.jpg b/docs/SafePOSIX Rust Diagram.jpg new file mode 100644 index 00000000..53c9a67f Binary files /dev/null and b/docs/SafePOSIX Rust Diagram.jpg differ diff --git a/gen_netdevs.c b/gen_netdevs.c new file mode 100644 index 00000000..7f8eb02e --- /dev/null +++ b/gen_netdevs.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include +#include + +int main () +{ + struct ifaddrs *ifap, *ifa; + struct sockaddr_in *sa, *ba, *na, *da; + char *addr, *baddr, *naddr, *daddr; + + getifaddrs (&ifap); + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family==AF_INET) { + sa = (struct sockaddr_in *) ifa->ifa_addr; + addr = inet_ntoa(sa->sin_addr); + na = (struct sockaddr_in *) ifa->ifa_netmask; + naddr = inet_ntoa(na->sin_addr); + ba = (struct sockaddr_in *) ifa->ifa_broadaddr; + baddr = inet_ntoa(ba->sin_addr); + + printf("%s %d %s %s %s\n", ifa->ifa_name, ifa->ifa_flags, addr, naddr, baddr); + } + } + + freeifaddrs(ifap); + return 0; +} diff --git a/gen_netdevs.sh b/gen_netdevs.sh new file mode 100755 index 00000000..78bbd7db --- /dev/null +++ b/gen_netdevs.sh @@ -0,0 +1,19 @@ +#!/bin/bash +WORKDIR=$(pwd) # Set working directory to the output of pwd +FILE="$WORKDIR/gen_netdevs" # Use the full path for the file +PARENTPATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) # needs the source location when being used in lind-build. + +if [ -f "$FILE" ]; then + echo "$FILE exists." +else + echo "$FILE does not exist. Compiling." + gcc "$PARENTPATH/gen_netdevs.c" -o "$WORKDIR/gen_netdevs" + if [ $? -ne 0 ]; then # Check if gcc succeeded + echo "Compilation failed." + exit 1 + fi +fi + +echo "Generating netdevs" + +"$WORKDIR/gen_netdevs" > "$WORKDIR/net_devices" # Execute and output using full paths diff --git a/src/example_grates/fdtable.rs b/src/example_grates/fdtable.rs new file mode 100644 index 00000000..7b6e1672 --- /dev/null +++ b/src/example_grates/fdtable.rs @@ -0,0 +1,129 @@ +// This is a basic fdtables library. The purpose is to allow a cage to have +// a set of virtual fds which is translated into real fds. +// +// For example, suppose a cage with cageid A, wants to open a file. That open +// operation needs to return a file descriptor to the cage. Rather than have +// each cage have the actual underlying numeric fd[*], each cage has its own +// virtual fd numbers. So cageid A's fd 6 will potentially be different from +// cageid B's fd 6. When a call from cageid A or B is made, this will need to +// be translated from that virtual fd into the read fd[**]. +// +// One other complexity deals with the CLOEXEC flag. If this is set on a file +// descriptor, then when exec is called, it must be closed. This library +// provides a few functions to simplify this process. +// +// To make this work, this library provides the following funtionality: +// +// translate_virtual_fd(cageid, virtualfd) -> Result +// get_unused_virtual_fd(cageid,realfd,is_cloexec,optionalinfo) -> Result +// set_cloexec(cageid,virtualfd,is_cloexec) -> Result<(), EBADFD> +// +// +// There are other helper functions: +// +// get_optionalinfo(cageid,virtualfd) -> Result +// set_optionalinfo(cageid,virtualfd,optionalinfo) -> Result<(), EBADFD> +// The above two are useful if you want to track other things like +// an id for other in-memory data structures +// +// copy_fdtable_for_cage(srccageid, newcageid) -> Result<(), ENFILE> +// This is mostly used in handling fork, etc. +// +// remove_cage_from_fdtable(cageid) +// This is mostly used in handling exit, etc. +// +// close_all_for_exec(cageid) +// This handles exec by removing all of the realfds from the cage. +// +// get_exec_iter(cageid) -> iter() +// This handles exec by returning an iterator over the realfds, +// removing each entry after the next iterator element is returned. +// +// In situations where this will be used by a grate, a few other calls are +// particularly useful: +// +// lind_3i::reserve_fd(cageid, Option) -> Result +// Used to have the grate, etc. beneath you reserve (or provide) a fd. +// This is useful for situatiosn where you want to have most fds +// handled elsewhere, but need to be able to acquire a few for your +// purposes (like implementing in-memory pipes, etc.) +// +// [*] This isn't possible because fork causes the same fd in the parent and +// child to have separate file pointers (e.g., read / write to separate +// locations in the file. +// +// [**] This is only the 'real' fd from the standpoint of the user of this +// library. If another part of the system below it, such as another grate or +// the microvisor, is using this library, it will get translated again. +// + + +// This library is likely the place in the system where we should consider +// putting in place limits on file descriptors. Linux does this through two +// error codes, one for a per-process limit and the other for an overall system +// limit. My thinking currently is that both will be configurable values in +// the library. +// +// EMFILE The per-process limit on the number of open file +// descriptors has been reached. +// +// ENFILE The system-wide limit on the total number of open files +// has been reached. + + +// We will raise a panic anywhere we receive an unknown cageid. This frankly +// should not be possible and indicates some sort of internal error in our +// code. However, it is expected we could receive an invalid file descriptor +// when a cage makes a call. + + + +#![feature(lazy_cell)] + +use std::collections::HashMap; + +use std::sync::LazyLock; + +// In order to store this information, I'm going to use a HashMap which +// has keys of (cageid:u64) and values that are another HashMap. The second +// HashMap has keys of (virtualfd:64) and values of (realfd:u64, +// should_cloexec:bool, optionalinfo:u64). +// +// To speed up lookups, I could have used arrays instead of HashMaps. In +// theory, that space is far too large, but likely each could be bounded to +// smaller values like 1024. For simplicity I avoided this for now. +// +// I thought also about having different tables for the tuple of values +// since they aren't always used together, but this seemed needlessly complex +// (at least at first). +// +// I'm using LazyLock because I think this is how I'm supposed to set up +// static / global variables. +static fdtable: LazyLock>> = LazyLock::new(|| { + let mut m = HashMap::new(); + // How do I initialize the first cage? When does this happen? + m +}); + +// These are the values we look up with at the end... +struct fdtableentry { + realfd:u64, // underlying fd (may be a virtual fd below us or a kernel fd) + should_cloexec:bool, // should I close this when exec is called? + optionalinfo:u64, // user specified / controlled data +} + + +// translate_virtual_fd(cageid, virtualfd) -> Result +pub fn translate_virtual_fd(cageid:u64, virtualfd:u64) -> Result { + // They should not be able to pass a new cage I don't know. I should + // always have a table for each cage because each new cage is added at fork + // time + let cagetable: HashMap = fdtable.get(cageid).unwrap("Internal error, unknown cageid in translate_virtual_fd"); + + return match cagetable.get(virtualfd) { + Some(tableentry) => tableentry[0], + None => EBADFD, + } +} +// get_unused_virtual_fd(cageid,realfd,is_cloexec,optionalinfo) -> Result +// set_cloexec(cageid,virtualfd,is_cloexec) -> Result<(), EBADFD> diff --git a/src/example_grates/imfs_grate.rs b/src/example_grates/imfs_grate.rs new file mode 100644 index 00000000..9864a432 --- /dev/null +++ b/src/example_grates/imfs_grate.rs @@ -0,0 +1,218 @@ +// Example grates +// +// handle file requests internally (need inode / dir, fd table) +// ex: in memory file system, network file system, separate /tmp per-process, +// lindfs +// +// note: +// it's slightly odd I need a fd table here. I guess it is custom to the +// grate though. +// + +// ******************************************************************* +// *** This file contains the sketch of Lind's in memory fs *** +// *** THIS CODE WILL NOT RUN! IT IS MEANT TO ILLUSTRATE *** +// *** MY THINKING ABOUT HOW SOMETHING LIKE THIS WOULD *** +// *** WORK. WE WILL MAKE A WORKING GRATE ONCE WE *** +// *** UNDERSTAND THE INTERFACES AND CHALLENGES BETTER. *** +// ******************************************************************* + + + + +use lind_encasement; +use lind_3i; +use lind_3i::constants::*; + +// I'm going to include this, which will use fdtable extensively... +use virt_fsmetadata; + +/**************** virt_fsmetadata selected exerpts *****************/ + +pub struct data_storage_interface { + pub fn create_item(item_id:u64); + pub fn open_item(item_id:u64); + pub fn close_item(item_id:u64); + pub fn store_data(targetcage: u64, item_id:u64, position:u64; data: *[u8], length:u64) -> Result<...>; + pub fn read_data(targetcage: u64, item_id:u64, position:u64; data: *[u8], amount:u64) -> Result<...>; + // could implement or add others... + pub fn log_action(...); + + +}; + +// I'm assuming that we always know all of the metadata because we virtualize +// it. This means we know the size of every file, etc. + +/*** All the inode, data structure code goes here... ***/ + + + +// We hook in the handlers for read, write, etc. +pub fn setup_interposition(calls_to_integrate:data_storage_interface) { + // The encasement library will help w/ syscall replacements for my children + let mut syscall_replacements: Vec = Vec::new(); + + // I handle all open calls, etc... + syscall_replacements.push(repltable{syscallid:PIPE_SYSID, func:open_syscall}); + + // Let's handle read and write... + syscall_replacements.push(repltable{syscallid:READ_SYSID, func:read_syscall}); + syscall_replacements.push(repltable{syscallid:WRITE_SYSID, func:write_syscall}); + + syscall_replacements.push(repltable{syscallid:MMAP_SYSID, func:mmap_syscall}); + // And so on until we have all the file system calls... + + //... until finally + lind_encasement.replace_syscalls(syscall_replacements); +} + +// I'm assuming that how the above calls are used is quite self-evident. +// In essence, we just have the existing interface for most things, but the +// above extracts out the file storage. + +// This gets overridden... As would related calls like munmap() +pub fn mmap_syscall() { + ... +} + + +pub fn initialize_fs_metadata() { + // I'll either make this fresh or read it in here... + ... +} + + +// The code here keeps a fdtable, inode information, directory information, +// etc, much the same way as the existing code. When we open something (which +// is not open), we call open_item + + + + +/**************** Unique code for this implementation ***************/ + +pub fn init_imfs() { + // Create a global data structure which allocates in memory blocks to store + // the things it needs to persist. +} + + +pub fn my_create_item(item_id:u64) { + // create an entry in the item table. The value stored is the fd. + + if item_id in itemtable { + panic!("Item already open"); + } + // Ensure we are creating this item or panic! + itemtable[item_id] = _make_block_list(); +} + + +pub fn my_store_data(targetcageid: u64, item_id:u64, position:u64; data: *[u8], length:u64) -> Result<...> { + + // This is my custom way to handle storing metadata... + let mut source_data_pos = data; + + let currentlength = length; + // loops through while getting and/or locating blocks + while currentlength > 0 { + let blockptr,allowedamount = _alloc_or_get_block_address(itemtable[item_id],position,currentlength); + lind_3i.read_from_cage(targetcageid, source_data_pos, blockptr, allowedamount); + currentlength -= allowedamount; + source_data_pos += allowedamount; + + } + return length; +} + + +pub fn my_read_data(targetcageid: u64, item_id:u64, position:u64; data: *[u8], amount:u64) -> Result<...> { + // This is my custom way to handle reading in data... + + let mut writedatapos = data; + // Walk through the blocks and use + for blockpos,length in _get_block_location_iter(item_id,position,amount) { + lind_3i.write_to_cage(targetcageid,blockpos, writedatapos,length); + writedatapos +=length; + } + + // I always return it all (?virt_metadata tracks the length?) + return amount; +} + + +// I'm going to interpose here, so I need to have the full interface... +pub fn my_mmap_syscall(cageid: u64, targetcageid: u64, callid: u64, addr: u64, length: u64, prot: u64, flags: u64, fd: u64, offset: u64) -> u64 { + // *mmap(void addr[.length], size_t length, int prot, int flags, int fd, off_t offset); + + // do I need to call their code to set anything up, like file descriptors? + + + if ! MAP_ANONYMOUS & flags { + // If this is file-backed, I would loop through and allocate the + // blocks, much like with my_store_data(...). + + } + else { // MAP_ANONYMOUS + // I probably don't understand all the nuances of ANONYMOUS mapping, + // but I'm assuming I can just zero out the blocks of memory... + if flags & MAP_PRIVATE { + // if not shared, likely okay to just do nothing but zero the + // memory... + return...; + } + // I'm not sure how to handle MAP_SHARED. Likely need to set up the + // shared memory mapping on fork... + // However, we don't currently handle this in lindfs... + } + + + // Now I loop through and allocated shared memory for each block so that + // it is mapped from my cache into the cage's memory... + +} + +// munmap is similar, but easier. Just removes memory mappings... + + +// We probably need to do something on fork so that we can handle mmap +// correctly... +pub fn my_fork_syscall(cageid: u64, targetcageid: u64, callid: u64, addr: u64, length: u64, prot: u64, flags: u64, fd: u64, offset: u64) -> u64 { + + // Need to handle MAP_SHARED mappings here, so they are mapped shared + // into the child +} + + + + +fn main() { + // Setup my item table... + init_item_table(); + + // Have the virtual_fs initialize itself... + virt_metadata.initialize_fs_metadata(); + + // Register my calls + let mycalls = virt_metadata::data_storage_interface { + create_item: my_create_item, + open_item: virt_metadata::NOOP, + close_item: virt_metadata::NOOP, + store_data: my_store_data, + read_data: my_read_data, + // could implement or add others... + log_action:virt_metadata::DEFAULT, + // Don't persist metadata, since it's an imfs... + store_directories:false, + store_inodes:false, + }; + + + // need to replace calls with the right ones for our children + virt_metadata.setup_interposition(mycalls); + + // Need to instantiate child cage(s) here... + lind_encasement.initialize_children_and_consume_thread(); + // Never reaches here! +} diff --git a/src/example_grates/imp_grate.rs b/src/example_grates/imp_grate.rs new file mode 100644 index 00000000..77fd1e57 --- /dev/null +++ b/src/example_grates/imp_grate.rs @@ -0,0 +1,242 @@ + +// +// custom handle non-file requests internally (fd table) +// ex: in memory pipes, loopback sockets, unix domain sockets, etc. +// +// note: +// How do I reserve the fd I need here? Do I have a call to reserve a fd? +// If so, I likely need a way to close or release it as well... +// I also should keep a fd table to see how to handle this... +// + +// ******************************************************************* +// *** This file contains the sketch of an in-memory pipe handler *** +// *** THIS CODE WILL NOT RUN! IT IS MEANT TO ILLUSTRATE *** +// *** MY THINKING ABOUT HOW SOMETHING LIKE THIS WOULD *** +// *** WORK. WE WILL MAKE A WORKING GRATE ONCE WE *** +// *** UNDERSTAND THE INTERFACES AND CHALLENGES BETTER. *** +// ******************************************************************* + +use fdtable; +use lind_3i; +use lind_3i::constants::*; +use lind_encasement; + + +// Initialize the circular buffers for the in memory pipes +def init_circular_buffer_table() { + // do whatever initialization I need to for the circular buffers... +} + +// Get a new, unused pipe number... +fn _get_pipe_num() { +} + +// handle pipe creation +pub fn my_pipe_syscall(cageid: u64, targetcageid: u64, callid: u64, pipefds: u64, _: u64, _: u64, _: u64, _: u64, _: u64) -> u64 { + // int pipe(int pipefd[2]); + + let real_read_fd = lind_3i::reserve_fd(targetcageid,None); + let real_write_fd = lind_3i::reserve_fd(targetcageid,None); + // size of a file descriptor in bytes... + const FD_SIZE = 4; + + // Adds an entry to the table. The CLOEXEC flag is needed so we know + // if we need to clobber this on exec or not. + let mypipenum = _get_pipe_num(); + let virt_read_pipe_fd =fdtable::get_unused_virtual_fd(targetcageid,real_read_fd,false,mypipenum); + let virt_write_pipe_fd = fdtable::get_unused_virtual_fd(targetcageid,real_write_fd,false,mypipenum); + + // Writing the fds back to the child... + lind_3i::write_to_cage(targetcageid,pipefds,virt_read_pipe_fd,FD_SIZE); + pipefds += FD_SIZE; + lind_3i::write_to_cage(targetcageid,pipefds,virt_write_pipe_fd,FD_SIZE); + + // No need to call below me... + + // Return success! + return 0; + +} + +pub fn my_read_syscall(cageid: u64, targetcageid: u64, callid: u64, fd: u64, buf: u64, count: u64, _: u64, _: u64, _: u64) -> u64 { + // ssize_t read(int fd, void buf[.count], size_t count); + + + /***** Read from the circular buffer, block, etc. here... *****/ + lind_3i::write_to_cage(targetcageid,buf,local_buffer,count); + + + return count; + + /**** End specific pipe logic */ + +} + +pub fn my_write_syscall(cageid: u64, targetcageid: u64, callid: u64, fd: u64, buf: u64, count: u64, _: u64, _: u64, _: u64) -> u64 { + // ssize_t write(int fd, const void buf[.count], size_t count); + + + /***** Write to the circular buffer, block, etc. here... *****/ + lind_3i::read_from_cage(targetcageid,local_buffer,buf,count); + + + return count; + + /**** End specific pipe logic */ + +} + + +pub fn my_close_syscall(cageid: u64, targetcageid: u64, callid: u64, fd: u64, buf: u64, count: u64, _: u64, _: u64, _: u64) -> u64 { + // int close(int fd); + + /***** clean up the buffer, if needed ****/ + + return 0; + + /**** End specific pipe logic */ + +} + +pub fn my_select_helper(targetcageid: u64, callid: u64, fdvec: Vec) -> Vec { + // Select, poll, etc. are a bit of a mess. Maybe it makes sense to have + // a helper instead? + + /* call with a vector of file descriptors and returns an vector of fds + * with an enum with a statue for each POLLERR, POLLHUP, POLLIN, POLLOUT + * etc. + */ + + + return ...; + + /**** End specific pipe logic */ + +} + + + + + + + + + + + + +/************** Example pseudocode from the fdtable code *****************/ + +pub fn fork_syscall(cageid: u64, targetcageid: u64, callid: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64, arg6: u64) -> u64 { + // int fork(); + let newcageid= lind_encasement::handle_fork(targetcageid); + + // Make a copy of the parent's FD table... + _dup_row(targetcageid,newcageid); + return newcageid; + +} + +pub fn exec_syscall(cageid: u64, targetcageid: u64, callid: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64, arg6: u64) -> u64 { + + // close fds that were close on exec... + _handle_cloexec(targetcageid); + + return MAKE_SYSCALL(targetcageid, callid, arg1, arg2, arg3, arg4, arg5, arg6); + +} + +// dup, etc. are handlede here as well... + + +// This is the basic logic for all of the system calls that fdtable handles. +// It decides where to route things... +fn _route_syscall_or_calldown(cageid: u64, targetcageid: u64, callid: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64, arg6: u64) -> u64 { + // close(fd); + + if _fd_table_in(targetcageid,arg1) { + // Call handler just calls the appropriate system call function from + // the table... + return call_handler(cageid,targetcageid, callid, arg1,arg2,arg3,arg4,arg5,arg6) + else { + // Call beneath us... + return MAKE_SYSCALL(targetcageid, callid, arg1, arg2, arg3, arg4, arg5, arg6); + } + +} + +pub fn boilerplate_init_for_fdtables(Vec ,enum handler) { + + // Let fdtable handle these (and likely I need to add others... This + // most likely would be handled for me via a function call to initialize + // these at the same time instead of me doing it individually... + syscall_replacements::push(repltable{syscallid:FORK_SYSID, func:fdtable::fork_syscall}); + syscall_replacements::push(repltable{syscallid:EXEC_SYSID, func:fdtable::exec_syscall}); + syscall_replacements::push(repltable{syscallid:DUP_SYSID, func:fdtable::dup_syscall}); + syscall_replacements::push(repltable{syscallid:DUP2_SYSID, func:fdtable::dup2_syscall}); + + // Ignore open because we don't care about files... If we handled named + // pipes, we would add open... +// syscall_replacements::push(repltable{syscallid:OPEN_SYSID, func:lind_encasement::DEFAULT}); + if handler == CALLDOWN { + add _route_syscall_or_calldown to essentially all calls... + + } + +} + +/*************************** MAIN *******************************/ + + + +// This sets up the calls so that the right things are interposed... +fn setup_interposition() { + // The encasement library will help w/ syscall replacements for my children + let mut syscall_replacements: Vec = Vec::new(); + + // This sets up basic handlers, and says unknown fds should call to the + // grate below... This is useful when we only want to intercept some calls + // but do want to pass others through. + boilerplate_init_for_fdtables(syscall_replacements,CALLDOWN); + + // I handle all pipe calls in my own code... + syscall_replacements::push(repltable{syscallid:PIPE_SYSID, func:my_pipe_syscall}); + + // Let's handle read and write... + syscall_replacements::push(repltable{syscallid:READ_SYSID, func:fdtable::read_syscall}); + // add my handler for read. Will only be called on fds I added to the + // table. Other FDs will call below... + fdtable::add_handler(syscallid::READ_SYSID, my_read_syscall); + syscall_replacements::push(repltable{syscallid:WRITE_SYSID, func:fdtable::write_syscall}); + fdtable::add_handler(syscallid::WRITE_SYSID, my_write_syscall); + + // add my handler for close. Only called on my fds + syscall_replacements::push(repltable{syscallid:CLOSE_SYSID, func:fdtable::close_syscall}); + fdtable::add_handler(syscallid::CLOSE_SYSID, my_close_syscall); + + + // This handles select, poll, etc. in a clean way. Only called for my fds + fdtable::add_select_helper(my_select_helper); + + + lind_encasement::replace_syscalls(syscall_replacements); + +} + + +fn main() { + // do my setup... Where does this live? Rust doesn't like globals, + // but I need to share this... + fdtable::get_new_table(); + + // If I'm setting up circular buffers, do it here... + init_circular_buffer_table(); + + // need to replace calls with the right ones for our children + setup_interposition(); + + // Need to instantiate child cage(s) here... + lind_encasement::initialize_children_and_consume_thread(); + // Never reaches here! +} diff --git a/src/example_grates/lindfs_grate.rs b/src/example_grates/lindfs_grate.rs new file mode 100644 index 00000000..f00acfe4 --- /dev/null +++ b/src/example_grates/lindfs_grate.rs @@ -0,0 +1,215 @@ +// Example grates +// +// handle file requests internally (need inode / dir, fd table) +// ex: in memory file system, network file system, separate /tmp per-process, +// lindfs +// +// note: +// it's slightly odd I need a fd table here. I guess it is custom to the +// grate though. +// + +// ******************************************************************* +// *** This file contains the sketch of Lind's safeposix_rust fs *** +// *** THIS CODE WILL NOT RUN! IT IS MEANT TO ILLUSTRATE *** +// *** MY THINKING ABOUT HOW SOMETHING LIKE THIS WOULD *** +// *** WORK. WE WILL MAKE A WORKING GRATE ONCE WE *** +// *** UNDERSTAND THE INTERFACES AND CHALLENGES BETTER. *** +// ******************************************************************* + + +use lind_encasement; +use lind_3i; +use lind_3i::constants::*; + +// I'm going to include this, which will use fdtable extensively... +use virt_fsmetadata; + +/**************** virt_fsmetadata selected exerpts *****************/ + +pub struct data_storage_interface { + pub fn create_item(item_id:u64); + pub fn open_item(item_id:u64); + pub fn close_item(item_id:u64); + pub fn store_data(targetcage: u64, item_id:u64, position:u64; data: *[u8], length:u64) -> Result<...>; + pub fn read_data(targetcage: u64, item_id:u64, position:u64; data: *[u8], amount:u64) -> Result<...>; + // could implement or add others... + pub fn log_action(...); + + +}; + +// I'm assuming that we always know all of the metadata because we virtualize +// it. This means we know the size of every file, etc. + +/*** All the inode, data structure code goes here... ***/ + + + +// We hook in the handlers for read, write, etc. +pub fn setup_interposition(calls_to_integrate:data_storage_interface) { + // The encasement library will help w/ syscall replacements for my children + let mut syscall_replacements: Vec = Vec::new(); + + // I handle all open calls, etc... + syscall_replacements.push(repltable{syscallid:PIPE_SYSID, func:open_syscall}); + + // Let's handle read and write... + syscall_replacements.push(repltable{syscallid:READ_SYSID, func:read_syscall}); + syscall_replacements.push(repltable{syscallid:WRITE_SYSID, func:write_syscall}); + + syscall_replacements.push(repltable{syscallid:MMAP_SYSID, func:mmap_syscall}); + // And so on until we have all the file system calls... + + //... until finally + lind_encasement.replace_syscalls(syscall_replacements); +} + +// I'm assuming that how the above calls are used is quite self-evident. +// In essence, we just have the existing interface for most things, but the +// above extracts out the file storage. + +// NOTE: mmap in safeposix_rust just does some error checking and then calls +// down. We can do the same here! +pub fn mmap_syscall() { + ... +} + + +pub fn initialize_fs_metadata() { + // I'll either make this fresh or read it in here... + ... +} + + +// The code here keeps a fdtable, inode information, directory information, +// etc, much the same way as the existing code. When we open something (which +// is not open), we call open_item + + + + +/**************** Unique code for this implementation ***************/ + +pub fn init_item_table() { + // Create a global table of items here. In this implementation, this + // will map item_ids to fds of mine. +} + + +pub fn my_create_item(item_id:u64) { + // create an entry in the item table. The value stored is the fd. + + if item_id in itemtable { + panic!("Item already open"); + } + // Ensure we are creating this item or panic! + itemtable[item_id] = open("linddata.{item_id}",O_CREAT|O_EXCL|O_RDWR,0o600).unwrap(); +} + + +pub fn my_open_item(item_id:u64) { + // create an entry in the item table. The value stored is the fd. + + if item_id in itemtable { + panic!("Item already open"); + } + // Ensure we're opening an existing item or panic! + itemtable[item_id] = open("linddata.{item_id}",O_RDWR,0o600).unwrap(); + +} + +pub fn my_close_item(item_id:u64) { + // create an entry in the item table. The value stored is the fd. + + if !item_id in itemtable { + panic!("Item is not open"); + } + close(itemtable[item_id]) + +} + +pub fn my_store_data(targetcage: u64, item_id:u64, position:u64; data: *[u8], length:u64) -> Result<...> { + // This is my custom way to handle storing data... + + // I should never be past the end of the file since the virt_fs knows + // the position and has checked it... + + let fd = itemtable[item_id]; + + // I need to lock this... + { + + let last_pos = virt_fsmetadata.get_item_id_entry(item_id).position; + + if position != last_pos { + lseek(fd, position); + } + // BUG: This fd is this cage's fd, while the data pointer is from the + // target cage. This means that 3i needs to be able to support a mix + // of cage associations for different arguments... BUG BUG BUG + let amt_written = MAKE_SYSCALL(targetcage, WRITE_SYSID, fd, data, length).unwrap(); + // do error handling, likely panic, since no error should occur... + } + return amt_written; // To be used to update the position... + +} + + +pub fn my_read_data(targetcage: u64, item_id:u64, position:u64; data: *[u8], amount:u64) -> Result<...> { + // This is my custom way to handle reading in data... + + // Once again, I should never be past the end of the file, etc. since the + // virt_fs knows the position and has checked it... + + let fd = itemtable[item_id]; + + // I need to lock this... + { + + let last_pos = virt_fsmetadata.get_item_id_entry(item_id).position; + + if position != last_pos { + lseek(fd, position); + } + // BUG: This fd is the grate's fd, while the data pointer is from the + // target cage. This means that 3i needs to be able to support a mix + // of cage associations for different arguments... BUG BUG BUG + let amt_read = MAKE_SYSCALL(targetcage, READ_SYSID, fd, data, amount).unwrap(); + // do error handling, likely panic, since no error should occur... + } + return amt_read; // Update virt_metadata position... + +} + + + +fn main() { + // Setup my item table... + init_item_table(); + + // Have the virtual_fs initialize itself... + virt_metadata.initialize_fs_metadata(); + + // Register my calls + let mycalls = virt_metadata::data_storage_interface { + create_item: my_create_item, + open_item: my_open_item, + close_item: my_close_item, + store_data: my_store_data, + read_data: my_read_data, + // could implement or add others... + log_action:virt_metadata::DEFAULT, + // persist metadata out into items. + store_directories:true, + store_inodes:true, + }; + + + // need to replace calls with the right ones for our children + virt_metadata.setup_interposition(mycalls); + + // Need to instantiate child cage(s) here... + lind_encasement.initialize_children_and_consume_thread(); + // Never reaches here! +} diff --git a/src/example_grates/log_grate.rs b/src/example_grates/log_grate.rs new file mode 100644 index 00000000..d504f035 --- /dev/null +++ b/src/example_grates/log_grate.rs @@ -0,0 +1,40 @@ +// Example grates +// +// pass through +// ex: filter network calls by dest or file accesses by path, measure API +// usage / do strace equivalent. +// +// note: +// With the ability to set the target cage ID, I don't need to do much to +// support multi-tenancy. I can just use the targetcageid to do this. +// +// + + +// This does the heavy lifting (such as there is any...) +use lind_encasement; + +// This file contains a basic, example system call handler that just prints +// out the arguments + +pub fn log_syscall(cageid: u64, targetcageid: u64, callid: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64, arg6: u64) -> u64 { + // do whatever we want to log here... + println!("{cageid}, {targetcageid}, {callid}, {arg1}, {arg2}, {arg3}, {arg4}, {arg5}, {arg6}"); + + // Note, we don’t need to ensure the cageid can do an op + // on the targetcage because 3i checks that for us. Nice! + + // ...and make the original system call! + return MAKE_SYSCALL(targetcageid, callid, arg1, cleaned_up_ptr_arg, count_as_u64, arg4, arg5, arg6); +} + + +fn main() { + + // Replace all calls with the one given here... + lind_encasement.replace_all_syscalls(log_syscall); + + // instantiate child cage(s) as is needed here... + lind_encasement.initialize_children_and_consume_thread(); + // Never reaches here! +} diff --git a/src/example_grates/route_grate.rs b/src/example_grates/route_grate.rs new file mode 100644 index 00000000..150ddfac --- /dev/null +++ b/src/example_grates/route_grate.rs @@ -0,0 +1,147 @@ +// Example grates +// +// +// route requests to different grates +// ex: /tmp to imfs + others to lower level fs, etc. +// +// We need a better API to support this example. +// +// - Encasement libary supports a way to save and restore sets of system +// calls. Grate A is instantiated with the pure API. A dummy child +// is forked from it and that API is saved. Grate B is instantiated +// with the pure API. A dummy child if forked from grate B and its +// API is also saved. The filter grate is forked with the pure API +// and the APIs from the two dummy children are provided. Note that +// the filter grate must return its cageID / PID from fork for each +// grate if it wants to make calls of its own to those grates. +// +// + + +// ******************************************************************* +// *** This file contains the sketch of a filtering handler which *** +// *** divides up the calls that are made between different grates *** +// *** THIS CODE WILL NOT RUN! IT IS MEANT TO ILLUSTRATE *** +// *** MY THINKING ABOUT HOW SOMETHING LIKE THIS WOULD *** +// *** WORK. WE WILL MAKE A WORKING GRATE ONCE WE *** +// *** UNDERSTAND THE INTERFACES AND CHALLENGES BETTER. *** +// ******************************************************************* + + +// I'm realizing this likely needs its own library. The reason is that +// there is one tedious part: separating out the grate to route a request +// to by system call. So, really, you just want to decide once for every +// creation of a fd, where to assign it, and thereafter all the calls for +// it should end up in the right place... Manually annotating write, send, +// recv, fcntl, etc. is a pain in the butt. Especially calls like mmap, +// select, etc. which put the fd in a weird spot or do other weird things... +use multigratelib; +use lind_3i; +use lind_3i::constants::*; +use lind_encasement; + + +/*********************** some multigratelib code *************************/ + + +pub gratemap: HashMap; + + +// This adds entries to the gratemap so that they can be called later +// by the other system calls. +pub fn initialize_grates(grates: Vec,interface:...) { + + + for gratename in grates { + + // create the dummy child, snatch its API, and stick it in the + // gratemap. This lets us use it later. + gratemap[gratename] = lind_encasement.dummy_initialize_grate_and_return_syscalls(gratename,interface); + } + +} + +// For each individual call, I check to see where the fd is from and then I +// route to this grate. These can be private functions... +fn fcntl_handler(cageid: u64, targetcageid: u64, callid: u64, fd: u64, cmd: u64, arg3: u64, arg4: u64, arg5: u64, arg6: u64) -> u64 { + //int fcntl(int fd, int cmd, ...); + + // panic if doesn't exist... + let gratename = fdtable.get_entry(targetcageid,fd).extradata; + + return makesyscall(gratename,targetcageid,callid,fd,cmd,arg3,arg4,arg5,arg6); +} + +// Above is just an example. There would be one for each call with a fd... +// Or, at least there is one for every call with a fd in a specific position. +// Some calls like dup, etc. also need special handling. + + +//***************** Code the grate author needs to write ******************* + + +pub fn my_open_syscall(cageid: u64, targetcageid: u64, callid: u64, pathname: u64, flags: u64, mode: u64, _: u64, _: u64, _: u64) -> u64 { + // int open(const char *pathname, int flags, mode_t mode */ + // + + // the max filename length is supposed to be 4096 + let mut localfn_buffer = vec!([0u8,4096]); + + lind_3i.icstrncpy(MY_CAGE_ID, localfn_buffer, targetcageid, pathname,4096); + + // I do my filtering here... + if abs_path_santize(pathname).startswith("/tmp/") { + let myfd = multigratelib::makesyscall("grate_a.rs",targetcageid,OPEN_SYSIDpathname,flags,mode); + // All future calls with this fd, go to this grate... + multigratelib::filterfd("grate_a.rs",targetcageid,myfd); + return myfd; + } + else { + let myfd = multigratelib::makesyscall("grate_b.rs",targetcageid,OPEN_SYSID,pathname,flags,mode); + // All future calls with this fd, go to this grate... + multigratelib::filterfd("grate_b.rs",targetcageid,myfd); + return myfd; + } + +} + + + + +// This sets up interposition so that calls go the right place... +fn setup_interposition() { + // This sets up the basic handlers and says unknown fds should return + // EBADFD. This is useful when all fds should be known to us. + multigratelib::add_handler(syscallid::OPEN_SYSID, my_open_syscall); + + // You can also always route a call to a specific grate without needing + // to write a function + multigratelib::always_handle_with_grate(syscallid::PIPE_SYSID, "grate_a.rs"); + + // You can also just block a certain way to crate FDs... + multigratelib::block_call(syscallid::SOCKET_SYSID); + + lind_encasement.replace_syscalls(syscall_replacements); + +} + + +fn main() { + + // Initialize these two grates and don't change the interface for them + // by setting None (just use my interface) + multigratelib::initialize_grates(["grate_A.rs","grate_B.rs",None); + // The above is equivalent to calling the function two times (each with + // one of the grates). If we want to have different interfaces for them, + // we can do it that way. + + // If I want to have things handled with my API, I could also pass + // the empty string "" as an option. + // For example: multigratelib::initialize_grates(["",None); + + + + // Now, onto the real children! + lind_encasement::initialize_children_and_consume_thread(); + // Never reaches here! +} diff --git a/src/interface/comm.rs b/src/interface/comm.rs new file mode 100644 index 00000000..46ea1c8e --- /dev/null +++ b/src/interface/comm.rs @@ -0,0 +1,635 @@ +// // Authors: Nicholas Renner and Jonathan Singer and Tristan Brigham +// // +// // + +use crate::interface; +use std::fs::read_to_string; +use std::mem::size_of; +use std::str::from_utf8; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +extern crate libc; + +static NET_DEV_FILENAME: &str = "net_devices"; + +static mut UD_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); + +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub enum GenSockaddr { + Unix(SockaddrUnix), + V4(SockaddrV4), + V6(SockaddrV6), +} +impl GenSockaddr { + pub fn port(&self) -> u16 { + match self { + GenSockaddr::Unix(_) => panic!("Invalid function called for this type of Sockaddr."), + GenSockaddr::V4(v4addr) => v4addr.sin_port, + GenSockaddr::V6(v6addr) => v6addr.sin6_port, + } + } + pub fn set_port(&mut self, port: u16) { + match self { + GenSockaddr::Unix(_) => panic!("Invalid function called for this type of Sockaddr."), + GenSockaddr::V4(v4addr) => v4addr.sin_port = port, + GenSockaddr::V6(v6addr) => v6addr.sin6_port = port, + }; + } + + pub fn addr(&self) -> GenIpaddr { + match self { + GenSockaddr::Unix(_) => panic!("Invalid function called for this type of Sockaddr."), + GenSockaddr::V4(v4addr) => GenIpaddr::V4(v4addr.sin_addr), + GenSockaddr::V6(v6addr) => GenIpaddr::V6(v6addr.sin6_addr), + } + } + + pub fn set_addr(&mut self, ip: GenIpaddr) { + match self { + GenSockaddr::Unix(_unixaddr) => { + panic!("Invalid function called for this type of Sockaddr.") + } + GenSockaddr::V4(v4addr) => { + v4addr.sin_addr = if let GenIpaddr::V4(v4ip) = ip { + v4ip + } else { + unreachable!() + } + } + GenSockaddr::V6(v6addr) => { + v6addr.sin6_addr = if let GenIpaddr::V6(v6ip) = ip { + v6ip + } else { + unreachable!() + } + } + }; + } + + pub fn set_family(&mut self, family: u16) { + match self { + GenSockaddr::Unix(unixaddr) => unixaddr.sun_family = family, + GenSockaddr::V4(v4addr) => v4addr.sin_family = family, + GenSockaddr::V6(v6addr) => v6addr.sin6_family = family, + }; + } + + pub fn get_family(&self) -> u16 { + match self { + GenSockaddr::Unix(unixaddr) => unixaddr.sun_family, + GenSockaddr::V4(v4addr) => v4addr.sin_family, + GenSockaddr::V6(v6addr) => v6addr.sin6_family, + } + } + + pub fn path(&self) -> &str { + match self { + GenSockaddr::Unix(unixaddr) => { + let pathiter = &mut unixaddr.sun_path.split(|idx| *idx == 0); + let pathslice = pathiter.next().unwrap(); + let path = from_utf8(pathslice).unwrap(); + path + } + GenSockaddr::V4(_) => panic!("Invalid function called for this type of Sockaddr."), + GenSockaddr::V6(_) => panic!("Invalid function called for this type of Sockaddr."), + } + } +} + +#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] +pub enum GenIpaddr { + V4(V4Addr), + V6(V6Addr), +} + +impl GenIpaddr { + pub fn is_unspecified(&self) -> bool { + match self { + GenIpaddr::V4(v4ip) => v4ip.s_addr == 0, + GenIpaddr::V6(v6ip) => v6ip.s6_addr == [0; 16], + } + } + pub fn from_string(string: &str) -> Option { + let v4candidate: Vec<&str> = string.split('.').collect(); + let v6candidate: Vec<&str> = string.split(':').collect(); + let v4l = v4candidate.len(); + let v6l = v6candidate.len(); + if v4l == 1 && v6l > 1 { + //then we should try parsing it as an ipv6 address + let mut shortarr = [0u8; 16]; + let mut shortindex = 0; + let mut encountered_doublecolon = false; + for short in v6candidate { + if short.is_empty() { + //you can only have a double colon once in an ipv6 address + if encountered_doublecolon { + return None; + } + encountered_doublecolon = true; + + let numzeros = 8 - v6l + 1; //+1 to account for this empty string element + if numzeros == 0 { + return None; + } + shortindex += numzeros; + } else { + //ok we can actually parse the element in this case + if let Ok(b) = short.parse::() { + //manually handle big endianness + shortarr[2 * shortindex] = (b >> 8) as u8; + shortarr[2 * shortindex + 1] = (b & 0xff) as u8; + shortindex += 1; + } else { + return None; + } + } + } + return Some(Self::V6(V6Addr { s6_addr: shortarr })); + } else if v4l == 4 && v6l == 1 { + //then we should try parsing it as an ipv4 address + let mut bytearr = [0u8; 4]; + let mut shortindex = 0; + for byte in v4candidate { + if let Ok(b) = byte.parse::() { + bytearr[shortindex] = b; + shortindex += 1; + } else { + return None; + } + } + return Some(Self::V4(V4Addr { + s_addr: u32::from_ne_bytes(bytearr), + })); + } else { + return None; + } + } +} + +#[repr(C)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct SockaddrUnix { + pub sun_family: u16, + pub sun_path: [u8; 108], +} + +pub fn new_sockaddr_unix(family: u16, path: &[u8]) -> SockaddrUnix { + let pathlen = path.len(); + if pathlen > 108 { + panic!("Unix domain paths cannot exceed 108 bytes.") + } + let mut array_path: [u8; 108] = [0; 108]; + array_path[0..pathlen].copy_from_slice(path); + SockaddrUnix { + sun_family: family, + sun_path: array_path, + } +} + +pub fn gen_ud_path() -> String { + let mut owned_path: String = "/sock".to_owned(); + unsafe { + let id = UD_ID_COUNTER.fetch_add(1, Ordering::Relaxed); + owned_path.push_str(&id.to_string()); + } + owned_path.clone() +} + +#[repr(C)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Default)] +pub struct V4Addr { + pub s_addr: u32, +} +#[repr(C)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Default)] +pub struct SockaddrV4 { + pub sin_family: u16, + pub sin_port: u16, + pub sin_addr: V4Addr, + pub padding: u64, +} + +#[repr(C)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Default)] +pub struct V6Addr { + pub s6_addr: [u8; 16], +} +#[repr(C)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Default)] +pub struct SockaddrV6 { + pub sin6_family: u16, + pub sin6_port: u16, + pub sin6_flowinfo: u32, + pub sin6_addr: V6Addr, + pub sin6_scope_id: u32, +} + +#[derive(Debug)] +pub struct Socket { + pub raw_sys_fd: i32, +} + +impl Socket { + pub fn new(domain: i32, socktype: i32, protocol: i32) -> Socket { + let fd = unsafe { libc::socket(domain, socktype, protocol) }; + + //we make every socket have a recieve timeout of one second + //This is in order to allow the socket to process and recieve + //shutdowns while blocked on blocking recv syscalls. + let timeoutval = libc::timeval { + tv_sec: 1, + tv_usec: 0, + }; + unsafe { + libc::setsockopt( + fd, + libc::SOL_SOCKET, + libc::SO_RCVTIMEO, + (&timeoutval as *const libc::timeval) as *const libc::c_void, + size_of::() as u32, + ) + }; + if fd < 0 { + panic!("Socket creation failed when it should never fail"); + } + Self { raw_sys_fd: fd } + } + + pub fn bind(&self, addr: &GenSockaddr) -> i32 { + let (finalsockaddr, addrlen) = match addr { + GenSockaddr::V6(addrref6) => ( + (addrref6 as *const SockaddrV6).cast::(), + size_of::(), + ), + GenSockaddr::V4(addrref) => ( + (addrref as *const SockaddrV4).cast::(), + size_of::(), + ), + _ => { + unreachable!() + } + }; + unsafe { libc::bind(self.raw_sys_fd, finalsockaddr, addrlen as u32) } + } + + pub fn connect(&self, addr: &GenSockaddr) -> i32 { + let (finalsockaddr, addrlen) = match addr { + GenSockaddr::V6(addrref6) => ( + (addrref6 as *const SockaddrV6).cast::(), + size_of::(), + ), + GenSockaddr::V4(addrref) => ( + (addrref as *const SockaddrV4).cast::(), + size_of::(), + ), + _ => { + unreachable!() + } + }; + unsafe { libc::connect(self.raw_sys_fd, finalsockaddr, addrlen as u32) } + } + + pub fn sendto(&self, buf: *const u8, len: usize, addr: Option<&GenSockaddr>) -> i32 { + let (finalsockaddr, addrlen) = match addr { + Some(GenSockaddr::V6(addrref6)) => ( + (addrref6 as *const SockaddrV6).cast::(), + size_of::(), + ), + Some(GenSockaddr::V4(addrref)) => ( + (addrref as *const SockaddrV4).cast::(), + size_of::(), + ), + Some(_) => { + unreachable!() + } + None => ( + std::ptr::null::() as *const libc::sockaddr, + 0, + ), + }; + unsafe { + libc::sendto( + self.raw_sys_fd, + buf as *const libc::c_void, + len, + 0, + finalsockaddr, + addrlen as u32, + ) as i32 + } + } + + pub fn recvfrom(&self, buf: *mut u8, len: usize, addr: &mut Option<&mut GenSockaddr>) -> i32 { + let (finalsockaddr, mut addrlen) = match addr { + Some(GenSockaddr::V6(ref mut addrref6)) => ( + (addrref6 as *mut SockaddrV6).cast::(), + size_of::() as u32, + ), + Some(GenSockaddr::V4(ref mut addrref)) => ( + (addrref as *mut SockaddrV4).cast::(), + size_of::() as u32, + ), + Some(_) => { + unreachable!() + } + None => (std::ptr::null::() as *mut libc::sockaddr, 0), + }; + unsafe { + libc::recvfrom( + self.raw_sys_fd, + buf as *mut libc::c_void, + len, + 0, + finalsockaddr, + &mut addrlen as *mut u32, + ) as i32 + } + } + + pub fn recvfrom_nonblocking( + &self, + buf: *mut u8, + len: usize, + addr: &mut Option<&mut GenSockaddr>, + ) -> i32 { + let (finalsockaddr, mut addrlen) = match addr { + Some(GenSockaddr::V6(ref mut addrref6)) => ( + (addrref6 as *mut SockaddrV6).cast::(), + size_of::() as u32, + ), + Some(GenSockaddr::V4(ref mut addrref)) => ( + (addrref as *mut SockaddrV4).cast::(), + size_of::() as u32, + ), + Some(_) => { + unreachable!() + } + None => (std::ptr::null::() as *mut libc::sockaddr, 0), + }; + self.set_nonblocking(); + let retval = unsafe { + libc::recvfrom( + self.raw_sys_fd, + buf as *mut libc::c_void, + len, + 0, + finalsockaddr, + &mut addrlen as *mut u32, + ) as i32 + }; + self.set_blocking(); + retval + } + + pub fn listen(&self, backlog: i32) -> i32 { + unsafe { libc::listen(self.raw_sys_fd, backlog) } + } + + pub fn set_blocking(&self) -> i32 { + unsafe { libc::fcntl(self.raw_sys_fd, libc::F_SETFL, 0) } + } + + pub fn set_nonblocking(&self) -> i32 { + unsafe { libc::fcntl(self.raw_sys_fd, libc::F_SETFL, libc::O_NONBLOCK) } + } + + pub fn accept(&self, isv4: bool) -> (Result, GenSockaddr) { + return if isv4 { + let mut inneraddrbuf = SockaddrV4::default(); + let mut sadlen = size_of::() as u32; + let newfd = unsafe { + libc::accept( + self.raw_sys_fd, + (&mut inneraddrbuf as *mut SockaddrV4).cast::(), + &mut sadlen as *mut u32, + ) + }; + + if newfd < 0 { + (Err(newfd), GenSockaddr::V4(inneraddrbuf)) + } else { + ( + Ok(Self { raw_sys_fd: newfd }), + GenSockaddr::V4(inneraddrbuf), + ) + } + } else { + let mut inneraddrbuf = SockaddrV6::default(); + let mut sadlen = size_of::() as u32; + let newfd = unsafe { + libc::accept( + self.raw_sys_fd, + (&mut inneraddrbuf as *mut SockaddrV6).cast::(), + &mut sadlen as *mut u32, + ) + }; + + if newfd < 0 { + (Err(newfd), GenSockaddr::V6(inneraddrbuf)) + } else { + ( + Ok(Self { raw_sys_fd: newfd }), + GenSockaddr::V6(inneraddrbuf), + ) + } + }; + } + + pub fn nonblock_accept(&self, isv4: bool) -> (Result, GenSockaddr) { + return if isv4 { + let mut inneraddrbuf = SockaddrV4::default(); + let mut sadlen = size_of::() as u32; + self.set_nonblocking(); + let newfd = unsafe { + libc::accept( + self.raw_sys_fd, + (&mut inneraddrbuf as *mut SockaddrV4).cast::(), + &mut sadlen as *mut u32, + ) + }; + self.set_blocking(); + + if newfd < 0 { + (Err(newfd), GenSockaddr::V4(inneraddrbuf)) + } else { + ( + Ok(Self { raw_sys_fd: newfd }), + GenSockaddr::V4(inneraddrbuf), + ) + } + } else { + let mut inneraddrbuf = SockaddrV6::default(); + let mut sadlen = size_of::() as u32; + self.set_nonblocking(); + let newfd = unsafe { + libc::accept( + self.raw_sys_fd, + (&mut inneraddrbuf as *mut SockaddrV6).cast::(), + &mut sadlen as *mut u32, + ) + }; + self.set_blocking(); + + if newfd < 0 { + (Err(newfd), GenSockaddr::V6(inneraddrbuf)) + } else { + ( + Ok(Self { raw_sys_fd: newfd }), + GenSockaddr::V6(inneraddrbuf), + ) + } + }; + } + + pub fn setsockopt(&self, level: i32, optname: i32, optval: i32) -> i32 { + let valbuf = optval; + let ret = unsafe { + libc::setsockopt( + self.raw_sys_fd, + level, + optname, + (&valbuf as *const i32).cast::(), + size_of::() as u32, + ) + }; + ret + } + + pub fn shutdown(&self, how: i32) -> i32 { + let ret = unsafe { libc::shutdown(self.raw_sys_fd, how) }; + ret + } + + pub fn check_rawconnection(&self) -> bool { + let mut valbuf = 0; + let mut len = size_of::() as u32; + let ret = unsafe { + libc::getsockopt( + self.raw_sys_fd, + libc::SOL_SOCKET, + libc::SO_ERROR, + (&mut valbuf as *mut i32).cast::(), + &mut len as *mut u32, + ) + }; + (ret == 0) && (valbuf == 0) // if return val is 0 and error is 0 it's connected + } +} + +impl Drop for Socket { + fn drop(&mut self) { + unsafe { + libc::close(self.raw_sys_fd); + } + } +} + +pub fn getifaddrs_from_file() -> String { + read_to_string(NET_DEV_FILENAME) + .expect("No net_devices file present!") + .to_owned() +} + +// Implementations of select related FD_SET structure +pub struct FdSet(libc::fd_set); + +impl FdSet { + pub fn new() -> FdSet { + unsafe { + let mut raw_fd_set = std::mem::MaybeUninit::::uninit(); + libc::FD_ZERO(raw_fd_set.as_mut_ptr()); + FdSet(raw_fd_set.assume_init()) + } + } + + pub fn new_from_ptr(raw_fdset_ptr: *const libc::fd_set) -> &'static mut FdSet { + unsafe { &mut *(raw_fdset_ptr as *mut FdSet) } + } + + // copy the src FdSet into self + pub fn copy_from(&mut self, src_fds: &FdSet) { + unsafe { + std::ptr::copy_nonoverlapping( + &src_fds.0 as *const libc::fd_set, + &mut self.0 as *mut libc::fd_set, + 1, + ); + } + } + + // turn off the fd bit in fd_set (currently only used by the tests) + #[allow(dead_code)] + pub fn clear(&mut self, fd: i32) { + unsafe { libc::FD_CLR(fd, &mut self.0) } + } + + // turn on the fd bit in fd_set + pub fn set(&mut self, fd: i32) { + unsafe { libc::FD_SET(fd, &mut self.0) } + } + + // return true if the bit for fd is set, false otherwise + pub fn is_set(&self, fd: i32) -> bool { + unsafe { libc::FD_ISSET(fd, &self.0) } + } + + pub fn is_empty(&self) -> bool { + let fd_array: &[u8] = unsafe { + std::slice::from_raw_parts(&self.0 as *const _ as *const u8, size_of::()) + }; + fd_array.iter().all(|&byte| byte == 0) + } + + // for each fd, if kernel_fds turned it on, then self will turn the corresponding tranlated fd on + pub fn set_from_kernelfds_and_translate( + &mut self, + kernel_fds: &FdSet, + nfds: i32, + rawfd_lindfd_tuples: &Vec<(i32, i32)>, + ) { + for fd in 0..nfds { + if !kernel_fds.is_set(fd) { + continue; + } + // translate and set + if let Some((_, lindfd)) = rawfd_lindfd_tuples.iter().find(|(rawfd, _)| *rawfd == fd) { + self.set(*lindfd); + } + } + } +} + +// for unwrapping in kernel_select +fn to_fdset_ptr(opt: Option<&mut FdSet>) -> *mut libc::fd_set { + match opt { + None => std::ptr::null_mut(), + Some(&mut FdSet(ref mut raw_fd_set)) => raw_fd_set, + } +} + +pub fn kernel_select( + nfds: libc::c_int, + readfds: Option<&mut FdSet>, + writefds: Option<&mut FdSet>, + errorfds: Option<&mut FdSet>, +) -> i32 { + // Call libc::select and store the result + let result = unsafe { + // Create a timeval struct with zero timeout + + let mut kselect_timeout = libc::timeval { + tv_sec: 0, // 0 seconds + tv_usec: 0, // 0 microseconds + }; + + libc::select( + nfds, + to_fdset_ptr(readfds), + to_fdset_ptr(writefds), + to_fdset_ptr(errorfds), + &mut kselect_timeout as *mut libc::timeval, + ) + }; + + return result; +} diff --git a/src/interface/errnos.rs b/src/interface/errnos.rs new file mode 100644 index 00000000..6340b921 --- /dev/null +++ b/src/interface/errnos.rs @@ -0,0 +1,174 @@ +#![allow(dead_code)] +// Error handling for SafePOSIX +use crate::interface; + +use std::sync::OnceLock; + +pub static VERBOSE: OnceLock = OnceLock::new(); + +//A macro which takes the enum and adds to it a try_from trait which can convert values back to +//enum variants +macro_rules! reversible_enum { + ($(#[$settings: meta])* $visibility: vis enum $enumname:ident { + $($valuename: ident = $value: expr,)* + }) => { + $(#[$settings])* + $visibility enum $enumname { + $($valuename = $value,)* + } + + impl $enumname { + $visibility fn from_discriminant(v: i32) -> Result { + match v { + $($value => Ok($enumname::$valuename),)* + _ => Err(()), + } + } + } + } +} + +reversible_enum! { + #[derive(Debug, PartialEq, Eq)] + #[repr(i32)] + pub enum Errno { + EPERM = 1, // Operation not permitted + ENOENT = 2, // No such file or directory + ESRCH = 3, // No such process + EINTR = 4, // Interrupted system call + EIO = 5, // I/O error + ENXIO = 6, // No such device or address + EBIG = 7, // Argument list too long + ENOEXEC = 8, // Exec format error + EBADF = 9, // Bad file number + ECHILD = 10, // No child processes + EAGAIN = 11, // Try again + ENOMEM = 12, // Out of memory + EACCES = 13, // Permission denied + EFAULT = 14, // Bad address + ENOTBLK = 15, // Block device required + EBUSY = 16, // Device or resource busy + EEXIST = 17, // File exists + EXDEV = 18, // Cross-device link + ENODEV = 19, // No such device + ENOTDIR = 20, // Not a directory + EISDIR = 21, // Is a directory + EINVAL = 22, // Invalid argument + ENFILE = 23, // File table overflow + EMFILE = 24, // Too many open files + ENOTTY = 25, // Not a typewriter + ETXTBSY = 26, // Text file busy + EFBIG = 27, // File too large + ENOSPC = 28, // No space left on device + ESPIPE = 29, // Illegal seek + EROFS = 30, // Read-only file system + EMLINK = 31, // Too many links + EPIPE = 32, // Broken pipe + EDOM = 33, // Math argument out of domain of func + ERANGE = 34, // Math result not representable + EDEADLK = 35, // Resource deadlock would occur + ENAMETOOLONG = 36, // File name too long + ENOLCK = 37, // No record locks available + ENOSYS = 38, // Function not implemented + ENOTEMPTY = 39, // Directory not empty + ELOOP = 40, // Too many symbolic links encountered + // EWOULDBLOCK = 11, // Operation would block, returns EAGAIN + ENOMSG = 42, // No message of desired type + EIDRM = 43, // Identifier removed + ECHRNG = 44, // Channel number out of range + EL2NSYNC = 45, // Level not synchronized + EL3HLT = 46, // Level halted + EL3RST = 47, // Level reset + ELNRNG = 48, // Link number out of range + EUNATCH = 49, // Protocol driver not attached + ENOCSI = 50, // No CSI structure available + EL2HLT = 51, // Level halted + EBADE = 52, // Invalid exchange + EBADR = 53, // Invalid request descriptor + EXFULL = 54, // Exchange full + ENOANO = 55, // No anode + EBADRQC = 56, // Invalid request code + EBADSLT = 57, // Invalid slot + EBFONT = 59, // Bad font file format + ENOSTR = 60, // Device not a stream + ENODATA = 61, // No data available + ETIME = 62, // Timer expired + ENOSR = 63, // Out of streams resources + ENONET = 64, // Machine is not on the network + ENOPKG = 65, // Package not installed + EREMOTE = 66, // Object is remote + ENOLINK = 67, // Link has been severed + EADV = 68, // Advertise error + ESRMNT = 69, // Srmount error + ECOMM = 70, // Communication error on send + EPROTO = 71, // Protocol error + EMULTIHOP = 72, // Multihop attempted + EDOTDOT = 73, // RFS specific error + EBADMSG = 74, // Not a data message + EOVERFLOW = 75, // Value too large for defined data type + ENOTUNIQ = 76, // Name not unique on network + EBADFD = 77, // File descriptor in bad state + EREMCHG = 78, // Remote address changed + ELIBACC = 79, // Can not access a needed shared library + ELIBBAD = 80, // Accessing a corrupted shared library + ELIBSCN = 81, // .lib section in a.out corrupted + ELIBMAX = 82, // Attempting to link in too many shared libraries + ELIBEXEC = 83, // Cannot exec a shared library directly + EILSEQ = 84, // Illegal byte sequence + ERESTART = 85, // Interrupted system call should be restarted + ESTRPIPE = 86, // Streams pipe error + EUSERS = 87, // Too many users + ENOTSOCK = 88, // Socket operation on non-socket + EDESTADDRREQ = 89, // Destination address required + EMSGSIZE = 90, // Message too long + EPROTOTYPE = 91, // Protocol wrong type for socket + ENOPROTOOPT = 92, // Protocol not available + EPROTONOSUPPORT = 93, // Protocol not supported + ESOCKTNOSUPPORT = 94, // Socket type not supported + EOPNOTSUPP = 95, // Operation not supported on transport endpoint + EPFNOSUPPORT = 96, // Protocol family not supported + EAFNOSUPPORT = 97, // Address family not supported by protocol + EADDRINUSE = 98, // Address already in use + EADDRNOTAVAIL = 99, // Cannot assign requested address + ENETDOWN = 100, // Network is down + ENETUNREACH = 101, // Network is unreachable + ENETRESET = 102, // Network dropped connection because of reset + ECONNABORTED = 103, // Software caused connection abort + ECONNRESET = 104, // Connection reset by peer + ENOBUFS = 105, // No buffer space available + EISCONN = 106, // Transport endpoint is already connected + ENOTCONN = 107, // Transport endpoint is not connected + ESHUTDOWN = 108, // Cannot send after transport endpoint shutdown + ETOOMANYREFS = 109, // Too many references cannot splice + ETIMEDOUT = 110, // Connection timed out + ECONNREFUSED = 111, // Connection refused + EHOSTDOWN = 112, // Host is down + EHOSTUNREACH = 113, // No route to host + EALREADY = 114, // Operation already in progress + EINPROGRESS = 115, // Operation now in progress + ESTALE = 116, // Stale NFS file handle + EUCLEAN = 117, // Structure needs cleaning + ENOTNAM = 118, // Not a XENIX named type file + ENAVAIL = 119, // No XENIX semaphores available + EISNAM = 120, // Is a named type file + EREMOTEIO = 121, // Remote I/O error + EDQUOT = 122, // Quota exceeded + ENOMEDIUM = 123, // No medium found + EMEDIUMTYPE = 124, // Wrong medium type + ECANCELED = 125, // Operation Canceled + ENOKEY = 126, // Required key not available + EKEYEXPIRED = 127, // Key has expired + EKEYREVOKED = 128, // Key has been revoked + EKEYREJECTED = 129, // Key was rejected by service for robust mutexes + EOWNERDEAD = 130, // Owner died + ENOTRECOVERABLE = 131, // State not recoverable + } +} + +pub fn syscall_error(e: Errno, syscall: &str, message: &str) -> i32 { + if *VERBOSE.get().unwrap() > 0 { + let msg = format!("Error in syscall: {} - {:?}: {}", syscall, e, message); + interface::log_to_stderr(&msg); + } + -(e as i32) +} diff --git a/src/interface/file.rs b/src/interface/file.rs new file mode 100644 index 00000000..fddaba48 --- /dev/null +++ b/src/interface/file.rs @@ -0,0 +1,481 @@ +// Author: Nicholas Renner +// +// File related interface +#![allow(dead_code)] + +use dashmap::DashSet; +use parking_lot::Mutex; +use std::env; +pub use std::ffi::CStr as RustCStr; +use std::fs::{self, canonicalize, File, OpenOptions}; +use std::io::{Read, Seek, SeekFrom, Write}; +pub use std::path::{Component as RustPathComponent, Path as RustPath, PathBuf as RustPathBuf}; +use std::slice; +use std::sync::Arc; +pub use std::sync::LazyLock as RustLazyGlobal; + +use crate::interface::errnos::{syscall_error, Errno}; +use libc::{mmap, mremap, munmap, off64_t, MAP_SHARED, MREMAP_MAYMOVE, PROT_READ, PROT_WRITE}; +use std::convert::TryInto; +use std::ffi::c_void; +use std::os::unix::io::{AsRawFd, RawFd}; + +pub fn removefile(filename: String) -> std::io::Result<()> { + let path: RustPathBuf = [".".to_string(), filename].iter().collect(); + + let absolute_filename = canonicalize(&path)?; //will return an error if the file does not exist + + fs::remove_file(absolute_filename)?; + + Ok(()) +} + +pub fn openfile(filename: String, filesize: usize) -> std::io::Result { + EmulatedFile::new(filename, filesize) +} + +pub fn openmetadata(filename: String) -> std::io::Result { + EmulatedFile::new_metadata(filename) +} + +#[derive(Debug)] +pub struct EmulatedFile { + filename: String, + fobj: Option>>, + filesize: usize, +} + +pub fn pathexists(filename: String) -> bool { + let path: RustPathBuf = [".".to_string(), filename.clone()].iter().collect(); + path.exists() +} + +impl EmulatedFile { + fn new(filename: String, filesize: usize) -> std::io::Result { + let f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(filename.clone()) + .unwrap(); + Ok(EmulatedFile { + filename, + fobj: Some(Arc::new(Mutex::new(f))), + filesize, + }) + } + + fn new_metadata(filename: String) -> std::io::Result { + let f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(filename.clone()) + .unwrap(); + + let filesize = f.metadata()?.len(); + + Ok(EmulatedFile { + filename, + fobj: Some(Arc::new(Mutex::new(f))), + filesize: filesize as usize, + }) + } + + pub fn close(&self) -> std::io::Result<()> { + Ok(()) + } + + pub fn shrink(&mut self, length: usize) -> std::io::Result<()> { + if length > self.filesize { + panic!( + "Something is wrong. {} is already smaller than length.", + self.filename + ); + } + match &self.fobj { + None => panic!("{} is already closed.", self.filename), + Some(f) => { + let fobj = f.lock(); + fobj.set_len(length as u64)?; + self.filesize = length; + Ok(()) + } + } + } + + pub fn fdatasync(&self) -> std::io::Result<()> { + match &self.fobj { + None => panic!("{} is already closed.", self.filename), + Some(f) => { + let fobj = f.lock(); + fobj.sync_data()?; + Ok(()) + } + } + } + + pub fn fsync(&self) -> std::io::Result<()> { + match &self.fobj { + None => panic!("{} is already closed.", self.filename), + Some(f) => { + let fobj = f.lock(); + fobj.sync_all()?; + Ok(()) + } + } + } + + pub fn sync_file_range(&self, offset: isize, nbytes: isize, flags: u32) -> i32 { + let fd = &self.as_fd_handle_raw_int(); + let valid_flags = libc::SYNC_FILE_RANGE_WAIT_BEFORE + | libc::SYNC_FILE_RANGE_WRITE + | libc::SYNC_FILE_RANGE_WAIT_AFTER; + if !(flags & !valid_flags == 0) { + return syscall_error( + Errno::EINVAL, + "sync_file_range", + "flags specifies an invalid bit", + ); + } + unsafe { libc::sync_file_range(*fd, offset as off64_t, nbytes as off64_t, flags) } + } + + // Read from file into provided C-buffer + pub fn readat(&self, ptr: *mut u8, length: usize, offset: usize) -> std::io::Result { + let buf = unsafe { + assert!(!ptr.is_null()); + slice::from_raw_parts_mut(ptr, length) + }; + + match &self.fobj { + None => panic!("{} is already closed.", self.filename), + Some(f) => { + let mut fobj = f.lock(); + if offset > self.filesize { + panic!("Seek offset extends past the EOF!"); + } + fobj.seek(SeekFrom::Start(offset as u64))?; + let bytes_read = fobj.read(buf)?; + Ok(bytes_read) + } + } + } + + // Write to file from provided C-buffer + pub fn writeat( + &mut self, + ptr: *const u8, + length: usize, + offset: usize, + ) -> std::io::Result { + let bytes_written; + + let buf = unsafe { + assert!(!ptr.is_null()); + slice::from_raw_parts(ptr, length) + }; + + match &self.fobj { + None => panic!("{} is already closed.", self.filename), + Some(f) => { + let mut fobj = f.lock(); + if offset > self.filesize { + panic!("Seek offset extends past the EOF!"); + } + fobj.seek(SeekFrom::Start(offset as u64))?; + bytes_written = fobj.write(buf)?; + } + } + + if offset + length > self.filesize { + self.filesize = offset + length; + } + + Ok(bytes_written) + } + + // Reads entire file into bytes + pub fn readfile_to_new_bytes(&self) -> std::io::Result> { + match &self.fobj { + None => panic!("{} is already closed.", self.filename), + Some(f) => { + let mut stringbuf = Vec::new(); + let mut fobj = f.lock(); + fobj.read_to_end(&mut stringbuf)?; + Ok(stringbuf) // return new buf string + } + } + } + + // Write to entire file from provided bytes + pub fn writefile_from_bytes(&mut self, buf: &[u8]) -> std::io::Result<()> { + let length = buf.len(); + let offset = self.filesize; + + match &self.fobj { + None => panic!("{} is already closed.", self.filename), + Some(f) => { + let mut fobj = f.lock(); + if offset > self.filesize { + panic!("Seek offset extends past the EOF!"); + } + fobj.seek(SeekFrom::Start(offset as u64))?; + fobj.write(buf)?; + } + } + + if offset + length > self.filesize { + self.filesize = offset + length; + } + + Ok(()) + } + + pub fn zerofill_at(&mut self, offset: usize, count: usize) -> std::io::Result { + let bytes_written; + let buf = vec![0; count]; + + match &self.fobj { + None => panic!("{} is already closed.", self.filename), + Some(f) => { + let mut fobj = f.lock(); + if offset > self.filesize { + panic!("Seek offset extends past the EOF!"); + } + fobj.seek(SeekFrom::Start(offset as u64))?; + bytes_written = fobj.write(buf.as_slice())?; + } + } + + if offset + count > self.filesize { + self.filesize = offset + count; + } + + Ok(bytes_written) + } + + //gets the raw fd handle (integer) from a rust fileobject + pub fn as_fd_handle_raw_int(&self) -> i32 { + if let Some(wrapped_barefile) = &self.fobj { + wrapped_barefile.lock().as_raw_fd() as i32 + } else { + -1 + } + } +} + +pub const COUNTMAPSIZE: usize = 8; +pub const MAP_1MB: usize = usize::pow(2, 20); + +#[derive(Debug)] +pub struct EmulatedFileMap { + filename: String, + fobj: Arc>, + map: Arc>>>, + count: usize, + countmap: Arc>>>, + mapsize: usize, +} + +pub fn mapfilenew(filename: String) -> std::io::Result { + EmulatedFileMap::new(filename) +} + +impl EmulatedFileMap { + fn new(filename: String) -> std::io::Result { + let f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(filename.clone()) + .unwrap(); + + let mapsize = MAP_1MB - COUNTMAPSIZE; + // set the file equal to where were mapping the count and the actual map + let _newsize = f.set_len((COUNTMAPSIZE + mapsize) as u64).unwrap(); + + let map: Vec; + let countmap: Vec; + + // here were going to map the first 8 bytes of the file as the "count" (amount of bytes written), and then map another 1MB for logging + unsafe { + let map_addr = mmap( + 0 as *mut c_void, + MAP_1MB, + PROT_READ | PROT_WRITE, + MAP_SHARED, + f.as_raw_fd() as i32, + 0 as i64, + ); + countmap = Vec::::from_raw_parts(map_addr as *mut u8, COUNTMAPSIZE, COUNTMAPSIZE); + let map_ptr = map_addr as *mut u8; + map = + Vec::::from_raw_parts(map_ptr.offset(COUNTMAPSIZE as isize), mapsize, mapsize); + } + + Ok(EmulatedFileMap { + filename, + fobj: Arc::new(Mutex::new(f)), + map: Arc::new(Mutex::new(Some(map))), + count: 0, + countmap: Arc::new(Mutex::new(Some(countmap))), + mapsize, + }) + } + + pub fn write_to_map(&mut self, bytes_to_write: &[u8]) -> std::io::Result<()> { + let writelen = bytes_to_write.len(); + + // if we're writing past the current map, increase the map another 1MB + if writelen + self.count > self.mapsize { + self.extend_map(); + } + + let mut mapopt = self.map.lock(); + let map = mapopt.as_deref_mut().unwrap(); + + let mapslice = &mut map[self.count..(self.count + writelen)]; + mapslice.copy_from_slice(bytes_to_write); + self.count += writelen; + + // update the bytes written in the map portion + let mut countmapopt = self.countmap.lock(); + let countmap = countmapopt.as_deref_mut().unwrap(); + countmap.copy_from_slice(&self.count.to_be_bytes()); + + Ok(()) + } + + fn extend_map(&mut self) { + // open count and map to resize mmap, and file to increase file size + let mut mapopt = self.map.lock(); + let map = mapopt.take().unwrap(); + let mut countmapopt = self.countmap.lock(); + let countmap = countmapopt.take().unwrap(); + let f = self.fobj.lock(); + + // add another 1MB to mapsize + let new_mapsize = self.mapsize + MAP_1MB; + let _newsize = f.set_len((COUNTMAPSIZE + new_mapsize) as u64).unwrap(); + + let newmap: Vec; + let newcountmap: Vec; + + // destruct count and map and re-map + unsafe { + let (old_count_map_addr, countlen, _countcap) = countmap.into_raw_parts(); + assert_eq!(COUNTMAPSIZE, countlen); + let (_old_map_addr, len, _cap) = map.into_raw_parts(); + assert_eq!(self.mapsize, len); + let map_addr = mremap( + old_count_map_addr as *mut c_void, + COUNTMAPSIZE + self.mapsize, + COUNTMAPSIZE + new_mapsize, + MREMAP_MAYMOVE, + ); + + newcountmap = + Vec::::from_raw_parts(map_addr as *mut u8, COUNTMAPSIZE, COUNTMAPSIZE); + let map_ptr = map_addr as *mut u8; + newmap = Vec::::from_raw_parts( + map_ptr.offset(COUNTMAPSIZE as isize), + new_mapsize, + new_mapsize, + ); + } + + // replace maps + mapopt.replace(newmap); + countmapopt.replace(newcountmap); + self.mapsize = new_mapsize; + } + + pub fn close(&self) -> std::io::Result<()> { + let mut mapopt = self.map.lock(); + let map = mapopt.take().unwrap(); + let mut countmapopt = self.countmap.lock(); + let countmap = countmapopt.take().unwrap(); + + unsafe { + let (countmap_addr, countlen, _countcap) = countmap.into_raw_parts(); + assert_eq!(COUNTMAPSIZE, countlen); + munmap(countmap_addr as *mut c_void, COUNTMAPSIZE); + + let (map_addr, len, _cap) = map.into_raw_parts(); + assert_eq!(self.mapsize, len); + munmap(map_addr as *mut c_void, self.mapsize); + } + + Ok(()) + } +} + +#[derive(Debug)] +pub struct ShmFile { + fobj: Arc>, + key: i32, + size: usize, +} + +pub fn new_shm_backing(key: i32, size: usize) -> std::io::Result { + ShmFile::new(key, size) +} + +// Mimic shared memory in Linux by creating a file backing and truncating it to the segment size +// We can then safely unlink the file while still holding a descriptor to that segment, +// which we can use to map shared across cages. +impl ShmFile { + fn new(key: i32, size: usize) -> std::io::Result { + // open file "shm-#id" + let filename = format!("{}{}", "shm-", key); + let f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(filename.clone()) + .unwrap(); + // truncate file to size + f.set_len(size as u64)?; + // unlink file + fs::remove_file(filename)?; + let shmfile = ShmFile { + fobj: Arc::new(Mutex::new(f)), + key, + size, + }; + + Ok(shmfile) + } + + //gets the raw fd handle (integer) from a rust fileobject + pub fn as_fd_handle_raw_int(&self) -> i32 { + self.fobj.lock().as_raw_fd() as i32 + } +} + +// convert a series of big endian bytes to a size +pub fn convert_bytes_to_size(bytes_to_write: &[u8]) -> usize { + let sizearray: [u8; 8] = bytes_to_write.try_into().unwrap(); + usize::from_be_bytes(sizearray) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::NamedTempFile; + + #[test] + fn test_path_exists_true() { + let temp_file = NamedTempFile::new().unwrap(); + let file_path = temp_file.path().to_str().unwrap().to_string(); + assert!(pathexists(file_path)); + } + + #[test] + fn test_path_exists_false() { + // Test that pathexists returns false for a non-existent file + let non_existent_file = "/tmp/non_existent_file.txt"; + assert!(!pathexists(non_existent_file.to_string())); + } +} diff --git a/src/interface/misc.rs b/src/interface/misc.rs new file mode 100644 index 00000000..1c5e99e0 --- /dev/null +++ b/src/interface/misc.rs @@ -0,0 +1,633 @@ +// Author: Nicholas Renner +// +// Misc functions for interface +// Random, locks, etc. +#![allow(dead_code)] + +pub use dashmap::{ + mapref::entry::Entry as RustHashEntry, DashMap as RustHashMap, DashSet as RustHashSet, +}; +pub use parking_lot::{ + Condvar, Mutex, RwLock as RustLock, RwLockReadGuard as RustLockReadGuard, + RwLockWriteGuard as RustLockWriteGuard, +}; +use std::cell::RefCell; +pub use std::cmp::{max as rust_max, min as rust_min}; +pub use std::collections::VecDeque as RustDeque; +use std::fs::File; +use std::io::{self, Read, Write}; +use std::str::{from_utf8, Utf8Error}; +pub use std::sync::atomic::{ + AtomicBool as RustAtomicBool, AtomicI32 as RustAtomicI32, AtomicU16 as RustAtomicU16, + AtomicU32 as RustAtomicU32, AtomicU64 as RustAtomicU64, AtomicUsize as RustAtomicUsize, + Ordering as RustAtomicOrdering, +}; +pub use std::sync::Arc as RustRfc; +pub use std::thread::spawn as helper_thread; + +use libc::{mmap, pthread_exit, pthread_kill, pthread_self, sched_yield}; +use std::ffi::c_void; + +pub use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize}; +pub use serde_cbor::{ + from_slice as serde_deserialize_from_bytes, ser::to_vec_packed as serde_serialize_to_bytes, +}; + +use crate::interface; +use crate::interface::errnos::VERBOSE; +use crate::interface::types::SigsetType; +use crate::safeposix::syscalls::fs_constants::SEM_VALUE_MAX; +use std::sync::LazyLock; +use std::time::Duration; + +pub const MAXCAGEID: i32 = 1024; +const EXIT_SUCCESS: i32 = 0; + +pub static RUSTPOSIX_TESTSUITE: LazyLock = + LazyLock::new(|| RustAtomicBool::new(false)); + +thread_local! { + static TRUSTED_SIGNAL_FLAG: RefCell = RefCell::new(0); +} + +use crate::safeposix::cage::Cage; + +pub static mut CAGE_TABLE: Vec>> = Vec::new(); + +pub fn check_cageid(cageid: u64) { + if cageid >= MAXCAGEID as u64 { + panic!("Cage ID is outside of valid range"); + } +} + +pub fn cagetable_init() { + unsafe { + for _cage in 0..MAXCAGEID { + CAGE_TABLE.push(None); + } + } +} + +pub fn cagetable_insert(cageid: u64, cageobj: Cage) { + check_cageid(cageid); + let _insertret = unsafe { CAGE_TABLE[cageid as usize].insert(RustRfc::new(cageobj)) }; +} + +pub fn cagetable_remove(cageid: u64) { + check_cageid(cageid); + unsafe { CAGE_TABLE[cageid as usize].take() }; +} + +pub fn cagetable_getref(cageid: u64) -> RustRfc { + check_cageid(cageid); + unsafe { CAGE_TABLE[cageid as usize].as_ref().unwrap().clone() } +} + +pub fn cagetable_getref_opt(cageid: u64) -> Option> { + check_cageid(cageid); + unsafe { + match CAGE_TABLE[cageid as usize].as_ref() { + Some(cage) => Some(cage.clone()), + None => None, + } + } +} + +pub fn cagetable_clear() { + let mut exitvec = Vec::new(); + unsafe { + for cage in CAGE_TABLE.iter_mut() { + let cageopt = cage.take(); + if cageopt.is_some() { + exitvec.push(cageopt.unwrap()); + } + } + } + + for cage in exitvec { + cage.exit_syscall(EXIT_SUCCESS); + } +} + +pub fn log_from_ptr(buf: *const u8, length: usize) { + if let Ok(s) = from_utf8(unsafe { std::slice::from_raw_parts(buf, length) }) { + log_to_stdout(s); + } +} + +// Print text to stdout +pub fn log_to_stdout(s: &str) { + print!("{}", s); +} + +pub fn log_verbose(s: &str) { + if *VERBOSE.get().unwrap() > 0 { + log_to_stdout(s); + } +} + +// Print text to stderr +pub fn log_to_stderr(s: &str) { + eprintln!("{}", s); +} + +// Flush contents of stdout +pub fn flush_stdout() { + io::stdout().flush().unwrap(); +} + +pub fn get_errno() -> i32 { + (unsafe { *libc::__errno_location() }) as i32 +} + +// Cancellation functions + +pub fn lind_threadexit() { + unsafe { + pthread_exit(0 as *mut c_void); + } +} + +pub fn lind_threadkill(thread_id: u64, sig: i32) -> i32 { + unsafe { pthread_kill(thread_id, sig) as i32 } +} + +pub fn get_pthreadid() -> u64 { + unsafe { pthread_self() as u64 } +} + +pub fn lind_yield() { + unsafe { + sched_yield(); + } +} + +// this function checks if a thread is killable and returns that state +pub fn check_thread(cageid: u64, tid: u64) -> bool { + let cage = cagetable_getref(cageid); + let killable = *cage.thread_table.get(&tid).unwrap(); + killable +} + +// in-rustposix cancelpoints checks if the thread is killable, +// and if sets killable back to false and kills the thread +pub fn cancelpoint(cageid: u64) { + if RUSTPOSIX_TESTSUITE.load(RustAtomicOrdering::Relaxed) { + return; + } // we don't use this when testing rustposix standalone + + let pthread_id = get_pthreadid(); + if check_thread(cageid, pthread_id) { + let cage = cagetable_getref(cageid); + cage.thread_table.insert(pthread_id, false); + lind_threadexit(); + } +} + +pub fn signalflag_set(value: u64) { + TRUSTED_SIGNAL_FLAG.with(|v| *v.borrow_mut() = value); +} + +pub fn signalflag_get() -> u64 { + TRUSTED_SIGNAL_FLAG.with(|v| *v.borrow()) +} + +pub fn sigcheck() -> bool { + if RUSTPOSIX_TESTSUITE.load(RustAtomicOrdering::Relaxed) { + return false; + } + + let boolptr = signalflag_get() as *const bool; + let sigbool = unsafe { *boolptr }; + + sigbool +} + +pub fn fillrandom(bufptr: *mut u8, count: usize) -> i32 { + let slice = unsafe { std::slice::from_raw_parts_mut(bufptr, count) }; + let mut f = std::fs::OpenOptions::new() + .read(true) + .write(false) + .open("/dev/urandom") + .unwrap(); + f.read(slice).unwrap() as i32 +} +pub fn fillzero(bufptr: *mut u8, count: usize) -> i32 { + let slice = unsafe { std::slice::from_raw_parts_mut(bufptr, count) }; + for i in 0..count { + slice[i] = 0u8; + } + count as i32 +} + +pub fn fill(bufptr: *mut u8, count: usize, values: &Vec) -> i32 { + let slice = unsafe { std::slice::from_raw_parts_mut(bufptr, count) }; + slice.copy_from_slice(&values[..count]); + count as i32 +} + +pub fn copy_fromrustdeque_sized(bufptr: *mut u8, count: usize, vecdeq: &RustDeque) { + let (slice1, slice2) = vecdeq.as_slices(); + if slice1.len() >= count { + unsafe { + std::ptr::copy(slice1.as_ptr(), bufptr, count); + } + } else { + unsafe { + std::ptr::copy(slice1.as_ptr(), bufptr, slice1.len()); + } + unsafe { + std::ptr::copy( + slice2.as_ptr(), + bufptr.wrapping_offset(slice1.len() as isize), + count - slice1.len(), + ); + } + } +} + +pub fn extend_fromptr_sized(bufptr: *const u8, count: usize, vecdeq: &mut RustDeque) { + let byteslice = unsafe { std::slice::from_raw_parts(bufptr, count) }; + vecdeq.extend(byteslice.iter()); +} + +// Wrapper to return a dictionary (hashmap) +pub fn new_hashmap() -> RustHashMap { + RustHashMap::new() +} + +#[cfg(target_os = "macos")] +type CharPtr = *const u8; + +#[cfg(not(target_os = "macos"))] +type CharPtr = *const i8; + +pub unsafe fn charstar_to_ruststr<'a>(cstr: CharPtr) -> Result<&'a str, Utf8Error> { + std::ffi::CStr::from_ptr(cstr as *const _).to_str() //returns a result to be unwrapped later +} + +pub fn libc_mmap(addr: *mut u8, len: usize, prot: i32, flags: i32, fildes: i32, off: i64) -> i32 { + return ((unsafe { mmap(addr as *mut c_void, len, prot, flags, fildes, off) } as i64) + & 0xffffffff) as i32; +} + +// Sigset Operations +// +// sigsetops defined here are different from the ones in glibc. Since the sigset is just a u64 +// bitmask, we can just return the modified version of the sigset instead of changing it in-place. +// It would also avoid any ownership issue and make the code cleaner. + +pub fn lind_sigemptyset() -> SigsetType { + 0 +} + +pub fn lind_sigfillset() -> SigsetType { + u64::MAX +} + +pub fn lind_sigaddset(set: SigsetType, signum: i32) -> SigsetType { + set | (1 << (signum - 1)) +} + +pub fn lind_sigdelset(set: SigsetType, signum: i32) -> SigsetType { + set & !(1 << (signum - 1)) +} + +pub fn lind_sigismember(set: SigsetType, signum: i32) -> bool { + set & (1 << (signum - 1)) != 0 +} + +// Signals +pub fn lind_kill_from_id(cage_id: u64, sig: i32) { + if let Some(cage) = cagetable_getref_opt(cage_id as u64) { + let cage_main_thread_id = cage.main_threadid.load(RustAtomicOrdering::Relaxed); + assert!(cage_main_thread_id != 0); + lind_threadkill(cage_main_thread_id, sig); + } +} + +#[derive(Debug)] +pub struct AdvisoryLock { + //0 signifies unlocked, -1 signifies locked exclusively, positive number signifies that many shared lock holders + advisory_lock: RustRfc>, + advisory_condvar: Condvar, +} + +/* +* AdvisoryLock is used to implement advisory locking for files. +* Specifically, it is used by the flock syscall. +* If works as follows: The underying mutex has a guard value associated with it. +* A guard value of zero indicates that it is unlocked. +* In case an exclusive lock is held, the guard value is set to -1. +* In case a shared lock is held, the guard value is incremented by 1. +*/ +impl AdvisoryLock { + pub fn new() -> Self { + Self { + advisory_lock: RustRfc::new(Mutex::new(0)), + advisory_condvar: Condvar::new(), + } + } + + // lock_ex is used to acquire an exclusive lock + // if the lock cannot be obtained, it waits + pub fn lock_ex(&self) { + let mut waitedguard = self.advisory_lock.lock(); + while *waitedguard != 0 { + self.advisory_condvar.wait(&mut waitedguard); + } + *waitedguard = -1; + } + + // lock_sh is used to acquire a shared lock + // if the lock cannot be obtained, it waits + pub fn lock_sh(&self) { + let mut waitedguard = self.advisory_lock.lock(); + while *waitedguard < 0 { + self.advisory_condvar.wait(&mut waitedguard); + } + *waitedguard += 1; + } + // try_lock_ex is used to try to acquire an exclusive lock + // if the lock cannot be obtained, it returns false + pub fn try_lock_ex(&self) -> bool { + if let Some(mut guard) = self.advisory_lock.try_lock() { + if *guard == 0 { + *guard = -1; + return true; + } + } + false + } + // try_lock_sh is used to try to acquire a shared lock + // if the lock cannot be obtained, it returns false + pub fn try_lock_sh(&self) -> bool { + if let Some(mut guard) = self.advisory_lock.try_lock() { + if *guard >= 0 { + *guard += 1; + return true; + } + } + false + } + + /* + * unlock is used to release a lock + * If a shared lock was held(guard value > 0), it decrements the guard value by one + * if no more shared locks are held (i.e. the guard value is now zero), then it notifies a waiting writer + * If an exclusive lock was held, it sets the guard value to zero and notifies all waiting readers and writers + */ + pub fn unlock(&self) -> bool { + let mut guard = self.advisory_lock.lock(); + + // check if a shared lock is held + if *guard > 0 { + // release one shared lock by decrementing the guard value + *guard -= 1; + + // if no more shared locks are held, notify a waiting writer and return + // only a writer could be waiting at this point + if *guard == 0 { + self.advisory_condvar.notify_one(); + } + true + } else if *guard == -1 { + // check if an exclusive lock is held + // release the exclusive lock by setting guard to 0 + *guard = 0; + + // notify any waiting reads or writers and return + self.advisory_condvar.notify_all(); + true + } else { + false + } + } +} + +pub struct RawMutex { + inner: libc::pthread_mutex_t, +} + +impl RawMutex { + pub fn create() -> Result { + let libcret; + let mut retval = Self { + inner: unsafe { std::mem::zeroed() }, + }; + unsafe { + libcret = libc::pthread_mutex_init( + (&mut retval.inner) as *mut libc::pthread_mutex_t, + std::ptr::null(), + ); + } + if libcret < 0 { + Err(libcret) + } else { + Ok(retval) + } + } + + pub fn lock(&self) -> i32 { + unsafe { + libc::pthread_mutex_lock( + (&self.inner) as *const libc::pthread_mutex_t as *mut libc::pthread_mutex_t, + ) + } + } + + pub fn trylock(&self) -> i32 { + unsafe { + libc::pthread_mutex_trylock( + (&self.inner) as *const libc::pthread_mutex_t as *mut libc::pthread_mutex_t, + ) + } + } + + pub fn unlock(&self) -> i32 { + unsafe { + libc::pthread_mutex_unlock( + (&self.inner) as *const libc::pthread_mutex_t as *mut libc::pthread_mutex_t, + ) + } + } +} + +impl std::fmt::Debug for RawMutex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("") + } +} + +impl Drop for RawMutex { + fn drop(&mut self) { + unsafe { + libc::pthread_mutex_destroy((&mut self.inner) as *mut libc::pthread_mutex_t); + } + } +} + +pub struct RawCondvar { + inner: libc::pthread_cond_t, +} + +impl RawCondvar { + pub fn create() -> Result { + let libcret; + let mut retval = Self { + inner: unsafe { std::mem::zeroed() }, + }; + unsafe { + libcret = libc::pthread_cond_init( + (&mut retval.inner) as *mut libc::pthread_cond_t, + std::ptr::null(), + ); + } + if libcret < 0 { + Err(libcret) + } else { + Ok(retval) + } + } + + pub fn signal(&self) -> i32 { + unsafe { + libc::pthread_cond_signal( + (&self.inner) as *const libc::pthread_cond_t as *mut libc::pthread_cond_t, + ) + } + } + + pub fn broadcast(&self) -> i32 { + unsafe { + libc::pthread_cond_broadcast( + (&self.inner) as *const libc::pthread_cond_t as *mut libc::pthread_cond_t, + ) + } + } + + pub fn wait(&self, mutex: &RawMutex) -> i32 { + unsafe { + libc::pthread_cond_wait( + (&self.inner) as *const libc::pthread_cond_t as *mut libc::pthread_cond_t, + (&mutex.inner) as *const libc::pthread_mutex_t as *mut libc::pthread_mutex_t, + ) + } + } + + pub fn timedwait(&self, mutex: &RawMutex, abs_duration: Duration) -> i32 { + let abstime = libc::timespec { + tv_sec: abs_duration.as_secs() as i64, + tv_nsec: (abs_duration.as_nanos() % 1000000000) as i64, + }; + unsafe { + libc::pthread_cond_timedwait( + (&self.inner) as *const libc::pthread_cond_t as *mut libc::pthread_cond_t, + (&mutex.inner) as *const libc::pthread_mutex_t as *mut libc::pthread_mutex_t, + (&abstime) as *const libc::timespec, + ) + } + } +} + +impl Drop for RawCondvar { + fn drop(&mut self) { + unsafe { + libc::pthread_cond_destroy((&mut self.inner) as *mut libc::pthread_cond_t); + } + } +} + +impl std::fmt::Debug for RawCondvar { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("") + } +} + +/* +* RustSemaphore is the rust version of sem_t +*/ +#[derive(Debug)] +pub struct RustSemaphore { + pub value: Mutex, + pub is_shared: RustAtomicBool, +} + +// Semaphore implementation +// we busy wait on lock if value is 0, otherwise we decrease the value +// unlock will increase value up to SEM_VALUE_MAX +impl RustSemaphore { + pub fn new(value_handle: u32, is_shared: bool) -> Self { + Self { + value: Mutex::new(value_handle), + is_shared: RustAtomicBool::new(is_shared), + } + } + + pub fn lock(&self) { + loop { + // acquire the mutex lock + let mut value = self.value.lock(); + if *value == 0 { + // wait for semaphore to be unlocked by another process/thread + interface::lind_yield(); + } else { + // decrement the value + *value = if *value > 0 { *value - 1 } else { 0 }; + break; + } + } + } + + pub fn unlock(&self) -> bool { + // acquire the mutex lock + let mut value = self.value.lock(); + // check if the maximum allowable value for a semaphore has been reached + if *value < SEM_VALUE_MAX { + // increment the value + *value = *value + 1; + return true; + } else { + return false; + } + } + + pub fn get_value(&self) -> i32 { + // returns the value of the semaphore + *self.value.lock() as i32 + } + + pub fn trylock(&self) -> bool { + // acquire the mutex lock + let mut value = self.value.lock(); + if *value == 0 { + // semaphore is locked by another process/thread + return false; + } else { + // decrement the value + *value = if *value > 0 { *value - 1 } else { 0 }; + return true; + } + } + + pub fn timedlock(&self, timeout: Duration) -> bool { + // start the timer to check for timeout + let start_time = interface::starttimer(); + loop { + // acquire the mutex lock + let mut value = self.value.lock(); + if *value == 0 { + // check if we have timed out + let elapsed_time = interface::readtimer(start_time); + if elapsed_time > timeout { + return false; + } + // if not timed out wait for semaphore to be unlocked by another process/thread + interface::lind_yield(); + } else { + *value = if *value > 0 { *value - 1 } else { 0 }; + return true; + } + } + } +} diff --git a/src/interface/mod.rs b/src/interface/mod.rs new file mode 100644 index 00000000..82171c29 --- /dev/null +++ b/src/interface/mod.rs @@ -0,0 +1,19 @@ +// Author: Nicholas Renner +// +// Module definitions for the SafePOSIX Rust interface +// this interface limits kernel access from Rust to the popular paths as defined in Lock-in-Pop + +mod comm; +pub mod errnos; +mod file; +mod misc; +mod pipe; +mod timer; +pub mod types; +pub use comm::*; +pub use errnos::*; +pub use file::*; +pub use misc::*; +pub use pipe::*; +pub use timer::*; +pub use types::*; diff --git a/src/interface/pipe.rs b/src/interface/pipe.rs new file mode 100644 index 00000000..85c983ca --- /dev/null +++ b/src/interface/pipe.rs @@ -0,0 +1,200 @@ +// Author: Nicholas Renner +// +// Pipes for SafePOSIX based on Lock-Free Circular Buffer + +#![allow(dead_code)] +use crate::interface; +use crate::interface::errnos::{syscall_error, Errno}; + +use parking_lot::Mutex; +use ringbuf::{Consumer, Producer, RingBuffer}; +use std::cmp::min; +use std::fmt; +use std::slice; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use std::sync::Arc; + +const O_RDONLY: i32 = 0o0; +const O_WRONLY: i32 = 0o1; +const O_RDWRFLAGS: i32 = 0o3; +const PAGE_SIZE: usize = 4096; + +const CANCEL_CHECK_INTERVAL: usize = 1048576; // check to cancel pipe reads every 2^20 iterations + +pub fn new_pipe(size: usize) -> EmulatedPipe { + EmulatedPipe::new_with_capacity(size) +} + +#[derive(Clone)] +pub struct EmulatedPipe { + write_end: Arc>>, + read_end: Arc>>, + pub refcount_write: Arc, + pub refcount_read: Arc, + eof: Arc, + size: usize, +} + +impl EmulatedPipe { + pub fn new_with_capacity(size: usize) -> EmulatedPipe { + let rb = RingBuffer::::new(size); + let (prod, cons) = rb.split(); + EmulatedPipe { + write_end: Arc::new(Mutex::new(prod)), + read_end: Arc::new(Mutex::new(cons)), + refcount_write: Arc::new(AtomicU32::new(1)), + refcount_read: Arc::new(AtomicU32::new(1)), + eof: Arc::new(AtomicBool::new(false)), + size: size, + } + } + + pub fn set_eof(&self) { + self.eof.store(true, Ordering::Relaxed); + } + + pub fn get_write_ref(&self) -> u32 { + self.refcount_write.load(Ordering::Relaxed) + } + + pub fn get_read_ref(&self) -> u32 { + self.refcount_read.load(Ordering::Relaxed) + } + + pub fn incr_ref(&self, flags: i32) { + if (flags & O_RDWRFLAGS) == O_RDONLY { + self.refcount_read.fetch_add(1, Ordering::Relaxed); + } + if (flags & O_RDWRFLAGS) == O_WRONLY { + self.refcount_write.fetch_add(1, Ordering::Relaxed); + } + } + + pub fn decr_ref(&self, flags: i32) { + if (flags & O_RDWRFLAGS) == O_RDONLY { + self.refcount_read.fetch_sub(1, Ordering::Relaxed); + } + if (flags & O_RDWRFLAGS) == O_WRONLY { + self.refcount_write.fetch_sub(1, Ordering::Relaxed); + } + } + + pub fn check_select_read(&self) -> bool { + let read_end = self.read_end.lock(); + let pipe_space = read_end.len(); + + if (pipe_space > 0) || self.eof.load(Ordering::SeqCst) { + return true; + } else { + return false; + } + } + pub fn check_select_write(&self) -> bool { + let write_end = self.write_end.lock(); + let pipe_space = write_end.remaining(); + + return pipe_space != 0; + } + + // Write length bytes from pointer into pipe + pub fn write_to_pipe(&self, ptr: *const u8, length: usize, nonblocking: bool) -> i32 { + let mut bytes_written = 0; + + let buf = unsafe { + assert!(!ptr.is_null()); + slice::from_raw_parts(ptr, length) + }; + + let mut write_end = self.write_end.lock(); + + let pipe_space = write_end.remaining(); + if nonblocking && (pipe_space == 0) { + return syscall_error( + Errno::EAGAIN, + "write", + "there is no data available right now, try again later", + ); + } + + while bytes_written < length { + if self.get_read_ref() == 0 { + return syscall_error(Errno::EPIPE, "write", "broken pipe"); + } // EPIPE, all read ends are closed + + let remaining = write_end.remaining(); + + if remaining == 0 { + interface::lind_yield(); //yield on a full pipe + continue; + } + // we write if the pipe is empty, otherwise we try to limit writes to 4096 bytes (unless whats leftover of this write is < 4096) + if remaining != self.size + && (length - bytes_written) > PAGE_SIZE + && remaining < PAGE_SIZE + { + continue; + }; + let bytes_to_write = min(length, bytes_written as usize + remaining); + write_end.push_slice(&buf[bytes_written..bytes_to_write]); + bytes_written = bytes_to_write; + } + + bytes_written as i32 + } + + // Read length bytes from the pipe into pointer + // Will wait for bytes unless pipe is empty and eof is set. + pub fn read_from_pipe(&self, ptr: *mut u8, length: usize, nonblocking: bool) -> i32 { + let buf = unsafe { + assert!(!ptr.is_null()); + slice::from_raw_parts_mut(ptr, length) + }; + + let mut read_end = self.read_end.lock(); + let mut pipe_space = read_end.len(); + if nonblocking && (pipe_space == 0) { + if self.eof.load(Ordering::SeqCst) { + return 0; + } + return syscall_error( + Errno::EAGAIN, + "read", + "there is no data available right now, try again later", + ); + } + + // wait for something to be in the pipe, but break on eof + // check cancel point after 2^20 cycles just in case + let mut count = 0; + while pipe_space == 0 { + if self.eof.load(Ordering::SeqCst) { + return 0; + } + + if count == CANCEL_CHECK_INTERVAL { + return -(Errno::EAGAIN as i32); // we've tried enough, return to pipe + } + + pipe_space = read_end.len(); + count = count + 1; + if pipe_space == 0 { + interface::lind_yield(); + } // yield on an empty pipe + } + + let bytes_to_read = min(length, pipe_space); + read_end.pop_slice(&mut buf[0..bytes_to_read]); + + bytes_to_read as i32 + } +} + +impl fmt::Debug for EmulatedPipe { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EmulatedPipe") + .field("refcount read", &self.refcount_read) + .field("refcount write", &self.refcount_write) + .field("eof", &self.eof) + .finish() + } +} diff --git a/src/interface/timer.rs b/src/interface/timer.rs new file mode 100644 index 00000000..28e94fe3 --- /dev/null +++ b/src/interface/timer.rs @@ -0,0 +1,141 @@ +// Author: Nicholas Renner +// +// Timer functions for Rust interface. +#![allow(dead_code)] + +use std::sync::{Arc, Mutex, MutexGuard}; +use std::thread; +pub use std::time::Duration as RustDuration; +pub use std::time::Instant as RustInstant; +use std::time::SystemTime; + +use crate::interface::lind_kill_from_id; + +pub fn timestamp() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() +} + +// Create a new timer +pub fn starttimer() -> RustInstant { + RustInstant::now() +} + +// Return time since timer was started +pub fn readtimer(now: RustInstant) -> RustDuration { + now.elapsed() +} + +// Sleep function to sleep for specified duration +pub fn sleep(dur: RustDuration) { + thread::sleep(dur); +} + +#[derive(Debug)] +struct _IntervalTimer { + pub cageid: u64, + pub init_instant: RustInstant, // The instant this process is created + + pub start_instant: RustInstant, + pub curr_duration: RustDuration, + pub next_duration: RustDuration, + + pub is_ticking: bool, +} + +#[derive(Clone, Debug)] +pub struct IntervalTimer { + _ac: Arc>, +} + +impl IntervalTimer { + pub fn new(cageid: u64) -> Self { + Self { + _ac: Arc::new(Mutex::new(_IntervalTimer { + cageid: cageid, + init_instant: RustInstant::now(), + start_instant: RustInstant::now(), + curr_duration: RustDuration::ZERO, + next_duration: RustDuration::ZERO, + is_ticking: false, + })), + } + } + + // Similar to getitimer. Returns (current value, next value) + pub fn get_itimer(&self) -> (RustDuration, RustDuration) { + let guard = self._ac.lock().unwrap(); + + (guard.curr_duration, guard.next_duration) + } + + fn _set_itimer( + &self, + guard: &mut MutexGuard<_IntervalTimer>, + curr_duration: RustDuration, + next_duration: RustDuration, + ) { + if curr_duration.is_zero() { + guard.is_ticking = false; + } else { + guard.start_instant = RustInstant::now(); + guard.curr_duration = curr_duration; + guard.next_duration = next_duration; + + if !guard.is_ticking { + guard.is_ticking = true; + + let self_dup = self.clone(); + thread::spawn(move || { + // There is a chance that there'll be two ticking threads running + // at the same time + self_dup.tick(); + }); + } + } + } + + pub fn set_itimer(&self, curr_duration: RustDuration, next_duration: RustDuration) { + let mut guard = self._ac.lock().unwrap(); + self._set_itimer(&mut guard, curr_duration, next_duration); + } + + pub fn tick(&self) { + loop { + { + let mut guard = self._ac.lock().unwrap(); + + if guard.is_ticking { + let remaining_seconds = guard + .curr_duration + .saturating_sub(guard.start_instant.elapsed()); + + if remaining_seconds == RustDuration::ZERO { + lind_kill_from_id(guard.cageid, 14); + + let new_curr_duration = guard.next_duration; + // Repeat the intervals until user cancel it + let new_next_duration = guard.next_duration; + + self._set_itimer(&mut guard, new_curr_duration, new_next_duration); + // Calling self.set_itimer will automatically turn of the timer if + // next_duration is ZERO + } + } else { + break; + } + } + + thread::sleep(RustDuration::from_millis(20)); // One jiffy + } + } + + pub fn clone_with_new_cageid(&self, cageid: u64) -> Self { + let mut guard = self._ac.lock().unwrap(); + guard.cageid = cageid; + + self.clone() + } +} diff --git a/src/interface/types.rs b/src/interface/types.rs new file mode 100644 index 00000000..97a04dfd --- /dev/null +++ b/src/interface/types.rs @@ -0,0 +1,787 @@ +#![allow(dead_code)] +use crate::interface; +use crate::interface::errnos::{syscall_error, Errno}; + +const SIZEOF_SOCKADDR: u32 = 16; + +//redefining the FSData struct in this file so that we maintain flow of program +//derive eq attributes for testing whether the structs equal other fsdata structs from stat/fstat +#[derive(Eq, PartialEq, Default)] +#[repr(C)] +pub struct FSData { + pub f_type: u64, + pub f_bsize: u64, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_bavail: u64, + //total files in the file system -- should be infinite + pub f_files: u64, + //free files in the file system -- should be infinite + pub f_ffiles: u64, + pub f_fsid: u64, + //not really a limit for naming, but 254 works + pub f_namelen: u64, + //arbitrary val for blocksize as well + pub f_frsize: u64, + pub f_spare: [u8; 32], +} + +//redefining the StatData struct in this file so that we maintain flow of program +//derive eq attributes for testing whether the structs equal other statdata structs from stat/fstat +#[derive(Eq, PartialEq, Default)] +#[repr(C)] +pub struct StatData { + pub st_dev: u64, + pub st_ino: usize, + pub st_mode: u32, + pub st_nlink: u32, + pub st_uid: u32, + pub st_gid: u32, + pub st_rdev: u64, + pub st_size: usize, + pub st_blksize: i32, + pub st_blocks: u32, + //currently we don't populate or care about the time bits here + pub st_atim: (u64, u64), + pub st_mtim: (u64, u64), + pub st_ctim: (u64, u64), +} + +//R Limit for getrlimit system call +#[repr(C)] +pub struct Rlimit { + pub rlim_cur: u64, + pub rlim_max: u64, +} + +#[derive(Eq, PartialEq, Default, Copy, Clone)] +#[repr(C)] +pub struct PipeArray { + pub readfd: i32, + pub writefd: i32, +} + +#[derive(Eq, PartialEq, Default, Copy, Clone)] +#[repr(C)] +pub struct SockPair { + pub sock1: i32, + pub sock2: i32, +} + +//EPOLL +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct EpollEvent { + pub events: u32, + pub fd: i32, //in native this is a union which could be one of a number of things + //however, we only support EPOLL_CTL subcommands which take the fd +} + +#[derive(Debug, Default)] +#[repr(C)] +pub struct PollStruct { + pub fd: i32, + pub events: i16, + pub revents: i16, +} + +#[repr(C)] +pub struct SockaddrDummy { + pub sa_family: u16, + pub _sa_data: [u16; 14], +} + +#[repr(C)] +pub struct TimeVal { + pub tv_sec: i64, + pub tv_usec: i64, +} + +#[repr(C)] +pub struct ITimerVal { + pub it_interval: TimeVal, + pub it_value: TimeVal, +} + +#[repr(C)] +pub struct TimeSpec { + pub tv_sec: i64, + pub tv_nsec: i64, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub union IoctlPtrUnion { + pub int_ptr: *mut i32, + pub c_char_ptr: *mut u8, //Right now, we do not support passing struct pointers to ioctl as the related call are not implemented +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct IpcPermStruct { + pub __key: i32, + pub uid: u32, + pub gid: u32, + pub cuid: u32, + pub cgid: u32, + pub mode: u16, + pub __pad1: u16, + pub __seq: u16, + pub __pad2: u16, + pub __unused1: u32, + pub __unused2: u32, +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct ShmidsStruct { + pub shm_perm: IpcPermStruct, + pub shm_segsz: u32, + pub shm_atime: isize, + pub shm_dtime: isize, + pub shm_ctime: isize, + pub shm_cpid: u32, + pub shm_lpid: u32, + pub shm_nattch: u32, +} + +pub type SigsetType = u64; + +#[derive(Copy, Clone, Debug, Default)] +#[repr(C)] +pub struct SigactionStruct { + pub sa_handler: u32, + pub sa_mask: SigsetType, + pub sa_flags: i32, +} + +//redefining the Arg union to maintain the flow of the program +#[derive(Copy, Clone)] +#[repr(C)] +pub union Arg { + pub dispatch_int: i32, + pub dispatch_uint: u32, + pub dispatch_ulong: u64, + pub dispatch_long: i64, + pub dispatch_usize: usize, //For types not specified to be a given length, but often set to word size (i.e. size_t) + pub dispatch_isize: isize, //For types not specified to be a given length, but often set to word size (i.e. off_t) + pub dispatch_cbuf: *const u8, //Typically corresponds to an immutable void* pointer as in write + pub dispatch_mutcbuf: *mut u8, //Typically corresponds to a mutable void* pointer as in read + pub dispatch_cstr: *const i8, //Typically corresponds to a passed in string of type char*, as in open + pub dispatch_cstrarr: *const *const i8, //Typically corresponds to a passed in string array of type char* const[] as in execve + pub dispatch_rlimitstruct: *mut Rlimit, + pub dispatch_statdatastruct: *mut StatData, + pub dispatch_fsdatastruct: *mut FSData, + pub dispatch_shmidstruct: *mut ShmidsStruct, + pub dispatch_constsockaddrstruct: *const SockaddrDummy, + pub dispatch_sockaddrstruct: *mut SockaddrDummy, + pub dispatch_socklen_t_ptr: *mut u32, + pub dispatch_intptr: *mut i32, + pub dispatch_pollstructarray: *mut PollStruct, + pub dispatch_epollevent: *mut EpollEvent, + pub dispatch_structtimeval: *mut TimeVal, + pub dispatch_structtimespec: *mut TimeSpec, + pub dispatch_pipearray: *mut PipeArray, + pub dispatch_sockpair: *mut SockPair, + pub dispatch_ioctlptrunion: IoctlPtrUnion, + pub dispatch_sigactionstruct: *mut SigactionStruct, + pub dispatch_constsigactionstruct: *const SigactionStruct, + pub dispatch_sigsett: *mut SigsetType, + pub dispatch_constsigsett: *const SigsetType, + pub dispatch_structitimerval: *mut ITimerVal, + pub dispatch_conststructitimerval: *const ITimerVal, + pub dispatch_fdset: *mut libc::fd_set, +} + +use std::mem::size_of; + +// Represents a Dirent struct without the string, as rust has no flexible array member support +#[repr(C, packed(1))] +pub struct ClippedDirent { + pub d_ino: u64, + pub d_off: u64, + pub d_reclen: u16, +} + +pub const CLIPPED_DIRENT_SIZE: u32 = size_of::() as u32; + +pub fn get_int(union_argument: Arg) -> Result { + let data = unsafe { union_argument.dispatch_int }; + let mut type_checker = Arg { dispatch_long: 0 }; + //turn part of the union into 0xffffffff, but, Rust + //does not like just using the hex value so we are forced to use + //a value of -1 + type_checker.dispatch_int = -1; + if (unsafe { union_argument.dispatch_long } & !unsafe { type_checker.dispatch_long }) == 0 { + return Ok(data); + } + return Err(syscall_error( + Errno::EINVAL, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_uint(union_argument: Arg) -> Result { + let data = unsafe { union_argument.dispatch_uint }; + let mut type_checker = Arg { dispatch_ulong: 0 }; + type_checker.dispatch_uint = 0xffffffff; + if (unsafe { union_argument.dispatch_ulong } & !unsafe { type_checker.dispatch_ulong }) == 0 { + return Ok(data); + } + return Err(syscall_error( + Errno::EINVAL, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_long(union_argument: Arg) -> Result { + return Ok(unsafe { union_argument.dispatch_long }); //this should not return error +} + +pub fn get_ulong(union_argument: Arg) -> Result { + return Ok(unsafe { union_argument.dispatch_ulong }); //this should not return error +} + +pub fn get_isize(union_argument: Arg) -> Result { + // also should not return error + return Ok(unsafe { union_argument.dispatch_isize }); +} + +pub fn get_usize(union_argument: Arg) -> Result { + //should not return an error + return Ok(unsafe { union_argument.dispatch_usize }); +} + +pub fn get_cbuf(union_argument: Arg) -> Result<*const u8, i32> { + let data = unsafe { union_argument.dispatch_cbuf }; + if !data.is_null() { + return Ok(data); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_mutcbuf(union_argument: Arg) -> Result<*mut u8, i32> { + let data = unsafe { union_argument.dispatch_mutcbuf }; + if !data.is_null() { + return Ok(data); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +// for the case where the buffer pointer being Null is normal +pub fn get_mutcbuf_null(union_argument: Arg) -> Result, i32> { + let data = unsafe { union_argument.dispatch_mutcbuf }; + if !data.is_null() { + return Ok(Some(data)); + } + return Ok(None); +} + +pub fn get_fdset(union_argument: Arg) -> Result, i32> { + let data: *mut libc::fd_set = unsafe { union_argument.dispatch_fdset }; + if !data.is_null() { + let internal_fds: &mut interface::FdSet = interface::FdSet::new_from_ptr(data); + return Ok(Some(internal_fds)); + } + return Ok(None); +} + +pub fn get_cstr<'a>(union_argument: Arg) -> Result<&'a str, i32> { + //first we check that the pointer is not null + //and then we check so that we can get data from the memory + + let pointer = unsafe { union_argument.dispatch_cstr }; + if !pointer.is_null() { + if let Ok(ret_data) = unsafe { interface::charstar_to_ruststr(pointer) } { + return Ok(ret_data); + } else { + return Err(syscall_error( + Errno::EILSEQ, + "dispatcher", + "could not parse input data to a string", + )); + } + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_cstrarr<'a>(union_argument: Arg) -> Result, i32> { + //iterate though the pointers in a function and: + // 1: check that the pointer is not null + // 2: push the data from that pointer onto the vector being returned + //once we encounter a null pointer, we know that we have either hit the end of the array or another null pointer in the memory + + let mut pointer = unsafe { union_argument.dispatch_cstrarr }; + let mut data_vector: Vec<&str> = Vec::new(); + + if !pointer.is_null() { + while unsafe { !(*pointer).is_null() } { + if let Ok(character_bytes) = unsafe { interface::charstar_to_ruststr(*pointer) } { + data_vector.push(character_bytes); + pointer = pointer.wrapping_offset(1); + } else { + return Err(syscall_error( + Errno::EILSEQ, + "dispatcher", + "could not parse input data to string", + )); + } + } + return Ok(data_vector); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_statdatastruct<'a>(union_argument: Arg) -> Result<&'a mut StatData, i32> { + let pointer = unsafe { union_argument.dispatch_statdatastruct }; + if !pointer.is_null() { + return Ok(unsafe { &mut *pointer }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_fsdatastruct<'a>(union_argument: Arg) -> Result<&'a mut FSData, i32> { + let pointer = unsafe { union_argument.dispatch_fsdatastruct }; + if !pointer.is_null() { + return Ok(unsafe { &mut *pointer }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_shmidstruct<'a>(union_argument: Arg) -> Result<&'a mut ShmidsStruct, i32> { + let pointer = unsafe { union_argument.dispatch_shmidstruct }; + if !pointer.is_null() { + return Ok(unsafe { &mut *pointer }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_ioctlptrunion(union_argument: Arg) -> Result { + return Ok(unsafe { union_argument.dispatch_ioctlptrunion }); +} + +pub fn get_ioctl_int<'a>(ptrunion: IoctlPtrUnion) -> Result { + let pointer = unsafe { ptrunion.int_ptr }; + if !pointer.is_null() { + return Ok(unsafe { *pointer }); + } + return Err(syscall_error(Errno::EFAULT, "ioctl", "argp is not valid")); +} + +pub fn get_ioctl_char<'a>(ptrunion: IoctlPtrUnion) -> Result { + let pointer = unsafe { ptrunion.c_char_ptr }; + if !pointer.is_null() { + return Ok(unsafe { *pointer }); + } + return Err(syscall_error(Errno::EFAULT, "ioctl", "argp is not valid")); +} + +/// Given the vector of tuples produced from getdents_syscall, each of which consists of +/// a ClippedDirent struct and a u8 vector representing the name, and also given the +/// pointer to the base of the buffer to which the getdents structs should be copied, +/// populate said buffer with these getdents structs and the names at the requisite locations +/// +/// We assume a number of things about the tuples that are input: +/// +/// 1. The name in the u8 vec is null terminated +/// 2. After being null terminated it is then padded to the next highest 8 byte boundary +/// 3. After being padded, the last byte of padding is populated with DT_UNKNOWN (0) for now, +/// as the d_type field does not have to be fully implemented for getdents to be POSIX compliant +/// 4. All fields in the clipped dirent, are correctly filled--i.e. d_off has the correct offset +/// of the next struct in the buffer and d_reclen has the length of the struct with the padded name +/// 5. The number of tuples in the vector is such that they all fit in the buffer +/// +/// There is enough information to produce a tuple vector that can satisfy these assumptions well +/// in getdents syscall, and thus all the work to satisfy these assumptions should be done there +pub fn pack_dirents(dirtuplevec: Vec<(ClippedDirent, Vec)>, baseptr: *mut u8) { + let mut curptr = baseptr; + + //for each tuple we write in the ClippedDirent struct, and then the padded name vec + for dirtuple in dirtuplevec { + //get pointer to start of next dirent in the buffer as a ClippedDirent pointer + let curclippedptr = curptr as *mut ClippedDirent; + //turn that pointer into a rust reference + let curwrappedptr = unsafe { &mut *curclippedptr }; + //assign to the data that reference points to with the value of the ClippedDirent from the tuple + *curwrappedptr = dirtuple.0; + + //advance pointer by the size of one ClippedDirent, std::mem::size_of should be added into the interface + curptr = curptr.wrapping_offset(size_of::() as isize); + + //write, starting from this advanced location, the u8 vec representation of the name + unsafe { curptr.copy_from(dirtuple.1.as_slice().as_ptr(), dirtuple.1.len()) }; + + //advance pointer by the size of name, which we assume to be null terminated and padded correctly + //and thus we are finished with this struct + curptr = curptr.wrapping_offset(dirtuple.1.len() as isize); + } +} + +pub fn get_pipearray<'a>(union_argument: Arg) -> Result<&'a mut PipeArray, i32> { + let pointer = unsafe { union_argument.dispatch_pipearray }; + if !pointer.is_null() { + return Ok(unsafe { &mut *pointer }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_sockpair<'a>(union_argument: Arg) -> Result<&'a mut SockPair, i32> { + let pointer = unsafe { union_argument.dispatch_sockpair }; + if !pointer.is_null() { + return Ok(unsafe { &mut *pointer }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_sockaddr(union_argument: Arg, addrlen: u32) -> Result { + let pointer = unsafe { union_argument.dispatch_constsockaddrstruct }; + if !pointer.is_null() { + let tmpsock = unsafe { &*pointer }; + match tmpsock.sa_family { + /*AF_UNIX*/ + 1 => { + if addrlen < SIZEOF_SOCKADDR + || addrlen > size_of::() as u32 + { + return Err(syscall_error( + Errno::EINVAL, + "dispatcher", + "input length incorrect for family of sockaddr", + )); + } + let unix_ptr = pointer as *const interface::SockaddrUnix; + return Ok(interface::GenSockaddr::Unix(unsafe { *unix_ptr })); + } + /*AF_INET*/ + 2 => { + if addrlen < size_of::() as u32 { + return Err(syscall_error( + Errno::EINVAL, + "dispatcher", + "input length too small for family of sockaddr", + )); + } + let v4_ptr = pointer as *const interface::SockaddrV4; + return Ok(interface::GenSockaddr::V4(unsafe { *v4_ptr })); + } + /*AF_INET6*/ + 30 => { + if addrlen < size_of::() as u32 { + return Err(syscall_error( + Errno::EINVAL, + "dispatcher", + "input length too small for family of sockaddr", + )); + } + let v6_ptr = pointer as *const interface::SockaddrV6; + return Ok(interface::GenSockaddr::V6(unsafe { *v6_ptr })); + } + _ => { + return Err(syscall_error( + Errno::EOPNOTSUPP, + "dispatcher", + "sockaddr family not supported", + )) + } + } + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn copy_out_sockaddr(union_argument: Arg, len_argument: Arg, gensock: interface::GenSockaddr) { + let copyoutaddr = unsafe { union_argument.dispatch_sockaddrstruct } as *mut u8; + let addrlen = unsafe { len_argument.dispatch_socklen_t_ptr }; + assert!(!copyoutaddr.is_null()); + assert!(!addrlen.is_null()); + let initaddrlen = unsafe { *addrlen }; + let mut mutgensock = gensock; + match mutgensock { + interface::GenSockaddr::Unix(ref mut unixa) => { + let unixlen = size_of::() as u32; + + let fullcopylen = interface::rust_min(initaddrlen, unixlen); + unsafe { + std::ptr::copy( + (unixa) as *mut interface::SockaddrUnix as *mut u8, + copyoutaddr, + fullcopylen as usize, + ) + }; + unsafe { + *addrlen = interface::rust_max(unixlen, fullcopylen); + } + } + + interface::GenSockaddr::V4(ref mut v4a) => { + let v4len = size_of::() as u32; + + let fullcopylen = interface::rust_min(initaddrlen, v4len); + unsafe { + std::ptr::copy( + (v4a) as *mut interface::SockaddrV4 as *mut u8, + copyoutaddr, + fullcopylen as usize, + ) + }; + unsafe { + *addrlen = interface::rust_max(v4len, fullcopylen); + } + } + + interface::GenSockaddr::V6(ref mut v6a) => { + let v6len = size_of::() as u32; + + let fullcopylen = interface::rust_min(initaddrlen, v6len); + unsafe { + std::ptr::copy( + (v6a) as *mut interface::SockaddrV6 as *mut u8, + copyoutaddr, + fullcopylen as usize, + ) + }; + unsafe { + *addrlen = interface::rust_max(v6len, fullcopylen); + } + } + } +} + +pub fn get_pollstruct_slice<'a>( + union_argument: Arg, + nfds: usize, +) -> Result<&'a mut [PollStruct], i32> { + let pollstructptr = unsafe { union_argument.dispatch_pollstructarray }; + if !pollstructptr.is_null() { + return Ok(unsafe { std::slice::from_raw_parts_mut(pollstructptr, nfds) }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_epollevent_slice<'a>( + union_argument: Arg, + nfds: i32, +) -> Result<&'a mut [EpollEvent], i32> { + let epolleventptr = unsafe { union_argument.dispatch_epollevent }; + if !epolleventptr.is_null() { + return Ok(unsafe { std::slice::from_raw_parts_mut(epolleventptr, nfds as usize) }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_slice_from_string<'a>(union_argument: Arg, len: usize) -> Result<&'a mut [u8], i32> { + let bufptr = unsafe { union_argument.dispatch_mutcbuf }; + if bufptr.is_null() { + return Ok(unsafe { std::slice::from_raw_parts_mut(bufptr, len as usize) }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_epollevent<'a>(union_argument: Arg) -> Result<&'a EpollEvent, i32> { + let epolleventptr = unsafe { union_argument.dispatch_epollevent }; + if !epolleventptr.is_null() { + return Ok(unsafe { &*epolleventptr }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +pub fn get_socklen_t_ptr(union_argument: Arg) -> Result { + let socklenptr = unsafe { union_argument.dispatch_socklen_t_ptr }; + if !socklenptr.is_null() { + return Ok(unsafe { *socklenptr }); + } + return Err(syscall_error( + Errno::EFAULT, + "dispatcher", + "input data not valid", + )); +} + +//arg checked for nullity beforehand +pub fn get_int_from_intptr(union_argument: Arg) -> i32 { + return unsafe { *union_argument.dispatch_intptr }; +} + +pub fn copy_out_intptr(union_argument: Arg, intval: i32) { + unsafe { + *union_argument.dispatch_intptr = intval; + } +} + +pub fn duration_fromtimeval(union_argument: Arg) -> Result, i32> { + let pointer = unsafe { union_argument.dispatch_structtimeval }; + if !pointer.is_null() { + let times = unsafe { &mut *pointer }; + return Ok(Some(interface::RustDuration::new( + times.tv_sec as u64, + times.tv_usec as u32 * 1000, + ))); + } else { + return Ok(None); + } +} + +pub fn get_itimerval<'a>(union_argument: Arg) -> Result, i32> { + let pointer = unsafe { union_argument.dispatch_structitimerval }; + if !pointer.is_null() { + Ok(Some(unsafe { &mut *pointer })) + } else { + Ok(None) + } +} + +pub fn get_constitimerval<'a>(union_argument: Arg) -> Result, i32> { + let pointer = unsafe { union_argument.dispatch_conststructitimerval }; + if !pointer.is_null() { + Ok(Some(unsafe { &*pointer })) + } else { + Ok(None) + } +} + +pub fn duration_fromtimespec(union_argument: Arg) -> Result { + let pointer = unsafe { union_argument.dispatch_structtimespec }; + if !pointer.is_null() { + let times = unsafe { &mut *pointer }; + if times.tv_nsec < 0 || times.tv_nsec >= 1000000000 { + return Err(syscall_error( + Errno::EINVAL, + "timedwait", + "nanosecond count was negative or more than 1 billion", + )); + } + return Ok(interface::RustDuration::new( + times.tv_sec as u64, + times.tv_nsec as u32 * 1000000000, + )); + } else { + return Err(syscall_error( + Errno::EFAULT, + "timedwait", + "input timespec is null", + )); + } +} + +pub fn get_duration_from_millis( + union_argument: Arg, +) -> Result, i32> { + let posstimemillis = get_int(union_argument); + match posstimemillis { + Ok(timemillis) => { + if timemillis >= 0 { + Ok(Some(interface::RustDuration::from_millis( + timemillis as u64, + ))) + } else { + Ok(None) + } + } + Err(e) => Err(e), + } +} + +pub fn arg_nullity(union_argument: &Arg) -> bool { + unsafe { union_argument.dispatch_cbuf }.is_null() +} + +pub fn get_sigactionstruct<'a>( + union_argument: Arg, +) -> Result, i32> { + let pointer = unsafe { union_argument.dispatch_sigactionstruct }; + + if !pointer.is_null() { + Ok(Some(unsafe { &mut *pointer })) + } else { + Ok(None) + } +} + +pub fn get_constsigactionstruct<'a>( + union_argument: Arg, +) -> Result, i32> { + let pointer = unsafe { union_argument.dispatch_constsigactionstruct }; + + if !pointer.is_null() { + Ok(Some(unsafe { &*pointer })) + } else { + Ok(None) + } +} + +pub fn get_sigsett<'a>(union_argument: Arg) -> Result, i32> { + let pointer = unsafe { union_argument.dispatch_sigsett }; + + if !pointer.is_null() { + Ok(Some(unsafe { &mut *pointer })) + } else { + Ok(None) + } +} + +pub fn get_constsigsett<'a>(union_argument: Arg) -> Result, i32> { + let pointer = unsafe { union_argument.dispatch_constsigsett }; + + if !pointer.is_null() { + Ok(Some(unsafe { &*pointer })) + } else { + Ok(None) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..499440dd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +#![feature(lazy_cell)] +#![feature(rustc_private)] //for private crate imports for tests +#![feature(vec_into_raw_parts)] +#![feature(thread_local)] +#![allow(unused_imports)] + +// interface and safeposix are public because otherwise there isn't a great +// way to 'use' them for benchmarking. +pub mod interface; +mod lib_fs_utils; +pub mod safeposix; +pub mod tests; diff --git a/src/safeposix/cage.rs b/src/safeposix/cage.rs new file mode 100644 index 00000000..3f16a04e --- /dev/null +++ b/src/safeposix/cage.rs @@ -0,0 +1,208 @@ +#![allow(dead_code)] +use crate::interface; +//going to get the datatypes and errnos from the cage file from now on +pub use crate::interface::errnos::{syscall_error, Errno}; +pub use crate::interface::types::{ + Arg, EpollEvent, FSData, IoctlPtrUnion, PipeArray, PollStruct, Rlimit, ShmidsStruct, StatData, +}; + +use super::filesystem::normpath; +use super::net::SocketHandle; +pub use super::syscalls::fs_constants::*; +pub use super::syscalls::net_constants::*; +pub use super::syscalls::sys_constants::*; + +pub use crate::interface::CAGE_TABLE; + +#[derive(Debug, Clone)] +pub enum FileDescriptor { + File(FileDesc), + Stream(StreamDesc), + Socket(SocketDesc), + Pipe(PipeDesc), + Epoll(EpollDesc), +} + +#[derive(Debug, Clone)] +pub struct FileDesc { + pub position: usize, + pub inode: usize, + pub flags: i32, + pub advlock: interface::RustRfc, +} + +#[derive(Debug, Clone)] +pub struct StreamDesc { + pub position: usize, + pub stream: i32, //0 for stdin, 1 for stdout, 2 for stderr + pub flags: i32, + pub advlock: interface::RustRfc, +} + +#[derive(Debug, Clone)] +pub struct SocketDesc { + pub flags: i32, + pub domain: i32, + pub rawfd: i32, + pub handle: interface::RustRfc>, + pub advlock: interface::RustRfc, +} + +#[derive(Debug, Clone)] +pub struct PipeDesc { + pub pipe: interface::RustRfc, + pub flags: i32, + pub advlock: interface::RustRfc, +} + +#[derive(Debug, Clone)] +pub struct EpollDesc { + pub mode: i32, + pub registered_fds: interface::RustHashMap, + pub advlock: interface::RustRfc, + pub errno: i32, + pub flags: i32, +} + +pub type FdTable = Vec>>>; + +#[derive(Debug)] +pub struct Cage { + pub cageid: u64, + pub cwd: interface::RustLock>, + pub parent: u64, + pub filedescriptortable: FdTable, + pub cancelstatus: interface::RustAtomicBool, + pub getgid: interface::RustAtomicI32, + pub getuid: interface::RustAtomicI32, + pub getegid: interface::RustAtomicI32, + pub geteuid: interface::RustAtomicI32, + pub rev_shm: interface::Mutex>, //maps addr within cage to shmid + pub mutex_table: interface::RustLock>>>, + pub cv_table: interface::RustLock>>>, + pub sem_table: interface::RustHashMap>, + pub thread_table: interface::RustHashMap, + pub signalhandler: interface::RustHashMap, + pub sigset: interface::RustHashMap, + pub pendingsigset: interface::RustHashMap, + pub main_threadid: interface::RustAtomicU64, + pub interval_timer: interface::IntervalTimer, +} + +impl Cage { + pub fn get_next_fd( + &self, + startfd: Option, + ) -> ( + i32, + Option>>, + ) { + let start = match startfd { + Some(startfd) => startfd, + None => STARTINGFD, + }; + + // let's get the next available fd number. The standard says we need to return the lowest open fd number. + for fd in start..MAXFD { + let fdguard = self.filedescriptortable[fd as usize].try_write(); + if let Some(ref fdopt) = fdguard { + // we grab the lock here and if there is no occupied cage, we return the fdno and guard while keeping the fd slot locked + if fdopt.is_none() { + return (fd, fdguard); + } + } + } + return ( + syscall_error( + Errno::ENFILE, + "get_next_fd", + "no available file descriptor number could be found", + ), + None, + ); + } + + pub fn changedir(&self, newdir: interface::RustPathBuf) { + let newwd = interface::RustRfc::new(normpath(newdir, self)); + let mut cwdbox = self.cwd.write(); + *cwdbox = newwd; + } + + // function to signal all cvs in a cage when forcing exit + pub fn signalcvs(&self) { + let cvtable = self.cv_table.read(); + + for cv_handle in 0..cvtable.len() { + if cvtable[cv_handle as usize].is_some() { + let clonedcv = cvtable[cv_handle as usize].as_ref().unwrap().clone(); + clonedcv.broadcast(); + } + } + } + + pub fn send_pending_signals(&self, sigset: interface::SigsetType, pthreadid: u64) { + for signo in 1..SIGNAL_MAX { + if interface::lind_sigismember(sigset, signo) { + interface::lind_threadkill(pthreadid, signo); + } + } + } + + pub fn get_filedescriptor( + &self, + fd: i32, + ) -> Result>>, ()> { + if (fd < 0) || (fd >= MAXFD) { + Err(()) + } else { + Ok(self.filedescriptortable[fd as usize].clone()) + } + } +} + +pub fn init_fdtable() -> FdTable { + let mut fdtable = Vec::new(); + // load lower handle stubs + let stdin = interface::RustRfc::new(interface::RustLock::new(Some(FileDescriptor::Stream( + StreamDesc { + position: 0, + stream: 0, + flags: O_RDONLY, + advlock: interface::RustRfc::new(interface::AdvisoryLock::new()), + }, + )))); + let stdout = interface::RustRfc::new(interface::RustLock::new(Some(FileDescriptor::Stream( + StreamDesc { + position: 0, + stream: 1, + flags: O_WRONLY, + advlock: interface::RustRfc::new(interface::AdvisoryLock::new()), + }, + )))); + let stderr = interface::RustRfc::new(interface::RustLock::new(Some(FileDescriptor::Stream( + StreamDesc { + position: 0, + stream: 2, + flags: O_WRONLY, + advlock: interface::RustRfc::new(interface::AdvisoryLock::new()), + }, + )))); + fdtable.push(stdin); + fdtable.push(stdout); + fdtable.push(stderr); + + for _fd in 3..MAXFD as usize { + fdtable.push(interface::RustRfc::new(interface::RustLock::new(None))); + } + fdtable +} + +pub fn create_unix_sockpipes() -> ( + interface::RustRfc, + interface::RustRfc, +) { + let pipe1 = interface::RustRfc::new(interface::new_pipe(UDSOCK_CAPACITY)); + let pipe2 = interface::RustRfc::new(interface::new_pipe(UDSOCK_CAPACITY)); + + (pipe1, pipe2) +} diff --git a/src/safeposix/dispatcher.rs b/src/safeposix/dispatcher.rs new file mode 100644 index 00000000..e82d21fe --- /dev/null +++ b/src/safeposix/dispatcher.rs @@ -0,0 +1,1092 @@ +#![allow(dead_code)] +#![allow(unused_variables)] +// retreive cage table + +const ACCESS_SYSCALL: i32 = 2; +const UNLINK_SYSCALL: i32 = 4; +const LINK_SYSCALL: i32 = 5; +const RENAME_SYSCALL: i32 = 6; + +const XSTAT_SYSCALL: i32 = 9; +const OPEN_SYSCALL: i32 = 10; +const CLOSE_SYSCALL: i32 = 11; +const READ_SYSCALL: i32 = 12; +const WRITE_SYSCALL: i32 = 13; +const LSEEK_SYSCALL: i32 = 14; +const IOCTL_SYSCALL: i32 = 15; +const TRUNCATE_SYSCALL: i32 = 16; +const FXSTAT_SYSCALL: i32 = 17; +const FTRUNCATE_SYSCALL: i32 = 18; +const FSTATFS_SYSCALL: i32 = 19; +const MMAP_SYSCALL: i32 = 21; +const MUNMAP_SYSCALL: i32 = 22; +const GETDENTS_SYSCALL: i32 = 23; +const DUP_SYSCALL: i32 = 24; +const DUP2_SYSCALL: i32 = 25; +const STATFS_SYSCALL: i32 = 26; +const FCNTL_SYSCALL: i32 = 28; + +const GETPPID_SYSCALL: i32 = 29; +const EXIT_SYSCALL: i32 = 30; +const GETPID_SYSCALL: i32 = 31; + +const BIND_SYSCALL: i32 = 33; +const SEND_SYSCALL: i32 = 34; +const SENDTO_SYSCALL: i32 = 35; +const RECV_SYSCALL: i32 = 36; +const RECVFROM_SYSCALL: i32 = 37; +const CONNECT_SYSCALL: i32 = 38; +const LISTEN_SYSCALL: i32 = 39; +const ACCEPT_SYSCALL: i32 = 40; + +const GETSOCKOPT_SYSCALL: i32 = 43; +const SETSOCKOPT_SYSCALL: i32 = 44; +const SHUTDOWN_SYSCALL: i32 = 45; +const SELECT_SYSCALL: i32 = 46; +const GETCWD_SYSCALL: i32 = 47; +const POLL_SYSCALL: i32 = 48; +const SOCKETPAIR_SYSCALL: i32 = 49; +const GETUID_SYSCALL: i32 = 50; +const GETEUID_SYSCALL: i32 = 51; +const GETGID_SYSCALL: i32 = 52; +const GETEGID_SYSCALL: i32 = 53; +const FLOCK_SYSCALL: i32 = 54; +const EPOLL_CREATE_SYSCALL: i32 = 56; +const EPOLL_CTL_SYSCALL: i32 = 57; +const EPOLL_WAIT_SYSCALL: i32 = 58; + +const SHMGET_SYSCALL: i32 = 62; +const SHMAT_SYSCALL: i32 = 63; +const SHMDT_SYSCALL: i32 = 64; +const SHMCTL_SYSCALL: i32 = 65; + +const PIPE_SYSCALL: i32 = 66; +const PIPE2_SYSCALL: i32 = 67; +const FORK_SYSCALL: i32 = 68; +const EXEC_SYSCALL: i32 = 69; + +const MUTEX_CREATE_SYSCALL: i32 = 70; +const MUTEX_DESTROY_SYSCALL: i32 = 71; +const MUTEX_LOCK_SYSCALL: i32 = 72; +const MUTEX_TRYLOCK_SYSCALL: i32 = 73; +const MUTEX_UNLOCK_SYSCALL: i32 = 74; +const COND_CREATE_SYSCALL: i32 = 75; +const COND_DESTROY_SYSCALL: i32 = 76; +const COND_WAIT_SYSCALL: i32 = 77; +const COND_BROADCAST_SYSCALL: i32 = 78; +const COND_SIGNAL_SYSCALL: i32 = 79; +const COND_TIMEDWAIT_SYSCALL: i32 = 80; + +const SEM_INIT_SYSCALL: i32 = 91; +const SEM_WAIT_SYSCALL: i32 = 92; +const SEM_TRYWAIT_SYSCALL: i32 = 93; +const SEM_TIMEDWAIT_SYSCALL: i32 = 94; +const SEM_POST_SYSCALL: i32 = 95; +const SEM_DESTROY_SYSCALL: i32 = 96; +const SEM_GETVALUE_SYSCALL: i32 = 97; + +const GETHOSTNAME_SYSCALL: i32 = 125; +const PREAD_SYSCALL: i32 = 126; +const PWRITE_SYSCALL: i32 = 127; +const CHDIR_SYSCALL: i32 = 130; +const MKDIR_SYSCALL: i32 = 131; +const RMDIR_SYSCALL: i32 = 132; +const CHMOD_SYSCALL: i32 = 133; +const FCHMOD_SYSCALL: i32 = 134; + +const SOCKET_SYSCALL: i32 = 136; + +const GETSOCKNAME_SYSCALL: i32 = 144; +const GETPEERNAME_SYSCALL: i32 = 145; +const GETIFADDRS_SYSCALL: i32 = 146; + +const SIGACTION_SYSCALL: i32 = 147; +const KILL_SYSCALL: i32 = 148; +const SIGPROCMASK_SYSCALL: i32 = 149; +const SETITIMER_SYSCALL: i32 = 150; + +const FCHDIR_SYSCALL: i32 = 161; +const FSYNC_SYSCALL: i32 = 162; +const FDATASYNC_SYSCALL: i32 = 163; +const SYNC_FILE_RANGE: i32 = 164; + +use super::cage::*; +use super::filesystem::{ + incref_root, load_fs, persist_metadata, remove_domain_sock, FilesystemMetadata, FS_METADATA, + LOGFILENAME, LOGMAP, +}; +use super::net::NET_METADATA; +use super::shm::SHM_METADATA; +use super::syscalls::{fs_constants::IPC_STAT, sys_constants::*}; +use crate::interface; +use crate::interface::errnos::*; +use crate::lib_fs_utils::{lind_deltree, visit_children}; + +macro_rules! get_onearg { + ($arg: expr) => { + match (move || Ok($arg?))() { + Ok(okval) => okval, + Err(e) => return e, + } + }; +} + +//this macro takes in a syscall invocation name (i.e. cage.fork_syscall), and all of the arguments +//to the syscall. Then it unwraps the arguments, returning the error if any one of them is an error +//value, and returning the value of the function if not. It does this by using the ? operator in +//the body of a closure within the variadic macro +macro_rules! check_and_dispatch { + ( $cage:ident . $func:ident, $($arg:expr),* ) => { + match (|| Ok($cage.$func( $($arg?),* )))() { + Ok(i) => i, Err(i) => i + } + }; +} + +macro_rules! check_and_dispatch_socketpair { + ( $func:expr, $cage:ident, $($arg:expr),* ) => { + match (|| Ok($func( $cage, $($arg?),* )))() { + Ok(i) => i, Err(i) => i + } + }; +} + +// the following "quick" functions are implemented for research purposes +// to increase I/O performance by bypassing the dispatcher and type checker +#[no_mangle] +pub extern "C" fn quick_write(fd: i32, buf: *const u8, count: usize, cageid: u64) -> i32 { + interface::check_cageid(cageid); + unsafe { + CAGE_TABLE[cageid as usize] + .as_ref() + .unwrap() + .write_syscall(fd, buf, count) + } +} + +#[no_mangle] +pub extern "C" fn quick_read(fd: i32, buf: *mut u8, size: usize, cageid: u64) -> i32 { + interface::check_cageid(cageid); + unsafe { + CAGE_TABLE[cageid as usize] + .as_ref() + .unwrap() + .read_syscall(fd, buf, size) + } +} + +#[no_mangle] +pub extern "C" fn rustposix_thread_init(cageid: u64, signalflag: u64) { + let cage = interface::cagetable_getref(cageid); + let pthreadid = interface::get_pthreadid(); + cage.main_threadid + .store(pthreadid, interface::RustAtomicOrdering::Relaxed); + let inheritedsigset = cage.sigset.remove(&0); // in cases of a forked cage, we've stored the inherited sigset at entry 0 + if inheritedsigset.is_some() { + cage.sigset.insert(pthreadid, inheritedsigset.unwrap().1); + } else { + cage.sigset + .insert(pthreadid, interface::RustAtomicU64::new(0)); + } + + cage.pendingsigset + .insert(pthreadid, interface::RustAtomicU64::new(0)); + interface::signalflag_set(signalflag); +} + +#[no_mangle] +pub extern "C" fn dispatcher( + cageid: u64, + callnum: i32, + arg1: Arg, + arg2: Arg, + arg3: Arg, + arg4: Arg, + arg5: Arg, + arg6: Arg, +) -> i32 { + // need to match based on if cage exists + let cage = interface::cagetable_getref(cageid); + + match callnum { + ACCESS_SYSCALL => { + check_and_dispatch!( + cage.access_syscall, + interface::get_cstr(arg1), + interface::get_uint(arg2) + ) + } + UNLINK_SYSCALL => { + check_and_dispatch!(cage.unlink_syscall, interface::get_cstr(arg1)) + } + LINK_SYSCALL => { + check_and_dispatch!( + cage.link_syscall, + interface::get_cstr(arg1), + interface::get_cstr(arg2) + ) + } + CHDIR_SYSCALL => { + check_and_dispatch!(cage.chdir_syscall, interface::get_cstr(arg1)) + } + FSYNC_SYSCALL => { + check_and_dispatch!(cage.fsync_syscall, interface::get_int(arg1)) + } + FDATASYNC_SYSCALL => { + check_and_dispatch!(cage.fdatasync_syscall, interface::get_int(arg1)) + } + SYNC_FILE_RANGE => { + check_and_dispatch!( + cage.sync_file_range_syscall, + interface::get_int(arg1), + interface::get_isize(arg2), + interface::get_isize(arg3), + interface::get_uint(arg4) + ) + } + FCHDIR_SYSCALL => { + check_and_dispatch!(cage.fchdir_syscall, interface::get_int(arg1)) + } + XSTAT_SYSCALL => { + check_and_dispatch!( + cage.stat_syscall, + interface::get_cstr(arg1), + interface::get_statdatastruct(arg2) + ) + } + OPEN_SYSCALL => { + check_and_dispatch!( + cage.open_syscall, + interface::get_cstr(arg1), + interface::get_int(arg2), + interface::get_uint(arg3) + ) + } + READ_SYSCALL => { + check_and_dispatch!( + cage.read_syscall, + interface::get_int(arg1), + interface::get_mutcbuf(arg2), + interface::get_usize(arg3) + ) + } + WRITE_SYSCALL => { + check_and_dispatch!( + cage.write_syscall, + interface::get_int(arg1), + interface::get_cbuf(arg2), + interface::get_usize(arg3) + ) + } + CLOSE_SYSCALL => { + check_and_dispatch!(cage.close_syscall, interface::get_int(arg1)) + } + LSEEK_SYSCALL => { + check_and_dispatch!( + cage.lseek_syscall, + interface::get_int(arg1), + interface::get_isize(arg2), + interface::get_int(arg3) + ) + } + FXSTAT_SYSCALL => { + check_and_dispatch!( + cage.fstat_syscall, + interface::get_int(arg1), + interface::get_statdatastruct(arg2) + ) + } + FSTATFS_SYSCALL => { + check_and_dispatch!( + cage.fstatfs_syscall, + interface::get_int(arg1), + interface::get_fsdatastruct(arg2) + ) + } + MMAP_SYSCALL => { + check_and_dispatch!( + cage.mmap_syscall, + interface::get_mutcbuf(arg1), + interface::get_usize(arg2), + interface::get_int(arg3), + interface::get_int(arg4), + interface::get_int(arg5), + interface::get_long(arg6) + ) + } + MUNMAP_SYSCALL => { + check_and_dispatch!( + cage.munmap_syscall, + interface::get_mutcbuf(arg1), + interface::get_usize(arg2) + ) + } + DUP_SYSCALL => { + check_and_dispatch!( + cage.dup_syscall, + interface::get_int(arg1), + Ok::, i32>(None) + ) + } + DUP2_SYSCALL => { + check_and_dispatch!( + cage.dup2_syscall, + interface::get_int(arg1), + interface::get_int(arg2) + ) + } + STATFS_SYSCALL => { + check_and_dispatch!( + cage.statfs_syscall, + interface::get_cstr(arg1), + interface::get_fsdatastruct(arg2) + ) + } + FCNTL_SYSCALL => { + check_and_dispatch!( + cage.fcntl_syscall, + interface::get_int(arg1), + interface::get_int(arg2), + interface::get_int(arg3) + ) + } + IOCTL_SYSCALL => { + check_and_dispatch!( + cage.ioctl_syscall, + interface::get_int(arg1), + interface::get_uint(arg2), + interface::get_ioctlptrunion(arg3) + ) + } + GETPPID_SYSCALL => { + check_and_dispatch!(cage.getppid_syscall,) + } + GETPID_SYSCALL => { + check_and_dispatch!(cage.getpid_syscall,) + } + SOCKET_SYSCALL => { + check_and_dispatch!( + cage.socket_syscall, + interface::get_int(arg1), + interface::get_int(arg2), + interface::get_int(arg3) + ) + } + BIND_SYSCALL => { + let addrlen = get_onearg!(interface::get_uint(arg3)); + let addr = get_onearg!(interface::get_sockaddr(arg2, addrlen)); + check_and_dispatch!( + cage.bind_syscall, + interface::get_int(arg1), + Ok::<&interface::GenSockaddr, i32>(&addr) + ) + } + SEND_SYSCALL => { + check_and_dispatch!( + cage.send_syscall, + interface::get_int(arg1), + interface::get_cbuf(arg2), + interface::get_usize(arg3), + interface::get_int(arg4) + ) + } + SENDTO_SYSCALL => { + let addrlen = get_onearg!(interface::get_uint(arg6)); + let addr = get_onearg!(interface::get_sockaddr(arg5, addrlen)); + check_and_dispatch!( + cage.sendto_syscall, + interface::get_int(arg1), + interface::get_cbuf(arg2), + interface::get_usize(arg3), + interface::get_int(arg4), + Ok::<&interface::GenSockaddr, i32>(&addr) + ) + } + RECV_SYSCALL => { + check_and_dispatch!( + cage.recv_syscall, + interface::get_int(arg1), + interface::get_mutcbuf(arg2), + interface::get_usize(arg3), + interface::get_int(arg4) + ) + } + RECVFROM_SYSCALL => { + let nullity1 = interface::arg_nullity(&arg5); + let nullity2 = interface::arg_nullity(&arg6); + + if nullity1 && nullity2 { + check_and_dispatch!( + cage.recvfrom_syscall, + interface::get_int(arg1), + interface::get_mutcbuf(arg2), + interface::get_usize(arg3), + interface::get_int(arg4), + Ok::<&mut Option<&mut interface::GenSockaddr>, i32>(&mut None) + ) + } else if !(nullity1 || nullity2) { + let addrlen = get_onearg!(interface::get_socklen_t_ptr(arg6)); + let mut newsockaddr = interface::GenSockaddr::V4(interface::SockaddrV4::default()); //dummy value, rust would complain if we used an uninitialized value here + let rv = check_and_dispatch!( + cage.recvfrom_syscall, + interface::get_int(arg1), + interface::get_mutcbuf(arg2), + interface::get_usize(arg3), + interface::get_int(arg4), + Ok::<&mut Option<&mut interface::GenSockaddr>, i32>(&mut Some( + &mut newsockaddr + )) + ); + + if rv >= 0 { + interface::copy_out_sockaddr(arg5, arg6, newsockaddr); + } + rv + } else { + syscall_error( + Errno::EINVAL, + "recvfrom", + "exactly one of the last two arguments was zero", + ) + } + } + CONNECT_SYSCALL => { + let addrlen = get_onearg!(interface::get_uint(arg3)); + let addr = get_onearg!(interface::get_sockaddr(arg2, addrlen)); + check_and_dispatch!( + cage.connect_syscall, + interface::get_int(arg1), + Ok::<&interface::GenSockaddr, i32>(&addr) + ) + } + LISTEN_SYSCALL => { + check_and_dispatch!( + cage.listen_syscall, + interface::get_int(arg1), + interface::get_int(arg2) + ) + } + ACCEPT_SYSCALL => { + let mut addr = interface::GenSockaddr::V4(interface::SockaddrV4::default()); //value doesn't matter + let nullity1 = interface::arg_nullity(&arg2); + let nullity2 = interface::arg_nullity(&arg3); + + if nullity1 && nullity2 { + check_and_dispatch!( + cage.accept_syscall, + interface::get_int(arg1), + Ok::<&mut interface::GenSockaddr, i32>(&mut addr) + ) + } else if !(nullity1 || nullity2) { + let rv = check_and_dispatch!( + cage.accept_syscall, + interface::get_int(arg1), + Ok::<&mut interface::GenSockaddr, i32>(&mut addr) + ); + if rv >= 0 { + interface::copy_out_sockaddr(arg2, arg3, addr); + } + rv + } else { + syscall_error( + Errno::EINVAL, + "accept", + "exactly one of the last two arguments was zero", + ) + } + } + GETPEERNAME_SYSCALL => { + let mut addr = interface::GenSockaddr::V4(interface::SockaddrV4::default()); //value doesn't matter + if interface::arg_nullity(&arg2) || interface::arg_nullity(&arg3) { + return syscall_error( + Errno::EINVAL, + "getpeername", + "Either the address or the length were null", + ); + } + let rv = check_and_dispatch!( + cage.getpeername_syscall, + interface::get_int(arg1), + Ok::<&mut interface::GenSockaddr, i32>(&mut addr) + ); + + if rv >= 0 { + interface::copy_out_sockaddr(arg2, arg3, addr); + } + rv + } + GETSOCKNAME_SYSCALL => { + let mut addr = interface::GenSockaddr::V4(interface::SockaddrV4::default()); //value doesn't matter + if interface::arg_nullity(&arg2) || interface::arg_nullity(&arg3) { + return syscall_error( + Errno::EINVAL, + "getsockname", + "Either the address or the length were null", + ); + } + let rv = check_and_dispatch!( + cage.getsockname_syscall, + interface::get_int(arg1), + Ok::<&mut interface::GenSockaddr, i32>(&mut addr) + ); + + if rv >= 0 { + interface::copy_out_sockaddr(arg2, arg3, addr); + } + rv + } + GETIFADDRS_SYSCALL => { + check_and_dispatch!( + cage.getifaddrs_syscall, + interface::get_mutcbuf(arg1), + interface::get_usize(arg2) + ) + } + GETSOCKOPT_SYSCALL => { + let mut sockval = 0; + if interface::arg_nullity(&arg4) || interface::arg_nullity(&arg5) { + return syscall_error( + Errno::EFAULT, + "getsockopt", + "Optval or optlen passed as null", + ); + } + if get_onearg!(interface::get_socklen_t_ptr(arg5)) != 4 { + return syscall_error(Errno::EINVAL, "setsockopt", "Invalid optlen passed"); + } + let rv = check_and_dispatch!( + cage.getsockopt_syscall, + interface::get_int(arg1), + interface::get_int(arg2), + interface::get_int(arg3), + Ok::<&mut i32, i32>(&mut sockval) + ); + + if rv >= 0 { + interface::copy_out_intptr(arg4, sockval); + } + //we take it as a given that the length is 4 both in and out + rv + } + SETSOCKOPT_SYSCALL => { + let sockval; + if !interface::arg_nullity(&arg4) { + if get_onearg!(interface::get_uint(arg5)) != 4 { + return syscall_error(Errno::EINVAL, "setsockopt", "Invalid optlen passed"); + } + sockval = interface::get_int_from_intptr(arg4); + } else { + sockval = 0; + } + check_and_dispatch!( + cage.setsockopt_syscall, + interface::get_int(arg1), + interface::get_int(arg2), + interface::get_int(arg3), + Ok::(sockval) + ) + } + SHUTDOWN_SYSCALL => { + check_and_dispatch!( + cage.netshutdown_syscall, + interface::get_int(arg1), + interface::get_int(arg2) + ) + } + SELECT_SYSCALL => { + let nfds = get_onearg!(interface::get_int(arg1)); + if nfds < 0 { + //RLIMIT_NOFILE check as well? + return syscall_error( + Errno::EINVAL, + "select", + "The number of fds passed was invalid", + ); + } + check_and_dispatch!( + cage.select_syscall, + Ok::(nfds), + interface::get_fdset(arg2), + interface::get_fdset(arg3), + interface::get_fdset(arg4), + interface::duration_fromtimeval(arg5) + ) + } + POLL_SYSCALL => { + let nfds = get_onearg!(interface::get_usize(arg2)); + check_and_dispatch!( + cage.poll_syscall, + interface::get_pollstruct_slice(arg1, nfds), + interface::get_duration_from_millis(arg3) + ) + } + SOCKETPAIR_SYSCALL => { + check_and_dispatch_socketpair!( + Cage::socketpair_syscall, + cage, + interface::get_int(arg1), + interface::get_int(arg2), + interface::get_int(arg3), + interface::get_sockpair(arg4) + ) + } + EXIT_SYSCALL => { + check_and_dispatch!(cage.exit_syscall, interface::get_int(arg1)) + } + FLOCK_SYSCALL => { + check_and_dispatch!( + cage.flock_syscall, + interface::get_int(arg1), + interface::get_int(arg2) + ) + } + FORK_SYSCALL => { + check_and_dispatch!(cage.fork_syscall, interface::get_ulong(arg1)) + } + EXEC_SYSCALL => { + check_and_dispatch!(cage.exec_syscall, interface::get_ulong(arg1)) + } + GETUID_SYSCALL => { + check_and_dispatch!(cage.getuid_syscall,) + } + GETEUID_SYSCALL => { + check_and_dispatch!(cage.geteuid_syscall,) + } + GETGID_SYSCALL => { + check_and_dispatch!(cage.getgid_syscall,) + } + GETEGID_SYSCALL => { + check_and_dispatch!(cage.getegid_syscall,) + } + PREAD_SYSCALL => { + check_and_dispatch!( + cage.pread_syscall, + interface::get_int(arg1), + interface::get_mutcbuf(arg2), + interface::get_usize(arg3), + interface::get_isize(arg4) + ) + } + PWRITE_SYSCALL => { + check_and_dispatch!( + cage.pwrite_syscall, + interface::get_int(arg1), + interface::get_mutcbuf(arg2), + interface::get_usize(arg3), + interface::get_isize(arg4) + ) + } + CHMOD_SYSCALL => { + check_and_dispatch!( + cage.chmod_syscall, + interface::get_cstr(arg1), + interface::get_uint(arg2) + ) + } + FCHMOD_SYSCALL => { + check_and_dispatch!( + cage.fchmod_syscall, + interface::get_int(arg1), + interface::get_uint(arg2) + ) + } + RMDIR_SYSCALL => { + check_and_dispatch!(cage.rmdir_syscall, interface::get_cstr(arg1)) + } + RENAME_SYSCALL => { + check_and_dispatch!( + cage.rename_syscall, + interface::get_cstr(arg1), + interface::get_cstr(arg2) + ) + } + EPOLL_CREATE_SYSCALL => { + check_and_dispatch!(cage.epoll_create_syscall, interface::get_int(arg1)) + } + EPOLL_CTL_SYSCALL => { + check_and_dispatch!( + cage.epoll_ctl_syscall, + interface::get_int(arg1), + interface::get_int(arg2), + interface::get_int(arg3), + interface::get_epollevent(arg4) + ) + } + EPOLL_WAIT_SYSCALL => { + let nfds = get_onearg!(interface::get_int(arg3)); + + if nfds < 0 { + //RLIMIT_NOFILE check as well? + return syscall_error( + Errno::EINVAL, + "select", + "The number of fds passed was invalid", + ); + } + + check_and_dispatch!( + cage.epoll_wait_syscall, + interface::get_int(arg1), + interface::get_epollevent_slice(arg2, nfds), + Ok::(nfds), + interface::get_duration_from_millis(arg4) + ) + } + GETDENTS_SYSCALL => { + check_and_dispatch!( + cage.getdents_syscall, + interface::get_int(arg1), + interface::get_mutcbuf(arg2), + interface::get_uint(arg3) + ) + } + PIPE_SYSCALL => { + check_and_dispatch!(cage.pipe_syscall, interface::get_pipearray(arg1)) + } + PIPE2_SYSCALL => { + check_and_dispatch!( + cage.pipe2_syscall, + interface::get_pipearray(arg1), + interface::get_int(arg2) + ) + } + GETCWD_SYSCALL => { + check_and_dispatch!( + cage.getcwd_syscall, + interface::get_mutcbuf(arg1), + interface::get_uint(arg2) + ) + } + GETHOSTNAME_SYSCALL => { + check_and_dispatch!( + cage.gethostname_syscall, + interface::get_mutcbuf(arg1), + interface::get_isize(arg2) + ) + } + MKDIR_SYSCALL => { + check_and_dispatch!( + cage.mkdir_syscall, + interface::get_cstr(arg1), + interface::get_uint(arg2) + ) + } + SHMGET_SYSCALL => { + check_and_dispatch!( + cage.shmget_syscall, + interface::get_int(arg1), + interface::get_usize(arg2), + interface::get_int(arg3) + ) + } + SHMAT_SYSCALL => { + check_and_dispatch!( + cage.shmat_syscall, + interface::get_int(arg1), + interface::get_mutcbuf(arg2), + interface::get_int(arg3) + ) + } + SHMDT_SYSCALL => { + check_and_dispatch!(cage.shmdt_syscall, interface::get_mutcbuf(arg1)) + } + SHMCTL_SYSCALL => { + let cmd = get_onearg!(interface::get_int(arg2)); + let buf = if cmd == IPC_STAT { + Some(get_onearg!(interface::get_shmidstruct(arg3))) + } else { + None + }; + check_and_dispatch!( + cage.shmctl_syscall, + interface::get_int(arg1), + Ok::(cmd), + Ok::, i32>(buf) + ) + } + + MUTEX_CREATE_SYSCALL => { + check_and_dispatch!(cage.mutex_create_syscall,) + } + MUTEX_DESTROY_SYSCALL => { + check_and_dispatch!(cage.mutex_destroy_syscall, interface::get_int(arg1)) + } + MUTEX_LOCK_SYSCALL => { + check_and_dispatch!(cage.mutex_lock_syscall, interface::get_int(arg1)) + } + MUTEX_TRYLOCK_SYSCALL => { + check_and_dispatch!(cage.mutex_trylock_syscall, interface::get_int(arg1)) + } + MUTEX_UNLOCK_SYSCALL => { + check_and_dispatch!(cage.mutex_unlock_syscall, interface::get_int(arg1)) + } + COND_CREATE_SYSCALL => { + check_and_dispatch!(cage.cond_create_syscall,) + } + COND_DESTROY_SYSCALL => { + check_and_dispatch!(cage.cond_destroy_syscall, interface::get_int(arg1)) + } + COND_WAIT_SYSCALL => { + check_and_dispatch!( + cage.cond_wait_syscall, + interface::get_int(arg1), + interface::get_int(arg2) + ) + } + COND_BROADCAST_SYSCALL => { + check_and_dispatch!(cage.cond_broadcast_syscall, interface::get_int(arg1)) + } + COND_SIGNAL_SYSCALL => { + check_and_dispatch!(cage.cond_signal_syscall, interface::get_int(arg1)) + } + COND_TIMEDWAIT_SYSCALL => { + check_and_dispatch!( + cage.cond_timedwait_syscall, + interface::get_int(arg1), + interface::get_int(arg2), + interface::duration_fromtimespec(arg3) + ) + } + TRUNCATE_SYSCALL => { + check_and_dispatch!( + cage.truncate_syscall, + interface::get_cstr(arg1), + interface::get_isize(arg2) + ) + } + FTRUNCATE_SYSCALL => { + check_and_dispatch!( + cage.ftruncate_syscall, + interface::get_int(arg1), + interface::get_isize(arg2) + ) + } + SIGACTION_SYSCALL => { + check_and_dispatch!( + cage.sigaction_syscall, + interface::get_int(arg1), + interface::get_constsigactionstruct(arg2), + interface::get_sigactionstruct(arg3) + ) + } + KILL_SYSCALL => { + check_and_dispatch!( + cage.kill_syscall, + interface::get_int(arg1), + interface::get_int(arg2) + ) + } + SIGPROCMASK_SYSCALL => { + check_and_dispatch!( + cage.sigprocmask_syscall, + interface::get_int(arg1), + interface::get_constsigsett(arg2), + interface::get_sigsett(arg3) + ) + } + SETITIMER_SYSCALL => { + check_and_dispatch!( + cage.setitimer_syscall, + interface::get_int(arg1), + interface::get_constitimerval(arg2), + interface::get_itimerval(arg3) + ) + } + SEM_INIT_SYSCALL => { + check_and_dispatch!( + cage.sem_init_syscall, + interface::get_uint(arg1), + interface::get_int(arg2), + interface::get_uint(arg3) + ) + } + SEM_WAIT_SYSCALL => { + check_and_dispatch!(cage.sem_wait_syscall, interface::get_uint(arg1)) + } + SEM_POST_SYSCALL => { + check_and_dispatch!(cage.sem_post_syscall, interface::get_uint(arg1)) + } + SEM_DESTROY_SYSCALL => { + check_and_dispatch!(cage.sem_destroy_syscall, interface::get_uint(arg1)) + } + SEM_GETVALUE_SYSCALL => { + check_and_dispatch!(cage.sem_getvalue_syscall, interface::get_uint(arg1)) + } + SEM_TRYWAIT_SYSCALL => { + check_and_dispatch!(cage.sem_trywait_syscall, interface::get_uint(arg1)) + } + SEM_TIMEDWAIT_SYSCALL => { + check_and_dispatch!( + cage.sem_timedwait_syscall, + interface::get_uint(arg1), + interface::duration_fromtimespec(arg2) + ) + } + + _ => { + //unknown syscall + -1 + } + } +} + +#[no_mangle] +pub extern "C" fn lindcancelinit(cageid: u64) { + let cage = interface::cagetable_getref(cageid); + cage.cancelstatus + .store(true, interface::RustAtomicOrdering::Relaxed); + cage.signalcvs(); +} + +#[no_mangle] +pub extern "C" fn lindsetthreadkill(cageid: u64, pthreadid: u64, kill: bool) { + let cage = interface::cagetable_getref(cageid); + cage.thread_table.insert(pthreadid, kill); + if cage + .main_threadid + .load(interface::RustAtomicOrdering::Relaxed) + == 0 + { + cage.main_threadid.store( + interface::get_pthreadid(), + interface::RustAtomicOrdering::Relaxed, + ); + } +} + +#[no_mangle] +pub extern "C" fn lindcheckthread(cageid: u64, pthreadid: u64) -> bool { + interface::check_thread(cageid, pthreadid) +} + +#[no_mangle] +pub extern "C" fn lindthreadremove(cageid: u64, pthreadid: u64) { + let cage = interface::cagetable_getref(cageid); + cage.thread_table.remove(&pthreadid); +} + +fn cleartmp(init: bool) { + let path = "/tmp"; + + let cage = interface::cagetable_getref(0); + let mut statdata = StatData::default(); + + if cage.stat_syscall(path, &mut statdata) == 0 { + visit_children(&cage, path, None, |childcage, childpath, isdir, _| { + if isdir { + lind_deltree(childcage, childpath); + } else { + childcage.unlink_syscall(childpath); + } + }); + } else { + if init == true { + cage.mkdir_syscall(path, S_IRWXA); + } + } +} + +#[no_mangle] +pub extern "C" fn lindgetsighandler(cageid: u64, signo: i32) -> u32 { + let cage = interface::cagetable_getref(cageid); + let pthreadid = interface::get_pthreadid(); + let sigset = cage.sigset.get(&pthreadid).unwrap(); // these lock sigset dashmaps for concurrency + let pendingset = cage.sigset.get(&pthreadid).unwrap(); + + if !interface::lind_sigismember(sigset.load(interface::RustAtomicOrdering::Relaxed), signo) { + return match cage.signalhandler.get(&signo) { + Some(action_struct) => { + action_struct.sa_handler // if we have a handler and its not blocked return it + } + None => 0, // if we dont have a handler return 0 + }; + } else { + let mutpendingset = sigset.load(interface::RustAtomicOrdering::Relaxed); + sigset.store( + interface::lind_sigaddset(mutpendingset, signo), + interface::RustAtomicOrdering::Relaxed, + ); + 1 // if its blocked add the signal to the pending set and return 1 to indicated it was blocked + // a signal handler cant be located at address 0x1 so this value is fine to return and check + } +} + +#[no_mangle] +pub extern "C" fn lindrustinit(verbosity: isize) { + let _ = interface::VERBOSE.set(verbosity); //assigned to suppress unused result warning + interface::cagetable_init(); + load_fs(); + incref_root(); + incref_root(); + + let utilcage = Cage { + cageid: 0, + cwd: interface::RustLock::new(interface::RustRfc::new(interface::RustPathBuf::from("/"))), + parent: 0, + filedescriptortable: init_fdtable(), + cancelstatus: interface::RustAtomicBool::new(false), + getgid: interface::RustAtomicI32::new(-1), + getuid: interface::RustAtomicI32::new(-1), + getegid: interface::RustAtomicI32::new(-1), + geteuid: interface::RustAtomicI32::new(-1), + rev_shm: interface::Mutex::new(vec![]), + mutex_table: interface::RustLock::new(vec![]), + cv_table: interface::RustLock::new(vec![]), + sem_table: interface::RustHashMap::new(), + thread_table: interface::RustHashMap::new(), + signalhandler: interface::RustHashMap::new(), + sigset: interface::RustHashMap::new(), + pendingsigset: interface::RustHashMap::new(), + main_threadid: interface::RustAtomicU64::new(0), + interval_timer: interface::IntervalTimer::new(0), + }; + + interface::cagetable_insert(0, utilcage); + + //init cage is its own parent + let initcage = Cage { + cageid: 1, + cwd: interface::RustLock::new(interface::RustRfc::new(interface::RustPathBuf::from("/"))), + parent: 1, + filedescriptortable: init_fdtable(), + cancelstatus: interface::RustAtomicBool::new(false), + getgid: interface::RustAtomicI32::new(-1), + getuid: interface::RustAtomicI32::new(-1), + getegid: interface::RustAtomicI32::new(-1), + geteuid: interface::RustAtomicI32::new(-1), + rev_shm: interface::Mutex::new(vec![]), + mutex_table: interface::RustLock::new(vec![]), + cv_table: interface::RustLock::new(vec![]), + sem_table: interface::RustHashMap::new(), + thread_table: interface::RustHashMap::new(), + signalhandler: interface::RustHashMap::new(), + sigset: interface::RustHashMap::new(), + pendingsigset: interface::RustHashMap::new(), + main_threadid: interface::RustAtomicU64::new(0), + interval_timer: interface::IntervalTimer::new(1), + }; + interface::cagetable_insert(1, initcage); + // make sure /tmp is clean + cleartmp(true); +} + +#[no_mangle] +pub extern "C" fn lindrustfinalize() { + // remove any open domain socket inodes + for truepath in NET_METADATA.get_domainsock_paths() { + remove_domain_sock(truepath); + } + + // clear /tmp folder + cleartmp(false); + interface::cagetable_clear(); + // if we get here, persist and delete log + persist_metadata(&FS_METADATA); + if interface::pathexists(LOGFILENAME.to_string()) { + // remove file if it exists, assigning it to nothing to avoid the compiler yelling about unused result + let mut logobj = LOGMAP.write(); + let log = logobj.take().unwrap(); + let _close = log.close().unwrap(); + let _logremove = interface::removefile(LOGFILENAME.to_string()); + } +} diff --git a/src/safeposix/filesystem.rs b/src/safeposix/filesystem.rs new file mode 100644 index 00000000..e46f66b9 --- /dev/null +++ b/src/safeposix/filesystem.rs @@ -0,0 +1,619 @@ +// Filesystem metadata struct +#![allow(dead_code)] + +use super::net::NET_METADATA; +use super::syscalls::fs_constants::*; +use super::syscalls::sys_constants::*; +use crate::interface; + +use super::cage::Cage; + +pub const METADATAFILENAME: &str = "lind.metadata"; + +pub const LOGFILENAME: &str = "lind.md.log"; + +pub static LOGMAP: interface::RustLazyGlobal< + interface::RustRfc>>, +> = interface::RustLazyGlobal::new(|| interface::RustRfc::new(interface::RustLock::new(None))); + +pub static FS_METADATA: interface::RustLazyGlobal> = + interface::RustLazyGlobal::new(|| { + interface::RustRfc::new(FilesystemMetadata::init_fs_metadata()) + }); //we want to check if fs exists before doing a blank init, but not for now + +type FileObjectTable = interface::RustHashMap; +pub static FILEOBJECTTABLE: interface::RustLazyGlobal = + interface::RustLazyGlobal::new(|| interface::RustHashMap::new()); + +#[derive(interface::SerdeSerialize, interface::SerdeDeserialize, Debug)] +pub enum Inode { + File(GenericInode), + CharDev(DeviceInode), + Socket(SocketInode), + Dir(DirectoryInode), +} + +#[derive(interface::SerdeSerialize, interface::SerdeDeserialize, Debug)] +pub struct GenericInode { + pub size: usize, + pub uid: u32, + pub gid: u32, + pub mode: u32, + pub linkcount: u32, + #[serde(skip)] + //skips serializing and deserializing field, will populate with u32 default of 0 (refcount should not be persisted) + pub refcount: u32, + pub atime: u64, + pub ctime: u64, + pub mtime: u64, +} + +#[derive(interface::SerdeSerialize, interface::SerdeDeserialize, Debug)] +pub struct DeviceInode { + pub size: usize, + pub uid: u32, + pub gid: u32, + pub mode: u32, + pub linkcount: u32, + #[serde(skip)] + //skips serializing and deserializing field, will populate with u32 default of 0 (refcount should not be persisted) + pub refcount: u32, + pub atime: u64, + pub ctime: u64, + pub mtime: u64, + pub dev: DevNo, +} + +#[derive(interface::SerdeSerialize, interface::SerdeDeserialize, Debug)] +pub struct SocketInode { + pub size: usize, + pub uid: u32, + pub gid: u32, + pub mode: u32, + pub linkcount: u32, + #[serde(skip)] + pub refcount: u32, + pub atime: u64, + pub ctime: u64, + pub mtime: u64, +} + +#[derive(interface::SerdeSerialize, interface::SerdeDeserialize, Debug)] +pub struct DirectoryInode { + pub size: usize, + pub uid: u32, + pub gid: u32, + pub mode: u32, + pub linkcount: u32, + #[serde(skip)] + //skips serializing and deserializing field, will populate with u32 default of 0 (refcount should not be persisted) + pub refcount: u32, + pub atime: u64, + pub ctime: u64, + pub mtime: u64, + pub filename_to_inode_dict: interface::RustHashMap, +} + +#[derive(interface::SerdeSerialize, interface::SerdeDeserialize, Debug)] +pub struct FilesystemMetadata { + pub nextinode: interface::RustAtomicUsize, + pub dev_id: u64, + pub inodetable: interface::RustHashMap, +} + +pub fn init_filename_to_inode_dict( + curinode: usize, + parentinode: usize, +) -> interface::RustHashMap { + let retval = interface::RustHashMap::new(); + retval.insert(".".to_string(), curinode); + retval.insert("..".to_string(), parentinode); + retval +} + +impl FilesystemMetadata { + pub fn blank_fs_init() -> FilesystemMetadata { + //remove open files? + let retval = FilesystemMetadata { + nextinode: interface::RustAtomicUsize::new(STREAMINODE + 1), + dev_id: 20, + inodetable: interface::RustHashMap::new(), + }; + let time = interface::timestamp(); //We do a real timestamp now + let dirinode = DirectoryInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_GID, + //linkcount is how many entries the directory has (as per linux kernel), . and .. making 2 for the root directory initially, + //plus one to make sure it can never be removed (can be thought of as mount point link) + //refcount is how many open file descriptors pointing to the directory exist, 0 as no cages exist yet + mode: S_IFDIR as u32 | S_IRWXA, + linkcount: 3, + refcount: 0, + atime: time, + ctime: time, + mtime: time, + filename_to_inode_dict: init_filename_to_inode_dict( + ROOTDIRECTORYINODE, + ROOTDIRECTORYINODE, + ), + }; + retval + .inodetable + .insert(ROOTDIRECTORYINODE, Inode::Dir(dirinode)); + + retval + } + + // Read file, and deserialize CBOR to FS METADATA + pub fn init_fs_metadata() -> FilesystemMetadata { + // Read CBOR from file + if interface::pathexists(METADATAFILENAME.to_string()) { + let metadata_fileobj = interface::openmetadata(METADATAFILENAME.to_string()).unwrap(); + let metadatabytes = metadata_fileobj.readfile_to_new_bytes().unwrap(); + metadata_fileobj.close().unwrap(); + + // Restore metadata + interface::serde_deserialize_from_bytes(&metadatabytes).unwrap() + } else { + FilesystemMetadata::blank_fs_init() + } + } +} + +pub fn format_fs() { + let newmetadata = FilesystemMetadata::blank_fs_init(); + //Because we keep the metadata as a synclazy, it is not possible to completely wipe it and + //reinstate something over it in-place. Thus we create a new file system, wipe the old one, and + //then persist our new one. In order to create the new one, because the FS_METADATA does not + //point to the same metadata that we are trying to create, we need to manually insert these + //rather than using system calls. + + let mut rootinode = newmetadata.inodetable.get_mut(&1).unwrap(); //get root to populate its dict + if let Inode::Dir(ref mut rootdir) = *rootinode { + rootdir.filename_to_inode_dict.insert("dev".to_string(), 2); + rootdir.linkcount += 1; + } else { + unreachable!(); + } + drop(rootinode); + + let devchildren = interface::RustHashMap::new(); + devchildren.insert("..".to_string(), 1); + devchildren.insert(".".to_string(), 2); + devchildren.insert("null".to_string(), 3); + devchildren.insert("zero".to_string(), 4); + devchildren.insert("urandom".to_string(), 5); + devchildren.insert("random".to_string(), 6); + + let tmpchildren = interface::RustHashMap::new(); + tmpchildren.insert("..".to_string(), 1); + tmpchildren.insert(".".to_string(), 2); + + let time = interface::timestamp(); //We do a real timestamp now + let devdirinode = Inode::Dir(DirectoryInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_GID, + mode: (S_IFDIR | 0o755) as u32, + linkcount: 3 + 4, //3 for ., .., and the parent dir, 4 is one for each child we will create + refcount: 0, + atime: time, + ctime: time, + mtime: time, + filename_to_inode_dict: devchildren, + }); //inode 2 + let nullinode = Inode::CharDev(DeviceInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_UID, + mode: (S_IFCHR | 0o666) as u32, + linkcount: 1, + refcount: 0, + atime: time, + ctime: time, + mtime: time, + dev: DevNo { major: 1, minor: 3 }, + }); //inode 3 + let zeroinode = Inode::CharDev(DeviceInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_UID, + mode: (S_IFCHR | 0o666) as u32, + linkcount: 1, + refcount: 0, + atime: time, + ctime: time, + mtime: time, + dev: DevNo { major: 1, minor: 5 }, + }); //inode 4 + let urandominode = Inode::CharDev(DeviceInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_UID, + mode: (S_IFCHR | 0o666) as u32, + linkcount: 1, + refcount: 0, + atime: time, + ctime: time, + mtime: time, + dev: DevNo { major: 1, minor: 9 }, + }); //inode 5 + let randominode = Inode::CharDev(DeviceInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_UID, + mode: (S_IFCHR | 0o666) as u32, + linkcount: 1, + refcount: 0, + atime: time, + ctime: time, + mtime: time, + dev: DevNo { major: 1, minor: 8 }, + }); //inode 6 + let tmpdirinode = Inode::Dir(DirectoryInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_GID, + mode: (S_IFDIR | 0o755) as u32, + linkcount: 3 + 4, + refcount: 0, + atime: time, + ctime: time, + mtime: time, + filename_to_inode_dict: tmpchildren, + }); //inode 7 + newmetadata + .nextinode + .store(8, interface::RustAtomicOrdering::Relaxed); + newmetadata.inodetable.insert(2, devdirinode); + newmetadata.inodetable.insert(3, nullinode); + newmetadata.inodetable.insert(4, zeroinode); + newmetadata.inodetable.insert(5, urandominode); + newmetadata.inodetable.insert(6, randominode); + newmetadata.inodetable.insert(7, tmpdirinode); + + let _logremove = interface::removefile(LOGFILENAME.to_string()); + + persist_metadata(&newmetadata); +} + +pub fn load_fs() { + // If the metadata file exists, just close the file for later restore + // If it doesn't, lets create a new one, load special files, and persist it. + if interface::pathexists(METADATAFILENAME.to_string()) { + let metadata_fileobj = interface::openmetadata(METADATAFILENAME.to_string()).unwrap(); + metadata_fileobj.close().unwrap(); + + // if we have a log file at this point, we need to sync it with the existing metadata + if interface::pathexists(LOGFILENAME.to_string()) { + let log_fileobj = interface::openmetadata(LOGFILENAME.to_string()).unwrap(); + // read log file and parse count + let mut logread = log_fileobj.readfile_to_new_bytes().unwrap(); + let logsize = interface::convert_bytes_to_size(&logread[0..interface::COUNTMAPSIZE]); + + // create vec of log file bounded by indefinite encoding bytes (0x9F, 0xFF) + let mut logbytes: Vec = Vec::new(); + logbytes.push(0x9F); + logbytes.extend_from_slice( + &mut logread[interface::COUNTMAPSIZE..(interface::COUNTMAPSIZE + logsize)], + ); + logbytes.push(0xFF); + let mut logvec: Vec<(usize, Option)> = + interface::serde_deserialize_from_bytes(&logbytes).unwrap(); + + // drain the vector and deserialize into pairs of inodenum + inodes, + // if the inode exists, add it, if not, remove it + // keep track of the largest inodenum we see so we can update the nextinode counter + let mut max_inodenum = FS_METADATA + .nextinode + .load(interface::RustAtomicOrdering::Relaxed); + for serialpair in logvec.drain(..) { + let (inodenum, inode) = serialpair; + match inode { + Some(inode) => { + max_inodenum = interface::rust_max(max_inodenum, inodenum); + FS_METADATA.inodetable.insert(inodenum, inode); + } + None => { + FS_METADATA.inodetable.remove(&inodenum); + } + } + } + + // update the nextinode counter to avoid collisions + FS_METADATA + .nextinode + .store(max_inodenum + 1, interface::RustAtomicOrdering::Relaxed); + + let _logclose = log_fileobj.close(); + let _logremove = interface::removefile(LOGFILENAME.to_string()); + + // clean up broken links + fsck(); + } + } else { + if interface::pathexists(LOGFILENAME.to_string()) { + println!("Filesystem in very corrupted state: log existed but metadata did not!"); + } + format_fs(); + } + + // then recreate the log + create_log(); +} + +pub fn fsck() { + FS_METADATA.inodetable.retain(|_inodenum, inode_obj| { + match inode_obj { + Inode::File(ref mut normalfile_inode) => normalfile_inode.linkcount != 0, + Inode::Dir(ref mut dir_inode) => { + //2 because . and .. always contribute to the linkcount of a directory + dir_inode.linkcount > 2 + } + Inode::CharDev(ref mut char_inodej) => char_inodej.linkcount != 0, + Inode::Socket(_) => false, + } + }); +} + +pub fn create_log() { + // reinstantiate the log file and assign it to the metadata struct + let log_mapobj = interface::mapfilenew(LOGFILENAME.to_string()).unwrap(); + let mut logobj = LOGMAP.write(); + logobj.replace(log_mapobj); +} + +// Serialize New Metadata to CBOR, write to logfile +pub fn log_metadata(metadata: &FilesystemMetadata, inodenum: usize) { + let serialpair: (usize, Option<&Inode>); + let entrybytes; + + // pack and serialize log entry + if let Some(inode) = metadata.inodetable.get(&inodenum) { + serialpair = (inodenum, Some(&*inode)); + entrybytes = interface::serde_serialize_to_bytes(&serialpair).unwrap(); + } else { + serialpair = (inodenum, None); + entrybytes = interface::serde_serialize_to_bytes(&serialpair).unwrap(); + } + + // write to file + let mut mapopt = LOGMAP.write(); + let map = mapopt.as_mut().unwrap(); + map.write_to_map(&entrybytes).unwrap(); +} + +// Serialize Metadata Struct to CBOR, write to file +pub fn persist_metadata(metadata: &FilesystemMetadata) { + // Serialize metadata to string + let metadatabytes = interface::serde_serialize_to_bytes(&metadata).unwrap(); + + // remove file if it exists, assigning it to nothing to avoid the compiler yelling about unused result + let _ = interface::removefile(METADATAFILENAME.to_string()); + + // write to file + let mut metadata_fileobj = interface::openmetadata(METADATAFILENAME.to_string()).unwrap(); + metadata_fileobj + .writefile_from_bytes(&metadatabytes) + .unwrap(); + metadata_fileobj.close().unwrap(); +} + +pub fn convpath(cpath: &str) -> interface::RustPathBuf { + interface::RustPathBuf::from(cpath) +} + +/// This function resolves the absolute path of a directory from its inode number in a filesystem. +/// Here's how it operates: +/// +/// - Starts from the given inode and fetches its associated metadata from the filesystem's inode table. +/// +/// - Verifies that the inode represents a directory. +/// +/// - Attempts to find the parent directory by looking for the ".." entry in the current directory's entries. +/// +/// - Retrieves the directory name associated with the current inode using the `filenamefrominode` function and prepends it to the `path_string`. +/// +/// - Continues this process recursively, updating the current inode to the parent inode, and accumulating directory names in the `path_string`. +/// +/// - Stops when it reaches the root directory, where it prepends a "/" to the `path_string` and returns the complete path. +/// +/// This function effectively constructs the absolute path by backtracking the parent directories. However, if any issues arise during this process, such as missing metadata or inability to find the parent directory, it returns `None`. +pub fn pathnamefrominodenum(inodenum: usize) -> Option { + let mut path_string = String::new(); + let mut first_iteration = true; + let mut current_inodenum = inodenum; + + loop { + let mut thisinode = match FS_METADATA.inodetable.get_mut(¤t_inodenum) { + Some(inode) => inode, + None => { + return None; + } + }; + + match *thisinode { + Inode::Dir(ref mut dir_inode) => { + // We try to get the parent directory inode. + if let Some(parent_dir_inode) = dir_inode.filename_to_inode_dict.get("..") { + // If the parent node is 1 (indicating the root directory) and this is not the first iteration, this indicates that we have arrived at the root directory. Here we add a '/' to the beginning of the path string and return it. + if *parent_dir_inode == (1 as usize) { + if !first_iteration { + path_string.insert(0, '/'); + return Some(path_string); + } + first_iteration = false; + } + + match filenamefrominode(*parent_dir_inode, current_inodenum) { + Some(filename) => { + path_string = filename + "/" + &path_string; + current_inodenum = *parent_dir_inode; + } + None => return None, + }; + } else { + return None; + } + } + _ => { + return None; + } + } + } +} + +// Find the file by the given inode number in the given directory +pub fn filenamefrominode(dir_inode_no: usize, target_inode: usize) -> Option { + let cur_node = Some(FS_METADATA.inodetable.get(&dir_inode_no).unwrap()); + + match &*cur_node.unwrap() { + Inode::Dir(d) => { + let mut target_variable_name: Option = None; + + for entry in d.filename_to_inode_dict.iter() { + if entry.value() == &target_inode { + target_variable_name = Some(entry.key().to_owned()); + break; + } + } + return target_variable_name; + } + _ => return None, + } +} + +//returns tuple consisting of inode number of file (if it exists), and inode number of parent (if it exists) +pub fn metawalkandparent(path: &interface::RustPath) -> (Option, Option) { + let mut curnode = Some(FS_METADATA.inodetable.get(&ROOTDIRECTORYINODE).unwrap()); + let mut inodeno = Some(ROOTDIRECTORYINODE); + let mut previnodeno = None; + + //Iterate over the components of the pathbuf in order to walk the file tree + for comp in path.components() { + match comp { + //We've already done what initialization needs to be done + interface::RustPathComponent::RootDir => {} + + interface::RustPathComponent::Normal(f) => { + //If we're trying to get the child of a nonexistent directory, exit out + if inodeno.is_none() { + return (None, None); + } + match &*curnode.unwrap() { + Inode::Dir(d) => { + previnodeno = inodeno; + + //populate child inode number from parent directory's inode dict + inodeno = match d + .filename_to_inode_dict + .get(&f.to_str().unwrap().to_string()) + { + Some(num) => { + curnode = FS_METADATA.inodetable.get(&num); + Some(*num) + } + + //if no such child exists, update curnode, inodeno accordingly so that + //we can check against none as we do at the beginning of the Normal match arm + None => { + curnode = None; + None + } + } + } + //if we're trying to get a child of a non-directory inode, exit out + _ => { + return (None, None); + } + } + } + + //If it's a component of the pathbuf that we don't expect given a normed path, exit out + _ => { + return (None, None); + } + } + } + //return inode number and it's parent's number + (inodeno, previnodeno) +} +pub fn metawalk(path: &interface::RustPath) -> Option { + metawalkandparent(path).0 +} +pub fn normpath(origp: interface::RustPathBuf, cage: &Cage) -> interface::RustPathBuf { + //If path is relative, prefix it with the current working directory, otherwise populate it with rootdir + let mut newp = if origp.is_relative() { + (**cage.cwd.read()).clone() + } else { + interface::RustPathBuf::from("/") + }; + + for comp in origp.components() { + match comp { + //if we have a normal path component, push it on to our normed path + interface::RustPathComponent::Normal(_) => { + newp.push(comp); + } + + //if we have a .. path component, pop the last component off our normed path + interface::RustPathComponent::ParentDir => { + newp.pop(); + } + + //if we have a . path component (Or a root dir or a prefix(?)) do nothing + _ => {} + }; + } + newp +} + +pub fn remove_domain_sock(truepath: interface::RustPathBuf) { + match metawalkandparent(truepath.as_path()) { + //If the file does not exist + (None, ..) => { + panic!("path does not exist") + } + //If the file exists but has no parent, it's the root directory + (Some(_), None) => { + panic!("cannot unlink root directory") + } + + //If both the file and the parent directory exists + (Some(inodenum), Some(parentinodenum)) => { + Cage::remove_from_parent_dir(parentinodenum, &truepath); + + FS_METADATA.inodetable.remove(&inodenum); + NET_METADATA.domsock_paths.remove(&truepath); + } + } +} + +pub fn incref_root() { + if let Inode::Dir(ref mut rootdir_dirinode_obj) = + *(FS_METADATA.inodetable.get_mut(&ROOTDIRECTORYINODE).unwrap()) + { + rootdir_dirinode_obj.refcount += 1; + } else { + panic!("Root directory inode was not a directory"); + } +} + +pub fn decref_dir(cwd_container: &interface::RustPathBuf) { + if let Some(cwdinodenum) = metawalk(&cwd_container) { + if let Inode::Dir(ref mut cwddir) = *(FS_METADATA.inodetable.get_mut(&cwdinodenum).unwrap()) + { + cwddir.refcount -= 1; + + //if the directory has been removed but this cwd was the last open handle to it + if cwddir.refcount == 0 && cwddir.linkcount == 0 { + FS_METADATA.inodetable.remove(&cwdinodenum); + } + } else { + panic!("Cage had a cwd that was not a directory!"); + } + } else { + panic!("Cage had a cwd which did not exist!"); + } //we probably want to handle this case, maybe cwd should be an inode number?? Not urgent +} diff --git a/src/safeposix/mod.rs b/src/safeposix/mod.rs new file mode 100644 index 00000000..6f648bc8 --- /dev/null +++ b/src/safeposix/mod.rs @@ -0,0 +1,6 @@ +pub mod cage; +pub mod dispatcher; +pub mod filesystem; +pub mod net; +pub mod shm; +pub mod syscalls; diff --git a/src/safeposix/net.rs b/src/safeposix/net.rs new file mode 100644 index 00000000..6f117557 --- /dev/null +++ b/src/safeposix/net.rs @@ -0,0 +1,564 @@ +use super::cage::{Cage, FileDescriptor}; +use super::syscalls::net_constants::*; +use crate::interface; +use crate::interface::errnos::{syscall_error, Errno}; + +//Because other processes on the OS may allocate ephemeral ports, we allocate them from high to +//low whereas the OS allocates them from low to high +//Additionally, we can't tell whether a port is truly rebindable, this is because even when a port +//is closed sometimes there still is cleanup that the OS needs to do (for ephemeral ports which end +//up in the TIME_WAIT state). Therefore, we will assign ephemeral ports rather than simply from the +//highest available one, in a cyclic fashion skipping over unavailable ports. While this still may +//cause issues if specific port adresses in the ephemeral port range are allocated and closed before +//an ephemeral port would be bound there, it is much less likely that this will happen and is easy +//to avoid and nonstandard in user programs. See the code for _get_available_udp_port and its tcp +//counterpart for the implementation details. +const EPHEMERAL_PORT_RANGE_START: u16 = 32768; //sane default on linux +const EPHEMERAL_PORT_RANGE_END: u16 = 60999; +pub const TCPPORT: bool = true; +pub const UDPPORT: bool = false; + +pub static NET_METADATA: interface::RustLazyGlobal> = + interface::RustLazyGlobal::new(|| { + interface::RustRfc::new(NetMetadata { + used_port_set: interface::RustHashMap::new(), + next_ephemeral_port_tcpv4: interface::RustRfc::new(interface::RustLock::new( + EPHEMERAL_PORT_RANGE_END, + )), + next_ephemeral_port_udpv4: interface::RustRfc::new(interface::RustLock::new( + EPHEMERAL_PORT_RANGE_END, + )), + next_ephemeral_port_tcpv6: interface::RustRfc::new(interface::RustLock::new( + EPHEMERAL_PORT_RANGE_END, + )), + next_ephemeral_port_udpv6: interface::RustRfc::new(interface::RustLock::new( + EPHEMERAL_PORT_RANGE_END, + )), + listening_port_set: interface::RustHashSet::new(), + pending_conn_table: interface::RustHashMap::new(), + domsock_accept_table: interface::RustHashMap::new(), // manages domain socket connection process + domsock_paths: interface::RustHashSet::new(), // set of all currently bound domain sockets + }) + }); //we want to check if fs exists before doing a blank init, but not for now + +//A list of all network devices present on the machine +//It is populated from a file that should be present prior to running rustposix, see +//the implementation of read_netdevs for specifics +pub static NET_IFADDRS_STR: interface::RustLazyGlobal = + interface::RustLazyGlobal::new(|| interface::getifaddrs_from_file()); + +pub static NET_DEVICE_IPLIST: interface::RustLazyGlobal> = + interface::RustLazyGlobal::new(|| ips_from_ifaddrs()); + +fn ips_from_ifaddrs() -> Vec { + let mut ips = vec![]; + for net_device in NET_IFADDRS_STR.as_str().split('\n') { + if net_device == "" { + continue; + } + let ifaddrstr: Vec<&str> = net_device.split(' ').collect(); + let genipopt = interface::GenIpaddr::from_string(ifaddrstr[2]); + ips.push(genipopt.expect("Could not parse device ip address from net_devices file")); + } + + let genipopt0 = interface::GenIpaddr::from_string("0.0.0.0"); + ips.push(genipopt0.expect("Could not parse device ip address from net_devices file")); + return ips; +} + +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub enum PortType { + IPv4UDP, + IPv4TCP, + IPv6UDP, + IPv6TCP, +} + +pub fn mux_port( + addr: interface::GenIpaddr, + port: u16, + domain: i32, + istcp: bool, +) -> (interface::GenIpaddr, u16, PortType) { + match domain { + PF_INET => ( + addr, + port, + if istcp { + PortType::IPv4TCP + } else { + PortType::IPv4UDP + }, + ), + PF_INET6 => ( + addr, + port, + if istcp { + PortType::IPv6TCP + } else { + PortType::IPv6UDP + }, + ), + _ => panic!("How did you manage to set an unsupported domain on the socket?"), + } +} + +//A substructure for information only populated in a unix domain socket +#[derive(Debug)] +pub struct UnixSocketInfo { + pub mode: i32, + pub sendpipe: Option>, + pub receivepipe: Option>, + pub inode: usize, +} + +//This structure contains all socket-associated data that is not held in the fd +#[derive(Debug)] +pub struct SocketHandle { + pub innersocket: Option, + pub socket_options: i32, + pub tcp_options: i32, + pub state: ConnState, + pub protocol: i32, + pub domain: i32, + pub last_peek: interface::RustDeque, + pub localaddr: Option, + pub remoteaddr: Option, + pub unix_info: Option, + pub socktype: i32, + pub sndbuf: i32, + pub rcvbuf: i32, + pub errno: i32, +} + +//This cleanup-on-drop strategy is used in lieu of manual refcounting in order to allow the close +//syscall not to have to wait to increase the refcnt manually in case for example it is in a +//blocking recv. This clean-on-drop strategy is made possible by the fact that file descriptors +//hold reference to a SocketHandle via an Arc, so only when the last reference to a SocketHandle is +//gone--that is when the last cage has closed it--do we actually attempt to shut down the inner +//socket, which is what we could have done manually in close instead. This should be both cleaner +//and faster, because we don't have to wait for the recv timeout like we do in shutdown +impl Drop for SocketHandle { + fn drop(&mut self) { + Cage::_cleanup_socket_inner_helper(self, -1, false); + } +} + +#[derive(Debug)] +pub struct ConnCondVar { + lock: interface::RustRfc>, + cv: interface::Condvar, +} + +impl ConnCondVar { + pub fn new() -> Self { + Self { + lock: interface::RustRfc::new(interface::Mutex::new(0)), + cv: interface::Condvar::new(), + } + } + + pub fn wait(&self) { + let mut guard = self.lock.lock(); + *guard += 1; + self.cv.wait(&mut guard); + } + + pub fn broadcast(&self) -> bool { + let guard = self.lock.lock(); + if *guard == 1 { + self.cv.notify_all(); + return true; + } else { + return false; + } + } +} + +pub struct DomsockTableEntry { + pub sockaddr: interface::GenSockaddr, + pub receive_pipe: interface::RustRfc, + pub send_pipe: interface::RustRfc, + pub cond_var: Option>, +} + +impl DomsockTableEntry { + pub fn get_cond_var(&self) -> Option<&interface::RustRfc> { + self.cond_var.as_ref() + } + pub fn get_sockaddr(&self) -> &interface::GenSockaddr { + &self.sockaddr + } + pub fn get_send_pipe(&self) -> &interface::RustRfc { + &self.send_pipe + } + pub fn get_receive_pipe(&self) -> &interface::RustRfc { + &self.receive_pipe + } +} + +pub struct NetMetadata { + pub used_port_set: interface::RustHashMap<(u16, PortType), Vec<(interface::GenIpaddr, u32)>>, //maps port tuple to whether rebinding is allowed: 0 means there's a user but rebinding is not allowed, positive number means that many users, rebinding is allowed + next_ephemeral_port_tcpv4: interface::RustRfc>, + next_ephemeral_port_udpv4: interface::RustRfc>, + next_ephemeral_port_tcpv6: interface::RustRfc>, + next_ephemeral_port_udpv6: interface::RustRfc>, + pub listening_port_set: interface::RustHashSet<(interface::GenIpaddr, u16, PortType)>, + pub pending_conn_table: interface::RustHashMap< + (interface::GenIpaddr, u16, PortType), + Vec<(Result, interface::GenSockaddr)>, + >, + pub domsock_accept_table: interface::RustHashMap, + pub domsock_paths: interface::RustHashSet, +} + +impl NetMetadata { + fn initialize_port( + &self, + tup: &(interface::GenIpaddr, u16, PortType), + rebindability: u32, + ) -> bool { + let used_port_tup = (tup.1, tup.2.clone()); + if tup.0.is_unspecified() { + let tupclone = used_port_tup.clone(); + let entry = self.used_port_set.entry(tupclone.clone()); + match entry { + interface::RustHashEntry::Occupied(_) => { + return false; + } + interface::RustHashEntry::Vacant(v) => { + let mut intervec = vec![]; + for interface_addr in &*NET_DEVICE_IPLIST { + intervec.push((interface_addr.clone(), rebindability)); + } + v.insert(intervec); + } + } + true + } else { + match self.used_port_set.entry(used_port_tup) { + interface::RustHashEntry::Occupied(mut o) => { + let addrsused = o.get_mut(); + for addrtup in addrsused.clone() { + if addrtup.0 == tup.0 { + return false; + } + } + addrsused.push((tup.0.clone(), rebindability)); + } + interface::RustHashEntry::Vacant(v) => { + v.insert(vec![(tup.0.clone(), rebindability)]); + } + } + true + } + } + + pub fn _get_available_udp_port( + &self, + addr: interface::GenIpaddr, + domain: i32, + rebindability: bool, + ) -> Result { + if !NET_DEVICE_IPLIST.contains(&addr) { + return Err(syscall_error( + Errno::EADDRNOTAVAIL, + "bind", + "Specified network device is not set up for lind or does not exist!", + )); + } + let mut porttuple = mux_port(addr, 0, domain, UDPPORT); + + //start from the starting location we specified in a previous attempt to get an ephemeral port + let mut next_ephemeral = if domain == AF_INET { + self.next_ephemeral_port_udpv4.write() + } else if domain == AF_INET6 { + self.next_ephemeral_port_udpv6.write() + } else { + unreachable!() + }; + for range in [ + (EPHEMERAL_PORT_RANGE_START..=*next_ephemeral), + (*next_ephemeral + 1..=EPHEMERAL_PORT_RANGE_END), + ] { + for ne_port in range.rev() { + let port = ne_port.to_be(); //ports are stored in network endian order + porttuple.1 = port; + + //if we think we can bind to this port + if self.initialize_port(&porttuple, if rebindability { 1 } else { 0 }) { + //rebindability of 0 means not rebindable, 1 means it's rebindable and there's 1 bound to it + *next_ephemeral -= 1; + if *next_ephemeral < EPHEMERAL_PORT_RANGE_START { + *next_ephemeral = EPHEMERAL_PORT_RANGE_END; + } + return Ok(port); + } + } + } + return Err(syscall_error( + Errno::EADDRINUSE, + "bind", + "No available ephemeral port could be found", + )); + } + pub fn _get_available_tcp_port( + &self, + addr: interface::GenIpaddr, + domain: i32, + rebindability: bool, + ) -> Result { + if !NET_DEVICE_IPLIST.contains(&addr) { + return Err(syscall_error( + Errno::EADDRNOTAVAIL, + "bind", + "Specified network device is not set up for lind or does not exist!", + )); + } + let mut porttuple = mux_port(addr.clone(), 0, domain, TCPPORT); + + //start from the starting location we specified in a previous attempt to get an ephemeral port + let mut next_ephemeral = if domain == AF_INET { + self.next_ephemeral_port_tcpv4.write() + } else if domain == AF_INET6 { + self.next_ephemeral_port_tcpv6.write() + } else { + unreachable!() + }; + for range in [ + (EPHEMERAL_PORT_RANGE_START..=*next_ephemeral), + (*next_ephemeral + 1..=EPHEMERAL_PORT_RANGE_END), + ] { + for ne_port in range.rev() { + let port = ne_port.to_be(); //ports are stored in network endian order + porttuple.1 = port; + + if self.initialize_port(&porttuple, if rebindability { 1 } else { 0 }) { + //rebindability of 0 means not rebindable, 1 means it's rebindable and there's 1 bound to it + + *next_ephemeral -= 1; + if *next_ephemeral < EPHEMERAL_PORT_RANGE_START { + *next_ephemeral = EPHEMERAL_PORT_RANGE_END; + } + + return Ok(port); + } + } + } + return Err(syscall_error( + Errno::EADDRINUSE, + "bind", + "No available ephemeral port could be found", + )); + } + + pub fn _reserve_localport( + &self, + addr: interface::GenIpaddr, + port: u16, + protocol: i32, + domain: i32, + rebindability: bool, + ) -> Result { + if !NET_DEVICE_IPLIST.contains(&addr) { + return Err(syscall_error( + Errno::EADDRNOTAVAIL, + "bind", + "Specified network device is not set up for lind or does not exist!", + )); + } + + let muxed; + if protocol == IPPROTO_UDP { + if port == 0 { + return self._get_available_udp_port(addr, domain, rebindability); + //assign ephemeral port + } else { + muxed = mux_port(addr, port, domain, UDPPORT); + } + } else if protocol == IPPROTO_TCP { + if port == 0 { + return self._get_available_tcp_port(addr, domain, rebindability); + //assign ephemeral port + } else { + muxed = mux_port(addr, port, domain, TCPPORT); + } + } else { + panic!("Unknown protocol was set on socket somehow"); + } + + let usedport_muxed = (muxed.1, muxed.2); + let entry = self.used_port_set.entry(usedport_muxed); + if addr.is_unspecified() { + match entry { + interface::RustHashEntry::Occupied(_) => { + return Err(syscall_error( + Errno::EADDRINUSE, + "reserve port", + "port is already in use", + )); + } + interface::RustHashEntry::Vacant(v) => { + v.insert( + NET_DEVICE_IPLIST + .iter() + .map(|x| (x.clone(), if rebindability { 1 } else { 0 })) + .collect(), + ); + } + } + } else { + match entry { + interface::RustHashEntry::Occupied(mut userentry) => { + for portuser in userentry.get_mut() { + if portuser.0 == muxed.0 { + if portuser.1 == 0 { + return Err(syscall_error( + Errno::EADDRINUSE, + "reserve port", + "port is already in use", + )); + } else { + portuser.1 += 1; + } + break; + } + } + } + interface::RustHashEntry::Vacant(v) => { + v.insert(vec![(muxed.0.clone(), if rebindability { 1 } else { 0 })]); + } + } + } + Ok(port) + } + + pub fn _release_localport( + &self, + addr: interface::GenIpaddr, + port: u16, + protocol: i32, + domain: i32, + ) -> Result<(), i32> { + if !NET_DEVICE_IPLIST.contains(&addr) { + return Err(syscall_error( + Errno::EADDRNOTAVAIL, + "bind", + "Specified network device is not set up for lind or does not exist!", + )); + } + + let muxed; + if protocol == IPPROTO_TCP { + muxed = mux_port(addr.clone(), port, domain, TCPPORT); + } else if protocol == IPPROTO_UDP { + muxed = mux_port(addr.clone(), port, domain, UDPPORT); + } else { + return Err(syscall_error( + Errno::EINVAL, + "release", + "provided port has nonsensical protocol", + )); + } + + let usedport_muxed = (muxed.1, muxed.2); + let entry = self.used_port_set.entry(usedport_muxed); + match entry { + interface::RustHashEntry::Occupied(mut userentry) => { + let mut index = 0; + let userarr = userentry.get_mut(); + if addr.is_unspecified() { + for portuser in userarr.clone() { + if portuser.1 <= 1 { + userarr.swap_remove(index); + } else { + //if it's rebindable and there are others bound to it + userarr[index].1 -= 1; + } + } + if userarr.len() == 0 { + userentry.remove(); + } + return Ok(()); + } else { + for portuser in userarr.clone() { + if portuser.0 == muxed.0 { + //if it's rebindable and we're removing the last bound port or it's just not rebindable + if portuser.1 <= 1 { + if userarr.len() == 1 { + userentry.remove(); + } else { + userarr.swap_remove(index); + } + } else { + //if it's rebindable and there are others bound to it + userarr[index].1 -= 1; + } + return Ok(()); + } + index += 1; + } + unreachable!(); + } + } + interface::RustHashEntry::Vacant(_) => { + return Err(syscall_error( + Errno::EINVAL, + "release", + "provided port is not being used", + )); + } + } + } + + pub fn get_domainsock_paths(&self) -> Vec { + let mut domainsock_paths: Vec = vec![]; + for ds_path in self.domsock_paths.iter() { + domainsock_paths.push(ds_path.clone()); + } // get vector of domain sock table keys + domainsock_paths + } +} + +pub struct SelectInetInfo { + pub rawfd_lindfd_tuples: Vec<(i32, i32)>, + pub kernel_fds: interface::FdSet, + pub highest_raw_fd: i32, +} + +impl SelectInetInfo { + pub fn new() -> Self { + SelectInetInfo { + rawfd_lindfd_tuples: Vec::new(), + kernel_fds: interface::FdSet::new(), + highest_raw_fd: 0, + } + } +} + +pub fn update_readfds_from_kernel_select( + readfds: &mut interface::FdSet, + inet_info: &mut SelectInetInfo, + retval: &mut i32, +) -> i32 { + let kernel_ret; + // note that this select call always have timeout = 0, so it doesn't block + + kernel_ret = interface::kernel_select( + inet_info.highest_raw_fd + 1, + Some(&mut inet_info.kernel_fds), + None, + None, + ); + if kernel_ret > 0 { + // increment retval of our select + *retval += kernel_ret; + // translate the kernel checked fds to lindfds, and add to our new_writefds + readfds.set_from_kernelfds_and_translate( + &mut inet_info.kernel_fds, + inet_info.highest_raw_fd + 1, + &inet_info.rawfd_lindfd_tuples, + ); + } + return kernel_ret; +} diff --git a/src/safeposix/shm.rs b/src/safeposix/shm.rs new file mode 100644 index 00000000..f12fcc95 --- /dev/null +++ b/src/safeposix/shm.rs @@ -0,0 +1,144 @@ +// Filesystem metadata struct +#![allow(dead_code)] + +use super::syscalls::fs_constants::*; +use super::syscalls::sys_constants::*; +use crate::interface; + +use super::cage::Cage; + +pub static SHM_METADATA: interface::RustLazyGlobal> = + interface::RustLazyGlobal::new(|| interface::RustRfc::new(ShmMetadata::init_shm_metadata())); + +pub struct ShmSegment { + pub shminfo: interface::ShmidsStruct, + pub key: i32, + pub size: usize, + pub filebacking: interface::ShmFile, + pub rmid: bool, + pub attached_cages: interface::RustHashMap, // attached cages, number of references in cage + pub semaphor_offsets: interface::RustHashSet, +} + +pub fn new_shm_segment( + key: i32, + size: usize, + cageid: u32, + uid: u32, + gid: u32, + mode: u16, +) -> ShmSegment { + ShmSegment::new(key, size, cageid, uid, gid, mode) +} + +impl ShmSegment { + pub fn new(key: i32, size: usize, cageid: u32, uid: u32, gid: u32, mode: u16) -> ShmSegment { + let filebacking = interface::new_shm_backing(key, size).unwrap(); + + let time = interface::timestamp() as isize; //We do a real timestamp now + let permstruct = interface::IpcPermStruct { + __key: key, + uid: uid, + gid: gid, + cuid: uid, + cgid: gid, + mode: mode, + __pad1: 0, + __seq: 0, + __pad2: 0, + __unused1: 0, + __unused2: 0, + }; + let shminfo = interface::ShmidsStruct { + shm_perm: permstruct, + shm_segsz: size as u32, + shm_atime: 0, + shm_dtime: 0, + shm_ctime: time, + shm_cpid: cageid, + shm_lpid: 0, + shm_nattch: 0, + }; + + ShmSegment { + shminfo: shminfo, + key: key, + size: size, + filebacking: filebacking, + rmid: false, + attached_cages: interface::RustHashMap::new(), + semaphor_offsets: interface::RustHashSet::new(), + } + } + // mmap shared segment into cage, and increase attachments + // increase in cage references within attached_cages map + pub fn map_shm(&mut self, shmaddr: *mut u8, prot: i32, cageid: u64) -> i32 { + let fobjfdno = self.filebacking.as_fd_handle_raw_int(); + self.shminfo.shm_nattch += 1; + self.shminfo.shm_atime = interface::timestamp() as isize; + + match self.attached_cages.entry(cageid) { + interface::RustHashEntry::Occupied(mut occupied) => { + *occupied.get_mut() += 1; + } + interface::RustHashEntry::Vacant(vacant) => { + vacant.insert(1); + } + }; + interface::libc_mmap( + shmaddr, + self.size as usize, + prot, + MAP_SHARED | MAP_FIXED, + fobjfdno, + 0, + ) + } + + // unmap shared segment, decrease attachments + // decrease references within attached cages map + pub fn unmap_shm(&mut self, shmaddr: *mut u8, cageid: u64) { + interface::libc_mmap( + shmaddr, + self.size as usize, + PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + -1, + 0, + ); + self.shminfo.shm_nattch -= 1; + self.shminfo.shm_dtime = interface::timestamp() as isize; + match self.attached_cages.entry(cageid) { + interface::RustHashEntry::Occupied(mut occupied) => { + *occupied.get_mut() -= 1; + if *occupied.get() == 0 { + occupied.remove_entry(); + } + } + interface::RustHashEntry::Vacant(_) => { + panic!("Cage not avilable in segment attached cages"); + } + }; + } +} + +pub struct ShmMetadata { + pub nextid: interface::RustAtomicI32, + pub shmkeyidtable: interface::RustHashMap, + pub shmtable: interface::RustHashMap, +} + +impl ShmMetadata { + pub fn init_shm_metadata() -> ShmMetadata { + ShmMetadata { + nextid: interface::RustAtomicI32::new(1), + shmkeyidtable: interface::RustHashMap::new(), + shmtable: interface::RustHashMap::new(), + } + } + + pub fn new_keyid(&self) -> i32 { + self.nextid + .fetch_add(1, interface::RustAtomicOrdering::Relaxed) + } +} diff --git a/src/safeposix/syscalls/fs_calls.rs b/src/safeposix/syscalls/fs_calls.rs new file mode 100644 index 00000000..55746329 --- /dev/null +++ b/src/safeposix/syscalls/fs_calls.rs @@ -0,0 +1,3562 @@ +#![allow(dead_code)] + +// File system related system calls +use super::fs_constants::*; +use super::sys_constants::*; +use crate::interface; +use crate::safeposix::cage::Errno::EINVAL; +use crate::safeposix::cage::{FileDescriptor::*, *}; +use crate::safeposix::filesystem::*; +use crate::safeposix::net::NET_METADATA; +use crate::safeposix::shm::*; + +impl Cage { + //------------------------------------OPEN SYSCALL------------------------------------ + + fn _file_initializer(&self, inodenum: usize, flags: i32, size: usize) -> FileDesc { + //insert file descriptor into self.filedescriptortableable of the cage + let position = if 0 != flags & O_APPEND { size } else { 0 }; + let allowmask = O_RDWRFLAGS | O_CLOEXEC; + FileDesc { + position: position, + inode: inodenum, + flags: flags & allowmask, + advlock: interface::RustRfc::new(interface::AdvisoryLock::new()), + } + } + + pub fn open_syscall(&self, path: &str, flags: i32, mode: u32) -> i32 { + //Check that path is not empty + if path.len() == 0 { + return syscall_error(Errno::ENOENT, "open", "given path was null"); + } + let truepath = normpath(convpath(path), self); + + let (fd, guardopt) = self.get_next_fd(None); + if fd < 0 { + return fd; + } + let fdoption = &mut *guardopt.unwrap(); + + match metawalkandparent(truepath.as_path()) { + //If neither the file nor parent exists + (None, None) => { + if 0 == (flags & O_CREAT) { + return syscall_error( + Errno::ENOENT, + "open", + "tried to open a file that did not exist, and O_CREAT was not specified", + ); + } + return syscall_error(Errno::ENOENT, "open", "a directory component in pathname does not exist or is a dangling symbolic link"); + } + + //If the file doesn't exist but the parent does + (None, Some(pardirinode)) => { + if 0 == (flags & O_CREAT) { + return syscall_error( + Errno::ENOENT, + "open", + "tried to open a file that did not exist, and O_CREAT was not specified", + ); + } + + let filename = truepath.file_name().unwrap().to_str().unwrap().to_string(); //for now we assume this is sane, but maybe this should be checked later + + if S_IFCHR == (S_IFCHR & flags) { + return syscall_error(Errno::EINVAL, "open", "Invalid value in flags"); + } + + let effective_mode = S_IFREG as u32 | mode; + + if mode & (S_IRWXA | S_FILETYPEFLAGS as u32) != mode { + return syscall_error(Errno::EPERM, "open", "Mode bits were not sane"); + } //assert sane mode bits + + let time = interface::timestamp(); //We do a real timestamp now + let newinode = Inode::File(GenericInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_GID, + mode: effective_mode, + linkcount: 1, + refcount: 1, + atime: time, + ctime: time, + mtime: time, + }); + + let newinodenum = FS_METADATA + .nextinode + .fetch_add(1, interface::RustAtomicOrdering::Relaxed); //fetch_add returns the previous value, which is the inode number we want + if let Inode::Dir(ref mut ind) = + *(FS_METADATA.inodetable.get_mut(&pardirinode).unwrap()) + { + ind.filename_to_inode_dict.insert(filename, newinodenum); + ind.linkcount += 1; + //insert a reference to the file in the parent directory + } else { + return syscall_error( + Errno::ENOTDIR, + "open", + "tried to create a file as a child of something that isn't a directory", + ); + } + FS_METADATA.inodetable.insert(newinodenum, newinode); + log_metadata(&FS_METADATA, pardirinode); + log_metadata(&FS_METADATA, newinodenum); + + if let interface::RustHashEntry::Vacant(vac) = FILEOBJECTTABLE.entry(newinodenum) { + let sysfilename = format!("{}{}", FILEDATAPREFIX, newinodenum); + vac.insert(interface::openfile(sysfilename, 0).unwrap()); // new file of size 0 + } + + let _insertval = + fdoption.insert(File(self._file_initializer(newinodenum, flags, 0))); + } + + //If the file exists (we don't need to look at parent here) + (Some(inodenum), ..) => { + if (O_CREAT | O_EXCL) == (flags & (O_CREAT | O_EXCL)) { + return syscall_error( + Errno::EEXIST, + "open", + "file already exists and O_CREAT and O_EXCL were used", + ); + } + let size; + + let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + match *inodeobj { + Inode::File(ref mut f) => { + if O_TRUNC == (flags & O_TRUNC) { + // We only do this to regular files, otherwise O_TRUNC is undefined + //close the file object if another cage has it open + let entry = FILEOBJECTTABLE.entry(inodenum); + if let interface::RustHashEntry::Occupied(occ) = &entry { + occ.get().close().unwrap(); + } + // resize it to 0 + f.size = 0; + + //remove the previous file and add a new one of 0 length + if let interface::RustHashEntry::Occupied(occ) = entry { + occ.remove_entry(); + } + + let sysfilename = format!("{}{}", FILEDATAPREFIX, inodenum); + interface::removefile(sysfilename.clone()).unwrap(); + } + + if let interface::RustHashEntry::Vacant(vac) = + FILEOBJECTTABLE.entry(inodenum) + { + let sysfilename = format!("{}{}", FILEDATAPREFIX, inodenum); + vac.insert(interface::openfile(sysfilename, f.size).unwrap()); + // use existing file size + } + + size = f.size; + f.refcount += 1; + } + Inode::Dir(ref mut f) => { + size = f.size; + f.refcount += 1; + } + Inode::CharDev(ref mut f) => { + size = f.size; + f.refcount += 1; + } + Inode::Socket(_) => { + return syscall_error(Errno::ENXIO, "open", "file is a UNIX domain socket"); + } + } + + let _insertval = + fdoption.insert(File(self._file_initializer(inodenum, flags, size))); + } + } + + fd //open returns the opened file descriptor + } + + //------------------MKDIR SYSCALL------------------ + + pub fn mkdir_syscall(&self, path: &str, mode: u32) -> i32 { + //Check that path is not empty + if path.len() == 0 { + return syscall_error(Errno::ENOENT, "mkdir", "given path was null"); + } + let truepath = normpath(convpath(path), self); + + //pass the metadata to this helper. If passed table is none, then create new instance + let metadata = &FS_METADATA; + + match metawalkandparent(truepath.as_path()) { + //If neither the file nor parent exists + (None, None) => syscall_error( + Errno::ENOENT, + "mkdir", + "a directory component in pathname does not exist or is a dangling symbolic link", + ), + + //If the file doesn't exist but the parent does + (None, Some(pardirinode)) => { + let filename = truepath.file_name().unwrap().to_str().unwrap().to_string(); //for now we assume this is sane, but maybe this should be checked later + + let effective_mode = S_IFDIR as u32 | mode; + + //assert sane mode bits + if mode & (S_IRWXA | S_FILETYPEFLAGS as u32) != mode { + return syscall_error(Errno::EPERM, "mkdir", "Mode bits were not sane"); + } + + let newinodenum = FS_METADATA + .nextinode + .fetch_add(1, interface::RustAtomicOrdering::Relaxed); //fetch_add returns the previous value, which is the inode number we want + let time = interface::timestamp(); //We do a real timestamp now + + let newinode = Inode::Dir(DirectoryInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_GID, + mode: effective_mode, + linkcount: 3, + refcount: 0, //2 because ., and .., as well as reference in parent directory + atime: time, + ctime: time, + mtime: time, + filename_to_inode_dict: init_filename_to_inode_dict(newinodenum, pardirinode), + }); + + if let Inode::Dir(ref mut parentdir) = + *(metadata.inodetable.get_mut(&pardirinode).unwrap()) + { + parentdir + .filename_to_inode_dict + .insert(filename, newinodenum); + parentdir.linkcount += 1; + } + //insert a reference to the file in the parent directory + else { + unreachable!(); + } + metadata.inodetable.insert(newinodenum, newinode); + log_metadata(&metadata, pardirinode); + log_metadata(&metadata, newinodenum); + 0 //mkdir has succeeded + } + + (Some(_), ..) => syscall_error( + Errno::EEXIST, + "mkdir", + "pathname already exists, cannot create directory", + ), + } + } + + //------------------MKNOD SYSCALL------------------ + + pub fn mknod_syscall(&self, path: &str, mode: u32, dev: u64) -> i32 { + //Check that path is not empty + if path.len() == 0 { + return syscall_error(Errno::ENOENT, "mknod", "given path was null"); + } + let truepath = normpath(convpath(path), self); + + //pass the metadata to this helper. If passed table is none, then create new instance + let metadata = &FS_METADATA; + + match metawalkandparent(truepath.as_path()) { + //If neither the file nor parent exists + (None, None) => syscall_error( + Errno::ENOENT, + "mknod", + "a directory component in pathname does not exist or is a dangling symbolic link", + ), + + //If the file doesn't exist but the parent does + (None, Some(pardirinode)) => { + let filename = truepath.file_name().unwrap().to_str().unwrap().to_string(); //for now we assume this is sane, but maybe this should be checked later + + //assert sane mode bits (asserting that the mode bits make sense) + if mode & (S_IRWXA | S_FILETYPEFLAGS as u32) != mode { + return syscall_error(Errno::EPERM, "mknod", "Mode bits were not sane"); + } + if mode as i32 & S_IFCHR == 0 { + return syscall_error( + Errno::EINVAL, + "mknod", + "only character files are supported", + ); + } + let time = interface::timestamp(); //We do a real timestamp now + let newinode = Inode::CharDev(DeviceInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_GID, + mode: mode, + linkcount: 1, + refcount: 0, + atime: time, + ctime: time, + mtime: time, + dev: devtuple(dev), + }); + + let newinodenum = FS_METADATA + .nextinode + .fetch_add(1, interface::RustAtomicOrdering::Relaxed); //fetch_add returns the previous value, which is the inode number we want + if let Inode::Dir(ref mut parentdir) = + *(FS_METADATA.inodetable.get_mut(&pardirinode).unwrap()) + { + parentdir + .filename_to_inode_dict + .insert(filename, newinodenum); + parentdir.linkcount += 1; + } //insert a reference to the file in the parent directory + metadata.inodetable.insert(newinodenum, newinode); + log_metadata(metadata, pardirinode); + log_metadata(metadata, newinodenum); + 0 //mknod has succeeded + } + + (Some(_), ..) => syscall_error( + Errno::EEXIST, + "mknod", + "pathname already exists, cannot create device file", + ), + } + } + + //------------------------------------LINK SYSCALL------------------------------------ + + pub fn link_syscall(&self, oldpath: &str, newpath: &str) -> i32 { + if oldpath.len() == 0 { + return syscall_error(Errno::ENOENT, "link", "given oldpath was null"); + } + if newpath.len() == 0 { + return syscall_error(Errno::ENOENT, "link", "given newpath was null"); + } + let trueoldpath = normpath(convpath(oldpath), self); + let truenewpath = normpath(convpath(newpath), self); + let filename = truenewpath + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(); //for now we assume this is sane, but maybe this should be checked later + + match metawalk(trueoldpath.as_path()) { + //If neither the file nor parent exists + None => syscall_error( + Errno::ENOENT, + "link", + "a directory component in pathname does not exist or is a dangling symbolic link", + ), + Some(inodenum) => { + let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + + match *inodeobj { + Inode::File(ref mut normalfile_inode_obj) => { + normalfile_inode_obj.linkcount += 1; //add link to inode + } + + Inode::CharDev(ref mut chardev_inode_obj) => { + chardev_inode_obj.linkcount += 1; //add link to inode + } + + Inode::Socket(ref mut socket_inode_obj) => { + socket_inode_obj.linkcount += 1; //add link to inode + } + + Inode::Dir(_) => { + return syscall_error(Errno::EPERM, "link", "oldpath is a directory") + } + } + + drop(inodeobj); + + let retval = match metawalkandparent(truenewpath.as_path()) { + (None, None) => { + syscall_error(Errno::ENOENT, "link", "newpath cannot be created") + } + + (None, Some(pardirinode)) => { + let mut parentinodeobj = + FS_METADATA.inodetable.get_mut(&pardirinode).unwrap(); + //insert a reference to the inode in the parent directory + if let Inode::Dir(ref mut parentdirinodeobj) = *parentinodeobj { + parentdirinodeobj + .filename_to_inode_dict + .insert(filename, inodenum); + parentdirinodeobj.linkcount += 1; + drop(parentinodeobj); + log_metadata(&FS_METADATA, pardirinode); + log_metadata(&FS_METADATA, inodenum); + } else { + panic!("Parent directory was not a directory!"); + } + 0 //link has succeeded + } + + (Some(_), ..) => syscall_error(Errno::EEXIST, "link", "newpath already exists"), + }; + + if retval != 0 { + //reduce the linkcount to its previous value if linking failed + let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + + match *inodeobj { + Inode::File(ref mut normalfile_inode_obj) => { + normalfile_inode_obj.linkcount -= 1; + } + + Inode::CharDev(ref mut chardev_inode_obj) => { + chardev_inode_obj.linkcount -= 1; + } + + Inode::Socket(ref mut socket_inode_obj) => { + socket_inode_obj.linkcount -= 1; + } + + Inode::Dir(_) => { + panic!("Known non-directory file has been replaced with a directory!"); + } + } + } + + return retval; + } + } + } + + //------------------------------------UNLINK SYSCALL------------------------------------ + + pub fn unlink_syscall(&self, path: &str) -> i32 { + if path.len() == 0 { + return syscall_error(Errno::ENOENT, "unmknod", "given oldpath was null"); + } + let truepath = normpath(convpath(path), self); + + match metawalkandparent(truepath.as_path()) { + //If the file does not exist + (None, ..) => syscall_error(Errno::ENOENT, "unlink", "path does not exist"), + + //If the file exists but has no parent, it's the root directory + (Some(_), None) => { + syscall_error(Errno::EISDIR, "unlink", "cannot unlink root directory") + } + + //If both the file and the parent directory exists + (Some(inodenum), Some(parentinodenum)) => { + let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + + let (currefcount, curlinkcount, has_fobj, log) = match *inodeobj { + Inode::File(ref mut f) => { + f.linkcount -= 1; + (f.refcount, f.linkcount, true, true) + } + Inode::CharDev(ref mut f) => { + f.linkcount -= 1; + (f.refcount, f.linkcount, false, true) + } + Inode::Socket(ref mut f) => { + f.linkcount -= 1; + (f.refcount, f.linkcount, false, false) + } + Inode::Dir(_) => { + return syscall_error(Errno::EISDIR, "unlink", "cannot unlink directory"); + } + }; //count current number of links and references + + drop(inodeobj); + + let removal_result = Self::remove_from_parent_dir(parentinodenum, &truepath); + if removal_result != 0 { + return removal_result; + } + + if curlinkcount == 0 { + if currefcount == 0 { + //actually remove file and the handle to it + FS_METADATA.inodetable.remove(&inodenum); + if has_fobj { + let sysfilename = format!("{}{}", FILEDATAPREFIX, inodenum); + interface::removefile(sysfilename).unwrap(); + } + } //we don't need a separate unlinked flag, we can just check that refcount is 0 + } + NET_METADATA.domsock_paths.remove(&truepath); + + // the log boolean will be false if we are workign on a domain socket + if log { + log_metadata(&FS_METADATA, parentinodenum); + log_metadata(&FS_METADATA, inodenum); + } + 0 //unlink has succeeded + } + } + } + + //------------------------------------CREAT SYSCALL------------------------------------ + + pub fn creat_syscall(&self, path: &str, mode: u32) -> i32 { + self.open_syscall(path, O_CREAT | O_TRUNC | O_WRONLY, mode) + } + + //------------------------------------STAT SYSCALL------------------------------------ + + pub fn stat_syscall(&self, path: &str, statbuf: &mut StatData) -> i32 { + let truepath = normpath(convpath(path), self); + + //Walk the file tree to get inode from path + if let Some(inodenum) = metawalk(truepath.as_path()) { + let inodeobj = FS_METADATA.inodetable.get(&inodenum).unwrap(); + + //populate those fields in statbuf which depend on things other than the inode object + statbuf.st_dev = FS_METADATA.dev_id; + statbuf.st_ino = inodenum; + + //delegate the rest of populating statbuf to the relevant helper + match &*inodeobj { + Inode::File(f) => { + Self::_istat_helper(&f, statbuf); + } + Inode::CharDev(f) => { + Self::_istat_helper_chr_file(&f, statbuf); + } + Inode::Socket(f) => { + Self::_istat_helper_sock(&f, statbuf); + } + Inode::Dir(f) => { + Self::_istat_helper_dir(&f, statbuf); + } + } + 0 //stat has succeeded! + } else { + syscall_error(Errno::ENOENT, "stat", "path refers to an invalid file") + } + } + + fn _istat_helper(inodeobj: &GenericInode, statbuf: &mut StatData) { + statbuf.st_mode = inodeobj.mode; + statbuf.st_nlink = inodeobj.linkcount; + statbuf.st_uid = inodeobj.uid; + statbuf.st_gid = inodeobj.gid; + statbuf.st_rdev = 0; + statbuf.st_size = inodeobj.size; + statbuf.st_blksize = 0; + statbuf.st_blocks = 0; + } + + fn _istat_helper_sock(inodeobj: &SocketInode, statbuf: &mut StatData) { + statbuf.st_mode = inodeobj.mode; + statbuf.st_nlink = inodeobj.linkcount; + statbuf.st_uid = inodeobj.uid; + statbuf.st_gid = inodeobj.gid; + statbuf.st_rdev = 0; + statbuf.st_size = inodeobj.size; + statbuf.st_blksize = 0; + statbuf.st_blocks = 0; + } + + fn _istat_helper_dir(inodeobj: &DirectoryInode, statbuf: &mut StatData) { + statbuf.st_mode = inodeobj.mode; + statbuf.st_nlink = inodeobj.linkcount; + statbuf.st_uid = inodeobj.uid; + statbuf.st_gid = inodeobj.gid; + statbuf.st_rdev = 0; + statbuf.st_size = inodeobj.size; + statbuf.st_blksize = 0; + statbuf.st_blocks = 0; + } + + fn _istat_helper_chr_file(inodeobj: &DeviceInode, statbuf: &mut StatData) { + statbuf.st_dev = 5; + statbuf.st_mode = inodeobj.mode; + statbuf.st_nlink = inodeobj.linkcount; + statbuf.st_uid = inodeobj.uid; + statbuf.st_gid = inodeobj.gid; + //compose device number into u64 + statbuf.st_rdev = makedev(&inodeobj.dev); + statbuf.st_size = inodeobj.size; + } + + //Streams and pipes don't have associated inodes so we populate them from mostly dummy information + fn _stat_alt_helper(&self, statbuf: &mut StatData, inodenum: usize) { + statbuf.st_dev = FS_METADATA.dev_id; + statbuf.st_ino = inodenum; + statbuf.st_mode = 49590; //r and w priveliged + statbuf.st_nlink = 1; + statbuf.st_uid = DEFAULT_UID; + statbuf.st_gid = DEFAULT_GID; + statbuf.st_rdev = 0; + statbuf.st_size = 0; + statbuf.st_blksize = 0; + statbuf.st_blocks = 0; + } + + //------------------------------------FSTAT SYSCALL------------------------------------ + + pub fn fstat_syscall(&self, fd: i32, statbuf: &mut StatData) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + //Delegate populating statbuf to the relevant helper depending on the file type. + //First we check in the file descriptor to handle sockets, streams, and pipes, + //and if it is a normal file descriptor we handle regular files, dirs, and char + //files based on the information in the inode. + match filedesc_enum { + File(normalfile_filedesc_obj) => { + let inode = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + + //populate those fields in statbuf which depend on things other than the inode object + statbuf.st_ino = normalfile_filedesc_obj.inode; + statbuf.st_dev = FS_METADATA.dev_id; + + match &*inode { + Inode::File(f) => { + Self::_istat_helper(&f, statbuf); + } + Inode::CharDev(f) => { + Self::_istat_helper_chr_file(&f, statbuf); + } + Inode::Socket(f) => { + Self::_istat_helper_sock(&f, statbuf); + } + Inode::Dir(f) => { + Self::_istat_helper_dir(&f, statbuf); + } + } + } + Socket(_) => { + return syscall_error( + Errno::EOPNOTSUPP, + "fstat", + "we don't support fstat on sockets yet", + ); + } + Stream(_) => { + self._stat_alt_helper(statbuf, STREAMINODE); + } + Pipe(_) => { + self._stat_alt_helper(statbuf, 0xfeef0000); + } + Epoll(_) => { + self._stat_alt_helper(statbuf, 0xfeef0000); + } + } + 0 //fstat has succeeded! + } else { + syscall_error(Errno::EBADF, "fstat", "invalid file descriptor") + } + } + + //------------------------------------STATFS SYSCALL------------------------------------ + + pub fn statfs_syscall(&self, path: &str, databuf: &mut FSData) -> i32 { + let truepath = normpath(convpath(path), self); + + //Walk the file tree to get inode from path + if let Some(inodenum) = metawalk(truepath.as_path()) { + let _inodeobj = FS_METADATA.inodetable.get(&inodenum).unwrap(); + + //populate the dev id field -- can be done outside of the helper + databuf.f_fsid = FS_METADATA.dev_id; + + //delegate the rest of populating statbuf to the relevant helper + return Self::_istatfs_helper(self, databuf); + } else { + syscall_error(Errno::ENOENT, "stat", "path refers to an invalid file") + } + } + + //------------------------------------FSTATFS SYSCALL------------------------------------ + + pub fn fstatfs_syscall(&self, fd: i32, databuf: &mut FSData) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + //populate the dev id field -- can be done outside of the helper + databuf.f_fsid = FS_METADATA.dev_id; + + match filedesc_enum { + File(normalfile_filedesc_obj) => { + let _inodeobj = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + + return Self::_istatfs_helper(self, databuf); + } + Socket(_) | Pipe(_) | Stream(_) | Epoll(_) => { + return syscall_error( + Errno::EBADF, + "fstatfs", + "can't fstatfs on socket, stream, pipe, or epollfd", + ); + } + } + } + return syscall_error(Errno::EBADF, "statfs", "invalid file descriptor"); + } + + pub fn _istatfs_helper(&self, databuf: &mut FSData) -> i32 { + databuf.f_type = 0xBEEFC0DE; //unassigned + databuf.f_bsize = 4096; + databuf.f_blocks = 0; //int(limits['diskused']) / 4096 + databuf.f_bfree = 1024 * 1024 * 1024; //(int(limits['diskused']-usage['diskused'])) / 4096 + databuf.f_bavail = 1024 * 1024 * 1024; //(int(limits['diskused']-usage['diskused'])) / 4096 + databuf.f_files = 1024 * 1024 * 1024; + databuf.f_ffiles = 1024 * 1024 * 515; + databuf.f_namelen = 254; + databuf.f_frsize = 4096; + databuf.f_spare = [0; 32]; + + 0 //success! + } + + //------------------------------------READ SYSCALL------------------------------------ + + pub fn read_syscall(&self, fd: i32, buf: *mut u8, count: usize) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + //delegate to pipe, stream, or socket helper if specified by file descriptor enum type (none of them are implemented yet) + match filedesc_enum { + //we must borrow the filedesc object as a mutable reference to update the position + File(ref mut normalfile_filedesc_obj) => { + if is_wronly(normalfile_filedesc_obj.flags) { + return syscall_error( + Errno::EBADF, + "read", + "specified file not open for reading", + ); + } + + let inodeobj = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + + //delegate to character if it's a character file, checking based on the type of the inode object + match &*inodeobj { + Inode::File(_) => { + let position = normalfile_filedesc_obj.position; + let fileobject = + FILEOBJECTTABLE.get(&normalfile_filedesc_obj.inode).unwrap(); + + if let Ok(bytesread) = fileobject.readat(buf, count, position) { + //move position forward by the number of bytes we've read + + normalfile_filedesc_obj.position += bytesread; + bytesread as i32 + } else { + 0 //0 bytes read, but not an error value that can/should be passed to the user + } + } + + Inode::CharDev(char_inode_obj) => { + self._read_chr_file(&char_inode_obj, buf, count) + } + + Inode::Socket(_) => { + panic!("read(): Socket inode found on a filedesc fd.") + } + + Inode::Dir(_) => syscall_error( + Errno::EISDIR, + "read", + "attempted to read from a directory", + ), + } + } + Socket(_) => { + drop(unlocked_fd); + self.recv_common(fd, buf, count, 0, &mut None) + } + Stream(_) => syscall_error( + Errno::EOPNOTSUPP, + "read", + "reading from stdin not implemented yet", + ), + Pipe(pipe_filedesc_obj) => { + if is_wronly(pipe_filedesc_obj.flags) { + return syscall_error( + Errno::EBADF, + "read", + "specified file not open for reading", + ); + } + let mut nonblocking = false; + if pipe_filedesc_obj.flags & O_NONBLOCK != 0 { + nonblocking = true; + } + loop { + // loop over pipe reads so we can periodically check for cancellation + let ret = pipe_filedesc_obj + .pipe + .read_from_pipe(buf, count, nonblocking) + as i32; + if pipe_filedesc_obj.flags & O_NONBLOCK == 0 + && ret == -(Errno::EAGAIN as i32) + { + if self + .cancelstatus + .load(interface::RustAtomicOrdering::Relaxed) + { + // if the cancel status is set in the cage, we trap around a cancel point + // until the individual thread is signaled to cancel itself + loop { + interface::cancelpoint(self.cageid); + } + } + continue; //received EAGAIN on blocking pipe, try again + } + return ret; // if we get here we can return + } + } + Epoll(_) => syscall_error( + Errno::EINVAL, + "read", + "fd is attached to an object which is unsuitable for reading", + ), + } + } else { + syscall_error(Errno::EBADF, "read", "invalid file descriptor") + } + } + + //------------------------------------PREAD SYSCALL------------------------------------ + pub fn pread_syscall(&self, fd: i32, buf: *mut u8, count: usize, offset: isize) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + //we must borrow the filedesc object as a mutable reference to update the position + File(ref mut normalfile_filedesc_obj) => { + if is_wronly(normalfile_filedesc_obj.flags) { + return syscall_error( + Errno::EBADF, + "pread", + "specified file not open for reading", + ); + } + + let inodeobj = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + + //delegate to character if it's a character file, checking based on the type of the inode object + match &*inodeobj { + Inode::File(_) => { + let fileobject = + FILEOBJECTTABLE.get(&normalfile_filedesc_obj.inode).unwrap(); + + if let Ok(bytesread) = fileobject.readat(buf, count, offset as usize) { + bytesread as i32 + } else { + 0 //0 bytes read, but not an error value that can/should be passed to the user + } + } + + Inode::CharDev(char_inode_obj) => { + self._read_chr_file(&char_inode_obj, buf, count) + } + + Inode::Socket(_) => { + panic!("pread(): Socket inode found on a filedesc fd") + } + + Inode::Dir(_) => syscall_error( + Errno::EISDIR, + "pread", + "attempted to read from a directory", + ), + } + } + Socket(_) => syscall_error( + Errno::ESPIPE, + "pread", + "file descriptor is associated with a socket, cannot seek", + ), + Stream(_) => syscall_error( + Errno::ESPIPE, + "pread", + "file descriptor is associated with a stream, cannot seek", + ), + Pipe(_) => syscall_error( + Errno::ESPIPE, + "pread", + "file descriptor is associated with a pipe, cannot seek", + ), + Epoll(_) => syscall_error( + Errno::ESPIPE, + "pread", + "file descriptor is associated with an epollfd, cannot seek", + ), + } + } else { + syscall_error(Errno::EBADF, "pread", "invalid file descriptor") + } + } + + fn _read_chr_file(&self, inodeobj: &DeviceInode, buf: *mut u8, count: usize) -> i32 { + match inodeobj.dev { + NULLDEVNO => 0, //reading from /dev/null always reads 0 bytes + ZERODEVNO => interface::fillzero(buf, count), + RANDOMDEVNO => interface::fillrandom(buf, count), + URANDOMDEVNO => interface::fillrandom(buf, count), + _ => syscall_error( + Errno::EOPNOTSUPP, + "read or pread", + "read from specified device not implemented", + ), + } + } + + //------------------------------------WRITE SYSCALL------------------------------------ + + pub fn write_syscall(&self, fd: i32, buf: *const u8, count: usize) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + //delegate to pipe, stream, or socket helper if specified by file descriptor enum type + match filedesc_enum { + //we must borrow the filedesc object as a mutable reference to update the position + File(ref mut normalfile_filedesc_obj) => { + if is_rdonly(normalfile_filedesc_obj.flags) { + return syscall_error( + Errno::EBADF, + "write", + "specified file not open for writing", + ); + } + + let mut inodeobj = FS_METADATA + .inodetable + .get_mut(&normalfile_filedesc_obj.inode) + .unwrap(); + + //delegate to character helper or print out if it's a character file or stream, + //checking based on the type of the inode object + match *inodeobj { + Inode::File(ref mut normalfile_inode_obj) => { + let position = normalfile_filedesc_obj.position; + + let filesize = normalfile_inode_obj.size; + let blankbytecount = position as isize - filesize as isize; + + let mut fileobject = FILEOBJECTTABLE + .get_mut(&normalfile_filedesc_obj.inode) + .unwrap(); + + //we need to pad the file with blank bytes if we are at a position past the end of the file! + if blankbytecount > 0 { + if let Ok(byteswritten) = + fileobject.zerofill_at(filesize, blankbytecount as usize) + { + if byteswritten != blankbytecount as usize { + panic!("Write of blank bytes for write failed!"); + } + } else { + panic!("Write of blank bytes for write failed!"); + } + } + + let newposition; + if let Ok(byteswritten) = fileobject.writeat(buf, count, position) { + //move position forward by the number of bytes we've written + normalfile_filedesc_obj.position = position + byteswritten; + newposition = normalfile_filedesc_obj.position; + if newposition > normalfile_inode_obj.size { + normalfile_inode_obj.size = newposition; + drop(inodeobj); + drop(fileobject); + log_metadata(&FS_METADATA, normalfile_filedesc_obj.inode); + } //update file size if necessary + + byteswritten as i32 + } else { + 0 //0 bytes written, but not an error value that can/should be passed to the user + } + } + + Inode::CharDev(ref char_inode_obj) => { + self._write_chr_file(&char_inode_obj, buf, count) + } + + Inode::Socket(_) => { + panic!("write(): Socket inode found on a filedesc fd") + } + + Inode::Dir(_) => syscall_error( + Errno::EISDIR, + "write", + "attempted to write to a directory", + ), + } + } + Socket(_) => { + drop(unlocked_fd); + self.send_syscall(fd, buf, count, 0) + } + Stream(stream_filedesc_obj) => { + //if it's stdout or stderr, print out and we're done + if stream_filedesc_obj.stream == 1 || stream_filedesc_obj.stream == 2 { + interface::log_from_ptr(buf, count); + count as i32 + } else { + return syscall_error( + Errno::EBADF, + "write", + "specified stream not open for writing", + ); + } + } + Pipe(pipe_filedesc_obj) => { + if is_rdonly(pipe_filedesc_obj.flags) { + return syscall_error( + Errno::EBADF, + "write", + "specified pipe not open for writing", + ); + } + + let mut nonblocking = false; + if pipe_filedesc_obj.flags & O_NONBLOCK != 0 { + nonblocking = true; + } + + let retval = pipe_filedesc_obj + .pipe + .write_to_pipe(buf, count, nonblocking) + as i32; + if retval == -(Errno::EPIPE as i32) { + interface::lind_kill_from_id(self.cageid, SIGPIPE); + } // Trigger SIGPIPE + retval + } + Epoll(_) => syscall_error( + Errno::EINVAL, + "write", + "fd is attached to an object which is unsuitable for writing", + ), + } + } else { + syscall_error(Errno::EBADF, "write", "invalid file descriptor") + } + } + + //------------------------------------PWRITE SYSCALL------------------------------------ + + pub fn pwrite_syscall(&self, fd: i32, buf: *const u8, count: usize, offset: isize) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + //we must borrow the filedesc object as a mutable reference to update the position + File(ref mut normalfile_filedesc_obj) => { + if is_rdonly(normalfile_filedesc_obj.flags) { + return syscall_error( + Errno::EBADF, + "pwrite", + "specified file not open for writing", + ); + } + + let mut inodeobj = FS_METADATA + .inodetable + .get_mut(&normalfile_filedesc_obj.inode) + .unwrap(); + + //delegate to character helper or print out if it's a character file or stream, + //checking based on the type of the inode object + match *inodeobj { + Inode::File(ref mut normalfile_inode_obj) => { + let position = offset as usize; + let filesize = normalfile_inode_obj.size; + let blankbytecount = offset - filesize as isize; + + let mut fileobject = FILEOBJECTTABLE + .get_mut(&normalfile_filedesc_obj.inode) + .unwrap(); + + //we need to pad the file with blank bytes if we are seeking past the end of the file! + if blankbytecount > 0 { + if let Ok(byteswritten) = + fileobject.zerofill_at(filesize, blankbytecount as usize) + { + if byteswritten != blankbytecount as usize { + panic!("Write of blank bytes for pwrite failed!"); + } + } else { + panic!("Write of blank bytes for pwrite failed!"); + } + } + + let newposition; + let retval = if let Ok(byteswritten) = + fileobject.writeat(buf, count, position) + { + //move position forward by the number of bytes we've written + newposition = position + byteswritten; + + byteswritten as i32 + } else { + newposition = position; + 0 //0 bytes written, but not an error value that can/should be passed to the user + //we still may need to update file size from blank bytes write, so we don't bail out + }; + + if newposition > filesize { + normalfile_inode_obj.size = newposition; + drop(fileobject); + drop(inodeobj); + log_metadata(&FS_METADATA, normalfile_filedesc_obj.inode); + } //update file size if necessary + + retval + } + + Inode::CharDev(ref char_inode_obj) => { + self._write_chr_file(&char_inode_obj, buf, count) + } + + Inode::Socket(_) => { + panic!("pwrite: socket fd and inode don't match types") + } + + Inode::Dir(_) => syscall_error( + Errno::EISDIR, + "pwrite", + "attempted to write to a directory", + ), + } + } + Socket(_) => syscall_error( + Errno::ESPIPE, + "pwrite", + "file descriptor is associated with a socket, cannot seek", + ), + Stream(_) => syscall_error( + Errno::ESPIPE, + "pwrite", + "file descriptor is associated with a stream, cannot seek", + ), + Pipe(_) => syscall_error( + Errno::ESPIPE, + "pwrite", + "file descriptor is associated with a pipe, cannot seek", + ), + Epoll(_) => syscall_error( + Errno::ESPIPE, + "pwrite", + "file descriptor is associated with an epollfd, cannot seek", + ), + } + } else { + syscall_error(Errno::EBADF, "pwrite", "invalid file descriptor") + } + } + + fn _write_chr_file(&self, inodeobj: &DeviceInode, _buf: *const u8, count: usize) -> i32 { + //writes to any of these device files transparently succeed while doing nothing + match inodeobj.dev { + NULLDEVNO => count as i32, + ZERODEVNO => count as i32, + RANDOMDEVNO => count as i32, + URANDOMDEVNO => count as i32, + _ => syscall_error( + Errno::EOPNOTSUPP, + "write or pwrite", + "write to specified device not implemented", + ), + } + } + + //------------------------------------LSEEK SYSCALL------------------------------------ + pub fn lseek_syscall(&self, fd: i32, offset: isize, whence: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + //confirm fd type is seekable + match filedesc_enum { + File(ref mut normalfile_filedesc_obj) => { + let inodeobj = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + + //handle files/directories differently + match &*inodeobj { + Inode::File(normalfile_inode_obj) => { + let eventualpos = match whence { + SEEK_SET => offset, + SEEK_CUR => normalfile_filedesc_obj.position as isize + offset, + SEEK_END => normalfile_inode_obj.size as isize + offset, + _ => { + return syscall_error(Errno::EINVAL, "lseek", "unknown whence"); + } + }; + + if eventualpos < 0 { + return syscall_error( + Errno::EINVAL, + "lseek", + "seek to before position 0 in file", + ); + } + //subsequent writes to the end of the file must zero pad up until this point if we + //overran the end of our file when seeking + + normalfile_filedesc_obj.position = eventualpos as usize; + //return the location that we sought to + eventualpos as i32 + } + + Inode::CharDev(_) => { + 0 //for character files, rather than seeking, we transparently do nothing + } + + Inode::Socket(_) => { + panic!("lseek: socket fd and inode don't match types") + } + + Inode::Dir(dir_inode_obj) => { + //for directories we seek between entries, and thus our end position is the total number of entries + let eventualpos = match whence { + SEEK_SET => offset, + SEEK_CUR => normalfile_filedesc_obj.position as isize + offset, + SEEK_END => { + dir_inode_obj.filename_to_inode_dict.len() as isize + offset + } + _ => { + return syscall_error(Errno::EINVAL, "lseek", "unknown whence"); + } + }; + + //confirm that the location we want to seek to is valid + if eventualpos < 0 { + return syscall_error( + Errno::EINVAL, + "lseek", + "seek to before position 0 in directory", + ); + } + if eventualpos > dir_inode_obj.filename_to_inode_dict.len() as isize { + return syscall_error( + Errno::EINVAL, + "lseek", + "seek to after last position in directory", + ); + } + + normalfile_filedesc_obj.position = eventualpos as usize; + //return the location that we sought to + eventualpos as i32 + } + } + } + Socket(_) => syscall_error( + Errno::ESPIPE, + "lseek", + "file descriptor is associated with a socket, cannot seek", + ), + Stream(_) => syscall_error( + Errno::ESPIPE, + "lseek", + "file descriptor is associated with a stream, cannot seek", + ), + Pipe(_) => syscall_error( + Errno::ESPIPE, + "lseek", + "file descriptor is associated with a pipe, cannot seek", + ), + Epoll(_) => syscall_error( + Errno::ESPIPE, + "lseek", + "file descriptor is associated with an epollfd, cannot seek", + ), + } + } else { + syscall_error(Errno::EBADF, "lseek", "invalid file descriptor") + } + } + + //------------------------------------ACCESS SYSCALL------------------------------------ + + pub fn access_syscall(&self, path: &str, amode: u32) -> i32 { + let truepath = normpath(convpath(path), self); + + //Walk the file tree to get inode from path + if let Some(inodenum) = metawalk(truepath.as_path()) { + let inodeobj = FS_METADATA.inodetable.get(&inodenum).unwrap(); + + //Get the mode bits if the type of the inode is sane + let mode = match &*inodeobj { + Inode::File(f) => f.mode, + Inode::CharDev(f) => f.mode, + Inode::Socket(f) => f.mode, + Inode::Dir(f) => f.mode, + }; + + //We assume that the current user owns the file + + //Construct desired access bits (i.e. 0777) based on the amode parameter + let mut newmode: u32 = 0; + if amode & X_OK == X_OK { + newmode |= S_IXUSR; + } + if amode & W_OK == W_OK { + newmode |= S_IWUSR; + } + if amode & R_OK == R_OK { + newmode |= S_IRUSR; + } + + //if the desired access bits are compatible with the actual access bits + //of the file, return a success result, else return a failure result + if mode & newmode == newmode { + 0 + } else { + syscall_error( + Errno::EACCES, + "access", + "the requested access would be denied to the file", + ) + } + } else { + syscall_error( + Errno::ENOENT, + "access", + "path does not refer to an existing file", + ) + } + } + + //------------------------------------FCHDIR SYSCALL------------------------------------ + + pub fn fchdir_syscall(&self, fd: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + + let path_string = match &*unlocked_fd { + Some(File(normalfile_filedesc_obj)) => { + let inodenum = normalfile_filedesc_obj.inode; + match pathnamefrominodenum(inodenum) { + Some(name) => name, + None => { + return syscall_error( + Errno::ENOTDIR, + "fchdir", + "the file descriptor does not refer to a directory", + ) + } + } + } + Some(_) => { + return syscall_error( + Errno::EACCES, + "fchdir", + "cannot change working directory on this file descriptor", + ) + } + None => return syscall_error(Errno::EBADF, "fchdir", "invalid file descriptor"), + }; + + let mut cwd_container = self.cwd.write(); + + *cwd_container = interface::RustRfc::new(convpath(path_string.as_str())); + + 0 // fchdir success + } + + //------------------------------------CHDIR SYSCALL------------------------------------ + + pub fn chdir_syscall(&self, path: &str) -> i32 { + let truepath = normpath(convpath(path), self); + //Walk the file tree to get inode from path + if let Some(inodenum) = metawalk(&truepath) { + if let Inode::Dir(ref mut dir) = *(FS_METADATA.inodetable.get_mut(&inodenum).unwrap()) { + //increment refcount of new cwd inode to ensure that you can't remove a directory while it is the cwd of a cage + dir.refcount += 1; + } else { + return syscall_error( + Errno::ENOTDIR, + "chdir", + "the last component in path is not a directory", + ); + } + } else { + return syscall_error( + Errno::ENOENT, + "chdir", + "the directory referred to in path does not exist", + ); + } + //at this point, syscall isn't an error + let mut cwd_container = self.cwd.write(); + + //decrement refcount of previous cwd's inode, to allow it to be removed if no cage has it as cwd + decref_dir(&*cwd_container); + + *cwd_container = interface::RustRfc::new(truepath); + 0 //chdir has succeeded!; + } + + //------------------------------------DUP & DUP2 SYSCALLS------------------------------------ + + pub fn dup_syscall(&self, fd: i32, start_desc: Option) -> i32 { + //if a starting fd was passed, then use that as the starting point, but otherwise, use the designated minimum of STARTINGFD + let start_fd = match start_desc { + Some(start_desc) => start_desc, + None => STARTINGFD, + }; + + if start_fd == fd { + return start_fd; + } //if the file descriptors are equal, return the new one + + // get the filedesc_enum + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let filedesc_enum = checkedfd.write(); + let filedesc_enum = if let Some(f) = &*filedesc_enum { + f + } else { + return syscall_error(Errno::EBADF, "dup", "Invalid old file descriptor."); + }; + + //checking whether the fd exists in the file table + return Self::_dup2_helper(&self, filedesc_enum, start_fd, false); + } + + pub fn dup2_syscall(&self, oldfd: i32, newfd: i32) -> i32 { + //checking if the new fd is out of range + if newfd >= MAXFD || newfd < 0 { + return syscall_error( + Errno::EBADF, + "dup2", + "provided file descriptor is out of range", + ); + } + + if newfd == oldfd { + return newfd; + } //if the file descriptors are equal, return the new one + + // get the filedesc_enum + let checkedfd = self.get_filedescriptor(oldfd).unwrap(); + let filedesc_enum = checkedfd.write(); + let filedesc_enum = if let Some(f) = &*filedesc_enum { + f + } else { + return syscall_error(Errno::EBADF, "dup2", "Invalid old file descriptor."); + }; + + //if the old fd exists, execute the helper, else return error + return Self::_dup2_helper(&self, filedesc_enum, newfd, true); + } + + pub fn _dup2_helper(&self, filedesc_enum: &FileDescriptor, newfd: i32, fromdup2: bool) -> i32 { + let (dupfd, mut dupfdguard) = if fromdup2 { + let mut fdguard = self.filedescriptortable[newfd as usize].write(); + let closebool = fdguard.is_some(); + drop(fdguard); + // close the fd in the way of the new fd. mirror the implementation of linux, ignore the potential error of the close here + if closebool { + let _close_result = Self::_close_helper_inner(&self, newfd); + } + + // re-grab clean fd + fdguard = self.filedescriptortable[newfd as usize].write(); + (newfd, fdguard) + } else { + let (newdupfd, guardopt) = self.get_next_fd(Some(newfd)); + if newdupfd < 0 { + return syscall_error( + Errno::ENFILE, + "dup2_helper", + "no available file descriptor number could be found", + ); + } + (newdupfd, guardopt.unwrap()) + }; + + let dupfdoption = &mut *dupfdguard; + + match filedesc_enum { + File(normalfile_filedesc_obj) => { + let inodenum = normalfile_filedesc_obj.inode; + let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + //incrementing the ref count so that when close is executed on the dup'd file + //the original file does not get a negative ref count + match *inodeobj { + Inode::File(ref mut normalfile_inode_obj) => { + normalfile_inode_obj.refcount += 1; + } + Inode::Dir(ref mut dir_inode_obj) => { + dir_inode_obj.refcount += 1; + } + Inode::CharDev(ref mut chardev_inode_obj) => { + chardev_inode_obj.refcount += 1; + } + Inode::Socket(_) => panic!("dup: fd and inode do not match."), + } + } + Pipe(pipe_filedesc_obj) => { + pipe_filedesc_obj.pipe.incr_ref(pipe_filedesc_obj.flags); + } + Socket(ref socket_filedesc_obj) => { + //we handle the closing of sockets on drop + // checking whether this is a domain socket + + let sock_tmp = socket_filedesc_obj.handle.clone(); + let sockhandle = sock_tmp.write(); + let socket_type = sockhandle.domain; + if socket_type == AF_UNIX { + if let Some(sockinfo) = &sockhandle.unix_info { + if let Some(sendpipe) = sockinfo.sendpipe.as_ref() { + sendpipe.incr_ref(O_WRONLY); + } + if let Some(receivepipe) = sockinfo.receivepipe.as_ref() { + receivepipe.incr_ref(O_RDONLY); + } + } + } + } + Stream(_normalfile_filedesc_obj) => { + // no stream refs + } + _ => { + return syscall_error(Errno::EACCES, "dup or dup2", "can't dup the provided file"); + } + } + + let mut dupd_fd_enum = filedesc_enum.clone(); //clones the arc for sockethandle + + // get and clone fd, wrap and insert into table. + match dupd_fd_enum { + // we don't want to pass on the CLOEXEC flag + File(ref mut normalfile_filedesc_obj) => { + normalfile_filedesc_obj.flags = normalfile_filedesc_obj.flags & !O_CLOEXEC; + } + Pipe(ref mut pipe_filedesc_obj) => { + pipe_filedesc_obj.flags = pipe_filedesc_obj.flags & !O_CLOEXEC; + } + Socket(ref mut socket_filedesc_obj) => { + // can do this for domainsockets and sockets + socket_filedesc_obj.flags = socket_filedesc_obj.flags & !O_CLOEXEC; + } + Stream(ref mut stream_filedesc_obj) => { + stream_filedesc_obj.flags = stream_filedesc_obj.flags & !O_CLOEXEC; + } + _ => { + return syscall_error(Errno::EACCES, "dup or dup2", "can't dup the provided file"); + } + } + + let _insertval = dupfdoption.insert(dupd_fd_enum); + + return dupfd; + } + + //------------------------------------CLOSE SYSCALL------------------------------------ + + pub fn close_syscall(&self, fd: i32) -> i32 { + //check that the fd is valid + return Self::_close_helper(self, fd); + } + + pub fn _close_helper_inner(&self, fd: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + //Decide how to proceed depending on the fd type. + //First we check in the file descriptor to handle sockets (no-op), sockets (clean the socket), and pipes (clean the pipe), + //and if it is a normal file descriptor we decrement the refcount to reflect + //one less reference to the file. + match filedesc_enum { + //if we are a socket, we dont change disk metadata + Stream(_) => {} + Epoll(_) => {} //Epoll closing not implemented yet + Socket(ref mut socket_filedesc_obj) => { + let sock_tmp = socket_filedesc_obj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + + // we need to do the following if UDS + if let Some(ref mut ui) = sockhandle.unix_info { + let inodenum = ui.inode; + if let Some(sendpipe) = ui.sendpipe.as_ref() { + sendpipe.decr_ref(O_WRONLY); + // we're closing the last write end, lets set eof + if sendpipe.get_write_ref() == 0 { + sendpipe.set_eof(); + } + //last reference, lets remove it + if (sendpipe.get_write_ref() as u64) + (sendpipe.get_read_ref() as u64) + == 0 + { + ui.sendpipe = None; + } + } + if let Some(receivepipe) = ui.receivepipe.as_ref() { + receivepipe.decr_ref(O_RDONLY); + //last reference, lets remove it + if (receivepipe.get_write_ref() as u64) + + (receivepipe.get_read_ref() as u64) + == 0 + { + ui.receivepipe = None; + } + } + let mut inodeobj = FS_METADATA.inodetable.get_mut(&ui.inode).unwrap(); + if let Inode::Socket(ref mut sock) = *inodeobj { + sock.refcount -= 1; + if sock.refcount == 0 { + if sock.linkcount == 0 { + drop(inodeobj); + let path = normpath( + convpath(sockhandle.localaddr.unwrap().path()), + self, + ); + FS_METADATA.inodetable.remove(&inodenum); + NET_METADATA.domsock_paths.remove(&path); + } + } + } + } + } + Pipe(ref pipe_filedesc_obj) => { + let pipe = &pipe_filedesc_obj.pipe; + pipe.decr_ref(pipe_filedesc_obj.flags); + + if pipe.get_write_ref() == 0 + && (pipe_filedesc_obj.flags & O_RDWRFLAGS) == O_WRONLY + { + // we're closing the last write end, lets set eof + pipe.set_eof(); + } + } + File(ref normalfile_filedesc_obj) => { + let inodenum = normalfile_filedesc_obj.inode; + let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + + match *inodeobj { + Inode::File(ref mut normalfile_inode_obj) => { + normalfile_inode_obj.refcount -= 1; + + //if it's not a reg file, then we have nothing to close + //Inode::File is a regular file by default + if normalfile_inode_obj.refcount == 0 { + FILEOBJECTTABLE + .remove(&inodenum) + .unwrap() + .1 + .close() + .unwrap(); + if normalfile_inode_obj.linkcount == 0 { + drop(inodeobj); + //removing the file from the entire filesystem (interface, metadata, and object table) + FS_METADATA.inodetable.remove(&inodenum); + let sysfilename = format!("{}{}", FILEDATAPREFIX, inodenum); + interface::removefile(sysfilename).unwrap(); + log_metadata(&FS_METADATA, inodenum); + } else { + drop(inodeobj); + } + } + } + Inode::Dir(ref mut dir_inode_obj) => { + dir_inode_obj.refcount -= 1; + + //if it's not a reg file, then we have nothing to close + match FILEOBJECTTABLE.get(&inodenum) { + Some(_) => { + return syscall_error( + Errno::ENOEXEC, + "close or dup", + "Non-regular file in file object table", + ); + } + None => {} + } + if dir_inode_obj.linkcount == 2 && dir_inode_obj.refcount == 0 { + //removing the file from the metadata + FS_METADATA.inodetable.remove(&inodenum); + drop(inodeobj); + log_metadata(&FS_METADATA, inodenum); + } + } + Inode::CharDev(ref mut char_inode_obj) => { + char_inode_obj.refcount -= 1; + + //if it's not a reg file, then we have nothing to close + match FILEOBJECTTABLE.get(&inodenum) { + Some(_) => { + return syscall_error( + Errno::ENOEXEC, + "close or dup", + "Non-regular file in file object table", + ); + } + None => {} + } + if char_inode_obj.linkcount == 0 && char_inode_obj.refcount == 0 { + //removing the file from the metadata + drop(inodeobj); + FS_METADATA.inodetable.remove(&inodenum); + } else { + drop(inodeobj); + } + log_metadata(&FS_METADATA, inodenum); + } + Inode::Socket(_) => { + panic!("close(): Socket inode found on a filedesc fd.") + } + } + } + } + 0 + } else { + return syscall_error(Errno::EBADF, "close", "invalid file descriptor"); + } + } + + pub fn _close_helper(&self, fd: i32) -> i32 { + let inner_result = self._close_helper_inner(fd); + if inner_result < 0 { + return inner_result; + } + + //removing inode from fd table + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if unlocked_fd.is_some() { + let _discarded_fd = unlocked_fd.take(); + } + 0 //_close_helper has succeeded! + } + + //------------------------------------FCNTL SYSCALL------------------------------------ + + pub fn fcntl_syscall(&self, fd: i32, cmd: i32, arg: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + let flags = match filedesc_enum { + Epoll(obj) => &mut obj.flags, + Pipe(obj) => &mut obj.flags, + Stream(obj) => &mut obj.flags, + File(obj) => &mut obj.flags, + Socket(ref mut sockfdobj) => { + if cmd == F_SETFL && arg >= 0 { + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + + if let Some(ins) = &mut sockhandle.innersocket { + let fcntlret; + if arg & O_NONBLOCK == O_NONBLOCK { + //set for non-blocking I/O + fcntlret = ins.set_nonblocking(); + } else { + //clear non-blocking I/O + fcntlret = ins.set_blocking(); + } + if fcntlret < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "fcntl", + "The libc call to fcntl failed!", + ); + } + Err(()) => panic!("Unknown errno value from fcntl returned!"), + }; + } + } + } + + &mut sockfdobj.flags + } + }; + + //matching the tuple + match (cmd, arg) { + //because the arg parameter is not used in certain commands, it can be anything (..) + (F_GETFD, ..) => *flags & O_CLOEXEC, + // set the flags but make sure that the flags are valid + (F_SETFD, arg) if arg >= 0 => { + if arg & O_CLOEXEC != 0 { + *flags |= O_CLOEXEC; + } else { + *flags &= !O_CLOEXEC; + } + 0 + } + (F_GETFL, ..) => { + //for get, we just need to return the flags + *flags & !O_CLOEXEC + } + (F_SETFL, arg) if arg >= 0 => { + *flags |= arg; + 0 + } + (F_DUPFD, arg) if arg >= 0 => self._dup2_helper(&filedesc_enum, arg, false), + //TO DO: implement. this one is saying get the signals + (F_GETOWN, ..) => { + 0 //TO DO: traditional SIGIO behavior + } + (F_SETOWN, arg) if arg >= 0 => { + 0 //this would return the PID if positive and the process group if negative, + //either way do nothing and return success + } + _ => syscall_error( + Errno::EINVAL, + "fcntl", + "Arguments provided do not match implemented parameters", + ), + } + } else { + syscall_error(Errno::EBADF, "fcntl", "Invalid file descriptor") + } + } + + //------------------------------------IOCTL SYSCALL------------------------------------ + + pub fn ioctl_syscall(&self, fd: i32, request: u32, ptrunion: IoctlPtrUnion) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match request { + FIONBIO => { + let arg_result = interface::get_ioctl_int(ptrunion); + //matching the tuple and passing in filedesc_enum + match (arg_result, filedesc_enum) { + (Err(arg_result), ..)=> { + return arg_result; //syscall_error + } + (Ok(arg_result), Socket(ref mut sockfdobj)) => { + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + + let flags = &mut sockfdobj.flags; + let arg: i32 = arg_result; + let mut ioctlret = 0; + + if arg == 0 { //clear non-blocking I/O + *flags &= !O_NONBLOCK; + if let Some(ins) = &mut sockhandle.innersocket { + ioctlret = ins.set_blocking(); + } + } else { //set for non-blocking I/O + *flags |= O_NONBLOCK; + if let Some(ins) = &mut sockhandle.innersocket { + ioctlret = ins.set_nonblocking(); + } + } + if ioctlret < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => {return syscall_error(i, "ioctl", "The libc call to ioctl failed!");}, + Err(()) => panic!("Unknown errno value from ioctl returned!"), + }; + } + + 0 + } + _ => {syscall_error(Errno::ENOTTY, "ioctl", "The specified request does not apply to the kind of object that the file descriptor fd references.")} + } + } + FIOASYNC => { + //not implemented + interface::log_verbose( + "ioctl(FIOASYNC) is not implemented, and just returns 0.", + ); + 0 + } + _ => syscall_error( + Errno::EINVAL, + "ioctl", + "Arguments provided do not match implemented parameters", + ), + } + } else { + syscall_error(Errno::EBADF, "ioctl", "Invalid file descriptor") + } + } + + //------------------------------------CHMOD HELPER FUNCTION------------------------------------ + + pub fn _chmod_helper(inodenum: usize, mode: u32) { + let mut thisinode = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + let mut log = true; + if mode & (S_IRWXA | (S_FILETYPEFLAGS as u32)) == mode { + match *thisinode { + Inode::File(ref mut general_inode) => { + general_inode.mode = (general_inode.mode & !S_IRWXA) | mode + } + Inode::CharDev(ref mut dev_inode) => { + dev_inode.mode = (dev_inode.mode & !S_IRWXA) | mode; + } + Inode::Socket(ref mut sock_inode) => { + sock_inode.mode = (sock_inode.mode & !S_IRWXA) | mode; + log = false; + } + Inode::Dir(ref mut dir_inode) => { + dir_inode.mode = (dir_inode.mode & !S_IRWXA) | mode; + } + } + drop(thisinode); + if log { + log_metadata(&FS_METADATA, inodenum) + }; + } + } + + //------------------------------------CHMOD SYSCALL------------------------------------ + + pub fn chmod_syscall(&self, path: &str, mode: u32) -> i32 { + let truepath = normpath(convpath(path), self); + + //check if there is a valid path or not there to an inode + if let Some(inodenum) = metawalk(truepath.as_path()) { + if mode & (S_IRWXA | (S_FILETYPEFLAGS as u32)) == mode { + Self::_chmod_helper(inodenum, mode); + } else { + //there doesn't seem to be a good syscall error errno for this + return syscall_error(Errno::EACCES, "chmod", "provided file mode is not valid"); + } + } else { + return syscall_error(Errno::ENOENT, "chmod", "the provided path does not exist"); + } + 0 //success! + } + + //------------------------------------FCHMOD SYSCALL------------------------------------ + + pub fn fchmod_syscall(&self, fd: i32, mode: u32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + match filedesc_enum { + File(normalfile_filedesc_obj) => { + let inodenum = normalfile_filedesc_obj.inode; + if mode & (S_IRWXA | (S_FILETYPEFLAGS as u32)) == mode { + Self::_chmod_helper(inodenum, mode); + } else { + return syscall_error( + Errno::EACCES, + "fchmod", + "provided file mode is not valid", + ); + } + } + Socket(_) => { + return syscall_error( + Errno::EACCES, + "fchmod", + "cannot change mode on this file descriptor", + ); + } + Stream(_) => { + return syscall_error( + Errno::EACCES, + "fchmod", + "cannot change mode on this file descriptor", + ); + } + Pipe(_) => { + return syscall_error( + Errno::EACCES, + "fchmod", + "cannot change mode on this file descriptor", + ); + } + Epoll(_) => { + return syscall_error( + Errno::EACCES, + "fchmod", + "cannot change mode on this file descriptor", + ); + } + } + } else { + return syscall_error( + Errno::ENOENT, + "fchmod", + "the provided file descriptor does not exist", + ); + } + 0 //success! + } + + //------------------------------------MMAP SYSCALL------------------------------------ + + pub fn mmap_syscall( + &self, + addr: *mut u8, + len: usize, + prot: i32, + flags: i32, + fildes: i32, + off: i64, + ) -> i32 { + if len == 0 { + syscall_error(Errno::EINVAL, "mmap", "the value of len is 0"); + } + + if 0 == flags & (MAP_PRIVATE | MAP_SHARED) { + syscall_error( + Errno::EINVAL, + "mmap", + "The value of flags is invalid (neither MAP_PRIVATE nor MAP_SHARED is set)", + ); + } + + if 0 != flags & MAP_ANONYMOUS { + return interface::libc_mmap(addr, len, prot, flags, -1, 0); + } + + let checkedfd = self.get_filedescriptor(fildes).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + //confirm fd type is mappable + match filedesc_enum { + File(ref mut normalfile_filedesc_obj) => { + let inodeobj = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + + //confirm inode type is mappable + match &*inodeobj { + Inode::File(normalfile_inode_obj) => { + //if we want to write our changes back to the file the file needs to be open for reading and writing + if (flags & MAP_SHARED != 0) && (flags & PROT_WRITE != 0) && (normalfile_filedesc_obj.flags & O_RDWR != 0) { + return syscall_error(Errno::EACCES, "mmap", "file descriptor is not open RDWR, but MAP_SHARED and PROT_WRITE are set"); + } + let filesize = normalfile_inode_obj.size; + if off < 0 || off > filesize as i64 { + return syscall_error(Errno::ENXIO, "mmap", "Addresses in the range [off,off+len) are invalid for the object specified by fildes."); + } + //because of NaCl's internal workings we must allow mappings to extend past the end of a file + let fobj = FILEOBJECTTABLE.get(&normalfile_filedesc_obj.inode).unwrap(); + //we cannot mmap a rust file in quite the right way so we retrieve the fd number from it + //this is the system fd number--the number of the lind. file in our host system + let fobjfdno = fobj.as_fd_handle_raw_int(); + + + interface::libc_mmap(addr, len, prot, flags, fobjfdno, off) + } + + Inode::CharDev(_chardev_inode_obj) => { + syscall_error(Errno::EOPNOTSUPP, "mmap", "lind currently does not support mapping character files") + } + + _ => {syscall_error(Errno::EACCES, "mmap", "the fildes argument refers to a file whose type is not supported by mmap")} + } + } + _ => syscall_error( + Errno::EACCES, + "mmap", + "the fildes argument refers to a file whose type is not supported by mmap", + ), + } + } else { + syscall_error(Errno::EBADF, "mmap", "invalid file descriptor") + } + } + + //------------------------------------MUNMAP SYSCALL------------------------------------ + + pub fn munmap_syscall(&self, addr: *mut u8, len: usize) -> i32 { + if len == 0 { + syscall_error(Errno::EINVAL, "mmap", "the value of len is 0"); + } + //NaCl's munmap implementation actually just writes over the previously mapped data with PROT_NONE + //This frees all of the resources except page table space, and is put inside safeposix for consistency + interface::libc_mmap( + addr, + len, + PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + -1, + 0, + ) + } + + //------------------------------------FLOCK SYSCALL------------------------------------ + + pub fn flock_syscall(&self, fd: i32, operation: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + let lock = match filedesc_enum { + File(normalfile_filedesc_obj) => &normalfile_filedesc_obj.advlock, + Socket(socket_filedesc_obj) => &socket_filedesc_obj.advlock, + Stream(stream_filedesc_obj) => &stream_filedesc_obj.advlock, + Pipe(pipe_filedesc_obj) => &pipe_filedesc_obj.advlock, + Epoll(epoll_filedesc_obj) => &epoll_filedesc_obj.advlock, + }; + match operation & (LOCK_SH | LOCK_EX | LOCK_UN) { + LOCK_SH => { + if operation & LOCK_NB == LOCK_NB { + //EAGAIN and EWOULDBLOCK are the same + if !lock.try_lock_sh() { + return syscall_error( + Errno::EAGAIN, + "flock", + "shared lock would block", + ); + }; + } else { + lock.lock_sh(); + } + } + LOCK_EX => { + if operation & LOCK_NB == LOCK_NB { + if !lock.try_lock_ex() { + return syscall_error( + Errno::EAGAIN, + "flock", + "exclusive lock would block", + ); + }; + } else { + lock.lock_ex(); + } + } + LOCK_UN => { + if operation & LOCK_NB == LOCK_NB { + lock.unlock(); + } else { + lock.unlock(); + } + } + _ => { + return syscall_error(Errno::EINVAL, "flock", "unknown operation"); + } + } + 0 //flock has succeeded! + } else { + syscall_error(Errno::EBADF, "flock", "invalid file descriptor") + } + } + + pub fn remove_from_parent_dir( + parent_inodenum: usize, + truepath: &interface::RustPathBuf, + ) -> i32 { + if let Inode::Dir(ref mut parent_dir) = + *(FS_METADATA.inodetable.get_mut(&parent_inodenum).unwrap()) + { + // check if parent dir has write permission + if parent_dir.mode as u32 & (S_IWOTH | S_IWGRP | S_IWUSR) == 0 { + return syscall_error( + Errno::EPERM, + "rmdir", + "Parent directory does not have write permission", + ); + } + + // remove entry of corresponding filename from filename-inode dict + parent_dir + .filename_to_inode_dict + .remove(&truepath.file_name().unwrap().to_str().unwrap().to_string()) + .unwrap(); + parent_dir.linkcount -= 1; // decrement linkcount of parent dir + } else { + panic!("Non directory file was parent!"); + } + 0 + } + + //------------------RMDIR SYSCALL------------------ + + pub fn rmdir_syscall(&self, path: &str) -> i32 { + if path.len() == 0 { + return syscall_error(Errno::ENOENT, "rmdir", "Given path is null"); + } + let truepath = normpath(convpath(path), self); + + // try to get inodenum of input path and its parent + match metawalkandparent(truepath.as_path()) { + (None, ..) => syscall_error(Errno::ENOENT, "rmdir", "Path does not exist"), + (Some(_), None) => { + // path exists but parent does not => path is root dir + syscall_error(Errno::EBUSY, "rmdir", "Cannot remove root directory") + } + (Some(inodenum), Some(parent_inodenum)) => { + let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + + match &mut *inodeobj { + // make sure inode matches a directory + Inode::Dir(ref mut dir_obj) => { + if dir_obj.linkcount > 3 { + return syscall_error( + Errno::ENOTEMPTY, + "rmdir", + "Directory is not empty", + ); + } + if !is_dir(dir_obj.mode) { + panic!("This directory does not have its mode set to S_IFDIR"); + } + + // check if dir has write permission + if dir_obj.mode as u32 & (S_IWOTH | S_IWGRP | S_IWUSR) == 0 { + return syscall_error( + Errno::EPERM, + "rmdir", + "Directory does not have write permission", + ); + } + + let remove_inode = dir_obj.refcount == 0; + if remove_inode { + dir_obj.linkcount = 2; + } // linkcount for an empty directory after rmdir must be 2 + drop(inodeobj); + + let removal_result = + Self::remove_from_parent_dir(parent_inodenum, &truepath); + if removal_result != 0 { + return removal_result; + } + + // remove entry of corresponding inodenum from inodetable + if remove_inode { + FS_METADATA.inodetable.remove(&inodenum).unwrap(); + } + + log_metadata(&FS_METADATA, parent_inodenum); + log_metadata(&FS_METADATA, inodenum); + 0 // success + } + _ => syscall_error(Errno::ENOTDIR, "rmdir", "Path is not a directory"), + } + } + } + } + + //------------------RENAME SYSCALL------------------ + + pub fn rename_syscall(&self, oldpath: &str, newpath: &str) -> i32 { + if oldpath.len() == 0 { + return syscall_error(Errno::ENOENT, "rename", "Old path is null"); + } + if newpath.len() == 0 { + return syscall_error(Errno::ENOENT, "rename", "New path is null"); + } + + let true_oldpath = normpath(convpath(oldpath), self); + let true_newpath = normpath(convpath(newpath), self); + + // try to get inodenum of old path and its parent + match metawalkandparent(true_oldpath.as_path()) { + (None, ..) => syscall_error(Errno::EEXIST, "rename", "Old path does not exist"), + (Some(_), None) => { + syscall_error(Errno::EBUSY, "rename", "Cannot rename root directory") + } + (Some(inodenum), Some(parent_inodenum)) => { + // make sure file is not moved to another dir + // get inodenum for parent of new path + let (_, new_par_inodenum) = metawalkandparent(true_newpath.as_path()); + // check if old and new paths share parent + if new_par_inodenum != Some(parent_inodenum) { + return syscall_error( + Errno::EOPNOTSUPP, + "rename", + "Cannot move file to another directory", + ); + } + + let pardir_inodeobj = FS_METADATA.inodetable.get_mut(&parent_inodenum).unwrap(); + if let Inode::Dir(parent_dir) = &*pardir_inodeobj { + // add pair of new path and its inodenum to filename-inode dict + parent_dir.filename_to_inode_dict.insert( + true_newpath + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(), + inodenum, + ); + + // remove entry of old path from filename-inode dict + parent_dir.filename_to_inode_dict.remove( + &true_oldpath + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(), + ); + drop(pardir_inodeobj); + log_metadata(&FS_METADATA, parent_inodenum); + } + NET_METADATA.domsock_paths.insert(true_newpath); + NET_METADATA.domsock_paths.remove(&true_oldpath); + 0 // success + } + } + } + + fn _truncate_helper(&self, inodenum: usize, length: isize, file_must_exist: bool) -> i32 { + if length < 0 { + return syscall_error(Errno::EINVAL, "truncate", "length specified as less than 0"); + } + let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + + match *inodeobj { + // only proceed when inode matches with a file + Inode::File(ref mut normalfile_inode_obj) => { + let ulength = length as usize; + let filesize = normalfile_inode_obj.size as usize; + + // get file object table with write lock + let mut maybe_fileobject = FILEOBJECTTABLE.entry(inodenum); + let mut tempbind; + let close_on_exit; + + //We check if the fileobject exists. If file_must_exist is true (i.e. we called the helper from + //ftruncate) then we know that an fd must exist and thus we panic if the fileobject does not + //exist. If file_must_exist is false (i.e. we called the helper from truncate), if the file does + //not exist, we create a new fileobject to use which we remove once we are done with it + let fileobject = if let interface::RustHashEntry::Occupied(ref mut occ) = + maybe_fileobject + { + close_on_exit = false; + occ.get_mut() + } else if file_must_exist { + panic!("Somehow a normal file with an fd was truncated but there was no file object in rustposix?"); + } else { + let sysfilename = format!("{}{}", FILEDATAPREFIX, inodenum); + tempbind = interface::openfile(sysfilename, filesize).unwrap(); // open file with size given from inode + close_on_exit = true; + &mut tempbind + }; + + // if length is greater than original filesize, + // file is extented with null bytes + if filesize < ulength { + let blankbytecount = ulength - filesize; + if let Ok(byteswritten) = fileobject.zerofill_at(filesize, blankbytecount) { + if byteswritten != blankbytecount { + panic!("zerofill_at() has failed"); + } + } else { + panic!("zerofill_at() has failed"); + } + } else { + // if length is smaller than original filesize, + // extra data are cut off + fileobject.shrink(ulength).unwrap(); + } + + if close_on_exit { + fileobject.close().unwrap(); + } + + drop(maybe_fileobject); + + normalfile_inode_obj.size = ulength; + + drop(inodeobj); + log_metadata(&FS_METADATA, inodenum); + 0 // truncating has succeeded! + } + Inode::CharDev(_) => syscall_error( + Errno::EINVAL, + "truncate", + "The named file is a character driver", + ), + Inode::Socket(_) => syscall_error( + Errno::EINVAL, + "truncate", + "The named file is a domain socket", + ), + Inode::Dir(_) => { + syscall_error(Errno::EISDIR, "truncate", "The named file is a directory") + } + } + } + + //------------------------------------FSYNC SYSCALL------------------------------------ + + pub fn fsync_syscall(&self, fd: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + File(ref mut normalfile_filedesc_obj) => { + if is_rdonly(normalfile_filedesc_obj.flags) { + return syscall_error( + Errno::EBADF, + "fsync", + "specified file not open for sync", + ); + } + let inodeobj = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + match &*inodeobj { + Inode::File(_) => { + let fileobject = + FILEOBJECTTABLE.get(&normalfile_filedesc_obj.inode).unwrap(); + + match fileobject.fsync() { + Ok(_) => 0, + _ => syscall_error( + Errno::EIO, + "fsync", + "an error occurred during synchronization", + ), + } + } + _ => syscall_error( + Errno::EROFS, + "fsync", + "does not support special files for synchronization", + ), + } + } + _ => syscall_error( + Errno::EINVAL, + "fsync", + "fd is attached to an object which is unsuitable for synchronization", + ), + } + } else { + syscall_error(Errno::EBADF, "fsync", "invalid file descriptor") + } + } + + //------------------------------------FDATASYNC SYSCALL------------------------------------ + + pub fn fdatasync_syscall(&self, fd: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + File(ref mut normalfile_filedesc_obj) => { + if is_rdonly(normalfile_filedesc_obj.flags) { + return syscall_error( + Errno::EBADF, + "fdatasync", + "specified file not open for sync", + ); + } + let inodeobj = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + match &*inodeobj { + Inode::File(_) => { + let fileobject = + FILEOBJECTTABLE.get(&normalfile_filedesc_obj.inode).unwrap(); + + match fileobject.fdatasync() { + Ok(_) => 0, + _ => syscall_error( + Errno::EIO, + "fdatasync", + "an error occurred during synchronization", + ), + } + } + _ => syscall_error( + Errno::EROFS, + "fdatasync", + "does not support special files for synchronization", + ), + } + } + _ => syscall_error( + Errno::EINVAL, + "fdatasync", + "fd is attached to an object which is unsuitable for synchronization", + ), + } + } else { + syscall_error(Errno::EBADF, "fdatasync", "invalid file descriptor") + } + } + + //------------------------------------SYNC_FILE_RANGE SYSCALL------------------------------------ + + pub fn sync_file_range_syscall( + &self, + fd: i32, + offset: isize, + nbytes: isize, + flags: u32, + ) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + File(ref mut normalfile_filedesc_obj) => { + let inodeobj = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + match &*inodeobj { + Inode::File(_) => { + // This code segment obtains the file object associated with the specified inode from FILEOBJECTTABLE. + // It calls 'sync_file_range' on this file object, where initially the flags are validated, returning -EINVAL for incorrect flags. + // If the flags are correct, libc::sync_file_range is invoked; if it fails (returns -1), 'from_discriminant' function handles the error code. + + let fobj = FILEOBJECTTABLE.get(&normalfile_filedesc_obj.inode).unwrap(); + let result = fobj.sync_file_range(offset, nbytes, flags); + if result == 0 || result == -(EINVAL as i32) { + return result; + } + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "sync_file_range", + "The libc call to sync_file_range failed!", + ); + } + Err(()) => panic!("Unknown errno value from setsockopt returned!"), + }; + } + _ => syscall_error( + Errno::ESPIPE, + "sync_file_range", + "does not support special files for synchronization", + ), + } + } + _ => syscall_error( + Errno::EBADF, + "sync_file_range", + "fd is attached to an object which is unsuitable for synchronization", + ), + } + } else { + syscall_error(Errno::EBADF, "sync_file_range", "invalid file descriptor") + } + } + + //------------------FTRUNCATE SYSCALL------------------ + + pub fn ftruncate_syscall(&self, fd: i32, length: isize) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + match filedesc_enum { + // only proceed when fd references a regular file + File(normalfile_filedesc_obj) => { + if is_rdonly(normalfile_filedesc_obj.flags) { + return syscall_error( + Errno::EBADF, + "ftruncate", + "specified file not open for writing", + ); + } + let inodenum = normalfile_filedesc_obj.inode; + self._truncate_helper(inodenum, length, true) + } + _ => syscall_error( + Errno::EINVAL, + "ftruncate", + "fd does not reference a regular file", + ), + } + } else { + syscall_error( + Errno::EBADF, + "ftruncate", + "fd is not a valid file descriptor", + ) + } + } + + //------------------TRUNCATE SYSCALL------------------ + pub fn truncate_syscall(&self, path: &str, length: isize) -> i32 { + let truepath = normpath(convpath(path), self); + + //Walk the file tree to get inode from path + if let Some(inodenum) = metawalk(truepath.as_path()) { + self._truncate_helper(inodenum, length, false) + } else { + syscall_error( + Errno::ENOENT, + "truncate", + "path does not refer to an existing file", + ) + } + } + + //------------------PIPE SYSCALL------------------ + pub fn pipe_syscall(&self, pipefd: &mut PipeArray) -> i32 { + self.pipe2_syscall(pipefd, 0) + } + + pub fn pipe2_syscall(&self, pipefd: &mut PipeArray, flags: i32) -> i32 { + let flagsmask = O_CLOEXEC | O_NONBLOCK; + let actualflags = flags & flagsmask; + + let pipe = interface::RustRfc::new(interface::new_pipe(PIPE_CAPACITY)); + + // get an fd for each end of the pipe and set flags to RD_ONLY and WR_ONLY + // append each to pipefds list + + let accflags = [O_RDONLY, O_WRONLY]; + for accflag in accflags { + let (fd, guardopt) = self.get_next_fd(None); + if fd < 0 { + return fd; + } + let fdoption = &mut *guardopt.unwrap(); + + let _insertval = fdoption.insert(Pipe(PipeDesc { + pipe: pipe.clone(), + flags: accflag | actualflags, + advlock: interface::RustRfc::new(interface::AdvisoryLock::new()), + })); + + match accflag { + O_RDONLY => { + pipefd.readfd = fd; + } + O_WRONLY => { + pipefd.writefd = fd; + } + _ => panic!("How did you get here."), + } + } + + 0 // success + } + + //------------------GETDENTS SYSCALL------------------ + + pub fn getdents_syscall(&self, fd: i32, dirp: *mut u8, bufsize: u32) -> i32 { + let mut vec: Vec<(interface::ClippedDirent, Vec)> = Vec::new(); + + // make sure bufsize is at least greater than size of a ClippedDirent struct + if bufsize <= interface::CLIPPED_DIRENT_SIZE { + return syscall_error(Errno::EINVAL, "getdents", "Result buffer is too small."); + } + + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + // only proceed when fd represents a file + File(ref mut normalfile_filedesc_obj) => { + let inodeobj = FS_METADATA + .inodetable + .get(&normalfile_filedesc_obj.inode) + .unwrap(); + + match &*inodeobj { + // only proceed when inode is a dir + Inode::Dir(dir_inode_obj) => { + let position = normalfile_filedesc_obj.position; + let mut bufcount = 0; + let mut curr_size; + let mut count = 0; + let mut temp_len; + + // iterate over filename-inode pairs in dict + for (filename, inode) in dir_inode_obj + .filename_to_inode_dict + .clone() + .into_iter() + .skip(position) + { + // convert filename to a filename vector of u8 + let mut vec_filename: Vec = filename.as_bytes().to_vec(); + vec_filename.push(b'\0'); // make filename null-terminated + + vec_filename.push(DT_UNKNOWN); // push DT_UNKNOWN as d_type (for now) + temp_len = + interface::CLIPPED_DIRENT_SIZE + vec_filename.len() as u32; // get length of current filename vector for padding calculation + + // pad filename vector to the next highest 8 byte boundary + for _ in 0..(temp_len + 7) / 8 * 8 - temp_len { + vec_filename.push(00); + } + + // the fixed dirent size and length of filename vector add up to total size + curr_size = + interface::CLIPPED_DIRENT_SIZE + vec_filename.len() as u32; + + bufcount += curr_size; // increment bufcount + + // stop iteration if current bufcount exceeds argument bufsize + if bufcount > bufsize { + bufcount = bufcount - curr_size; // decrement bufcount since current element is not actually written + break; + } + + // push properly constructed tuple to vector storing result + vec.push(( + interface::ClippedDirent { + d_ino: inode as u64, + d_off: bufcount as u64, + d_reclen: curr_size as u16, + }, + vec_filename, + )); + count += 1; + } + // update file position + normalfile_filedesc_obj.position = interface::rust_min( + position + count, + dir_inode_obj.filename_to_inode_dict.len(), + ); + + interface::pack_dirents(vec, dirp); + bufcount as i32 // return the number of bytes written + } + _ => syscall_error( + Errno::ENOTDIR, + "getdents", + "File descriptor does not refer to a directory.", + ), + } + } + // raise error when fd represents a socket, pipe, or stream + _ => syscall_error( + Errno::ESPIPE, + "getdents", + "Cannot getdents since fd does not refer to a file.", + ), + } + } else { + syscall_error(Errno::EBADF, "getdents", "Invalid file descriptor") + } + } + + //------------------------------------GETCWD SYSCALL------------------------------------ + + pub fn getcwd_syscall(&self, buf: *mut u8, bufsize: u32) -> i32 { + let mut bytes: Vec = self.cwd.read().to_str().unwrap().as_bytes().to_vec(); + bytes.push(0u8); //Adding a null terminator to the end of the string + let length = bytes.len(); + + if (bufsize as usize) < length { + return syscall_error(Errno::ERANGE, "getcwd", "the length (in bytes) of the absolute pathname of the current working directory exceeds the given size"); + } + + interface::fill(buf, length, &bytes); + + 0 //getcwd has succeeded!; + } + + //------------------SHMHELPERS---------------------- + + pub fn rev_shm_find_index_by_addr(rev_shm: &Vec<(u32, i32)>, shmaddr: u32) -> Option { + for (index, val) in rev_shm.iter().enumerate() { + if val.0 == shmaddr as u32 { + return Some(index); + } + } + None + } + + pub fn rev_shm_find_addrs_by_shmid(rev_shm: &Vec<(u32, i32)>, shmid: i32) -> Vec { + let mut addrvec = Vec::new(); + for val in rev_shm.iter() { + if val.1 == shmid as i32 { + addrvec.push(val.0); + } + } + + return addrvec; + } + + pub fn search_for_addr_in_region( + rev_shm: &Vec<(u32, i32)>, + search_addr: u32, + ) -> Option<(u32, i32)> { + let metadata = &SHM_METADATA; + for val in rev_shm.iter() { + let addr = val.0; + let shmid = val.1; + if let Some(segment) = metadata.shmtable.get_mut(&shmid) { + let range = addr..(addr + segment.size as u32); + if range.contains(&search_addr) { + return Some((addr, shmid)); + } + } + } + None + } + + //------------------SHMGET SYSCALL------------------ + + pub fn shmget_syscall(&self, key: i32, size: usize, shmflg: i32) -> i32 { + if key == IPC_PRIVATE { + return syscall_error(Errno::ENOENT, "shmget", "IPC_PRIVATE not implemented"); + } + let shmid: i32; + let metadata = &SHM_METADATA; + + match metadata.shmkeyidtable.entry(key) { + interface::RustHashEntry::Occupied(occupied) => { + if (IPC_CREAT | IPC_EXCL) == (shmflg & (IPC_CREAT | IPC_EXCL)) { + return syscall_error( + Errno::EEXIST, + "shmget", + "key already exists and IPC_CREAT and IPC_EXCL were used", + ); + } + shmid = *occupied.get(); + } + interface::RustHashEntry::Vacant(vacant) => { + if 0 == (shmflg & IPC_CREAT) { + return syscall_error( + Errno::ENOENT, + "shmget", + "tried to use a key that did not exist, and IPC_CREAT was not specified", + ); + } + + if (size as u32) < SHMMIN || (size as u32) > SHMMAX { + return syscall_error( + Errno::EINVAL, + "shmget", + "Size is less than SHMMIN or more than SHMMAX", + ); + } + + shmid = metadata.new_keyid(); + vacant.insert(shmid); + let mode = (shmflg & 0x1FF) as u16; // mode is 9 least signficant bits of shmflag, even if we dont really do anything with them + + let segment = new_shm_segment( + key, + size, + self.cageid as u32, + DEFAULT_UID, + DEFAULT_GID, + mode, + ); + metadata.shmtable.insert(shmid, segment); + } + }; + shmid // return the shmid + } + + //------------------SHMAT SYSCALL------------------ + + pub fn shmat_syscall(&self, shmid: i32, shmaddr: *mut u8, shmflg: i32) -> i32 { + let metadata = &SHM_METADATA; + let prot: i32; + if let Some(mut segment) = metadata.shmtable.get_mut(&shmid) { + if 0 != (shmflg & SHM_RDONLY) { + prot = PROT_READ; + } else { + prot = PROT_READ | PROT_WRITE; + } + let mut rev_shm = self.rev_shm.lock(); + rev_shm.push((shmaddr as u32, shmid)); + drop(rev_shm); + + // update semaphores + if !segment.semaphor_offsets.is_empty() { + // lets just look at the first cage in the set, since we only need to grab the ref from one + if let Some(cageid) = segment + .attached_cages + .clone() + .into_read_only() + .keys() + .next() + { + let cage2 = interface::cagetable_getref(*cageid); + let cage2_rev_shm = cage2.rev_shm.lock(); + let addrs = Self::rev_shm_find_addrs_by_shmid(&cage2_rev_shm, shmid); // find all the addresses assoc. with shmid + for offset in segment.semaphor_offsets.iter() { + let sementry = cage2.sem_table.get(&(addrs[0] + *offset)).unwrap().clone(); //add semaphors into semtable at addr + offsets + self.sem_table.insert(shmaddr as u32 + *offset, sementry); + } + } + } + + segment.map_shm(shmaddr, prot, self.cageid) + } else { + syscall_error(Errno::EINVAL, "shmat", "Invalid shmid value") + } + } + + //------------------SHMDT SYSCALL------------------ + + pub fn shmdt_syscall(&self, shmaddr: *mut u8) -> i32 { + let metadata = &SHM_METADATA; + let mut rm = false; + let mut rev_shm = self.rev_shm.lock(); + let rev_shm_index = Self::rev_shm_find_index_by_addr(&rev_shm, shmaddr as u32); + + if let Some(index) = rev_shm_index { + let shmid = rev_shm[index].1; + match metadata.shmtable.entry(shmid) { + interface::RustHashEntry::Occupied(mut occupied) => { + let segment = occupied.get_mut(); + + // update semaphores + for offset in segment.semaphor_offsets.iter() { + self.sem_table.remove(&(shmaddr as u32 + *offset)); + } + + segment.unmap_shm(shmaddr, self.cageid); + + if segment.rmid && segment.shminfo.shm_nattch == 0 { + rm = true; + } + rev_shm.swap_remove(index); + + if rm { + let key = segment.key; + occupied.remove_entry(); + metadata.shmkeyidtable.remove(&key); + } + + return shmid; //NaCl relies on this non-posix behavior of returning the shmid on success + } + interface::RustHashEntry::Vacant(_) => { + panic!("Inode not created for some reason"); + } + }; + } else { + return syscall_error( + Errno::EINVAL, + "shmdt", + "No shared memory segment at shmaddr", + ); + } + } + + //------------------SHMCTL SYSCALL------------------ + + pub fn shmctl_syscall(&self, shmid: i32, cmd: i32, buf: Option<&mut ShmidsStruct>) -> i32 { + let metadata = &SHM_METADATA; + + if let Some(mut segment) = metadata.shmtable.get_mut(&shmid) { + match cmd { + IPC_STAT => { + *buf.unwrap() = segment.shminfo; + } + IPC_RMID => { + segment.rmid = true; + segment.shminfo.shm_perm.mode |= SHM_DEST as u16; + if segment.shminfo.shm_nattch == 0 { + let key = segment.key; + drop(segment); + metadata.shmtable.remove(&shmid); + metadata.shmkeyidtable.remove(&key); + } + } + _ => { + return syscall_error( + Errno::EINVAL, + "shmctl", + "Arguments provided do not match implemented parameters", + ); + } + } + } else { + return syscall_error(Errno::EINVAL, "shmctl", "Invalid identifier"); + } + + 0 //shmctl has succeeded! + } + + //------------------MUTEX SYSCALLS------------------ + + pub fn mutex_create_syscall(&self) -> i32 { + let mut mutextable = self.mutex_table.write(); + let mut index_option = None; + for i in 0..mutextable.len() { + if mutextable[i].is_none() { + index_option = Some(i); + break; + } + } + + let index = if let Some(ind) = index_option { + ind + } else { + mutextable.push(None); + mutextable.len() - 1 + }; + + let mutex_result = interface::RawMutex::create(); + match mutex_result { + Ok(mutex) => { + mutextable[index] = Some(interface::RustRfc::new(mutex)); + index as i32 + } + Err(_) => match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => syscall_error( + i, + "mutex_create", + "The libc call to pthread_mutex_init failed!", + ), + Err(()) => panic!("Unknown errno value from pthread_mutex_init returned!"), + }, + } + } + + pub fn mutex_destroy_syscall(&self, mutex_handle: i32) -> i32 { + let mut mutextable = self.mutex_table.write(); + if mutex_handle < mutextable.len() as i32 + && mutex_handle >= 0 + && mutextable[mutex_handle as usize].is_some() + { + mutextable[mutex_handle as usize] = None; + 0 + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "mutex_destroy", + "Mutex handle does not refer to a valid mutex!", + ) + } + //the RawMutex is destroyed on Drop + + //this is currently assumed to always succeed, as the man page does not list possible + //errors for pthread_mutex_destroy + } + + pub fn mutex_lock_syscall(&self, mutex_handle: i32) -> i32 { + let mutextable = self.mutex_table.read(); + if mutex_handle < mutextable.len() as i32 + && mutex_handle >= 0 + && mutextable[mutex_handle as usize].is_some() + { + let clonedmutex = mutextable[mutex_handle as usize].as_ref().unwrap().clone(); + drop(mutextable); + let retval = clonedmutex.lock(); + + if retval < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "mutex_lock", + "The libc call to pthread_mutex_lock failed!", + ); + } + Err(()) => panic!("Unknown errno value from pthread_mutex_lock returned!"), + }; + } + + retval + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "mutex_lock", + "Mutex handle does not refer to a valid mutex!", + ) + } + } + + pub fn mutex_trylock_syscall(&self, mutex_handle: i32) -> i32 { + let mutextable = self.mutex_table.read(); + if mutex_handle < mutextable.len() as i32 + && mutex_handle >= 0 + && mutextable[mutex_handle as usize].is_some() + { + let clonedmutex = mutextable[mutex_handle as usize].as_ref().unwrap().clone(); + drop(mutextable); + let retval = clonedmutex.trylock(); + + if retval < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "mutex_trylock", + "The libc call to pthread_mutex_trylock failed!", + ); + } + Err(()) => panic!("Unknown errno value from pthread_mutex_trylock returned!"), + }; + } + + retval + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "mutex_trylock", + "Mutex handle does not refer to a valid mutex!", + ) + } + } + + pub fn mutex_unlock_syscall(&self, mutex_handle: i32) -> i32 { + let mutextable = self.mutex_table.read(); + if mutex_handle < mutextable.len() as i32 + && mutex_handle >= 0 + && mutextable[mutex_handle as usize].is_some() + { + let clonedmutex = mutextable[mutex_handle as usize].as_ref().unwrap().clone(); + drop(mutextable); + let retval = clonedmutex.unlock(); + + if retval < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "mutex_unlock", + "The libc call to pthread_mutex_unlock failed!", + ); + } + Err(()) => panic!("Unknown errno value from pthread_mutex_unlock returned!"), + }; + } + + retval + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "mutex_unlock", + "Mutex handle does not refer to a valid mutex!", + ) + } + } + + //------------------CONDVAR SYSCALLS------------------ + + pub fn cond_create_syscall(&self) -> i32 { + let mut cvtable = self.cv_table.write(); + let mut index_option = None; + for i in 0..cvtable.len() { + if cvtable[i].is_none() { + index_option = Some(i); + break; + } + } + + let index = if let Some(ind) = index_option { + ind + } else { + cvtable.push(None); + cvtable.len() - 1 + }; + + let cv_result = interface::RawCondvar::create(); + match cv_result { + Ok(cv) => { + cvtable[index] = Some(interface::RustRfc::new(cv)); + index as i32 + } + Err(_) => match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => syscall_error( + i, + "cond_create", + "The libc call to pthread_cond_init failed!", + ), + Err(()) => panic!("Unknown errno value from pthread_cond_init returned!"), + }, + } + } + + pub fn cond_destroy_syscall(&self, cv_handle: i32) -> i32 { + let mut cvtable = self.cv_table.write(); + if cv_handle < cvtable.len() as i32 + && cv_handle >= 0 + && cvtable[cv_handle as usize].is_some() + { + cvtable[cv_handle as usize] = None; + 0 + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "cond_destroy", + "Condvar handle does not refer to a valid condvar!", + ) + } + //the RawCondvar is destroyed on Drop + + //this is currently assumed to always succeed, as the man page does not list possible + //errors for pthread_cv_destroy + } + + pub fn cond_signal_syscall(&self, cv_handle: i32) -> i32 { + let cvtable = self.cv_table.read(); + if cv_handle < cvtable.len() as i32 + && cv_handle >= 0 + && cvtable[cv_handle as usize].is_some() + { + let clonedcv = cvtable[cv_handle as usize].as_ref().unwrap().clone(); + drop(cvtable); + let retval = clonedcv.signal(); + + if retval < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "cond_signal", + "The libc call to pthread_cond_signal failed!", + ); + } + Err(()) => panic!("Unknown errno value from pthread_cond_signal returned!"), + }; + } + + retval + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "cond_signal", + "Condvar handle does not refer to a valid condvar!", + ) + } + } + + pub fn cond_broadcast_syscall(&self, cv_handle: i32) -> i32 { + let cvtable = self.cv_table.read(); + if cv_handle < cvtable.len() as i32 + && cv_handle >= 0 + && cvtable[cv_handle as usize].is_some() + { + let clonedcv = cvtable[cv_handle as usize].as_ref().unwrap().clone(); + drop(cvtable); + let retval = clonedcv.broadcast(); + + if retval < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "cond_broadcast", + "The libc call to pthread_cond_broadcast failed!", + ); + } + Err(()) => panic!("Unknown errno value from pthread_cond_broadcast returned!"), + }; + } + + retval + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "cond_broadcast", + "Condvar handle does not refer to a valid condvar!", + ) + } + } + + pub fn cond_wait_syscall(&self, cv_handle: i32, mutex_handle: i32) -> i32 { + let cvtable = self.cv_table.read(); + if cv_handle < cvtable.len() as i32 + && cv_handle >= 0 + && cvtable[cv_handle as usize].is_some() + { + let clonedcv = cvtable[cv_handle as usize].as_ref().unwrap().clone(); + drop(cvtable); + + let mutextable = self.mutex_table.read(); + if mutex_handle < mutextable.len() as i32 + && mutex_handle >= 0 + && mutextable[mutex_handle as usize].is_some() + { + let clonedmutex = mutextable[mutex_handle as usize].as_ref().unwrap().clone(); + drop(mutextable); + let retval = clonedcv.wait(&*clonedmutex); + + // if the cancel status is set in the cage, we trap around a cancel point + // until the individual thread is signaled to cancel itself + if self + .cancelstatus + .load(interface::RustAtomicOrdering::Relaxed) + { + loop { + interface::cancelpoint(self.cageid); + } // we check cancellation status here without letting the function return + } + + if retval < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "cond_wait", + "The libc call to pthread_cond_wait failed!", + ); + } + Err(()) => panic!("Unknown errno value from pthread_cond_wait returned!"), + }; + } + + retval + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "cond_wait", + "Mutex handle does not refer to a valid mutex!", + ) + } + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "cond_wait", + "Condvar handle does not refer to a valid condvar!", + ) + } + } + + pub fn cond_timedwait_syscall( + &self, + cv_handle: i32, + mutex_handle: i32, + time: interface::RustDuration, + ) -> i32 { + let cvtable = self.cv_table.read(); + if cv_handle < cvtable.len() as i32 + && cv_handle >= 0 + && cvtable[cv_handle as usize].is_some() + { + let clonedcv = cvtable[cv_handle as usize].as_ref().unwrap().clone(); + drop(cvtable); + + let mutextable = self.mutex_table.read(); + if mutex_handle < mutextable.len() as i32 + && mutex_handle >= 0 + && mutextable[mutex_handle as usize].is_some() + { + let clonedmutex = mutextable[mutex_handle as usize].as_ref().unwrap().clone(); + drop(mutextable); + let retval = clonedcv.timedwait(&*clonedmutex, time); + if retval < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "cond_wait", + "The libc call to pthread_cond_wait failed!", + ); + } + Err(()) => panic!("Unknown errno value from pthread_cond_wait returned!"), + }; + } + + retval + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "cond_wait", + "Mutex handle does not refer to a valid mutex!", + ) + } + } else { + //undefined behavior + syscall_error( + Errno::EBADF, + "cond_wait", + "Condvar handle does not refer to a valid condvar!", + ) + } + } + + //------------------SEMAPHORE SYSCALLS------------------ + /* + * Initialize semaphore object SEM to value + * pshared used to indicate whether the semaphore is shared in threads (when equals to 0) + * or shared between processes (when nonzero) + */ + pub fn sem_init_syscall(&self, sem_handle: u32, pshared: i32, value: u32) -> i32 { + // Boundary check + if value > SEM_VALUE_MAX { + return syscall_error(Errno::EINVAL, "sem_init", "value exceeds SEM_VALUE_MAX"); + } + + let metadata = &SHM_METADATA; + let is_shared = pshared != 0; + + // Iterate semaphore table, if semaphore is already initialzed return error + let semtable = &self.sem_table; + + // Will initialize only it's new + if !semtable.contains_key(&sem_handle) { + let new_semaphore = + interface::RustRfc::new(interface::RustSemaphore::new(value, is_shared)); + semtable.insert(sem_handle, new_semaphore.clone()); + + if is_shared { + let rev_shm = self.rev_shm.lock(); + // if its shared and exists in an existing mapping we need to add it to other cages + if let Some((mapaddr, shmid)) = + Self::search_for_addr_in_region(&rev_shm, sem_handle) + { + let offset = mapaddr - sem_handle; + if let Some(segment) = metadata.shmtable.get_mut(&shmid) { + for cageid in segment.attached_cages.clone().into_read_only().keys() { + // iterate through all cages with segment attached and add semaphor in segments at attached addr + offset + let cage = interface::cagetable_getref(*cageid); + let addrs = Self::rev_shm_find_addrs_by_shmid(&rev_shm, shmid); + for addr in addrs.iter() { + cage.sem_table.insert(addr + offset, new_semaphore.clone()); + } + } + segment.semaphor_offsets.insert(offset); + } + } + } + return 0; + } + + return syscall_error(Errno::EBADF, "sem_init", "semaphore already initialized"); + } + + pub fn sem_wait_syscall(&self, sem_handle: u32) -> i32 { + let semtable = &self.sem_table; + // Check whether semaphore exists + if let Some(sementry) = semtable.get_mut(&sem_handle) { + let semaphore = sementry.clone(); + drop(sementry); + semaphore.lock(); + } else { + return syscall_error(Errno::EINVAL, "sem_wait", "sem is not a valid semaphore"); + } + return 0; + } + + pub fn sem_post_syscall(&self, sem_handle: u32) -> i32 { + let semtable = &self.sem_table; + if let Some(sementry) = semtable.get_mut(&sem_handle) { + let semaphore = sementry.clone(); + drop(sementry); + if !semaphore.unlock() { + return syscall_error( + Errno::EOVERFLOW, + "sem_post", + "The maximum allowable value for a semaphore would be exceeded", + ); + } + } else { + return syscall_error(Errno::EINVAL, "sem_wait", "sem is not a valid semaphore"); + } + return 0; + } + + pub fn sem_destroy_syscall(&self, sem_handle: u32) -> i32 { + let metadata = &SHM_METADATA; + + let semtable = &self.sem_table; + // remove entry from semaphore table + if let Some(sementry) = semtable.remove(&sem_handle) { + if sementry + .1 + .is_shared + .load(interface::RustAtomicOrdering::Relaxed) + { + // if its shared we'll need to remove it from other attachments + let rev_shm = self.rev_shm.lock(); + if let Some((mapaddr, shmid)) = + Self::search_for_addr_in_region(&rev_shm, sem_handle) + { + // find all segments that contain semaphore + let offset = mapaddr - sem_handle; + if let Some(segment) = metadata.shmtable.get_mut(&shmid) { + for cageid in segment.attached_cages.clone().into_read_only().keys() { + // iterate through all cages containing segment + let cage = interface::cagetable_getref(*cageid); + let addrs = Self::rev_shm_find_addrs_by_shmid(&rev_shm, shmid); + for addr in addrs.iter() { + cage.sem_table.remove(&(addr + offset)); //remove semapoores at attached addresses + the offset + } + } + } + } + } + return 0; + } else { + return syscall_error(Errno::EINVAL, "sem_destroy", "sem is not a valid semaphore"); + } + } + + /* + * Take only sem_t *sem as argument, and return int *sval + */ + pub fn sem_getvalue_syscall(&self, sem_handle: u32) -> i32 { + let semtable = &self.sem_table; + if let Some(sementry) = semtable.get_mut(&sem_handle) { + let semaphore = sementry.clone(); + drop(sementry); + return semaphore.get_value(); + } + return syscall_error( + Errno::EINVAL, + "sem_getvalue", + "sem is not a valid semaphore", + ); + } + + pub fn sem_trywait_syscall(&self, sem_handle: u32) -> i32 { + let semtable = &self.sem_table; + // Check whether semaphore exists + if let Some(sementry) = semtable.get_mut(&sem_handle) { + let semaphore = sementry.clone(); + drop(sementry); + if !semaphore.trylock() { + return syscall_error( + Errno::EAGAIN, + "sem_trywait", + "The operation could not be performed without blocking", + ); + } + } else { + return syscall_error(Errno::EINVAL, "sem_trywait", "sem is not a valid semaphore"); + } + return 0; + } + + pub fn sem_timedwait_syscall(&self, sem_handle: u32, time: interface::RustDuration) -> i32 { + let abstime = libc::timespec { + tv_sec: time.as_secs() as i64, + tv_nsec: (time.as_nanos() % 1000000000) as i64, + }; + if abstime.tv_nsec < 0 { + return syscall_error(Errno::EINVAL, "sem_timedwait", "Invalid timedout"); + } + let semtable = &self.sem_table; + // Check whether semaphore exists + if let Some(sementry) = semtable.get_mut(&sem_handle) { + let semaphore = sementry.clone(); + drop(sementry); + if !semaphore.timedlock(time) { + return syscall_error( + Errno::ETIMEDOUT, + "sem_timedwait", + "The call timed out before the semaphore could be locked", + ); + } + } else { + return syscall_error( + Errno::EINVAL, + "sem_timedwait", + "sem is not a valid semaphore", + ); + } + return 0; + } +} diff --git a/src/safeposix/syscalls/fs_constants.rs b/src/safeposix/syscalls/fs_constants.rs new file mode 100644 index 00000000..1bbb85fc --- /dev/null +++ b/src/safeposix/syscalls/fs_constants.rs @@ -0,0 +1,196 @@ +// File system related constants +#![allow(dead_code)] +#![allow(unused_variables)] + +use crate::interface; + +// Define constants using static or const +// Imported into fs_calls file +pub const DT_UNKNOWN: u8 = 0; + +pub const STARTINGFD: i32 = 0; +pub const MAXFD: i32 = 1024; +pub const STARTINGPIPE: i32 = 0; +pub const MAXPIPE: i32 = 1024; + +pub const ROOTDIRECTORYINODE: usize = 1; +pub const STREAMINODE: usize = 2; + +pub const PIPE_CAPACITY: usize = 65536; + +pub const F_OK: u32 = 0; +pub const X_OK: u32 = 1; +pub const W_OK: u32 = 2; +pub const R_OK: u32 = 4; + +pub const O_RDONLY: i32 = 0o0; +pub const O_WRONLY: i32 = 0o1; +pub const O_RDWR: i32 = 0o2; +pub const O_RDWRFLAGS: i32 = 0o3; + +pub const O_CREAT: i32 = 0o100; +pub const O_EXCL: i32 = 0o200; +pub const O_NOCTTY: i32 = 0o400; +pub const O_TRUNC: i32 = 0o1000; +pub const O_APPEND: i32 = 0o2000; +pub const O_NONBLOCK: i32 = 0o4000; +// O_NDELAY=O_NONBLOCK +pub const O_SYNC: i32 = 0o10000; +// O_FSYNC=O_SYNC +pub const O_ASYNC: i32 = 0o20000; +pub const O_CLOEXEC: i32 = 0o2000000; + +pub const DEFAULTTIME: u64 = 1323630836; + +//Standard flag combinations +pub const S_IRWXA: u32 = 0o777; +pub const S_IRWXU: u32 = 0o700; +pub const S_IRUSR: u32 = 0o400; +pub const S_IWUSR: u32 = 0o200; +pub const S_IXUSR: u32 = 0o100; +pub const S_IRWXG: u32 = 0o070; +pub const S_IRGRP: u32 = 0o040; +pub const S_IWGRP: u32 = 0o020; +pub const S_IXGRP: u32 = 0o010; +pub const S_IRWXO: u32 = 0o007; +pub const S_IROTH: u32 = 0o004; +pub const S_IWOTH: u32 = 0o002; +pub const S_IXOTH: u32 = 0o001; + +//Commands for FCNTL +pub const F_DUPFD: i32 = 0; +pub const F_GETFD: i32 = 1; +pub const F_SETFD: i32 = 2; +pub const F_GETFL: i32 = 3; +pub const F_SETFL: i32 = 4; +pub const F_GETLK: i32 = 5; +pub const F_GETLK64: i32 = 5; +pub const F_SETLK: i32 = 6; +pub const F_SETLK64: i32 = 6; +pub const F_SETLKW: i32 = 7; +pub const F_SETLKW64: i32 = 7; +pub const F_SETOWN: i32 = 8; +pub const F_GETOWN: i32 = 9; +pub const F_SETSIG: i32 = 10; +pub const F_GETSIG: i32 = 11; +pub const F_SETLEASE: i32 = 1024; +pub const F_GETLEASE: i32 = 1025; +pub const F_NOTIFY: i32 = 1026; + +//Commands for IOCTL +pub const FIONBIO: u32 = 21537; +pub const FIOASYNC: u32 = 21586; + +//File types for open/stat etc. +pub const S_IFBLK: i32 = 0o60000; +pub const S_IFCHR: i32 = 0o20000; +pub const S_IFDIR: i32 = 0o40000; +pub const S_IFIFO: i32 = 0o10000; +pub const S_IFLNK: i32 = 0o120000; +pub const S_IFREG: i32 = 0o100000; +pub const S_IFSOCK: i32 = 0o140000; +pub const S_FILETYPEFLAGS: i32 = 0o170000; + +//for flock syscall +pub const LOCK_SH: i32 = 1; +pub const LOCK_EX: i32 = 2; +pub const LOCK_UN: i32 = 8; +pub const LOCK_NB: i32 = 4; +//for mmap/munmap syscall +pub const MAP_SHARED: i32 = 1; +pub const MAP_PRIVATE: i32 = 2; +pub const MAP_FIXED: i32 = 16; +pub const MAP_ANONYMOUS: i32 = 32; +pub const MAP_HUGE_SHIFT: i32 = 26; +pub const MAP_HUGETLB: i32 = 262144; + +pub const PROT_NONE: i32 = 0; +pub const PROT_READ: i32 = 1; +pub const PROT_WRITE: i32 = 2; +pub const PROT_EXEC: i32 = 4; + +pub const SEEK_SET: i32 = 0; +pub const SEEK_CUR: i32 = 1; +pub const SEEK_END: i32 = 2; + +pub const IPC_PRIVATE: i32 = 0o0; +pub const IPC_CREAT: i32 = 0o1000; +pub const IPC_EXCL: i32 = 0o2000; + +pub const IPC_RMID: i32 = 0; +pub const IPC_SET: i32 = 1; +pub const IPC_STAT: i32 = 2; + +pub const SHM_DEST: i32 = 0o1000; +pub const SHM_LOCKED: i32 = 0o2000; +pub const SHM_HUGETLB: i32 = 0o4000; + +pub const SHM_R: i32 = 0o400; +pub const SHM_W: i32 = 0o200; +pub const SHM_RDONLY: i32 = 0o10000; +pub const SHM_RND: i32 = 0o20000; +pub const SHM_REMAP: i32 = 0o40000; +pub const SHM_EXEC: i32 = 0o100000; + +pub const SHMMIN: u32 = 1; +pub const SHMMNI: u32 = 4096; +pub const SHMMAX: u32 = 4278190079; // (ULONG_MAX - (1UL << 24)) +pub const SHMALL: u32 = 4278190079; // (ULONG_MAX - (1UL << 24)); +pub const SHMSEG: u32 = SHMMNI; + +pub const SEM_VALUE_MAX: u32 = 2147483647; + +//device info for char files +#[derive(interface::SerdeSerialize, interface::SerdeDeserialize, PartialEq, Eq, Debug)] +pub struct DevNo { + pub major: u32, + pub minor: u32, +} +pub const NULLDEVNO: DevNo = DevNo { major: 1, minor: 3 }; +pub const ZERODEVNO: DevNo = DevNo { major: 1, minor: 5 }; +pub const RANDOMDEVNO: DevNo = DevNo { major: 1, minor: 8 }; +pub const URANDOMDEVNO: DevNo = DevNo { major: 1, minor: 9 }; + +pub const FILEDATAPREFIX: &str = "linddata."; + +pub fn is_reg(mode: u32) -> bool { + (mode as i32 & S_FILETYPEFLAGS) == S_IFREG +} + +pub fn is_chr(mode: u32) -> bool { + (mode as i32 & S_FILETYPEFLAGS) == S_IFCHR +} + +pub fn is_dir(mode: u32) -> bool { + (mode as i32 & S_FILETYPEFLAGS) == S_IFDIR +} + +pub fn is_wronly(flags: i32) -> bool { + (flags & O_RDWRFLAGS) == O_WRONLY +} +pub fn is_rdonly(flags: i32) -> bool { + (flags & O_RDWRFLAGS) == O_RDONLY +} + +//the same as the glibc makedev +pub fn makedev(dev: &DevNo) -> u64 { + ((dev.major as u64 & 0x00000fff) << 8) + | ((dev.major as u64 & 0xfffff000) << 32) + | ((dev.minor as u64 & 0x000000ff) << 0) + | ((dev.minor as u64 & 0xffffff00) << 12) +} + +//the same as the glibc major and minor functions +pub fn major(devnum: u64) -> u32 { + (((devnum & 0x00000000000fff00) >> 8) | ((devnum & 0xfffff00000000000) >> 32)) as u32 +} +pub fn minor(devnum: u64) -> u32 { + (((devnum & 0x00000000000000ff) >> 0) | ((devnum & 0x00000ffffff00000) >> 12)) as u32 +} + +pub fn devtuple(devnum: u64) -> DevNo { + DevNo { + major: major(devnum), + minor: minor(devnum), + } +} diff --git a/src/safeposix/syscalls/mod.rs b/src/safeposix/syscalls/mod.rs new file mode 100644 index 00000000..51b60b43 --- /dev/null +++ b/src/safeposix/syscalls/mod.rs @@ -0,0 +1,12 @@ +pub mod fs_calls; +pub mod fs_constants; +pub mod net_calls; +pub mod net_constants; +pub mod sys_calls; +pub mod sys_constants; +pub use fs_calls::*; +pub use fs_constants::*; +pub use net_calls::*; +pub use net_constants::*; +pub use sys_calls::*; +pub use sys_constants::*; diff --git a/src/safeposix/syscalls/net_calls.rs b/src/safeposix/syscalls/net_calls.rs new file mode 100644 index 00000000..83042f11 --- /dev/null +++ b/src/safeposix/syscalls/net_calls.rs @@ -0,0 +1,2952 @@ +#![allow(dead_code)] +// Network related system calls +// outlines and implements all of the networking system calls that are being emulated/faked in Lind + +use super::fs_constants::*; +use super::net_constants::*; +use super::sys_constants::*; +use crate::interface; +use crate::interface::errnos::{syscall_error, Errno}; +use crate::safeposix::cage::{FileDescriptor::*, *}; +use crate::safeposix::filesystem::*; +use crate::safeposix::net::*; + +impl Cage { + fn _socket_initializer( + &self, + domain: i32, + socktype: i32, + protocol: i32, + nonblocking: bool, + cloexec: bool, + conn: ConnState, + ) -> SocketDesc { + let flags = if nonblocking { O_NONBLOCK } else { 0 } | if cloexec { O_CLOEXEC } else { 0 }; + + let sockfd = SocketDesc { + flags: flags, + domain: domain, + rawfd: -1, // RawFD set in bind for inet, or stays at -1 for others + handle: interface::RustRfc::new(interface::RustLock::new(Self::mksockhandle( + domain, socktype, protocol, conn, flags, + ))), + advlock: interface::RustRfc::new(interface::AdvisoryLock::new()), + }; //currently on failure to create handle we create successfully but it's corrupted, change? + + return sockfd; + } + + fn _socket_inserter(&self, sockfd: FileDescriptor) -> i32 { + let (fd, guardopt) = self.get_next_fd(None); + if fd < 0 { + return fd; + } + let fdoption = &mut *guardopt.unwrap(); + let _insertval = fdoption.insert(sockfd); + return fd; + } + + fn _implicit_bind(&self, sockhandle: &mut SocketHandle, domain: i32) -> i32 { + if sockhandle.localaddr.is_none() { + let localaddr = match Self::assign_new_addr( + sockhandle, + domain, + sockhandle.protocol & (1 << SO_REUSEPORT) != 0, + ) { + Ok(a) => a, + Err(e) => return e, + }; + + let bindret = self.bind_inner_socket(sockhandle, &localaddr, true); + + if bindret < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "recvfrom", + "syscall error from attempting to bind within recvfrom", + ); + } + Err(()) => panic!("Unknown errno value from socket recvfrom returned!"), + }; + } + } + 0 + } + + pub fn socket_syscall(&self, domain: i32, socktype: i32, protocol: i32) -> i32 { + let real_socktype = socktype & 0x7; //get the type without the extra flags, it's stored in the last 3 bits + let nonblocking = (socktype & SOCK_NONBLOCK) != 0; + let cloexec = (socktype & SOCK_CLOEXEC) != 0; + + match real_socktype { + SOCK_STREAM => { + //SOCK_STREAM defaults to TCP for protocol, otherwise protocol is unsupported + let newprotocol = if protocol == 0 { IPPROTO_TCP } else { protocol }; + + if newprotocol != IPPROTO_TCP { + return syscall_error( + Errno::EOPNOTSUPP, + "socket", + "The only SOCK_STREAM implemented is TCP. Unknown protocol input.", + ); + } + match domain { + PF_INET | PF_UNIX => { + let sockfdobj = self._socket_initializer( + domain, + socktype, + newprotocol, + nonblocking, + cloexec, + ConnState::NOTCONNECTED, + ); + return self._socket_inserter(Socket(sockfdobj)); + } + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "socket", + "trying to use an unimplemented domain", + ); + } + } + } + + SOCK_DGRAM => { + //SOCK_DGRAM defaults to UDP for protocol, otherwise protocol is unsuported + let newprotocol = if protocol == 0 { IPPROTO_UDP } else { protocol }; + + if newprotocol != IPPROTO_UDP { + return syscall_error( + Errno::EOPNOTSUPP, + "socket", + "The only SOCK_DGRAM implemented is UDP. Unknown protocol input.", + ); + } + match domain { + PF_INET | PF_UNIX => { + let sockfdobj = self._socket_initializer( + domain, + socktype, + newprotocol, + nonblocking, + cloexec, + ConnState::NOTCONNECTED, + ); + return self._socket_inserter(Socket(sockfdobj)); + } + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "socket", + "trying to use an unimplemented domain", + ); + } + } + } + + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "socket", + "trying to use an unimplemented socket type", + ); + } + } + } + + //creates a sockhandle if none exists, otherwise this is a no-op + pub fn force_innersocket(sockhandle: &mut SocketHandle) { + if let None = sockhandle.innersocket { + let thissock = + interface::Socket::new(sockhandle.domain, sockhandle.socktype, sockhandle.protocol); + + for reuse in [SO_REUSEPORT, SO_REUSEADDR] { + if sockhandle.socket_options & (1 << reuse) == 0 { + continue; + } + let sockret = thissock.setsockopt(SOL_SOCKET, reuse, 1); + if sockret < 0 { + panic!("Cannot handle failure in setsockopt on socket creation"); + } + } + + sockhandle.innersocket = Some(thissock); + }; + } + + //we assume we've converted into a RustSockAddr in the dispatcher + pub fn bind_syscall(&self, fd: i32, localaddr: &interface::GenSockaddr) -> i32 { + self.bind_inner(fd, localaddr, false) + } + + fn bind_inner_socket( + &self, + sockhandle: &mut SocketHandle, + localaddr: &interface::GenSockaddr, + prereserved: bool, + ) -> i32 { + if localaddr.get_family() != sockhandle.domain as u16 { + return syscall_error( + Errno::EINVAL, + "bind", + "An address with an invalid family for the given domain was specified", + ); + } + + if sockhandle.localaddr.is_some() { + return syscall_error( + Errno::EINVAL, + "bind", + "The socket is already bound to an address", + ); + } + + let mut newsockaddr = localaddr.clone(); + + let res = match sockhandle.domain { + AF_UNIX => self.bind_inner_socket_unix(sockhandle, &mut newsockaddr), + AF_INET | AF_INET6 => { + self.bind_inner_socket_inet(sockhandle, &mut newsockaddr, prereserved) + } + _ => { + return syscall_error(Errno::EINVAL, "bind", "Unsupported domain provided"); + } + }; + + sockhandle.localaddr = Some(newsockaddr); + + res + } + + fn bind_inner_socket_unix( + &self, + sockhandle: &mut SocketHandle, + newsockaddr: &mut interface::GenSockaddr, + ) -> i32 { + // Unix Sockets + let path = newsockaddr.path(); + //Check that path is not empty + if path.len() == 0 { + return syscall_error(Errno::ENOENT, "bind", "given path was null"); + } + let truepath = normpath(convpath(path), self); + + match metawalkandparent(truepath.as_path()) { + //If neither the file nor parent exists + (None, None) => { + return syscall_error(Errno::ENOENT, "bind", "a directory component in pathname does not exist or is a dangling symbolic link"); + } + //If the file doesn't exist but the parent does + (None, Some(pardirinode)) => { + let filename = truepath.file_name().unwrap().to_str().unwrap().to_string(); //for now we assume this is sane, but maybe this should be checked later + + //this may end up skipping an inode number in the case of ENOTDIR, but that's not catastrophic + let newinodenum = FS_METADATA + .nextinode + .fetch_add(1, interface::RustAtomicOrdering::Relaxed); //fetch_add returns the previous value, which is the inode number we want + let newinode; + + if let Inode::Dir(ref mut dir) = + *(FS_METADATA.inodetable.get_mut(&pardirinode).unwrap()) + { + let mode = (dir.mode | S_FILETYPEFLAGS as u32) & S_IRWXA; + let effective_mode = S_IFSOCK as u32 | mode; + + let time = interface::timestamp(); //We do a real timestamp now + newinode = Inode::Socket(SocketInode { + size: 0, + uid: DEFAULT_UID, + gid: DEFAULT_GID, + mode: effective_mode, + linkcount: 1, + refcount: 1, + atime: time, + ctime: time, + mtime: time, + }); + + dir.filename_to_inode_dict + .insert(filename.clone(), newinodenum); + dir.linkcount += 1; + } else { + return syscall_error( + Errno::ENOTDIR, + "bind", + "unix domain socket path made socket address child of non-directory file", + ); + } + sockhandle.unix_info = Some(UnixSocketInfo { + mode: S_IFSOCK | 0o666, + sendpipe: None, + receivepipe: None, + inode: newinodenum, + }); + + NET_METADATA.domsock_paths.insert(truepath); + FS_METADATA.inodetable.insert(newinodenum, newinode); + } + (Some(_inodenum), ..) => { + return syscall_error(Errno::EADDRINUSE, "bind", "Address already in use"); + } + } + + 0 + } + + fn bind_inner_socket_inet( + &self, + sockhandle: &mut SocketHandle, + newsockaddr: &mut interface::GenSockaddr, + prereserved: bool, + ) -> i32 { + // INET Sockets + let intent_to_rebind = sockhandle.socket_options & (1 << SO_REUSEPORT) != 0; + Self::force_innersocket(sockhandle); + + let newlocalport = if prereserved { + newsockaddr.port() + } else { + let localout = NET_METADATA._reserve_localport( + newsockaddr.addr(), + newsockaddr.port(), + sockhandle.protocol, + sockhandle.domain, + intent_to_rebind, + ); + if let Err(errnum) = localout { + return errnum; + } + localout.unwrap() + }; + + newsockaddr.set_port(newlocalport); + let bindret = sockhandle.innersocket.as_ref().unwrap().bind(&newsockaddr); + + if bindret < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error(i, "bind", "The libc call to bind failed!"); + } + Err(()) => panic!("Unknown errno value from socket bind returned!"), + }; + } + + 0 + } + + pub fn bind_inner( + &self, + fd: i32, + localaddr: &interface::GenSockaddr, + prereserved: bool, + ) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + Socket(ref mut sockfdobj) => { + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + self.bind_inner_socket(&mut *sockhandle, localaddr, prereserved) + } + _ => syscall_error( + Errno::ENOTSOCK, + "bind", + "file descriptor refers to something other than a socket", + ), + } + } else { + syscall_error(Errno::EBADF, "bind", "invalid file descriptor") + } + } + + fn assign_new_addr_unix(sockhandle: &SocketHandle) -> interface::GenSockaddr { + if let Some(addr) = sockhandle.localaddr.clone() { + addr + } else { + let path = interface::gen_ud_path(); + let newremote = interface::GenSockaddr::Unix(interface::new_sockaddr_unix( + AF_UNIX as u16, + path.as_bytes(), + )); + newremote + } + } + + fn assign_new_addr( + sockhandle: &SocketHandle, + domain: i32, + rebindability: bool, + ) -> Result { + if let Some(addr) = &sockhandle.localaddr { + Ok(addr.clone()) + } else { + let mut newremote: interface::GenSockaddr; + //This is the specified behavior for the berkeley sockets API + match domain { + AF_UNIX => { + let path = interface::gen_ud_path(); + newremote = interface::GenSockaddr::Unix(interface::new_sockaddr_unix( + AF_UNIX as u16, + path.as_bytes(), + )); + } + AF_INET => { + newremote = interface::GenSockaddr::V4(interface::SockaddrV4::default()); + let addr = interface::GenIpaddr::V4(interface::V4Addr::default()); + newremote.set_addr(addr); + newremote.set_family(AF_INET as u16); + newremote.set_port( + match NET_METADATA._reserve_localport( + addr.clone(), + 0, + sockhandle.protocol, + sockhandle.domain, + rebindability, + ) { + Ok(portnum) => portnum, + Err(errnum) => return Err(errnum), + }, + ); + } + AF_INET6 => { + newremote = interface::GenSockaddr::V6(interface::SockaddrV6::default()); + let addr = interface::GenIpaddr::V6(interface::V6Addr::default()); + newremote.set_addr(addr); + newremote.set_family(AF_INET6 as u16); + newremote.set_port( + match NET_METADATA._reserve_localport( + addr.clone(), + 0, + sockhandle.protocol, + sockhandle.domain, + rebindability, + ) { + Ok(portnum) => portnum, + Err(errnum) => return Err(errnum), + }, + ); + } + _ => { + return Err(syscall_error( + Errno::EOPNOTSUPP, + "assign", + "Unkown protocol when assigning", + )); + } + }; + Ok(newremote) + } + } + + pub fn connect_syscall(&self, fd: i32, remoteaddr: &interface::GenSockaddr) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + Socket(ref mut sockfdobj) => { + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + if remoteaddr.get_family() != sockhandle.domain as u16 { + return syscall_error( + Errno::EINVAL, + "connect", + "An address with an invalid family for the given domain was specified", + ); + } + + match sockhandle.protocol { + IPPROTO_UDP => { + return self.connect_udp(&mut *sockhandle, sockfdobj, remoteaddr) + } + IPPROTO_TCP => { + return self.connect_tcp(&mut *sockhandle, sockfdobj, remoteaddr) + } + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "connect", + "Unknown protocol in connect", + ) + } + }; + } + _ => { + return syscall_error( + Errno::ENOTSOCK, + "connect", + "file descriptor refers to something other than a socket", + ); + } + } + } else { + return syscall_error(Errno::EBADF, "connect", "invalid file descriptor"); + } + } + + fn connect_udp( + &self, + sockhandle: &mut SocketHandle, + sockfdobj: &mut SocketDesc, + remoteaddr: &interface::GenSockaddr, + ) -> i32 { + //for UDP, just set the addresses and return + //we don't need to check connection state for UDP, it's connectionless! + sockhandle.remoteaddr = Some(remoteaddr.clone()); + match sockhandle.localaddr { + Some(_) => return 0, + None => { + let localaddr = match Self::assign_new_addr( + &*sockhandle, + sockhandle.domain, + sockhandle.protocol & (1 << SO_REUSEPORT) != 0, + ) { + Ok(a) => a, + Err(e) => return e, + }; + + let bindret = self.bind_inner_socket(&mut *sockhandle, &localaddr, true); + // udp now connected so lets set rawfd for select + sockfdobj.rawfd = sockhandle.innersocket.as_ref().unwrap().raw_sys_fd; + return bindret; + } + }; + } + + fn connect_tcp( + &self, + sockhandle: &mut SocketHandle, + sockfdobj: &mut SocketDesc, + remoteaddr: &interface::GenSockaddr, + ) -> i32 { + // TCP connection logic + if sockhandle.state != ConnState::NOTCONNECTED { + return syscall_error( + Errno::EISCONN, + "connect", + "The descriptor is already connected", + ); + } + + match sockhandle.domain { + AF_UNIX => self.connect_tcp_unix(&mut *sockhandle, sockfdobj, remoteaddr), + AF_INET | AF_INET6 => self.connect_tcp_inet(&mut *sockhandle, sockfdobj, remoteaddr), + _ => return syscall_error(Errno::EINVAL, "connect", "Unsupported domain provided"), + } + } + + fn connect_tcp_unix( + &self, + sockhandle: &mut SocketHandle, + sockfdobj: &mut SocketDesc, + remoteaddr: &interface::GenSockaddr, + ) -> i32 { + // TCP domain socket logic + if let None = sockhandle.localaddr { + let localaddr = Self::assign_new_addr_unix(&sockhandle); + self.bind_inner_socket(&mut *sockhandle, &localaddr, false); + } + let remotepathbuf = normpath(convpath(remoteaddr.path()), self); + + // try to get and hold reference to the key-value pair, so other process can't alter it + let path_ref = NET_METADATA.domsock_paths.get(&remotepathbuf); + // if the entry doesn't exist, return an error. + if path_ref.is_none() { + return syscall_error(Errno::ENOENT, "connect", "not valid unix domain path"); + } + + let (pipe1, pipe2) = create_unix_sockpipes(); + + sockhandle.remoteaddr = Some(remoteaddr.clone()); + sockhandle.unix_info.as_mut().unwrap().sendpipe = Some(pipe1.clone()); + sockhandle.unix_info.as_mut().unwrap().receivepipe = Some(pipe2.clone()); + + let connvar = if sockfdobj.flags & O_NONBLOCK == 0 { + Some(interface::RustRfc::new(ConnCondVar::new())) + } else { + None + }; + + // receive_pipe and send_pipe need to be swapped here + // because the receive_pipe and send_pipe are opposites between the + // sender and receiver. Swapping here also means we do not need to swap in + // accept. + let entry = DomsockTableEntry { + sockaddr: sockhandle.localaddr.unwrap().clone(), + receive_pipe: Some(pipe1.clone()).unwrap(), + send_pipe: Some(pipe2.clone()).unwrap(), + cond_var: connvar.clone(), + }; + NET_METADATA + .domsock_accept_table + .insert(remotepathbuf, entry); + sockhandle.state = ConnState::CONNECTED; + if sockfdobj.flags & O_NONBLOCK == 0 { + connvar.unwrap().wait(); + } + return 0; + } + + fn connect_tcp_inet( + &self, + sockhandle: &mut SocketHandle, + sockfdobj: &mut SocketDesc, + remoteaddr: &interface::GenSockaddr, + ) -> i32 { + // TCP inet domain logic + //for TCP, actually create the internal socket object and connect it + let remoteclone = remoteaddr.clone(); + + if sockhandle.state != ConnState::NOTCONNECTED { + return syscall_error( + Errno::EISCONN, + "connect", + "The descriptor is already connected", + ); + } + + if let None = sockhandle.localaddr { + Self::force_innersocket(sockhandle); + + let localaddr = match Self::assign_new_addr( + &*sockhandle, + sockhandle.domain, + sockhandle.protocol & (1 << SO_REUSEPORT) != 0, + ) { + Ok(a) => a, + Err(e) => return e, + }; + let bindret = sockhandle.innersocket.as_ref().unwrap().bind(&localaddr); + if bindret < 0 { + sockhandle.localaddr = Some(localaddr); + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "connect", + "The libc call to bind within connect failed", + ); + } + Err(()) => { + panic!("Unknown errno value from socket bind within connect returned!") + } + }; + } + } + + let mut inprogress = false; + let connectret = sockhandle + .innersocket + .as_ref() + .unwrap() + .connect(&remoteclone); + if connectret < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + if i == Errno::EINPROGRESS { + inprogress = true; + } else { + return syscall_error(i, "connect", "The libc call to connect failed!"); + }; + } + Err(()) => panic!("Unknown errno value from socket connect returned!"), + }; + } + + sockhandle.state = ConnState::CONNECTED; + sockhandle.remoteaddr = Some(remoteaddr.clone()); + sockhandle.errno = 0; + // set the rawfd for select + sockfdobj.rawfd = sockhandle.innersocket.as_ref().unwrap().raw_sys_fd; + if inprogress { + sockhandle.state = ConnState::INPROGRESS; + return syscall_error( + Errno::EINPROGRESS, + "connect", + "The libc call to connect is in progress.", + ); + } else { + return 0; + } + } + + fn mksockhandle( + domain: i32, + socktype: i32, + protocol: i32, + conn: ConnState, + socket_options: i32, + ) -> SocketHandle { + SocketHandle { + innersocket: None, + socket_options: socket_options, + tcp_options: 0, + state: conn, + protocol: protocol, + domain: domain, + last_peek: interface::RustDeque::new(), + localaddr: None, + remoteaddr: None, + unix_info: None, + socktype: socktype, + sndbuf: 131070, //buffersize, which is only used by getsockopt + rcvbuf: 262140, //buffersize, which is only used by getsockopt + errno: 0, + } + } + + pub fn sendto_syscall( + &self, + fd: i32, + buf: *const u8, + buflen: usize, + flags: i32, + dest_addr: &interface::GenSockaddr, + ) -> i32 { + //if ip and port are not specified, shunt off to send + if dest_addr.port() == 0 && dest_addr.addr().is_unspecified() { + return self.send_syscall(fd, buf, buflen, flags); + } + + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + Socket(ref mut sockfdobj) => { + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + + // check if this is a domain socket + if sockhandle.domain == AF_UNIX { + return syscall_error( + Errno::EISCONN, + "sendto", + "The descriptor is connection-oriented", + ); + } + + if dest_addr.get_family() != sockhandle.domain as u16 { + return syscall_error( + Errno::EINVAL, + "sendto", + "An address with an invalid family for the given domain was specified", + ); + } + if (flags & !MSG_NOSIGNAL) != 0 { + return syscall_error( + Errno::EOPNOTSUPP, + "sendto", + "The flags are not understood!", + ); + } + + if sockhandle.state != ConnState::NOTCONNECTED { + return syscall_error( + Errno::EISCONN, + "sendto", + "The descriptor is connected", + ); + } + + match sockhandle.protocol { + //Sendto doesn't make sense for the TCP protocol, it's connection oriented + IPPROTO_TCP => { + return syscall_error( + Errno::EISCONN, + "sendto", + "The descriptor is connection-oriented", + ); + } + + IPPROTO_UDP => { + let tmpdest = *dest_addr; + let ibindret = + self._implicit_bind(&mut *sockhandle, tmpdest.get_family() as i32); + if ibindret < 0 { + return ibindret; + } + + //unwrap ok because we implicit_bind_right before + let sockret = sockhandle.innersocket.as_ref().unwrap().sendto( + buf, + buflen, + Some(dest_addr), + ); + + //we don't mind if this fails for now and we will just get the error + //from calling sendto + if sockret < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "sendto", + "The libc call to sendto failed!", + ); + } + Err(()) => { + panic!("Unknown errno value from socket sendto returned!") + } + }; + } else { + return sockret; + } + } + + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "sendto", + "Unkown protocol in sendto", + ); + } + } + } + + _ => { + return syscall_error( + Errno::ENOTSOCK, + "sendto", + "file descriptor refers to something other than a socket", + ); + } + } + } else { + return syscall_error(Errno::EBADF, "sendto", "invalid file descriptor"); + } + } + + pub fn send_syscall(&self, fd: i32, buf: *const u8, buflen: usize, flags: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + Socket(ref mut sockfdobj) => { + let sock_tmp = sockfdobj.handle.clone(); + let sockhandle = sock_tmp.write(); + + if (flags & !MSG_NOSIGNAL) != 0 { + return syscall_error( + Errno::EOPNOTSUPP, + "send", + "The flags are not understood!", + ); + } + + // check if this is a domain socket + let socket_type = sockhandle.domain; + match socket_type { + AF_UNIX => { + match sockhandle.protocol { + IPPROTO_TCP => { + if sockhandle.state != ConnState::CONNECTED { + return syscall_error( + Errno::ENOTCONN, + "send", + "The descriptor is not connected", + ); + } + + // get the socket pipe, write to it, and return bytes written + if let Some(sockinfo) = &sockhandle.unix_info { + let mut nonblocking = false; + if sockfdobj.flags & O_NONBLOCK != 0 { + nonblocking = true; + } + let retval = match sockinfo.sendpipe.as_ref() { + Some(sendpipe) => { + sendpipe.write_to_pipe(buf, buflen, nonblocking) + as i32 + } + None => { + return syscall_error(Errno::EAGAIN, "write", "there is no data available right now, try again later"); + } + }; + if retval < 0 { + return syscall_error(Errno::EAGAIN, "write", "there is no data available right now, try again later"); + } else { + return retval; + } + } + + return syscall_error( + Errno::EINPROGRESS, + "connect", + "The libc call to connect failed!", + ); + } + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "send", + "Unkown protocol in send", + ); + } + } + } + // for inet + AF_INET | AF_INET6 => match sockhandle.protocol { + IPPROTO_TCP => { + if (sockhandle.state != ConnState::CONNECTED) + && (sockhandle.state != ConnState::CONNWRONLY) + { + return syscall_error( + Errno::ENOTCONN, + "send", + "The descriptor is not connected", + ); + } + + //because socket must be connected it must have an inner socket + let retval = sockhandle + .innersocket + .as_ref() + .unwrap() + .sendto(buf, buflen, None); + if retval < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "send", + "The libc call to sendto failed!", + ); + } + Err(()) => panic!( + "Unknown errno value from socket sendto returned!" + ), + }; + } else { + return retval; + } + } + + IPPROTO_UDP => { + let remoteaddr = match &sockhandle.remoteaddr { + Some(x) => x.clone(), + None => { + return syscall_error( + Errno::ENOTCONN, + "send", + "The descriptor is not connected", + ); + } + }; + drop(unlocked_fd); + drop(sockhandle); + //send from a udp socket is just shunted off to sendto with the remote address set + return self.sendto_syscall(fd, buf, buflen, flags, &remoteaddr); + } + + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "send", + "Unkown protocol in send", + ); + } + }, + _ => { + return syscall_error( + Errno::EINVAL, + "connect", + "Unsupported domain provided", + ) + } + } + } + _ => { + return syscall_error( + Errno::ENOTSOCK, + "send", + "file descriptor refers to something other than a socket", + ); + } + } + } else { + return syscall_error(Errno::EBADF, "send", "invalid file descriptor"); + } + } + + fn recv_common_inner( + &self, + filedesc_enum: &mut FileDescriptor, + buf: *mut u8, + buflen: usize, + flags: i32, + addr: &mut Option<&mut interface::GenSockaddr>, + ) -> i32 { + match &mut *filedesc_enum { + Socket(ref mut sockfdobj) => { + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + match sockhandle.protocol { + IPPROTO_TCP => { + return self.recv_common_inner_tcp( + &mut sockhandle, + sockfdobj, + buf, + buflen, + flags, + addr, + ) + } + IPPROTO_UDP => { + return self.recv_common_inner_udp( + &mut sockhandle, + sockfdobj, + buf, + buflen, + addr, + ) + } + + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "recvfrom", + "Unkown protocol in recvfrom", + ); + } + } + } + _ => { + return syscall_error( + Errno::ENOTSOCK, + "recvfrom", + "file descriptor refers to something other than a socket", + ); + } + } + } + + fn recv_common_inner_tcp( + &self, + sockhandle: &mut interface::RustLockWriteGuard, + sockfdobj: &mut SocketDesc, + buf: *mut u8, + buflen: usize, + flags: i32, + addr: &mut Option<&mut interface::GenSockaddr>, + ) -> i32 { + // maybe select reported a INPROGRESS tcp socket as readable, so re-check the state here + if sockhandle.state == ConnState::INPROGRESS + && sockhandle + .innersocket + .as_ref() + .unwrap() + .check_rawconnection() + { + sockhandle.state = ConnState::CONNECTED; + } + + if (sockhandle.state != ConnState::CONNECTED) && (sockhandle.state != ConnState::CONNRDONLY) + { + return syscall_error( + Errno::ENOTCONN, + "recvfrom", + "The descriptor is not connected", + ); + } + + let mut newbuflen = buflen; + let mut newbufptr = buf; + + //if we have peeked some data before, fill our buffer with that data before moving on + if !sockhandle.last_peek.is_empty() { + let bytecount = interface::rust_min(sockhandle.last_peek.len(), newbuflen); + interface::copy_fromrustdeque_sized(buf, bytecount, &sockhandle.last_peek); + newbuflen -= bytecount; + newbufptr = newbufptr.wrapping_add(bytecount); + + //if we're not still peeking data, consume the data we peeked from our peek buffer + //and if the bytecount is more than the length of the peeked data, then we remove the entire + //buffer + if flags & MSG_PEEK == 0 { + let len = sockhandle.last_peek.len(); + sockhandle + .last_peek + .drain(..(if bytecount > len { len } else { bytecount })); + } + + if newbuflen == 0 { + //if we've filled all of the buffer with peeked data, return + return bytecount as i32; + } + } + + let bufleft = newbufptr; + let buflenleft = newbuflen; + let mut retval; + + if sockhandle.domain == AF_UNIX { + // get the remote socket pipe, read from it, and return bytes read + let mut nonblocking = false; + if sockfdobj.flags & O_NONBLOCK != 0 { + nonblocking = true; + } + loop { + let sockinfo = &sockhandle.unix_info.as_ref().unwrap(); + let receivepipe = sockinfo.receivepipe.as_ref().unwrap(); + retval = receivepipe.read_from_pipe(bufleft, buflenleft, nonblocking) as i32; + if retval < 0 { + //If we have already read from a peek but have failed to read more, exit! + if buflen != buflenleft { + return (buflen - buflenleft) as i32; + } + if sockfdobj.flags & O_NONBLOCK == 0 && retval == -(Errno::EAGAIN as i32) { + // with blocking sockets, we return EAGAIN here to check for cancellation, then return to reading + if self + .cancelstatus + .load(interface::RustAtomicOrdering::Relaxed) + { + // if the cancel status is set in the cage, we trap around a cancel point + // until the individual thread is signaled to cancel itself + loop { + interface::cancelpoint(self.cageid) + } + } + // in order to prevent deadlock + interface::RustLockWriteGuard::::bump(sockhandle); + continue; + } else { + //if not EAGAIN, return the error + return retval; + } + } + break; + } + } else { + loop { + // we loop here so we can cancel blocking recvs + //socket must be connected so unwrap ok + if sockfdobj.flags & O_NONBLOCK != 0 { + retval = sockhandle + .innersocket + .as_ref() + .unwrap() + .recvfrom_nonblocking(bufleft, buflenleft, addr); + } else { + retval = sockhandle + .innersocket + .as_ref() + .unwrap() + .recvfrom(bufleft, buflenleft, addr); + } + + if retval < 0 { + //If we have already read from a peek but have failed to read more, exit! + if buflen != buflenleft { + return (buflen - buflenleft) as i32; + } + + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + //We have the recieve timeout set to every one second, so + //if our blocking socket ever returns EAGAIN, it must be + //the case that this recv timeout was exceeded, and we + //should thus not treat this as a failure in our emulated + //socket; see comment in Socket::new in interface/comm.rs + if sockfdobj.flags & O_NONBLOCK == 0 && i == Errno::EAGAIN { + if self + .cancelstatus + .load(interface::RustAtomicOrdering::Relaxed) + { + // if the cancel status is set in the cage, we trap around a cancel point + // until the individual thread is signaled to cancel itself + loop { + interface::cancelpoint(self.cageid); + } + } + interface::RustLockWriteGuard::::bump(sockhandle); + continue; // EAGAIN, try again + } + + return syscall_error( + i, + "recvfrom", + "Internal call to recvfrom failed", + ); + } + Err(()) => panic!("Unknown errno value from socket recvfrom returned!"), + }; + } + break; // we're okay to move on + } + } + let totalbyteswritten = (buflen - buflenleft) as i32 + retval; + + if flags & MSG_PEEK != 0 { + //extend from the point after we read our previously peeked bytes + interface::extend_fromptr_sized(newbufptr, retval as usize, &mut sockhandle.last_peek); + } + + return totalbyteswritten; + } + + fn recv_common_inner_udp( + &self, + sockhandle: &mut interface::RustLockWriteGuard, + sockfdobj: &mut SocketDesc, + buf: *mut u8, + buflen: usize, + addr: &mut Option<&mut interface::GenSockaddr>, + ) -> i32 { + let binddomain = if let Some(baddr) = addr { + baddr.get_family() as i32 + } else { + AF_INET + }; + + let ibindret = self._implicit_bind(&mut *sockhandle, binddomain); + if ibindret < 0 { + return ibindret; + } + + loop { + // loop for blocking sockets + //if the remoteaddr is set and addr is not, use remoteaddr + //unwrap is ok because of implicit bind + let retval = if let (None, Some(ref mut remoteaddr)) = (&addr, sockhandle.remoteaddr) { + sockhandle.innersocket.as_ref().unwrap().recvfrom( + buf, + buflen, + &mut Some(remoteaddr), + ) + } else { + sockhandle + .innersocket + .as_ref() + .unwrap() + .recvfrom(buf, buflen, addr) + }; + + if retval < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + if sockfdobj.flags & O_NONBLOCK == 0 && i == Errno::EAGAIN { + if self + .cancelstatus + .load(interface::RustAtomicOrdering::Relaxed) + { + // if the cancel status is set in the cage, we trap around a cancel point + // until the individual thread is signaled to cancel itself + loop { + interface::cancelpoint(self.cageid); + } + } + interface::RustLockWriteGuard::::bump(sockhandle); + continue; //received EAGAIN on blocking socket, try again + } + return syscall_error(i, "recvfrom", "Internal call to recvfrom failed"); + } + Err(()) => panic!("Unknown errno value from socket recvfrom returned!"), + }; + } else { + return retval; // we can proceed + } + } + } + + pub fn recv_common( + &self, + fd: i32, + buf: *mut u8, + buflen: usize, + flags: i32, + addr: &mut Option<&mut interface::GenSockaddr>, + ) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(ref mut filedesc_enum) = &mut *unlocked_fd { + return self.recv_common_inner(filedesc_enum, buf, buflen, flags, addr); + } else { + return syscall_error(Errno::EBADF, "recvfrom", "invalid file descriptor"); + } + } + + pub fn recvfrom_syscall( + &self, + fd: i32, + buf: *mut u8, + buflen: usize, + flags: i32, + addr: &mut Option<&mut interface::GenSockaddr>, + ) -> i32 { + return self.recv_common(fd, buf, buflen, flags, addr); + } + + pub fn recv_syscall(&self, fd: i32, buf: *mut u8, buflen: usize, flags: i32) -> i32 { + return self.recv_common(fd, buf, buflen, flags, &mut None); + } + + //we currently ignore backlog + pub fn listen_syscall(&self, fd: i32, _backlog: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + match filedesc_enum { + Socket(ref mut sockfdobj) => { + //get or create the socket and bind it before listening + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + + match sockhandle.state { + ConnState::LISTEN => { + return 0; //Already done! + } + + ConnState::CONNECTED + | ConnState::CONNRDONLY + | ConnState::CONNWRONLY + | ConnState::INPROGRESS => { + return syscall_error( + Errno::EOPNOTSUPP, + "listen", + "We don't support closing a prior socket connection on listen", + ); + } + + ConnState::NOTCONNECTED => { + if sockhandle.protocol != IPPROTO_TCP { + return syscall_error( + Errno::EOPNOTSUPP, + "listen", + "This protocol doesn't support listening", + ); + } + + // simple if it's a domain socket + if sockhandle.domain == AF_UNIX { + sockhandle.state = ConnState::LISTEN; + return 0; + } + + if sockhandle.localaddr.is_none() { + let shd = sockhandle.domain as i32; + let ibindret = self._implicit_bind(&mut *sockhandle, shd); + if ibindret < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => {return syscall_error(i, "listen", "The libc call to bind within listen failed");}, + Err(()) => panic!("Unknown errno value from socket bind within listen returned!"), + }; + } + } + + let ladr = sockhandle.localaddr.unwrap().clone(); //must have been populated by implicit bind + let porttuple = mux_port( + ladr.addr().clone(), + ladr.port(), + sockhandle.domain, + TCPPORT, + ); + + NET_METADATA.listening_port_set.insert(porttuple.clone()); + sockhandle.state = ConnState::LISTEN; + + let listenret = sockhandle.innersocket.as_ref().unwrap().listen(5); //default backlog in repy for whatever reason, we replicate it + if listenret < 0 { + let lr = match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => syscall_error( + i, + "listen", + "The libc call to listen failed!", + ), + Err(()) => { + panic!("Unknown errno value from socket listen returned!") + } + }; + NET_METADATA.listening_port_set.remove(&mux_port( + ladr.addr().clone(), + ladr.port(), + sockhandle.domain, + TCPPORT, + )); + sockhandle.state = ConnState::NOTCONNECTED; + return lr; + }; + + //set rawfd for select + sockfdobj.rawfd = sockhandle.innersocket.as_ref().unwrap().raw_sys_fd; + + if !NET_METADATA.pending_conn_table.contains_key(&porttuple) { + NET_METADATA + .pending_conn_table + .insert(porttuple.clone(), vec![]); + } + + return 0; + } + } + } + + _ => { + return syscall_error( + Errno::ENOTSOCK, + "listen", + "file descriptor refers to something other than a socket", + ); + } + } + } else { + return syscall_error(Errno::EBADF, "listen", "invalid file descriptor"); + } + } + + pub fn netshutdown_syscall(&self, fd: i32, how: i32) -> i32 { + match how { + SHUT_RDWR | SHUT_RD | SHUT_WR => { + return Self::_cleanup_socket(self, fd, how); + } + _ => { + //See http://linux.die.net/man/2/shutdown for nuance to this error + return syscall_error( + Errno::EINVAL, + "netshutdown", + "the shutdown how argument passed is not supported", + ); + } + } + } + + pub fn _cleanup_socket_inner_helper( + sockhandle: &mut SocketHandle, + how: i32, + shutdown: bool, + ) -> i32 { + // we need to do a bunch of actual socket cleanup for INET sockets + if sockhandle.domain != AF_UNIX { + let mut releaseflag = false; + if let Some(ref sobj) = sockhandle.innersocket { + if shutdown { + let shutresult = sobj.shutdown(how); + + if shutresult < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "shutdown", + "The libc call to setsockopt failed!", + ); + } + Err(()) => panic!("Unknown errno value from setsockopt returned!"), + }; + } + + match how { + SHUT_RD => { + if sockhandle.state == ConnState::CONNRDONLY { + releaseflag = true; + } + } + SHUT_WR => { + if sockhandle.state == ConnState::CONNWRONLY { + releaseflag = true; + } + } + SHUT_RDWR => { + releaseflag = true; + } + _ => { + //See http://linux.die.net/man/2/shutdown for nuance to this error + return syscall_error( + Errno::EINVAL, + "netshutdown", + "the shutdown how argument passed is not supported", + ); + } + } + } else { + //Reaching this means that the socket is closed. Removing the sockobj + //indicates that the sockobj will drop, and therefore close + releaseflag = true; + sockhandle.innersocket = None; + } + } + + if releaseflag { + if let Some(localaddr) = sockhandle.localaddr.as_ref().clone() { + //move to end + let release_ret_val = NET_METADATA._release_localport( + localaddr.addr(), + localaddr.port(), + sockhandle.protocol, + sockhandle.domain, + ); + sockhandle.localaddr = None; + if let Err(e) = release_ret_val { + return e; + } + } + } + } + + // now change the connections for all socket types + match how { + SHUT_RD => { + if sockhandle.state == ConnState::CONNWRONLY { + sockhandle.state = ConnState::NOTCONNECTED; + } else { + sockhandle.state = ConnState::CONNWRONLY; + } + } + SHUT_WR => { + if sockhandle.state == ConnState::CONNRDONLY { + sockhandle.state = ConnState::NOTCONNECTED; + } else { + sockhandle.state = ConnState::CONNRDONLY; + } + } + SHUT_RDWR => { + sockhandle.state = ConnState::NOTCONNECTED; + } + _ => { + //See http://linux.die.net/man/2/shutdown for nuance to this error + return syscall_error( + Errno::EINVAL, + "netshutdown", + "the shutdown how argument passed is not supported", + ); + } + } + + return 0; + } + + pub fn _cleanup_socket_inner( + &self, + filedesc: &mut FileDescriptor, + how: i32, + shutdown: bool, + ) -> i32 { + if let Socket(sockfdobj) = filedesc { + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + + Self::_cleanup_socket_inner_helper(&mut *sockhandle, how, shutdown) + } else { + syscall_error( + Errno::ENOTSOCK, + "cleanup socket", + "file descriptor is not a socket", + ) + } + } + + pub fn _cleanup_socket(&self, fd: i32, how: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(ref mut filedesc_enum) = &mut *unlocked_fd { + let inner_result = self._cleanup_socket_inner(filedesc_enum, how, true); + if inner_result < 0 { + return inner_result; + } + + if how == SHUT_RDWR { + let _discarded_fd = unlocked_fd.take(); + } + } else { + return syscall_error(Errno::EBADF, "cleanup socket", "invalid file descriptor"); + } + + return 0; + } + + pub fn accept_syscall(&self, fd: i32, addr: &mut interface::GenSockaddr) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + let (newfd, guardopt) = self.get_next_fd(None); + if newfd < 0 { + return fd; + } + let newfdoption: &mut Option = &mut *guardopt.unwrap(); + + match filedesc_enum { + Socket(ref mut sockfdobj) => { + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.read(); + + // check if domain socket + match sockhandle.domain { + AF_UNIX => { + return self.accept_unix( + &mut sockhandle, + sockfdobj, + newfd, + newfdoption, + addr, + ) + } + AF_INET | AF_INET6 => { + return self.accept_inet( + &mut sockhandle, + sockfdobj, + newfd, + newfdoption, + addr, + ) + } + _ => { + return syscall_error( + Errno::EINVAL, + "accept", + "Unsupported domain provided", + ) + } + } + } + _ => { + return syscall_error( + Errno::ENOTSOCK, + "listen", + "file descriptor refers to something other than a socket", + ); + } + } + } else { + return syscall_error(Errno::EBADF, "listen", "invalid file descriptor"); + } + } + + fn accept_unix( + &self, + sockhandle: &mut interface::RustLockReadGuard, + sockfdobj: &mut SocketDesc, + newfd: i32, + newfdoption: &mut Option, + addr: &mut interface::GenSockaddr, + ) -> i32 { + match sockhandle.protocol { + IPPROTO_UDP => { + return syscall_error( + Errno::EOPNOTSUPP, + "accept", + "Protocol does not support listening", + ); + } + IPPROTO_TCP => { + if sockhandle.state != ConnState::LISTEN { + return syscall_error( + Errno::EINVAL, + "accept", + "Socket must be listening before accept is called", + ); + } + let newsockfd = self._socket_initializer( + sockhandle.domain, + sockhandle.socktype, + sockhandle.protocol, + sockfdobj.flags & O_NONBLOCK != 0, + sockfdobj.flags & O_CLOEXEC != 0, + ConnState::CONNECTED, + ); + + let remote_addr: interface::GenSockaddr; + let sendpipenumber; + let receivepipenumber; + + loop { + let localpathbuf = + normpath(convpath(sockhandle.localaddr.unwrap().path()), self); + let dsconnobj = NET_METADATA.domsock_accept_table.get(&localpathbuf); + + if let Some(ds) = dsconnobj { + // we loop here to accept the connection + // if we get a connection object from the accept table, we complete the connection and set up the address and pipes + // if theres no object, we retry, except in the case of non-blocking accept where we return EAGAIN + if let Some(connvar) = ds.get_cond_var() { + if !connvar.broadcast() { + drop(ds); + continue; + } + } + let addr = ds.get_sockaddr().clone(); + remote_addr = addr.clone(); + receivepipenumber = ds.get_receive_pipe().clone(); + sendpipenumber = ds.get_send_pipe().clone(); + drop(ds); + NET_METADATA.domsock_accept_table.remove(&localpathbuf); + break; + } else { + if 0 != (sockfdobj.flags & O_NONBLOCK) { + // if non block return EAGAIN + return syscall_error( + Errno::EAGAIN, + "accept", + "host system accept call failed", + ); + } + } + } + + let newsock_tmp = newsockfd.handle.clone(); + let mut newsockhandle = newsock_tmp.write(); + + let pathclone = normpath(convpath(remote_addr.path()), self); + if let Some(inodenum) = metawalk(pathclone.as_path()) { + newsockhandle.unix_info = Some(UnixSocketInfo { + inode: inodenum.clone(), + mode: sockhandle.unix_info.as_ref().unwrap().mode, + sendpipe: Some(sendpipenumber.clone()), + receivepipe: Some(receivepipenumber.clone()), + }); + if let Inode::Socket(ref mut sock) = + *(FS_METADATA.inodetable.get_mut(&inodenum).unwrap()) + { + sock.refcount += 1; + } + }; + + newsockhandle.localaddr = Some(sockhandle.localaddr.unwrap().clone()); + newsockhandle.remoteaddr = Some(remote_addr.clone()); + newsockhandle.state = ConnState::CONNECTED; + + let _insertval = newfdoption.insert(Socket(newsockfd)); + *addr = remote_addr; //populate addr with what address it connected to + + return newfd; + } + _ => { + return syscall_error(Errno::EOPNOTSUPP, "accept", "Unkown protocol in accept"); + } + } + } + + fn accept_inet( + &self, + sockhandle: &mut interface::RustLockReadGuard, + sockfdobj: &mut SocketDesc, + newfd: i32, + newfdoption: &mut Option, + addr: &mut interface::GenSockaddr, + ) -> i32 { + match sockhandle.protocol { + IPPROTO_UDP => { + return syscall_error( + Errno::EOPNOTSUPP, + "accept", + "Protocol does not support listening", + ); + } + IPPROTO_TCP => { + if sockhandle.state != ConnState::LISTEN { + return syscall_error( + Errno::EINVAL, + "accept", + "Socket must be listening before accept is called", + ); + } + let mut newsockfd = self._socket_initializer( + sockhandle.domain, + sockhandle.socktype, + sockhandle.protocol, + sockfdobj.flags & O_NONBLOCK != 0, + sockfdobj.flags & O_CLOEXEC != 0, + ConnState::CONNECTED, + ); + + loop { + // we loop here so we can cancel blocking accept, see comments below and in Socket::new in interface/comm.rs + + // if we got a pending connection in select/poll/whatever, return that here instead + let ladr = sockhandle.localaddr.unwrap().clone(); //must have been populated by implicit bind + let porttuple = + mux_port(ladr.addr().clone(), ladr.port(), sockhandle.domain, TCPPORT); + + let mut pendingvec = + NET_METADATA.pending_conn_table.get_mut(&porttuple).unwrap(); + let pendingoption = pendingvec.pop(); + let (acceptedresult, remote_addr) = match pendingoption { + Some(pendingtup) => pendingtup, + None => { + //unwrap ok because listening + if 0 == (sockfdobj.flags & O_NONBLOCK) { + match sockhandle.domain { + PF_INET => { + sockhandle.innersocket.as_ref().unwrap().accept(true) + } + PF_INET6 => { + sockhandle.innersocket.as_ref().unwrap().accept(false) + } + _ => panic!("Unknown domain in accepting socket"), + } + } else { + match sockhandle.domain { + PF_INET => sockhandle + .innersocket + .as_ref() + .unwrap() + .nonblock_accept(true), + PF_INET6 => sockhandle + .innersocket + .as_ref() + .unwrap() + .nonblock_accept(false), + _ => panic!("Unknown domain in accepting socket"), + } + } + } + }; + + if let Err(_) = acceptedresult { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + //We have the socket timeout set to every one second, so + //if our blocking socket ever returns EAGAIN, it must be + //the case that this recv timeout was exceeded, and we + //should thus not treat this as a failure in our emulated + //socket; see comment in Socket::new in interface/comm.rs + if sockfdobj.flags & O_NONBLOCK == 0 && i == Errno::EAGAIN { + if self + .cancelstatus + .load(interface::RustAtomicOrdering::Relaxed) + { + // if the cancel status is set in the cage, we trap around a cancel point + // until the individual thread is signaled to cancel itself + loop { + interface::cancelpoint(self.cageid); + } + } + continue; // EAGAIN, try again + } + + return syscall_error( + i, + "accept", + "Internal call to accept failed", + ); + } + Err(()) => panic!("Unknown errno value from socket accept returned!"), + }; + } + + // if we get here we have an accepted socket + let acceptedsock = acceptedresult.unwrap(); + + let mut newaddr = sockhandle.localaddr.unwrap().clone(); + let newport = match NET_METADATA._reserve_localport( + newaddr.addr(), + 0, + sockhandle.protocol, + sockhandle.domain, + false, + ) { + Ok(portnum) => portnum, + Err(errnum) => { + return errnum; + } + }; + newaddr.set_port(newport); + + let newsock_tmp = newsockfd.handle.clone(); + let mut newsockhandle = newsock_tmp.write(); + + newsockhandle.localaddr = Some(newaddr); + newsockhandle.remoteaddr = Some(remote_addr.clone()); + + //create socket object for new connected socket + newsockhandle.innersocket = Some(acceptedsock); + // set lock-free rawfd for select + newsockfd.rawfd = newsockhandle.innersocket.as_ref().unwrap().raw_sys_fd; + + let _insertval = newfdoption.insert(Socket(newsockfd)); + *addr = remote_addr; //populate addr with what address it connected to + + return newfd; + } + } + _ => { + return syscall_error(Errno::EOPNOTSUPP, "accept", "Unkown protocol in accept"); + } + } + } + + pub fn select_syscall( + &self, + nfds: i32, + readfds: Option<&mut interface::FdSet>, + writefds: Option<&mut interface::FdSet>, + exceptfds: Option<&mut interface::FdSet>, + timeout: Option, + ) -> i32 { + if nfds < STARTINGFD || nfds >= FD_SET_MAX_FD { + return syscall_error(Errno::EINVAL, "select", "Number of FDs is wrong"); + } + + let start_time = interface::starttimer(); + + let end_time = match timeout { + Some(time) => time, + None => interface::RustDuration::MAX, + }; + + let mut retval = 0; + // in the loop below, we always read from original fd_sets, but make updates to the new copies + let new_readfds = &mut interface::FdSet::new(); + let new_writefds = &mut interface::FdSet::new(); + loop { + //we must block manually + // 1. iterate thru readfds + if let Some(readfds_ref) = readfds.as_ref() { + let res = self.select_readfds(nfds, readfds_ref, new_readfds, &mut retval); + if res != 0 { + return res; + } + } + + // 2. iterate thru writefds + if let Some(writefds_ref) = writefds.as_ref() { + let res = self.select_writefds(nfds, writefds_ref, new_writefds, &mut retval); + if res != 0 { + return res; + } + } + + // 3. iterate thru exceptfds + // currently we don't really do select on execptfds, we just check if those fds are valid + if let Some(exceptfds_ref) = exceptfds.as_ref() { + for fd in 0..nfds { + // find the bit and see if it's on + if !exceptfds_ref.is_set(fd) { + continue; + } + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if unlocked_fd.is_none() { + return syscall_error(Errno::EBADF, "select", "invalid file descriptor"); + } + } + } + + if retval != 0 || interface::readtimer(start_time) > end_time { + break; + } else { + // at this point lets check if we got a signal before sleeping + if interface::sigcheck() { + return syscall_error(Errno::EINTR, "select", "interrupted function call"); + } + interface::lind_yield(); + } + } + + // Now we copy our internal FdSet struct results back into the *mut libc::fd_set + if readfds.is_some() { + readfds.unwrap().copy_from(&new_readfds); + } + + if writefds.is_some() { + writefds.unwrap().copy_from(&new_writefds); + } + + return retval; + } + + fn select_readfds( + &self, + nfds: i32, + readfds: &interface::FdSet, + new_readfds: &mut interface::FdSet, + retval: &mut i32, + ) -> i32 { + // For INET: prepare the data structures for the kernel_select's use + let mut inet_info = SelectInetInfo::new(); + + for fd in 0..nfds { + // check if current i is in readfd + if !readfds.is_set(fd) { + continue; + } + + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + match filedesc_enum { + Socket(ref sockfdobj) => { + let mut newconnection = false; + match sockfdobj.domain { + AF_UNIX => { + let sock_tmp = sockfdobj.handle.clone(); + let sockhandle = sock_tmp.read(); + if sockhandle.state == ConnState::INPROGRESS { + let remotepathbuf = normpath( + convpath(sockhandle.remoteaddr.unwrap().path()), + self, + ); + let dsconnobj = + NET_METADATA.domsock_accept_table.get(&remotepathbuf); + if dsconnobj.is_none() { + newconnection = true; + } + } + + if sockhandle.state == ConnState::LISTEN { + let localpathbuf = normpath( + convpath(sockhandle.localaddr.unwrap().path()), + self, + ); + let dsconnobj = + NET_METADATA.domsock_accept_table.get(&localpathbuf); + if dsconnobj.is_some() { + // we have a connecting domain socket, return as readable to be accepted + new_readfds.set(fd); + *retval += 1; + } + } else if sockhandle.state == ConnState::CONNECTED || newconnection + { + let sockinfo = &sockhandle.unix_info.as_ref().unwrap(); + let receivepipe = sockinfo.receivepipe.as_ref().unwrap(); + if receivepipe.check_select_read() { + new_readfds.set(fd); + *retval += 1; + } + } + } + AF_INET | AF_INET6 => { + // here we simply record the inet fd into inet_fds and the tuple list for using kernel_select + if sockfdobj.rawfd < 0 { + continue; + } + + inet_info.kernel_fds.set(sockfdobj.rawfd); + inet_info.rawfd_lindfd_tuples.push((sockfdobj.rawfd, fd)); + if sockfdobj.rawfd > inet_info.highest_raw_fd { + inet_info.highest_raw_fd = sockfdobj.rawfd; + } + } + _ => { + return syscall_error( + Errno::EINVAL, + "select", + "Unsupported domain provided", + ) + } + } + + if newconnection { + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + sockhandle.state = ConnState::CONNECTED; + } + } + + //we don't support selecting streams + Stream(_) => { + continue; + } + + Pipe(pipefdobj) => { + if pipefdobj.pipe.check_select_read() { + new_readfds.set(fd); + *retval += 1; + } + } + + //these file reads never block + _ => { + new_readfds.set(fd); + *retval += 1; + } + } + } else { + return syscall_error(Errno::EBADF, "select", "invalid file descriptor"); + } + } + + // do the kernel_select for inet sockets + if !inet_info.kernel_fds.is_empty() { + let kernel_ret = update_readfds_from_kernel_select(new_readfds, &mut inet_info, retval); + // NOTE: we ignore the kernel_select error if some domsocks are ready + if kernel_ret < 0 && *retval <= 0 { + return kernel_ret; + } + } + + return 0; + } + + fn select_writefds( + &self, + nfds: i32, + writefds: &interface::FdSet, + new_writefds: &mut interface::FdSet, + retval: &mut i32, + ) -> i32 { + for fd in 0..nfds { + // check if current i is in writefds + if !writefds.is_set(fd) { + continue; + } + + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + match filedesc_enum { + Socket(ref sockfdobj) => { + // check if we've made an in progress connection first + let sock_tmp = sockfdobj.handle.clone(); + let sockhandle = sock_tmp.read(); + let mut newconnection = false; + match sockhandle.domain { + AF_UNIX => { + if sockhandle.state == ConnState::INPROGRESS { + let remotepathbuf = + convpath(sockhandle.remoteaddr.unwrap().path()); + let dsconnobj = + NET_METADATA.domsock_accept_table.get(&remotepathbuf); + if dsconnobj.is_none() { + newconnection = true; + } + } + } + AF_INET => { + if sockhandle.state == ConnState::INPROGRESS + && sockhandle + .innersocket + .as_ref() + .unwrap() + .check_rawconnection() + { + newconnection = true; + } + } + _ => { + return syscall_error(Errno::EINVAL, "select", "Unsupported domain") + } + } + + if newconnection { + let mut newconnhandle = sock_tmp.write(); + newconnhandle.state = ConnState::CONNECTED; + } + + //we always say sockets are writable? Even though this is not true + new_writefds.set(fd); + *retval += 1; + } + + //we always say streams are writable? + Stream(_) => { + new_writefds.set(fd); + *retval += 1; + } + + Pipe(pipefdobj) => { + if pipefdobj.pipe.check_select_write() { + new_writefds.set(fd); + *retval += 1; + } + } + + //these file writes never block + _ => { + new_writefds.set(fd); + *retval += 1; + } + } + } else { + return syscall_error(Errno::EBADF, "select", "invalid file descriptor"); + } + } + return 0; + } + + pub fn getsockopt_syscall(&self, fd: i32, level: i32, optname: i32, optval: &mut i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + if let Socket(ref mut sockfdobj) = filedesc_enum { + let optbit = 1 << optname; + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + match level { + SOL_UDP => { + return syscall_error( + Errno::EOPNOTSUPP, + "getsockopt", + "UDP is not supported for getsockopt", + ); + } + SOL_TCP => { + // Checking the tcp_options here + // Currently only support TCP_NODELAY option for SOL_TCP + if optname == TCP_NODELAY { + let optbit = 1 << optname; + if optbit & sockhandle.tcp_options == optbit { + *optval = 1; + } else { + *optval = 0; + } + return 0; + } + return syscall_error( + Errno::EOPNOTSUPP, + "getsockopt", + "TCP options not remembered by getsockopt", + ); + } + SOL_SOCKET => { + // checking the socket_options here + match optname { + //indicate whether we are accepting connections or not in the moment + SO_ACCEPTCONN => { + if sockhandle.state == ConnState::LISTEN { + *optval = 1; + } else { + *optval = 0; + } + } + //if the option is a stored binary option, just return it... + SO_LINGER | SO_KEEPALIVE | SO_SNDLOWAT | SO_RCVLOWAT | SO_REUSEPORT + | SO_REUSEADDR => { + if sockhandle.socket_options & optbit == optbit { + *optval = 1; + } else { + *optval = 0; + } + } + //handling the ignored buffer settings: + SO_SNDBUF => { + *optval = sockhandle.sndbuf; + } + SO_RCVBUF => { + *optval = sockhandle.rcvbuf; + } + //returning the type if asked + SO_TYPE => { + *optval = sockhandle.socktype; + } + //should always be true + SO_OOBINLINE => { + *optval = 1; + } + SO_ERROR => { + let tmp = sockhandle.errno; + sockhandle.errno = 0; + *optval = tmp; + } + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "getsockopt", + "unknown optname passed into syscall", + ); + } + } + } + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "getsockopt", + "unknown level passed into syscall", + ); + } + } + } else { + return syscall_error( + Errno::ENOTSOCK, + "getsockopt", + "the provided file descriptor is not a socket", + ); + } + } else { + return syscall_error( + Errno::EBADF, + "getsockopt", + "the provided file descriptor is invalid", + ); + } + return 0; + } + + pub fn setsockopt_syscall(&self, fd: i32, level: i32, optname: i32, optval: i32) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + if let Socket(ref mut sockfdobj) = filedesc_enum { + //checking that we recieved SOL_SOCKET + match level { + SOL_UDP => { + return syscall_error( + Errno::EOPNOTSUPP, + "setsockopt", + "UDP is not supported for getsockopt", + ); + } + SOL_TCP => { + // Here we check and set tcp_options + // Currently only support TCP_NODELAY for SOL_TCP + if optname == TCP_NODELAY { + let optbit = 1 << optname; + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + let mut newoptions = sockhandle.tcp_options; + //now let's set this if we were told to + if optval != 0 { + //optval should always be 1 or 0. + newoptions |= optbit; + } else { + newoptions &= !optbit; + } + + if newoptions != sockhandle.tcp_options { + if let Some(sock) = sockhandle.innersocket.as_ref() { + let sockret = sock.setsockopt(SOL_TCP, optname, optval); + if sockret < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "setsockopt", + "The libc call to setsockopt failed!", + ); + } + Err(()) => panic!( + "Unknown errno value from setsockopt returned!" + ), + }; + } + } + } + sockhandle.tcp_options = newoptions; + return 0; + } + return syscall_error( + Errno::EOPNOTSUPP, + "setsockopt", + "This TCP option is not remembered by setsockopt", + ); + } + SOL_SOCKET => { + // Here we check and set socket_options + let optbit = 1 << optname; + let sock_tmp = sockfdobj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + + match optname { + SO_ACCEPTCONN | SO_TYPE | SO_SNDLOWAT | SO_RCVLOWAT => { + let error_string = + format!("Cannot set option using setsockopt. {}", optname); + return syscall_error( + Errno::ENOPROTOOPT, + "setsockopt", + &error_string, + ); + } + SO_LINGER | SO_KEEPALIVE => { + if optval == 0 { + sockhandle.socket_options &= !optbit; + } else { + //optval should always be 1 or 0. + sockhandle.socket_options |= optbit; + } + + return 0; + } + + SO_REUSEPORT | SO_REUSEADDR => { + let mut newoptions = sockhandle.socket_options; + //now let's set this if we were told to + if optval != 0 { + //optval should always be 1 or 0. + newoptions |= optbit; + } else { + newoptions &= !optbit; + } + + if newoptions != sockhandle.socket_options { + if let Some(sock) = sockhandle.innersocket.as_ref() { + let sockret = sock.setsockopt(SOL_SOCKET, optname, optval); + if sockret < 0 { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "setsockopt", + "The libc call to setsockopt failed!", + ); + } + Err(()) => panic!( + "Unknown errno value from setsockopt returned!" + ), + }; + } + } + } + + sockhandle.socket_options = newoptions; + + return 0; + } + SO_SNDBUF => { + sockhandle.sndbuf = optval; + return 0; + } + SO_RCVBUF => { + sockhandle.rcvbuf = optval; + return 0; + } + //should always be one -- can only handle it being 1 + SO_OOBINLINE => { + if optval != 1 { + return syscall_error( + Errno::EOPNOTSUPP, + "getsockopt", + "does not support OOBINLINE being set to anything but 1", + ); + } + return 0; + } + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "getsockopt", + "unknown optname passed into syscall", + ); + } + } + } + _ => { + return syscall_error( + Errno::EOPNOTSUPP, + "getsockopt", + "unknown level passed into syscall", + ); + } + } + } else { + return syscall_error( + Errno::ENOTSOCK, + "getsockopt", + "the provided file descriptor is not a socket", + ); + } + } else { + return syscall_error( + Errno::EBADF, + "getsockopt", + "the provided file descriptor is invalid", + ); + } + } + + pub fn getpeername_syscall(&self, fd: i32, ret_addr: &mut interface::GenSockaddr) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + if let Socket(sockfdobj) = filedesc_enum { + //if the socket is not connected, then we should return an error + let sock_tmp = sockfdobj.handle.clone(); + let sockhandle = sock_tmp.read(); + if sockhandle.remoteaddr == None { + return syscall_error( + Errno::ENOTCONN, + "getpeername", + "the socket is not connected", + ); + } + *ret_addr = sockhandle.remoteaddr.unwrap(); + return 0; + } else { + return syscall_error( + Errno::ENOTSOCK, + "getpeername", + "the provided file is not a socket", + ); + } + } else { + return syscall_error( + Errno::EBADF, + "getpeername", + "the provided file descriptor is not valid", + ); + } + } + + pub fn getsockname_syscall(&self, fd: i32, ret_addr: &mut interface::GenSockaddr) -> i32 { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + if let Socket(sockfdobj) = filedesc_enum { + let sock_tmp = sockfdobj.handle.clone(); + let sockhandle = sock_tmp.read(); + if sockhandle.domain == AF_UNIX { + if sockhandle.localaddr == None { + let null_path: &[u8] = &[]; + *ret_addr = interface::GenSockaddr::Unix(interface::new_sockaddr_unix( + sockhandle.domain as u16, + null_path, + )); + return 0; + } + //if the socket is not none, then return the socket + *ret_addr = sockhandle.localaddr.unwrap(); + return 0; + } else { + if sockhandle.localaddr == None { + //sets the address to 0.0.0.0 if the address is not initialized yet + //setting the family as well based on the domain + let addr = match sockhandle.domain { + AF_INET => interface::GenIpaddr::V4(interface::V4Addr::default()), + AF_INET6 => interface::GenIpaddr::V6(interface::V6Addr::default()), + _ => { + unreachable!() + } + }; + ret_addr.set_addr(addr); + ret_addr.set_port(0); + ret_addr.set_family(sockhandle.domain as u16); + return 0; + } + *ret_addr = sockhandle.localaddr.unwrap(); + return 0; + } + } else { + return syscall_error( + Errno::ENOTSOCK, + "getsockname", + "the provided file is not a socket", + ); + } + } else { + return syscall_error( + Errno::EBADF, + "getsockname", + "the provided file descriptor is not valid", + ); + } + } + + //we only return the default host name because we do not allow for the user to change the host name right now + pub fn gethostname_syscall(&self, address_ptr: *mut u8, length: isize) -> i32 { + if length < 0 { + return syscall_error( + Errno::EINVAL, + "gethostname_syscall", + "provided length argument is invalid", + ); + } + + let mut bytes: Vec = DEFAULT_HOSTNAME.as_bytes().to_vec(); + bytes.push(0u8); //Adding a null terminator to the end of the string + let name_length = bytes.len(); + + let mut len = name_length; + if (length as usize) < len { + len = length as usize; + } + + interface::fill(address_ptr, len, &bytes); + + return 0; + } + + pub fn poll_syscall( + &self, + fds: &mut [PollStruct], + timeout: Option, + ) -> i32 { + //timeout is supposed to be in milliseconds + + let mut return_code: i32 = 0; + let start_time = interface::starttimer(); + + let end_time = match timeout { + Some(time) => time, + None => interface::RustDuration::MAX, + }; + + loop { + for structpoll in &mut *fds { + let fd = structpoll.fd; + let events = structpoll.events; + + // init FdSet structures + let reads = &mut interface::FdSet::new(); + let writes = &mut interface::FdSet::new(); + let errors = &mut interface::FdSet::new(); + + //read + if events & POLLIN > 0 { + reads.set(fd) + } + //write + if events & POLLOUT > 0 { + writes.set(fd) + } + //err + if events & POLLERR > 0 { + errors.set(fd) + } + + let mut mask: i16 = 0; + + //0 essentially sets the timeout to the max value allowed (which is almost always more than enough time) + // NOTE that the nfds argument is highest fd + 1 + let selectret = Self::select_syscall( + &self, + fd + 1, + Some(reads), + Some(writes), + Some(errors), + Some(interface::RustDuration::ZERO), + ); + if selectret > 0 { + mask += if !reads.is_empty() { POLLIN } else { 0 }; + mask += if !writes.is_empty() { POLLOUT } else { 0 }; + mask += if !errors.is_empty() { POLLERR } else { 0 }; + return_code += 1; + } else if selectret < 0 { + return selectret; + } + structpoll.revents = mask; + } + + if return_code != 0 || interface::readtimer(start_time) > end_time { + break; + } else { + if interface::sigcheck() { + return syscall_error(Errno::EINTR, "poll", "interrupted function call"); + } + interface::lind_yield(); + } + } + return return_code; + } + + pub fn _epoll_object_allocator(&self) -> i32 { + //seems to only be called in functions that don't have a filedesctable lock, so not passing the lock. + + let epollobjfd = Epoll(EpollDesc { + mode: 0000, + registered_fds: interface::RustHashMap::::new(), + advlock: interface::RustRfc::new(interface::AdvisoryLock::new()), + errno: 0, + flags: 0, + }); + //get a file descriptor + let (fd, guardopt) = self.get_next_fd(None); + if fd < 0 { + return fd; + } + let fdoption = &mut *guardopt.unwrap(); + let _insertval = fdoption.insert(epollobjfd); + + return fd; + } + + pub fn epoll_create_syscall(&self, size: i32) -> i32 { + if size <= 0 { + return syscall_error( + Errno::EINVAL, + "epoll create", + "provided size argument is invalid", + ); + } + return Self::_epoll_object_allocator(self); + } + + //this one can still be optimized + pub fn epoll_ctl_syscall(&self, epfd: i32, op: i32, fd: i32, event: &EpollEvent) -> i32 { + //making sure that the epfd is really an epoll fd + let checkedfd = self.get_filedescriptor(epfd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum_epollfd) = &mut *unlocked_fd { + if let Epoll(epollfdobj) = filedesc_enum_epollfd { + //check if the other fd is an epoll or not... + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + if let Epoll(_) = filedesc_enum { + return syscall_error( + Errno::EBADF, + "epoll ctl", + "provided fd is not a valid file descriptor", + ); + } + } else { + return syscall_error( + Errno::EBADF, + "epoll ctl", + "provided fd is not a valid file descriptor", + ); + } + + //now that we know that the types are all good... + match op { + EPOLL_CTL_DEL => { + //since remove returns the value at the key and the values will always be EpollEvents, + //I am using this to optimize the code + epollfdobj.registered_fds.remove(&fd).unwrap().1; + } + EPOLL_CTL_MOD => { + //check if the fd that we are modifying exists or not + if !epollfdobj.registered_fds.contains_key(&fd) { + return syscall_error( + Errno::ENOENT, + "epoll ctl", + "fd is not registered with this epfd", + ); + } + //if the fd already exists, insert overwrites the prev entry + epollfdobj.registered_fds.insert( + fd, + EpollEvent { + events: event.events, + fd: event.fd, + }, + ); + } + EPOLL_CTL_ADD => { + if epollfdobj.registered_fds.contains_key(&fd) { + return syscall_error( + Errno::EEXIST, + "epoll ctl", + "fd is already registered", + ); + } + epollfdobj.registered_fds.insert( + fd, + EpollEvent { + events: event.events, + fd: event.fd, + }, + ); + } + _ => { + return syscall_error(Errno::EINVAL, "epoll ctl", "provided op is invalid"); + } + } + } else { + return syscall_error( + Errno::EBADF, + "epoll ctl", + "provided fd is not a valid file descriptor", + ); + } + } else { + return syscall_error( + Errno::EBADF, + "epoll ctl", + "provided epoll fd is not a valid epoll file descriptor", + ); + } + return 0; + } + + pub fn epoll_wait_syscall( + &self, + epfd: i32, + events: &mut [EpollEvent], + maxevents: i32, + timeout: Option, + ) -> i32 { + let checkedfd = self.get_filedescriptor(epfd).unwrap(); + let mut unlocked_fd = checkedfd.write(); + if let Some(filedesc_enum) = &mut *unlocked_fd { + if let Epoll(epollfdobj) = filedesc_enum { + if maxevents < 0 { + return syscall_error( + Errno::EINVAL, + "epoll wait", + "max events argument is not a positive number", + ); + } + let mut poll_fds_vec: Vec = vec![]; + let mut rm_fds_vec: Vec = vec![]; + let mut num_events: usize = 0; + for set in epollfdobj.registered_fds.iter() { + let (&key, &value) = set.pair(); + + // check if any of the registered fds were closed, add them to remove list + let checkedregfd = self.get_filedescriptor(key).unwrap(); + let unlocked_regfd = checkedregfd.read(); + if unlocked_regfd.is_none() { + rm_fds_vec.push(key); + continue; + } + + let events = value.events; + let mut structpoll = PollStruct { + fd: key, + events: 0, + revents: 0, + }; + if events & EPOLLIN as u32 > 0 { + structpoll.events |= POLLIN; + } + if events & EPOLLOUT as u32 > 0 { + structpoll.events |= POLLOUT; + } + if events & EPOLLERR as u32 > 0 { + structpoll.events |= POLLERR; + } + poll_fds_vec.push(structpoll); + num_events += 1; + } + + for fd in rm_fds_vec.iter() { + epollfdobj.registered_fds.remove(fd); + } // remove closed fds + + let poll_fds_slice = &mut poll_fds_vec[..]; + let pollret = Self::poll_syscall(&self, poll_fds_slice, timeout); + if pollret < 0 { + return pollret; + } + let mut count = 0; + let end_idx: usize = interface::rust_min(num_events, maxevents as usize); + for result in poll_fds_slice[..end_idx].iter() { + let mut poll_event = false; + let mut event = EpollEvent { + events: 0, + fd: epollfdobj.registered_fds.get(&result.fd).unwrap().fd, + }; + if result.revents & POLLIN > 0 { + event.events |= EPOLLIN as u32; + poll_event = true; + } + if result.revents & POLLOUT > 0 { + event.events |= EPOLLOUT as u32; + poll_event = true; + } + if result.revents & POLLERR > 0 { + event.events |= EPOLLERR as u32; + poll_event = true; + } + + if poll_event { + events[count] = event; + count += 1; + } + } + return count as i32; + } else { + return syscall_error( + Errno::EINVAL, + "epoll wait", + "provided fd is not an epoll file descriptor", + ); + } + } else { + return syscall_error( + Errno::EBADF, + "epoll wait", + "provided fd is not a valid file descriptor", + ); + } + } + + // Because socketpair needs to spawn off a helper thread to connect the two ends of the socket pair, and because that helper thread, + // along with the main thread, need to access the cage to call methods (syscalls) of it, and because rust's threading model states that + // any reference passed into a thread but not moved into it mut have a static lifetime, we cannot use a standard member function to perform + // this syscall, and must use an arc wrapped cage instead as a "this" parameter in lieu of self + pub fn socketpair_syscall( + this: interface::RustRfc, + domain: i32, + socktype: i32, + protocol: i32, + sv: &mut interface::SockPair, + ) -> i32 { + let newprotocol = if protocol == 0 { IPPROTO_TCP } else { protocol }; + // firstly check the parameters + if domain != AF_UNIX { + return syscall_error( + Errno::EOPNOTSUPP, + "socketpair", + "Linux socketpair only supports AF_UNIX aka AF_LOCAL domain.", + ); + } else if socktype & 0x7 != SOCK_STREAM || newprotocol != IPPROTO_TCP { + return syscall_error( + Errno::EOPNOTSUPP, + "socketpair", + "Socketpair currently only supports SOCK_STREAM TCP.", + ); + } + + let nonblocking = (socktype & SOCK_NONBLOCK) != 0; + let cloexec = (socktype & SOCK_CLOEXEC) != 0; + + // create 2 file discriptors + let sock1fdobj = this._socket_initializer( + domain, + socktype, + newprotocol, + nonblocking, + cloexec, + ConnState::NOTCONNECTED, + ); + let sock1fd = this._socket_inserter(Socket(sock1fdobj.clone())); + let sock2fdobj = this._socket_initializer( + domain, + socktype, + newprotocol, + nonblocking, + cloexec, + ConnState::NOTCONNECTED, + ); + let sock2fd = this._socket_inserter(Socket(sock2fdobj.clone())); + + // assign local addresses and connect + let sock1tmp = sock1fdobj.handle.clone(); + let sock2tmp = sock2fdobj.handle.clone(); + let mut sock1handle = sock1tmp.write(); + let mut sock2handle = sock2tmp.write(); + let localaddr1 = Self::assign_new_addr_unix(&sock1handle); + let localaddr2 = Self::assign_new_addr_unix(&sock2handle); + this.bind_inner_socket(&mut *sock1handle, &localaddr1, false); + this.bind_inner_socket(&mut *sock2handle, &localaddr2, false); + + // setup the pipes + let (pipe1, pipe2) = create_unix_sockpipes(); + // one handle's remote address is the other's local address + sock1handle.remoteaddr = Some(localaddr2.clone()); + sock2handle.remoteaddr = Some(localaddr1.clone()); + // one handle's sendpipe is the other's receivepipe + sock1handle.unix_info.as_mut().unwrap().sendpipe = Some(pipe1.clone()); + sock1handle.unix_info.as_mut().unwrap().receivepipe = Some(pipe2.clone()); + sock2handle.unix_info.as_mut().unwrap().sendpipe = Some(pipe2.clone()); + sock2handle.unix_info.as_mut().unwrap().receivepipe = Some(pipe1.clone()); + + // now they are connected + sock1handle.state = ConnState::CONNECTED; + sock2handle.state = ConnState::CONNECTED; + + sv.sock1 = sock1fd; + sv.sock2 = sock2fd; + + // we need to increment the refcount of the sockets we created + // reason: in bind_inner_socket, we added entries to the inode table + let inode1num = sock1handle.unix_info.as_mut().unwrap().inode; + if let Inode::Socket(ref mut sock) = *(FS_METADATA.inodetable.get_mut(&inode1num).unwrap()) + { + sock.refcount += 1; + } + let inode2num = sock2handle.unix_info.as_mut().unwrap().inode; + if let Inode::Socket(ref mut sock) = *(FS_METADATA.inodetable.get_mut(&inode2num).unwrap()) + { + sock.refcount += 1; + } + + return 0; + } + + // all this does is send the net_devs data in a string to libc, where we will later parse and + // alloc into getifaddrs structs + pub fn getifaddrs_syscall(&self, buf: *mut u8, count: usize) -> i32 { + if NET_IFADDRS_STR.len() < count { + interface::fill( + buf, + NET_IFADDRS_STR.len(), + &NET_IFADDRS_STR.as_bytes().to_vec(), + ); + 0 // return success + } else { + return syscall_error(Errno::EOPNOTSUPP, "getifaddrs", "invalid ifaddrs length"); + } + } +} diff --git a/src/safeposix/syscalls/net_constants.rs b/src/safeposix/syscalls/net_constants.rs new file mode 100644 index 00000000..438231c2 --- /dev/null +++ b/src/safeposix/syscalls/net_constants.rs @@ -0,0 +1,393 @@ +// Network related constants +#![allow(dead_code)] +#![allow(non_upper_case_globals)] + +use crate::interface; + +//used for gethostname syscall +pub const DEFAULT_HOSTNAME: &str = "Lind"; +pub const BLOCK_TIME: interface::RustDuration = interface::RustDuration::from_micros(100); + +pub const UDSOCK_CAPACITY: usize = 212992; + +// Define constants using static or const +// Imported into net_calls file + +pub const SOCK_STREAM: i32 = 1; //stream socket +pub const SOCK_DGRAM: i32 = 2; //datagram socket +pub const SOCK_RAW: i32 = 3; //raw protocol interface +pub const SOCK_RDM: i32 = 4; //reliably delivered message +pub const SOCK_SEQPACKET: i32 = 5; //sequenced packet stream +pub const SOCK_CLOEXEC: i32 = 0o02000000; // Atomically set close-on-exec +pub const SOCK_NONBLOCK: i32 = 0o00004000;// Mark as non-blocking + +/* Supported address families. */ +pub const AF_UNSPEC: i32 = 0; +pub const AF_UNIX: i32 = 1; /* Unix domain sockets */ +pub const AF_LOCAL: i32 = 1; /* POSIX name for AF_UNIX */ +pub const AF_INET: i32 = 2; /* Internet IP Protocol */ +pub const AF_AX25: i32 = 3; /* Amateur Radio AX.25 */ +pub const AF_IPX: i32 = 4; /* Novell IPX */ +pub const AF_APPLETALK: i32 = 5; /* AppleTalk DDP */ +pub const AF_NETROM: i32 = 6; /* Amateur Radio NET/ROM */ +pub const AF_BRIDGE: i32 = 7; /* Multiprotocol bridge */ +pub const AF_ATMPVC: i32 = 8; /* ATM PVCs */ +pub const AF_X25: i32 = 9; /* Reserved for X.25 project */ +pub const AF_INET6: i32 = 10; /* IP version 6 */ +pub const AF_ROSE: i32 = 11; /* Amateur Radio X.25 PLP */ +pub const AF_DECnet: i32 = 12; /* Reserved for DECnet project */ +pub const AF_NETBEUI: i32 = 13; /* Reserved for 802.2LLC project*/ +pub const AF_SECURITY: i32 = 14; /* Security callback pseudo AF */ +pub const AF_KEY: i32 = 15; /* PF_KEY key management API */ +pub const AF_NETLINK: i32 = 16; +pub const AF_ROUTE: i32 = AF_NETLINK; /* Alias to emulate 4.4BSD */ +pub const AF_PACKET: i32 = 17; /* Packet family */ +pub const AF_ASH: i32 = 18; /* Ash */ +pub const AF_ECONET: i32 = 19; /* Acorn Econet */ +pub const AF_ATMSVC: i32 = 20; /* ATM SVCs */ +pub const AF_RDS: i32 = 21; /* RDS sockets */ +pub const AF_SNA: i32 = 22; /* Linux SNA Project (nutters!) */ +pub const AF_IRDA: i32 = 23; /* IRDA sockets */ +pub const AF_PPPOX: i32 = 24; /* PPPoX sockets */ +pub const AF_WANPIPE: i32 = 25; /* Wanpipe API Sockets */ +pub const AF_LLC: i32 = 26; /* Linux LLC */ +pub const AF_IB: i32 = 27; /* Native InfiniBand address */ +pub const AF_MPLS: i32 = 28; /* MPLS */ +pub const AF_CAN: i32 = 29; /* Controller Area Network */ +pub const AF_TIPC: i32 = 30; /* TIPC sockets */ +pub const AF_BLUETOOTH: i32 = 31; /* Bluetooth sockets */ +pub const AF_IUCV: i32 = 32; /* IUCV sockets */ +pub const AF_RXRPC: i32 = 33; /* RxRPC sockets */ +pub const AF_ISDN: i32 = 34; /* mISDN sockets */ +pub const AF_PHONET: i32 = 35; /* Phonet sockets */ +pub const AF_IEEE802154: i32 = 36; /* IEEE802154 sockets */ +pub const AF_CAIF: i32 = 37; /* CAIF sockets */ +pub const AF_ALG: i32 = 38; /* Algorithm sockets */ +pub const AF_NFC: i32 = 39; /* NFC sockets */ +pub const AF_VSOCK: i32 = 40; /* vSockets */ +pub const AF_KCM: i32 = 41; /* Kernel Connection Multiplexor*/ +pub const AF_QIPCRTR: i32 = 42; /* Qualcomm IPC Router */ +pub const AF_SMC: i32 = 43; /* smc sockets: reserve number for + * PF_SMC protocol family that + * reuses AF_INET address family + */ +pub const AF_XDP: i32 = 44; /* XDP sockets */ +pub const AF_MCTP: i32 = 45; /* Management component + * transport protocol + */ + +pub const AF_MAX: i32 = 46; /* For now.. */ + +/* Protocol families, same as address families. */ +pub const PF_UNSPEC: i32 = AF_UNSPEC; +pub const PF_UNIX: i32 = AF_UNIX; +pub const PF_LOCAL: i32 = AF_LOCAL; +pub const PF_INET: i32 = AF_INET; +pub const PF_AX25: i32 = AF_AX25; +pub const PF_IPX: i32 = AF_IPX; +pub const PF_APPLETALK: i32 = AF_APPLETALK; +pub const PF_NETROM: i32 = AF_NETROM; +pub const PF_BRIDGE: i32 = AF_BRIDGE; +pub const PF_ATMPVC: i32 = AF_ATMPVC; +pub const PF_X25: i32 = AF_X25; +pub const PF_INET6: i32 = AF_INET6; +pub const PF_ROSE: i32 = AF_ROSE; +pub const PF_DECnet: i32 = AF_DECnet; +pub const PF_NETBEUI: i32 = AF_NETBEUI; +pub const PF_SECURITY: i32 = AF_SECURITY; +pub const PF_KEY: i32 = AF_KEY; +pub const PF_NETLINK: i32 = AF_NETLINK; +pub const PF_ROUTE: i32 = AF_ROUTE; +pub const PF_PACKET: i32 = AF_PACKET; +pub const PF_ASH: i32 = AF_ASH; +pub const PF_ECONET: i32 = AF_ECONET; +pub const PF_ATMSVC: i32 = AF_ATMSVC; +pub const PF_RDS: i32 = AF_RDS; +pub const PF_SNA: i32 = AF_SNA; +pub const PF_IRDA: i32 = AF_IRDA; +pub const PF_PPPOX: i32 = AF_PPPOX; +pub const PF_WANPIPE: i32 = AF_WANPIPE; +pub const PF_LLC: i32 = AF_LLC; +pub const PF_IB: i32 = AF_IB; +pub const PF_MPLS: i32 = AF_MPLS; +pub const PF_CAN: i32 = AF_CAN; +pub const PF_TIPC: i32 = AF_TIPC; +pub const PF_BLUETOOTH: i32 = AF_BLUETOOTH; +pub const PF_IUCV: i32 = AF_IUCV; +pub const PF_RXRPC: i32 = AF_RXRPC; +pub const PF_ISDN: i32 = AF_ISDN; +pub const PF_PHONET: i32 = AF_PHONET; +pub const PF_IEEE802154: i32 = AF_IEEE802154; +pub const PF_CAIF: i32 = AF_CAIF; +pub const PF_ALG: i32 = AF_ALG; +pub const PF_NFC: i32 = AF_NFC; +pub const PF_VSOCK: i32 = AF_VSOCK; +pub const PF_KCM: i32 = AF_KCM; +pub const PF_QIPCRTR: i32 = AF_QIPCRTR; +pub const PF_SMC: i32 = AF_SMC; +pub const PF_XDP: i32 = AF_XDP; +pub const PF_MCTP: i32 = AF_MCTP; +pub const PF_MAX: i32 = AF_MAX; + +// protocols... + +pub const IPPROTO_IP: i32 = 0; // dummy for IP +pub const IPPROTO_ICMP: i32 = 1; // control message protocol +pub const IPPROTO_IGMP: i32 = 2; // group mgmt protocol +pub const IPPROTO_GGP: i32 = 3; // gateway^2 (deprecated) +pub const IPPROTO_IPV4: i32 = 4; // IPv4 encapsulation +pub const IPPROTO_IPIP: i32 = IPPROTO_IPV4; // for compatibility +pub const IPPROTO_TCP: i32 = 6; // tcp +pub const IPPROTO_ST: i32 = 7; // Stream protocol II +pub const IPPROTO_EGP: i32 = 8; // exterior gateway protocol +pub const IPPROTO_PIGP: i32 = 9; // private interior gateway +pub const IPPROTO_RCCMON: i32 = 10; // BBN RCC Monitoring +pub const IPPROTO_NVPII: i32 = 11; // network voice protocol +pub const IPPROTO_PUP: i32 = 12; // pup +pub const IPPROTO_ARGUS: i32 = 13; // Argus +pub const IPPROTO_EMCON: i32 = 14; // EMCON +pub const IPPROTO_XNET: i32 = 15; // Cross Net Debugger +pub const IPPROTO_CHAOS: i32 = 16; // Chaos +pub const IPPROTO_UDP: i32 = 17; // user datagram protocol +pub const IPPROTO_MUX: i32 = 18; // Multiplexing +pub const IPPROTO_MEAS: i32 = 19; // DCN Measurement Subsystems +pub const IPPROTO_HMP: i32 = 20; // Host Monitoring +pub const IPPROTO_PRM: i32 = 21; // Packet Radio Measurement +pub const IPPROTO_IDP: i32 = 22; // xns idp +pub const IPPROTO_TRUNK1: i32 = 23; // Trunk-1 +pub const IPPROTO_TRUNK2: i32 = 24; // Trunk-2 +pub const IPPROTO_LEAF1: i32 = 25; // Leaf-1 +pub const IPPROTO_LEAF2: i32 = 26; // Leaf-2 +pub const IPPROTO_RDP: i32 = 27; // Reliable Data +pub const IPPROTO_IRTP: i32 = 28; // Reliable Transaction +pub const IPPROTO_TP: i32 = 29; // tp-4 w/ class negotiation +pub const IPPROTO_BLT: i32 = 30; // Bulk Data Transfer +pub const IPPROTO_NSP: i32 = 31; // Network Services +pub const IPPROTO_INP: i32 = 32; // Merit Internodal +pub const IPPROTO_SEP: i32 = 33; // Sequential Exchange +pub const IPPROTO_3PC: i32 = 34; // Third Party Connect +pub const IPPROTO_IDPR: i32 = 35; // InterDomain Policy Routing +pub const IPPROTO_XTP: i32 = 36; // XTP +pub const IPPROTO_DDP: i32 = 37; // Datagram Delivery +pub const IPPROTO_CMTP: i32 = 38; // Control Message Transport +pub const IPPROTO_TPXX: i32 = 39; // TP++ Transport +pub const IPPROTO_IL: i32 = 40; // IL transport protocol +pub const IPPROTO_IPV6: i32 = 41; // IP6 header +pub const IPPROTO_SDRP: i32 = 42; // Source Demand Routing +pub const IPPROTO_ROUTING: i32 = 43; // IP6 routing header +pub const IPPROTO_FRAGMENT: i32 = 44; // IP6 fragmentation header +pub const IPPROTO_IDRP: i32 = 45; // InterDomain Routing +pub const IPPROTO_RSVP: i32 = 46; // resource reservation +pub const IPPROTO_GRE: i32 = 47; // General Routing Encap. +pub const IPPROTO_MHRP: i32 = 48; // Mobile Host Routing +pub const IPPROTO_BHA: i32 = 49; // BHA +pub const IPPROTO_ESP: i32 = 50; // IP6 Encap Sec. Payload +pub const IPPROTO_AH: i32 = 51; // IP6 Auth Header +pub const IPPROTO_INLSP: i32 = 52; // Integ. Net Layer Security +pub const IPPROTO_SWIPE: i32 = 53; // IP with encryption +pub const IPPROTO_NHRP: i32 = 54; // Next Hop Resolution + // 55-57: Unassigned +pub const IPPROTO_ICMPV6: i32 = 58; // ICMP6 +pub const IPPROTO_NONE: i32 = 59; // IP6 no next header +pub const IPPROTO_DSTOPTS: i32 = 60; // IP6 destination option +pub const IPPROTO_AHIP: i32 = 61; // any host internal protocol +pub const IPPROTO_CFTP: i32 = 62; // CFTP +pub const IPPROTO_HELLO: i32 = 63; // "hello" routing protocol +pub const IPPROTO_SATEXPAK: i32 = 64; // SATNET/Backroom EXPAK +pub const IPPROTO_KRYPTOLAN: i32 = 65; // Kryptolan +pub const IPPROTO_RVD: i32 = 66; // Remote Virtual Disk +pub const IPPROTO_IPPC: i32 = 67; // Pluribus Packet Core +pub const IPPROTO_ADFS: i32 = 68; // Any distributed FS +pub const IPPROTO_SATMON: i32 = 69; // Satnet Monitoring +pub const IPPROTO_VISA: i32 = 70; // VISA Protocol +pub const IPPROTO_IPCV: i32 = 71; // Packet Core Utility +pub const IPPROTO_CPNX: i32 = 72; // Comp. Prot. Net. Executive +pub const IPPROTO_CPHB: i32 = 73; // Comp. Prot. HeartBeat +pub const IPPROTO_WSN: i32 = 74; // Wang Span Network +pub const IPPROTO_PVP: i32 = 75; // Packet Video Protocol +pub const IPPROTO_BRSATMON: i32 = 76; // BackRoom SATNET Monitoring +pub const IPPROTO_ND: i32 = 77; // Sun net disk proto (temp.) +pub const IPPROTO_WBMON: i32 = 78; // WIDEBAND Monitoring +pub const IPPROTO_WBEXPAK: i32 = 79; // WIDEBAND EXPAK +pub const IPPROTO_EON: i32 = 80; // ISO cnlp +pub const IPPROTO_VMTP: i32 = 81; // VMTP +pub const IPPROTO_SVMTP: i32 = 82; // Secure VMTP +pub const IPPROTO_VINES: i32 = 83; // Banyon VINES +pub const IPPROTO_TTP: i32 = 84; // TTP +pub const IPPROTO_IGP: i32 = 85; // NSFNET-IGP +pub const IPPROTO_DGP: i32 = 86; // dissimilar gateway prot. +pub const IPPROTO_TCF: i32 = 87; // TCF +pub const IPPROTO_IGRP: i32 = 88; // Cisco/GXS IGRP +pub const IPPROTO_OSPFIGP: i32 = 89; // OSPFIGP +pub const IPPROTO_SRPC: i32 = 90; // Strite RPC protocol +pub const IPPROTO_LARP: i32 = 91; // Locus Address Resoloution +pub const IPPROTO_MTP: i32 = 92; // Multicast Transport +pub const IPPROTO_AX25: i32 = 93; // AX.25 Frames +pub const IPPROTO_IPEIP: i32 = 94; // IP encapsulated in IP +pub const IPPROTO_MICP: i32 = 95; // Mobile Int.ing control +pub const IPPROTO_SCCSP: i32 = 96; // Semaphore Comm. security +pub const IPPROTO_ETHERIP: i32 = 97; // Ethernet IP encapsulation +pub const IPPROTO_ENCAP: i32 = 98; // encapsulation header +pub const IPPROTO_APES: i32 = 99; // any private encr. scheme +pub const IPPROTO_GMTP: i32 = 100; // GMTP +pub const IPPROTO_PIM: i32 = 103; // Protocol Independent Mcast +pub const IPPROTO_IPCOMP: i32 = 108; // payload compression (IPComp) +pub const IPPROTO_PGM: i32 = 113; // PGM +pub const IPPROTO_SCTP: i32 = 132; // SCTP +pub const IPPROTO_DIVERT: i32 = 254; // divert pseudo-protocol +pub const IPPROTO_RAW: i32 = 255; // raw IP packet +pub const IPPROTO_MAX: i32 = 256; +// last return value of *_input(), meaning "all job for this pkt is done". +pub const IPPROTO_DONE: i32 = 257; + +pub const MSG_OOB: i32 = 1; +pub const MSG_PEEK: i32 = 2; +pub const MSG_DONTROUTE: i32 = 4; +pub const MSG_TRYHARD: i32 = 4; /* Synonym for MSG_DONTROUTE for DECnet */ +pub const MSG_CTRUNC: i32 = 8; +pub const MSG_PROBE: i32 = 0x10; /* Do not send. Only probe path f.e. for MTU */ +pub const MSG_TRUNC: i32 = 0x20; +pub const MSG_DONTWAIT: i32 = 0x40; /* Nonblocking io */ +pub const MSG_EOR: i32 = 0x80; /* End of record */ +pub const MSG_WAITALL: i32 = 0x100; /* Wait for a full request */ +pub const MSG_FIN: i32 = 0x200; +pub const MSG_SYN: i32 = 0x400; +pub const MSG_CONFIRM: i32 = 0x800; /* Confirm path validity */ +pub const MSG_RST: i32 = 0x1000; +pub const MSG_ERRQUEUE: i32 = 0x2000; /* Fetch message from error queue */ +pub const MSG_NOSIGNAL: i32 = 0x4000; /* Do not generate SIGPIPE */ +pub const MSG_MORE: i32 = 0x8000; /* Sender will send more */ +pub const MSG_WAITFORONE: i32 = 0x10000; /* recvmmsg(): block until 1+ packets avail */ +pub const MSG_SENDPAGE_NOPOLICY: i32 = 0x10000; /* sendpage() internal : do no apply policy */ +pub const MSG_SENDPAGE_NOTLAST: i32 = 0x20000; /* sendpage() internal : not the last page */ +pub const MSG_BATCH: i32 = 0x40000; /* sendmmsg(): more messages coming */ +pub const MSG_EOF: i32 = MSG_FIN; +pub const MSG_NO_SHARED_FRAGS: i32 = 0x80000; /* sendpage() internal : page frags are not shared */ +pub const MSG_SENDPAGE_DECRYPTED: i32 = 0x100000; /* sendpage() internal : page may carry + * plain text and require encryption + */ + +//shutdown +pub const SHUT_RD: i32 = 0; +pub const SHUT_WR: i32 = 1; +pub const SHUT_RDWR: i32 = 2; + +////////////////////// setsockopt / getsockopt... +pub const SOL_SOCKET: i32 = 1; + +pub const SO_DEBUG: i32 = 1; +pub const SO_REUSEADDR: i32 = 2; +pub const SO_TYPE: i32 = 3; +pub const SO_ERROR: i32 = 4; +pub const SO_DONTROUTE: i32 = 5; +pub const SO_BROADCAST: i32 = 6; +pub const SO_SNDBUF: i32 = 7; +pub const SO_RCVBUF: i32 = 8; +pub const SO_SNDBUFFORCE: i32 = 32; +pub const SO_RCVBUFFORCE: i32 = 33; +pub const SO_KEEPALIVE: i32 = 9; +pub const SO_OOBINLINE: i32 = 10; +pub const SO_NO_CHECK: i32 = 11; +pub const SO_PRIORITY: i32 = 12; +pub const SO_LINGER: i32 = 13; +pub const SO_BSDCOMPAT: i32 = 14; +pub const SO_REUSEPORT: i32 = 15; +pub const SO_PASSCRED: i32 = 16; +pub const SO_PEERCRED: i32 = 17; +pub const SO_RCVLOWAT: i32 = 18; +pub const SO_SNDLOWAT: i32 = 19; +pub const SO_RCVTIMEO_OLD: i32 = 20; +pub const SO_SNDTIMEO_OLD: i32 = 21; +pub const SO_PEERNAME: i32 = 28; +pub const SO_ACCEPTCONN: i32 = 30; + +// pub const SO_SECURITY_AUTHENTICATION: i32 = 22; +// pub const SO_SECURITY_ENCRYPTION_TRANSPORT: i32 = 23; +// pub const SO_SECURITY_ENCRYPTION_NETWORK: i32 = 24; + +// pub const SO_BINDTODEVICE: i32 = 25; + +// /* Socket filtering */ +// pub const SO_ATTACH_FILTER: i32 = 26; +// pub const SO_DETACH_FILTER: i32 = 27; + +// pub const SO_TIMESTAMP: i32 = 29; +// pub const SCM_TIMESTAMP: i32 = SO_TIMESTAMP; + +// pub const SO_PEERSEC: i32 = 31; +// pub const SO_PASSSEC: i32 = 34; +// pub const SO_TIMESTAMPNS: i32 = 35; +// pub const SCM_TIMESTAMPNS: i32 = SO_TIMESTAMPNS; + +// pub const SO_MARK: i32 = 36; + +// pub const SO_TIMESTAMPING: i32 = 37; +// pub const SCM_TIMESTAMPING: i32 = SO_TIMESTAMPING; + +// pub const SO_PROTOCOL: i32 = 38; +// pub const SO_DOMAIN: i32 = 39; + +// pub const SO_RXQ_OVFL: i32 = 40; + +// Use this to specify options on a socket. Use the protocol with setsockopt +// to specify something for all sockets with a protocol +pub const SOL_TCP: i32 = IPPROTO_TCP; +pub const SOL_UDP: i32 = IPPROTO_UDP; + +pub const TCP_NODELAY: i32 = 0x01; // don't delay send to coalesce packets +pub const TCP_MAXSEG: i32 = 0x02; // set maximum segment size +pub const TCP_NOPUSH: i32 = 0x04; // don't push last block of write +pub const TCP_NOOPT: i32 = 0x08; // don't use TCP options +pub const TCP_KEEPALIVE: i32 = 0x10; // idle time used when SO_KEEPALIVE is enabled +pub const TCP_CONNECTIONTIMEOUT: i32 = 0x20; // connection timeout +pub const PERSIST_TIMEOUT: i32 = 0x40; // time after which a connection in persist timeout + // will terminate. + // see draft-ananth-tcpm-persist-02.txt +pub const TCP_RXT_CONNDROPTIME: i32 = 0x80; // time after which tcp retransmissions will be + // stopped and the connection will be dropped +pub const TCP_RXT_FINDROP: i32 = 0x100; // When set, a connection is dropped after 3 FINs + +pub const MINSOCKOBJID: i32 = 0; +pub const MAXSOCKOBJID: i32 = 1024; + +//POLL CONSTANTS +pub const POLLIN: i16 = 0o1; // There is data to read. +pub const POLLPRI: i16 = 0o2; //There is urgent data to read. +pub const POLLOUT: i16 = 0o4; // Writing now will not block. +pub const POLLERR: i16 = 0o10; // Error condition. +pub const POLLHUP: i16 = 0o20; // Hung up. +pub const POLLNVAL: i16 = 0o40; // Invalid polling request. + + +//EPOLL CONSTANTS +pub const EPOLLIN: i32 = 0x001; +pub const EPOLLPRI: i32 = 0x002; +pub const EPOLLOUT: i32 = 0x004; +pub const EPOLLRDNORM: i32 = 0x040; +pub const EPOLLRDBAND: i32 = 0x080; +pub const EPOLLWRNORM: i32 = 0x100; +pub const EPOLLWRBAND: i32 = 0x200; +pub const EPOLLMSG: i32 = 0x400; +pub const EPOLLERR: i32 = 0x008; +pub const EPOLLHUP: i32 = 0x010; +pub const EPOLLRDHUP: i32 = 0x2000; +pub const EPOLLWAKEUP: i32 = 1 << 29; +pub const EPOLLONESHOT: i32 = 1 << 30; +pub const EPOLLET: i32 = 1 << 31; + +pub const EPOLL_CTL_ADD: i32 = 1; +pub const EPOLL_CTL_DEL: i32 = 2; +pub const EPOLL_CTL_MOD: i32 = 3; + +pub const FD_SET_MAX_FD: i32 = 1024; + +//for internal use +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ConnState { + NOTCONNECTED, + CONNECTED, + CONNRDONLY, + CONNWRONLY, + LISTEN, + INPROGRESS, +} diff --git a/src/safeposix/syscalls/sys_calls.rs b/src/safeposix/syscalls/sys_calls.rs new file mode 100644 index 00000000..67e952b5 --- /dev/null +++ b/src/safeposix/syscalls/sys_calls.rs @@ -0,0 +1,573 @@ +#![allow(dead_code)] + +// System related system calls +use super::fs_constants::*; +use super::net_constants::*; +use super::sys_constants::*; +use crate::interface; +use crate::safeposix::cage::{FileDescriptor::*, *}; +use crate::safeposix::filesystem::{decref_dir, metawalk, Inode, FS_METADATA}; +use crate::safeposix::net::NET_METADATA; +use crate::safeposix::shm::SHM_METADATA; + +use std::sync::Arc as RustRfc; + +impl Cage { + fn unmap_shm_mappings(&self) { + //unmap shm mappings on exit or exec + for rev_mapping in self.rev_shm.lock().iter() { + let shmid = rev_mapping.1; + let metadata = &SHM_METADATA; + match metadata.shmtable.entry(shmid) { + interface::RustHashEntry::Occupied(mut occupied) => { + let segment = occupied.get_mut(); + segment.shminfo.shm_nattch -= 1; + segment.shminfo.shm_dtime = interface::timestamp() as isize; + segment.attached_cages.remove(&self.cageid); + + if segment.rmid && segment.shminfo.shm_nattch == 0 { + let key = segment.key; + occupied.remove_entry(); + metadata.shmkeyidtable.remove(&key); + } + } + interface::RustHashEntry::Vacant(_) => { + panic!("Shm entry not created for some reason"); + } + }; + } + } + + pub fn fork_syscall(&self, child_cageid: u64) -> i32 { + //construct a new mutex in the child cage where each initialized mutex is in the parent cage + let mutextable = self.mutex_table.read(); + let mut new_mutex_table = vec![]; + for elem in mutextable.iter() { + if elem.is_some() { + let new_mutex_result = interface::RawMutex::create(); + match new_mutex_result { + Ok(new_mutex) => new_mutex_table.push(Some(interface::RustRfc::new(new_mutex))), + Err(_) => { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "fork", + "The libc call to pthread_mutex_init failed!", + ); + } + Err(()) => { + panic!("Unknown errno value from pthread_mutex_init returned!") + } + }; + } + } + } else { + new_mutex_table.push(None); + } + } + drop(mutextable); + + //construct a new condvar in the child cage where each initialized condvar is in the parent cage + let cvtable = self.cv_table.read(); + let mut new_cv_table = vec![]; + for elem in cvtable.iter() { + if elem.is_some() { + let new_cv_result = interface::RawCondvar::create(); + match new_cv_result { + Ok(new_cv) => new_cv_table.push(Some(interface::RustRfc::new(new_cv))), + Err(_) => { + match Errno::from_discriminant(interface::get_errno()) { + Ok(i) => { + return syscall_error( + i, + "fork", + "The libc call to pthread_cond_init failed!", + ); + } + Err(()) => { + panic!("Unknown errno value from pthread_cond_init returned!") + } + }; + } + } + } else { + new_cv_table.push(None); + } + } + drop(cvtable); + + //construct new cage struct with a cloned fdtable + let newfdtable = init_fdtable(); + for fd in 0..MAXFD { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + match filedesc_enum { + File(_normalfile_filedesc_obj) => { + let inodenum_option = if let File(f) = filedesc_enum { + Some(f.inode) + } else { + None + }; + + if let Some(inodenum) = inodenum_option { + //increment the reference count on the inode + let mut inode = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + match *inode { + Inode::File(ref mut f) => { + f.refcount += 1; + } + Inode::CharDev(ref mut f) => { + f.refcount += 1; + } + Inode::Socket(ref mut f) => { + f.refcount += 1; + } + Inode::Dir(ref mut f) => { + f.refcount += 1; + } + } + } + } + Pipe(pipe_filedesc_obj) => { + pipe_filedesc_obj.pipe.incr_ref(pipe_filedesc_obj.flags) + } + Socket(socket_filedesc_obj) => { + // checking whether this is a domain socket + let sock_tmp = socket_filedesc_obj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + let socket_type = sockhandle.domain; + if socket_type == AF_UNIX { + if let Some(sockinfo) = &sockhandle.unix_info { + if let Some(sendpipe) = sockinfo.sendpipe.as_ref() { + sendpipe.incr_ref(O_WRONLY); + } + if let Some(receivepipe) = sockinfo.receivepipe.as_ref() { + receivepipe.incr_ref(O_RDONLY); + } + if let Some(uinfo) = &mut sockhandle.unix_info { + if let Inode::Socket(ref mut sock) = + *(FS_METADATA.inodetable.get_mut(&uinfo.inode).unwrap()) + { + sock.refcount += 1; + } + } + } + } + drop(sockhandle); + let sock_tmp = socket_filedesc_obj.handle.clone(); + let mut sockhandle = sock_tmp.write(); + if let Some(uinfo) = &mut sockhandle.unix_info { + if let Inode::Socket(ref mut sock) = + *(FS_METADATA.inodetable.get_mut(&uinfo.inode).unwrap()) + { + sock.refcount += 1; + } + } + } + _ => {} + } + + let newfdobj = filedesc_enum.clone(); + + let _insertval = newfdtable[fd as usize].write().insert(newfdobj); + //add deep copied fd to fd table + } + } + let cwd_container = self.cwd.read(); + if let Some(cwdinodenum) = metawalk(&cwd_container) { + if let Inode::Dir(ref mut cwddir) = + *(FS_METADATA.inodetable.get_mut(&cwdinodenum).unwrap()) + { + cwddir.refcount += 1; + } else { + panic!("We changed from a directory that was not a directory in chdir!"); + } + } else { + panic!("We changed from a directory that was not a directory in chdir!"); + } + + // we grab the parent cages main threads sigset and store it at 0 + // we do this because we haven't established a thread for the cage yet, and dont have a threadid to store it at + // this way the child can initialize the sigset properly when it establishes its own mainthreadid + let newsigset = interface::RustHashMap::new(); + if !interface::RUSTPOSIX_TESTSUITE.load(interface::RustAtomicOrdering::Relaxed) { + // we don't add these for the test suite + let mainsigsetatomic = self + .sigset + .get( + &self + .main_threadid + .load(interface::RustAtomicOrdering::Relaxed), + ) + .unwrap(); + let mainsigset = interface::RustAtomicU64::new( + mainsigsetatomic.load(interface::RustAtomicOrdering::Relaxed), + ); + newsigset.insert(0, mainsigset); + } + + /* + * Construct a new semaphore table in child cage which equals to the one in the parent cage + */ + let semtable = &self.sem_table; + let new_semtable: interface::RustHashMap< + u32, + interface::RustRfc, + > = interface::RustHashMap::new(); + // Loop all pairs + for pair in semtable.iter() { + new_semtable.insert((*pair.key()).clone(), pair.value().clone()); + } + + let cageobj = Cage { + cageid: child_cageid, + cwd: interface::RustLock::new(self.cwd.read().clone()), + parent: self.cageid, + filedescriptortable: newfdtable, + cancelstatus: interface::RustAtomicBool::new(false), + // This happens because self.getgid tries to copy atomic value which does not implement "Copy" trait; self.getgid.load returns i32. + getgid: interface::RustAtomicI32::new( + self.getgid.load(interface::RustAtomicOrdering::Relaxed), + ), + getuid: interface::RustAtomicI32::new( + self.getuid.load(interface::RustAtomicOrdering::Relaxed), + ), + getegid: interface::RustAtomicI32::new( + self.getegid.load(interface::RustAtomicOrdering::Relaxed), + ), + geteuid: interface::RustAtomicI32::new( + self.geteuid.load(interface::RustAtomicOrdering::Relaxed), + ), + rev_shm: interface::Mutex::new((*self.rev_shm.lock()).clone()), + mutex_table: interface::RustLock::new(new_mutex_table), + cv_table: interface::RustLock::new(new_cv_table), + sem_table: new_semtable, + thread_table: interface::RustHashMap::new(), + signalhandler: self.signalhandler.clone(), + sigset: newsigset, + pendingsigset: interface::RustHashMap::new(), + main_threadid: interface::RustAtomicU64::new(0), + interval_timer: interface::IntervalTimer::new(child_cageid), + }; + + let shmtable = &SHM_METADATA.shmtable; + //update fields for shared mappings in cage + for rev_mapping in cageobj.rev_shm.lock().iter() { + let mut shment = shmtable.get_mut(&rev_mapping.1).unwrap(); + shment.shminfo.shm_nattch += 1; + let refs = shment.attached_cages.get(&self.cageid).unwrap(); + let childrefs = refs.clone(); + drop(refs); + shment.attached_cages.insert(child_cageid, childrefs); + } + interface::cagetable_insert(child_cageid, cageobj); + + 0 + } + + pub fn exec_syscall(&self, child_cageid: u64) -> i32 { + interface::cagetable_remove(self.cageid); + + self.unmap_shm_mappings(); + + let mut cloexecvec = vec![]; + for fd in 0..MAXFD { + let checkedfd = self.get_filedescriptor(fd).unwrap(); + let unlocked_fd = checkedfd.read(); + if let Some(filedesc_enum) = &*unlocked_fd { + if match filedesc_enum { + File(f) => f.flags & O_CLOEXEC, + Stream(s) => s.flags & O_CLOEXEC, + Socket(s) => s.flags & O_CLOEXEC, + Pipe(p) => p.flags & O_CLOEXEC, + Epoll(p) => p.flags & O_CLOEXEC, + } != 0 + { + cloexecvec.push(fd); + } + } + } + + for fdnum in cloexecvec { + self.close_syscall(fdnum); + } + + // we grab the parent cages main threads sigset and store it at 0 + // this way the child can initialize the sigset properly when it establishes its own mainthreadid + let newsigset = interface::RustHashMap::new(); + if !interface::RUSTPOSIX_TESTSUITE.load(interface::RustAtomicOrdering::Relaxed) { + // we don't add these for the test suite + let mainsigsetatomic = self + .sigset + .get( + &self + .main_threadid + .load(interface::RustAtomicOrdering::Relaxed), + ) + .unwrap(); + let mainsigset = interface::RustAtomicU64::new( + mainsigsetatomic.load(interface::RustAtomicOrdering::Relaxed), + ); + newsigset.insert(0, mainsigset); + } + + let newcage = Cage { + cageid: child_cageid, + cwd: interface::RustLock::new(self.cwd.read().clone()), + parent: self.parent, + filedescriptortable: self.filedescriptortable.clone(), + cancelstatus: interface::RustAtomicBool::new(false), + getgid: interface::RustAtomicI32::new(-1), + getuid: interface::RustAtomicI32::new(-1), + getegid: interface::RustAtomicI32::new(-1), + geteuid: interface::RustAtomicI32::new(-1), + rev_shm: interface::Mutex::new(vec![]), + mutex_table: interface::RustLock::new(vec![]), + cv_table: interface::RustLock::new(vec![]), + sem_table: interface::RustHashMap::new(), + thread_table: interface::RustHashMap::new(), + signalhandler: interface::RustHashMap::new(), + sigset: newsigset, + pendingsigset: interface::RustHashMap::new(), + main_threadid: interface::RustAtomicU64::new(0), + interval_timer: self.interval_timer.clone_with_new_cageid(child_cageid), + }; + //wasteful clone of fdtable, but mutability constraints exist + + interface::cagetable_insert(child_cageid, newcage); + 0 + } + + pub fn exit_syscall(&self, status: i32) -> i32 { + //flush anything left in stdout + interface::flush_stdout(); + + self.unmap_shm_mappings(); + + // close fds + for fd in 0..MAXFD { + self._close_helper(fd); + } + + //get file descriptor table into a vector + let cwd_container = self.cwd.read(); + decref_dir(&*cwd_container); + + //may not be removable in case of lindrustfinalize, we don't unwrap the remove result + interface::cagetable_remove(self.cageid); + + // Trigger SIGCHLD + if !interface::RUSTPOSIX_TESTSUITE.load(interface::RustAtomicOrdering::Relaxed) { + // dont trigger SIGCHLD for test suite + if self.cageid != self.parent { + interface::lind_kill_from_id(self.parent, SIGCHLD); + } + } + + //fdtable will be dropped at end of dispatcher scope because of Arc + status + } + + pub fn getpid_syscall(&self) -> i32 { + self.cageid as i32 //not sure if this is quite what we want but it's easy enough to change later + } + pub fn getppid_syscall(&self) -> i32 { + self.parent as i32 // mimicing the call above -- easy to change later if necessary + } + + /*if its negative 1 + return -1, but also set the values in the cage struct to the DEFAULTs for future calls*/ + pub fn getgid_syscall(&self) -> i32 { + if self.getgid.load(interface::RustAtomicOrdering::Relaxed) == -1 { + self.getgid + .store(DEFAULT_GID as i32, interface::RustAtomicOrdering::Relaxed); + return -1; + } + DEFAULT_GID as i32 //Lind is only run in one group so a default value is returned + } + pub fn getegid_syscall(&self) -> i32 { + if self.getegid.load(interface::RustAtomicOrdering::Relaxed) == -1 { + self.getegid + .store(DEFAULT_GID as i32, interface::RustAtomicOrdering::Relaxed); + return -1; + } + DEFAULT_GID as i32 //Lind is only run in one group so a default value is returned + } + + pub fn getuid_syscall(&self) -> i32 { + if self.getuid.load(interface::RustAtomicOrdering::Relaxed) == -1 { + self.getuid + .store(DEFAULT_UID as i32, interface::RustAtomicOrdering::Relaxed); + return -1; + } + DEFAULT_UID as i32 //Lind is only run as one user so a default value is returned + } + pub fn geteuid_syscall(&self) -> i32 { + if self.geteuid.load(interface::RustAtomicOrdering::Relaxed) == -1 { + self.geteuid + .store(DEFAULT_UID as i32, interface::RustAtomicOrdering::Relaxed); + return -1; + } + DEFAULT_UID as i32 //Lind is only run as one user so a default value is returned + } + + pub fn sigaction_syscall( + &self, + sig: i32, + act: Option<&interface::SigactionStruct>, + oact: Option<&mut interface::SigactionStruct>, + ) -> i32 { + if let Some(some_oact) = oact { + let old_sigactionstruct = self.signalhandler.get(&sig); + + if let Some(entry) = old_sigactionstruct { + some_oact.clone_from(entry.value()); + } else { + some_oact.clone_from(&interface::SigactionStruct::default()); // leave handler field as NULL + } + } + + if let Some(some_act) = act { + if sig == 9 || sig == 19 { + // Disallow changing the action for SIGKILL and SIGSTOP + return syscall_error( + Errno::EINVAL, + "sigaction", + "Cannot modify the action of SIGKILL or SIGSTOP", + ); + } + + self.signalhandler.insert(sig, some_act.clone()); + } + + 0 + } + + pub fn kill_syscall(&self, cage_id: i32, sig: i32) -> i32 { + if (cage_id < 0) || (cage_id >= interface::MAXCAGEID) { + return syscall_error(Errno::EINVAL, "sigkill", "Invalid cage id."); + } + + if let Some(cage) = interface::cagetable_getref_opt(cage_id as u64) { + interface::lind_threadkill( + cage.main_threadid + .load(interface::RustAtomicOrdering::Relaxed), + sig, + ); + return 0; + } else { + return syscall_error(Errno::ESRCH, "kill", "Target cage does not exist"); + } + } + + pub fn sigprocmask_syscall( + &self, + how: i32, + set: Option<&interface::SigsetType>, + oldset: Option<&mut interface::SigsetType>, + ) -> i32 { + let mut res = 0; + let pthreadid = interface::get_pthreadid(); + + let sigset = self.sigset.get(&pthreadid).unwrap(); + + if let Some(some_oldset) = oldset { + *some_oldset = sigset.load(interface::RustAtomicOrdering::Relaxed); + } + + if let Some(some_set) = set { + let curr_sigset = sigset.load(interface::RustAtomicOrdering::Relaxed); + res = match how { + SIG_BLOCK => { + // Block signals in set + sigset.store( + curr_sigset | *some_set, + interface::RustAtomicOrdering::Relaxed, + ); + 0 + } + SIG_UNBLOCK => { + // Unblock signals in set + let newset = curr_sigset & !*some_set; + let pendingsignals = curr_sigset & some_set; + sigset.store(newset, interface::RustAtomicOrdering::Relaxed); + self.send_pending_signals(pendingsignals, pthreadid); + 0 + } + SIG_SETMASK => { + // Set sigset to set + sigset.store(*some_set, interface::RustAtomicOrdering::Relaxed); + 0 + } + _ => syscall_error(Errno::EINVAL, "sigprocmask", "Invalid value for how"), + } + } + res + } + + pub fn setitimer_syscall( + &self, + which: i32, + new_value: Option<&interface::ITimerVal>, + old_value: Option<&mut interface::ITimerVal>, + ) -> i32 { + match which { + ITIMER_REAL => { + if let Some(some_old_value) = old_value { + let (curr_duration, next_duration) = self.interval_timer.get_itimer(); + some_old_value.it_value.tv_sec = curr_duration.as_secs() as i64; + some_old_value.it_value.tv_usec = curr_duration.subsec_millis() as i64; + some_old_value.it_interval.tv_sec = next_duration.as_secs() as i64; + some_old_value.it_interval.tv_usec = next_duration.subsec_millis() as i64; + } + + if let Some(some_new_value) = new_value { + let curr_duration = interface::RustDuration::new( + some_new_value.it_value.tv_sec as u64, + some_new_value.it_value.tv_usec as u32, + ); + let next_duration = interface::RustDuration::new( + some_new_value.it_interval.tv_sec as u64, + some_new_value.it_interval.tv_usec as u32, + ); + + self.interval_timer.set_itimer(curr_duration, next_duration); + } + } + + _ => { /* ITIMER_VIRTUAL and ITIMER_PROF is not implemented*/ } + } + 0 + } + + pub fn getrlimit(&self, res_type: u64, rlimit: &mut Rlimit) -> i32 { + match res_type { + RLIMIT_NOFILE => { + rlimit.rlim_cur = NOFILE_CUR; + rlimit.rlim_max = NOFILE_MAX; + } + RLIMIT_STACK => { + rlimit.rlim_cur = STACK_CUR; + rlimit.rlim_max = STACK_MAX; + } + _ => return -1, + } + 0 + } + + pub fn setrlimit(&self, res_type: u64, _limit_value: u64) -> i32 { + match res_type { + RLIMIT_NOFILE => { + if NOFILE_CUR > NOFILE_MAX { + -1 + } else { + 0 + } + //FIXME: not implemented yet to update value in program + } + _ => -1, + } + } +} diff --git a/src/safeposix/syscalls/sys_constants.rs b/src/safeposix/syscalls/sys_constants.rs new file mode 100644 index 00000000..89feb715 --- /dev/null +++ b/src/safeposix/syscalls/sys_constants.rs @@ -0,0 +1,77 @@ +// System related constants +#![allow(dead_code)] +#![allow(unused_variables)] + +use crate::interface; + +// Define constants using static or const +// Imported into fs_calls file + +//GID AND UID DEFAULT VALUES + +pub const DEFAULT_UID: u32 = 1000; +pub const DEFAULT_GID: u32 = 1000; + +// RESOURCE LIMITS + +pub const SIGNAL_MAX: i32 = 64; + +pub const NOFILE_CUR: u64 = 1024; +pub const NOFILE_MAX: u64 = 4 * 1024; + +pub const STACK_CUR: u64 = 8192 * 1024; +pub const STACK_MAX: u64 = 1 << 32; + +pub const RLIMIT_STACK: u64 = 0; +pub const RLIMIT_NOFILE: u64 = 1; + +// Constants for exit_syscall status + +pub const EXIT_SUCCESS: i32 = 0; +pub const EXIT_FAILURE: i32 = 1; + +// Signal Table (x86/ARM) +// Based on https://man7.org/linux/man-pages/man7/signal.7.html +pub const SIGHUP: i32 = 1; +pub const SIGINT: i32 = 2; +pub const SIGQUIT: i32 = 3; +pub const SIGILL: i32 = 4; +pub const SIGTRAP: i32 = 5; +pub const SIGABRT: i32 = 6; +pub const SIGIOT: i32 = 6; +pub const SIGBUS: i32 = 7; +// pub const SIGEMT: i32 +pub const SIGFPE: i32 = 8; +pub const SIGKILL: i32 = 9; +pub const SIGUSR1: i32 = 10; +pub const SIGSEGV: i32 = 11; +pub const SIGUSR2: i32 = 12; +pub const SIGPIPE: i32 = 13; +pub const SIGALRM: i32 = 14; +pub const SIGTERM: i32 = 15; +pub const SIGSTKFLT: i32 = 16; +pub const SIGCHLD: i32 = 17; +// pub const SIGCLD: i32 +pub const SIGCONT: i32 = 18; +pub const SIGSTOP: i32 = 19; +pub const SIGTSTP: i32 = 20; +pub const SIGTTIN: i32 = 21; +pub const SIGTTOU: i32 = 22; +pub const SIGURG: i32 = 23; +pub const SIGXCPU: i32 = 24; +pub const SIGXFSZ: i32 = 25; +pub const SIGVTALRM: i32 = 26; +pub const SIGPROF: i32 = 27; +pub const SIGWINCH: i32 = 28; +pub const SIGIO: i32 = 29; +pub const SIGPOLL: i32 = 29; +pub const SIGPWR: i32 = 30; +// pub const SIGINFO: i32 +// pub const SIGLOST: i32 +pub const SIGSYS: i32 = 31; +pub const SIGUNUSED: i32 = 31; + +pub const SIG_BLOCK: i32 = 0; +pub const SIG_UNBLOCK: i32 = 1; +pub const SIG_SETMASK: i32 = 2; +pub const ITIMER_REAL: i32 = 0; diff --git a/src/tests/fs_tests.rs b/src/tests/fs_tests.rs new file mode 100644 index 00000000..69f9fce9 --- /dev/null +++ b/src/tests/fs_tests.rs @@ -0,0 +1,1242 @@ +#[allow(unused_parens)] +#[cfg(test)] +pub mod fs_tests { + use super::super::*; + use crate::interface; + use crate::safeposix::syscalls::fs_calls::*; + use crate::safeposix::{cage::*, dispatcher::*, filesystem}; + use std::fs::OpenOptions; + use std::os::unix::fs::PermissionsExt; + + pub fn test_fs() { + ut_lind_fs_simple(); // has to go first, else the data files created screw with link count test + + ut_lind_fs_broken_close(); + ut_lind_fs_chmod(); + ut_lind_fs_fchmod(); + ut_lind_fs_dir_chdir(); + ut_lind_fs_dir_mode(); + ut_lind_fs_dir_multiple(); + ut_lind_fs_dup(); + ut_lind_fs_dup2(); + ut_lind_fs_fcntl(); + ut_lind_fs_ioctl(); + ut_lind_fs_fdflags(); + ut_lind_fs_file_link_unlink(); + ut_lind_fs_file_lseek_past_end(); + ut_lind_fs_fstat_complex(); + ut_lind_fs_getuid(); + ut_lind_fs_load_fs(); + ut_lind_fs_mknod(); + ut_lind_fs_multiple_open(); + ut_lind_fs_rename(); + ut_lind_fs_rmdir(); + ut_lind_fs_stat_file_complex(); + ut_lind_fs_stat_file_mode(); + ut_lind_fs_statfs(); + ut_lind_fs_fstatfs(); + ut_lind_fs_ftruncate(); + ut_lind_fs_truncate(); + ut_lind_fs_getdents(); + ut_lind_fs_dir_chdir_getcwd(); + rdwrtest(); + prdwrtest(); + chardevtest(); + ut_lind_fs_exec_cloexec(); + ut_lind_fs_shm(); + ut_lind_fs_getpid_getppid(); + ut_lind_fs_sem_fork(); + ut_lind_fs_sem_trytimed(); + ut_lind_fs_sem_test(); + ut_lind_fs_tmp_file_test(); + } + + pub fn ut_lind_fs_simple() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + assert_eq!(cage.access_syscall("/", F_OK), 0); + assert_eq!(cage.access_syscall("/", X_OK | R_OK), 0); + + let mut statdata2 = StatData::default(); + + assert_eq!(cage.stat_syscall("/", &mut statdata2), 0); + //ensure that there are two hard links + + assert_eq!(statdata2.st_nlink, 5); //2 for . and .., one for dev, and one so that it can never be removed + + //ensure that there is no associated size + assert_eq!(statdata2.st_size, 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn rdwrtest() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let fd = cage.open_syscall("/foobar", O_CREAT | O_TRUNC | O_RDWR, S_IRWXA); + assert!(fd >= 0); + + assert_eq!(cage.write_syscall(fd, str2cbuf("hello there!"), 12), 12); + + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + let mut read_buf1 = sizecbuf(5); + assert_eq!(cage.read_syscall(fd, read_buf1.as_mut_ptr(), 5), 5); + assert_eq!(cbuf2str(&read_buf1), "hello"); + + assert_eq!(cage.write_syscall(fd, str2cbuf(" world"), 6), 6); + + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + let mut read_buf2 = sizecbuf(12); + assert_eq!(cage.read_syscall(fd, read_buf2.as_mut_ptr(), 12), 12); + assert_eq!(cbuf2str(&read_buf2), "hello world!"); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + + lindrustfinalize(); + } + + pub fn prdwrtest() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let fd = cage.open_syscall("/foobar2", O_CREAT | O_TRUNC | O_RDWR, S_IRWXA); + assert!(fd >= 0); + + assert_eq!(cage.pwrite_syscall(fd, str2cbuf("hello there!"), 12, 0), 12); + + let mut read_buf1 = sizecbuf(5); + assert_eq!(cage.pread_syscall(fd, read_buf1.as_mut_ptr(), 5, 0), 5); + assert_eq!(cbuf2str(&read_buf1), "hello"); + + assert_eq!(cage.pwrite_syscall(fd, str2cbuf(" world"), 6, 5), 6); + + let mut read_buf2 = sizecbuf(12); + assert_eq!(cage.pread_syscall(fd, read_buf2.as_mut_ptr(), 12, 0), 12); + assert_eq!(cbuf2str(&read_buf2), "hello world!"); + + lindrustfinalize(); + } + + pub fn chardevtest() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let fd = cage.open_syscall("/dev/zero", O_RDWR, S_IRWXA); + assert!(fd >= 0); + + assert_eq!( + cage.pwrite_syscall( + fd, + str2cbuf("Lorem ipsum dolor sit amet, consectetur adipiscing elit"), + 55, + 0 + ), + 55 + ); + + let mut read_bufzero = sizecbuf(1000); + assert_eq!( + cage.pread_syscall(fd, read_bufzero.as_mut_ptr(), 1000, 0), + 1000 + ); + assert_eq!( + cbuf2str(&read_bufzero), + std::iter::repeat("\0") + .take(1000) + .collect::() + .as_str() + ); + + assert_eq!(cage.chdir_syscall("dev"), 0); + assert_eq!(cage.close_syscall(fd), 0); + + let fd2 = cage.open_syscall("./urandom", O_RDWR, S_IRWXA); + assert!(fd2 >= 0); + let mut read_bufrand = sizecbuf(1000); + assert_eq!( + cage.read_syscall(fd2, read_bufrand.as_mut_ptr(), 1000), + 1000 + ); + assert_eq!(cage.close_syscall(fd2), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_broken_close() { + //testing a muck up with the inode table where the regular close does not work as intended + + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //write should work + let mut fd = cage.open_syscall("/broken_close_file", O_CREAT | O_EXCL | O_RDWR, S_IRWXA); + assert_eq!(cage.write_syscall(fd, str2cbuf("Hello There!"), 12), 12); + assert_eq!(cage.close_syscall(fd), 0); + + //close the file and then open it again... and then close it again + fd = cage.open_syscall("/broken_close_file", O_RDWR, S_IRWXA); + assert_eq!(cage.close_syscall(fd), 0); + + //let's try some things with connect + //we are going to open a socket with a UDP specification... + let sockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + //bind should not be interesting + let mut sockad = interface::GenSockaddr::V4(interface::SockaddrV4::default()); + sockad.set_family(AF_INET as u16); + assert_eq!(cage.bind_syscall(sockfd, &sockad), 0); + + fd = cage.open_syscall("/broken_close_file", O_RDWR, S_IRWXA); + assert_eq!(cage.close_syscall(fd), 0); + + fd = cage.open_syscall("/broken_close_file", O_RDWR, S_IRWXA); + assert_eq!(cage.close_syscall(fd), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_chmod() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/chmodTestFile"; + + let mut statdata = StatData::default(); + + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + assert_eq!(cage.stat_syscall(filepath, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IRWXA | S_IFREG as u32); + + assert_eq!(cage.chmod_syscall(filepath, S_IRUSR | S_IRGRP), 0); + assert_eq!(cage.stat_syscall(filepath, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IRUSR | S_IRGRP | S_IFREG as u32); + + assert_eq!(cage.chmod_syscall(filepath, S_IRWXA), 0); + assert_eq!(cage.stat_syscall(filepath, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IRWXA | S_IFREG as u32); + + assert_eq!(cage.close_syscall(fd), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_fchmod() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/fchmodTestFile"; + + let mut statdata = StatData::default(); + + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + assert_eq!(cage.fstat_syscall(fd, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IRWXA | S_IFREG as u32); + + assert_eq!(cage.fchmod_syscall(fd, S_IRUSR | S_IRGRP), 0); + assert_eq!(cage.fstat_syscall(fd, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IRUSR | S_IRGRP | S_IFREG as u32); + + assert_eq!(cage.fchmod_syscall(fd, S_IRWXA), 0); + assert_eq!(cage.fstat_syscall(fd, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IRWXA | S_IFREG as u32); + + assert_eq!(cage.close_syscall(fd), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_dir_chdir() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //testing the ability to make and change to directories + + assert_eq!(cage.mkdir_syscall("/subdir1", S_IRWXA), 0); + assert_eq!(cage.mkdir_syscall("/subdir1/subdir2", S_IRWXA), 0); + assert_eq!(cage.mkdir_syscall("/subdir1/subdir2/subdir3", 0), 0); + + assert_eq!(cage.access_syscall("subdir1", F_OK), 0); + assert_eq!(cage.chdir_syscall("subdir1"), 0); + + assert_eq!(cage.access_syscall("subdir2", F_OK), 0); + assert_eq!(cage.chdir_syscall(".."), 0); + + assert_eq!(cage.access_syscall("subdir1", F_OK), 0); + assert_eq!(cage.chdir_syscall("/subdir1/subdir2/subdir3"), 0); + assert_eq!(cage.access_syscall("../../../subdir1", F_OK), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_dir_mode() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let filepath1 = "/subdirDirMode1"; + let filepath2 = "/subdirDirMode2"; + + let mut statdata = StatData::default(); + + assert_eq!(cage.mkdir_syscall(filepath1, S_IRWXA), 0); + assert_eq!(cage.stat_syscall(filepath1, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IRWXA | S_IFDIR as u32); + + assert_eq!(cage.mkdir_syscall(filepath2, 0), 0); + assert_eq!(cage.stat_syscall(filepath2, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IFDIR as u32); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_dir_multiple() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + assert_eq!(cage.mkdir_syscall("/subdirMultiple1", S_IRWXA), 0); + assert_eq!( + cage.mkdir_syscall("/subdirMultiple1/subdirMultiple2", S_IRWXA), + 0 + ); + assert_eq!( + cage.mkdir_syscall("/subdirMultiple1/subdirMultiple2/subdirMultiple3", 0), + 0 + ); + + let mut statdata = StatData::default(); + + //ensure that the file is a dir with all of the correct bits on for nodes + assert_eq!( + cage.stat_syscall("/subdirMultiple1/subdirMultiple2", &mut statdata), + 0 + ); + assert_eq!(statdata.st_mode, S_IRWXA | S_IFDIR as u32); + + assert_eq!( + cage.stat_syscall( + "/subdirMultiple1/subdirMultiple2/subdirMultiple3", + &mut statdata + ), + 0 + ); + assert_eq!(statdata.st_mode, S_IFDIR as u32); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_dup() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/dupfile"; + + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + let mut temp_buffer = sizecbuf(2); + assert!(fd >= 0); + assert_eq!(cage.write_syscall(fd, str2cbuf("12"), 2), 2); + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + assert_eq!(cage.read_syscall(fd, temp_buffer.as_mut_ptr(), 2), 2); + assert_eq!(cbuf2str(&temp_buffer), "12"); + + //duplicate the file descriptor + let fd2 = cage.dup_syscall(fd, None); + assert!(fd != fd2); + + //essentially a no-op, but duplicate again -- they should be diff &fd's + let fd3 = cage.dup_syscall(fd, None); + assert!(fd != fd2 && fd != fd3); + + //We don't need all three, though: + assert_eq!(cage.close_syscall(fd3), 0); + + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_END), 2); + assert_eq!(cage.lseek_syscall(fd2, 0, SEEK_END), 2); + + // write some data to move the first position + assert_eq!(cage.write_syscall(fd, str2cbuf("34"), 2), 2); + + //Make sure that they are still in the same place: + let mut buffer = sizecbuf(4); + assert_eq!( + cage.lseek_syscall(fd, 0, SEEK_SET), + cage.lseek_syscall(fd2, 0, SEEK_SET) + ); + assert_eq!(cage.read_syscall(fd, buffer.as_mut_ptr(), 4), 4); + assert_eq!(cbuf2str(&buffer), "1234"); + + assert_eq!(cage.close_syscall(fd), 0); + + //the other &fd should still work + assert_eq!(cage.lseek_syscall(fd2, 0, SEEK_END), 4); + assert_eq!(cage.write_syscall(fd2, str2cbuf("5678"), 4), 4); + + assert_eq!(cage.lseek_syscall(fd2, 0, SEEK_SET), 0); + let mut buffer2 = sizecbuf(8); + assert_eq!(cage.read_syscall(fd2, buffer2.as_mut_ptr(), 8), 8); + assert_eq!(cage.close_syscall(fd2), 0); + assert_eq!(cbuf2str(&buffer2), "12345678"); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_dup2() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/dup2file"; + + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + + assert_eq!(cage.write_syscall(fd, str2cbuf("12"), 2), 2); + + //trying to dup fd into fd + 1 + let _fd2: i32 = cage.dup2_syscall(fd, fd + 1 as i32); + + //should be a no-op since the last line did the same thing + let fd2: i32 = cage.dup2_syscall(fd, fd + 1 as i32); + + //read/write tests for the files + assert_eq!( + cage.lseek_syscall(fd, 0, SEEK_END), + cage.lseek_syscall(fd2, 0, SEEK_END) + ); + assert_eq!(cage.write_syscall(fd, str2cbuf("34"), 2), 2); + assert_eq!( + cage.lseek_syscall(fd, 0, SEEK_SET), + cage.lseek_syscall(fd2, 0, SEEK_SET) + ); + + let mut buffer = sizecbuf(4); + assert_eq!(cage.lseek_syscall(fd2, 0, SEEK_SET), 0); + assert_eq!(cage.read_syscall(fd, buffer.as_mut_ptr(), 4), 4); + assert_eq!(cbuf2str(&buffer), "1234"); + + assert_eq!(cage.close_syscall(fd), 0); + + let mut buffer2 = sizecbuf(8); + assert_eq!(cage.lseek_syscall(fd2, 0, SEEK_END), 4); + assert_eq!(cage.write_syscall(fd2, str2cbuf("5678"), 4), 4); + + assert_eq!(cage.lseek_syscall(fd2, 0, SEEK_SET), 0); + assert_eq!(cage.read_syscall(fd2, buffer2.as_mut_ptr(), 8), 8); + assert_eq!(cbuf2str(&buffer2), "12345678"); + + assert_eq!(cage.close_syscall(fd2), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_fcntl() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let sockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let filefd = cage.open_syscall("/fcntl_file", O_CREAT | O_EXCL, S_IRWXA); + + //set the setfd flag + assert_eq!(cage.fcntl_syscall(sockfd, F_SETFD, O_CLOEXEC), 0); + + //checking to see if the wrong flag was set or not + assert_eq!(cage.fcntl_syscall(sockfd, F_GETFD, 0), O_CLOEXEC); + + //let's get some more flags on the filefd + assert_eq!( + cage.fcntl_syscall(filefd, F_SETFL, O_RDONLY | O_NONBLOCK), + 0 + ); + + //checking if the flags are updated... + assert_eq!(cage.fcntl_syscall(filefd, F_GETFL, 0), 2048); + + assert_eq!(cage.close_syscall(filefd), 0); + assert_eq!(cage.close_syscall(sockfd), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_ioctl() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let mut arg0: i32 = 0; + let mut arg1: i32 = 1; + + let union0: IoctlPtrUnion = IoctlPtrUnion { int_ptr: &mut arg0 }; + let union1: IoctlPtrUnion = IoctlPtrUnion { int_ptr: &mut arg1 }; + + let sockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let filefd = cage.open_syscall("/ioctl_file", O_CREAT | O_EXCL, S_IRWXA); + + //try to use FIONBIO for a non-socket + assert_eq!( + cage.ioctl_syscall(filefd, FIONBIO, union0), + -(Errno::ENOTTY as i32) + ); + + //clear the O_NONBLOCK flag + assert_eq!(cage.ioctl_syscall(sockfd, FIONBIO, union0), 0); + + //checking to see if the flag was updated + assert_eq!(cage.fcntl_syscall(sockfd, F_GETFL, 0) & O_NONBLOCK, 0); + + //set the O_NONBLOCK flag + assert_eq!(cage.ioctl_syscall(sockfd, FIONBIO, union1), 0); + + //checking to see if the flag was updated + assert_eq!( + cage.fcntl_syscall(sockfd, F_GETFL, 0) & O_NONBLOCK, + O_NONBLOCK + ); + + //clear the O_NONBLOCK flag + assert_eq!(cage.ioctl_syscall(sockfd, FIONBIO, union0), 0); + + //checking to see if the flag was updated + assert_eq!(cage.fcntl_syscall(sockfd, F_GETFL, 0) & O_NONBLOCK, 0); + + assert_eq!(cage.close_syscall(filefd), 0); + assert_eq!(cage.close_syscall(sockfd), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_fdflags() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let path = "/fdFlagsFile"; + + let fd = cage.creat_syscall(path, S_IRWXA); + assert_eq!(cage.close_syscall(fd), 0); + + let read_fd = cage.open_syscall(path, O_RDONLY, S_IRWXA); + assert_eq!(cage.lseek_syscall(read_fd, 0, SEEK_SET), 0); + assert_eq!( + cage.write_syscall(read_fd, str2cbuf("Hello! This should not write."), 28), + -(Errno::EBADF as i32) + ); + + let mut buf = sizecbuf(100); + assert_eq!(cage.lseek_syscall(read_fd, 0, SEEK_SET), 0); + + //this fails because nothing is written to the readfd (the previous write was unwritable) + assert_eq!(cage.read_syscall(read_fd, buf.as_mut_ptr(), 100), 0); + assert_eq!(cage.close_syscall(read_fd), 0); + + let write_fd = cage.open_syscall(path, O_WRONLY, S_IRWXA); + let mut buf2 = sizecbuf(100); + assert_eq!(cage.lseek_syscall(write_fd, 0, SEEK_SET), 0); + assert_eq!( + cage.read_syscall(write_fd, buf2.as_mut_ptr(), 100), + -(Errno::EBADF as i32) + ); + + assert_eq!(cage.lseek_syscall(write_fd, 0, SEEK_SET), 0); + assert_eq!( + cage.write_syscall(write_fd, str2cbuf("Hello! This should write."), 24), + 24 + ); + assert_eq!(cage.close_syscall(write_fd), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_file_link_unlink() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let path = "/fileLink"; + let path2 = "/fileLink2"; + + let fd = cage.open_syscall(path, O_CREAT | O_EXCL | O_WRONLY, S_IRWXA); + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + assert_eq!(cage.write_syscall(fd, str2cbuf("hi"), 2), 2); + + let mut statdata = StatData::default(); + + assert_eq!(cage.stat_syscall(path, &mut statdata), 0); + assert_eq!(statdata.st_size, 2); + assert_eq!(statdata.st_nlink, 1); + + let mut statdata2 = StatData::default(); + + //make sure that this has the same traits as the other file that we linked + // and make sure that the link count on the orig file has increased + assert_eq!(cage.link_syscall(path, path2), 0); + assert_eq!(cage.stat_syscall(path, &mut statdata), 0); + assert_eq!(cage.stat_syscall(path2, &mut statdata2), 0); + assert!(statdata == statdata2); + assert_eq!(statdata.st_nlink, 2); + + //now we unlink + assert_eq!(cage.unlink_syscall(path), 0); + assert_eq!(cage.stat_syscall(path2, &mut statdata2), 0); + assert_eq!(statdata2.st_nlink, 1); + + //it shouldn't work to stat the orig since it is gone + assert_ne!(cage.stat_syscall(path, &mut statdata), 0); + assert_eq!(cage.unlink_syscall(path2), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_file_lseek_past_end() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let path = "/lseekPastEnd"; + + let fd = cage.open_syscall(path, O_CREAT | O_EXCL | O_RDWR, S_IRWXA); + assert_eq!(cage.write_syscall(fd, str2cbuf("hello"), 5), 5); + + //seek past the end and then write + assert_eq!(cage.lseek_syscall(fd, 10, SEEK_SET), 10); + assert_eq!(cage.write_syscall(fd, str2cbuf("123456"), 6), 6); + + let mut buf = sizecbuf(16); + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + assert_eq!(cage.read_syscall(fd, buf.as_mut_ptr(), 20), 16); + assert_eq!(cbuf2str(&buf), "hello\0\0\0\0\0123456"); + + assert_eq!(cage.close_syscall(fd), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_fstat_complex() { + lindrustinit(0); + + let cage = interface::cagetable_getref(1); + let path = "/complexFile"; + + let fd = cage.open_syscall(path, O_CREAT | O_WRONLY, S_IRWXA); + assert_eq!(cage.write_syscall(fd, str2cbuf("testing"), 4), 4); + + let mut statdata = StatData::default(); + + assert_eq!(cage.fstat_syscall(fd, &mut statdata), 0); + assert_eq!(statdata.st_size, 4); + assert_eq!(statdata.st_nlink, 1); + + assert_eq!(cage.close_syscall(fd), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_getuid() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //let's get the initial -1s out of the way + cage.getgid_syscall(); + cage.getegid_syscall(); + cage.getuid_syscall(); + cage.geteuid_syscall(); + + //testing to make sure that all of the gid and uid values are good to go when system is initialized + assert_eq!(cage.getgid_syscall() as u32, DEFAULT_GID); + assert_eq!(cage.getegid_syscall() as u32, DEFAULT_GID); + assert_eq!(cage.getuid_syscall() as u32, DEFAULT_UID); + assert_eq!(cage.geteuid_syscall() as u32, DEFAULT_UID); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_load_fs() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let mut statdata = StatData::default(); + + //testing that all of the dev files made it out safe and sound + cage.stat_syscall("/dev", &mut statdata); + + assert_eq!(cage.stat_syscall("/dev/null", &mut statdata), 0); + assert_eq!(statdata.st_rdev, makedev(&DevNo { major: 1, minor: 3 })); + + assert_eq!(cage.stat_syscall("/dev/random", &mut statdata), 0); + assert_eq!(statdata.st_rdev, makedev(&DevNo { major: 1, minor: 8 })); + + assert_eq!(cage.stat_syscall("/dev/urandom", &mut statdata), 0); + assert_eq!(statdata.st_rdev, makedev(&DevNo { major: 1, minor: 9 })); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_mknod() { + // let's create /dev/null + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let dev = makedev(&DevNo { major: 1, minor: 3 }); + let path = "/null"; + + //now we are going to mknod /dev/null with create, read and write flags and permissions + //and then makr sure that it exists + assert_eq!(cage.mknod_syscall(path, S_IFCHR as u32, dev), 0); + let fd = cage.open_syscall(path, O_RDWR, S_IRWXA); + + //checking the metadata of the file: + let mut statdata = StatData::default(); + + //should be a chr file, so let's check this + let mut buf = sizecbuf(4); + assert_eq!(cage.fstat_syscall(fd, &mut statdata), 0); + assert_eq!(statdata.st_mode & S_FILETYPEFLAGS as u32, S_IFCHR as u32); + assert_eq!(statdata.st_rdev, dev); + assert_eq!(cage.write_syscall(fd, str2cbuf("test"), 4), 4); + assert_eq!(cage.read_syscall(fd, buf.as_mut_ptr(), 4), 0); + assert_eq!(cbuf2str(&buf), "\0\0\0\0"); + assert_eq!(cage.close_syscall(fd), 0); + + let mut statdata2 = StatData::default(); + + //try it again with /dev/random + let dev2 = makedev(&DevNo { major: 1, minor: 8 }); + let path2 = "/random"; + + //making the node and then making sure that it exists + assert_eq!(cage.mknod_syscall(path2, S_IFCHR as u32, dev2), 0); + let fd2 = cage.open_syscall(path2, O_RDWR, S_IRWXA); + + let mut buf2 = sizecbuf(4); + assert_eq!(cage.fstat_syscall(fd2, &mut statdata2), 0); + assert_eq!(statdata2.st_mode & S_FILETYPEFLAGS as u32, S_IFCHR as u32); + assert_eq!(statdata2.st_rdev, dev2); + assert_eq!(cage.write_syscall(fd2, str2cbuf("testing"), 7), 7); + assert_ne!(cage.read_syscall(fd2, buf2.as_mut_ptr(), 7), 0); + assert_eq!(cage.close_syscall(fd2), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_multiple_open() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //try to open several files at once -- the fd's should not be overwritten + let fd1 = cage.open_syscall("/foo", O_CREAT | O_EXCL | O_RDWR, S_IRWXA); + let fd2 = cage.open_syscall("/foo", O_RDWR, S_IRWXA); + assert_ne!(fd1, fd2); + + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let mode: u32 = 0o666; // 0666 + let name = "double_open_file"; + + let mut read_buf = sizecbuf(2); + let fd3 = cage.open_syscall(name, flags, mode); + assert_eq!(cage.write_syscall(fd3, str2cbuf("hi"), 2), 2); + assert_eq!(cage.lseek_syscall(fd3, 0, SEEK_SET), 0); + assert_eq!(cage.read_syscall(fd3, read_buf.as_mut_ptr(), 2), 2); + assert_eq!(cbuf2str(&read_buf), "hi"); + + let _fd4 = cage.open_syscall(name, flags, mode); + let mut buf = sizecbuf(5); + assert_eq!(cage.lseek_syscall(fd3, 2, SEEK_SET), 2); + assert_eq!(cage.write_syscall(fd3, str2cbuf("boo"), 3), 3); + assert_eq!(cage.lseek_syscall(fd3, 0, SEEK_SET), 0); + assert_eq!(cage.read_syscall(fd3, buf.as_mut_ptr(), 5), 5); + assert_eq!(cbuf2str(&buf), "\0\0boo"); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_rmdir() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let path = "/parent_dir/dir"; + assert_eq!(cage.mkdir_syscall("/parent_dir", S_IRWXA), 0); + assert_eq!(cage.mkdir_syscall(path, S_IRWXA), 0); + assert_eq!(cage.rmdir_syscall(path), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_stat_file_complex() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let fd = cage.open_syscall("/fooComplex", O_CREAT | O_EXCL | O_WRONLY, S_IRWXA); + + assert_eq!(cage.write_syscall(fd, str2cbuf("hi"), 2), 2); + + let mut statdata = StatData::default(); + let mut statdata2 = StatData::default(); + + assert_eq!(cage.fstat_syscall(fd, &mut statdata), 0); + assert_eq!(statdata.st_size, 2); + assert_eq!(statdata.st_nlink, 1); + + assert_eq!(cage.link_syscall("/fooComplex", "/barComplex"), 0); + assert_eq!(cage.stat_syscall("/fooComplex", &mut statdata), 0); + assert_eq!(cage.stat_syscall("/barComplex", &mut statdata2), 0); + + //check that they are the same and that the link count is 0 + assert!(statdata == statdata2); + assert_eq!(statdata.st_nlink, 2); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_stat_file_mode() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let path = "/fooFileMode"; + let _fd = cage.open_syscall(path, O_CREAT | O_EXCL | O_WRONLY, S_IRWXA); + + let mut statdata = StatData::default(); + assert_eq!(cage.stat_syscall(path, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IRWXA | S_IFREG as u32); + + //make a file without permissions and check that it is a reg file without permissions + let path2 = "/fooFileMode2"; + let _fd2 = cage.open_syscall(path2, O_CREAT | O_EXCL | O_WRONLY, 0); + assert_eq!(cage.stat_syscall(path2, &mut statdata), 0); + assert_eq!(statdata.st_mode, S_IFREG as u32); + + //check that stat can be done on the current (root) dir + assert_eq!(cage.stat_syscall(".", &mut statdata), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_statfs() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let mut fsdata = FSData::default(); + + assert_eq!(cage.statfs_syscall("/", &mut fsdata), 0); + assert_eq!(fsdata.f_type, 0xBEEFC0DE); + assert_eq!(fsdata.f_bsize, 4096); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_fstatfs() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let mut fsdata = FSData::default(); + + // Get fd + let fd = cage.open_syscall("/", O_RDONLY, 0); + assert!(fd >= 0); + // fstatfs + assert_eq!(cage.fstatfs_syscall(fd, &mut fsdata), 0); + // Check the output + assert_eq!(fsdata.f_type, 0xBEEFC0DE); + assert_eq!(fsdata.f_bsize, 4096); + // Close the file + assert_eq!(cage.close_syscall(fd), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_rename() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let old_path = "/test_dir"; + assert_eq!(cage.mkdir_syscall(old_path, S_IRWXA), 0); + assert_eq!(cage.rename_syscall(old_path, "/test_dir_renamed"), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_ftruncate() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let fd = cage.open_syscall("/ftruncate", O_CREAT | O_TRUNC | O_RDWR, S_IRWXA); + assert!(fd >= 0); + + // check if ftruncate() works for extending file with null bytes + assert_eq!(cage.write_syscall(fd, str2cbuf("Hello there!"), 12), 12); + assert_eq!(cage.ftruncate_syscall(fd, 15), 0); + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + let mut buf = sizecbuf(15); + assert_eq!(cage.read_syscall(fd, buf.as_mut_ptr(), 15), 15); + assert_eq!(cbuf2str(&buf), "Hello there!\0\0\0"); + + // check if ftruncate() works for cutting off extra bytes + assert_eq!(cage.ftruncate_syscall(fd, 5), 0); + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + let mut buf1 = sizecbuf(7); + assert_eq!(cage.read_syscall(fd, buf1.as_mut_ptr(), 7), 5); + assert_eq!(cbuf2str(&buf1), "Hello\0\0"); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_truncate() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let path = String::from("/truncate"); + let fd = cage.open_syscall(&path, O_CREAT | O_TRUNC | O_RDWR, S_IRWXA); + assert!(fd >= 0); + + // check if truncate() works for extending file with null bytes + assert_eq!(cage.write_syscall(fd, str2cbuf("Hello there!"), 12), 12); + assert_eq!(cage.truncate_syscall(&path, 15), 0); + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + let mut buf = sizecbuf(15); + assert_eq!(cage.read_syscall(fd, buf.as_mut_ptr(), 15), 15); + assert_eq!(cbuf2str(&buf), "Hello there!\0\0\0"); + + // check if truncate() works for cutting off extra bytes + assert_eq!(cage.truncate_syscall(&path, 5), 0); + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + let mut buf1 = sizecbuf(7); + assert_eq!(cage.read_syscall(fd, buf1.as_mut_ptr(), 7), 5); + assert_eq!(cbuf2str(&buf1), "Hello\0\0"); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[cfg(target_os = "macos")] + type CharPtr = *const u8; + + #[cfg(not(target_os = "macos"))] + type CharPtr = *const i8; + + pub fn ut_lind_fs_getdents() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let bufsize = 50; + let mut vec = vec![0u8; bufsize as usize]; + let baseptr: *mut u8 = &mut vec[0]; + + assert_eq!(cage.mkdir_syscall("/getdents", S_IRWXA), 0); + let fd = cage.open_syscall("/getdents", O_RDWR, S_IRWXA); + assert_eq!(cage.getdents_syscall(fd, baseptr, bufsize as u32), 48); + + unsafe { + let first_dirent = baseptr as *mut interface::ClippedDirent; + assert!((*first_dirent).d_off == 24); + let reclen_matched: bool = ((*first_dirent).d_reclen == 24); + assert_eq!(reclen_matched, true); + + let nameoffset = baseptr.wrapping_offset(interface::CLIPPED_DIRENT_SIZE as isize); + let returnedname = interface::RustCStr::from_ptr(nameoffset as *const _); + let name_matched: bool = (returnedname + == interface::RustCStr::from_bytes_with_nul(b".\0").unwrap()) + | (returnedname == interface::RustCStr::from_bytes_with_nul(b"..\0").unwrap()); + assert_eq!(name_matched, true); + + let second_dirent = baseptr.wrapping_offset(24) as *mut interface::ClippedDirent; + assert!((*second_dirent).d_off >= 48); + } + + assert_eq!(cage.close_syscall(fd), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_dir_chdir_getcwd() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let needed = "/subdir1\0".as_bytes().to_vec().len(); + + let needed_u32: u32 = needed as u32; + + let mut buf = vec![0u8; needed]; + let bufptr: *mut u8 = &mut buf[0]; + + assert_eq!(cage.chdir_syscall("/"), 0); + assert_eq!(cage.getcwd_syscall(bufptr, 0), -(Errno::ERANGE as i32)); + assert_eq!(cage.getcwd_syscall(bufptr, 1), -(Errno::ERANGE as i32)); + assert_eq!(cage.getcwd_syscall(bufptr, 2), 0); + assert_eq!(std::str::from_utf8(&buf).unwrap(), "/\0\0\0\0\0\0\0\0"); + + cage.mkdir_syscall("/subdir1", S_IRWXA); + assert_eq!(cage.access_syscall("subdir1", F_OK), 0); + assert_eq!(cage.chdir_syscall("subdir1"), 0); + + assert_eq!(cage.getcwd_syscall(bufptr, 0), -(Errno::ERANGE as i32)); + assert_eq!( + cage.getcwd_syscall(bufptr, needed_u32 - 1), + -(Errno::ERANGE as i32) + ); + assert_eq!(cage.getcwd_syscall(bufptr, needed_u32), 0); + assert_eq!(std::str::from_utf8(&buf).unwrap(), "/subdir1\0"); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_exec_cloexec() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let mut uselessstatdata = StatData::default(); + + let fd1 = cage.open_syscall( + "/cloexecuted", + O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, + S_IRWXA, + ); + let fd2 = cage.open_syscall("/cloexekept", O_CREAT | O_TRUNC | O_RDWR, S_IRWXA); + assert!(fd1 > 0); + assert!(fd2 > 0); + assert_eq!(cage.fstat_syscall(fd1, &mut uselessstatdata), 0); + assert_eq!(cage.fstat_syscall(fd2, &mut uselessstatdata), 0); + + assert_eq!(cage.exec_syscall(2), 0); + + let execcage = interface::cagetable_getref(2); + assert_eq!( + execcage.fstat_syscall(fd1, &mut uselessstatdata), + -(Errno::EBADF as i32) + ); + assert_eq!(execcage.fstat_syscall(fd2, &mut uselessstatdata), 0); + + assert_eq!(execcage.close_syscall(fd2), 0); + assert_eq!(cage.unlink_syscall("/cloexecuted"), 0); + assert_eq!(cage.unlink_syscall("/cloexekept"), 0); + + assert_eq!(execcage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + use libc::c_void; + pub fn ut_lind_fs_shm() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let key = 31337; + let mut shmidstruct = ShmidsStruct::default(); + + // shmget returns an identifier in shmid + let shmid = cage.shmget_syscall(key, 1024, 0666 | IPC_CREAT); + + // shmat to attach to shared memory + let shmatret = cage.shmat_syscall(shmid, 0xfffff000 as *mut u8, 0); + + assert_ne!(shmatret, -1); + + // get struct info + let shmctlret1 = cage.shmctl_syscall(shmid, IPC_STAT, Some(&mut shmidstruct)); + + assert_eq!(shmctlret1, 0); + + assert_eq!(shmidstruct.shm_nattch, 1); + + // mark the shared memory to be rmoved + let shmctlret2 = cage.shmctl_syscall(shmid, IPC_RMID, None); + + assert_eq!(shmctlret2, 0); + + //detach from shared memory + let shmdtret = cage.shmdt_syscall(0xfffff000 as *mut u8); + + assert_eq!(shmdtret, shmid); //NaCl requires shmdt to return the shmid, so this is non-posixy + + lindrustfinalize(); + } + + pub fn ut_lind_fs_getpid_getppid() { + lindrustinit(0); + + let cage1 = interface::cagetable_getref(1); + let pid1 = cage1.getpid_syscall(); + + assert_eq!(cage1.fork_syscall(2), 0); + + let child = std::thread::spawn(move || { + let cage2 = interface::cagetable_getref(2); + let pid2 = cage2.getpid_syscall(); + let ppid2 = cage2.getppid_syscall(); + + assert_ne!(pid2, pid1); // make sure the child and the parent have different pids + assert_eq!(ppid2, pid1); // make sure the child's getppid is correct + + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + child.join().unwrap(); + assert_eq!(cage1.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_fs_sem_fork() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let key = 31337; + // Create a shared memory region + let shmid = cage.shmget_syscall(key, 1024, 0666 | IPC_CREAT); + // Attach the shared memory region + let shmatret = cage.shmat_syscall(shmid, 0xfffff000 as *mut u8, 0); + assert_ne!(shmatret, -1); + // Initialize the semaphore with shared between process + let ret_init = cage.sem_init_syscall(shmatret as u32, 1, 1); + assert_eq!(ret_init, 0); + assert_eq!(cage.sem_getvalue_syscall(shmatret as u32), 1); + // Fork child process + assert_eq!(cage.fork_syscall(2), 0); + // Child process + let thread_child = interface::helper_thread(move || { + let cage1 = interface::cagetable_getref(2); + // Child waits for the semaphore + assert_eq!(cage1.sem_wait_syscall(shmatret as u32), 0); + interface::sleep(interface::RustDuration::from_millis(40)); + // Release the semaphore + assert_eq!(cage1.sem_post_syscall(shmatret as u32), 0); + cage1.exit_syscall(EXIT_SUCCESS); + }); + //Parent processes + let thread_parent = interface::helper_thread(move || { + // Parents waits for the semaphore + assert_eq!(cage.sem_wait_syscall(shmatret as u32), 0); + assert_eq!(cage.sem_getvalue_syscall(shmatret as u32), 0); + interface::sleep(interface::RustDuration::from_millis(100)); + // Parents release the semaphore + assert_eq!(cage.sem_post_syscall(shmatret as u32), 0); + assert_eq!(cage.sem_getvalue_syscall(shmatret as u32), 1); + // Destroy the semaphore + assert_eq!(cage.sem_destroy_syscall(shmatret as u32), 0); + // mark the shared memory to be rmoved + let shmctlret2 = cage.shmctl_syscall(shmid, IPC_RMID, None); + assert_eq!(shmctlret2, 0); + //detach from shared memory + let shmdtret = cage.shmdt_syscall(0xfffff000 as *mut u8); + assert_eq!(shmdtret, shmid); + cage.exit_syscall(EXIT_SUCCESS); + }); + thread_child.join().unwrap(); + thread_parent.join().unwrap(); + lindrustfinalize(); + } + + pub fn ut_lind_fs_sem_trytimed() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let key = 31337; + // Create a shared memory region + let shmid = cage.shmget_syscall(key, 1024, 0666 | IPC_CREAT); + // Attach the shared memory region + let shmatret = cage.shmat_syscall(shmid, 0xfffff000 as *mut u8, 0); + assert_ne!(shmatret, -1); + // Initialize the semaphore with shared between process + let ret_init = cage.sem_init_syscall(shmatret as u32, 1, 1); + // assert_eq!(shmatret as u32, 0); + assert_eq!(ret_init, 0); + assert_eq!(cage.sem_getvalue_syscall(shmatret as u32), 1); + // Fork child process + assert_eq!(cage.fork_syscall(2), 0); + // Child process + let thread_child = interface::helper_thread(move || { + let cage1 = interface::cagetable_getref(2); + // Child waits for the semaphore + assert_eq!(cage1.sem_trywait_syscall(shmatret as u32), 0); + // Wait + interface::sleep(interface::RustDuration::from_millis(20)); + // Release the semaphore + assert_eq!(cage1.sem_post_syscall(shmatret as u32), 0); + cage1.exit_syscall(EXIT_SUCCESS); + }); + //Parent processes + let thread_parent = interface::helper_thread(move || { + // Parents waits for the semaphore + assert_eq!( + cage.sem_timedwait_syscall( + shmatret as u32, + interface::RustDuration::from_millis(100) + ), + 0 + ); + assert_eq!(cage.sem_getvalue_syscall(shmatret as u32), 0); + interface::sleep(interface::RustDuration::from_millis(10)); + // Parents release the semaphore + assert_eq!(cage.sem_post_syscall(shmatret as u32), 0); + assert_eq!(cage.sem_getvalue_syscall(shmatret as u32), 1); + // Destroy the semaphore + assert_eq!(cage.sem_destroy_syscall(shmatret as u32), 0); + // mark the shared memory to be rmoved + let shmctlret2 = cage.shmctl_syscall(shmid, IPC_RMID, None); + assert_eq!(shmctlret2, 0); + //detach from shared memory + let shmdtret = cage.shmdt_syscall(0xfffff000 as *mut u8); + assert_eq!(shmdtret, shmid); + cage.exit_syscall(EXIT_SUCCESS); + }); + thread_child.join().unwrap(); + thread_parent.join().unwrap(); + lindrustfinalize(); + } + + pub fn ut_lind_fs_sem_test() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let key = 31337; + // Create a shared memory region + let shmid = cage.shmget_syscall(key, 1024, 0666 | IPC_CREAT); + // Attach the shared memory region + let shmatret = cage.shmat_syscall(shmid, 0xfffff000 as *mut u8, 0); + assert_ne!(shmatret, -1); + assert_eq!(cage.sem_destroy_syscall(shmatret as u32), -22); + assert_eq!(cage.sem_getvalue_syscall(shmatret as u32), -22); + assert_eq!(cage.sem_post_syscall(shmatret as u32), -22); + // Initialize the semaphore with shared between process + let ret_init = cage.sem_init_syscall(shmatret as u32, 1, 0); + assert_eq!(ret_init, 0); + // Should return errno + assert_eq!( + cage.sem_timedwait_syscall(shmatret as u32, interface::RustDuration::from_millis(100)), + -110 + ); + assert_eq!(cage.sem_trywait_syscall(shmatret as u32), -11); + lindrustfinalize(); + } + + pub fn ut_lind_fs_tmp_file_test() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + // Check if /tmp is there + assert_eq!(cage.access_syscall("/tmp", F_OK), 0); + + // Open file in /tmp + let file_path = "/tmp/testfile"; + let fd = cage.open_syscall(file_path, O_CREAT | O_TRUNC | O_RDWR, S_IRWXA); + + assert_eq!(cage.write_syscall(fd, str2cbuf("Hello world"), 6), 6); + assert_eq!(cage.close_syscall(fd), 0); + + lindrustfinalize(); + + // Init again + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + // Check if /tmp is there + assert_eq!(cage.access_syscall("/tmp", F_OK), 0); + // Check if file is still there (it shouldn't be, assert no) + assert_eq!(cage.access_syscall(file_path, F_OK), -2); + + lindrustfinalize(); + } +} diff --git a/src/tests/ipc_tests.rs b/src/tests/ipc_tests.rs new file mode 100644 index 00000000..cb65a74f --- /dev/null +++ b/src/tests/ipc_tests.rs @@ -0,0 +1,342 @@ +#[cfg(test)] +pub mod ipc_tests { + use super::super::*; + use crate::interface; + use crate::safeposix::{cage::*, dispatcher::*, filesystem}; + use std::fs::OpenOptions; + use std::os::unix::fs::PermissionsExt; + use std::time::Instant; + + //#[test] + pub fn test_ipc() { + ut_lind_ipc_pipe(); + ut_lind_ipc_domain_socket(); + ut_lind_ipc_socketpair(); + } + + pub fn ut_lind_ipc_pipe() { + let byte_chunk: usize = 131072; // 128 KB + let num_writes: usize = 8192; // 8 KB + + lindrustinit(0); + + let cage1 = interface::cagetable_getref(1); + + let mut pipefds = PipeArray { + readfd: -1, + writefd: -1, + }; + assert_eq!(cage1.pipe_syscall(&mut pipefds), 0); + assert_eq!(cage1.fork_syscall(2), 0); + + let sender = std::thread::spawn(move || { + let cage2 = interface::cagetable_getref(2); + + assert_eq!(cage2.close_syscall(pipefds.writefd), 0); + assert_eq!(cage2.dup2_syscall(pipefds.readfd, 0), 0); + assert_eq!(cage2.close_syscall(pipefds.readfd), 0); + + let mut bytes_read: usize = 1; + + let mut buf: Vec = Vec::with_capacity(byte_chunk * num_writes); + let mut bufptr = buf.as_mut_ptr(); + let mut buflen: usize = 0; + + while bytes_read != 0 { + bytes_read = cage2.read_syscall(0, bufptr, byte_chunk) as usize; + unsafe { + bufptr = bufptr.add(bytes_read); + } + buf.resize(buflen + bytes_read, 0); + buflen += bytes_read; + } + assert_eq!(cage2.close_syscall(0), 0); + + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + assert_eq!(cage1.close_syscall(pipefds.readfd), 0); + assert_eq!(cage1.dup2_syscall(pipefds.writefd, 1), 1); + assert_eq!(cage1.close_syscall(pipefds.writefd), 0); + + for _i in 0..num_writes { + let mut buf: Vec = vec!['A' as u8; byte_chunk]; + let bufptr = buf.as_mut_ptr(); + buf.resize(byte_chunk, 0); + cage1.write_syscall(1, bufptr, byte_chunk); + } + + assert_eq!(cage1.close_syscall(1), 0); + + sender.join().unwrap(); + + assert_eq!(cage1.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + + lindrustfinalize(); + } + + pub fn ut_lind_ipc_domain_socket() { + //bind net zero test reformatted for domain sockets + + let clientsockfilename = "/client.sock"; + let serversockfilename = "/server.sock"; + + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //both the server and the socket are run from this file + let serversockfd = cage.socket_syscall(AF_UNIX, SOCK_STREAM, 0); + let clientsockfd = cage.socket_syscall(AF_UNIX, SOCK_STREAM, 0); + + //making sure that the assigned fd's are valid + assert!(serversockfd > 0); + assert!(clientsockfd > 0); + + //binding to a socket + let serversockaddr = + interface::new_sockaddr_unix(AF_UNIX as u16, serversockfilename.as_bytes()); + let serversocket = interface::GenSockaddr::Unix(serversockaddr); + let clientsockaddr = + interface::new_sockaddr_unix(AF_UNIX as u16, clientsockfilename.as_bytes()); + let clientsocket = interface::GenSockaddr::Unix(clientsockaddr); + + assert_eq!(cage.bind_syscall(serversockfd, &serversocket), 0); + assert_eq!(cage.bind_syscall(clientsockfd, &clientsocket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 1), 0); //we are only allowing for one client at a time + + //forking the cage to get another cage with the same information + assert_eq!(cage.fork_syscall(2), 0); + + //creating a thread for the server so that the information can be sent between the two threads + let thread = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + let mut socket2 = interface::GenSockaddr::Unix(interface::new_sockaddr_unix( + AF_UNIX as u16, + "".as_bytes(), + )); // blank unix sockaddr + + let sockfd = cage2.accept_syscall(serversockfd, &mut socket2); //really can only make sure that the fd is valid + assert!(sockfd > 0); + + interface::sleep(interface::RustDuration::from_millis(100)); + + //process the first test... + //Writing 100, then peek 100, then read 100 + let mut buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 100, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 100 + ); //peeking at the input message + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); //reading the input message + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the second test... + //Writing 100, read 20, peek 20, read 80 + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 20, 0, &mut Some(&mut socket2)), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 80, 0, &mut Some(&mut socket2)), + 80 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(80) + &"\0".repeat(20)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the third test... + //Writing 100, peek several times, read 100 + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 10, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 10 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(10) + &"\0".repeat(90)); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 30, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 30 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(30) + &"\0".repeat(70)); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 40, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 40 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(40) + &"\0".repeat(60)); + buf = sizecbuf(100); + } + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the fourth test... + //Writing 50, peek 50 + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 50, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 50 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(50) + &"\0".repeat(50)); + assert_eq!(cage2.close_syscall(sockfd), 0); + assert_eq!(cage2.close_syscall(serversockfd), 0); + + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + //connect to the server + interface::sleep(interface::RustDuration::from_millis(20)); + + assert_eq!(cage.connect_syscall(clientsockfd, &serversocket), 0); + + //send the data with delays so that the server can process the information cleanly + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(50)), 50, 0), + 50 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!(cage.close_syscall(clientsockfd), 0); + + thread.join().unwrap(); + + cage.unlink_syscall(serversockfilename); + cage.unlink_syscall(clientsockfilename); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_ipc_socketpair() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let mut socketpair = interface::SockPair::default(); + assert_eq!( + Cage::socketpair_syscall(cage.clone(), AF_UNIX, SOCK_STREAM, 0, &mut socketpair), + 0 + ); + let cage2 = cage.clone(); + + let thread = interface::helper_thread(move || { + let mut buf = sizecbuf(10); + cage2.recv_syscall(socketpair.sock2, buf.as_mut_ptr(), 10, 0); + assert_eq!(cbuf2str(&buf), "test\0\0\0\0\0\0"); + + interface::sleep(interface::RustDuration::from_millis(30)); + assert_eq!( + cage2.send_syscall(socketpair.sock2, str2cbuf("Socketpair Test"), 15, 0), + 15 + ); + }); + + assert_eq!( + cage.send_syscall(socketpair.sock1, str2cbuf("test"), 4, 0), + 4 + ); + + let mut buf2 = sizecbuf(15); + cage.recv_syscall(socketpair.sock1, buf2.as_mut_ptr(), 15, 0); + assert_eq!(cbuf2str(&buf2), "Socketpair Test"); + + thread.join().unwrap(); + + assert_eq!(cage.close_syscall(socketpair.sock1), 0); + assert_eq!(cage.close_syscall(socketpair.sock2), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 00000000..ec61858c --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,92 @@ +#![allow(dead_code)] //suppress warning for these functions not being used in targets other than the tests + +mod fs_tests; +mod ipc_tests; +mod networking_tests; + +use crate::interface; +use crate::safeposix::{cage::*, filesystem::*}; + +#[cfg(test)] +mod main_tests { + use crate::tests::fs_tests::fs_tests::test_fs; + use crate::tests::ipc_tests::ipc_tests::test_ipc; + use crate::tests::networking_tests::net_tests::net_tests; + + use crate::interface; + use crate::safeposix::{cage::*, dispatcher::*, filesystem::*}; + + use std::process::Command; + + #[test] + pub fn tests() { + interface::RUSTPOSIX_TESTSUITE.store(true, interface::RustAtomicOrdering::Relaxed); + + lindrustinit(0); + { + let cage = interface::cagetable_getref(1); + crate::lib_fs_utils::lind_deltree(&cage, "/"); + assert_eq!(cage.mkdir_syscall("/dev", S_IRWXA), 0); + assert_eq!( + cage.mknod_syscall( + "/dev/null", + S_IFCHR as u32 | 0o777, + makedev(&DevNo { major: 1, minor: 3 }) + ), + 0 + ); + assert_eq!( + cage.mknod_syscall( + "/dev/zero", + S_IFCHR as u32 | 0o777, + makedev(&DevNo { major: 1, minor: 5 }) + ), + 0 + ); + assert_eq!( + cage.mknod_syscall( + "/dev/urandom", + S_IFCHR as u32 | 0o777, + makedev(&DevNo { major: 1, minor: 9 }) + ), + 0 + ); + assert_eq!( + cage.mknod_syscall( + "/dev/random", + S_IFCHR as u32 | 0o777, + makedev(&DevNo { major: 1, minor: 8 }) + ), + 0 + ); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + } + lindrustfinalize(); + + println!("FS TESTS"); + test_fs(); + + println!("NET TESTS"); + net_tests(); + + println!("IPC TESTS"); + test_ipc(); + } +} + +pub fn str2cbuf(ruststr: &str) -> *mut u8 { + let cbuflenexpected = ruststr.len(); + let (ptr, len, _) = ruststr.to_string().into_raw_parts(); + assert_eq!(len, cbuflenexpected); + return ptr; +} + +pub fn sizecbuf<'a>(size: usize) -> Box<[u8]> { + let v = vec![0u8; size]; + v.into_boxed_slice() + //buf.as_mut_ptr() as *mut u8 +} + +pub fn cbuf2str(buf: &[u8]) -> &str { + std::str::from_utf8(buf).unwrap() +} diff --git a/src/tests/networking_tests.rs b/src/tests/networking_tests.rs new file mode 100644 index 00000000..4ae2a6c6 --- /dev/null +++ b/src/tests/networking_tests.rs @@ -0,0 +1,2272 @@ +#[cfg(test)] +pub mod net_tests { + use super::super::*; + use crate::interface; + use crate::safeposix::{cage::*, dispatcher::*, filesystem}; + use std::mem::size_of; + use std::sync::{Arc, Barrier}; + + pub fn net_tests() { + ut_lind_net_bind(); + ut_lind_net_bind_multiple(); + ut_lind_net_bind_on_zero(); + ut_lind_net_connect_basic_udp(); + ut_lind_net_getpeername(); + ut_lind_net_getsockname(); + ut_lind_net_listen(); + ut_lind_net_poll(); + ut_lind_net_recvfrom(); + ut_lind_net_select(); + ut_lind_net_shutdown(); + ut_lind_net_socket(); + ut_lind_net_socketoptions(); + ut_lind_net_socketpair(); + ut_lind_net_udp_bad_bind(); + ut_lind_net_udp_simple(); + ut_lind_net_udp_connect(); + ut_lind_net_gethostname(); + ut_lind_net_dns_rootserver_ping(); + ut_lind_net_domain_socket(); + ut_lind_net_epoll(); + } + + pub fn ut_lind_net_bind() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let sockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + let socket = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50102u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); //127.0.0.1 + + //first bind should work... but second bind should not + assert_eq!(cage.bind_syscall(sockfd, &socket), 0); + assert_eq!(cage.bind_syscall(sockfd, &socket), -(Errno::EINVAL as i32)); //already bound so should fail + + //trying to bind another to the same IP/PORT + let sockfd2 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + assert_eq!( + cage.bind_syscall(sockfd2, &socket), + -(Errno::EADDRINUSE as i32) + ); //already bound so should fail + + //UDP should still work... + let sockfd3 = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + assert_eq!(cage.bind_syscall(sockfd3, &socket), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_bind_on_zero() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //both the server and the socket are run from this file + let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd2 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + let port: u16 = 53002; + + //making sure that the assigned fd's are valid + assert!(serversockfd > 0); + assert!(clientsockfd > 0); + assert!(clientsockfd2 > 0); + + //binding to a socket + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 1), 0); //we are only allowing for one client at a time + + //forking the cage to get another cage with the same information + assert_eq!(cage.fork_syscall(2), 0); + + //creating a thread for the server so that the information can be sent between the two threads + let thread = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + let mut socket2 = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { s_addr: 0 }, + padding: 0, + }); //0.0.0.0 + + let mut sockfd = cage2.accept_syscall(serversockfd, &mut socket2); //really can only make sure that the fd is valid + assert!(sockfd > 0); + + interface::sleep(interface::RustDuration::from_millis(100)); + + //process the first test... + //Writing 100, then peek 100, then read 100 + let mut buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 100, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 100 + ); //peeking at the input message + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); //reading the input message + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the second test... + //Writing 100, read 20, peek 20, read 80 + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 20, 0, &mut Some(&mut socket2)), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 80, 0, &mut Some(&mut socket2)), + 80 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(80) + &"\0".repeat(20)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the third test... + //Writing 100, peek several times, read 100 + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 10, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 10 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(10) + &"\0".repeat(90)); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 30, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 30 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(30) + &"\0".repeat(70)); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 40, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 40 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(40) + &"\0".repeat(60)); + buf = sizecbuf(100); + } + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the fourth test... + //Writing 50, peek 50 + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 50, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 50 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(50) + &"\0".repeat(50)); + assert_eq!(cage2.close_syscall(sockfd), 0); + + socket2 = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { s_addr: 0 }, + padding: 0, + }); //0.0.0.0 + interface::sleep(interface::RustDuration::from_millis(200)); + sockfd = cage2.accept_syscall(serversockfd, &mut socket2); //really can only make sure that the fd is valid + assert!(sockfd > 0); + + //process the first test... + //Writing 100, then peek 100, then read 100 + let mut buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 100, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 100 + ); //peeking at the input message + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); //reading the input message + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the second test... + //Writing 100, read 20, peek 20, read 80 + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 20, 0, &mut Some(&mut socket2)), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 80, 0, &mut Some(&mut socket2)), + 80 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(80) + &"\0".repeat(20)); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the third test... + //Writing 100, peek several times, read 100 + for _ in 0..4 { + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 10, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 10 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(10) + &"\0".repeat(90)); + } + for _ in 0..4 { + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + } + for _ in 0..4 { + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 30, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 30 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(30) + &"\0".repeat(70)); + } + for _ in 0..4 { + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 40, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 40 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(40) + &"\0".repeat(60)); + } + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the fourth test... + //Writing 50, peek 50 + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 50, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 50 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(50) + &"\0".repeat(50)); + + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!(cage2.close_syscall(sockfd), 0); + assert_eq!(cage2.close_syscall(serversockfd), 0); + + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + //connect to the server + interface::sleep(interface::RustDuration::from_millis(20)); + + assert_eq!(cage.connect_syscall(clientsockfd, &socket), 0); + + //send the data with delays so that the server can process the information cleanly + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(50)), 50, 0), + 50 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!(cage.close_syscall(clientsockfd), 0); + + //connect to the server with the other sockfd + assert_eq!(cage.connect_syscall(clientsockfd2, &socket), 0); + + //send the data with delays so that the server can process the information cleanly + assert_eq!( + cage.send_syscall(clientsockfd2, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd2, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd2, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd2, str2cbuf(&"A".repeat(50)), 50, 0), + 50 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!(cage.close_syscall(clientsockfd2), 0); + + thread.join().unwrap(); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_bind_multiple() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let mut sockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let socket = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50103u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); //127.0.0.1 + assert_eq!( + cage.setsockopt_syscall(sockfd, SOL_SOCKET, SO_REUSEPORT, 1), + 0 + ); + assert_eq!(cage.bind_syscall(sockfd, &socket), 0); + + let sockfd2 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + //allowing port reuse + assert_eq!( + cage.setsockopt_syscall(sockfd2, SOL_SOCKET, SO_REUSEPORT, 1), + 0 + ); + + assert_eq!(cage.bind_syscall(sockfd2, &socket), 0); + + //double listen should be allowed + assert_eq!(cage.listen_syscall(sockfd, 1), 0); + assert_eq!(cage.listen_syscall(sockfd2, 1), 0); + + //UDP bind should be allowed + sockfd = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + assert_eq!(cage.bind_syscall(sockfd, &socket), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_connect_basic_udp() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //should be okay... + let sockfd = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + let mut socket = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50103u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); //127.0.0.1 + assert_eq!(cage.connect_syscall(sockfd, &socket), 0); + + //should be able to retarget the socket + socket = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50104u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); //127.0.0.1 + assert_eq!(cage.connect_syscall(sockfd, &socket), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_getpeername() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //doing a few things with connect -- only UDP right now + let sockfd = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + let mut socket = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50103u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); //127.0.0.1 + let mut retsocket = interface::GenSockaddr::V4(interface::SockaddrV4::default()); //127.0.0.1 + + assert_eq!(cage.connect_syscall(sockfd, &socket), 0); + assert_eq!(cage.getpeername_syscall(sockfd, &mut retsocket), 0); + assert_eq!(retsocket, socket); + + //should be able to retarget + socket = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50104u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); //127.0.0.1 + assert_eq!(cage.connect_syscall(sockfd, &socket), 0); + assert_eq!(cage.getpeername_syscall(sockfd, &mut retsocket), 0); + assert_eq!(retsocket, socket); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_getsockname() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let sockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let mut retsocket = interface::GenSockaddr::V4(interface::SockaddrV4::default()); + + assert_eq!(cage.getsockname_syscall(sockfd, &mut retsocket), 0); + assert_eq!(retsocket.port(), 0); + assert_eq!( + retsocket.addr(), + interface::GenIpaddr::V4(interface::V4Addr::default()) + ); + + let socket = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50104u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); //127.0.0.1 + + assert_eq!(cage.bind_syscall(sockfd, &socket), 0); + assert_eq!(cage.getsockname_syscall(sockfd, &mut retsocket), 0); + assert_eq!(retsocket, socket); + + //checking that we cannot rebind the socket + assert_eq!(cage.bind_syscall(sockfd, &socket), -(Errno::EINVAL as i32)); //already bound so should fail + assert_eq!(cage.getsockname_syscall(sockfd, &mut retsocket), 0); + assert_eq!(retsocket, socket); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_listen() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + assert!(serversockfd > 0); + assert!(clientsockfd > 0); + + //binding to a socket + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 53003_u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 10), 0); + + //forking the cage to get another cage with the same information + assert_eq!(cage.fork_syscall(2), 0); + + let thread = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + let mut socket2 = interface::GenSockaddr::V4(interface::SockaddrV4::default()); + assert!(cage2.accept_syscall(serversockfd, &mut socket2) > 0); //really can only make sure that the fd is valid + + assert_eq!(cage2.close_syscall(serversockfd), 0); + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + interface::sleep(interface::RustDuration::from_millis(100)); + assert_eq!(cage.connect_syscall(clientsockfd, &socket), 0); + + let mut retsocket = interface::GenSockaddr::V4(interface::SockaddrV4::default()); + assert_eq!(cage.getsockname_syscall(clientsockfd, &mut retsocket), 0); + assert_ne!(retsocket, socket); + + assert_eq!(cage.close_syscall(serversockfd), 0); + + thread.join().unwrap(); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_poll() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let filefd = cage.open_syscall("/netpolltest.txt", O_CREAT | O_EXCL | O_RDWR, S_IRWXA); + assert!(filefd > 0); + + let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd1 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd2 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + let port: u16 = 53009; + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 from bytes above + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 4), 0); + + let serverpoll = interface::PollStruct { + fd: serversockfd, + events: POLLIN, + revents: 0, + }; + let filepoll = interface::PollStruct { + fd: filefd, + events: POLLIN, + revents: 0, + }; + let mut polled = vec![serverpoll, filepoll]; + + cage.fork_syscall(2); + //client 1 connects to the server to send and recv data... + let thread1 = interface::helper_thread(move || { + interface::sleep(interface::RustDuration::from_millis(30)); + let cage2 = interface::cagetable_getref(2); + + assert_eq!(cage2.connect_syscall(clientsockfd1, &socket), 0); + assert_eq!( + cage2.send_syscall(clientsockfd1, str2cbuf(&"test"), 4, 0), + 4 + ); + //giving it a longer pause time to that it can process all of the data that it is recieving + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!(cage2.close_syscall(serversockfd), 0); + cage2.exit_syscall(EXIT_SUCCESS); + }); + + cage.fork_syscall(3); + //client 2 connects to the server to send and recv data... + let thread2 = interface::helper_thread(move || { + //give it a longer time so that it can sufficiently process all of the data + interface::sleep(interface::RustDuration::from_millis(45)); + let cage3 = interface::cagetable_getref(3); + + assert_eq!(cage3.connect_syscall(clientsockfd2, &socket), 0); + assert_eq!( + cage3.send_syscall(clientsockfd2, str2cbuf(&"test"), 4, 0), + 4 + ); + + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!(cage3.close_syscall(serversockfd), 0); + cage3.exit_syscall(EXIT_SUCCESS); + }); + + //acting as the server and processing the request + let thread3 = interface::helper_thread(move || { + let mut infds: Vec; + let mut outfds: Vec; + for _counter in 0..600 { + //start a while true loop for processing requests + let pollretvalue = cage.poll_syscall( + &mut polled.as_mut_slice(), + Some(interface::RustDuration::ZERO), + ); + assert!(pollretvalue >= 0); + + infds = vec![]; + outfds = vec![]; + + for polledfile in &mut polled { + if polledfile.revents & POLLIN != 0 { + infds.push(polledfile.fd); + } + if polledfile.revents & POLLOUT != 0 { + outfds.push(polledfile.fd); + } + } + + //check for any activity in the input sockets + for sockfd in infds { + //If the socket returned was listerner socket, then there's a new connection + //so we accept it, and put the client socket in the list of inputs. + if sockfd == serversockfd { + let port: u16 = 53009; + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let mut addr = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 from bytes above + + let newsockfd = cage.accept_syscall(sockfd, &mut addr); + polled.push(interface::PollStruct { + fd: newsockfd, + events: POLLIN, + revents: 0, + }) + } else if sockfd == filefd { + //Write to a file... + assert_eq!(cage.write_syscall(sockfd, str2cbuf("test"), 4), 4); + assert_eq!(cage.lseek_syscall(sockfd, 0, SEEK_SET), 0); + //Once the write is successful into a file, modify the file descriptor so that its ready for reading out of the file. + for polledfile in &mut polled { + if polledfile.fd == sockfd { + polledfile.events = POLLOUT; + break; + } + } + } else { + //If the socket is in established conn., then we recv the data. If there's no data, then close the client socket. + let mut buf = sizecbuf(4); + let mut result: i32; + loop { + result = cage.recv_syscall(sockfd, buf.as_mut_ptr(), 4, 0); + if result != -libc::EINTR { + assert_eq!(result & !4, 0); //This must be 0 or 4 to be correct, either the socket is good for recieving or it's closed + break; // if the error was EINTR, retry the syscall + } + } + if result == 4 { + assert_eq!(cbuf2str(&buf), "test"); + //This socket is ready for writing, modify the socket descriptor to be in read-write mode. This socket can write data out to network + for polledfile in &mut polled { + if polledfile.fd == sockfd { + polledfile.events = POLLOUT; + break; + } + } + } else { + //No data means remote socket closed, hence close the client socket in server, also remove this socket from polling. + assert_eq!(cage.close_syscall(sockfd), 0); + polled.retain(|x| x.fd != sockfd); + } + } + } + + for sockfd in outfds { + if sockfd == filefd { + let mut read_buf1 = sizecbuf(4); + assert_eq!(cage.read_syscall(sockfd, read_buf1.as_mut_ptr(), 4), 4); + assert_eq!(cbuf2str(&read_buf1), "test"); + //test for file finished, remove from polling. + polled.retain(|x| x.fd != sockfd); + } else { + //Data is sent out of this socket, it's no longer ready for writing, modify it only read mode. + assert_eq!(cage.send_syscall(sockfd, str2cbuf(&"test"), 4, 0), 4); + for polledfile in &mut polled { + if polledfile.fd == sockfd { + polledfile.events = POLLIN; + } + } + } + } + } + assert_eq!(cage.close_syscall(serversockfd), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + thread1.join().unwrap(); + thread2.join().unwrap(); + thread3.join().unwrap(); + + lindrustfinalize(); + } + + pub fn ut_lind_net_recvfrom() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + let port: u16 = 53001; + + //making sure that the assigned fd's are valid + assert!(serversockfd > 0); + assert!(clientsockfd > 0); + + //binding to a socket + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 1), 0); //we are only allowing for one client at a time + + //forking the cage to get another cage with the same information + assert_eq!(cage.fork_syscall(2), 0); + + //creating a thread for the server so that the information can be sent between the two threads + let thread = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + interface::sleep(interface::RustDuration::from_millis(100)); + + let mut socket2 = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); //127.0.0.1 + let sockfd = cage2.accept_syscall(serversockfd, &mut socket2); //really can only make sure that the fd is valid + assert!(sockfd > 0); + + //process the first test... + //Writing 100, then peek 100, then read 100 + let mut buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 100, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 100 + ); //peeking at the input message + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); //reading the input message + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the second test... + //Writing 100, read 20, peek 20, read 80 + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 20, 0, &mut Some(&mut socket2)), + 20 + ); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 80, 0, &mut Some(&mut socket2)), + 80 + ); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the third test... + //Writing 100, peek several times, read 100 + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 10, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 10 + ); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 30, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 30 + ); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 40, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 40 + ); + buf = sizecbuf(100); + } + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the fourth test... + //Writing 50, peek 50 + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 50, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 50 + ); + + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!(cage2.close_syscall(sockfd), 0); + assert_eq!(cage2.close_syscall(serversockfd), 0); + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + //connect to the server + assert_eq!(cage.connect_syscall(clientsockfd, &socket), 0); + + //send the data with delays so that the server can process the information cleanly + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(50)), 50, 0), + 50 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + thread.join().unwrap(); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_select() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let filefd = cage.open_syscall("/netselecttest.txt", O_CREAT | O_EXCL | O_RDWR, S_IRWXA); + assert!(filefd > 0); + + let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd1 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd2 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + let port: u16 = 53008; + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 from bytes above + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 4), 0); + + // allocate spaces for fd_set bitmaps + let inputs = &mut interface::FdSet::new(); + let outputs = &mut interface::FdSet::new(); + + inputs.set(serversockfd); + inputs.set(filefd); + outputs.set(filefd); + + assert_eq!(inputs.is_set(serversockfd), true); + assert_eq!(inputs.is_set(filefd), true); + assert_eq!(outputs.is_set(filefd), true); + + assert_eq!(cage.fork_syscall(2), 0); + assert_eq!(cage.fork_syscall(3), 0); + + assert_eq!(cage.close_syscall(clientsockfd1), 0); + assert_eq!(cage.close_syscall(clientsockfd2), 0); + + // these barriers ensures that the clients finish the connect before we do the select + let barrier = Arc::new(Barrier::new(3)); + let barrier_clone1 = barrier.clone(); + let barrier_clone2 = barrier.clone(); + + //client 1 connects to the server to send and recv data... + let threadclient1 = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + assert_eq!(cage2.close_syscall(serversockfd), 0); + + assert_eq!(cage2.connect_syscall(clientsockfd1, &socket), 0); + barrier_clone1.wait(); + assert_eq!(cage2.send_syscall(clientsockfd1, str2cbuf("test"), 4, 0), 4); + + interface::sleep(interface::RustDuration::from_millis(1)); + + let mut buf = sizecbuf(4); + assert_eq!(cage2.recv_syscall(clientsockfd1, buf.as_mut_ptr(), 4, 0), 4); + assert_eq!(cbuf2str(&buf), "test"); + + assert_eq!(cage2.close_syscall(clientsockfd1), 0); + cage2.exit_syscall(EXIT_SUCCESS); + }); + + //client 2 connects to the server to send and recv data... + let threadclient2 = interface::helper_thread(move || { + let cage3 = interface::cagetable_getref(3); + + assert_eq!(cage3.close_syscall(serversockfd), 0); + + assert_eq!(cage3.connect_syscall(clientsockfd2, &socket), 0); + barrier_clone2.wait(); + assert_eq!(cage3.send_syscall(clientsockfd2, str2cbuf("test"), 4, 0), 4); + + interface::sleep(interface::RustDuration::from_millis(1)); + + let mut buf = sizecbuf(4); + let mut result: i32; + loop { + result = cage3.recv_syscall(clientsockfd2, buf.as_mut_ptr(), 4, 0); + if result != -libc::EINTR { + break; // if the error was EINTR, retry the syscall + } + } + assert_eq!(result, 4); + assert_eq!(cbuf2str(&buf), "test"); + + assert_eq!(cage3.close_syscall(clientsockfd2), 0); + cage3.exit_syscall(EXIT_SUCCESS); + }); + barrier.wait(); + //acting as the server and processing the request + for _counter in 0..600 { + let select_result = cage.select_syscall( + 11, + Some(inputs), + Some(outputs), + None, + Some(interface::RustDuration::ZERO), + ); + assert!(select_result >= 0); + + //Check for any activity in any of the Input sockets... + //for sock in binputs { + for sock in 0..FD_SET_MAX_FD { + if !inputs.is_set(sock) { + continue; + } + + //If the socket returned was listerner socket, then there's a new conn., so we accept it, and put the client socket in the list of Inputs. + if sock == serversockfd { + let mut sockgarbage = + interface::GenSockaddr::V4(interface::SockaddrV4::default()); + let sockfd = cage.accept_syscall(sock as i32, &mut sockgarbage); //really can only make sure that the fd is valid + assert!(sockfd > 0); + inputs.set(sockfd); + outputs.set(sockfd) + } else if sock == filefd { + //Write to a file... + assert_eq!(cage.write_syscall(sock as i32, str2cbuf("test"), 4), 4); + assert_eq!(cage.lseek_syscall(sock as i32, 0, SEEK_SET), 0); + inputs.clear(sock) + } else { + //If the socket is in established conn., then we recv the data. If there's no data, then close the client socket. + let mut buf = sizecbuf(4); + let mut recvresult: i32; + loop { + recvresult = cage.recv_syscall(sock as i32, buf.as_mut_ptr(), 4, 0); + if recvresult != -libc::EINTR { + break; // if the error was EINTR, retry the syscall + } + } + if recvresult == 4 { + if cbuf2str(&buf) == "test" { + outputs.set(sock); + continue; + } + } else { + assert_eq!(recvresult, 0); + } + assert_eq!(cage.close_syscall(sock as i32), 0); + inputs.clear(sock); + } + } + + //for sock in boutputs { + for sock in 0..FD_SET_MAX_FD { + if !outputs.is_set(sock) { + continue; + } + if sock == filefd { + let mut buf = sizecbuf(4); + assert_eq!(cage.read_syscall(sock as i32, buf.as_mut_ptr(), 4), 4); + assert_eq!(cbuf2str(&buf), "test"); + outputs.clear(sock); + } else { + //Data is sent out this socket, it's no longer ready for writing remove this socket from writefd's. + assert_eq!(cage.send_syscall(sock as i32, str2cbuf("test"), 4, 0), 4); + outputs.clear(sock); + } + } + } + assert_eq!(cage.close_syscall(serversockfd), 0); + + threadclient1.join().unwrap(); + threadclient2.join().unwrap(); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_shutdown() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + assert!(serversockfd > 0); + assert!(clientsockfd > 0); + + //binding to a socket + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50431_u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 10), 0); + + //forking the cage to get another cage with the same information + assert_eq!(cage.fork_syscall(2), 0); + + let thread = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + + interface::sleep(interface::RustDuration::from_millis(100)); + + let mut socket2 = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50431_u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); //127.0.0.1 + let fd = cage2.accept_syscall(serversockfd, &mut socket2); + assert!(fd > 0); + + assert_eq!(cage2.netshutdown_syscall(fd, SHUT_RD), 0); + assert_eq!(cage2.send_syscall(fd, str2cbuf("random string"), 13, 0), 13); + assert_eq!(cage2.netshutdown_syscall(fd, SHUT_RDWR), 0); + assert_ne!(cage2.netshutdown_syscall(fd, SHUT_RDWR), 0); //should fail + + assert_eq!(cage2.close_syscall(serversockfd), 0); + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + assert_eq!(cage.connect_syscall(clientsockfd, &socket), 0); + + let mut retsocket = interface::GenSockaddr::V4(interface::SockaddrV4::default()); + assert_eq!(cage.getsockname_syscall(clientsockfd, &mut retsocket), 0); + assert_ne!(retsocket, socket); + + thread.join().unwrap(); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_socket() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let mut sockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let sockfd2 = cage.socket_syscall(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + let sockfd3 = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + let sockfd4 = cage.socket_syscall(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + //checking that the fd's are correct + assert!(sockfd > 0); + assert!(sockfd2 > 0); + assert!(sockfd3 > 0); + assert!(sockfd4 > 0); + + //let's check an illegal operation... + let sockfddomain = cage.socket_syscall(AF_UNIX, SOCK_DGRAM, 0); + assert!(sockfddomain > 0); + + sockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + assert!(sockfd > 0); + + assert_eq!(cage.close_syscall(sockfd), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_socketoptions() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let sockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + assert!(sockfd > 0); + + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50115_u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 + assert_eq!(cage.bind_syscall(sockfd, &socket), 0); + assert_eq!(cage.listen_syscall(sockfd, 4), 0); + + //set and get some options: + let mut optstore = -12; + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_REUSEPORT, &mut optstore), + 0 + ); + assert_eq!(optstore, 0); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_LINGER, &mut optstore), + 0 + ); + assert_eq!(optstore, 0); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_KEEPALIVE, &mut optstore), + 0 + ); + assert_eq!(optstore, 0); + + //linger... + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_LINGER, &mut optstore), + 0 + ); + assert_eq!(optstore, 0); + assert_eq!(cage.setsockopt_syscall(sockfd, SOL_SOCKET, SO_LINGER, 1), 0); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_LINGER, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + + //check the options + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_REUSEPORT, &mut optstore), + 0 + ); + assert_eq!(optstore, 0); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_LINGER, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_KEEPALIVE, &mut optstore), + 0 + ); + assert_eq!(optstore, 0); + + //reuseport... + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_REUSEPORT, &mut optstore), + 0 + ); + assert_eq!(optstore, 0); + assert_eq!( + cage.setsockopt_syscall(sockfd, SOL_SOCKET, SO_REUSEPORT, 1), + 0 + ); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_REUSEPORT, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + + //check the options + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_REUSEPORT, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_LINGER, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_KEEPALIVE, &mut optstore), + 0 + ); + assert_eq!(optstore, 0); + + //keep alive... + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_KEEPALIVE, &mut optstore), + 0 + ); + assert_eq!(optstore, 0); + assert_eq!( + cage.setsockopt_syscall(sockfd, SOL_SOCKET, SO_KEEPALIVE, 1), + 0 + ); + + //check the options + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_REUSEPORT, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_LINGER, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_KEEPALIVE, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + + assert_eq!( + cage.setsockopt_syscall(sockfd, SOL_SOCKET, SO_SNDBUF, 1000), + 0 + ); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_SNDBUF, &mut optstore), + 0 + ); + assert_eq!(optstore, 1000); + + assert_eq!( + cage.setsockopt_syscall(sockfd, SOL_SOCKET, SO_RCVBUF, 2000), + 0 + ); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_RCVBUF, &mut optstore), + 0 + ); + assert_eq!(optstore, 2000); + + //check the options + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_REUSEPORT, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_LINGER, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + assert_eq!( + cage.getsockopt_syscall(sockfd, SOL_SOCKET, SO_KEEPALIVE, &mut optstore), + 0 + ); + assert_eq!(optstore, 1); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_socketpair() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + let mut socketpair = interface::SockPair::default(); + assert_eq!( + Cage::socketpair_syscall(cage.clone(), AF_UNIX, SOCK_STREAM, 0, &mut socketpair), + 0 + ); + let cage2 = cage.clone(); + + let thread = interface::helper_thread(move || { + let mut buf = sizecbuf(10); + loop { + let result = cage2.recv_syscall(socketpair.sock2, buf.as_mut_ptr(), 10, 0); + if result != -libc::EINTR { + break; // if the error was EINTR, retry the syscall + } + } + assert_eq!(cbuf2str(&buf), "test\0\0\0\0\0\0"); + + interface::sleep(interface::RustDuration::from_millis(30)); + assert_eq!( + cage2.send_syscall(socketpair.sock2, str2cbuf("Socketpair Test"), 15, 0), + 15 + ); + }); + + assert_eq!( + cage.send_syscall(socketpair.sock1, str2cbuf("test"), 4, 0), + 4 + ); + + let mut buf2 = sizecbuf(15); + loop { + let result = cage.recv_syscall(socketpair.sock1, buf2.as_mut_ptr(), 15, 0); + if result != -libc::EINTR { + break; // if the error was EINTR, retry the syscall + } + } + let str2 = cbuf2str(&buf2); + assert_eq!(str2, "Socketpair Test"); + + thread.join().unwrap(); + + assert_eq!(cage.close_syscall(socketpair.sock1), 0); + assert_eq!(cage.close_syscall(socketpair.sock2), 0); + + // end of the socket pair test (note we are only supporting AF_UNIX and TCP) + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_udp_bad_bind() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let sockfd = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + assert!(sockfd > 0); //checking that the sockfd is valid + + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50116_u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 + + let _sockaddr2 = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50303_u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket2 = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 + + assert_eq!(cage.bind_syscall(sockfd, &socket), 0); + assert_eq!(cage.connect_syscall(sockfd, &socket2), 0); + + //now the bind should fail... + assert_ne!(cage.bind_syscall(sockfd, &socket), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + pub fn ut_lind_net_udp_simple() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //just going to test the basic connect with UDP now... + let serverfd = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + let clientfd = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + + let socket = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50121_u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }); + + assert!(serverfd > 0); + assert!(clientfd > 0); + + //forking the cage to get another cage with the same information + assert_eq!(cage.fork_syscall(2), 0); + let thread = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + assert_eq!(cage2.bind_syscall(serverfd, &socket), 0); + + interface::sleep(interface::RustDuration::from_millis(30)); + + let mut buf = sizecbuf(10); + loop { + let result = cage2.recv_syscall(serverfd, buf.as_mut_ptr(), 10, 0); + if result != -libc::EINTR { + break; // if the error was EINTR, retry the syscall + } + } + assert_eq!(cbuf2str(&buf), "test\0\0\0\0\0\0"); + + interface::sleep(interface::RustDuration::from_millis(30)); + loop { + let result = cage2.recv_syscall(serverfd, buf.as_mut_ptr(), 10, 0); + if result != -libc::EINTR { + assert_eq!(result, 5); + break; // if the error was EINTR, retry the syscall + } + } + assert_eq!(cbuf2str(&buf), "test2\0\0\0\0\0"); + + assert_eq!(cage2.close_syscall(serverfd), 0); + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + interface::sleep(interface::RustDuration::from_millis(50)); + let mut buf2 = str2cbuf("test"); + assert_eq!(cage.sendto_syscall(clientfd, buf2, 4, 0, &socket), 4); + let sendsockfd2 = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + assert!(sendsockfd2 > 0); + + let sockaddr2 = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 50992_u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket2 = interface::GenSockaddr::V4(sockaddr2); //127.0.0.1 + + interface::sleep(interface::RustDuration::from_millis(50)); + + buf2 = str2cbuf("test2"); + assert_eq!(cage.bind_syscall(sendsockfd2, &socket2), 0); + assert_eq!(cage.sendto_syscall(sendsockfd2, buf2, 5, 0, &socket), 5); + + thread.join().unwrap(); + + assert_eq!(cage.close_syscall(sendsockfd2), 0); + assert_eq!(cage.close_syscall(clientfd), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_udp_connect() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //getting the sockets set up... + let listenfd = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + let sendfd = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 51111_u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 + + assert!(listenfd > 0); + assert!(sendfd > 0); + + assert_eq!(cage.bind_syscall(listenfd, &socket), 0); + + //forking the cage to get another cage with the same information + assert_eq!(cage.fork_syscall(2), 0); + + let thread = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + + interface::sleep(interface::RustDuration::from_millis(20)); + let mut buf = sizecbuf(16); + loop { + let result = cage2.recv_syscall(listenfd, buf.as_mut_ptr(), 16, 0); + if result != -libc::EINTR { + assert_eq!(result, 16); + break; // if the error was EINTR, retry the syscall + } + } + assert_ne!(buf, sizecbuf(16)); + assert_eq!(cbuf2str(&buf), "UDP Connect Test"); + + assert_eq!(cage2.close_syscall(listenfd), 0); + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + assert_eq!(cage.connect_syscall(sendfd, &socket), 0); + interface::sleep(interface::RustDuration::from_millis(50)); + assert_eq!( + cage.send_syscall(sendfd, str2cbuf("UDP Connect Test"), 16, 0), + 16 + ); + thread.join().unwrap(); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_gethostname() { + //Assuming DEFAULT_HOSTNAME == "Lind" and change of hostname is not allowed + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let mut buf = vec![0u8; 5]; + let bufptr: *mut u8 = &mut buf[0]; + assert_eq!( + cage.gethostname_syscall(bufptr, -1), + -(Errno::EINVAL as i32) + ); + assert_eq!(cage.gethostname_syscall(bufptr, 5), 0); + assert_eq!(std::str::from_utf8(&buf).unwrap(), "Lind\0"); + + let mut buf = vec![0u8; 5]; + let bufptr: *mut u8 = &mut buf[0]; + assert_eq!(cage.gethostname_syscall(bufptr, 4), 0); + assert_eq!(std::str::from_utf8(&buf).unwrap(), "Lind\0"); + + let mut buf = vec![0u8; 5]; + let bufptr: *mut u8 = &mut buf[0]; + assert_eq!(cage.gethostname_syscall(bufptr, 2), 0); + assert_eq!(std::str::from_utf8(&buf).unwrap(), "Li\0\0\0"); + + let mut buf = vec![0u8; 4]; + let bufptr: *mut u8 = &mut buf[0]; + assert_eq!(cage.gethostname_syscall(bufptr, 4), 0); + assert_eq!(std::str::from_utf8(&buf).unwrap(), "Lind"); + + let mut buf = vec![0u8; 2]; + let bufptr: *mut u8 = &mut buf[0]; + assert_eq!(cage.gethostname_syscall(bufptr, 2), 0); + assert_eq!(std::str::from_utf8(&buf).unwrap(), "Li"); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + pub fn ut_lind_net_dns_rootserver_ping() { + //https://w3.cs.jmu.edu/kirkpams/OpenCSF/Books/csf/html/UDPSockets.html + #[repr(C)] + struct DnsHeader { + xid: u16, + flags: u16, + qdcount: u16, + ancount: u16, + nscount: u16, + arcount: u16, + } + + /* Structure of the bytes for an IPv4 answer */ + #[repr(C, packed(1))] + struct DnsRecordAT { + compression: u16, + typ: u16, + clas: u16, + ttl: u32, + length: u16, + addr: interface::V4Addr, + } + + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let dnssocket = cage.socket_syscall(AF_INET, SOCK_DGRAM, 0); + assert!(dnssocket > 0); + + let dnsh = DnsHeader { + xid: 0x1234u16.to_be(), + flags: 0x0100u16.to_be(), + qdcount: 0x0001u16.to_be(), + ancount: 0, + nscount: 0, + arcount: 0, + }; + + //specify payload information for dns request + let hostname = "\x0Bengineering\x03nyu\x03edu\0".to_string().into_bytes(); //numbers signify how many characters until next dot + let dnstype = 1u16; + let dnsclass = 1u16; + + //construct packet + let packetlen = std::mem::size_of::() + + hostname.len() + + std::mem::size_of::() + + std::mem::size_of::(); + let mut packet = vec![0u8; packetlen]; + + let packslice = packet.as_mut_slice(); + let mut pslen = std::mem::size_of::(); + unsafe { + let dnss = ::std::slice::from_raw_parts( + ((&dnsh) as *const DnsHeader) as *const u8, + std::mem::size_of::(), + ); + packslice[..pslen].copy_from_slice(dnss); + } + packslice[pslen..pslen + hostname.len()].copy_from_slice(hostname.as_slice()); + pslen += hostname.len(); + packslice[pslen..pslen + 2].copy_from_slice(&dnstype.to_be_bytes()); + packslice[pslen + 2..pslen + 4].copy_from_slice(&dnsclass.to_be_bytes()); + + //send packet + let mut dnsaddr = interface::GenSockaddr::V4(interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: 53u16.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([208, 67, 222, 222]), + }, + padding: 0, + }); //opendns ip addr + assert_eq!( + cage.sendto_syscall(dnssocket, packslice.as_ptr(), packslice.len(), 0, &dnsaddr), + packslice.len() as i32 + ); + + let mut dnsresp = [0u8; 512]; + + //recieve DNS response + loop { + let result = cage.recvfrom_syscall( + dnssocket, + dnsresp.as_mut_ptr(), + 512, + 0, + &mut Some(&mut dnsaddr), + ); + + if result != -libc::EINTR { + assert!(result >= 0); + break; + } + // if the error was EINTR, retry the syscall + } + + //extract packet header + let response_header = unsafe { &*(dnsresp.as_ptr() as *const DnsHeader) }; + assert_eq!(u16::from_be(response_header.flags) & 0xf, 0); + + //skip over the name + let mut nameptr = std::mem::size_of::(); + while dnsresp[nameptr] != 0 { + nameptr += dnsresp[nameptr] as usize + 1; + } + + //next we need to skip the null byte, qtype, and qclass to extract the main response payload + let recordptr = + dnsresp.as_ptr().wrapping_offset(nameptr as isize + 5) as *const DnsRecordAT; + let record = unsafe { &*recordptr }; + let addr = u32::from_be(record.addr.s_addr); + assert_eq!(addr, 0x23ac5973); //check that what is returned is the actual ip, 35.172.89.115 + //assert_eq!(record.addr.s_addr, 0x7359ac23); //check that what is returned is the actual ip, 35.172.89.115 + + lindrustfinalize(); + } + + pub fn ut_lind_net_domain_socket() { + //bind net zero test reformatted for domain sockets + + let clientsockfilename = "/client.sock"; + let serversockfilename = "/server.sock"; + + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + //both the server and the socket are run from this file + let serversockfd = cage.socket_syscall(AF_UNIX, SOCK_STREAM, 0); + let clientsockfd = cage.socket_syscall(AF_UNIX, SOCK_STREAM, 0); + + //making sure that the assigned fd's are valid + assert!(serversockfd > 0); + assert!(clientsockfd > 0); + + //binding to a socket + let serversockaddr = + interface::new_sockaddr_unix(AF_UNIX as u16, serversockfilename.as_bytes()); + let serversocket = interface::GenSockaddr::Unix(serversockaddr); + let clientsockaddr = + interface::new_sockaddr_unix(AF_UNIX as u16, clientsockfilename.as_bytes()); + let clientsocket = interface::GenSockaddr::Unix(clientsockaddr); + + assert_eq!(cage.bind_syscall(serversockfd, &serversocket), 0); + assert_eq!(cage.bind_syscall(clientsockfd, &clientsocket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 1), 0); //we are only allowing for one client at a time + + //forking the cage to get another cage with the same information + assert_eq!(cage.fork_syscall(2), 0); + + //creating a thread for the server so that the information can be sent between the two threads + let thread = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + let mut socket2 = interface::GenSockaddr::Unix(interface::new_sockaddr_unix( + AF_UNIX as u16, + "".as_bytes(), + )); // blank unix sockaddr + + let sockfd = cage2.accept_syscall(serversockfd, &mut socket2); //really can only make sure that the fd is valid + assert!(sockfd > 0); + + interface::sleep(interface::RustDuration::from_millis(100)); + + //process the first test... + //Writing 100, then peek 100, then read 100 + let mut buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 100, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 100 + ); //peeking at the input message + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); //reading the input message + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the second test... + //Writing 100, read 20, peek 20, read 80 + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 20, 0, &mut Some(&mut socket2)), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 80, 0, &mut Some(&mut socket2)), + 80 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(80) + &"\0".repeat(20)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the third test... + //Writing 100, peek several times, read 100 + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 10, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 10 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(10) + &"\0".repeat(90)); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 20, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 20 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(20) + &"\0".repeat(80)); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 30, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 30 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(30) + &"\0".repeat(70)); + buf = sizecbuf(100); + } + for _ in 0..4 { + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 40, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 40 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(40) + &"\0".repeat(60)); + buf = sizecbuf(100); + } + assert_eq!( + cage2.recvfrom_syscall(sockfd, buf.as_mut_ptr(), 100, 0, &mut Some(&mut socket2)), + 100 + ); + assert_eq!(cbuf2str(&buf), &"A".repeat(100)); + buf = sizecbuf(100); + + interface::sleep(interface::RustDuration::from_millis(200)); + + //process the fourth test... + //Writing 50, peek 50 + assert_eq!( + cage2.recvfrom_syscall( + sockfd, + buf.as_mut_ptr(), + 50, + MSG_PEEK, + &mut Some(&mut socket2) + ), + 50 + ); + assert_eq!(cbuf2str(&buf), "A".repeat(50) + &"\0".repeat(50)); + assert_eq!(cage2.close_syscall(sockfd), 0); + + assert_eq!(cage2.close_syscall(serversockfd), 0); + + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + //connect to the server + interface::sleep(interface::RustDuration::from_millis(20)); + + assert_eq!(cage.connect_syscall(clientsockfd, &serversocket), 0); + + //send the data with delays so that the server can process the information cleanly + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(100)), 100, 0), + 100 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!( + cage.send_syscall(clientsockfd, str2cbuf(&"A".repeat(50)), 50, 0), + 50 + ); + interface::sleep(interface::RustDuration::from_millis(100)); + + assert_eq!(cage.close_syscall(clientsockfd), 0); + + thread.join().unwrap(); + + cage.unlink_syscall(serversockfilename); + cage.unlink_syscall(clientsockfilename); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + /* Creates an epoll instance, registers the server socket and file descriptor with epoll, and then wait for events using + epoll_wait_syscall(). It handles the events based on their types (EPOLLIN or EPOLLOUT) and performs the necessary operations + like accepting new connections, sending/receiving data, and modifying the event flags */ + pub fn ut_lind_net_epoll() { + lindrustinit(0); + let cage = interface::cagetable_getref(1); + + let filefd = cage.open_syscall("/netepolltest.txt", O_CREAT | O_EXCL | O_RDWR, S_IRWXA); + assert!(filefd > 0); + + let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd1 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd2 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + // Create and set up the file descriptor and sockets + let port: u16 = 53019; + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 4), 0); + + let mut event_list = vec![ + EpollEvent { + events: EPOLLIN as u32, + fd: serversockfd, + }, + EpollEvent { + events: EPOLLIN as u32, + fd: filefd, + }, + ]; + + cage.fork_syscall(2); + // Client 1 connects to the server to send and recv data + let thread1 = interface::helper_thread(move || { + interface::sleep(interface::RustDuration::from_millis(30)); + let cage2 = interface::cagetable_getref(2); + // Connect to server and send data + assert_eq!(cage2.connect_syscall(clientsockfd1, &socket), 0); + assert_eq!( + cage2.send_syscall(clientsockfd1, str2cbuf(&"test"), 4, 0), + 4 + ); + // Wait for data processing, give it a longer pause time so that it can process all of the data received + interface::sleep(interface::RustDuration::from_millis(100)); + // Close the server socket and exit the thread + assert_eq!(cage2.close_syscall(serversockfd), 0); + cage2.exit_syscall(EXIT_SUCCESS); + }); + + cage.fork_syscall(3); + // Client 2 connects to the server to send and recv data + let thread2 = interface::helper_thread(move || { + interface::sleep(interface::RustDuration::from_millis(45)); + let cage3 = interface::cagetable_getref(3); + // Connect to server and send data + assert_eq!(cage3.connect_syscall(clientsockfd2, &socket), 0); + assert_eq!( + cage3.send_syscall(clientsockfd2, str2cbuf(&"test"), 4, 0), + 4 + ); + + interface::sleep(interface::RustDuration::from_millis(100)); + // Close the server socket and exit the thread + assert_eq!(cage3.close_syscall(serversockfd), 0); + cage3.exit_syscall(EXIT_SUCCESS); + }); + + // Acting as the server and processing the request + let thread3 = interface::helper_thread(move || { + let epfd = cage.epoll_create_syscall(1); + assert!(epfd > 0); + + assert_eq!( + cage.epoll_ctl_syscall(epfd, EPOLL_CTL_ADD, serversockfd, &mut event_list[0]), + 0 + ); + assert_eq!( + cage.epoll_ctl_syscall(epfd, EPOLL_CTL_ADD, filefd, &mut event_list[1]), + 0 + ); + // Event processing loop + for _counter in 0..600 { + let num_events = cage.epoll_wait_syscall( + epfd, + &mut event_list, + 1, + Some(interface::RustDuration::ZERO), + ); + assert!(num_events >= 0); + + // Wait for events using epoll_wait_syscall + for event in &mut event_list[..num_events as usize] { + // Check for any activity in the input socket and if there are events ready for reading + if event.events & (EPOLLIN as u32) != 0 { + // If the socket returned was listener socket, then there's a new connection + if event.fd == serversockfd { + // Handle new connections + let port: u16 = 53019; + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let mut addr = interface::GenSockaddr::V4(sockaddr); // 127.0.0.1 from bytes above + let newsockfd = cage.accept_syscall(serversockfd, &mut addr); + let event = interface::EpollEvent { + events: EPOLLIN as u32, + fd: newsockfd, + }; + // Error raised to indicate that the socket file descriptor couldn't be added to the epoll instance + assert_eq!( + cage.epoll_ctl_syscall(epfd, EPOLL_CTL_ADD, newsockfd, &event), + 0 + ); + } else if event.fd == filefd { + // Handle writing to the file + // Update + assert_eq!(cage.write_syscall(filefd, str2cbuf("test"), 4), 4); + assert_eq!(cage.lseek_syscall(filefd, 0, SEEK_SET), 0); + event.events = EPOLLOUT as u32; + } else { + // Handle receiving data from established connections + let mut buf = sizecbuf(4); + let recres = cage.recv_syscall(event.fd, buf.as_mut_ptr(), 4, 0); + assert_eq!(recres & !4, 0); + if recres == 4 { + assert_eq!(cbuf2str(&buf), "test"); + event.events = EPOLLOUT as u32; + } else { + assert_eq!(cage.close_syscall(event.fd), 0); + } + } + } + + if event.events & (EPOLLOUT as u32) != 0 { + // Check if there are events ready for writing + if event.fd == filefd { + // Handle reading from the file + let mut read_buf1 = sizecbuf(4); + assert_eq!(cage.read_syscall(filefd, read_buf1.as_mut_ptr(), 4), 4); + assert_eq!(cbuf2str(&read_buf1), "test"); + } else { + // Handle sending data over connections + assert_eq!(cage.send_syscall(event.fd, str2cbuf(&"test"), 4, 0), 4); + event.events = EPOLLIN as u32; + } + } + } + } + + // Close the server socket and exit the thread + assert_eq!(cage.close_syscall(serversockfd), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + thread1.join().unwrap(); + thread2.join().unwrap(); + thread3.join().unwrap(); + + lindrustfinalize(); + } +} diff --git a/src/tools/fs_utils.rs b/src/tools/fs_utils.rs new file mode 100644 index 00000000..e60f9c30 --- /dev/null +++ b/src/tools/fs_utils.rs @@ -0,0 +1,247 @@ +#![feature(lazy_cell)] +#![feature(rustc_private)] //for private crate imports for tests +#![feature(vec_into_raw_parts)] +#![feature(duration_constants)] +#![allow(unused)] + +/// Author: Jonathan Singer +/// +/// This file provides a command line interface for interacting in certain ways with the lind file +/// system from the host, such as copying files from the host into lind, removing files and +/// directories, and listing files in the lind fs, and more +/// +/// This interface should be sufficient for anything we'd need to do between lind and the host +use std::env; +use std::iter::repeat; + +mod interface; +mod lib_fs_utils; +mod safeposix; +use lib_fs_utils::*; +use safeposix::{ + cage::*, + dispatcher::{lindrustfinalize, lindrustinit}, + filesystem::*, +}; + +fn lind_tree(cage: &Cage, path: &str, indentlevel: usize) { + let mut lindstat_res: StatData = StatData::default(); + let stat_us = cage.stat_syscall(path, &mut lindstat_res); + if stat_us == 0 { + if !is_dir(lindstat_res.st_mode) { + eprintln!("Tree must be run on a directory!"); + return; + } + + //visit the children of this directory, and show them all as being children of this directory in the tree + visit_children( + cage, + path, + Some(indentlevel), + |childcage, childpath, isdir, childindentlevelopt| { + let childindentlevel = childindentlevelopt.unwrap(); + //lines to connect non-parent ancestors to their remaining children(if any) + print!("{}", "| ".repeat(childindentlevel)); + //line to connect parent to its child + print!("{}", "|---"); + //actually print out file name + println!("{}", childpath); + + //recursive call for child + if isdir { + lind_tree(childcage, childpath, childindentlevel + 1); + } + }, + ); + } else { + eprintln!("No such directory exists!"); + } +} + +fn lind_ls(cage: &Cage, path: &str) { + let mut lindstat_res: StatData = StatData::default(); + let stat_us = cage.stat_syscall(path, &mut lindstat_res); + + if stat_us == 0 { + if is_dir(lindstat_res.st_mode) { + //for each child, if it's a directory, print its name with a slash, otherwise omit the slash + visit_children(cage, path, None, |_childcage, childpath, isdir, _| { + if isdir { + print!("{}/ ", childpath); + } else { + print!("{} ", childpath); + } + }); + } else { + print!("{} ", path); + } + println!(); + } else { + eprintln!("No such file exists!"); + } +} + +fn print_usage() { + println!( + " +Usage: lind_fs_utils [commandname] [arguments...] + +Where commandname is one of the following: + +cp [hostsource] [linddest] : Copies files from the host file system into the lind filesystem. + For example, cp bar/etc/passwd /etc/passwd will copy the + former file in the host file system to the latter in lind's fs. + Directories are handled recursively, cp bar/etc /etc/ will make a + directory at /etc in the lind fs, and then populate it with all + of the files in the root fs. +deltree [linddir] : Delete a directory on the lind file system and all it contains +format : Make a new blank fs, removing the current one +help : Print this message +ls [lindpath] : List the contents of a lind file system directory +mkdir [linddir1...] : Create a lind file system directory (for each arg) +rm [lindfile1...] : Delete a file on the lind file system +rmdir [linddir1...] : Delete a directory on the lind file system +tree [startlindpath] : Print the lindfs file tree starting at the specified directory + Assumes root directory if no starting path is specified. +update [hostsource] [linddest] : Copies files from the host file system into the lind filesystem. + Will not copy files if the host and lind files are identical. + For example, update bar/etc/passwd /etc/passwd will copy the + former file in the host file system to the latter in lind's fs if + the latter does not exist or is not identical to the former. + Directories are handled recursively, cp bar/etc /etc/ will make a + directory at /etc in the lind fs, and then populate it with all + of the files in the root fs, with identical files being skipped. +" + ); +} + +fn main() { + lindrustinit(0); // no verbosity + let mut args = env::args(); + let utilcage = Cage { + cageid: 0, + cwd: interface::RustLock::new(interface::RustRfc::new(interface::RustPathBuf::from("/"))), + parent: 0, + filedescriptortable: init_fdtable(), + cancelstatus: interface::RustAtomicBool::new(false), + getgid: interface::RustAtomicI32::new(-1), + getuid: interface::RustAtomicI32::new(-1), + getegid: interface::RustAtomicI32::new(-1), + geteuid: interface::RustAtomicI32::new(-1), + rev_shm: interface::Mutex::new(vec![]), + mutex_table: interface::RustLock::new(vec![]), + cv_table: interface::RustLock::new(vec![]), + sem_table: interface::RustHashMap::new(), + thread_table: interface::RustHashMap::new(), + signalhandler: interface::RustHashMap::new(), + sigset: interface::RustHashMap::new(), + pendingsigset: interface::RustHashMap::new(), + main_threadid: interface::RustAtomicU64::new(0), + interval_timer: interface::IntervalTimer::new(0), + }; + + args.next(); //first arg is executable, we don't care + let command = if let Some(cmd) = args.next() { + cmd + } else { + print_usage(); + return; //print usage + }; + + match command.as_str() { + "help" | "usage" => { + print_usage(); + } + + "cp" => { + let source = args.next().expect("cp needs 2 arguments"); + let dest = args.next().expect("cp needs 2 arguments"); + args.next() + .and_then:: Option>(|_| { + panic!("cp cannot take more than 2 arguments") + }); + cp_dir_into_lind( + &utilcage, + interface::RustPath::new(&source), + dest.as_str(), + true, + ); + } + + "update" => { + let source = args.next().expect("update needs 2 arguments"); + let dest = args.next().expect("update needs 2 arguments"); + args.next() + .and_then:: Option>(|_| { + panic!("update cannot take more than 2 arguments") + }); + update_dir_into_lind(&utilcage, interface::RustPath::new(&source), dest.as_str()); + } + + "ls" => { + let file = args.next().expect("ls needs 1 argument"); + args.next() + .and_then:: Option>(|_| { + panic!("ls cannot take more than 1 argument") + }); + lind_ls(&utilcage, file.as_str()); + } + + "tree" => { + let rootdir = if let Some(dirstr) = args.next() { + dirstr + } else { + "/".to_owned() + }; + println!("{}", rootdir); + lind_tree(&utilcage, rootdir.as_str(), 0); + } + + "format" => { + lind_deltree(&utilcage, "/"); //This doesn't actually fully remove all of the linddata files... TODO: debug + + let mut logobj = LOGMAP.write(); + let log = logobj.take().unwrap(); + let _close = log.close().unwrap(); + drop(logobj); + let _logremove = interface::removefile(LOGFILENAME.to_string()); + + format_fs(); + return; + } + + "deltree" => { + let rootdir = args.next().expect("deltree needs 1 argument"); + args.next() + .and_then:: Option>(|_| { + panic!("deltree cannot take more than 1 argument") + }); + lind_deltree(&utilcage, rootdir.as_str()); + } + + "rm" => { + for file in args { + utilcage.unlink_syscall(file.as_str()); + } + } + + "mkdir" => { + for dir in args { + utilcage.mkdir_syscall(dir.as_str(), S_IRWXA); + } + } + + "rmdir" => { + for dir in args { + utilcage.chmod_syscall(dir.as_str(), S_IRWXA); + utilcage.rmdir_syscall(dir.as_str()); + } + } + + _ => { + eprintln!("Error, command unknown"); + return; + } + } + lindrustfinalize(); +} diff --git a/src/tools/interface b/src/tools/interface new file mode 120000 index 00000000..340b894b --- /dev/null +++ b/src/tools/interface @@ -0,0 +1 @@ +../interface/ \ No newline at end of file diff --git a/src/tools/lib_fs_utils.rs b/src/tools/lib_fs_utils.rs new file mode 100644 index 00000000..1f551d29 --- /dev/null +++ b/src/tools/lib_fs_utils.rs @@ -0,0 +1,320 @@ +#![allow(dead_code)] //suppress warning for these functions not being used in main library target + +use std::ffi::CStr; +use std::fs::File; +use std::io::{prelude, Read}; +use std::os::raw::c_char; + +use crate::interface; +use crate::interface::errnos::{syscall_error, Errno}; +use crate::interface::types::{ClippedDirent, CLIPPED_DIRENT_SIZE}; +use crate::safeposix::{cage::*, filesystem::*}; + +const LINUX_MAX_RW_COUNT: usize = 0x7FFFF000; + +//we currently handle symlinks as normal files + +pub fn update_dir_into_lind(cage: &Cage, hostfilepath: &interface::RustPath, lindfilepath: &str) { + if hostfilepath.exists() { + if let Ok(_) = hostfilepath.read_link() { + println!("following symlink at {:?} on host fs", hostfilepath); + } //if read_link succeeds it's a symlink, whose destination must exist because of the nature of the .exists function + } else { + eprintln!("Cannot locate file on host fs: {:?}", hostfilepath); + return; + } + + //update directly if not a directory on the host, otherwise recursively handle children + if hostfilepath.is_file() { + update_into_lind(cage, hostfilepath, lindfilepath); + } else { + let children = hostfilepath.read_dir().unwrap(); + for wrappedchild in children { + let child = wrappedchild.unwrap(); + let newlindpath = if lindfilepath.ends_with("/") { + format!("{}{}", lindfilepath, child.file_name().to_str().unwrap()) + } else { + format!("{}/{}", lindfilepath, child.file_name().to_str().unwrap()) + }; + update_dir_into_lind(cage, child.path().as_path(), newlindpath.as_str()); + } + } +} + +fn update_into_lind(cage: &Cage, hostfilepath: &interface::RustPath, lindfilepath: &str) { + if !hostfilepath.exists() || !hostfilepath.is_file() { + println!( + "{:?} does not exist or is not a regular file, skipping", + hostfilepath + ); + return; + } + let fmetadata = hostfilepath.metadata().unwrap(); + + let host_size = fmetadata.len(); + let mut lindstat_res: StatData = StatData::default(); + let stat_us = cage.stat_syscall(lindfilepath, &mut lindstat_res); + + let lind_exists; + let lind_isfile; + let lind_size; + if stat_us < 0 { + lind_exists = false; + lind_isfile = false; + lind_size = 0; + } else { + lind_exists = true; + lind_isfile = is_reg(lindstat_res.st_mode); + lind_size = lindstat_res.st_size; + } + + if lind_exists && !lind_isfile { + println!( + "{:?} on lind file system is not a regular file, skipping", + hostfilepath + ); + return; + } + + //compare files to tell whether they are identical + let samefile = if host_size as usize == lind_size { + let mut hostslice = vec![0u8; lind_size]; + let mut lindslice = vec![0u8; lind_size]; + let mut hostfile = File::open(hostfilepath).unwrap(); + hostfile.read(hostslice.as_mut_slice()).unwrap(); + let lindfd = cage.open_syscall(lindfilepath, O_RDONLY | O_CREAT, S_IRWXA); + cage.read_syscall(lindfd, lindslice.as_mut_ptr(), lind_size); + cage.close_syscall(lindfd); + hostslice == lindslice + } else { + false + }; + + //if they are not the same file, remove the lind file and replace it with the host file + if !samefile { + if lind_exists { + cage.unlink_syscall(lindfilepath); + println!("removing {} on lind file system", lindfilepath); + } + cp_into_lind(cage, hostfilepath, lindfilepath, true); + } else { + println!( + "Same files on host and lind--{:?} and {}, skipping", + hostfilepath, lindfilepath + ); + } +} + +pub fn cp_dir_into_lind( + cage: &Cage, + hostfilepath: &interface::RustPath, + lindfilepath: &str, + create_missing_dirs: bool, +) { + if hostfilepath.exists() { + if let Ok(_) = hostfilepath.read_link() { + println!("following symlink at {:?} on host fs", hostfilepath); + } //if read_link succeeds it's a symlink, whose destination must exist because of the nature of the .exists function + } else { + eprintln!("Cannot locate file on host fs: {:?}", hostfilepath); + return; + } + + //update directly if not a directory on the host, otherwise recursively handle children + if hostfilepath.is_file() { + cp_into_lind(cage, hostfilepath, lindfilepath, create_missing_dirs); + } else if hostfilepath.is_dir() { + let children = hostfilepath.read_dir().unwrap(); + for wrappedchild in children { + let child = wrappedchild.unwrap(); + let newlindpath = if lindfilepath.ends_with("/") { + format!("{}{}", lindfilepath, child.file_name().to_str().unwrap()) + } else { + format!("{}/{}", lindfilepath, child.file_name().to_str().unwrap()) + }; + cp_dir_into_lind( + cage, + child.path().as_path(), + newlindpath.as_str(), + create_missing_dirs, + ); + } + } +} + +fn cp_into_lind( + cage: &Cage, + hostfilepath: &interface::RustPath, + lindfilepath: &str, + create_missing_dirs: bool, +) { + if !hostfilepath.exists() { + eprintln!("Cannot locate file on host fs: {:?}", hostfilepath); + return; + } + if !hostfilepath.is_file() { + eprintln!("File is not a regular file on host fs: {:?}", hostfilepath); + return; + } + + let lindtruepath = normpath(convpath(lindfilepath), cage); + + //if a directory in the lindfilepath does not exist in the lind file system, create it! + let mut ancestor = interface::RustPathBuf::from("/"); + for component in lindtruepath.parent().unwrap().components() { + ancestor.push(component); + let mut lindstat_res: StatData = StatData::default(); + + //check whether file exists + let stat_us = cage.stat_syscall(ancestor.to_str().unwrap(), &mut lindstat_res); + if stat_us == 0 { + if !is_dir(lindstat_res.st_mode) { + eprintln!("Fatal error in trying to create child of non-directory file"); + return; + } + continue; + } + if stat_us != -(Errno::ENOENT as i32) { + eprintln!("Fatal error in trying to get lind file path"); + return; + } + + //check whether we are supposed to create missing directories, and whether we'd be + //clobbering anything to do so (if so error out) + if create_missing_dirs { + if cage.mkdir_syscall(ancestor.to_str().unwrap(), S_IRWXA) != 0 { + //let's not mirror stat data + eprintln!("Lind fs path does not exist but should not be created (is rooted at non-directory) {:?}", ancestor); + return; + } + } else { + eprintln!( + "Lind fs path does not exist but should not be created {:?}", + ancestor + ); + return; + } + } + + //copy file contents into lind file system + let mut host_fileobj = File::open(hostfilepath).unwrap(); + let mut filecontents: Vec = Vec::new(); + host_fileobj.read_to_end(&mut filecontents).unwrap(); + + let lindfd = cage.open_syscall( + lindtruepath.to_str().unwrap(), + O_CREAT | O_TRUNC | O_WRONLY, + S_IRWXA, + ); + assert!(lindfd >= 0); + + let veclen = filecontents.len(); + let mut writtenlen: usize = 0; + + //on Linux, write() (and similar system calls) will transfer at most 0x7ffff000 (2,147,479,552) bytes + //dividing filecontents into chunks of 0x7ffff000 (2,147,479,552) bytes and writing each chunk + for chunk in filecontents.chunks(LINUX_MAX_RW_COUNT) { + writtenlen += cage.write_syscall(lindfd, chunk.as_ptr(), chunk.len()) as usize; + } + //confirm that write succeeded + assert_eq!(veclen, writtenlen); + + //get diagnostic data to print + let mut lindstat_res: StatData = StatData::default(); + let _stat_us = cage.fstat_syscall(lindfd, &mut lindstat_res); + let inode = lindstat_res.st_ino; + + assert_eq!(cage.close_syscall(lindfd), 0); + + println!("Copied {:?} as {} ({})", hostfilepath, lindfilepath, inode); +} + +pub fn visit_children( + cage: &Cage, + path: &str, + arg: Option, + visitor: fn(&Cage, &str, bool, Option), +) { + //get buffer in which getdents will write its stuff + let mut bigbuffer = [0u8; 65536]; + let dentptr = bigbuffer.as_mut_ptr(); + + let dirfd = cage.open_syscall(path, O_RDONLY, 0); + assert!(dirfd >= 0); + + loop { + let direntres = cage.getdents_syscall(dirfd, dentptr, 65536); + + //if we've read every entry in this directory, we're done + if direntres == 0 { + break; + } + + let mut dentptrindex = 0isize; + + //while there are still more entries to read + while dentptrindex < direntres as isize { + //get information for where the next entry is (if relevant) + let clipped_dirent_ptr = dentptr.wrapping_offset(dentptrindex) as *mut ClippedDirent; + let clipped_dirent = unsafe { &*clipped_dirent_ptr }; + + //get the file name for the child + let cstrptr = dentptr.wrapping_offset(dentptrindex + CLIPPED_DIRENT_SIZE as isize); + let filenamecstr = unsafe { CStr::from_ptr(cstrptr as *const c_char) }; + let filenamestr = filenamecstr.to_str().unwrap(); + + dentptrindex += clipped_dirent.d_reclen as isize; + + //ignore these entries + if filenamestr == "." || filenamestr == ".." { + continue; + } + + let fullstatpath = if path.ends_with("/") { + [path, filenamestr].join("") + } else { + [path, "/", filenamestr].join("") + }; + + //stat to tell whether it's a directory + let mut lindstat_res: StatData = StatData::default(); + let _stat_us = cage.stat_syscall(fullstatpath.as_str(), &mut lindstat_res); + + //call the visitor function on the child path + visitor( + cage, + fullstatpath.as_str(), + is_dir(lindstat_res.st_mode), + arg, + ); + } + } + cage.close_syscall(dirfd); +} + +pub fn lind_deltree(cage: &Cage, path: &str) { + let mut lindstat_res: StatData = StatData::default(); + let stat_us = cage.stat_syscall(path, &mut lindstat_res); + + if stat_us == 0 { + if !is_dir(lindstat_res.st_mode) { + cage.unlink_syscall(path); + return; + } else { + //remove all children recursively + visit_children(cage, path, None, |childcage, childpath, isdir, _| { + if isdir { + lind_deltree(childcage, childpath); + } else { + childcage.unlink_syscall(childpath); + } + }); + + //remove specified directory now that it is empty + cage.chmod_syscall(path, S_IRWXA); + cage.rmdir_syscall(path); + } + } else { + eprintln!("No such directory exists!"); + } +} diff --git a/src/tools/safeposix b/src/tools/safeposix new file mode 120000 index 00000000..3b39e91d --- /dev/null +++ b/src/tools/safeposix @@ -0,0 +1 @@ +../safeposix/ \ No newline at end of file