├── .github ├── actions │ └── install-rust │ │ ├── README.md │ │ ├── action.yml │ │ └── main.js └── workflows │ └── main.yml ├── .gitignore ├── .rustfmt.toml ├── CODE_OF_CONDUCT.md ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── ORG_CODE_OF_CONDUCT.md ├── README.md ├── SECURITY.md ├── build.rs ├── examples ├── copy.rs ├── hello-error.rs └── hello-world.rs ├── src ├── async_std.rs ├── buffered │ ├── buf_duplexer.rs │ ├── buf_reader_line_writer.rs │ ├── buf_reader_line_writer_shim.rs │ └── mod.rs ├── lib.rs ├── lockers.rs ├── streams.rs └── tokio.rs └── tests ├── buffered.rs └── tests.rs /.github/actions/install-rust/README.md: -------------------------------------------------------------------------------- 1 | # install-rust 2 | 3 | A small github action to install `rustup` and a Rust toolchain. This is 4 | generally expressed inline, but it was repeated enough in this repository it 5 | seemed worthwhile to extract. 6 | 7 | Some gotchas: 8 | 9 | * Can't `--self-update` on Windows due to permission errors (a bug in Github 10 | Actions) 11 | * `rustup` isn't installed on macOS (a bug in Github Actions) 12 | 13 | When the above are fixed we should delete this action and just use this inline: 14 | 15 | ```yml 16 | - run: rustup update $toolchain && rustup default $toolchain 17 | shell: bash 18 | ``` 19 | -------------------------------------------------------------------------------- /.github/actions/install-rust/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Install Rust toolchain' 2 | description: 'Install both `rustup` and a Rust toolchain' 3 | 4 | inputs: 5 | toolchain: 6 | description: 'Default toolchan to install' 7 | required: false 8 | default: 'stable' 9 | 10 | runs: 11 | using: node20 12 | main: 'main.js' 13 | -------------------------------------------------------------------------------- /.github/actions/install-rust/main.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process'); 2 | const toolchain = process.env.INPUT_TOOLCHAIN; 3 | const fs = require('fs'); 4 | 5 | function set_env(name, val) { 6 | fs.appendFileSync(process.env['GITHUB_ENV'], `${name}=${val}\n`) 7 | } 8 | 9 | // Needed for now to get 1.24.2 which fixes a bug in 1.24.1 that causes issues 10 | // on Windows. 11 | if (process.platform === 'win32') { 12 | child_process.execFileSync('rustup', ['self', 'update']); 13 | } 14 | 15 | child_process.execFileSync('rustup', ['set', 'profile', 'minimal']); 16 | child_process.execFileSync('rustup', ['update', toolchain, '--no-self-update']); 17 | child_process.execFileSync('rustup', ['default', toolchain]); 18 | 19 | // Deny warnings on CI to keep our code warning-free as it lands in-tree. Don't 20 | // do this on nightly though since there's a fair amount of warning churn there. 21 | if (!toolchain.startsWith('nightly')) { 22 | set_env("RUSTFLAGS", "-D warnings"); 23 | } 24 | 25 | // Save disk space by avoiding incremental compilation, and also we don't use 26 | // any caching so incremental wouldn't help anyway. 27 | set_env("CARGO_INCREMENTAL", "0"); 28 | 29 | // Turn down debuginfo from 2 to 1 to help save disk space 30 | set_env("CARGO_PROFILE_DEV_DEBUG", "1"); 31 | set_env("CARGO_PROFILE_TEST_DEBUG", "1"); 32 | 33 | if (process.platform === 'darwin') { 34 | set_env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "unpacked"); 35 | set_env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "unpacked"); 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | rustfmt: 11 | name: Rustfmt 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | submodules: true 17 | - uses: ./.github/actions/install-rust 18 | with: 19 | toolchain: stable 20 | - run: cargo fmt --all -- --check 21 | 22 | test: 23 | name: Test 24 | runs-on: ${{ matrix.os }} 25 | strategy: 26 | matrix: 27 | build: [stable, nightly, windows] 28 | include: 29 | - build: stable 30 | os: ubuntu-latest 31 | rust: stable 32 | - build: nightly 33 | os: ubuntu-latest 34 | rust: nightly 35 | - build: windows 36 | os: windows-latest 37 | rust: stable 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | with: 42 | submodules: true 43 | - uses: ./.github/actions/install-rust 44 | with: 45 | toolchain: ${{ matrix.rust }} 46 | - run: cargo test --workspace --all-features 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # This file tells tools we use rustfmt. We use the default settings. 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | *Note*: this Code of Conduct pertains to individuals' behavior. Please also see the [Organizational Code of Conduct][OCoC]. 4 | 5 | ## Our Pledge 6 | 7 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to creating a positive environment include: 12 | 13 | * Using welcoming and inclusive language 14 | * Being respectful of differing viewpoints and experiences 15 | * Gracefully accepting constructive criticism 16 | * Focusing on what is best for the community 17 | * Showing empathy towards other community members 18 | 19 | Examples of unacceptable behavior by participants include: 20 | 21 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 22 | * Trolling, insulting/derogatory comments, and personal or political attacks 23 | * Public or private harassment 24 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 25 | * Other conduct which could reasonably be considered inappropriate in a professional setting 26 | 27 | ## Our Responsibilities 28 | 29 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 30 | 31 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 32 | 33 | ## Scope 34 | 35 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 36 | 37 | ## Enforcement 38 | 39 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Bytecode Alliance CoC team at [report@bytecodealliance.org](mailto:report@bytecodealliance.org). The CoC team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The CoC team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 40 | 41 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the Bytecode Alliance's leadership. 42 | 43 | ## Attribution 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 46 | 47 | [OCoC]: https://github.com/bytecodealliance/io-streams/blob/main/ORG_CODE_OF_CONDUCT.md 48 | [homepage]: https://www.contributor-covenant.org 49 | [version]: https://www.contributor-covenant.org/version/1/4/ 50 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `io-streams` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `io-streams` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `io-streams` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `io-streams` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "io-streams" 3 | version = "0.16.3" 4 | description = "Unbuffered and unlocked I/O streams" 5 | authors = ["Dan Gohman "] 6 | edition = "2021" 7 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 8 | keywords = ["io"] 9 | categories = ["rust-patterns"] 10 | repository = "https://github.com/sunfishcode/io-streams" 11 | exclude = ["/.github"] 12 | 13 | [dependencies] 14 | async-std = { version = "1.12.0", optional = true } 15 | char-device = { version = "0.16.0", optional = true } 16 | duplex = "0.16.0" 17 | layered-io = { version = "0.23.0", optional = true } 18 | memchr = "2.3.4" 19 | parking = "2.0.0" 20 | socketpair = { version = "0.19.0", optional = true } 21 | system-interface = { version = "0.27.0", features = ["use_os_pipe", "socketpair"] } 22 | terminal-io = { version = "0.19.0", optional = true } 23 | tokio = { version = "1.8.1", optional = true, features = ["fs", "net"] } 24 | io-extras = { version = "0.18.0", features = ["os_pipe"] } 25 | utf8-io = { version = "0.19.0", optional = true } 26 | io-lifetimes = { version = "2.0.0", features = ["os_pipe"], default-features = false } 27 | 28 | # WASI doesn't support pipes yet 29 | [target.'cfg(not(target_os = "wasi"))'.dependencies] 30 | os_pipe = { version = "1.0.0", features = ["io_safety"] } 31 | 32 | [target.'cfg(not(windows))'.dependencies] 33 | rustix = { version = "0.38.0", features = ["pipe"] } 34 | 35 | [dev-dependencies] 36 | anyhow = "1.0.38" 37 | cap-tempfile = "3.0.0" 38 | char-device = "0.16.0" 39 | duplex = { version = "0.16.0", features = ["char-device"] } 40 | 41 | [features] 42 | default = [] 43 | use_socketpair = ["socketpair", "duplex/socketpair", "system-interface/socketpair"] 44 | use_char_device = ["char-device", "duplex/char-device", "system-interface/char-device"] 45 | use_async_std = ["async-std"] 46 | use_tokio = ["tokio", "io-lifetimes/tokio"] 47 | #use_async_std_char_device = ["use_char_device", "char-device/use_async_std", "duplex/use_async_std_char_device", "system-interface/char-device"] 48 | #use_async_std_socketpair = ["use_socketpair", "socketpair/use_async_std", "duplex/use_async_std_socketpair", "system-interface/socketpair"] 49 | #use_tokio_char_device = ["use_char_device", "char-device/use_tokio", "duplex/use_tokio_char_device", "system-interface/char-device"] 50 | #use_tokio_socketpair = ["use_socketpair", "socketpair/use_tokio", "duplex/use_tokio_socketpair", "system-interface/socketpair"] 51 | 52 | [package.metadata.docs.rs] 53 | features = ["use_char_device", "use_socketpair"] 54 | #"use_async_std_char_device", "use_async_std_socketpair", "use_tokio_char_device", "use_tokio_socketpair" 55 | 56 | [lints.rust.unexpected_cfgs] 57 | level = "warn" 58 | check-cfg = [ 59 | 'cfg(bench)', 60 | 'cfg(read_initializer)', 61 | 'cfg(can_vector)', 62 | 'cfg(clamp)', 63 | 'cfg(extend_one)', 64 | 'cfg(pattern)', 65 | 'cfg(seek_stream_len)', 66 | 'cfg(shrink_to)', 67 | 'cfg(toowned_clone_into)', 68 | 'cfg(try_reserve)', 69 | 'cfg(unix_socket_peek)', 70 | 'cfg(windows_by_handle)', 71 | 'cfg(write_all_vectored)', 72 | 'cfg(windows_file_type_ext)', 73 | ] 74 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-Apache-2.0_WITH_LLVM-exception: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | 205 | --- LLVM Exceptions to the Apache 2.0 License ---- 206 | 207 | As an exception, if, as a result of your compiling your source code, portions 208 | of this Software are embedded into an Object form of such source code, you 209 | may redistribute such embedded portions in such Object form without complying 210 | with the conditions of Sections 4(a), 4(b) and 4(d) of the License. 211 | 212 | In addition, if you combine or link compiled forms of this Software with 213 | software that is licensed under the GPLv2 ("Combined Software") and if a 214 | court of competent jurisdiction determines that the patent provision (Section 215 | 3), the indemnity provision (Section 9) or other Section of the License 216 | conflicts with the conditions of the GPLv2, you may retroactively and 217 | prospectively choose to deem waived or otherwise exclude such Section(s) of 218 | the License, but only in their entirety and only with respect to the Combined 219 | Software. 220 | 221 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /ORG_CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Bytecode Alliance Organizational Code of Conduct (OCoC) 2 | 3 | *Note*: this Code of Conduct pertains to organizations' behavior. Please also see the [Individual Code of Conduct](CODE_OF_CONDUCT.md). 4 | 5 | ## Preamble 6 | 7 | The Bytecode Alliance (BA) welcomes involvement from organizations, 8 | including commercial organizations. This document is an 9 | *organizational* code of conduct, intended particularly to provide 10 | guidance to commercial organizations. It is distinct from the 11 | [Individual Code of Conduct (ICoC)](CODE_OF_CONDUCT.md), and does not 12 | replace the ICoC. This OCoC applies to any group of people acting in 13 | concert as a BA member or as a participant in BA activities, whether 14 | or not that group is formally incorporated in some jurisdiction. 15 | 16 | The code of conduct described below is not a set of rigid rules, and 17 | we did not write it to encompass every conceivable scenario that might 18 | arise. For example, it is theoretically possible there would be times 19 | when asserting patents is in the best interest of the BA community as 20 | a whole. In such instances, consult with the BA, strive for 21 | consensus, and interpret these rules with an intent that is generous 22 | to the community the BA serves. 23 | 24 | While we may revise these guidelines from time to time based on 25 | real-world experience, overall they are based on a simple principle: 26 | 27 | *Bytecode Alliance members should observe the distinction between 28 | public community functions and private functions — especially 29 | commercial ones — and should ensure that the latter support, or at 30 | least do not harm, the former.* 31 | 32 | ## Guidelines 33 | 34 | * **Do not cause confusion about Wasm standards or interoperability.** 35 | 36 | Having an interoperable WebAssembly core is a high priority for 37 | the BA, and members should strive to preserve that core. It is fine 38 | to develop additional non-standard features or APIs, but they 39 | should always be clearly distinguished from the core interoperable 40 | Wasm. 41 | 42 | Treat the WebAssembly name and any BA-associated names with 43 | respect, and follow BA trademark and branding guidelines. If you 44 | distribute a customized version of software originally produced by 45 | the BA, or if you build a product or service using BA-derived 46 | software, use names that clearly distinguish your work from the 47 | original. (You should still provide proper attribution to the 48 | original, of course, wherever such attribution would normally be 49 | given.) 50 | 51 | Further, do not use the WebAssembly name or BA-associated names in 52 | other public namespaces in ways that could cause confusion, e.g., 53 | in company names, names of commercial service offerings, domain 54 | names, publicly-visible social media accounts or online service 55 | accounts, etc. It may sometimes be reasonable, however, to 56 | register such a name in a new namespace and then immediately donate 57 | control of that account to the BA, because that would help the project 58 | maintain its identity. 59 | 60 | For further guidance, see the BA Trademark and Branding Policy 61 | [TODO: create policy, then insert link]. 62 | 63 | * **Do not restrict contributors.** If your company requires 64 | employees or contractors to sign non-compete agreements, those 65 | agreements must not prevent people from participating in the BA or 66 | contributing to related projects. 67 | 68 | This does not mean that all non-compete agreements are incompatible 69 | with this code of conduct. For example, a company may restrict an 70 | employee's ability to solicit the company's customers. However, an 71 | agreement must not block any form of technical or social 72 | participation in BA activities, including but not limited to the 73 | implementation of particular features. 74 | 75 | The accumulation of experience and expertise in individual persons, 76 | who are ultimately free to direct their energy and attention as 77 | they decide, is one of the most important drivers of progress in 78 | open source projects. A company that limits this freedom may hinder 79 | the success of the BA's efforts. 80 | 81 | * **Do not use patents as offensive weapons.** If any BA participant 82 | prevents the adoption or development of BA technologies by 83 | asserting its patents, that undermines the purpose of the 84 | coalition. The collaboration fostered by the BA cannot include 85 | members who act to undermine its work. 86 | 87 | * **Practice responsible disclosure** for security vulnerabilities. 88 | Use designated, non-public reporting channels to disclose technical 89 | vulnerabilities, and give the project a reasonable period to 90 | respond, remediate, and patch. [TODO: optionally include the 91 | security vulnerability reporting URL here.] 92 | 93 | Vulnerability reporters may patch their company's own offerings, as 94 | long as that patching does not significantly delay the reporting of 95 | the vulnerability. Vulnerability information should never be used 96 | for unilateral commercial advantage. Vendors may legitimately 97 | compete on the speed and reliability with which they deploy 98 | security fixes, but withholding vulnerability information damages 99 | everyone in the long run by risking harm to the BA project's 100 | reputation and to the security of all users. 101 | 102 | * **Respect the letter and spirit of open source practice.** While 103 | there is not space to list here all possible aspects of standard 104 | open source practice, some examples will help show what we mean: 105 | 106 | * Abide by all applicable open source license terms. Do not engage 107 | in copyright violation or misattribution of any kind. 108 | 109 | * Do not claim others' ideas or designs as your own. 110 | 111 | * When others engage in publicly visible work (e.g., an upcoming 112 | demo that is coordinated in a public issue tracker), do not 113 | unilaterally announce early releases or early demonstrations of 114 | that work ahead of their schedule in order to secure private 115 | advantage (such as marketplace advantage) for yourself. 116 | 117 | The BA reserves the right to determine what constitutes good open 118 | source practices and to take action as it deems appropriate to 119 | encourage, and if necessary enforce, such practices. 120 | 121 | ## Enforcement 122 | 123 | Instances of organizational behavior in violation of the OCoC may 124 | be reported by contacting the Bytecode Alliance CoC team at 125 | [report@bytecodealliance.org](mailto:report@bytecodealliance.org). The 126 | CoC team will review and investigate all complaints, and will respond 127 | in a way that it deems appropriate to the circumstances. The CoC team 128 | is obligated to maintain confidentiality with regard to the reporter of 129 | an incident. Further details of specific enforcement policies may be 130 | posted separately. 131 | 132 | When the BA deems an organization in violation of this OCoC, the BA 133 | will, at its sole discretion, determine what action to take. The BA 134 | will decide what type, degree, and duration of corrective action is 135 | needed, if any, before a violating organization can be considered for 136 | membership (if it was not already a member) or can have its membership 137 | reinstated (if it was a member and the BA canceled its membership due 138 | to the violation). 139 | 140 | In practice, the BA's first approach will be to start a conversation, 141 | with punitive enforcement used only as a last resort. Violations 142 | often turn out to be unintentional and swiftly correctable with all 143 | parties acting in good faith. 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

io-streams

3 | 4 |

5 | Unbuffered and unlocked I/O streams 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | This crate defines [`StreamReader`], [`StreamWriter`], and [`StreamDuplexer`] 16 | types which provide safe, owning, unbuffered, and unlocked access to a raw I/O 17 | stream, such as standard input, standard output, files, sockets, pipes, or 18 | character devices. It also supports a "piped thread" concept, where an 19 | arbitrary `Box` or `Box` can be provided, 20 | and the I/O is performed on a thread and connecting to the `StreamReader` or 21 | `StreamWriter` with a [pipe], and a "socketed thread" concept, where a provided 22 | function is called on a thread and connected to the main thread via a 23 | bidirectional socket. 24 | 25 | This crate also defines [`AsyncStreamReader`], [`AsyncStreamWriter`], and 26 | [`AsyncStreamDuplexer`], which are async functions that work with `async-std`. 27 | And [`TokioStreamReader`], [`TokioStreamWriter`], and [`TokioStreamDuplexer`], 28 | which are async functions that work with `tokio`. Not all features are 29 | supported yet, and they aren't fully optimized yet, but basic file and socket 30 | support is in place. 31 | 32 | On Posix-ish platforms, including limited support for WASI, these types just 33 | contain a single file descriptor (and implement [`AsFd`]), plus any 34 | resources needed to safely hold the file descriptor live. On Windows, they 35 | contain an enum holding either `RawHandle` or `RawSocket`. 36 | 37 | Since these types are unbuffered, it's advisable for most use cases to wrap 38 | them in buffering types such as [`std::io::BufReader`], [`std::io::BufWriter`], 39 | [`std::io::LineWriter`], [`io_streams::BufDuplexer`], or 40 | [`io_streams::BufReaderLineWriter`]. 41 | 42 | Rust's [`std::io::Stdin`] and [`std::io::Stdout`] are always buffered, while 43 | its [`std::fs::File`] and [`std::net::TcpStream`] are unbuffered. A key purpose 44 | of the `io_streams` crate is to abstract over the underlying inputs and outputs 45 | without adding buffering, so that buffering can be applied without redundancy. 46 | 47 | This crate locks `stdio::io::Stdin` and `std::io::Stdout` while it has their 48 | corresponding streams open, to prevent accidental mixing of buffered and 49 | unbuffered output on the same stream. Attempts to use the buffered streams when 50 | they are locked will block indefinitely. 51 | 52 | Support for async-std and tokio in char-device and socketpair is temporarily 53 | disabled until those crates contain the needed implementations of the 54 | I/O safety traits. 55 | 56 | [`StreamReader`]: https://docs.rs/io-streams/latest/io_streams/struct.StreamReader.html 57 | [`StreamWriter`]: https://docs.rs/io-streams/latest/io_streams/struct.StreamWriter.html 58 | [`StreamDuplexer`]: https://docs.rs/io-streams/latest/io_streams/struct.StreamDuplexer.html 59 | [`AsyncStreamReader`]: https://docs.rs/io-streams/latest/io_streams/struct.AsyncStreamReader.html 60 | [`AsyncStreamWriter`]: https://docs.rs/io-streams/latest/io_streams/struct.AsyncStreamWriter.html 61 | [`AsyncStreamDuplexer`]: https://docs.rs/io-streams/latest/io_streams/struct.AsyncStreamDuplexer.html 62 | [`TokioStreamReader`]: https://docs.rs/io-streams/latest/io_streams/struct.TokioStreamReader.html 63 | [`TokioStreamWriter`]: https://docs.rs/io-streams/latest/io_streams/struct.TokioStreamWriter.html 64 | [`TokioStreamDuplexer`]: https://docs.rs/io-streams/latest/io_streams/struct.TokioStreamDuplexer.html 65 | [`io_streams::BufDuplexer`]: https://docs.rs/io-streams/latest/io_streams/struct.BufDuplexer.html 66 | [`io_streams::BufReaderLineWriter`]: https://docs.rs/io-streams/latest/io_streams/struct.BufReaderLineWriter.html 67 | [`std::io::Stdin`]: https://doc.rust-lang.org/std/io/struct.Stdin.html 68 | [`std::io::Stdout`]: https://doc.rust-lang.org/std/io/struct.Stdout.html 69 | [`std::io::BufReader`]: https://doc.rust-lang.org/std/io/struct.BufReader.html 70 | [`std::io::BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html 71 | [`std::io::LineWriter`]: https://doc.rust-lang.org/std/io/struct.LineWriter.html 72 | [`AsFd`]: https://docs.rs/io-lifetimes/0.2.0/io_lifetimes/trait.AsFd.html 73 | [pipe]: https://crates.io/crates/os_pipe 74 | [`std::fs::File`]: https://doc.rust-lang.org/std/fs/struct.File.html 75 | [`std::net::TcpStream`]: https://doc.rust-lang.org/std/net/struct.TcpStream.html 76 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Building secure foundations for software development is at the core of what we do in the Bytecode Alliance. Contributions of external security researchers are a vital part of that. 4 | 5 | ## Scope 6 | 7 | If you believe you've found a security issue in any website, service, or software owned or operated by the Bytecode Alliance, we encourage you to notify us. 8 | 9 | ## How to Submit a Report 10 | 11 | To submit a vulnerability report to the Bytecode Alliance, please contact us at [security@bytecodealliance.org](mailto:security@bytecodealliance.org). Your submission will be reviewed and validated by a member of our security team. 12 | 13 | ## Safe Harbor 14 | 15 | The Bytecode Alliance supports safe harbor for security researchers who: 16 | 17 | * Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our services. 18 | * Only interact with accounts you own or with explicit permission of the account holder. If you do encounter Personally Identifiable Information (PII) contact us immediately, do not proceed with access, and immediately purge any local information. 19 | * Provide us with a reasonable amount of time to resolve vulnerabilities prior to any disclosure to the public or a third-party. 20 | 21 | We will consider activities conducted consistent with this policy to constitute "authorized" conduct and will not pursue civil action or initiate a complaint to law enforcement. We will help to the extent we can if legal action is initiated by a third party against you. 22 | 23 | Please submit a report to us before engaging in conduct that may be inconsistent with or unaddressed by this policy. 24 | 25 | ## Preferences 26 | 27 | * Please provide detailed reports with reproducible steps and a clearly defined impact. 28 | * Submit one vulnerability per report. 29 | * Social engineering (e.g. phishing, vishing, smishing) is prohibited. 30 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env::var; 2 | use std::io::Write; 3 | 4 | fn main() { 5 | use_feature_or_nothing("can_vector"); // https://github.com/rust-lang/rust/issues/69941 6 | use_feature_or_nothing("clamp"); // https://github.com/rust-lang/rust/issues/44095 7 | use_feature_or_nothing("extend_one"); // https://github.com/rust-lang/rust/issues/72631 8 | use_feature_or_nothing("pattern"); // https://github.com/rust-lang/rust/issues/27721 9 | use_feature_or_nothing("seek_stream_len"); // https://github.com/rust-lang/rust/issues/59359 10 | use_feature_or_nothing("shrink_to"); // https://github.com/rust-lang/rust/issues/56431 11 | use_feature_or_nothing("toowned_clone_into"); // https://github.com/rust-lang/rust/issues/41263 12 | use_feature_or_nothing("try_reserve"); // https://github.com/rust-lang/rust/issues/56431 13 | use_feature_or_nothing("unix_socket_peek"); // https://github.com/rust-lang/rust/issues/76923 14 | use_feature_or_nothing("windows_by_handle"); // https://github.com/rust-lang/rust/issues/63010 15 | use_feature_or_nothing("write_all_vectored"); // https://github.com/rust-lang/rust/issues/70436 16 | // https://doc.rust-lang.org/unstable-book/library-features/windows-file-type-ext.html 17 | use_feature_or_nothing("windows_file_type_ext"); 18 | 19 | // Don't rerun this on changes other than build.rs, as we only depend on 20 | // the rustc version. 21 | println!("cargo:rerun-if-changed=build.rs"); 22 | } 23 | 24 | fn use_feature_or_nothing(feature: &str) { 25 | if has_feature(feature) { 26 | use_feature(feature); 27 | } 28 | } 29 | 30 | fn use_feature(feature: &str) { 31 | println!("cargo:rustc-cfg={}", feature); 32 | } 33 | 34 | /// Test whether the rustc at `var("RUSTC")` supports the given feature. 35 | fn has_feature(feature: &str) -> bool { 36 | can_compile(format!( 37 | "#![allow(stable_features)]\n#![feature({})]", 38 | feature 39 | )) 40 | } 41 | 42 | /// Test whether the rustc at `var("RUSTC")` can compile the given code. 43 | fn can_compile>(test: T) -> bool { 44 | use std::process::Stdio; 45 | 46 | let out_dir = var("OUT_DIR").unwrap(); 47 | let rustc = var("RUSTC").unwrap(); 48 | let target = var("TARGET").unwrap(); 49 | 50 | // Use `RUSTC_WRAPPER` if it's set, unless it's set to an empty string, as 51 | // documented [here]. 52 | // [here]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads 53 | let wrapper = var("RUSTC_WRAPPER") 54 | .ok() 55 | .and_then(|w| if w.is_empty() { None } else { Some(w) }); 56 | 57 | let mut cmd = if let Some(wrapper) = wrapper { 58 | let mut cmd = std::process::Command::new(wrapper); 59 | // The wrapper's first argument is supposed to be the path to rustc. 60 | cmd.arg(rustc); 61 | cmd 62 | } else { 63 | std::process::Command::new(rustc) 64 | }; 65 | 66 | cmd.arg("--crate-type=rlib") // Don't require `main`. 67 | .arg("--emit=metadata") // Do as little as possible but still parse. 68 | .arg("--target") 69 | .arg(target) 70 | .arg("--out-dir") 71 | .arg(out_dir); // Put the output somewhere inconsequential. 72 | 73 | // If Cargo wants to set RUSTFLAGS, use that. 74 | if let Ok(rustflags) = var("CARGO_ENCODED_RUSTFLAGS") { 75 | if !rustflags.is_empty() { 76 | for arg in rustflags.split('\x1f') { 77 | cmd.arg(arg); 78 | } 79 | } 80 | } 81 | 82 | let mut child = cmd 83 | .arg("-") // Read from stdin. 84 | .stdin(Stdio::piped()) // Stdin is a pipe. 85 | .stderr(Stdio::null()) // Errors from feature detection aren't interesting and can be confusing. 86 | .spawn() 87 | .unwrap(); 88 | 89 | writeln!(child.stdin.take().unwrap(), "{}", test.as_ref()).unwrap(); 90 | 91 | child.wait().unwrap().success() 92 | } 93 | -------------------------------------------------------------------------------- /examples/copy.rs: -------------------------------------------------------------------------------- 1 | use io_streams::{StreamReader, StreamWriter}; 2 | use std::io::copy; 3 | 4 | fn main() -> anyhow::Result<()> { 5 | let mut input = StreamReader::stdin()?; 6 | let mut output = StreamWriter::stdout()?; 7 | copy(&mut input, &mut output)?; 8 | Ok(()) 9 | } 10 | -------------------------------------------------------------------------------- /examples/hello-error.rs: -------------------------------------------------------------------------------- 1 | use io_streams::{StreamReader, StreamWriter}; 2 | use std::io::copy; 3 | 4 | fn main() -> anyhow::Result<()> { 5 | #[cfg(not(target_os = "wasi"))] // WASI doesn't support pipes yet 6 | let mut input = StreamReader::str("hello world\n")?; 7 | 8 | #[cfg(target_os = "wasi")] 9 | let mut input = StreamReader::stdin()?; 10 | assert!(!cfg!(target_os = "wasi"), "WASI doesn't support pipes yet"); 11 | 12 | let mut output = StreamWriter::stderr()?; 13 | copy(&mut input, &mut output)?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /examples/hello-world.rs: -------------------------------------------------------------------------------- 1 | use io_streams::{StreamReader, StreamWriter}; 2 | use std::io::copy; 3 | 4 | fn main() -> anyhow::Result<()> { 5 | #[cfg(not(target_os = "wasi"))] // WASI doesn't support pipes yet 6 | let mut input = StreamReader::str("hello world\n")?; 7 | 8 | #[cfg(target_os = "wasi")] 9 | let mut input = StreamReader::stdin()?; 10 | assert!(!cfg!(target_os = "wasi"), "WASI doesn't support pipes yet"); 11 | 12 | let mut output = StreamWriter::stdout()?; 13 | copy(&mut input, &mut output)?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /src/buffered/buf_duplexer.rs: -------------------------------------------------------------------------------- 1 | //! This file is derived from Rust's library/std/src/io/buffered at revision 2 | //! f7801d6c7cc19ab22bdebcc8efa894a564c53469. 3 | 4 | use super::{IntoInnerError, DEFAULT_BUF_SIZE}; 5 | use duplex::HalfDuplex; 6 | #[cfg(feature = "layered-io")] 7 | use layered_io::{default_suggested_buffer_size, Bufferable, HalfDuplexLayered}; 8 | #[cfg(read_initializer)] 9 | use std::io::Initializer; 10 | use std::io::{self, BufRead, Error, ErrorKind, IoSlice, IoSliceMut, Read, Write}; 11 | use std::{cmp, fmt}; 12 | #[cfg(not(windows))] 13 | use { 14 | io_extras::os::rustix::{AsRawFd, RawFd}, 15 | io_lifetimes::{AsFd, BorrowedFd}, 16 | }; 17 | #[cfg(windows)] 18 | use { 19 | io_extras::os::windows::{ 20 | AsHandleOrSocket, AsRawHandleOrSocket, BorrowedHandleOrSocket, RawHandleOrSocket, 21 | }, 22 | io_lifetimes::{AsHandle, AsSocket, BorrowedHandle, BorrowedSocket}, 23 | std::os::windows::io::{AsRawHandle, AsRawSocket, RawHandle, RawSocket}, 24 | }; 25 | 26 | /// Wraps a reader and writer and buffers their output. 27 | /// 28 | /// It can be excessively inefficient to work directly with something that 29 | /// implements [`Write`]. For example, every call to 30 | /// [`write`][`TcpStream::write`] on [`TcpStream`] results in a system call. A 31 | /// `BufDuplexer` keeps an in-memory buffer of data and writes it to an 32 | /// underlying writer in large, infrequent batches. 33 | /// 34 | /// It can be excessively inefficient to work directly with a [`Read`] 35 | /// instance. For example, every call to [`read`][`TcpStream::read`] on 36 | /// [`TcpStream`] results in a system call. A `BufDuplexer` performs 37 | /// large, infrequent reads on the underlying [`Read`] and maintains an 38 | /// in-memory buffer of the results. 39 | /// 40 | /// `BufDuplexer` can improve the speed of programs that make *small* 41 | /// and *repeated* write calls to the same file or network socket. It does not 42 | /// help when writing very large amounts at once, or writing just one or a few 43 | /// times. It also provides no advantage when writing to a destination that is 44 | /// in memory, like a [`Vec`]``. 45 | /// 46 | /// `BufDuplexer` can improve the speed of programs that make *small* 47 | /// and *repeated* read calls to the same file or network socket. It does not 48 | /// help when reading very large amounts at once, or reading just one or a few 49 | /// times. It also provides no advantage when reading from a source that is 50 | /// already in memory, like a [`Vec`]``. 51 | /// 52 | /// It is critical to call [`flush`] before `BufDuplexer` is dropped. 53 | /// Though dropping will attempt to flush the contents of the writer buffer, 54 | /// any errors that happen in the process of dropping will be ignored. Calling 55 | /// [`flush`] ensures that the writer buffer is empty and thus dropping will 56 | /// not even attempt file operations. 57 | /// 58 | /// When the `BufDuplexer` is dropped, the contents of its reader buffer 59 | /// will be discarded. Creating multiple instances of a `BufDuplexer` on 60 | /// the same stream can cause data loss. Reading from the underlying reader 61 | /// after unwrapping the `BufDuplexer` with [`BufDuplexer::into_inner`] 62 | /// can also cause data loss. 63 | /// 64 | /// # Examples 65 | /// 66 | /// Let's write the numbers one through ten to a [`TcpStream`]: 67 | /// 68 | /// ```no_run 69 | /// use std::io::prelude::*; 70 | /// use std::net::TcpStream; 71 | /// 72 | /// let mut stream = TcpStream::connect("127.0.0.1:34254").unwrap(); 73 | /// 74 | /// for i in 0..10 { 75 | /// stream.write(&[i + 1]).unwrap(); 76 | /// } 77 | /// ``` 78 | /// 79 | /// Because we're not buffering, we write each one in turn, incurring the 80 | /// overhead of a system call per byte written. We can fix this with a 81 | /// `BufDuplexer`: 82 | /// 83 | /// ```no_run 84 | /// use io_streams::BufDuplexer; 85 | /// use std::io::prelude::*; 86 | /// use std::net::TcpStream; 87 | /// 88 | /// let mut stream = BufDuplexer::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 89 | /// 90 | /// for i in 0..10 { 91 | /// stream.write(&[i + 1]).unwrap(); 92 | /// } 93 | /// stream.flush().unwrap(); 94 | /// ``` 95 | /// 96 | /// By wrapping the stream with a `BufDuplexer`, these ten writes are 97 | /// all grouped together by the buffer and will all be written out in one 98 | /// system call when the `stream` is flushed. 99 | /// 100 | /// ```no_run 101 | /// use io_streams::BufDuplexer; 102 | /// use std::io::prelude::*; 103 | /// use std::net::TcpStream; 104 | /// 105 | /// fn main() -> std::io::Result<()> { 106 | /// let mut stream = BufDuplexer::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 107 | /// 108 | /// let mut line = String::new(); 109 | /// let len = stream.read_line(&mut line)?; 110 | /// println!("First line is {} bytes long", len); 111 | /// Ok(()) 112 | /// } 113 | /// ``` 114 | /// 115 | /// [`TcpStream::read`]: std::io::Read::read 116 | /// [`TcpStream::write`]: std::io::Write::write 117 | /// [`TcpStream`]: std::net::TcpStream 118 | /// [`flush`]: std::io::Write::flush 119 | pub struct BufDuplexer { 120 | inner: BufDuplexerBackend, 121 | } 122 | 123 | pub(crate) struct BufDuplexerBackend { 124 | inner: Option, 125 | 126 | // writer fields 127 | writer_buf: Vec, 128 | // #30888: If the inner writer panics in a call to write, we don't want to 129 | // write the buffered data a second time in BufDuplexer's destructor. This 130 | // flag tells the Drop impl if it should skip the flush. 131 | panicked: bool, 132 | 133 | // reader fields 134 | reader_buf: Box<[u8]>, 135 | pos: usize, 136 | cap: usize, 137 | } 138 | 139 | impl BufDuplexer { 140 | /// Creates a new `BufDuplexer` with default buffer capacities. The 141 | /// default is currently 8 KB, but may change in the future. 142 | /// 143 | /// # Examples 144 | /// 145 | /// ```no_run 146 | /// use io_streams::BufDuplexer; 147 | /// use std::net::TcpStream; 148 | /// 149 | /// let mut buffer = BufDuplexer::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 150 | /// ``` 151 | #[inline] 152 | pub fn new(inner: Inner) -> Self { 153 | Self { 154 | inner: BufDuplexerBackend::new(inner), 155 | } 156 | } 157 | 158 | /// Creates a new `BufDuplexer` with the specified buffer 159 | /// capacities. 160 | /// 161 | /// # Examples 162 | /// 163 | /// Creating a buffer with ten bytes of reader capacity and a writer buffer 164 | /// of a hundered bytes: 165 | /// 166 | /// ```no_run 167 | /// use io_streams::BufDuplexer; 168 | /// use std::net::TcpStream; 169 | /// 170 | /// let stream = TcpStream::connect("127.0.0.1:34254").unwrap(); 171 | /// let mut buffer = BufDuplexer::with_capacities(10, 100, stream); 172 | /// ``` 173 | #[inline] 174 | pub fn with_capacities(reader_capacity: usize, writer_capacity: usize, inner: Inner) -> Self { 175 | Self { 176 | inner: BufDuplexerBackend::with_capacities(reader_capacity, writer_capacity, inner), 177 | } 178 | } 179 | 180 | /// Gets a reference to the underlying reader/writer. 181 | /// 182 | /// # Examples 183 | /// 184 | /// ```no_run 185 | /// use io_streams::BufDuplexer; 186 | /// use std::net::TcpStream; 187 | /// 188 | /// let mut buffer = BufDuplexer::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 189 | /// 190 | /// // we can use reference just like buffer 191 | /// let reference = buffer.get_ref(); 192 | /// ``` 193 | #[inline] 194 | pub fn get_ref(&self) -> &Inner { 195 | self.inner.get_ref() 196 | } 197 | 198 | /// Gets a mutable reference to the underlying reader/writer. 199 | /// 200 | /// It is inadvisable to directly write to the underlying reader/writer. 201 | /// 202 | /// # Examples 203 | /// 204 | /// ```no_run 205 | /// use io_streams::BufDuplexer; 206 | /// use std::net::TcpStream; 207 | /// 208 | /// let mut buffer = BufDuplexer::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 209 | /// 210 | /// // we can use reference just like buffer 211 | /// let reference = buffer.get_mut(); 212 | /// ``` 213 | #[inline] 214 | pub fn get_mut(&mut self) -> &mut Inner { 215 | self.inner.get_mut() 216 | } 217 | 218 | /// Returns a reference to the internally buffered writer data. 219 | /// 220 | /// # Examples 221 | /// 222 | /// ```no_run 223 | /// use io_streams::BufDuplexer; 224 | /// use std::net::TcpStream; 225 | /// 226 | /// let buf_writer = BufDuplexer::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 227 | /// 228 | /// // See how many bytes are currently buffered 229 | /// let bytes_buffered = buf_writer.writer_buffer().len(); 230 | /// ``` 231 | #[inline] 232 | pub fn writer_buffer(&self) -> &[u8] { 233 | self.inner.writer_buffer() 234 | } 235 | 236 | /// Returns a reference to the internally buffered reader data. 237 | /// 238 | /// Unlike [`fill_buf`], this will not attempt to fill the buffer if it is 239 | /// empty. 240 | /// 241 | /// [`fill_buf`]: BufRead::fill_buf 242 | /// 243 | /// # Examples 244 | /// 245 | /// ```no_run 246 | /// use char_device::CharDevice; 247 | /// use io_streams::BufDuplexer; 248 | /// use std::fs::File; 249 | /// use std::io::BufRead; 250 | /// 251 | /// fn main() -> std::io::Result<()> { 252 | /// let f = CharDevice::new(File::open("/dev/ttyS0")?)?; 253 | /// let mut reader = BufDuplexer::new(f); 254 | /// assert!(reader.reader_buffer().is_empty()); 255 | /// 256 | /// if reader.fill_buf()?.len() > 0 { 257 | /// assert!(!reader.reader_buffer().is_empty()); 258 | /// } 259 | /// Ok(()) 260 | /// } 261 | /// ``` 262 | pub fn reader_buffer(&self) -> &[u8] { 263 | self.inner.reader_buffer() 264 | } 265 | 266 | /// Returns the number of bytes the internal writer buffer can hold without 267 | /// flushing. 268 | /// 269 | /// # Examples 270 | /// 271 | /// ```no_run 272 | /// use io_streams::BufDuplexer; 273 | /// use std::net::TcpStream; 274 | /// 275 | /// let buf_duplexer = BufDuplexer::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 276 | /// 277 | /// // Check the capacity of the inner buffer 278 | /// let capacity = buf_duplexer.writer_capacity(); 279 | /// // Calculate how many bytes can be written without flushing 280 | /// let without_flush = capacity - buf_duplexer.writer_buffer().len(); 281 | /// ``` 282 | #[inline] 283 | pub fn writer_capacity(&self) -> usize { 284 | self.inner.writer_capacity() 285 | } 286 | 287 | /// Returns the number of bytes the internal reader buffer can hold at 288 | /// once. 289 | /// 290 | /// # Examples 291 | /// 292 | /// ```no_run 293 | /// use char_device::CharDevice; 294 | /// use io_streams::BufDuplexer; 295 | /// use std::fs::File; 296 | /// use std::io::BufRead; 297 | /// 298 | /// fn main() -> std::io::Result<()> { 299 | /// let f = CharDevice::new(File::open("/dev/tty")?)?; 300 | /// let mut reader = BufDuplexer::new(f); 301 | /// 302 | /// let capacity = reader.reader_capacity(); 303 | /// let buffer = reader.fill_buf()?; 304 | /// assert!(buffer.len() <= capacity); 305 | /// Ok(()) 306 | /// } 307 | /// ``` 308 | pub fn reader_capacity(&self) -> usize { 309 | self.inner.reader_capacity() 310 | } 311 | 312 | /// Unwraps this `BufDuplexer`, returning the underlying 313 | /// reader/writer. 314 | /// 315 | /// The buffer is written out before returning the reader/writer. 316 | /// 317 | /// # Errors 318 | /// 319 | /// An [`Err`] will be returned if an error occurs while flushing the 320 | /// buffer. 321 | /// 322 | /// # Examples 323 | /// 324 | /// ```no_run 325 | /// use io_streams::BufDuplexer; 326 | /// use std::net::TcpStream; 327 | /// 328 | /// let mut buffer = BufDuplexer::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 329 | /// 330 | /// // unwrap the TcpStream and flush the buffer 331 | /// let stream = buffer.into_inner().unwrap(); 332 | /// ``` 333 | pub fn into_inner(self) -> Result> { 334 | self.inner 335 | .into_inner() 336 | .map_err(|err| err.new_wrapped(|inner| Self { inner })) 337 | } 338 | } 339 | 340 | impl BufDuplexerBackend { 341 | pub fn new(inner: Inner) -> Self { 342 | Self::with_capacities(DEFAULT_BUF_SIZE, DEFAULT_BUF_SIZE, inner) 343 | } 344 | 345 | pub fn with_capacities(reader_capacity: usize, writer_capacity: usize, inner: Inner) -> Self { 346 | #[cfg(not(read_initializer))] 347 | let buffer = vec![0; reader_capacity]; 348 | 349 | #[cfg(read_initializer)] 350 | let buffer = unsafe { 351 | let mut buffer = Vec::with_capacity(reader_capacity); 352 | buffer.set_len(reader_capacity); 353 | inner.initializer().initialize(&mut buffer); 354 | buffer 355 | }; 356 | 357 | Self { 358 | inner: Some(inner), 359 | writer_buf: Vec::with_capacity(writer_capacity), 360 | panicked: false, 361 | reader_buf: buffer.into_boxed_slice(), 362 | pos: 0, 363 | cap: 0, 364 | } 365 | } 366 | 367 | /// Send data in our local buffer into the inner writer, looping as 368 | /// necessary until either it's all been sent or an error occurs. 369 | /// 370 | /// Because all the data in the buffer has been reported to our owner as 371 | /// "successfully written" (by returning nonzero success values from 372 | /// `write`), any 0-length writes from `inner` must be reported as i/o 373 | /// errors from this method. 374 | pub(super) fn flush_buf(&mut self) -> io::Result<()> { 375 | /// Helper struct to ensure the buffer is updated after all the writes 376 | /// are complete. It tracks the number of written bytes and drains them 377 | /// all from the front of the buffer when dropped. 378 | struct BufGuard<'a> { 379 | buffer: &'a mut Vec, 380 | written: usize, 381 | } 382 | 383 | impl<'a> BufGuard<'a> { 384 | fn new(buffer: &'a mut Vec) -> Self { 385 | Self { buffer, written: 0 } 386 | } 387 | 388 | /// The unwritten part of the buffer 389 | fn remaining(&self) -> &[u8] { 390 | &self.buffer[self.written..] 391 | } 392 | 393 | /// Flag some bytes as removed from the front of the buffer 394 | fn consume(&mut self, amt: usize) { 395 | self.written += amt; 396 | } 397 | 398 | /// true if all of the bytes have been written 399 | fn done(&self) -> bool { 400 | self.written >= self.buffer.len() 401 | } 402 | } 403 | 404 | impl Drop for BufGuard<'_> { 405 | fn drop(&mut self) { 406 | if self.written > 0 { 407 | self.buffer.drain(..self.written); 408 | } 409 | } 410 | } 411 | 412 | let mut guard = BufGuard::new(&mut self.writer_buf); 413 | let inner = self.inner.as_mut().unwrap(); 414 | while !guard.done() { 415 | self.panicked = true; 416 | let r = inner.write(guard.remaining()); 417 | self.panicked = false; 418 | 419 | match r { 420 | Ok(0) => { 421 | return Err(Error::new( 422 | ErrorKind::WriteZero, 423 | "failed to write the buffered data", 424 | )); 425 | } 426 | Ok(n) => guard.consume(n), 427 | Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} 428 | Err(e) => return Err(e), 429 | } 430 | } 431 | Ok(()) 432 | } 433 | 434 | /// Buffer some data without flushing it, regardless of the size of the 435 | /// data. Writes as much as possible without exceeding capacity. Returns 436 | /// the number of bytes written. 437 | pub(super) fn write_to_buf(&mut self, buf: &[u8]) -> usize { 438 | let available = self.writer_buf.capacity() - self.writer_buf.len(); 439 | let amt_to_buffer = available.min(buf.len()); 440 | self.writer_buf.extend_from_slice(&buf[..amt_to_buffer]); 441 | amt_to_buffer 442 | } 443 | 444 | #[inline] 445 | pub fn get_ref(&self) -> &Inner { 446 | self.inner.as_ref().unwrap() 447 | } 448 | 449 | #[inline] 450 | pub fn get_mut(&mut self) -> &mut Inner { 451 | self.inner.as_mut().unwrap() 452 | } 453 | 454 | #[inline] 455 | pub fn writer_buffer(&self) -> &[u8] { 456 | &self.writer_buf 457 | } 458 | 459 | pub fn reader_buffer(&self) -> &[u8] { 460 | &self.reader_buf[self.pos..self.cap] 461 | } 462 | 463 | #[inline] 464 | pub fn writer_capacity(&self) -> usize { 465 | self.writer_buf.capacity() 466 | } 467 | 468 | pub fn reader_capacity(&self) -> usize { 469 | self.reader_buf.len() 470 | } 471 | 472 | pub fn into_inner(mut self) -> Result> { 473 | match self.flush_buf() { 474 | Err(e) => Err(IntoInnerError::new(self, e)), 475 | Ok(()) => Ok(self.inner.take().unwrap()), 476 | } 477 | } 478 | 479 | /// Invalidates all data in the internal buffer. 480 | #[inline] 481 | fn discard_reader_buffer(&mut self) { 482 | self.pos = 0; 483 | self.cap = 0; 484 | } 485 | } 486 | 487 | impl Write for BufDuplexer { 488 | #[inline] 489 | fn write(&mut self, buf: &[u8]) -> io::Result { 490 | self.inner.write(buf) 491 | } 492 | 493 | #[inline] 494 | fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { 495 | self.inner.write_all(buf) 496 | } 497 | 498 | #[inline] 499 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { 500 | self.inner.write_vectored(bufs) 501 | } 502 | 503 | #[cfg(can_vector)] 504 | #[inline] 505 | fn is_write_vectored(&self) -> bool { 506 | self.inner.is_write_vectored() 507 | } 508 | 509 | #[inline] 510 | fn flush(&mut self) -> io::Result<()> { 511 | self.inner.flush() 512 | } 513 | } 514 | 515 | impl Write for BufDuplexerBackend { 516 | fn write(&mut self, buf: &[u8]) -> io::Result { 517 | if self.writer_buf.len() + buf.len() > self.writer_buf.capacity() { 518 | self.flush_buf()?; 519 | } 520 | // FIXME: Why no len > capacity? Why not buffer len == capacity? #72919 521 | if buf.len() >= self.writer_buf.capacity() { 522 | self.panicked = true; 523 | let r = self.get_mut().write(buf); 524 | self.panicked = false; 525 | r 526 | } else { 527 | self.writer_buf.extend_from_slice(buf); 528 | Ok(buf.len()) 529 | } 530 | } 531 | 532 | fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { 533 | // Normally, `write_all` just calls `write` in a loop. We can do better 534 | // by calling `self.get_mut().write_all()` directly, which avoids 535 | // round trips through the buffer in the event of a series of partial 536 | // writes in some circumstances. 537 | if self.writer_buf.len() + buf.len() > self.writer_buf.capacity() { 538 | self.flush_buf()?; 539 | } 540 | // FIXME: Why no len > capacity? Why not buffer len == capacity? #72919 541 | if buf.len() >= self.writer_buf.capacity() { 542 | self.panicked = true; 543 | let r = self.get_mut().write_all(buf); 544 | self.panicked = false; 545 | r 546 | } else { 547 | self.writer_buf.extend_from_slice(buf); 548 | Ok(()) 549 | } 550 | } 551 | 552 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { 553 | let total_len = bufs.iter().map(|b| b.len()).sum::(); 554 | if self.writer_buf.len() + total_len > self.writer_buf.capacity() { 555 | self.flush_buf()?; 556 | } 557 | // FIXME: Why no len > capacity? Why not buffer len == capacity? #72919 558 | if total_len >= self.writer_buf.capacity() { 559 | self.panicked = true; 560 | let r = self.get_mut().write_vectored(bufs); 561 | self.panicked = false; 562 | r 563 | } else { 564 | bufs.iter() 565 | .for_each(|b| self.writer_buf.extend_from_slice(b)); 566 | Ok(total_len) 567 | } 568 | } 569 | 570 | #[cfg(can_vector)] 571 | #[inline] 572 | fn is_write_vectored(&self) -> bool { 573 | self.get_ref().is_write_vectored() 574 | } 575 | 576 | #[inline] 577 | fn flush(&mut self) -> io::Result<()> { 578 | self.flush_buf().and_then(|()| self.get_mut().flush()) 579 | } 580 | } 581 | 582 | impl Read for BufDuplexer { 583 | #[inline] 584 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 585 | // Flush the writer half of this `BufDuplexer` before reading. 586 | self.inner.flush()?; 587 | 588 | self.inner.read(buf) 589 | } 590 | 591 | #[inline] 592 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { 593 | // Flush the writer half of this `BufDuplexer` before reading. 594 | self.inner.flush()?; 595 | 596 | self.inner.read_vectored(bufs) 597 | } 598 | 599 | #[cfg(can_vector)] 600 | #[inline] 601 | fn is_read_vectored(&self) -> bool { 602 | self.inner.is_read_vectored() 603 | } 604 | 605 | // we can't skip unconditionally because of the large buffer case in read. 606 | #[cfg(read_initializer)] 607 | #[inline] 608 | unsafe fn initializer(&self) -> Initializer { 609 | self.inner.initializer() 610 | } 611 | } 612 | 613 | impl Read for BufDuplexerBackend { 614 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 615 | if self.pos == self.cap && buf.len() >= self.reader_buf.len() { 616 | self.discard_reader_buffer(); 617 | return self.inner.as_mut().unwrap().read(buf); 618 | } 619 | let size = { 620 | let mut rem = self.fill_buf()?; 621 | rem.read(buf)? 622 | }; 623 | self.consume(size); 624 | Ok(size) 625 | } 626 | 627 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { 628 | let total_len = bufs.iter().map(|b| b.len()).sum::(); 629 | if self.pos == self.cap && total_len >= self.reader_buf.len() { 630 | self.discard_reader_buffer(); 631 | return self.inner.as_mut().unwrap().read_vectored(bufs); 632 | } 633 | let size = { 634 | let mut rem = self.fill_buf()?; 635 | rem.read_vectored(bufs)? 636 | }; 637 | self.consume(size); 638 | Ok(size) 639 | } 640 | 641 | #[cfg(can_vector)] 642 | fn is_read_vectored(&self) -> bool { 643 | self.inner.as_ref().unwrap().is_read_vectored() 644 | } 645 | 646 | // we can't skip unconditionally because of the large buffer case in read. 647 | #[cfg(read_initializer)] 648 | unsafe fn initializer(&self) -> Initializer { 649 | self.inner.as_ref().unwrap().initializer() 650 | } 651 | } 652 | 653 | impl BufRead for BufDuplexer { 654 | #[inline] 655 | fn fill_buf(&mut self) -> io::Result<&[u8]> { 656 | self.inner.fill_buf() 657 | } 658 | 659 | #[inline] 660 | fn consume(&mut self, amt: usize) { 661 | self.inner.consume(amt) 662 | } 663 | 664 | #[inline] 665 | fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { 666 | // Flush the writer half of this `BufDuplexer` before reading. 667 | self.inner.flush()?; 668 | 669 | self.inner.read_until(byte, buf) 670 | } 671 | 672 | #[inline] 673 | fn read_line(&mut self, buf: &mut String) -> io::Result { 674 | // Flush the writer half of this `BufDuplexer` before reading. 675 | self.inner.flush()?; 676 | 677 | self.inner.read_line(buf) 678 | } 679 | } 680 | 681 | // FIXME: impl read_line for BufRead explicitly? 682 | impl BufRead for BufDuplexerBackend { 683 | fn fill_buf(&mut self) -> io::Result<&[u8]> { 684 | // If we've reached the end of our internal buffer then we need to fetch 685 | // some more data from the underlying reader. 686 | // Branch using `>=` instead of the more correct `==` 687 | // to tell the compiler that the pos..cap slice is always valid. 688 | if self.pos >= self.cap { 689 | debug_assert!(self.pos == self.cap); 690 | self.cap = self.inner.as_mut().unwrap().read(&mut self.reader_buf)?; 691 | self.pos = 0; 692 | } 693 | Ok(&self.reader_buf[self.pos..self.cap]) 694 | } 695 | 696 | fn consume(&mut self, amt: usize) { 697 | self.pos = cmp::min(self.pos + amt, self.cap); 698 | } 699 | } 700 | 701 | impl fmt::Debug for BufDuplexer 702 | where 703 | Inner: fmt::Debug, 704 | { 705 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 706 | self.inner.fmt(fmt) 707 | } 708 | } 709 | 710 | impl fmt::Debug for BufDuplexerBackend 711 | where 712 | Inner: fmt::Debug, 713 | { 714 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 715 | fmt.debug_struct("BufDuplexer") 716 | .field("inner", &self.inner.as_ref().unwrap()) 717 | .field( 718 | "reader_buffer", 719 | &format_args!("{}/{}", self.cap - self.pos, self.reader_buf.len()), 720 | ) 721 | .field( 722 | "writer_buffer", 723 | &format_args!("{}/{}", self.writer_buf.len(), self.writer_buf.capacity()), 724 | ) 725 | .finish() 726 | } 727 | } 728 | 729 | impl Drop for BufDuplexerBackend { 730 | fn drop(&mut self) { 731 | if self.inner.is_some() && !self.panicked { 732 | // dtors should not panic, so we ignore a failed flush 733 | let _r = self.flush_buf(); 734 | } 735 | } 736 | } 737 | 738 | #[cfg(not(windows))] 739 | impl AsRawFd for BufDuplexer { 740 | #[inline] 741 | fn as_raw_fd(&self) -> RawFd { 742 | self.inner.as_raw_fd() 743 | } 744 | } 745 | 746 | #[cfg(windows)] 747 | impl AsRawHandle for BufDuplexer { 748 | #[inline] 749 | fn as_raw_handle(&self) -> RawHandle { 750 | self.inner.as_raw_handle() 751 | } 752 | } 753 | 754 | #[cfg(windows)] 755 | impl AsRawSocket for BufDuplexer { 756 | #[inline] 757 | fn as_raw_socket(&self) -> RawSocket { 758 | self.inner.as_raw_socket() 759 | } 760 | } 761 | 762 | #[cfg(windows)] 763 | impl AsRawHandleOrSocket for BufDuplexer { 764 | #[inline] 765 | fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { 766 | self.inner.as_raw_handle_or_socket() 767 | } 768 | } 769 | 770 | #[cfg(not(windows))] 771 | impl AsRawFd for BufDuplexerBackend { 772 | #[inline] 773 | fn as_raw_fd(&self) -> RawFd { 774 | self.inner.as_ref().unwrap().as_raw_fd() 775 | } 776 | } 777 | 778 | #[cfg(windows)] 779 | impl AsRawHandle for BufDuplexerBackend { 780 | #[inline] 781 | fn as_raw_handle(&self) -> RawHandle { 782 | self.inner.as_ref().unwrap().as_raw_handle() 783 | } 784 | } 785 | 786 | #[cfg(windows)] 787 | impl AsRawSocket for BufDuplexerBackend { 788 | #[inline] 789 | fn as_raw_socket(&self) -> RawSocket { 790 | self.inner.as_ref().unwrap().as_raw_socket() 791 | } 792 | } 793 | 794 | #[cfg(windows)] 795 | impl AsRawHandleOrSocket for BufDuplexerBackend { 796 | #[inline] 797 | fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { 798 | self.inner.as_ref().unwrap().as_raw_handle_or_socket() 799 | } 800 | } 801 | 802 | #[cfg(not(windows))] 803 | impl AsFd for BufDuplexer { 804 | #[inline] 805 | fn as_fd(&self) -> BorrowedFd<'_> { 806 | self.inner.as_fd() 807 | } 808 | } 809 | 810 | #[cfg(windows)] 811 | impl AsHandle for BufDuplexer { 812 | #[inline] 813 | fn as_handle(&self) -> BorrowedHandle<'_> { 814 | self.inner.as_handle() 815 | } 816 | } 817 | 818 | #[cfg(windows)] 819 | impl AsSocket for BufDuplexer { 820 | #[inline] 821 | fn as_socket(&self) -> BorrowedSocket<'_> { 822 | self.inner.as_socket() 823 | } 824 | } 825 | 826 | #[cfg(windows)] 827 | impl AsHandleOrSocket for BufDuplexer { 828 | #[inline] 829 | fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> { 830 | self.inner.as_handle_or_socket() 831 | } 832 | } 833 | 834 | #[cfg(not(windows))] 835 | impl AsFd for BufDuplexerBackend { 836 | #[inline] 837 | fn as_fd(&self) -> BorrowedFd<'_> { 838 | self.inner.as_ref().unwrap().as_fd() 839 | } 840 | } 841 | 842 | #[cfg(windows)] 843 | impl AsHandle for BufDuplexerBackend { 844 | #[inline] 845 | fn as_handle(&self) -> BorrowedHandle<'_> { 846 | self.inner.as_ref().unwrap().as_handle() 847 | } 848 | } 849 | 850 | #[cfg(windows)] 851 | impl AsSocket for BufDuplexerBackend { 852 | #[inline] 853 | fn as_socket(&self) -> BorrowedSocket<'_> { 854 | self.inner.as_ref().unwrap().as_socket() 855 | } 856 | } 857 | 858 | #[cfg(windows)] 859 | impl AsHandleOrSocket for BufDuplexerBackend { 860 | #[inline] 861 | fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> { 862 | self.inner.as_ref().unwrap().as_handle_or_socket() 863 | } 864 | } 865 | 866 | #[cfg(feature = "terminal-io")] 867 | impl terminal_io::Terminal for BufDuplexer {} 868 | 869 | #[cfg(feature = "terminal-io")] 870 | impl terminal_io::Terminal 871 | for BufDuplexerBackend 872 | { 873 | } 874 | 875 | #[cfg(feature = "terminal-io")] 876 | impl terminal_io::WriteTerminal 877 | for BufDuplexer 878 | { 879 | #[inline] 880 | fn color_support(&self) -> terminal_io::TerminalColorSupport { 881 | self.inner.color_support() 882 | } 883 | 884 | #[inline] 885 | fn color_preference(&self) -> bool { 886 | self.inner.color_preference() 887 | } 888 | 889 | #[inline] 890 | fn is_output_terminal(&self) -> bool { 891 | self.inner.is_output_terminal() 892 | } 893 | } 894 | 895 | #[cfg(feature = "terminal-io")] 896 | impl terminal_io::WriteTerminal 897 | for BufDuplexerBackend 898 | { 899 | #[inline] 900 | fn color_support(&self) -> terminal_io::TerminalColorSupport { 901 | self.inner.as_ref().unwrap().color_support() 902 | } 903 | 904 | #[inline] 905 | fn color_preference(&self) -> bool { 906 | self.inner.as_ref().unwrap().color_preference() 907 | } 908 | 909 | #[inline] 910 | fn is_output_terminal(&self) -> bool { 911 | match &self.inner { 912 | Some(inner) => inner.is_output_terminal(), 913 | None => false, 914 | } 915 | } 916 | } 917 | 918 | #[cfg(feature = "layered-io")] 919 | impl Bufferable for BufDuplexer { 920 | #[inline] 921 | fn abandon(&mut self) { 922 | self.inner.abandon() 923 | } 924 | 925 | #[inline] 926 | fn suggested_buffer_size(&self) -> usize { 927 | self.inner.suggested_buffer_size() 928 | } 929 | } 930 | 931 | #[cfg(feature = "layered-io")] 932 | impl Bufferable for BufDuplexerBackend { 933 | #[inline] 934 | fn abandon(&mut self) { 935 | match &mut self.inner { 936 | Some(inner) => inner.abandon(), 937 | None => (), 938 | } 939 | } 940 | 941 | #[inline] 942 | fn suggested_buffer_size(&self) -> usize { 943 | match &self.inner { 944 | Some(inner) => { 945 | std::cmp::max(inner.minimum_buffer_size(), inner.suggested_buffer_size()) 946 | } 947 | None => default_suggested_buffer_size(self), 948 | } 949 | } 950 | } 951 | -------------------------------------------------------------------------------- /src/buffered/buf_reader_line_writer.rs: -------------------------------------------------------------------------------- 1 | //! This file is derived from Rust's library/std/src/io/buffered at revision 2 | //! f7801d6c7cc19ab22bdebcc8efa894a564c53469. 3 | 4 | use super::buf_duplexer::BufDuplexerBackend; 5 | use super::{BufReaderLineWriterShim, IntoInnerError}; 6 | use duplex::HalfDuplex; 7 | #[cfg(feature = "layered-io")] 8 | use layered_io::{Bufferable, HalfDuplexLayered}; 9 | use std::fmt; 10 | #[cfg(read_initializer)] 11 | use std::io::Initializer; 12 | use std::io::{self, BufRead, IoSlice, IoSliceMut, Read, Write}; 13 | #[cfg(not(windows))] 14 | use { 15 | io_extras::os::rustix::{AsRawFd, RawFd}, 16 | io_lifetimes::{AsFd, BorrowedFd}, 17 | }; 18 | #[cfg(windows)] 19 | use { 20 | io_extras::os::windows::{ 21 | AsHandleOrSocket, AsRawHandleOrSocket, BorrowedHandleOrSocket, RawHandleOrSocket, 22 | }, 23 | io_lifetimes::{AsHandle, AsSocket, BorrowedHandle, BorrowedSocket}, 24 | std::os::windows::io::{AsRawHandle, AsRawSocket, RawHandle, RawSocket}, 25 | }; 26 | 27 | /// Wraps a reader and writer and buffers input and output to and from it, 28 | /// flushing the writer whenever a newline (`0x0a`, `'\n'`) is detected on 29 | /// output. 30 | /// 31 | /// The [`BufDuplexer`] struct wraps a reader and writer and buffers their 32 | /// input and output. But it only does this batched write when it goes out of 33 | /// scope, or when the internal buffer is full. Sometimes, you'd prefer to 34 | /// write each line as it's completed, rather than the entire buffer at once. 35 | /// Enter `BufReaderLineWriter`. It does exactly that. 36 | /// 37 | /// Like [`BufDuplexer`], a `BufReaderLineWriter`’s buffer will also be flushed 38 | /// when the `BufReaderLineWriter` goes out of scope or when its internal 39 | /// buffer is full. 40 | /// 41 | /// If there's still a partial line in the buffer when the 42 | /// `BufReaderLineWriter` is dropped, it will flush those contents. 43 | /// 44 | /// # Examples 45 | /// 46 | /// We can use `BufReaderLineWriter` to write one line at a time, significantly 47 | /// reducing the number of actual writes to the file. 48 | /// 49 | /// ```no_run 50 | /// use char_device::CharDevice; 51 | /// use io_streams::BufReaderLineWriter; 52 | /// use std::fs; 53 | /// use std::io::prelude::*; 54 | /// 55 | /// fn main() -> std::io::Result<()> { 56 | /// let road_not_taken = b"I shall be telling this with a sigh 57 | /// Somewhere ages and ages hence: 58 | /// Two roads diverged in a wood, and I - 59 | /// I took the one less traveled by, 60 | /// And that has made all the difference."; 61 | /// 62 | /// let file = CharDevice::open("/dev/tty")?; 63 | /// let mut file = BufReaderLineWriter::new(file); 64 | /// 65 | /// file.write_all(b"I shall be telling this with a sigh")?; 66 | /// 67 | /// // No bytes are written until a newline is encountered (or 68 | /// // the internal buffer is filled). 69 | /// assert_eq!(fs::read_to_string("poem.txt")?, ""); 70 | /// file.write_all(b"\n")?; 71 | /// assert_eq!( 72 | /// fs::read_to_string("poem.txt")?, 73 | /// "I shall be telling this with a sigh\n", 74 | /// ); 75 | /// 76 | /// // Write the rest of the poem. 77 | /// file.write_all( 78 | /// b"Somewhere ages and ages hence: 79 | /// Two roads diverged in a wood, and I - 80 | /// I took the one less traveled by, 81 | /// And that has made all the difference.", 82 | /// )?; 83 | /// 84 | /// // The last line of the poem doesn't end in a newline, so 85 | /// // we have to flush or drop the `BufReaderLineWriter` to finish 86 | /// // writing. 87 | /// file.flush()?; 88 | /// 89 | /// // Confirm the whole poem was written. 90 | /// assert_eq!(fs::read("poem.txt")?, &road_not_taken[..]); 91 | /// Ok(()) 92 | /// } 93 | /// ``` 94 | /// 95 | /// [`BufDuplexer`]: crate::BufDuplexer 96 | pub struct BufReaderLineWriter { 97 | inner: BufReaderLineWriterBackend, 98 | } 99 | 100 | /// The "backend" of `BufReaderLineWriter`, split off so that the public 101 | /// `BufReaderLineWriter` functions can do extra flushing, and we can 102 | /// use the private `BufReaderLineWriterBackend` functions internally 103 | /// after flushing is already done. 104 | struct BufReaderLineWriterBackend { 105 | inner: BufDuplexerBackend, 106 | } 107 | 108 | impl BufReaderLineWriter { 109 | /// Creates a new `BufReaderLineWriter`. 110 | /// 111 | /// # Examples 112 | /// 113 | /// ```no_run 114 | /// use char_device::CharDevice; 115 | /// use io_streams::BufReaderLineWriter; 116 | /// 117 | /// fn main() -> std::io::Result<()> { 118 | /// let file = CharDevice::open("/dev/tty")?; 119 | /// let file = BufReaderLineWriter::new(file); 120 | /// Ok(()) 121 | /// } 122 | /// ``` 123 | #[inline] 124 | pub fn new(inner: Inner) -> Self { 125 | Self { 126 | inner: BufReaderLineWriterBackend::new(inner), 127 | } 128 | } 129 | 130 | /// Creates a new `BufReaderLineWriter` with a specified capacities for the 131 | /// internal buffers. 132 | /// 133 | /// # Examples 134 | /// 135 | /// ```no_run 136 | /// use char_device::CharDevice; 137 | /// use io_streams::BufReaderLineWriter; 138 | /// 139 | /// fn main() -> std::io::Result<()> { 140 | /// let file = CharDevice::open("/dev/tty")?; 141 | /// let file = BufReaderLineWriter::with_capacities(10, 100, file); 142 | /// Ok(()) 143 | /// } 144 | /// ``` 145 | #[inline] 146 | pub fn with_capacities(reader_capacity: usize, writer_capacity: usize, inner: Inner) -> Self { 147 | Self { 148 | inner: BufReaderLineWriterBackend::with_capacities( 149 | reader_capacity, 150 | writer_capacity, 151 | inner, 152 | ), 153 | } 154 | } 155 | 156 | /// Gets a reference to the underlying writer. 157 | /// 158 | /// # Examples 159 | /// 160 | /// ```no_run 161 | /// use char_device::CharDevice; 162 | /// use io_streams::BufReaderLineWriter; 163 | /// 164 | /// fn main() -> std::io::Result<()> { 165 | /// let file = CharDevice::open("/dev/tty")?; 166 | /// let file = BufReaderLineWriter::new(file); 167 | /// 168 | /// let reference = file.get_ref(); 169 | /// Ok(()) 170 | /// } 171 | /// ``` 172 | #[inline] 173 | pub fn get_ref(&self) -> &Inner { 174 | self.inner.get_ref() 175 | } 176 | 177 | /// Gets a mutable reference to the underlying writer. 178 | /// 179 | /// Caution must be taken when calling methods on the mutable reference 180 | /// returned as extra writes could corrupt the output stream. 181 | /// 182 | /// # Examples 183 | /// 184 | /// ```no_run 185 | /// use char_device::CharDevice; 186 | /// use io_streams::BufReaderLineWriter; 187 | /// 188 | /// fn main() -> std::io::Result<()> { 189 | /// let file = CharDevice::open("/dev/tty")?; 190 | /// let mut file = BufReaderLineWriter::new(file); 191 | /// 192 | /// // we can use reference just like file 193 | /// let reference = file.get_mut(); 194 | /// Ok(()) 195 | /// } 196 | /// ``` 197 | #[inline] 198 | pub fn get_mut(&mut self) -> &mut Inner { 199 | self.inner.get_mut() 200 | } 201 | 202 | /// Unwraps this `BufReaderLineWriter`, returning the underlying writer. 203 | /// 204 | /// The internal buffer is written out before returning the writer. 205 | /// 206 | /// # Errors 207 | /// 208 | /// An [`Err`] will be returned if an error occurs while flushing the 209 | /// buffer. 210 | /// 211 | /// # Examples 212 | /// 213 | /// ```no_run 214 | /// use char_device::CharDevice; 215 | /// use io_streams::BufReaderLineWriter; 216 | /// 217 | /// fn main() -> std::io::Result<()> { 218 | /// let file = CharDevice::open("/dev/tty")?; 219 | /// 220 | /// let writer: BufReaderLineWriter = BufReaderLineWriter::new(file); 221 | /// 222 | /// let file: CharDevice = writer.into_inner()?; 223 | /// Ok(()) 224 | /// } 225 | /// ``` 226 | #[inline] 227 | pub fn into_inner(self) -> Result> { 228 | self.inner 229 | .into_inner() 230 | .map_err(|err| err.new_wrapped(|inner| Self { inner })) 231 | } 232 | } 233 | 234 | impl BufReaderLineWriterBackend { 235 | pub fn new(inner: Inner) -> Self { 236 | // Lines typically aren't that long, don't use giant buffers 237 | Self::with_capacities(1024, 1024, inner) 238 | } 239 | 240 | pub fn with_capacities(reader_capacity: usize, writer_capacity: usize, inner: Inner) -> Self { 241 | Self { 242 | inner: BufDuplexerBackend::with_capacities(reader_capacity, writer_capacity, inner), 243 | } 244 | } 245 | 246 | #[inline] 247 | pub fn get_ref(&self) -> &Inner { 248 | self.inner.get_ref() 249 | } 250 | 251 | #[inline] 252 | pub fn get_mut(&mut self) -> &mut Inner { 253 | self.inner.get_mut() 254 | } 255 | 256 | pub fn into_inner(self) -> Result> { 257 | self.inner 258 | .into_inner() 259 | .map_err(|err| err.new_wrapped(|inner| Self { inner })) 260 | } 261 | } 262 | 263 | impl Write for BufReaderLineWriter { 264 | #[inline] 265 | fn write(&mut self, buf: &[u8]) -> io::Result { 266 | self.inner.write(buf) 267 | } 268 | 269 | #[inline] 270 | fn flush(&mut self) -> io::Result<()> { 271 | self.inner.flush() 272 | } 273 | 274 | #[inline] 275 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { 276 | self.inner.write_vectored(bufs) 277 | } 278 | 279 | #[cfg(can_vector)] 280 | #[inline] 281 | fn is_write_vectored(&self) -> bool { 282 | self.inner.is_write_vectored() 283 | } 284 | 285 | #[inline] 286 | fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { 287 | self.inner.write_all(buf) 288 | } 289 | 290 | #[cfg(write_all_vectored)] 291 | #[inline] 292 | fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { 293 | self.inner.write_all_vectored(bufs) 294 | } 295 | 296 | #[inline] 297 | fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { 298 | self.inner.write_fmt(fmt) 299 | } 300 | } 301 | 302 | impl Write for BufReaderLineWriterBackend { 303 | #[inline] 304 | fn write(&mut self, buf: &[u8]) -> io::Result { 305 | BufReaderLineWriterShim::new(&mut self.inner).write(buf) 306 | } 307 | 308 | #[inline] 309 | fn flush(&mut self) -> io::Result<()> { 310 | self.inner.flush() 311 | } 312 | 313 | #[inline] 314 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { 315 | BufReaderLineWriterShim::new(&mut self.inner).write_vectored(bufs) 316 | } 317 | 318 | #[cfg(can_vector)] 319 | #[inline] 320 | fn is_write_vectored(&self) -> bool { 321 | self.inner.is_write_vectored() 322 | } 323 | 324 | #[inline] 325 | fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { 326 | BufReaderLineWriterShim::new(&mut self.inner).write_all(buf) 327 | } 328 | 329 | #[cfg(write_all_vectored)] 330 | #[inline] 331 | fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { 332 | BufReaderLineWriterShim::new(&mut self.inner).write_all_vectored(bufs) 333 | } 334 | 335 | #[inline] 336 | fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { 337 | BufReaderLineWriterShim::new(&mut self.inner).write_fmt(fmt) 338 | } 339 | } 340 | 341 | impl Read for BufReaderLineWriter { 342 | #[inline] 343 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 344 | // Flush the output buffer before reading. 345 | self.inner.flush()?; 346 | 347 | self.inner.read(buf) 348 | } 349 | 350 | #[inline] 351 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { 352 | // Flush the output buffer before reading. 353 | self.inner.flush()?; 354 | 355 | self.inner.read_vectored(bufs) 356 | } 357 | 358 | #[cfg(can_vector)] 359 | #[inline] 360 | fn is_read_vectored(&self) -> bool { 361 | self.inner.is_read_vectored() 362 | } 363 | 364 | #[cfg(read_initializer)] 365 | #[inline] 366 | unsafe fn initializer(&self) -> Initializer { 367 | self.inner.initializer() 368 | } 369 | } 370 | 371 | impl Read for BufReaderLineWriterBackend { 372 | #[inline] 373 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 374 | self.inner.read(buf) 375 | } 376 | 377 | #[inline] 378 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { 379 | self.inner.read_vectored(bufs) 380 | } 381 | 382 | #[cfg(can_vector)] 383 | #[inline] 384 | fn is_read_vectored(&self) -> bool { 385 | self.inner.is_read_vectored() 386 | } 387 | 388 | // we can't skip unconditionally because of the large buffer case in read. 389 | #[cfg(read_initializer)] 390 | #[inline] 391 | unsafe fn initializer(&self) -> Initializer { 392 | self.inner.initializer() 393 | } 394 | } 395 | 396 | impl BufRead for BufReaderLineWriter { 397 | #[inline] 398 | fn fill_buf(&mut self) -> io::Result<&[u8]> { 399 | self.inner.fill_buf() 400 | } 401 | 402 | #[inline] 403 | fn consume(&mut self, amt: usize) { 404 | self.inner.consume(amt) 405 | } 406 | 407 | #[inline] 408 | fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { 409 | // Flush the output buffer before reading. 410 | self.flush()?; 411 | 412 | self.inner.read_until(byte, buf) 413 | } 414 | 415 | #[inline] 416 | fn read_line(&mut self, buf: &mut String) -> io::Result { 417 | // Flush the output buffer before reading. 418 | self.flush()?; 419 | 420 | let t = self.inner.read_line(buf)?; 421 | 422 | Ok(t) 423 | } 424 | } 425 | 426 | impl BufRead for BufReaderLineWriterBackend { 427 | #[inline] 428 | fn fill_buf(&mut self) -> io::Result<&[u8]> { 429 | self.inner.fill_buf() 430 | } 431 | 432 | #[inline] 433 | fn consume(&mut self, amt: usize) { 434 | self.inner.consume(amt) 435 | } 436 | } 437 | 438 | impl fmt::Debug for BufReaderLineWriter 439 | where 440 | Inner: fmt::Debug, 441 | { 442 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 443 | self.inner.fmt(fmt) 444 | } 445 | } 446 | 447 | impl fmt::Debug for BufReaderLineWriterBackend 448 | where 449 | Inner: fmt::Debug, 450 | { 451 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 452 | fmt.debug_struct("BufReaderLineWriter") 453 | .field("inner", &self.get_ref()) 454 | .field( 455 | "reader_buffer", 456 | &format_args!( 457 | "{}/{}", 458 | self.inner.reader_buffer().len(), 459 | self.inner.reader_capacity() 460 | ), 461 | ) 462 | .field( 463 | "writer_buffer", 464 | &format_args!( 465 | "{}/{}", 466 | self.inner.writer_buffer().len(), 467 | self.inner.writer_capacity() 468 | ), 469 | ) 470 | .finish() 471 | } 472 | } 473 | 474 | #[cfg(not(windows))] 475 | impl AsRawFd for BufReaderLineWriter { 476 | #[inline] 477 | fn as_raw_fd(&self) -> RawFd { 478 | self.inner.as_raw_fd() 479 | } 480 | } 481 | 482 | #[cfg(windows)] 483 | impl AsRawHandle for BufReaderLineWriter { 484 | #[inline] 485 | fn as_raw_handle(&self) -> RawHandle { 486 | self.inner.as_raw_handle() 487 | } 488 | } 489 | 490 | #[cfg(windows)] 491 | impl AsRawSocket for BufReaderLineWriter { 492 | #[inline] 493 | fn as_raw_socket(&self) -> RawSocket { 494 | self.inner.as_raw_socket() 495 | } 496 | } 497 | 498 | #[cfg(windows)] 499 | impl AsRawHandleOrSocket for BufReaderLineWriter { 500 | #[inline] 501 | fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { 502 | self.inner.as_raw_handle_or_socket() 503 | } 504 | } 505 | 506 | #[cfg(not(windows))] 507 | impl AsRawFd for BufReaderLineWriterBackend { 508 | #[inline] 509 | fn as_raw_fd(&self) -> RawFd { 510 | self.inner.as_raw_fd() 511 | } 512 | } 513 | 514 | #[cfg(windows)] 515 | impl AsRawHandle for BufReaderLineWriterBackend { 516 | #[inline] 517 | fn as_raw_handle(&self) -> RawHandle { 518 | self.inner.as_raw_handle() 519 | } 520 | } 521 | 522 | #[cfg(windows)] 523 | impl AsRawSocket for BufReaderLineWriterBackend { 524 | #[inline] 525 | fn as_raw_socket(&self) -> RawSocket { 526 | self.inner.as_raw_socket() 527 | } 528 | } 529 | 530 | #[cfg(windows)] 531 | impl AsRawHandleOrSocket 532 | for BufReaderLineWriterBackend 533 | { 534 | #[inline] 535 | fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { 536 | self.inner.as_raw_handle_or_socket() 537 | } 538 | } 539 | 540 | #[cfg(not(windows))] 541 | impl AsFd for BufReaderLineWriter { 542 | #[inline] 543 | fn as_fd(&self) -> BorrowedFd<'_> { 544 | self.inner.as_fd() 545 | } 546 | } 547 | 548 | #[cfg(windows)] 549 | impl AsHandle for BufReaderLineWriter { 550 | #[inline] 551 | fn as_handle(&self) -> BorrowedHandle<'_> { 552 | self.inner.as_handle() 553 | } 554 | } 555 | 556 | #[cfg(windows)] 557 | impl AsSocket for BufReaderLineWriter { 558 | #[inline] 559 | fn as_socket(&self) -> BorrowedSocket<'_> { 560 | self.inner.as_socket() 561 | } 562 | } 563 | 564 | #[cfg(windows)] 565 | impl AsHandleOrSocket for BufReaderLineWriter { 566 | #[inline] 567 | fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> { 568 | self.inner.as_handle_or_socket() 569 | } 570 | } 571 | 572 | #[cfg(not(windows))] 573 | impl AsFd for BufReaderLineWriterBackend { 574 | #[inline] 575 | fn as_fd(&self) -> BorrowedFd<'_> { 576 | self.inner.as_fd() 577 | } 578 | } 579 | 580 | #[cfg(windows)] 581 | impl AsHandle for BufReaderLineWriterBackend { 582 | #[inline] 583 | fn as_handle(&self) -> BorrowedHandle<'_> { 584 | self.inner.as_handle() 585 | } 586 | } 587 | 588 | #[cfg(windows)] 589 | impl AsSocket for BufReaderLineWriterBackend { 590 | #[inline] 591 | fn as_socket(&self) -> BorrowedSocket<'_> { 592 | self.inner.as_socket() 593 | } 594 | } 595 | 596 | #[cfg(windows)] 597 | impl AsHandleOrSocket for BufReaderLineWriterBackend { 598 | #[inline] 599 | fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> { 600 | self.inner.as_handle_or_socket() 601 | } 602 | } 603 | 604 | #[cfg(feature = "terminal-io")] 605 | impl terminal_io::Terminal 606 | for BufReaderLineWriter 607 | { 608 | } 609 | 610 | #[cfg(feature = "terminal-io")] 611 | impl terminal_io::Terminal 612 | for BufReaderLineWriterBackend 613 | { 614 | } 615 | 616 | #[cfg(feature = "terminal-io")] 617 | impl terminal_io::WriteTerminal 618 | for BufReaderLineWriter 619 | { 620 | #[inline] 621 | fn color_support(&self) -> terminal_io::TerminalColorSupport { 622 | self.inner.color_support() 623 | } 624 | 625 | #[inline] 626 | fn color_preference(&self) -> bool { 627 | self.inner.color_preference() 628 | } 629 | 630 | #[inline] 631 | fn is_output_terminal(&self) -> bool { 632 | self.inner.is_output_terminal() 633 | } 634 | } 635 | 636 | #[cfg(feature = "terminal-io")] 637 | impl terminal_io::WriteTerminal 638 | for BufReaderLineWriterBackend 639 | { 640 | #[inline] 641 | fn color_support(&self) -> terminal_io::TerminalColorSupport { 642 | self.inner.color_support() 643 | } 644 | 645 | #[inline] 646 | fn color_preference(&self) -> bool { 647 | self.inner.color_preference() 648 | } 649 | 650 | #[inline] 651 | fn is_output_terminal(&self) -> bool { 652 | self.inner.is_output_terminal() 653 | } 654 | } 655 | 656 | #[cfg(feature = "layered-io")] 657 | impl Bufferable for BufReaderLineWriter { 658 | #[inline] 659 | fn abandon(&mut self) { 660 | self.inner.abandon() 661 | } 662 | 663 | #[inline] 664 | fn suggested_buffer_size(&self) -> usize { 665 | self.inner.suggested_buffer_size() 666 | } 667 | } 668 | 669 | #[cfg(feature = "layered-io")] 670 | impl Bufferable for BufReaderLineWriterBackend { 671 | #[inline] 672 | fn abandon(&mut self) { 673 | self.inner.abandon() 674 | } 675 | 676 | #[inline] 677 | fn suggested_buffer_size(&self) -> usize { 678 | self.inner.suggested_buffer_size() 679 | } 680 | } 681 | -------------------------------------------------------------------------------- /src/buffered/buf_reader_line_writer_shim.rs: -------------------------------------------------------------------------------- 1 | //! This file is derived from Rust's library/std/src/io/buffered at revision 2 | //! f7801d6c7cc19ab22bdebcc8efa894a564c53469. 3 | 4 | use super::buf_duplexer::BufDuplexerBackend; 5 | use duplex::HalfDuplex; 6 | use std::io::{self, IoSlice, Write}; 7 | 8 | /// Private helper struct for implementing the line-buffered writing logic. 9 | /// This shim temporarily wraps a `BufDuplexer`, and uses its internals to 10 | /// implement a line-buffered writer (specifically by using the internal 11 | /// methods like `write_to_buf` and `flush_buf`). In this way, a more efficient 12 | /// abstraction can be created than one that only had access to `write` and 13 | /// `flush`, without needlessly duplicating a lot of the implementation details 14 | /// of `BufDuplexer`. This also allows existing `BufDuplexer`s to be 15 | /// temporarily given line-buffering logic; this is what enables Stdout to be 16 | /// alternately in line-buffered or block-buffered mode. 17 | #[derive(Debug)] 18 | pub(super) struct BufReaderLineWriterShim<'a, Inner: HalfDuplex> { 19 | buffer: &'a mut BufDuplexerBackend, 20 | } 21 | 22 | impl<'a, Inner: HalfDuplex> BufReaderLineWriterShim<'a, Inner> { 23 | #[inline] 24 | pub fn new(buffer: &'a mut BufDuplexerBackend) -> Self { 25 | Self { buffer } 26 | } 27 | 28 | /// Get a mutable reference to the inner writer (that is, the writer 29 | /// wrapped by the `BufDuplexer`). Be careful with this writer, as writes 30 | /// to it will bypass the buffer. 31 | #[inline] 32 | fn inner_mut(&mut self) -> &mut Inner { 33 | self.buffer.get_mut() 34 | } 35 | 36 | /// Get the content currently buffered in self.buffer's writer buffer 37 | #[inline] 38 | fn writer_buffered(&self) -> &[u8] { 39 | self.buffer.writer_buffer() 40 | } 41 | 42 | /// Flush the buffer iff the last byte is a newline (indicating that an 43 | /// earlier write only succeeded partially, and we want to retry flushing 44 | /// the buffered line before continuing with a subsequent write) 45 | fn flush_if_completed_line(&mut self) -> io::Result<()> { 46 | match self.writer_buffered().last().copied() { 47 | Some(b'\n') => self.buffer.flush_buf(), 48 | _ => Ok(()), 49 | } 50 | } 51 | } 52 | 53 | impl<'a, Inner: HalfDuplex> Write for BufReaderLineWriterShim<'a, Inner> { 54 | /// Write some data into this `BufReaderLineWriterShim` with line 55 | /// buffering. This means that, if any newlines are present in the 56 | /// data, the data up to the last newline is sent directly to the 57 | /// underlying writer, and data after it is buffered. Returns the 58 | /// number of bytes written. 59 | /// 60 | /// This function operates on a "best effort basis"; in keeping with the 61 | /// convention of `std::io::Write::write`, it makes at most one attempt to 62 | /// write new data to the underlying writer. If that write only reports 63 | /// a partial success, the remaining data will be buffered. 64 | /// 65 | /// Because this function attempts to send completed lines to the 66 | /// underlying writer, it will also flush the existing buffer if it 67 | /// ends with a newline, even if the incoming data does not contain any 68 | /// newlines. 69 | fn write(&mut self, buf: &[u8]) -> io::Result { 70 | let newline_idx = match memchr::memrchr(b'\n', buf) { 71 | // If there are no new newlines (that is, if this write is less than 72 | // one line), just do a regular buffered write (which may flush if 73 | // we exceed the inner buffer's size) 74 | None => { 75 | self.flush_if_completed_line()?; 76 | return self.buffer.write(buf); 77 | } 78 | // Otherwise, arrange for the lines to be written directly to the 79 | // inner writer. 80 | Some(newline_idx) => newline_idx + 1, 81 | }; 82 | 83 | // Flush existing content to prepare for our write. We have to do this 84 | // before attempting to write `buf` in order to maintain consistency; 85 | // if we add `buf` to the buffer then try to flush it all at once, 86 | // we're obligated to return Ok(), which would mean suppressing any 87 | // errors that occur during flush. 88 | self.buffer.flush_buf()?; 89 | 90 | // This is what we're going to try to write directly to the inner 91 | // writer. The rest will be buffered, if nothing goes wrong. 92 | let lines = &buf[..newline_idx]; 93 | 94 | // Write `lines` directly to the inner writer. In keeping with the 95 | // `write` convention, make at most one attempt to add new (unbuffered) 96 | // data. Because this write doesn't touch the `BufDuplexer` state directly, 97 | // and the buffer is known to be empty, we don't need to worry about 98 | // self.buffer.panicked here. 99 | let flushed = self.inner_mut().write(lines)?; 100 | 101 | // If buffer returns Ok(0), propagate that to the caller without 102 | // doing additional buffering; otherwise we're just guaranteeing 103 | // an "ErrorKind::WriteZero" later. 104 | if flushed == 0 { 105 | return Ok(0); 106 | } 107 | 108 | // Now that the write has succeeded, buffer the rest (or as much of 109 | // the rest as possible). If there were any unwritten newlines, we 110 | // only buffer out to the last unwritten newline that fits in the 111 | // buffer; this helps prevent flushing partial lines on subsequent 112 | // calls to BufReaderLineWriterShim::write. 113 | 114 | // Handle the cases in order of most-common to least-common, under 115 | // the presumption that most writes succeed in totality, and that most 116 | // writes are smaller than the buffer. 117 | // - Is this a partial line (ie, no newlines left in the unwritten tail) 118 | // - If not, does the data out to the last unwritten newline fit in the buffer? 119 | // - If not, scan for the last newline that *does* fit in the buffer 120 | let tail = if flushed >= newline_idx { 121 | &buf[flushed..] 122 | } else if newline_idx - flushed <= self.buffer.writer_capacity() { 123 | &buf[flushed..newline_idx] 124 | } else { 125 | let scan_area = &buf[flushed..]; 126 | let scan_area = &scan_area[..self.buffer.writer_capacity()]; 127 | match memchr::memrchr(b'\n', scan_area) { 128 | Some(newline_idx) => &scan_area[..=newline_idx], 129 | None => scan_area, 130 | } 131 | }; 132 | 133 | let buffered = self.buffer.write_to_buf(tail); 134 | Ok(flushed + buffered) 135 | } 136 | 137 | #[inline] 138 | fn flush(&mut self) -> io::Result<()> { 139 | self.buffer.flush() 140 | } 141 | 142 | /// Write some vectored data into this `BufReaderLineWriterShim` with line 143 | /// buffering. This means that, if any newlines are present in the 144 | /// data, the data up to and including the buffer containing the last 145 | /// newline is sent directly to the inner writer, and the data after it 146 | /// is buffered. Returns the number of bytes written. 147 | /// 148 | /// This function operates on a "best effort basis"; in keeping with the 149 | /// convention of `std::io::Write::write`, it makes at most one attempt to 150 | /// write new data to the underlying writer. 151 | /// 152 | /// Because this function attempts to send completed lines to the 153 | /// underlying writer, it will also flush the existing buffer if it 154 | /// contains any newlines. 155 | /// 156 | /// Because sorting through an array of `IoSlice` can be a bit convoluted, 157 | /// This method differs from write in the following ways: 158 | /// 159 | /// - It attempts to write the full content of all the buffers up to and 160 | /// including the one containing the last newline. This means that it may 161 | /// attempt to write a partial line, that buffer has data past the 162 | /// newline. 163 | /// - If the write only reports partial success, it does not attempt to 164 | /// find the precise location of the written bytes and buffer the rest. 165 | /// 166 | /// If the underlying vector doesn't support vectored writing, we instead 167 | /// simply write the first non-empty buffer with `write`. This way, we 168 | /// get the benefits of more granular partial-line handling without losing 169 | /// anything in efficiency 170 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { 171 | // If there's no specialized behavior for write_vectored, just use 172 | // write. This has the benefit of more granular partial-line handling. 173 | #[cfg(can_vector)] 174 | if !self.is_write_vectored() { 175 | return match bufs.iter().find(|buf| !buf.is_empty()) { 176 | Some(buf) => self.write(buf), 177 | None => Ok(0), 178 | }; 179 | } 180 | 181 | // Find the buffer containing the last newline 182 | let last_newline_buf_idx = bufs 183 | .iter() 184 | .enumerate() 185 | .rev() 186 | .find_map(|(i, buf)| memchr::memchr(b'\n', buf).map(|_| i)); 187 | 188 | // If there are no new newlines (that is, if this write is less than 189 | // one line), just do a regular buffered write 190 | let last_newline_buf_idx = match last_newline_buf_idx { 191 | // No newlines; just do a normal buffered write 192 | None => { 193 | self.flush_if_completed_line()?; 194 | return self.buffer.write_vectored(bufs); 195 | } 196 | Some(i) => i, 197 | }; 198 | 199 | // Flush existing content to prepare for our write 200 | self.buffer.flush_buf()?; 201 | 202 | // This is what we're going to try to write directly to the inner 203 | // writer. The rest will be buffered, if nothing goes wrong. 204 | let (lines, tail) = bufs.split_at(last_newline_buf_idx + 1); 205 | 206 | // Write `lines` directly to the inner writer. In keeping with the 207 | // `write` convention, make at most one attempt to add new (unbuffered) 208 | // data. Because this write doesn't touch the `BufDuplexer` state directly, 209 | // and the buffer is known to be empty, we don't need to worry about 210 | // self.panicked here. 211 | let flushed = self.inner_mut().write_vectored(lines)?; 212 | 213 | // If inner returns Ok(0), propagate that to the caller without 214 | // doing additional buffering; otherwise we're just guaranteeing 215 | // an "ErrorKind::WriteZero" later. 216 | if flushed == 0 { 217 | return Ok(0); 218 | } 219 | 220 | // Don't try to reconstruct the exact amount written; just bail 221 | // in the event of a partial write 222 | let lines_len = lines.iter().map(|buf| buf.len()).sum(); 223 | if flushed < lines_len { 224 | return Ok(flushed); 225 | } 226 | 227 | // Now that the write has succeeded, buffer the rest (or as much of the 228 | // rest as possible) 229 | let buffered: usize = tail 230 | .iter() 231 | .filter(|buf| !buf.is_empty()) 232 | .map(|buf| self.buffer.write_to_buf(buf)) 233 | .take_while(|&n| n > 0) 234 | .sum(); 235 | 236 | Ok(flushed + buffered) 237 | } 238 | 239 | #[cfg(can_vector)] 240 | #[inline] 241 | fn is_write_vectored(&self) -> bool { 242 | self.buffer.is_write_vectored() 243 | } 244 | 245 | /// Write some data into this `BufReaderLineWriterShim` with line 246 | /// buffering. This means that, if any newlines are present in the 247 | /// data, the data up to the last newline is sent directly to the 248 | /// underlying writer, and data after it is buffered. 249 | /// 250 | /// Because this function attempts to send completed lines to the 251 | /// underlying writer, it will also flush the existing buffer if it 252 | /// contains any newlines, even if the incoming data does not contain 253 | /// any newlines. 254 | fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { 255 | match memchr::memrchr(b'\n', buf) { 256 | // If there are no new newlines (that is, if this write is less than 257 | // one line), just do a regular buffered write (which may flush if 258 | // we exceed the inner buffer's size) 259 | None => { 260 | self.flush_if_completed_line()?; 261 | self.buffer.write_all(buf) 262 | } 263 | Some(newline_idx) => { 264 | let (lines, tail) = buf.split_at(newline_idx + 1); 265 | 266 | if self.writer_buffered().is_empty() { 267 | self.inner_mut().write_all(lines)?; 268 | } else { 269 | // If there is any buffered data, we add the incoming lines 270 | // to that buffer before flushing, which saves us at least 271 | // one write call. We can't really do this with `write`, 272 | // since we can't do this *and* not suppress errors *and* 273 | // report a consistent state to the caller in a return 274 | // value, but here in write_all it's fine. 275 | self.buffer.write_all(lines)?; 276 | self.buffer.flush_buf()?; 277 | } 278 | 279 | self.buffer.write_all(tail) 280 | } 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/buffered/mod.rs: -------------------------------------------------------------------------------- 1 | //! This file is derived from Rust's library/std/src/io/buffered at revision 2 | //! f7801d6c7cc19ab22bdebcc8efa894a564c53469. 3 | //! 4 | //! Buffering wrappers for I/O traits. 5 | //! 6 | //! This differs from [`bufreaderwriter`] in not requiring or implementing 7 | //! `Seek`. This crate is meant for interactive streams, and not files. 8 | //! 9 | //! [`bufreaderwriter`]: https://crates.io/crates/bufreaderwriter 10 | 11 | mod buf_duplexer; 12 | mod buf_reader_line_writer; 13 | mod buf_reader_line_writer_shim; 14 | 15 | use std::io::Error; 16 | use std::{error, fmt}; 17 | 18 | pub use buf_duplexer::BufDuplexer; 19 | pub use buf_reader_line_writer::BufReaderLineWriter; 20 | use buf_reader_line_writer_shim::BufReaderLineWriterShim; 21 | 22 | /// The value from `library/std/src/sys_common/io.rs`. 23 | pub(super) const DEFAULT_BUF_SIZE: usize = 8 * 1024; 24 | 25 | /// An error returned by [`BufWriter::into_inner`] which combines an error that 26 | /// happened while writing out the buffer, and the buffered writer object 27 | /// which may be used to recover from the condition. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ```no_run 32 | /// use std::io::BufWriter; 33 | /// use std::net::TcpStream; 34 | /// 35 | /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 36 | /// 37 | /// // do stuff with the stream 38 | /// 39 | /// // we want to get our `TcpStream` back, so let's try: 40 | /// 41 | /// let stream = match stream.into_inner() { 42 | /// Ok(s) => s, 43 | /// Err(e) => { 44 | /// // Here, e is an `IntoInnerError` 45 | /// panic!("An error occurred"); 46 | /// } 47 | /// }; 48 | /// ``` 49 | /// 50 | /// [`BufWriter::into_inner`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html#method.into_inner 51 | #[derive(Debug)] 52 | pub struct IntoInnerError(W, Error); 53 | 54 | impl IntoInnerError { 55 | /// Construct a new `IntoInnerError` 56 | fn new(writer: W, error: Error) -> Self { 57 | Self(writer, error) 58 | } 59 | 60 | /// Helper to construct a new `IntoInnerError`; intended to help with` 61 | /// adapters that wrap other adapters 62 | fn new_wrapped(self, f: impl FnOnce(W) -> W2) -> IntoInnerError { 63 | let Self(writer, error) = self; 64 | IntoInnerError::new(f(writer), error) 65 | } 66 | 67 | /// Returns the error which caused the call to [`BufWriter::into_inner()`] 68 | /// to fail. 69 | /// 70 | /// This error was returned when attempting to write the internal buffer. 71 | /// 72 | /// # Examples 73 | /// 74 | /// ```no_run 75 | /// use std::io::BufWriter; 76 | /// use std::net::TcpStream; 77 | /// 78 | /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 79 | /// 80 | /// // do stuff with the stream 81 | /// 82 | /// // we want to get our `TcpStream` back, so let's try: 83 | /// 84 | /// let stream = match stream.into_inner() { 85 | /// Ok(s) => s, 86 | /// Err(e) => { 87 | /// // Here, e is an `IntoInnerError`, let's log the inner error. 88 | /// // 89 | /// // We'll just 'log' to stdout for this example. 90 | /// println!("{}", e.error()); 91 | /// 92 | /// panic!("An unexpected error occurred."); 93 | /// } 94 | /// }; 95 | /// ``` 96 | /// 97 | /// [`BufWriter::into_inner()`]: std::io::BufWriter::into_inner 98 | pub fn error(&self) -> &Error { 99 | &self.1 100 | } 101 | 102 | /// Returns the buffered writer instance which generated the error. 103 | /// 104 | /// The returned object can be used for error recovery, such as 105 | /// re-inspecting the buffer. 106 | /// 107 | /// # Examples 108 | /// 109 | /// ```no_run 110 | /// use std::io::BufWriter; 111 | /// use std::net::TcpStream; 112 | /// 113 | /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); 114 | /// 115 | /// // do stuff with the stream 116 | /// 117 | /// // we want to get our `TcpStream` back, so let's try: 118 | /// 119 | /// let stream = match stream.into_inner() { 120 | /// Ok(s) => s, 121 | /// Err(e) => { 122 | /// // Here, e is an `IntoInnerError`, let's re-examine the buffer: 123 | /// let buffer = e.into_inner(); 124 | /// 125 | /// // do stuff to try to recover 126 | /// 127 | /// // afterwards, let's just return the stream 128 | /// buffer.into_inner().unwrap() 129 | /// } 130 | /// }; 131 | /// ``` 132 | pub fn into_inner(self) -> W { 133 | self.0 134 | } 135 | } 136 | 137 | impl From> for Error { 138 | fn from(iie: IntoInnerError) -> Self { 139 | iie.1 140 | } 141 | } 142 | 143 | impl error::Error for IntoInnerError { 144 | #[allow(deprecated, deprecated_in_future)] 145 | fn description(&self) -> &str { 146 | error::Error::description(self.error()) 147 | } 148 | } 149 | 150 | impl fmt::Display for IntoInnerError { 151 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 152 | self.error().fmt(f) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Unbuffered and unlocked I/O streams. 2 | //! 3 | //! For a starting point, see [`StreamReader`] and [`StreamWriter`] for input 4 | //! and output streams. There's also [`StreamDuplexer`] for interactive 5 | //! streams. 6 | //! 7 | //! Since these types are unbuffered, it's advisable for most use cases to wrap 8 | //! them in buffering types such as [`std::io::BufReader`], 9 | //! [`std::io::BufWriter`], [`std::io::LineWriter`], [`BufDuplexer`], or 10 | //! [`BufReaderLineWriter`]. 11 | //! 12 | //! [`BufReader`]: std::io::BufReader 13 | //! [`BufWriter`]: std::io::BufWriter 14 | //! [`LineWriter`]: std::io::LineWriter 15 | //! [`AsRawFd`]: std::os::unix::io::AsRawFd 16 | //! [pipe]: https://crates.io/crates/os_pipe 17 | 18 | #![deny(missing_docs)] 19 | #![cfg_attr(can_vector, feature(can_vector))] 20 | #![cfg_attr(write_all_vectored, feature(write_all_vectored))] 21 | #![cfg_attr(read_initializer, feature(read_initializer))] 22 | #![cfg_attr(docsrs, feature(doc_cfg))] 23 | #![cfg_attr(target_os = "wasi", feature(wasi_ext))] 24 | 25 | /* 26 | #[cfg(feature = "async-std")] 27 | #[cfg_attr(docsrs, doc(cfg(feature = "async-std")))] 28 | mod async_std; 29 | */ 30 | mod buffered; 31 | mod lockers; 32 | mod streams; 33 | /* 34 | #[cfg(feature = "tokio")] 35 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 36 | mod tokio; 37 | */ 38 | 39 | /* 40 | #[cfg(feature = "async-std")] 41 | #[cfg_attr(docsrs, doc(cfg(feature = "async-std")))] 42 | pub use crate::async_std::{AsyncStreamDuplexer, AsyncStreamReader, AsyncStreamWriter}; 43 | #[cfg(feature = "tokio")] 44 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 45 | pub use crate::tokio::{TokioStreamDuplexer, TokioStreamReader, TokioStreamWriter}; 46 | */ 47 | pub use buffered::{BufDuplexer, BufReaderLineWriter, IntoInnerError}; 48 | pub use streams::{StreamDuplexer, StreamReader, StreamWriter}; 49 | -------------------------------------------------------------------------------- /src/lockers.rs: -------------------------------------------------------------------------------- 1 | //! Hold locks for the process' stdin and stdout. 2 | 3 | use io_lifetimes::AsFilelike; 4 | #[cfg(not(windows))] 5 | use io_lifetimes::{AsFd, BorrowedFd}; 6 | use os_pipe::PipeReader; 7 | use parking::{Parker, Unparker}; 8 | use std::io::{self, stderr, stdin, stdout}; 9 | #[cfg(unix)] 10 | use std::os::unix::io::{AsRawFd, RawFd}; 11 | #[cfg(target_os = "wasi")] 12 | use std::os::wasi::io::{AsRawFd, RawFd}; 13 | use std::sync::atomic::AtomicBool; 14 | use std::sync::atomic::Ordering::SeqCst; 15 | use std::thread::{self, JoinHandle}; 16 | use system_interface::io::ReadReady; 17 | #[cfg(windows)] 18 | use { 19 | io_extras::os::windows::{ 20 | AsHandleOrSocket, AsRawHandleOrSocket, BorrowedHandleOrSocket, RawHandleOrSocket, 21 | }, 22 | io_lifetimes::{AsHandle, BorrowedHandle}, 23 | std::os::windows::io::{AsRawHandle, RawHandle}, 24 | }; 25 | 26 | // Statically track whether stdin and stdout are claimed. This allows us to 27 | // issue an error if they're ever claimed multiple times, instead of just 28 | // hanging. 29 | static STDIN_CLAIMED: AtomicBool = AtomicBool::new(false); 30 | static STDOUT_CLAIMED: AtomicBool = AtomicBool::new(false); 31 | static STDERR_CLAIMED: AtomicBool = AtomicBool::new(false); 32 | 33 | // The locker thread just acquires a lock and parks, so it doesn't need much 34 | // memory. Rust adjusts this up to `PTHREAD_STACK_MIN`/etc. as needed. 35 | #[cfg(not(target_os = "freebsd"))] 36 | const LOCKER_STACK_SIZE: usize = 64; 37 | 38 | // On FreeBSD, we reportedly need more than the minimum: 39 | // 40 | #[cfg(target_os = "freebsd")] 41 | const LOCKER_STACK_SIZE: usize = 32 * 1024; 42 | 43 | /// This class acquires a lock on `stdin` and prevents applications from 44 | /// accidentally accessing it through other means. 45 | pub(crate) struct StdinLocker { 46 | #[cfg(not(windows))] 47 | raw_fd: RawFd, 48 | #[cfg(windows)] 49 | raw_handle: RawHandle, 50 | unparker: Unparker, 51 | join_handle: Option>, 52 | } 53 | 54 | /// This class acquires a lock on `stdout` and prevents applications from 55 | /// accidentally accessing it through other means. 56 | pub(crate) struct StdoutLocker { 57 | #[cfg(not(windows))] 58 | raw_fd: RawFd, 59 | #[cfg(windows)] 60 | raw_handle: RawHandle, 61 | unparker: Unparker, 62 | join_handle: Option>, 63 | } 64 | 65 | /// This class acquires a lock on `stderr` and prevents applications from 66 | /// accidentally accessing it through other means. 67 | pub(crate) struct StderrLocker { 68 | #[cfg(not(windows))] 69 | raw_fd: RawFd, 70 | #[cfg(windows)] 71 | raw_handle: RawHandle, 72 | unparker: Unparker, 73 | join_handle: Option>, 74 | } 75 | 76 | impl StdinLocker { 77 | /// An `InputByteStream` can take the value of the process' stdin, in which 78 | /// case we want it to have exclusive access to `stdin`. Lock the Rust 79 | /// standard library's `stdin` to prevent accidental misuse. 80 | /// 81 | /// Fails if a `StdinLocker` instance already exists. 82 | pub(crate) fn new() -> io::Result { 83 | if STDIN_CLAIMED 84 | .compare_exchange(false, true, SeqCst, SeqCst) 85 | .is_ok() 86 | { 87 | // `StdinLock` is not `Send`. To let `StdinLocker` be send, hold 88 | // the lock on a parked thread. 89 | let parker = Parker::new(); 90 | let unparker = parker.unparker(); 91 | let stdin = stdin(); 92 | #[cfg(not(windows))] 93 | let raw_fd = stdin.as_raw_fd(); 94 | #[cfg(windows)] 95 | let raw_handle = stdin.as_raw_handle(); 96 | let join_handle = Some( 97 | thread::Builder::new() 98 | .name("ensure exclusive access to stdin".to_owned()) 99 | .stack_size(LOCKER_STACK_SIZE) 100 | .spawn(move || { 101 | let _lock = stdin.lock(); 102 | parker.park() 103 | })?, 104 | ); 105 | 106 | Ok(Self { 107 | #[cfg(not(windows))] 108 | raw_fd, 109 | #[cfg(windows)] 110 | raw_handle, 111 | unparker, 112 | join_handle, 113 | }) 114 | } else { 115 | Err(io::Error::new( 116 | io::ErrorKind::Other, 117 | "attempted dual-ownership of stdin", 118 | )) 119 | } 120 | } 121 | } 122 | 123 | impl StdoutLocker { 124 | /// An `OutputByteStream` can take the value of the process' stdout, in 125 | /// which case we want it to have exclusive access to `stdout`. Lock the 126 | /// Rust standard library's `stdout` to prevent accidental misuse. 127 | /// 128 | /// Fails if a `StdoutLocker` instance already exists. 129 | pub(crate) fn new() -> io::Result { 130 | if STDOUT_CLAIMED 131 | .compare_exchange(false, true, SeqCst, SeqCst) 132 | .is_ok() 133 | { 134 | // `StdoutLock` is not `Send`. To let `StdoutLocker` be send, hold 135 | // the lock on a parked thread. 136 | let parker = Parker::new(); 137 | let unparker = parker.unparker(); 138 | let stdout = stdout(); 139 | #[cfg(not(windows))] 140 | let raw_fd = stdout.as_raw_fd(); 141 | #[cfg(windows)] 142 | let raw_handle = stdout.as_raw_handle(); 143 | let join_handle = Some( 144 | thread::Builder::new() 145 | .name("ensure exclusive access to stdout".to_owned()) 146 | .stack_size(LOCKER_STACK_SIZE) 147 | .spawn(move || { 148 | let _lock = stdout.lock(); 149 | parker.park() 150 | })?, 151 | ); 152 | 153 | Ok(Self { 154 | #[cfg(not(windows))] 155 | raw_fd, 156 | #[cfg(windows)] 157 | raw_handle, 158 | unparker, 159 | join_handle, 160 | }) 161 | } else { 162 | Err(io::Error::new( 163 | io::ErrorKind::Other, 164 | "attempted dual-ownership of stdout", 165 | )) 166 | } 167 | } 168 | } 169 | 170 | impl StderrLocker { 171 | /// An `OutputByteStream` can take the value of the process' stderr, in 172 | /// which case we want it to have exclusive access to `stderr`. Lock the 173 | /// Rust standard library's `stderr` to prevent accidental misuse. 174 | /// 175 | /// Fails if a `StderrLocker` instance already exists. 176 | pub(crate) fn new() -> io::Result { 177 | if STDOUT_CLAIMED 178 | .compare_exchange(false, true, SeqCst, SeqCst) 179 | .is_ok() 180 | { 181 | // `StderrLock` is not `Send`. To let `StderrLocker` be send, hold 182 | // the lock on a parked thread. 183 | let parker = Parker::new(); 184 | let unparker = parker.unparker(); 185 | let stderr = stderr(); 186 | #[cfg(not(windows))] 187 | let raw_fd = stderr.as_raw_fd(); 188 | #[cfg(windows)] 189 | let raw_handle = stderr.as_raw_handle(); 190 | let join_handle = Some( 191 | thread::Builder::new() 192 | .name("ensure exclusive access to stderr".to_owned()) 193 | .stack_size(LOCKER_STACK_SIZE) 194 | .spawn(move || { 195 | let _lock = stderr.lock(); 196 | parker.park() 197 | })?, 198 | ); 199 | 200 | Ok(Self { 201 | #[cfg(not(windows))] 202 | raw_fd, 203 | #[cfg(windows)] 204 | raw_handle, 205 | unparker, 206 | join_handle, 207 | }) 208 | } else { 209 | Err(io::Error::new( 210 | io::ErrorKind::Other, 211 | "attempted dual-ownership of stderr", 212 | )) 213 | } 214 | } 215 | } 216 | 217 | impl Drop for StdinLocker { 218 | #[inline] 219 | fn drop(&mut self) { 220 | self.unparker.unpark(); 221 | self.join_handle.take().unwrap().join().unwrap(); 222 | STDIN_CLAIMED.store(false, SeqCst); 223 | } 224 | } 225 | 226 | impl Drop for StdoutLocker { 227 | #[inline] 228 | fn drop(&mut self) { 229 | self.unparker.unpark(); 230 | self.join_handle.take().unwrap().join().unwrap(); 231 | STDOUT_CLAIMED.store(false, SeqCst); 232 | } 233 | } 234 | 235 | impl Drop for StderrLocker { 236 | #[inline] 237 | fn drop(&mut self) { 238 | self.unparker.unpark(); 239 | self.join_handle.take().unwrap().join().unwrap(); 240 | STDERR_CLAIMED.store(false, SeqCst); 241 | } 242 | } 243 | 244 | #[cfg(not(windows))] 245 | impl AsRawFd for StdinLocker { 246 | #[inline] 247 | fn as_raw_fd(&self) -> RawFd { 248 | self.raw_fd 249 | } 250 | } 251 | 252 | #[cfg(not(windows))] 253 | impl AsRawFd for StdoutLocker { 254 | #[inline] 255 | fn as_raw_fd(&self) -> RawFd { 256 | self.raw_fd 257 | } 258 | } 259 | 260 | #[cfg(not(windows))] 261 | impl AsRawFd for StderrLocker { 262 | #[inline] 263 | fn as_raw_fd(&self) -> RawFd { 264 | self.raw_fd 265 | } 266 | } 267 | 268 | #[cfg(windows)] 269 | impl AsRawHandle for StdinLocker { 270 | #[inline] 271 | fn as_raw_handle(&self) -> RawHandle { 272 | self.raw_handle 273 | } 274 | } 275 | 276 | #[cfg(windows)] 277 | impl AsRawHandle for StdoutLocker { 278 | #[inline] 279 | fn as_raw_handle(&self) -> RawHandle { 280 | self.raw_handle 281 | } 282 | } 283 | 284 | #[cfg(windows)] 285 | impl AsRawHandle for StderrLocker { 286 | #[inline] 287 | fn as_raw_handle(&self) -> RawHandle { 288 | self.raw_handle 289 | } 290 | } 291 | 292 | #[cfg(windows)] 293 | impl AsRawHandleOrSocket for StdinLocker { 294 | #[inline] 295 | fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { 296 | RawHandleOrSocket::unowned_from_raw_handle(self.as_raw_handle()) 297 | } 298 | } 299 | 300 | #[cfg(windows)] 301 | impl AsRawHandleOrSocket for StdoutLocker { 302 | #[inline] 303 | fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { 304 | RawHandleOrSocket::unowned_from_raw_handle(self.as_raw_handle()) 305 | } 306 | } 307 | 308 | #[cfg(windows)] 309 | impl AsRawHandleOrSocket for StderrLocker { 310 | #[inline] 311 | fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { 312 | RawHandleOrSocket::unowned_from_raw_handle(self.as_raw_handle()) 313 | } 314 | } 315 | 316 | #[cfg(not(windows))] 317 | impl AsFd for StdinLocker { 318 | #[inline] 319 | fn as_fd(&self) -> BorrowedFd<'_> { 320 | unsafe { BorrowedFd::borrow_raw(self.raw_fd) } 321 | } 322 | } 323 | 324 | #[cfg(not(windows))] 325 | impl AsFd for StdoutLocker { 326 | #[inline] 327 | fn as_fd(&self) -> BorrowedFd<'_> { 328 | unsafe { BorrowedFd::borrow_raw(self.raw_fd) } 329 | } 330 | } 331 | 332 | #[cfg(not(windows))] 333 | impl AsFd for StderrLocker { 334 | #[inline] 335 | fn as_fd(&self) -> BorrowedFd<'_> { 336 | unsafe { BorrowedFd::borrow_raw(self.raw_fd) } 337 | } 338 | } 339 | 340 | #[cfg(windows)] 341 | impl AsHandle for StdinLocker { 342 | #[inline] 343 | fn as_handle(&self) -> BorrowedHandle<'_> { 344 | unsafe { BorrowedHandle::borrow_raw(self.raw_handle) } 345 | } 346 | } 347 | 348 | #[cfg(windows)] 349 | impl AsHandle for StdoutLocker { 350 | #[inline] 351 | fn as_handle(&self) -> BorrowedHandle<'_> { 352 | unsafe { BorrowedHandle::borrow_raw(self.raw_handle) } 353 | } 354 | } 355 | 356 | #[cfg(windows)] 357 | impl AsHandle for StderrLocker { 358 | #[inline] 359 | fn as_handle(&self) -> BorrowedHandle<'_> { 360 | unsafe { BorrowedHandle::borrow_raw(self.raw_handle) } 361 | } 362 | } 363 | 364 | #[cfg(windows)] 365 | impl AsHandleOrSocket for StdinLocker { 366 | #[inline] 367 | fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> { 368 | unsafe { 369 | BorrowedHandleOrSocket::borrow_raw(RawHandleOrSocket::unowned_from_raw_handle( 370 | self.raw_handle, 371 | )) 372 | } 373 | } 374 | } 375 | 376 | #[cfg(windows)] 377 | impl AsHandleOrSocket for StdoutLocker { 378 | #[inline] 379 | fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> { 380 | unsafe { 381 | BorrowedHandleOrSocket::borrow_raw(RawHandleOrSocket::unowned_from_raw_handle( 382 | self.raw_handle, 383 | )) 384 | } 385 | } 386 | } 387 | 388 | #[cfg(windows)] 389 | impl AsHandleOrSocket for StderrLocker { 390 | #[inline] 391 | fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> { 392 | unsafe { 393 | BorrowedHandleOrSocket::borrow_raw(RawHandleOrSocket::unowned_from_raw_handle( 394 | self.raw_handle, 395 | )) 396 | } 397 | } 398 | } 399 | 400 | impl ReadReady for StdinLocker { 401 | #[inline] 402 | fn num_ready_bytes(&self) -> io::Result { 403 | self.as_filelike_view::().num_ready_bytes() 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /tests/buffered.rs: -------------------------------------------------------------------------------- 1 | //! This file is derived from Rust's library/std/src/io/buffered/tests.rs at 2 | //! revision f7801d6c7cc19ab22bdebcc8efa894a564c53469. 3 | 4 | #![cfg_attr(can_vector, feature(can_vector))] 5 | #![cfg_attr(write_all_vectored, feature(write_all_vectored))] 6 | #![cfg_attr(bench, feature(test))] 7 | 8 | #[cfg(bench)] 9 | extern crate test; 10 | 11 | use duplex::Duplex; 12 | use io_streams::{BufDuplexer, BufReaderLineWriter}; 13 | use std::fmt::Arguments; 14 | use std::io::prelude::*; 15 | use std::io::{self, ErrorKind, IoSlice, IoSliceMut}; 16 | use std::ops::{Deref, DerefMut}; 17 | use std::sync::atomic::{AtomicUsize, Ordering}; 18 | use std::{panic, thread}; 19 | 20 | /// The tests in this file were written for read-only and write-only streams; 21 | /// `JustReader` and `JustWriter` minimally adapt read-only and write-only 22 | /// streams to implement `Duplex`. 23 | #[derive(Debug)] 24 | struct JustReader(T); 25 | 26 | impl Deref for JustReader { 27 | type Target = T; 28 | 29 | fn deref(&self) -> &T { 30 | &self.0 31 | } 32 | } 33 | 34 | impl DerefMut for JustReader { 35 | fn deref_mut(&mut self) -> &mut T { 36 | &mut self.0 37 | } 38 | } 39 | 40 | impl Read for JustReader { 41 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 42 | self.0.read(buf) 43 | } 44 | 45 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut]) -> io::Result { 46 | self.0.read_vectored(bufs) 47 | } 48 | 49 | #[cfg(can_vector)] 50 | fn is_read_vectored(&self) -> bool { 51 | self.0.is_read_vectored() 52 | } 53 | 54 | fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { 55 | self.0.read_to_end(buf) 56 | } 57 | 58 | fn read_to_string(&mut self, buf: &mut String) -> io::Result { 59 | self.0.read_to_string(buf) 60 | } 61 | 62 | fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { 63 | self.0.read_exact(buf) 64 | } 65 | } 66 | 67 | impl Write for JustReader { 68 | fn write(&mut self, _buf: &[u8]) -> io::Result { 69 | panic!("Write function called on a JustReader") 70 | } 71 | 72 | fn flush(&mut self) -> io::Result<()> { 73 | Ok(()) 74 | } 75 | 76 | fn write_vectored(&mut self, _bufs: &[IoSlice]) -> io::Result { 77 | panic!("Write function called on a JustReader") 78 | } 79 | 80 | #[cfg(can_vector)] 81 | fn is_write_vectored(&self) -> bool { 82 | panic!("Write function called on a JustReader") 83 | } 84 | 85 | fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { 86 | panic!("Write function called on a JustReader") 87 | } 88 | 89 | #[cfg(write_all_vectored)] 90 | fn write_all_vectored(&mut self, _bufs: &mut [IoSlice]) -> io::Result<()> { 91 | panic!("Write function called on a JustReader") 92 | } 93 | 94 | fn write_fmt(&mut self, _fmt: Arguments) -> io::Result<()> { 95 | panic!("Write function called on a JustReader") 96 | } 97 | } 98 | 99 | impl Duplex for JustReader {} 100 | 101 | #[derive(Debug)] 102 | struct JustWriter(T); 103 | 104 | impl Deref for JustWriter { 105 | type Target = T; 106 | 107 | fn deref(&self) -> &T { 108 | &self.0 109 | } 110 | } 111 | 112 | impl DerefMut for JustWriter { 113 | fn deref_mut(&mut self) -> &mut T { 114 | &mut self.0 115 | } 116 | } 117 | 118 | impl Read for JustWriter { 119 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 120 | panic!("Read function called on a JustWriter") 121 | } 122 | 123 | fn read_vectored(&mut self, _bufs: &mut [IoSliceMut]) -> io::Result { 124 | panic!("Read function called on a JustWriter") 125 | } 126 | 127 | #[cfg(can_vector)] 128 | fn is_read_vectored(&self) -> bool { 129 | panic!("Read function called on a JustWriter") 130 | } 131 | 132 | fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { 133 | panic!("Read function called on a JustWriter") 134 | } 135 | 136 | fn read_to_string(&mut self, _buf: &mut String) -> io::Result { 137 | panic!("Read function called on a JustWriter") 138 | } 139 | 140 | fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { 141 | panic!("Read function called on a JustWriter") 142 | } 143 | } 144 | 145 | impl Write for JustWriter { 146 | #[inline] 147 | fn write(&mut self, buf: &[u8]) -> io::Result { 148 | self.0.write(buf) 149 | } 150 | 151 | #[inline] 152 | fn flush(&mut self) -> io::Result<()> { 153 | self.0.flush() 154 | } 155 | 156 | #[inline] 157 | fn write_vectored(&mut self, bufs: &[IoSlice]) -> io::Result { 158 | self.0.write_vectored(bufs) 159 | } 160 | 161 | #[cfg(can_vector)] 162 | #[inline] 163 | fn is_write_vectored(&self) -> bool { 164 | self.0.is_write_vectored() 165 | } 166 | 167 | #[inline] 168 | fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { 169 | self.0.write_all(buf) 170 | } 171 | 172 | #[cfg(write_all_vectored)] 173 | #[inline] 174 | fn write_all_vectored(&mut self, bufs: &mut [IoSlice]) -> io::Result<()> { 175 | self.0.write_all_vectored(bufs) 176 | } 177 | 178 | #[inline] 179 | fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { 180 | self.0.write_fmt(fmt) 181 | } 182 | } 183 | 184 | impl Duplex for JustWriter {} 185 | 186 | /// A dummy reader intended at testing short-reads propagation. 187 | pub struct ShortReader { 188 | lengths: Vec, 189 | } 190 | 191 | // FIXME: rustfmt and tidy disagree about the correct formatting of this 192 | // function. This leads to issues for users with editors configured to 193 | // rustfmt-on-save. 194 | impl Read for ShortReader { 195 | fn read(&mut self, _: &mut [u8]) -> io::Result { 196 | if self.lengths.is_empty() { 197 | Ok(0) 198 | } else { 199 | Ok(self.lengths.remove(0)) 200 | } 201 | } 202 | } 203 | 204 | impl Write for ShortReader { 205 | fn write(&mut self, _: &[u8]) -> io::Result { 206 | panic!("ShortReader doesn't support writing") 207 | } 208 | 209 | fn flush(&mut self) -> io::Result<()> { 210 | Ok(()) 211 | } 212 | } 213 | 214 | #[test] 215 | fn test_buffered_reader() { 216 | let inner: Vec = vec![5, 6, 7, 0, 1, 2, 3, 4]; 217 | let mut reader = BufDuplexer::with_capacities(2, 2, JustReader(io::Cursor::new(inner))); 218 | 219 | let mut buf = [0, 0, 0]; 220 | let nread = reader.read(&mut buf); 221 | assert_eq!(nread.unwrap(), 3); 222 | assert_eq!(buf, [5, 6, 7]); 223 | assert_eq!(reader.reader_buffer(), []); 224 | 225 | let mut buf = [0, 0]; 226 | let nread = reader.read(&mut buf); 227 | assert_eq!(nread.unwrap(), 2); 228 | assert_eq!(buf, [0, 1]); 229 | assert_eq!(reader.reader_buffer(), []); 230 | 231 | let mut buf = [0]; 232 | let nread = reader.read(&mut buf); 233 | assert_eq!(nread.unwrap(), 1); 234 | assert_eq!(buf, [2]); 235 | assert_eq!(reader.reader_buffer(), [3]); 236 | 237 | let mut buf = [0, 0, 0]; 238 | let nread = reader.read(&mut buf); 239 | assert_eq!(nread.unwrap(), 1); 240 | assert_eq!(buf, [3, 0, 0]); 241 | assert_eq!(reader.reader_buffer(), []); 242 | 243 | let nread = reader.read(&mut buf); 244 | assert_eq!(nread.unwrap(), 1); 245 | assert_eq!(buf, [4, 0, 0]); 246 | assert_eq!(reader.reader_buffer(), []); 247 | 248 | assert_eq!(reader.read(&mut buf).unwrap(), 0); 249 | } 250 | 251 | #[test] 252 | fn test_buffered_reader_invalidated_after_read() { 253 | let inner: Vec = vec![5, 6, 7, 0, 1, 2, 3, 4]; 254 | let mut reader = BufDuplexer::with_capacities(3, 3, JustReader(io::Cursor::new(inner))); 255 | 256 | assert_eq!(reader.fill_buf().ok(), Some(&[5, 6, 7][..])); 257 | reader.consume(3); 258 | 259 | let mut buffer = [0, 0, 0, 0, 0]; 260 | assert_eq!(reader.read(&mut buffer).ok(), Some(5)); 261 | assert_eq!(buffer, [0, 1, 2, 3, 4]); 262 | } 263 | 264 | #[test] 265 | fn test_buffered_writer() { 266 | let inner = Vec::new(); 267 | let mut writer = BufDuplexer::with_capacities(2, 2, JustWriter(io::Cursor::new(inner))); 268 | 269 | assert_eq!(writer.write(&[0, 1]).unwrap(), 2); 270 | assert_eq!(writer.writer_buffer(), []); 271 | assert_eq!(*writer.get_ref().get_ref(), [0, 1]); 272 | 273 | assert_eq!(writer.write(&[2]).unwrap(), 1); 274 | assert_eq!(writer.writer_buffer(), [2]); 275 | assert_eq!(*writer.get_ref().get_ref(), [0, 1]); 276 | 277 | assert_eq!(writer.write(&[3]).unwrap(), 1); 278 | assert_eq!(writer.writer_buffer(), [2, 3]); 279 | assert_eq!(*writer.get_ref().get_ref(), [0, 1]); 280 | 281 | writer.flush().unwrap(); 282 | assert_eq!(writer.writer_buffer(), []); 283 | assert_eq!(*writer.get_ref().get_ref(), [0, 1, 2, 3]); 284 | 285 | assert_eq!(writer.write(&[4]).unwrap(), 1); 286 | assert_eq!(writer.write(&[5]).unwrap(), 1); 287 | assert_eq!(writer.writer_buffer(), [4, 5]); 288 | assert_eq!(*writer.get_ref().get_ref(), [0, 1, 2, 3]); 289 | 290 | assert_eq!(writer.write(&[6]).unwrap(), 1); 291 | assert_eq!(writer.writer_buffer(), [6]); 292 | assert_eq!(*writer.get_ref().get_ref(), [0, 1, 2, 3, 4, 5]); 293 | 294 | assert_eq!(writer.write(&[7, 8]).unwrap(), 2); 295 | assert_eq!(writer.writer_buffer(), []); 296 | assert_eq!(*writer.get_ref().get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8]); 297 | 298 | assert_eq!(writer.write(&[9, 10, 11]).unwrap(), 3); 299 | assert_eq!(writer.writer_buffer(), []); 300 | assert_eq!( 301 | *writer.get_ref().get_ref(), 302 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 303 | ); 304 | 305 | writer.flush().unwrap(); 306 | assert_eq!(writer.writer_buffer(), []); 307 | assert_eq!( 308 | *writer.get_ref().get_ref(), 309 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 310 | ); 311 | } 312 | 313 | #[test] 314 | fn test_buffered_writer_inner_flushes() { 315 | let mut w = BufDuplexer::with_capacities(3, 3, JustWriter(io::Cursor::new(Vec::new()))); 316 | assert_eq!(w.write(&[0, 1]).unwrap(), 2); 317 | assert_eq!(*w.get_ref().get_ref(), []); 318 | let w = w.into_inner().unwrap(); 319 | assert_eq!(w.get_ref(), &vec![0, 1]); 320 | } 321 | 322 | #[test] 323 | fn test_read_until() { 324 | let inner: Vec = vec![0, 1, 2, 1, 0]; 325 | let mut reader = BufDuplexer::with_capacities(2, 2, JustReader(io::Cursor::new(inner))); 326 | let mut v = Vec::new(); 327 | reader.read_until(0, &mut v).unwrap(); 328 | assert_eq!(v, [0]); 329 | v.truncate(0); 330 | reader.read_until(2, &mut v).unwrap(); 331 | assert_eq!(v, [1, 2]); 332 | v.truncate(0); 333 | reader.read_until(1, &mut v).unwrap(); 334 | assert_eq!(v, [1]); 335 | v.truncate(0); 336 | reader.read_until(8, &mut v).unwrap(); 337 | assert_eq!(v, [0]); 338 | v.truncate(0); 339 | reader.read_until(9, &mut v).unwrap(); 340 | assert_eq!(v, []); 341 | } 342 | 343 | #[test] 344 | fn test_line_buffer() { 345 | let mut writer = BufReaderLineWriter::new(JustWriter(io::Cursor::new(Vec::new()))); 346 | assert_eq!(writer.write(&[0]).unwrap(), 1); 347 | assert_eq!(*writer.get_ref().get_ref(), []); 348 | assert_eq!(writer.write(&[1]).unwrap(), 1); 349 | assert_eq!(*writer.get_ref().get_ref(), []); 350 | writer.flush().unwrap(); 351 | assert_eq!(*writer.get_ref().get_ref(), [0, 1]); 352 | assert_eq!(writer.write(&[0, b'\n', 1, b'\n', 2]).unwrap(), 5); 353 | assert_eq!(*writer.get_ref().get_ref(), [0, 1, 0, b'\n', 1, b'\n']); 354 | writer.flush().unwrap(); 355 | assert_eq!(*writer.get_ref().get_ref(), [0, 1, 0, b'\n', 1, b'\n', 2]); 356 | assert_eq!(writer.write(&[3, b'\n']).unwrap(), 2); 357 | assert_eq!( 358 | *writer.get_ref().get_ref(), 359 | [0, 1, 0, b'\n', 1, b'\n', 2, 3, b'\n'] 360 | ); 361 | } 362 | 363 | #[test] 364 | fn test_read_line() { 365 | let in_buf: Vec = b"a\nb\nc".to_vec(); 366 | let mut reader = BufDuplexer::with_capacities(2, 2, JustReader(io::Cursor::new(in_buf))); 367 | let mut s = String::new(); 368 | reader.read_line(&mut s).unwrap(); 369 | assert_eq!(s, "a\n"); 370 | s.truncate(0); 371 | reader.read_line(&mut s).unwrap(); 372 | assert_eq!(s, "b\n"); 373 | s.truncate(0); 374 | reader.read_line(&mut s).unwrap(); 375 | assert_eq!(s, "c"); 376 | s.truncate(0); 377 | reader.read_line(&mut s).unwrap(); 378 | assert_eq!(s, ""); 379 | } 380 | 381 | #[test] 382 | fn test_lines() { 383 | let in_buf: Vec = b"a\nb\nc".to_vec(); 384 | let reader = BufDuplexer::with_capacities(2, 2, JustReader(io::Cursor::new(in_buf))); 385 | let mut it = reader.lines(); 386 | assert_eq!(it.next().unwrap().unwrap(), "a".to_string()); 387 | assert_eq!(it.next().unwrap().unwrap(), "b".to_string()); 388 | assert_eq!(it.next().unwrap().unwrap(), "c".to_string()); 389 | assert!(it.next().is_none()); 390 | } 391 | 392 | #[test] 393 | fn test_short_reads() { 394 | let inner = ShortReader { 395 | lengths: vec![0, 1, 2, 0, 1, 0], 396 | }; 397 | let mut reader = BufDuplexer::new(JustReader(inner)); 398 | let mut buf = [0, 0]; 399 | assert_eq!(reader.read(&mut buf).unwrap(), 0); 400 | assert_eq!(reader.read(&mut buf).unwrap(), 1); 401 | assert_eq!(reader.read(&mut buf).unwrap(), 2); 402 | assert_eq!(reader.read(&mut buf).unwrap(), 0); 403 | assert_eq!(reader.read(&mut buf).unwrap(), 1); 404 | assert_eq!(reader.read(&mut buf).unwrap(), 0); 405 | assert_eq!(reader.read(&mut buf).unwrap(), 0); 406 | } 407 | 408 | #[test] 409 | #[should_panic] 410 | fn dont_panic_in_drop_on_panicked_flush() { 411 | struct FailFlushWriter; 412 | 413 | impl Read for FailFlushWriter { 414 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 415 | panic!("FailFlushWriter doesn't support reading") 416 | } 417 | } 418 | 419 | impl Write for FailFlushWriter { 420 | fn write(&mut self, buf: &[u8]) -> io::Result { 421 | Ok(buf.len()) 422 | } 423 | 424 | fn flush(&mut self) -> io::Result<()> { 425 | Err(io::Error::last_os_error()) 426 | } 427 | } 428 | 429 | let writer = FailFlushWriter; 430 | let _writer = BufDuplexer::new(JustWriter(writer)); 431 | 432 | // If writer panics *again* due to the flush error then the process will 433 | // abort. 434 | panic!(); 435 | } 436 | 437 | #[test] 438 | #[cfg_attr(target_os = "emscripten", ignore)] 439 | fn panic_in_write_doesnt_flush_in_drop() { 440 | static WRITES: AtomicUsize = AtomicUsize::new(0); 441 | 442 | struct PanicWriter; 443 | 444 | impl Read for PanicWriter { 445 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 446 | panic!("PanicWriter doesn't support reading") 447 | } 448 | } 449 | 450 | impl Write for PanicWriter { 451 | fn write(&mut self, _: &[u8]) -> io::Result { 452 | WRITES.fetch_add(1, Ordering::SeqCst); 453 | panic!(); 454 | } 455 | 456 | fn flush(&mut self) -> io::Result<()> { 457 | Ok(()) 458 | } 459 | } 460 | 461 | thread::spawn(|| { 462 | let mut writer = BufDuplexer::new(JustWriter(PanicWriter)); 463 | let _ = writer.write(b"hello world"); 464 | let _ = writer.flush(); 465 | }) 466 | .join() 467 | .unwrap_err(); 468 | 469 | assert_eq!(WRITES.load(Ordering::SeqCst), 1); 470 | } 471 | 472 | #[cfg(bench)] 473 | struct Empty; 474 | 475 | #[cfg(bench)] 476 | impl Read for Empty { 477 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 478 | Ok(0) 479 | } 480 | } 481 | 482 | #[cfg(bench)] 483 | impl Write for Empty { 484 | fn write(&mut self, _data: &[u8]) -> io::Result { 485 | panic!("Empty doesn't support writing") 486 | } 487 | 488 | fn flush(&mut self) -> io::Result<()> { 489 | Ok(()) 490 | } 491 | } 492 | 493 | #[cfg(bench)] 494 | #[bench] 495 | fn bench_buffered_reader(b: &mut test::bench::Bencher) { 496 | b.iter(|| BufDuplexer::new(JustWriter(Empty))); 497 | } 498 | 499 | #[cfg(bench)] 500 | struct Sink; 501 | 502 | #[cfg(bench)] 503 | impl Read for Sink { 504 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 505 | panic!("Sink doesn't support reading") 506 | } 507 | } 508 | 509 | #[cfg(bench)] 510 | impl Write for Sink { 511 | fn write(&mut self, data: &[u8]) -> io::Result { 512 | Ok(data.len()) 513 | } 514 | 515 | fn flush(&mut self) -> io::Result<()> { 516 | Ok(()) 517 | } 518 | } 519 | 520 | #[cfg(bench)] 521 | #[bench] 522 | fn bench_buffered_writer(b: &mut test::bench::Bencher) { 523 | b.iter(|| BufDuplexer::new(JustWriter(Sink))); 524 | } 525 | 526 | /// A simple `Write` target, designed to be wrapped by `BufReaderLineWriter` / 527 | /// `BufDuplexer` / etc, that can have its `write` & `flush` behavior 528 | /// configured 529 | #[derive(Default, Clone)] 530 | struct ProgrammableSink { 531 | // Writes append to this slice 532 | pub buffer: Vec, 533 | 534 | // Flush sets this flag 535 | pub flushed: bool, 536 | 537 | // If true, writes will always be an error 538 | pub always_write_error: bool, 539 | 540 | // If true, flushes will always be an error 541 | pub always_flush_error: bool, 542 | 543 | // If set, only up to this number of bytes will be written in a single 544 | // call to `write` 545 | pub accept_prefix: Option, 546 | 547 | // If set, counts down with each write, and writes return an error 548 | // when it hits 0 549 | pub max_writes: Option, 550 | 551 | // If set, attempting to write when max_writes == Some(0) will be an 552 | // error; otherwise, it will return Ok(0). 553 | pub error_after_max_writes: bool, 554 | } 555 | 556 | impl Read for ProgrammableSink { 557 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 558 | panic!("ProgrammableSink doesn't support reading") 559 | } 560 | } 561 | 562 | impl Write for ProgrammableSink { 563 | fn write(&mut self, data: &[u8]) -> io::Result { 564 | if self.always_write_error { 565 | return Err(io::Error::new( 566 | io::ErrorKind::Other, 567 | "test - always_write_error", 568 | )); 569 | } 570 | 571 | match self.max_writes { 572 | Some(0) if self.error_after_max_writes => { 573 | return Err(io::Error::new(io::ErrorKind::Other, "test - max_writes")); 574 | } 575 | Some(0) => return Ok(0), 576 | Some(ref mut count) => *count -= 1, 577 | None => {} 578 | } 579 | 580 | let len = match self.accept_prefix { 581 | None => data.len(), 582 | Some(prefix) => data.len().min(prefix), 583 | }; 584 | 585 | let data = &data[..len]; 586 | self.buffer.extend_from_slice(data); 587 | 588 | Ok(len) 589 | } 590 | 591 | fn flush(&mut self) -> io::Result<()> { 592 | if self.always_flush_error { 593 | Err(io::Error::new( 594 | io::ErrorKind::Other, 595 | "test - always_flush_error", 596 | )) 597 | } else { 598 | self.flushed = true; 599 | Ok(()) 600 | } 601 | } 602 | } 603 | 604 | /// Previously the `BufReaderLineWriter` could successfully write some bytes 605 | /// but then fail to report that it has done so. Additionally, an erroneous 606 | /// flush after a successful write was permanently ignored. 607 | /// 608 | /// Test that a line writer correctly reports the number of written bytes, 609 | /// and that it attempts to flush buffered lines from previous writes 610 | /// before processing new data 611 | /// 612 | /// Regression test for #37807 613 | #[test] 614 | fn erroneous_flush_retried() { 615 | let writer = ProgrammableSink { 616 | // Only write up to 4 bytes at a time 617 | accept_prefix: Some(4), 618 | 619 | // Accept the first two writes, then error the others 620 | max_writes: Some(2), 621 | error_after_max_writes: true, 622 | 623 | ..Default::default() 624 | }; 625 | 626 | // This should write the first 4 bytes. The rest will be buffered, out 627 | // to the last newline. 628 | let mut writer = BufReaderLineWriter::new(JustWriter(writer)); 629 | assert_eq!(writer.write(b"a\nb\nc\nd\ne").unwrap(), 8); 630 | 631 | // This write should attempt to flush "c\nd\n", then buffer "e". No 632 | // errors should happen here because no further writes should be 633 | // attempted against `writer`. 634 | assert_eq!(writer.write(b"e").unwrap(), 1); 635 | assert_eq!(&writer.get_ref().buffer, b"a\nb\nc\nd\n"); 636 | } 637 | 638 | #[test] 639 | fn line_vectored() { 640 | let mut a = BufReaderLineWriter::new(JustWriter(io::Cursor::new(Vec::new()))); 641 | assert_eq!( 642 | a.write_vectored(&[ 643 | IoSlice::new(&[]), 644 | IoSlice::new(b"\n"), 645 | IoSlice::new(&[]), 646 | IoSlice::new(b"a"), 647 | ]) 648 | .unwrap(), 649 | 2, 650 | ); 651 | assert_eq!(a.get_ref().get_ref(), b"\n"); 652 | 653 | assert_eq!( 654 | a.write_vectored(&[ 655 | IoSlice::new(&[]), 656 | IoSlice::new(b"b"), 657 | IoSlice::new(&[]), 658 | IoSlice::new(b"a"), 659 | IoSlice::new(&[]), 660 | IoSlice::new(b"c"), 661 | ]) 662 | .unwrap(), 663 | 3, 664 | ); 665 | assert_eq!(a.get_ref().get_ref(), b"\n"); 666 | a.flush().unwrap(); 667 | assert_eq!(a.get_ref().get_ref(), b"\nabac"); 668 | assert_eq!(a.write_vectored(&[]).unwrap(), 0); 669 | assert_eq!( 670 | a.write_vectored(&[ 671 | IoSlice::new(&[]), 672 | IoSlice::new(&[]), 673 | IoSlice::new(&[]), 674 | IoSlice::new(&[]), 675 | ]) 676 | .unwrap(), 677 | 0, 678 | ); 679 | assert_eq!(a.write_vectored(&[IoSlice::new(b"a\nb"),]).unwrap(), 3); 680 | assert_eq!(a.get_ref().get_ref(), b"\nabaca\nb"); 681 | } 682 | 683 | #[test] 684 | fn line_vectored_partial_and_errors() { 685 | use std::collections::VecDeque; 686 | 687 | enum Call { 688 | Write { 689 | inputs: Vec<&'static [u8]>, 690 | output: io::Result, 691 | }, 692 | Flush { 693 | output: io::Result<()>, 694 | }, 695 | } 696 | 697 | #[derive(Default)] 698 | struct Writer { 699 | calls: VecDeque, 700 | } 701 | 702 | impl Read for Writer { 703 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 704 | panic!("Writer doesn't support reading") 705 | } 706 | } 707 | 708 | impl Write for Writer { 709 | fn write(&mut self, buf: &[u8]) -> io::Result { 710 | self.write_vectored(&[IoSlice::new(buf)]) 711 | } 712 | 713 | fn write_vectored(&mut self, buf: &[IoSlice<'_>]) -> io::Result { 714 | match self.calls.pop_front().expect("unexpected call to write") { 715 | Call::Write { inputs, output } => { 716 | assert_eq!(inputs, buf.iter().map(|b| &**b).collect::>()); 717 | output 718 | } 719 | Call::Flush { .. } => panic!("unexpected call to write; expected a flush"), 720 | } 721 | } 722 | 723 | #[cfg(can_vector)] 724 | fn is_write_vectored(&self) -> bool { 725 | true 726 | } 727 | 728 | fn flush(&mut self) -> io::Result<()> { 729 | match self.calls.pop_front().expect("Unexpected call to flush") { 730 | Call::Flush { output } => output, 731 | Call::Write { .. } => panic!("unexpected call to flush; expected a write"), 732 | } 733 | } 734 | } 735 | 736 | impl Drop for Writer { 737 | fn drop(&mut self) { 738 | if !thread::panicking() { 739 | assert_eq!(self.calls.len(), 0); 740 | } 741 | } 742 | } 743 | 744 | // partial writes keep going 745 | let mut a = BufReaderLineWriter::new(JustWriter(Writer::default())); 746 | assert_eq!( 747 | a.write_vectored(&[IoSlice::new(&[]), IoSlice::new(b"abc")]) 748 | .unwrap(), 749 | 3 750 | ); 751 | 752 | a.get_mut().calls.push_back(Call::Write { 753 | inputs: vec![b"abc"], 754 | output: Ok(1), 755 | }); 756 | a.get_mut().calls.push_back(Call::Write { 757 | inputs: vec![b"bc"], 758 | output: Ok(2), 759 | }); 760 | a.get_mut().calls.push_back(Call::Write { 761 | inputs: vec![b"x", b"\n"], 762 | output: Ok(2), 763 | }); 764 | 765 | assert_eq!( 766 | a.write_vectored(&[IoSlice::new(b"x"), IoSlice::new(b"\n")]) 767 | .unwrap(), 768 | 2 769 | ); 770 | 771 | a.get_mut().calls.push_back(Call::Flush { output: Ok(()) }); 772 | a.flush().unwrap(); 773 | 774 | // erroneous writes stop and don't write more 775 | a.get_mut().calls.push_back(Call::Write { 776 | inputs: vec![b"x", b"\na"], 777 | output: Err(err()), 778 | }); 779 | a.get_mut().calls.push_back(Call::Flush { output: Ok(()) }); 780 | assert!(a 781 | .write_vectored(&[IoSlice::new(b"x"), IoSlice::new(b"\na")]) 782 | .is_err()); 783 | a.flush().unwrap(); 784 | 785 | fn err() -> io::Error { 786 | io::Error::new(io::ErrorKind::Other, "x") 787 | } 788 | } 789 | 790 | /// Test that, in cases where vectored writing is not enabled, the 791 | /// BufReaderLineWriter uses the normal `write` call, which more-correctly 792 | /// handles partial lines 793 | #[test] 794 | #[cfg(can_vector)] 795 | fn line_vectored_ignored() { 796 | let writer = ProgrammableSink::default(); 797 | let mut writer = BufReaderLineWriter::new(JustWriter(writer)); 798 | 799 | let content = [ 800 | IoSlice::new(&[]), 801 | IoSlice::new(b"Line 1\nLine"), 802 | IoSlice::new(b" 2\nLine 3\nL"), 803 | IoSlice::new(&[]), 804 | IoSlice::new(&[]), 805 | IoSlice::new(b"ine 4"), 806 | IoSlice::new(b"\nLine 5\n"), 807 | ]; 808 | 809 | let count = writer.write_vectored(&content).unwrap(); 810 | assert_eq!(count, 11); 811 | assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); 812 | 813 | let count = writer.write_vectored(&content[2..]).unwrap(); 814 | assert_eq!(count, 11); 815 | assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n"); 816 | 817 | let count = writer.write_vectored(&content[5..]).unwrap(); 818 | assert_eq!(count, 5); 819 | assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n"); 820 | 821 | let count = writer.write_vectored(&content[6..]).unwrap(); 822 | assert_eq!(count, 8); 823 | assert_eq!( 824 | writer.get_ref().buffer.as_slice(), 825 | b"Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n".as_ref() 826 | ); 827 | } 828 | 829 | /// Test that, given this input: 830 | /// 831 | /// Line 1\n 832 | /// Line 2\n 833 | /// Line 3\n 834 | /// Line 4 835 | /// 836 | /// And given a result that only writes to midway through Line 2 837 | /// 838 | /// That only up to the end of Line 3 is buffered 839 | /// 840 | /// This behavior is desirable because it prevents flushing partial lines 841 | #[test] 842 | fn partial_write_buffers_line() { 843 | let writer = ProgrammableSink { 844 | accept_prefix: Some(13), 845 | ..Default::default() 846 | }; 847 | let mut writer = BufReaderLineWriter::new(JustWriter(writer)); 848 | 849 | assert_eq!(writer.write(b"Line 1\nLine 2\nLine 3\nLine4").unwrap(), 21); 850 | assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2"); 851 | 852 | assert_eq!(writer.write(b"Line 4").unwrap(), 6); 853 | assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n"); 854 | } 855 | 856 | /// Test that, given this input: 857 | /// 858 | /// Line 1\n 859 | /// Line 2\n 860 | /// Line 3 861 | /// 862 | /// And given that the full write of lines 1 and 2 was successful 863 | /// That data up to Line 3 is buffered 864 | #[test] 865 | fn partial_line_buffered_after_line_write() { 866 | let writer = ProgrammableSink::default(); 867 | let mut writer = BufReaderLineWriter::new(JustWriter(writer)); 868 | 869 | assert_eq!(writer.write(b"Line 1\nLine 2\nLine 3").unwrap(), 20); 870 | assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\n"); 871 | 872 | assert!(writer.flush().is_ok()); 873 | assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3"); 874 | } 875 | 876 | /// Test that, given a partial line that exceeds the length of 877 | /// LineBuffer's buffer (that is, without a trailing newline), that that 878 | /// line is written to the inner writer 879 | #[test] 880 | fn long_line_flushed() { 881 | let writer = ProgrammableSink::default(); 882 | let mut writer = BufReaderLineWriter::with_capacities(5, 5, JustWriter(writer)); 883 | 884 | assert_eq!(writer.write(b"0123456789").unwrap(), 10); 885 | assert_eq!(&writer.get_ref().buffer, b"0123456789"); 886 | } 887 | 888 | /// Test that, given a very long partial line *after* successfully 889 | /// flushing a complete line, that that line is buffered unconditionally, 890 | /// and no additional writes take place. This assures the property that 891 | /// `write` should make at-most-one attempt to write new data. 892 | #[test] 893 | fn line_long_tail_not_flushed() { 894 | let writer = ProgrammableSink::default(); 895 | let mut writer = BufReaderLineWriter::with_capacities(5, 5, JustWriter(writer)); 896 | 897 | // Assert that Line 1\n is flushed, and 01234 is buffered 898 | assert_eq!(writer.write(b"Line 1\n0123456789").unwrap(), 12); 899 | assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); 900 | 901 | // Because the buffer is full, this subsequent write will flush it 902 | assert_eq!(writer.write(b"5").unwrap(), 1); 903 | assert_eq!(&writer.get_ref().buffer, b"Line 1\n01234"); 904 | } 905 | 906 | /// Test that, if an attempt to pre-flush buffered data returns Ok(0), 907 | /// this is propagated as an error. 908 | #[test] 909 | fn line_buffer_write0_error() { 910 | let writer = ProgrammableSink { 911 | // Accept one write, then return Ok(0) on subsequent ones 912 | max_writes: Some(1), 913 | 914 | ..Default::default() 915 | }; 916 | let mut writer = BufReaderLineWriter::new(JustWriter(writer)); 917 | 918 | // This should write "Line 1\n" and buffer "Partial" 919 | assert_eq!(writer.write(b"Line 1\nPartial").unwrap(), 14); 920 | assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); 921 | 922 | // This will attempt to flush "partial", which will return Ok(0), which 923 | // needs to be an error, because we've already informed the client 924 | // that we accepted the write. 925 | let err = writer.write(b" Line End\n").unwrap_err(); 926 | assert_eq!(err.kind(), ErrorKind::WriteZero); 927 | assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); 928 | } 929 | 930 | /// Test that, if a write returns Ok(0) after a successful pre-flush, this 931 | /// is propagated as Ok(0) 932 | #[test] 933 | fn line_buffer_write0_normal() { 934 | let writer = ProgrammableSink { 935 | // Accept two writes, then return Ok(0) on subsequent ones 936 | max_writes: Some(2), 937 | 938 | ..Default::default() 939 | }; 940 | let mut writer = BufReaderLineWriter::new(JustWriter(writer)); 941 | 942 | // This should write "Line 1\n" and buffer "Partial" 943 | assert_eq!(writer.write(b"Line 1\nPartial").unwrap(), 14); 944 | assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); 945 | 946 | // This will flush partial, which will succeed, but then return Ok(0) 947 | // when flushing " Line End\n" 948 | assert_eq!(writer.write(b" Line End\n").unwrap(), 0); 949 | assert_eq!(&writer.get_ref().buffer, b"Line 1\nPartial"); 950 | } 951 | 952 | /// BufReaderLineWriter has a custom `write_all`; make sure it works correctly 953 | #[test] 954 | fn line_write_all() { 955 | let writer = ProgrammableSink { 956 | // Only write 5 bytes at a time 957 | accept_prefix: Some(5), 958 | ..Default::default() 959 | }; 960 | let mut writer = BufReaderLineWriter::new(JustWriter(writer)); 961 | 962 | writer 963 | .write_all(b"Line 1\nLine 2\nLine 3\nLine 4\nPartial") 964 | .unwrap(); 965 | assert_eq!( 966 | &writer.get_ref().buffer, 967 | b"Line 1\nLine 2\nLine 3\nLine 4\n" 968 | ); 969 | writer.write_all(b" Line 5\n").unwrap(); 970 | assert_eq!( 971 | writer.get_ref().buffer.as_slice(), 972 | b"Line 1\nLine 2\nLine 3\nLine 4\nPartial Line 5\n".as_ref(), 973 | ); 974 | } 975 | 976 | #[test] 977 | fn line_write_all_error() { 978 | let writer = ProgrammableSink { 979 | // Only accept up to 3 writes of up to 5 bytes each 980 | accept_prefix: Some(5), 981 | max_writes: Some(3), 982 | ..Default::default() 983 | }; 984 | 985 | let mut writer = BufReaderLineWriter::new(JustWriter(writer)); 986 | let res = writer.write_all(b"Line 1\nLine 2\nLine 3\nLine 4\nPartial"); 987 | assert!(res.is_err()); 988 | // An error from write_all leaves everything in an indeterminate state, 989 | // so there's nothing else to test here 990 | } 991 | 992 | /// Under certain circumstances, the old implementation of BufReaderLineWriter 993 | /// would try to buffer "to the last newline" but be forced to buffer 994 | /// less than that, leading to inappropriate partial line writes. 995 | /// Regression test for that issue. 996 | #[test] 997 | fn partial_multiline_buffering() { 998 | let writer = ProgrammableSink { 999 | // Write only up to 5 bytes at a time 1000 | accept_prefix: Some(5), 1001 | ..Default::default() 1002 | }; 1003 | 1004 | let mut writer = BufReaderLineWriter::with_capacities(10, 10, JustWriter(writer)); 1005 | 1006 | let content = b"AAAAABBBBB\nCCCCDDDDDD\nEEE"; 1007 | 1008 | // When content is written, BufReaderLineWriter will try to write blocks A, B, 1009 | // C, and D. Only block A will succeed. Under the old behavior, 1010 | // BufReaderLineWriter would then try to buffer B, C and D, but because its 1011 | // capacity is 10, it will only be able to buffer B and C. We don't want to 1012 | // buffer partial lines concurrent with whole lines, so the correct 1013 | // behavior is to buffer only block B (out to the newline) 1014 | assert_eq!(writer.write(content).unwrap(), 11); 1015 | assert_eq!(writer.get_ref().buffer, *b"AAAAA"); 1016 | 1017 | writer.flush().unwrap(); 1018 | assert_eq!(writer.get_ref().buffer, *b"AAAAABBBBB\n"); 1019 | } 1020 | 1021 | /// Same as test_partial_multiline_buffering, but in the event NO full lines 1022 | /// fit in the buffer, just buffer as much as possible 1023 | #[test] 1024 | fn partial_multiline_buffering_without_full_line() { 1025 | let writer = ProgrammableSink { 1026 | // Write only up to 5 bytes at a time 1027 | accept_prefix: Some(5), 1028 | ..Default::default() 1029 | }; 1030 | 1031 | let mut writer = BufReaderLineWriter::with_capacities(5, 5, JustWriter(writer)); 1032 | 1033 | let content = b"AAAAABBBBBBBBBB\nCCCCC\nDDDDD"; 1034 | 1035 | // When content is written, BufReaderLineWriter will try to write blocks A, B, 1036 | // and C. Only block A will succeed. Under the old behavior, 1037 | // BufReaderLineWriter would then try to buffer B and C, but because its 1038 | // capacity is 5, it will only be able to buffer part of B. Because it's 1039 | // not possible for it to buffer any complete lines, it should buffer as 1040 | // much of B as possible 1041 | assert_eq!(writer.write(content).unwrap(), 10); 1042 | assert_eq!(writer.get_ref().buffer, *b"AAAAA"); 1043 | 1044 | writer.flush().unwrap(); 1045 | assert_eq!(writer.get_ref().buffer, *b"AAAAABBBBB"); 1046 | } 1047 | 1048 | #[derive(Debug, Clone, PartialEq, Eq)] 1049 | enum RecordedEvent { 1050 | Write(String), 1051 | Flush, 1052 | } 1053 | 1054 | #[derive(Debug, Clone, Default)] 1055 | struct WriteRecorder { 1056 | pub events: Vec, 1057 | } 1058 | 1059 | impl Read for WriteRecorder { 1060 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 1061 | panic!("WriteRecorder doesn't support reading") 1062 | } 1063 | } 1064 | 1065 | impl Write for WriteRecorder { 1066 | fn write(&mut self, buf: &[u8]) -> io::Result { 1067 | use std::str::from_utf8; 1068 | 1069 | self.events 1070 | .push(RecordedEvent::Write(from_utf8(buf).unwrap().to_string())); 1071 | Ok(buf.len()) 1072 | } 1073 | 1074 | fn flush(&mut self) -> io::Result<()> { 1075 | self.events.push(RecordedEvent::Flush); 1076 | Ok(()) 1077 | } 1078 | } 1079 | 1080 | /// Test that a normal, formatted writeln only results in a single write 1081 | /// call to the underlying writer. A naive implementation of 1082 | /// BufReaderLineWriter::write_all results in two writes: one of the buffered 1083 | /// data, and another of the final substring in the formatted set 1084 | #[test] 1085 | fn single_formatted_write() { 1086 | let writer = WriteRecorder::default(); 1087 | let mut writer = BufReaderLineWriter::new(JustWriter(writer)); 1088 | 1089 | // Under a naive implementation of BufReaderLineWriter, this will result in two 1090 | // writes: "hello, world" and "!\n", because write() has to flush the 1091 | // buffer before attempting to write the last "!\n". write_all shouldn't 1092 | // have this limitation. 1093 | writeln!(&mut writer, "{}, {}!", "hello", "world").unwrap(); 1094 | assert_eq!( 1095 | writer.get_ref().events, 1096 | [RecordedEvent::Write("hello, world!\n".to_string())] 1097 | ); 1098 | } 1099 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(can_vector, feature(can_vector))] 2 | #![cfg_attr(write_all_vectored, feature(write_all_vectored))] 3 | 4 | use cap_tempfile::{ambient_authority, tempdir, TempDir}; 5 | #[cfg(not(target_os = "wasi"))] 6 | use io_streams::StreamDuplexer; 7 | use io_streams::{StreamReader, StreamWriter}; 8 | use std::io::{copy, Read, Write}; 9 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 10 | use {socketpair::SocketpairStream, std::str}; 11 | 12 | fn tmpdir() -> TempDir { 13 | tempdir(ambient_authority()).expect("expected to be able to create a temporary directory") 14 | } 15 | 16 | #[test] 17 | fn test_copy() -> anyhow::Result<()> { 18 | let dir = tmpdir(); 19 | let in_txt = "in.txt"; 20 | let out_txt = "out.txt"; 21 | 22 | let mut in_file = dir.create(in_txt)?; 23 | write!(in_file, "Hello, world!")?; 24 | 25 | // Test regular file I/O. 26 | { 27 | let mut input = StreamReader::file(dir.open(in_txt)?); 28 | let mut output = StreamWriter::file(dir.create(out_txt)?); 29 | copy(&mut input, &mut output)?; 30 | output.flush()?; 31 | let mut s = String::new(); 32 | dir.open(out_txt)?.read_to_string(&mut s)?; 33 | assert_eq!(s, "Hello, world!"); 34 | dir.remove_file(out_txt)?; 35 | } 36 | 37 | // Test I/O through piped threads. 38 | #[cfg(not(target_os = "wasi"))] // WASI doesn't support pipes yet 39 | { 40 | let mut input = StreamReader::piped_thread(Box::new(dir.open(in_txt)?))?; 41 | let mut output = StreamWriter::piped_thread(Box::new(dir.create(out_txt)?))?; 42 | copy(&mut input, &mut output)?; 43 | output.flush()?; 44 | let mut s = String::new(); 45 | dir.open(out_txt)?.read_to_string(&mut s)?; 46 | assert_eq!(s, "Hello, world!"); 47 | dir.remove_file(out_txt)?; 48 | } 49 | 50 | // Test regular file I/O through piped threads, not because this is 51 | // amazingly useful, but because these things should compose and we can. 52 | // This also tests that `StreamReader` and `StreamWriter` 53 | // implement `Send`. 54 | #[cfg(not(target_os = "wasi"))] // WASI doesn't support pipes yet 55 | { 56 | let mut input = 57 | StreamReader::piped_thread(Box::new(StreamReader::file(dir.open(in_txt)?)))?; 58 | let mut output = 59 | StreamWriter::piped_thread(Box::new(StreamWriter::file(dir.create(out_txt)?)))?; 60 | copy(&mut input, &mut output)?; 61 | output.flush()?; 62 | let mut s = String::new(); 63 | dir.open(out_txt)?.read_to_string(&mut s)?; 64 | assert_eq!(s, "Hello, world!"); 65 | dir.remove_file(out_txt)?; 66 | } 67 | 68 | // They compose with themselves too. 69 | #[cfg(not(target_os = "wasi"))] // WASI doesn't support pipes yet 70 | { 71 | let mut input = StreamReader::piped_thread(Box::new(StreamReader::piped_thread( 72 | Box::new(StreamReader::file(dir.open(in_txt)?)), 73 | )?))?; 74 | let mut output = StreamWriter::piped_thread(Box::new(StreamWriter::piped_thread( 75 | Box::new(StreamWriter::file(dir.create(out_txt)?)), 76 | )?))?; 77 | copy(&mut input, &mut output)?; 78 | output.flush()?; 79 | let mut s = String::new(); 80 | dir.open(out_txt)?.read_to_string(&mut s)?; 81 | assert_eq!(s, "Hello, world!"); 82 | dir.remove_file(out_txt)?; 83 | } 84 | 85 | // Test flushing between writes. 86 | #[cfg(not(target_os = "wasi"))] // WASI doesn't support pipes yet 87 | { 88 | let mut input = StreamReader::piped_thread(Box::new(StreamReader::piped_thread( 89 | Box::new(StreamReader::file(dir.open(in_txt)?)), 90 | )?))?; 91 | let mut output = StreamWriter::piped_thread(Box::new(StreamWriter::piped_thread( 92 | Box::new(StreamWriter::file(dir.create(out_txt)?)), 93 | )?))?; 94 | copy(&mut input, &mut output)?; 95 | output.flush()?; 96 | let mut s = String::new(); 97 | dir.open(out_txt)?.read_to_string(&mut s)?; 98 | assert_eq!(s, "Hello, world!"); 99 | input = StreamReader::piped_thread(Box::new(StreamReader::piped_thread(Box::new( 100 | StreamReader::file(dir.open(in_txt)?), 101 | ))?))?; 102 | copy(&mut input, &mut output)?; 103 | output.flush()?; 104 | s = String::new(); 105 | dir.open(out_txt)?.read_to_string(&mut s)?; 106 | assert_eq!(s, "Hello, world!Hello, world!"); 107 | input = StreamReader::piped_thread(Box::new(StreamReader::piped_thread(Box::new( 108 | StreamReader::file(dir.open(in_txt)?), 109 | ))?))?; 110 | copy(&mut input, &mut output)?; 111 | output.flush()?; 112 | s = String::new(); 113 | dir.open(out_txt)?.read_to_string(&mut s)?; 114 | assert_eq!(s, "Hello, world!Hello, world!Hello, world!"); 115 | dir.remove_file(out_txt)?; 116 | } 117 | 118 | Ok(()) 119 | } 120 | 121 | #[test] 122 | #[cfg(not(target_os = "wasi"))] // WASI doesn't support pipes yet 123 | fn test_null_output() -> anyhow::Result<()> { 124 | let mut input = StreamReader::str("send to null")?; 125 | let mut output = StreamWriter::null()?; 126 | copy(&mut input, &mut output)?; 127 | output.flush()?; 128 | Ok(()) 129 | } 130 | 131 | #[test] 132 | #[cfg(not(target_os = "wasi"))] // WASI doesn't support pipes yet 133 | fn test_null_input() -> anyhow::Result<()> { 134 | let mut input = StreamReader::null()?; 135 | let mut s = String::new(); 136 | input.read_to_string(&mut s)?; 137 | assert!(s.is_empty()); 138 | Ok(()) 139 | } 140 | 141 | #[test] 142 | #[cfg(not(target_os = "wasi"))] // WASI doesn't support pipes yet 143 | fn test_null_duplex() -> anyhow::Result<()> { 144 | let mut duplex = StreamDuplexer::null()?; 145 | let mut s = String::new(); 146 | duplex.read_to_string(&mut s)?; 147 | assert!(s.is_empty()); 148 | let mut input = StreamReader::str("send to null")?; 149 | copy(&mut input, &mut duplex)?; 150 | duplex.flush()?; 151 | Ok(()) 152 | } 153 | 154 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 155 | #[test] 156 | fn test_socketed_thread_func() -> anyhow::Result<()> { 157 | let mut thread = StreamDuplexer::socketed_thread_func(Box::new( 158 | |mut stream: SocketpairStream| -> std::io::Result { 159 | let mut buf = [0_u8; 4096]; 160 | let n = stream.read(&mut buf)?; 161 | assert_eq!(str::from_utf8(&buf[..n]).unwrap(), "hello world\n"); 162 | 163 | writeln!(stream, "greetings")?; 164 | stream.flush()?; 165 | 166 | let n = stream.read(&mut buf)?; 167 | assert_eq!(str::from_utf8(&buf[..n]).unwrap(), "goodbye\n"); 168 | Ok(stream) 169 | }, 170 | ))?; 171 | 172 | writeln!(thread, "hello world")?; 173 | thread.flush()?; 174 | 175 | let mut buf = [0_u8; 4096]; 176 | let n = thread.read(&mut buf)?; 177 | assert_eq!(str::from_utf8(&buf[..n]).unwrap(), "greetings\n"); 178 | 179 | writeln!(thread, "goodbye")?; 180 | thread.flush()?; 181 | 182 | Ok(()) 183 | } 184 | 185 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 186 | struct Mock(bool); 187 | 188 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 189 | impl Read for Mock { 190 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 191 | assert!(!self.0); 192 | self.0 = true; 193 | assert!(!buf.is_empty()); 194 | let len = buf.len() - 1; 195 | buf[..len].copy_from_slice(&vec![0xab_u8; len]); 196 | Ok(len) 197 | } 198 | } 199 | 200 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 201 | impl Write for Mock { 202 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 203 | assert!(self.0); 204 | self.0 = false; 205 | assert_eq!(buf, &vec![0xcd_u8; buf.len()][..]); 206 | Ok(buf.len()) 207 | } 208 | 209 | fn flush(&mut self) -> std::io::Result<()> { 210 | Ok(()) 211 | } 212 | } 213 | 214 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 215 | impl system_interface::io::ReadReady for Mock { 216 | fn num_ready_bytes(&self) -> std::io::Result { 217 | if self.0 { 218 | Ok(0) 219 | } else { 220 | Ok(1) 221 | } 222 | } 223 | } 224 | 225 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 226 | impl duplex::Duplex for Mock {} 227 | 228 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 229 | #[test] 230 | fn test_socketed_thread_read_first() -> anyhow::Result<()> { 231 | let mut thread = StreamDuplexer::socketed_thread_read_first(Box::new(Mock(false)))?; 232 | 233 | let mut buf = [0_u8; 4]; 234 | 235 | let n = thread.read(&mut buf)?; 236 | assert_eq!(n, 4); 237 | assert_eq!(&buf, &[0xab_u8; 4]); 238 | 239 | let n = thread.write(b"\xcd\xcd\xcd\xcd")?; 240 | assert_eq!(n, 4); 241 | thread.flush()?; 242 | 243 | let n = thread.read(&mut buf)?; 244 | assert_eq!(n, 4); 245 | assert_eq!(&buf, &[0xab_u8; 4]); 246 | 247 | let n = thread.write(b"\xcd\xcd\xcd\xcd")?; 248 | assert_eq!(n, 4); 249 | thread.flush()?; 250 | 251 | Ok(()) 252 | } 253 | 254 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 255 | #[test] 256 | fn test_socketed_thread_write_first() -> anyhow::Result<()> { 257 | let mut thread = StreamDuplexer::socketed_thread_write_first(Box::new(Mock(true)))?; 258 | let mut buf = [0_u8; 4]; 259 | 260 | let n = thread.write(b"\xcd\xcd\xcd\xcd")?; 261 | assert_eq!(n, 4); 262 | thread.flush()?; 263 | 264 | let n = thread.read(&mut buf)?; 265 | assert_eq!(n, 4); 266 | assert_eq!(&buf, &[0xab_u8; 4]); 267 | 268 | let n = thread.write(b"\xcd\xcd\xcd\xcd")?; 269 | assert_eq!(n, 4); 270 | thread.flush()?; 271 | 272 | let n = thread.read(&mut buf)?; 273 | assert_eq!(n, 4); 274 | assert_eq!(&buf, &[0xab_u8; 4]); 275 | 276 | Ok(()) 277 | } 278 | 279 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 280 | #[test] 281 | fn test_socketed_thread_auto_read_first() -> anyhow::Result<()> { 282 | let mut thread = StreamDuplexer::socketed_thread(Box::new(Mock(false)))?; 283 | 284 | let mut buf = [0_u8; 4]; 285 | 286 | let n = thread.read(&mut buf)?; 287 | assert_eq!(n, 4); 288 | assert_eq!(&buf, &[0xab_u8; 4]); 289 | 290 | let n = thread.write(b"\xcd\xcd\xcd\xcd")?; 291 | assert_eq!(n, 4); 292 | thread.flush()?; 293 | 294 | let n = thread.read(&mut buf)?; 295 | assert_eq!(n, 4); 296 | assert_eq!(&buf, &[0xab_u8; 4]); 297 | 298 | let n = thread.write(b"\xcd\xcd\xcd")?; 299 | assert_eq!(n, 3); 300 | thread.flush()?; 301 | 302 | Ok(()) 303 | } 304 | 305 | #[cfg(all(not(target_os = "wasi"), feature = "socketpair"))] 306 | #[test] 307 | fn test_socketed_thread_auto_write_first() -> anyhow::Result<()> { 308 | let mut thread = StreamDuplexer::socketed_thread(Box::new(Mock(true)))?; 309 | let mut buf = [0_u8; 4]; 310 | 311 | let n = thread.write(b"\xcd\xcd\xcd\xcd")?; 312 | assert_eq!(n, 4); 313 | thread.flush()?; 314 | 315 | let n = thread.read(&mut buf)?; 316 | assert_eq!(n, 4); 317 | assert_eq!(&buf, &[0xab_u8; 4]); 318 | 319 | let n = thread.write(b"\xcd\xcd\xcd")?; 320 | assert_eq!(n, 3); 321 | thread.flush()?; 322 | 323 | let n = thread.read(&mut buf)?; 324 | assert_eq!(n, 4); 325 | assert_eq!(&buf, &[0xab_u8; 4]); 326 | 327 | Ok(()) 328 | } 329 | 330 | #[test] 331 | fn test_stdio() { 332 | // Test that we can at least construct the stdio streams. 333 | let _ = StreamReader::stdin(); 334 | let _ = StreamWriter::stdout(); 335 | let _ = StreamWriter::stderr(); 336 | } 337 | --------------------------------------------------------------------------------