mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-12-22 13:45:20 +00:00
vendor: Move to the rust-vmm vmm-sys-util package
Locked to 60fe35be but no longer dependent on liujing2 repo. Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
parent
d5f5648b37
commit
a6b7715f4b
@ -6,11 +6,6 @@ git = "https://github.com/bjzhjing/linux-loader"
|
||||
rev = "2b95f1e1958a2b6399b590f64344cab5b4a6d608"
|
||||
replace-with = "vendor+https://github.com/bjzhjing/linux-loader"
|
||||
|
||||
[source."https://github.com/liujing2/vmm-sys-util"]
|
||||
git = "https://github.com/liujing2/vmm-sys-util"
|
||||
branch = "master"
|
||||
replace-with = "vendor+https://github.com/liujing2/vmm-sys-util"
|
||||
|
||||
[source."https://github.com/rust-vmm/vm-memory"]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
rev = "281b8bd6cd2927f7a65130194b203a1c2b0ad2e3"
|
||||
@ -27,9 +22,6 @@ directory = "./vendor/registry-40351f815f426200"
|
||||
[source."vendor+https://github.com/bjzhjing/linux-loader"]
|
||||
directory = "./vendor/git-4af8f4552cd0d200"
|
||||
|
||||
[source."vendor+https://github.com/liujing2/vmm-sys-util"]
|
||||
directory = "./vendor/git-ccfa5f04f3f14300"
|
||||
|
||||
[source."vendor+https://github.com/rust-vmm/vm-memory"]
|
||||
directory = "./vendor/git-89548d8276566400"
|
||||
|
||||
|
18
Cargo.lock
generated
18
Cargo.lock
generated
@ -97,7 +97,7 @@ dependencies = [
|
||||
"epoll 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/liujing2/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -161,7 +161,7 @@ dependencies = [
|
||||
name = "net_gen"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/liujing2/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -172,7 +172,7 @@ dependencies = [
|
||||
"net_gen 0.1.0",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/liujing2/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -192,7 +192,7 @@ dependencies = [
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vm-allocator 0.1.0",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/liujing2/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -211,7 +211,7 @@ dependencies = [
|
||||
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remain 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/liujing2/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -468,7 +468,7 @@ dependencies = [
|
||||
"virtio-bindings 0.1.0",
|
||||
"vm-allocator 0.1.0",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/liujing2/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -489,13 +489,13 @@ dependencies = [
|
||||
"vm-allocator 0.1.0",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vm-virtio 0.1.0",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/liujing2/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vmm-sys-util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/liujing2/vmm-sys-util#700a05b942cf904fca9cf1ac3e825e58c9dc9ea9"
|
||||
source = "git+https://github.com/rust-vmm/vmm-sys-util#60fe35bea0bdce8b36c6186a740878880f944bdc"
|
||||
dependencies = [
|
||||
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@ -574,7 +574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)" = "<none>"
|
||||
"checksum vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory?rev=281b8bd6cd2927f7a65130194b203a1c2b0ad2e3)" = "<none>"
|
||||
"checksum vmm-sys-util 0.1.0 (git+https://github.com/liujing2/vmm-sys-util)" = "<none>"
|
||||
"checksum vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)" = "<none>"
|
||||
"checksum vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util?rev=60fe35bea0bdce8b36c6186a740878880f944bdc)" = "<none>"
|
||||
"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
@ -8,7 +8,7 @@ byteorder = ">=1.2.1"
|
||||
epoll = "=4.0.1"
|
||||
libc = ">=0.2.39"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = { git = "https://github.com/liujing2/vmm-sys-util" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = ">=3.0.2"
|
||||
|
@ -4,4 +4,4 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
vmm-sys-util = { git = "https://github.com/liujing2/vmm-sys-util" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
@ -9,7 +9,7 @@ rand = ">=0.6.5"
|
||||
serde = ">=1.0.27"
|
||||
|
||||
net_gen = { path = "../net_gen" }
|
||||
vmm-sys-util = { git = "https://github.com/liujing2/vmm-sys-util" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = ">=1.1.0"
|
||||
|
@ -13,4 +13,4 @@ kvm-ioctls = "0.1.0"
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = { git = "https://github.com/liujing2/vmm-sys-util" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
@ -13,4 +13,4 @@ byteorder = "*"
|
||||
libc = "*"
|
||||
log = "*"
|
||||
remain = "*"
|
||||
vmm-sys-util = { git = "https://github.com/liujing2/vmm-sys-util" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
2
vendor/.sources
vendored
2
vendor/.sources
vendored
@ -1 +1 @@
|
||||
["git-3e7c44ea7d5fd800","git-4af8f4552cd0d200","git-89548d8276566400","git-bad78e1967b13e00","git-bda1448fb2afcb00","git-ccfa5f04f3f14300","registry-40351f815f426200"]
|
||||
["git-3e7c44ea7d5fd800","git-4af8f4552cd0d200","git-643dc7ede01ca600","git-89548d8276566400","git-bad78e1967b13e00","git-bda1448fb2afcb00","registry-40351f815f426200"]
|
@ -1,75 +0,0 @@
|
||||
steps:
|
||||
- label: "build-gnu-x86"
|
||||
commands:
|
||||
- cargo build --release
|
||||
retry:
|
||||
automatic: false
|
||||
agents:
|
||||
platform: x86_64.metal
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "fandree/rust-vmm-dev"
|
||||
always-pull: true
|
||||
|
||||
- label: "build-gnu-arm"
|
||||
commands:
|
||||
- cargo build --release
|
||||
retry:
|
||||
automatic: false
|
||||
agents:
|
||||
platform: arm.metal
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "fandree/rust-vmm-dev"
|
||||
always-pull: true
|
||||
|
||||
- label: "style"
|
||||
command: cargo fmt --all -- --check
|
||||
retry:
|
||||
automatic: false
|
||||
agents:
|
||||
platform: x86_64.metal
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "fandree/rust-vmm-dev"
|
||||
always-pull: true
|
||||
|
||||
- label: "unittests-gnu-x86"
|
||||
commands:
|
||||
- cargo test
|
||||
retry:
|
||||
automatic: false
|
||||
agents:
|
||||
platform: x86_64.metal
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
privileged: true
|
||||
image: "fandree/rust-vmm-dev"
|
||||
always-pull: true
|
||||
tmpfs: [ "/tmp:exec" ]
|
||||
|
||||
- label: "unittests-gnu-arm"
|
||||
commands:
|
||||
- cargo test
|
||||
retry:
|
||||
automatic: false
|
||||
agents:
|
||||
platform: arm.metal
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
privileged: true
|
||||
image: "fandree/rust-vmm-dev"
|
||||
always-pull: true
|
||||
tmpfs: [ "/tmp:exec" ]
|
||||
|
||||
- label: "clippy-x86"
|
||||
commands:
|
||||
- cargo clippy --all -- -D warnings
|
||||
retry:
|
||||
automatic: false
|
||||
agents:
|
||||
platform: x86_64.metal
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "fandree/rust-vmm-dev"
|
||||
always-pull: true
|
@ -1 +0,0 @@
|
||||
{"files":{".buildkite/pipeline.yml":"0194df2d3534e3c6938ba2945e05696a12b8ed63d4941b3a4173c5bcf6e587c4","Cargo.toml":"d19b52349ada25dbbae4e2bea4850cf1b09ea546d5efee03ddfbbf9f184c0c72","LICENSE-APACHE":"c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4","LICENSE-BSD-3-Clause":"a6d3ebd1c2f37d4fd83d0676621f695fc0cc2d8c6e646cdbb831b46e0650c208","README.md":"94512ed84e8e89faba431d9107e85d229363c187705ab9244f0ab5269b5961b5","src/errno.rs":"b93845c200fc596b0ef2414671d965cf5c4fdbf6bba6e5e5f0d032a32c73f8ac","src/eventfd.rs":"7bd871242f49c14d8783714e6814456f51a9c280dcadf1625e1bd2313d2b5f7f","src/file_traits.rs":"398c529e7ebce143ecb9f9bd2f5f47ea3e953ac34cc211ad71cdcf1898cc7d38","src/ioctl.rs":"5c4abf75e7b6786e7da3191ac1e4460e1ec7d073a53331a6d9597bb9ccc3f88a","src/lib.rs":"ee0818e0ca6fdc340c52d514eeb2e3aeb4f7ba8e4e522bb946cdbce4779926f1","src/poll.rs":"1498c14ba022ede57c4faf17bee49cf5ac9d1c8d3883db441697ee224dac7818","src/seek_hole.rs":"de43f21bc2c5d9eb7f06e21e3c20f93476bf6016e4d041df71a02b9e54b3c3ca","src/signal.rs":"724f679cb62d268a5ec8f0704a8d6b01882f302f508c189e82054657ed8b31bf","src/syslog.rs":"fbf4bde16b1059b5b39c5318e8bb918dc431e8e0ccbc82c0d765b9ce4a8d5f96","src/tempdir.rs":"4993460e81f7df6398e0f2b07cc3d81e728aa7e0559c7f3d83b6df1876bc3776","src/terminal.rs":"85efb1df641730fa1981bac6fd65bd75f7d532bb8680a56e94d6d006eeb363e9","src/timerfd.rs":"fd3c52e3918d881c16cb1498f8f66253ee758275a6a66ed8eb11c78e69f69e55","src/write_zeroes.rs":"c2951bbdb3ab07727eda29e9a91a51e427fdf6fed0b611ea6a3732edbd9a1246"},"package":null}
|
@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "vmm-sys-util"
|
||||
version = "0.1.0"
|
||||
authors = ["Jing Liu <jing2.liu@linux.intel.com>"]
|
||||
license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
libc = ">=0.2.39"
|
||||
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,27 +0,0 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,2 +0,0 @@
|
||||
# vmm-sys-util
|
||||
This crate is a collection of modules that provides helpers and utilities.
|
@ -1,80 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
use std::result;
|
||||
|
||||
use libc::__errno_location;
|
||||
|
||||
/// An error number, retrieved from [`errno`](http://man7.org/linux/man-pages/man3/errno.3.html),
|
||||
/// set by a libc function that returned an error.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Error(i32);
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
impl Error {
|
||||
/// Constructs a new error with the given `errno`.
|
||||
pub fn new(e: i32) -> Error {
|
||||
Error(e)
|
||||
}
|
||||
|
||||
/// Constructs an error from the current `errno`.
|
||||
///
|
||||
/// The result of this only has any meaning just after a libc call that returned a value
|
||||
/// indicating `errno` was set.
|
||||
pub fn last() -> Error {
|
||||
Error(unsafe { *__errno_location() })
|
||||
}
|
||||
|
||||
/// Gets the `errno` for this error.
|
||||
pub fn errno(self) -> i32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
io::Error::from_raw_os_error(self.0).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Error::new(e.raw_os_error().unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the last `errno` as a [`Result`] that is always an error.
|
||||
///
|
||||
/// [`Result`]: type.Result.html
|
||||
pub fn errno_result<T>() -> Result<T> {
|
||||
Err(Error::last())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libc;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
#[test]
|
||||
pub fn test_invalid_fd() {
|
||||
let mut file = unsafe { File::from_raw_fd(-1) };
|
||||
assert!(file.write(b"test").is_err());
|
||||
let last_err = errno_result::<i32>().unwrap_err();
|
||||
assert_eq!(last_err, Error::new(libc::EBADF));
|
||||
assert_eq!(last_err.errno(), libc::EBADF);
|
||||
assert_eq!(last_err, Error::from(io::Error::last_os_error()));
|
||||
assert_eq!(last_err, Error::last());
|
||||
}
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::{io, mem, result};
|
||||
|
||||
use libc::{c_void, dup, eventfd, read, write};
|
||||
|
||||
/// A safe wrapper around a Linux eventfd (man 2 eventfd).
|
||||
pub struct EventFd {
|
||||
eventfd: File,
|
||||
}
|
||||
|
||||
impl EventFd {
|
||||
/// Creates a new blocking EventFd with an initial value.
|
||||
///
|
||||
/// `flag`: The initial value. Refer to Linux eventfd(2).
|
||||
pub fn new(flag: i32) -> result::Result<EventFd, io::Error> {
|
||||
// This is safe because eventfd merely allocated an eventfd for
|
||||
// our process and we handle the error case.
|
||||
let ret = unsafe { eventfd(0, flag) };
|
||||
if ret < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
// This is safe because we checked ret for success and know
|
||||
// the kernel gave us an fd that we own.
|
||||
Ok(EventFd {
|
||||
eventfd: unsafe { File::from_raw_fd(ret) },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds `v` to the eventfd's count, does not block if the result will overflow the count
|
||||
pub fn write(&self, v: u64) -> result::Result<(), io::Error> {
|
||||
// This is safe because we made this fd and the pointer we pass
|
||||
// can not overflow because we give the syscall's size parameter properly.
|
||||
let ret = unsafe {
|
||||
write(
|
||||
self.as_raw_fd(),
|
||||
&v as *const u64 as *const c_void,
|
||||
mem::size_of::<u64>(),
|
||||
)
|
||||
};
|
||||
if ret <= 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to read from the eventfd, does not block if the counter is zero
|
||||
pub fn read(&self) -> result::Result<u64, io::Error> {
|
||||
let mut buf: u64 = 0;
|
||||
let ret = unsafe {
|
||||
// This is safe because we made this fd and the pointer we
|
||||
// pass can not overflow because we give the syscall's size parameter properly.
|
||||
read(
|
||||
self.as_raw_fd(),
|
||||
&mut buf as *mut u64 as *mut c_void,
|
||||
mem::size_of::<u64>(),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clones this EventFd, internally creating a new file descriptor. The new EventFd will share
|
||||
/// the same underlying count within the kernel.
|
||||
pub fn try_clone(&self) -> result::Result<EventFd, io::Error> {
|
||||
// This is safe because we made this fd and properly check that it returns without error.
|
||||
let ret = unsafe { dup(self.as_raw_fd()) };
|
||||
if ret < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
// This is safe because we checked ret for success and know the kernel gave us an fd that we
|
||||
// own.
|
||||
Ok(EventFd {
|
||||
eventfd: unsafe { File::from_raw_fd(ret) },
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for EventFd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.eventfd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRawFd for EventFd {
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||
EventFd {
|
||||
eventfd: File::from_raw_fd(fd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libc::EFD_NONBLOCK;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
EventFd::new(0).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_write() {
|
||||
let evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
evt.write(55).unwrap();
|
||||
assert_eq!(evt.read().unwrap(), 55);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_overflow() {
|
||||
let evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
evt.write(std::u64::MAX - 1).unwrap();
|
||||
let r = evt.write(1);
|
||||
match r {
|
||||
Err(ref inner) if inner.kind() == io::ErrorKind::WouldBlock => (),
|
||||
_ => panic!("Unexpected"),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_read_nothing() {
|
||||
let evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
let r = evt.read();
|
||||
match r {
|
||||
Err(ref inner) if inner.kind() == io::ErrorKind::WouldBlock => (),
|
||||
_ => panic!("Unexpected"),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_clone() {
|
||||
let evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
let evt_clone = evt.try_clone().unwrap();
|
||||
evt.write(923).unwrap();
|
||||
assert_eq!(evt_clone.read().unwrap(), 923);
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Result;
|
||||
|
||||
/// A trait for flushing the contents of a file to disk.
|
||||
/// This is equivalent to File's `sync_all` method, but
|
||||
/// wrapped in a trait so that it can be implemented for
|
||||
/// other types.
|
||||
pub trait FileSync {
|
||||
// Flush buffers related to this file to disk.
|
||||
fn fsync(&mut self) -> Result<()>;
|
||||
}
|
||||
|
||||
impl FileSync for File {
|
||||
fn fsync(&mut self) -> Result<()> {
|
||||
self.sync_all()
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for setting the size of a file.
|
||||
/// This is equivalent to File's `set_len` method, but
|
||||
/// wrapped in a trait so that it can be implemented for
|
||||
/// other types.
|
||||
pub trait FileSetLen {
|
||||
// Set the size of this file.
|
||||
// This is the moral equivalent of `ftruncate()`.
|
||||
fn set_len(&self, _len: u64) -> Result<()>;
|
||||
}
|
||||
|
||||
impl FileSetLen for File {
|
||||
fn set_len(&self, len: u64) -> Result<()> {
|
||||
File::set_len(self, len)
|
||||
}
|
||||
}
|
@ -1,225 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
//! Macros and wrapper functions for dealing with ioctls.
|
||||
use libc;
|
||||
use std::os::raw::{c_int, c_uint, c_ulong, c_void};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
/// Raw macro to declare the expression that calculates an ioctl number
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_expr {
|
||||
($dir:expr, $ty:expr, $nr:expr, $size:expr) => {
|
||||
(($dir << $crate::ioctl::_IOC_DIRSHIFT)
|
||||
| ($ty << $crate::ioctl::_IOC_TYPESHIFT)
|
||||
| ($nr << $crate::ioctl::_IOC_NRSHIFT)
|
||||
| ($size << $crate::ioctl::_IOC_SIZESHIFT)) as ::std::os::raw::c_ulong
|
||||
};
|
||||
}
|
||||
|
||||
/// Raw macro to declare a function that returns an ioctl number.
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_ioc_nr {
|
||||
($name:ident, $dir:expr, $ty:expr, $nr:expr, $size:expr) => {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::cast_lossless)]
|
||||
pub fn $name() -> ::std::os::raw::c_ulong {
|
||||
ioctl_expr!($dir, $ty, $nr, $size)
|
||||
}
|
||||
};
|
||||
($name:ident, $dir:expr, $ty:expr, $nr:expr, $size:expr, $($v:ident),+) => {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::cast_lossless)]
|
||||
pub fn $name($($v: ::std::os::raw::c_uint),+) -> ::std::os::raw::c_ulong {
|
||||
ioctl_expr!($dir, $ty, $nr, $size)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Declare an ioctl that transfers no data.
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_io_nr {
|
||||
($name:ident, $ty:expr, $nr:expr) => {
|
||||
ioctl_ioc_nr!($name, $crate::ioctl::_IOC_NONE, $ty, $nr, 0);
|
||||
};
|
||||
($name:ident, $ty:expr, $nr:expr, $($v:ident),+) => {
|
||||
ioctl_ioc_nr!($name, $crate::ioctl::_IOC_NONE, $ty, $nr, 0, $($v),+);
|
||||
};
|
||||
}
|
||||
|
||||
/// Declare an ioctl that reads data.
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_ior_nr {
|
||||
($name:ident, $ty:expr, $nr:expr, $size:ty) => {
|
||||
ioctl_ioc_nr!(
|
||||
$name,
|
||||
$crate::ioctl::_IOC_READ,
|
||||
$ty,
|
||||
$nr,
|
||||
::std::mem::size_of::<$size>() as u32
|
||||
);
|
||||
};
|
||||
($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
|
||||
ioctl_ioc_nr!(
|
||||
$name,
|
||||
$crate::ioctl::_IOC_READ,
|
||||
$ty,
|
||||
$nr,
|
||||
::std::mem::size_of::<$size>() as u32,
|
||||
$($v),+
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// Declare an ioctl that writes data.
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_iow_nr {
|
||||
($name:ident, $ty:expr, $nr:expr, $size:ty) => {
|
||||
ioctl_ioc_nr!(
|
||||
$name,
|
||||
$crate::ioctl::_IOC_WRITE,
|
||||
$ty,
|
||||
$nr,
|
||||
::std::mem::size_of::<$size>() as u32
|
||||
);
|
||||
};
|
||||
($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
|
||||
ioctl_ioc_nr!(
|
||||
$name,
|
||||
$crate::ioctl::_IOC_WRITE,
|
||||
$ty,
|
||||
$nr,
|
||||
::std::mem::size_of::<$size>() as u32,
|
||||
$($v),+
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// Declare an ioctl that reads and writes data.
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_iowr_nr {
|
||||
($name:ident, $ty:expr, $nr:expr, $size:ty) => {
|
||||
ioctl_ioc_nr!(
|
||||
$name,
|
||||
$crate::ioctl::_IOC_READ | $crate::ioctl::_IOC_WRITE,
|
||||
$ty,
|
||||
$nr,
|
||||
::std::mem::size_of::<$size>() as u32
|
||||
);
|
||||
};
|
||||
($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
|
||||
ioctl_ioc_nr!(
|
||||
$name,
|
||||
$crate::ioctl::_IOC_READ | $crate::ioctl::_IOC_WRITE,
|
||||
$ty,
|
||||
$nr,
|
||||
::std::mem::size_of::<$size>() as u32,
|
||||
$($v),+
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
pub const _IOC_NRBITS: c_uint = 8;
|
||||
pub const _IOC_TYPEBITS: c_uint = 8;
|
||||
pub const _IOC_SIZEBITS: c_uint = 14;
|
||||
pub const _IOC_DIRBITS: c_uint = 2;
|
||||
pub const _IOC_NRMASK: c_uint = 255;
|
||||
pub const _IOC_TYPEMASK: c_uint = 255;
|
||||
pub const _IOC_SIZEMASK: c_uint = 16383;
|
||||
pub const _IOC_DIRMASK: c_uint = 3;
|
||||
pub const _IOC_NRSHIFT: c_uint = 0;
|
||||
pub const _IOC_TYPESHIFT: c_uint = 8;
|
||||
pub const _IOC_SIZESHIFT: c_uint = 16;
|
||||
pub const _IOC_DIRSHIFT: c_uint = 30;
|
||||
pub const _IOC_NONE: c_uint = 0;
|
||||
pub const _IOC_WRITE: c_uint = 1;
|
||||
pub const _IOC_READ: c_uint = 2;
|
||||
pub const IOC_IN: c_uint = 1_073_741_824;
|
||||
pub const IOC_OUT: c_uint = 2_147_483_648;
|
||||
pub const IOC_INOUT: c_uint = 3_221_225_472;
|
||||
pub const IOCSIZE_MASK: c_uint = 1_073_676_288;
|
||||
pub const IOCSIZE_SHIFT: c_uint = 16;
|
||||
|
||||
// The type of the `req` parameter is different for the `musl` library. This will enable
|
||||
// successful build for other non-musl libraries.
|
||||
#[cfg(target_env = "musl")]
|
||||
type IoctlRequest = c_int;
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
type IoctlRequest = c_ulong;
|
||||
|
||||
/// Run an ioctl with no arguments.
|
||||
pub unsafe fn ioctl<F: AsRawFd>(fd: &F, req: c_ulong) -> c_int {
|
||||
libc::ioctl(fd.as_raw_fd(), req as IoctlRequest, 0)
|
||||
}
|
||||
|
||||
/// Run an ioctl with a single value argument.
|
||||
pub unsafe fn ioctl_with_val<F: AsRawFd>(fd: &F, req: c_ulong, arg: c_ulong) -> c_int {
|
||||
libc::ioctl(fd.as_raw_fd(), req as IoctlRequest, arg)
|
||||
}
|
||||
|
||||
/// Run an ioctl with an immutable reference.
|
||||
pub unsafe fn ioctl_with_ref<F: AsRawFd, T>(fd: &F, req: c_ulong, arg: &T) -> c_int {
|
||||
libc::ioctl(
|
||||
fd.as_raw_fd(),
|
||||
req as IoctlRequest,
|
||||
arg as *const T as *const c_void,
|
||||
)
|
||||
}
|
||||
|
||||
/// Run an ioctl with a mutable reference.
|
||||
pub unsafe fn ioctl_with_mut_ref<F: AsRawFd, T>(fd: &F, req: c_ulong, arg: &mut T) -> c_int {
|
||||
libc::ioctl(
|
||||
fd.as_raw_fd(),
|
||||
req as IoctlRequest,
|
||||
arg as *mut T as *mut c_void,
|
||||
)
|
||||
}
|
||||
|
||||
/// Run an ioctl with a raw pointer.
|
||||
pub unsafe fn ioctl_with_ptr<F: AsRawFd, T>(fd: &F, req: c_ulong, arg: *const T) -> c_int {
|
||||
libc::ioctl(fd.as_raw_fd(), req as IoctlRequest, arg as *const c_void)
|
||||
}
|
||||
|
||||
/// Run an ioctl with a mutable raw pointer.
|
||||
pub unsafe fn ioctl_with_mut_ptr<F: AsRawFd, T>(fd: &F, req: c_ulong, arg: *mut T) -> c_int {
|
||||
libc::ioctl(fd.as_raw_fd(), req as IoctlRequest, arg as *mut c_void)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
const TUNTAP: ::std::os::raw::c_uint = 0x54;
|
||||
const VHOST: ::std::os::raw::c_uint = 0xAF;
|
||||
const EVDEV: ::std::os::raw::c_uint = 0x45;
|
||||
|
||||
const KVMIO: ::std::os::raw::c_uint = 0xAE;
|
||||
|
||||
ioctl_io_nr!(KVM_CREATE_VM, KVMIO, 0x01);
|
||||
ioctl_ior_nr!(TUNGETFEATURES, TUNTAP, 0xcf, ::std::os::raw::c_uint);
|
||||
ioctl_iow_nr!(TUNSETQUEUE, TUNTAP, 0xd9, ::std::os::raw::c_int);
|
||||
ioctl_io_nr!(VHOST_SET_OWNER, VHOST, 0x01);
|
||||
ioctl_iowr_nr!(VHOST_GET_VRING_BASE, VHOST, 0x12, ::std::os::raw::c_int);
|
||||
ioctl_iowr_nr!(KVM_GET_MSR_INDEX_LIST, KVMIO, 0x2, ::std::os::raw::c_int);
|
||||
|
||||
ioctl_ior_nr!(EVIOCGBIT, EVDEV, 0x20 + evt, [u8; 128], evt);
|
||||
ioctl_io_nr!(FAKE_IOCTL_2_ARG, EVDEV, 0x01 + x + y, x, y);
|
||||
|
||||
#[test]
|
||||
fn test_ioctl_macros() {
|
||||
assert_eq!(0x0000_AE01, KVM_CREATE_VM());
|
||||
assert_eq!(0x0000_AF01, VHOST_SET_OWNER());
|
||||
assert_eq!(0x8004_54CF, TUNGETFEATURES());
|
||||
assert_eq!(0x4004_54D9, TUNSETQUEUE());
|
||||
assert_eq!(0xC004_AE02, KVM_GET_MSR_INDEX_LIST());
|
||||
assert_eq!(0xC004_AF12, VHOST_GET_VRING_BASE());
|
||||
|
||||
assert_eq!(0x8080_4522, EVIOCGBIT(2));
|
||||
assert_eq!(0x0000_4509, FAKE_IOCTL_2_ARG(3, 5));
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
extern crate libc;
|
||||
|
||||
mod tempdir;
|
||||
|
||||
#[macro_use]
|
||||
pub mod ioctl;
|
||||
|
||||
pub mod errno;
|
||||
pub mod eventfd;
|
||||
pub mod file_traits;
|
||||
pub mod seek_hole;
|
||||
pub mod signal;
|
||||
pub mod terminal;
|
||||
pub mod timerfd;
|
||||
pub mod write_zeroes;
|
||||
|
||||
#[macro_use]
|
||||
pub mod syslog;
|
||||
|
||||
pub mod poll;
|
||||
|
||||
pub use crate::tempdir::*;
|
||||
pub use errno::*;
|
||||
pub use eventfd::*;
|
||||
pub use poll::*;
|
||||
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
pub use crate::file_traits::{FileSetLen, FileSync};
|
||||
pub use crate::seek_hole::SeekHole;
|
||||
pub use crate::write_zeroes::{PunchHole, WriteZeroes};
|
||||
|
||||
pub enum FallocateMode {
|
||||
PunchHole,
|
||||
ZeroRange,
|
||||
}
|
||||
|
||||
/// Safe wrapper for `fallocate()`.
|
||||
pub fn fallocate(
|
||||
file: &dyn AsRawFd,
|
||||
mode: FallocateMode,
|
||||
keep_size: bool,
|
||||
offset: u64,
|
||||
len: u64,
|
||||
) -> Result<()> {
|
||||
let offset = if offset > libc::off64_t::max_value() as u64 {
|
||||
return Err(Error::new(libc::EINVAL));
|
||||
} else {
|
||||
offset as libc::off64_t
|
||||
};
|
||||
|
||||
let len = if len > libc::off64_t::max_value() as u64 {
|
||||
return Err(Error::new(libc::EINVAL));
|
||||
} else {
|
||||
len as libc::off64_t
|
||||
};
|
||||
|
||||
let mut mode = match mode {
|
||||
FallocateMode::PunchHole => libc::FALLOC_FL_PUNCH_HOLE,
|
||||
FallocateMode::ZeroRange => libc::FALLOC_FL_ZERO_RANGE,
|
||||
};
|
||||
|
||||
if keep_size {
|
||||
mode |= libc::FALLOC_FL_KEEP_SIZE;
|
||||
}
|
||||
|
||||
// Safe since we pass in a valid fd and fallocate mode, validate offset and len,
|
||||
// and check the return value.
|
||||
let ret = unsafe { libc::fallocate64(file.as_raw_fd(), mode, offset, len) };
|
||||
if ret < 0 {
|
||||
errno_result()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
711
vendor/git-ccfa5f04f3f14300/vmm-sys-util/src/poll.rs
vendored
711
vendor/git-ccfa5f04f3f14300/vmm-sys-util/src/poll.rs
vendored
@ -1,711 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::cell::{Cell, Ref, RefCell};
|
||||
use std::cmp::min;
|
||||
use std::fs::File;
|
||||
use std::i32;
|
||||
use std::i64;
|
||||
use std::marker::PhantomData;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::ptr::null_mut;
|
||||
use std::slice;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use libc::{
|
||||
c_int, epoll_create1, epoll_ctl, epoll_event, epoll_wait, EINTR, EPOLLHUP, EPOLLIN, EPOLLOUT,
|
||||
EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD,
|
||||
};
|
||||
|
||||
use crate::{errno_result, Error, Result};
|
||||
|
||||
macro_rules! handle_eintr_errno {
|
||||
($x:expr) => {{
|
||||
let mut res;
|
||||
loop {
|
||||
res = $x;
|
||||
if res != -1 || Error::last() != Error::new(EINTR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
res
|
||||
}};
|
||||
}
|
||||
|
||||
const POLL_CONTEXT_MAX_EVENTS: usize = 16;
|
||||
|
||||
/// EpollEvents wraps raw epoll_events, it should only be used with EpollContext.
|
||||
pub struct EpollEvents(RefCell<[epoll_event; POLL_CONTEXT_MAX_EVENTS]>);
|
||||
|
||||
impl EpollEvents {
|
||||
pub fn new() -> EpollEvents {
|
||||
EpollEvents(RefCell::new(
|
||||
[epoll_event { events: 0, u64: 0 }; POLL_CONTEXT_MAX_EVENTS],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EpollEvents {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for a token that can be associated with an `fd` in a `PollContext`.
|
||||
///
|
||||
/// Simple enums that have no or primitive variant data can use the `#[derive(PollToken)]`
|
||||
/// custom derive to implement this trait.
|
||||
pub trait PollToken {
|
||||
/// Converts this token into a u64 that can be turned back into a token via `from_raw_token`.
|
||||
fn as_raw_token(&self) -> u64;
|
||||
|
||||
/// Converts a raw token as returned from `as_raw_token` back into a token.
|
||||
///
|
||||
/// It is invalid to give a raw token that was not returned via `as_raw_token` from the same
|
||||
/// `Self`. The implementation can expect that this will never happen as a result of its usage
|
||||
/// in `PollContext`.
|
||||
fn from_raw_token(data: u64) -> Self;
|
||||
}
|
||||
|
||||
impl PollToken for usize {
|
||||
fn as_raw_token(&self) -> u64 {
|
||||
*self as u64
|
||||
}
|
||||
|
||||
fn from_raw_token(data: u64) -> Self {
|
||||
data as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl PollToken for u64 {
|
||||
fn as_raw_token(&self) -> u64 {
|
||||
*self as u64
|
||||
}
|
||||
|
||||
fn from_raw_token(data: u64) -> Self {
|
||||
data as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl PollToken for u32 {
|
||||
fn as_raw_token(&self) -> u64 {
|
||||
u64::from(*self)
|
||||
}
|
||||
|
||||
fn from_raw_token(data: u64) -> Self {
|
||||
data as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl PollToken for u16 {
|
||||
fn as_raw_token(&self) -> u64 {
|
||||
u64::from(*self)
|
||||
}
|
||||
|
||||
fn from_raw_token(data: u64) -> Self {
|
||||
data as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl PollToken for u8 {
|
||||
fn as_raw_token(&self) -> u64 {
|
||||
u64::from(*self)
|
||||
}
|
||||
|
||||
fn from_raw_token(data: u64) -> Self {
|
||||
data as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl PollToken for () {
|
||||
fn as_raw_token(&self) -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
fn from_raw_token(_data: u64) -> Self {}
|
||||
}
|
||||
|
||||
/// An event returned by `PollContext::wait`.
|
||||
pub struct PollEvent<'a, T> {
|
||||
event: &'a epoll_event,
|
||||
token: PhantomData<T>, // Needed to satisfy usage of T
|
||||
}
|
||||
|
||||
impl<'a, T: PollToken> PollEvent<'a, T> {
|
||||
/// Gets the token associated in `PollContext::add` with this event.
|
||||
pub fn token(&self) -> T {
|
||||
T::from_raw_token(self.event.u64)
|
||||
}
|
||||
|
||||
/// True if the `fd` associated with this token in `PollContext::add` is readable.
|
||||
pub fn readable(&self) -> bool {
|
||||
self.event.events & (EPOLLIN as u32) != 0
|
||||
}
|
||||
|
||||
/// True if the `fd` associated with this token in `PollContext::add` has been hungup on.
|
||||
pub fn hungup(&self) -> bool {
|
||||
self.event.events & (EPOLLHUP as u32) != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over some (sub)set of events returned by `PollContext::wait`.
|
||||
pub struct PollEventIter<'a, I, T>
|
||||
where
|
||||
I: Iterator<Item = &'a epoll_event>,
|
||||
{
|
||||
mask: u32,
|
||||
iter: I,
|
||||
tokens: PhantomData<[T]>, // Needed to satisfy usage of T
|
||||
}
|
||||
|
||||
impl<'a, I, T> Iterator for PollEventIter<'a, I, T>
|
||||
where
|
||||
I: Iterator<Item = &'a epoll_event>,
|
||||
T: PollToken,
|
||||
{
|
||||
type Item = PollEvent<'a, T>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mask = self.mask;
|
||||
self.iter
|
||||
.find(|event| (event.events & mask) != 0)
|
||||
.map(|event| PollEvent {
|
||||
event,
|
||||
token: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The list of event returned by `PollContext::wait`.
|
||||
pub struct PollEvents<'a, T> {
|
||||
count: usize,
|
||||
events: Ref<'a, [epoll_event; POLL_CONTEXT_MAX_EVENTS]>,
|
||||
tokens: PhantomData<[T]>, // Needed to satisfy usage of T
|
||||
}
|
||||
|
||||
impl<'a, T: PollToken> PollEvents<'a, T> {
|
||||
/// Copies the events to an owned structure so the reference to this (and by extension
|
||||
/// `PollContext`) can be dropped.
|
||||
pub fn to_owned(&self) -> PollEventsOwned<T> {
|
||||
PollEventsOwned {
|
||||
count: self.count,
|
||||
events: RefCell::new(*self.events),
|
||||
tokens: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over each event.
|
||||
pub fn iter(&self) -> PollEventIter<slice::Iter<epoll_event>, T> {
|
||||
PollEventIter {
|
||||
mask: 0xffff_ffff,
|
||||
iter: self.events[..self.count].iter(),
|
||||
tokens: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over each readable event.
|
||||
pub fn iter_readable(&self) -> PollEventIter<slice::Iter<epoll_event>, T> {
|
||||
PollEventIter {
|
||||
mask: EPOLLIN as u32,
|
||||
iter: self.events[..self.count].iter(),
|
||||
tokens: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over each hungup event.
|
||||
pub fn iter_hungup(&self) -> PollEventIter<slice::Iter<epoll_event>, T> {
|
||||
PollEventIter {
|
||||
mask: EPOLLHUP as u32,
|
||||
iter: self.events[..self.count].iter(),
|
||||
tokens: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A deep copy of the event records from `PollEvents`.
|
||||
pub struct PollEventsOwned<T> {
|
||||
count: usize,
|
||||
events: RefCell<[epoll_event; POLL_CONTEXT_MAX_EVENTS]>,
|
||||
tokens: PhantomData<T>, // Needed to satisfy usage of T
|
||||
}
|
||||
|
||||
impl<T: PollToken> PollEventsOwned<T> {
|
||||
/// Takes a reference to the events so that they can be iterated via methods in `PollEvents`.
|
||||
pub fn as_ref(&self) -> PollEvents<T> {
|
||||
PollEvents {
|
||||
count: self.count,
|
||||
events: self.events.borrow(),
|
||||
tokens: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Watching events taken by PollContext.
|
||||
pub struct WatchingEvents(u32);
|
||||
|
||||
impl WatchingEvents {
|
||||
/// Returns empty Events.
|
||||
#[inline(always)]
|
||||
pub fn empty() -> WatchingEvents {
|
||||
WatchingEvents(0)
|
||||
}
|
||||
|
||||
/// Build Events from raw epoll events (defined in epoll_ctl(2)).
|
||||
#[inline(always)]
|
||||
pub fn new(raw: u32) -> WatchingEvents {
|
||||
WatchingEvents(raw)
|
||||
}
|
||||
|
||||
/// Set read events.
|
||||
#[inline(always)]
|
||||
pub fn set_read(self) -> WatchingEvents {
|
||||
WatchingEvents(self.0 | EPOLLIN as u32)
|
||||
}
|
||||
|
||||
/// Set write events.
|
||||
#[inline(always)]
|
||||
pub fn set_write(self) -> WatchingEvents {
|
||||
WatchingEvents(self.0 | EPOLLOUT as u32)
|
||||
}
|
||||
|
||||
/// Get the underlying epoll events.
|
||||
pub fn get_raw(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// EpollContext wraps linux epoll. It provides similar interface to PollContext.
|
||||
/// It is thread safe while PollContext is not. It requires user to pass in a reference of
|
||||
/// EpollEvents while PollContext does not. Always use PollContext if you don't need to access the
|
||||
/// same epoll from different threads.
|
||||
pub struct EpollContext<T> {
|
||||
epoll_ctx: File,
|
||||
// Needed to satisfy usage of T
|
||||
tokens: PhantomData<[T]>,
|
||||
}
|
||||
|
||||
impl<T: PollToken> EpollContext<T> {
|
||||
/// Creates a new `EpollContext`.
|
||||
pub fn new() -> Result<EpollContext<T>> {
|
||||
// Safe because we check the return value.
|
||||
let epoll_fd = unsafe { epoll_create1(EPOLL_CLOEXEC) };
|
||||
if epoll_fd < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
Ok(EpollContext {
|
||||
epoll_ctx: unsafe { File::from_raw_fd(epoll_fd) },
|
||||
tokens: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds the given `fd` to this context and associates the given `token` with the `fd`'s
|
||||
/// readable events.
|
||||
///
|
||||
/// A `fd` can only be added once and does not need to be kept open. If the `fd` is dropped and
|
||||
/// there were no duplicated file descriptors (i.e. adding the same descriptor with a different
|
||||
/// FD number) added to this context, events will not be reported by `wait` anymore.
|
||||
pub fn add(&self, fd: &AsRawFd, token: T) -> Result<()> {
|
||||
self.add_fd_with_events(fd, WatchingEvents::empty().set_read(), token)
|
||||
}
|
||||
|
||||
/// Adds the given `fd` to this context, watching for the specified events and associates the
|
||||
/// given 'token' with those events.
|
||||
///
|
||||
/// A `fd` can only be added once and does not need to be kept open. If the `fd` is dropped and
|
||||
/// there were no duplicated file descriptors (i.e. adding the same descriptor with a different
|
||||
/// FD number) added to this context, events will not be reported by `wait` anymore.
|
||||
pub fn add_fd_with_events(&self, fd: &AsRawFd, events: WatchingEvents, token: T) -> Result<()> {
|
||||
let mut evt = epoll_event {
|
||||
events: events.get_raw(),
|
||||
u64: token.as_raw_token(),
|
||||
};
|
||||
// Safe because we give a valid epoll FD and FD to watch, as well as a valid epoll_event
|
||||
// structure. Then we check the return value.
|
||||
let ret = unsafe {
|
||||
epoll_ctl(
|
||||
self.epoll_ctx.as_raw_fd(),
|
||||
EPOLL_CTL_ADD,
|
||||
fd.as_raw_fd(),
|
||||
&mut evt,
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If `fd` was previously added to this context, the watched events will be replaced with
|
||||
/// `events` and the token associated with it will be replaced with the given `token`.
|
||||
pub fn modify(&self, fd: &AsRawFd, events: WatchingEvents, token: T) -> Result<()> {
|
||||
let mut evt = epoll_event {
|
||||
events: events.0,
|
||||
u64: token.as_raw_token(),
|
||||
};
|
||||
// Safe because we give a valid epoll FD and FD to modify, as well as a valid epoll_event
|
||||
// structure. Then we check the return value.
|
||||
let ret = unsafe {
|
||||
epoll_ctl(
|
||||
self.epoll_ctx.as_raw_fd(),
|
||||
EPOLL_CTL_MOD,
|
||||
fd.as_raw_fd(),
|
||||
&mut evt,
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes the given `fd` from this context.
|
||||
///
|
||||
/// If an `fd`'s token shows up in the list of hangup events, it should be removed using this
|
||||
/// method or by closing/dropping (if and only if the fd was never dup()'d/fork()'d) the `fd`.
|
||||
/// Failure to do so will cause the `wait` method to always return immediately, causing ~100%
|
||||
/// CPU load.
|
||||
pub fn delete(&self, fd: &AsRawFd) -> Result<()> {
|
||||
// Safe because we give a valid epoll FD and FD to stop watching. Then we check the return
|
||||
// value.
|
||||
let ret = unsafe {
|
||||
epoll_ctl(
|
||||
self.epoll_ctx.as_raw_fd(),
|
||||
EPOLL_CTL_DEL,
|
||||
fd.as_raw_fd(),
|
||||
null_mut(),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits for any events to occur in FDs that were previously added to this context.
|
||||
///
|
||||
/// The events are level-triggered, meaning that if any events are unhandled (i.e. not reading
|
||||
/// for readable events and not closing for hungup events), subsequent calls to `wait` will
|
||||
/// return immediately. The consequence of not handling an event perpetually while calling
|
||||
/// `wait` is that the callers loop will degenerated to busy loop polling, pinning a CPU to
|
||||
/// ~100% usage.
|
||||
pub fn wait<'a>(&self, events: &'a EpollEvents) -> Result<PollEvents<'a, T>> {
|
||||
self.wait_timeout(events, Duration::new(i64::MAX as u64, 0))
|
||||
}
|
||||
|
||||
/// Like `wait` except will only block for a maximum of the given `timeout`.
|
||||
///
|
||||
/// This may return earlier than `timeout` with zero events if the duration indicated exceeds
|
||||
/// system limits.
|
||||
pub fn wait_timeout<'a>(
|
||||
&self,
|
||||
events: &'a EpollEvents,
|
||||
timeout: Duration,
|
||||
) -> Result<PollEvents<'a, T>> {
|
||||
let timeout_millis = if timeout.as_secs() as i64 == i64::max_value() {
|
||||
// We make the convenient assumption that 2^63 seconds is an effectively unbounded time
|
||||
// frame. This is meant to mesh with `wait` calling us with no timeout.
|
||||
-1
|
||||
} else {
|
||||
// In cases where we the number of milliseconds would overflow an i32, we substitute the
|
||||
// maximum timeout which is ~24.8 days.
|
||||
let millis = timeout
|
||||
.as_secs()
|
||||
.checked_mul(1_000)
|
||||
.and_then(|ms| ms.checked_add(u64::from(timeout.subsec_nanos()) / 1_000_000))
|
||||
.unwrap_or(i32::max_value() as u64);
|
||||
min(i32::max_value() as u64, millis) as i32
|
||||
};
|
||||
let ret = {
|
||||
let mut epoll_events = events.0.borrow_mut();
|
||||
let max_events = epoll_events.len() as c_int;
|
||||
// Safe because we give an epoll context and a properly sized epoll_events array
|
||||
// pointer, which we trust the kernel to fill in properly.
|
||||
unsafe {
|
||||
handle_eintr_errno!(epoll_wait(
|
||||
self.epoll_ctx.as_raw_fd(),
|
||||
&mut epoll_events[0],
|
||||
max_events,
|
||||
timeout_millis
|
||||
))
|
||||
}
|
||||
};
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
let epoll_events = events.0.borrow();
|
||||
let events = PollEvents {
|
||||
count: ret as usize,
|
||||
events: epoll_events,
|
||||
tokens: PhantomData,
|
||||
};
|
||||
Ok(events)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PollToken> AsRawFd for EpollContext<T> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.epoll_ctx.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PollToken> IntoRawFd for EpollContext<T> {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.epoll_ctx.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to poll multiple objects that have file descriptors.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use vmm_sys_util::{Result, EventFd, PollContext, PollEvents};
|
||||
/// # fn test() -> Result<()> {
|
||||
/// let evt1 = EventFd::new(0)?;
|
||||
/// let evt2 = EventFd::new(0)?;
|
||||
/// evt2.write(1)?;
|
||||
///
|
||||
/// let ctx: PollContext<u32> = PollContext::new()?;
|
||||
/// ctx.add(&evt1, 1)?;
|
||||
/// ctx.add(&evt2, 2)?;
|
||||
///
|
||||
/// let pollevents: PollEvents<u32> = ctx.wait()?;
|
||||
/// let tokens: Vec<u32> = pollevents.iter_readable().map(|e| e.token()).collect();
|
||||
/// assert_eq!(&tokens[..], &[2]);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct PollContext<T> {
|
||||
epoll_ctx: EpollContext<T>,
|
||||
|
||||
// We use a RefCell here so that the `wait` method only requires an immutable self reference
|
||||
// while returning the events (encapsulated by PollEvents). Without the RefCell, `wait` would
|
||||
// hold a mutable reference that lives as long as its returned reference (i.e. the PollEvents),
|
||||
// even though that reference is immutable. This is terribly inconvenient for the caller because
|
||||
// the borrow checking would prevent them from using `delete` and `add` while the events are in
|
||||
// scope.
|
||||
events: EpollEvents,
|
||||
|
||||
// Hangup busy loop detection variables. See `check_for_hungup_busy_loop`.
|
||||
hangups: Cell<usize>,
|
||||
max_hangups: Cell<usize>,
|
||||
}
|
||||
|
||||
impl<T: PollToken> PollContext<T> {
|
||||
/// Creates a new `PollContext`.
|
||||
pub fn new() -> Result<PollContext<T>> {
|
||||
Ok(PollContext {
|
||||
epoll_ctx: EpollContext::new()?,
|
||||
events: EpollEvents::new(),
|
||||
hangups: Cell::new(0),
|
||||
max_hangups: Cell::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds the given `fd` to this context and associates the given `token` with the `fd`'s
|
||||
/// readable events.
|
||||
///
|
||||
/// A `fd` can only be added once and does not need to be kept open. If the `fd` is dropped and
|
||||
/// there were no duplicated file descriptors (i.e. adding the same descriptor with a different
|
||||
/// FD number) added to this context, events will not be reported by `wait` anymore.
|
||||
pub fn add(&self, fd: &AsRawFd, token: T) -> Result<()> {
|
||||
self.add_fd_with_events(fd, WatchingEvents::empty().set_read(), token)
|
||||
}
|
||||
|
||||
/// Adds the given `fd` to this context, watching for the specified events and associates the
|
||||
/// given 'token' with those events.
|
||||
///
|
||||
/// A `fd` can only be added once and does not need to be kept open. If the `fd` is dropped and
|
||||
/// there were no duplicated file descriptors (i.e. adding the same descriptor with a different
|
||||
/// FD number) added to this context, events will not be reported by `wait` anymore.
|
||||
pub fn add_fd_with_events(&self, fd: &AsRawFd, events: WatchingEvents, token: T) -> Result<()> {
|
||||
self.epoll_ctx.add_fd_with_events(fd, events, token)?;
|
||||
self.hangups.set(0);
|
||||
self.max_hangups.set(self.max_hangups.get() + 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If `fd` was previously added to this context, the watched events will be replaced with
|
||||
/// `events` and the token associated with it will be replaced with the given `token`.
|
||||
pub fn modify(&self, fd: &AsRawFd, events: WatchingEvents, token: T) -> Result<()> {
|
||||
self.epoll_ctx.modify(fd, events, token)
|
||||
}
|
||||
|
||||
/// Deletes the given `fd` from this context.
|
||||
///
|
||||
/// If an `fd`'s token shows up in the list of hangup events, it should be removed using this
|
||||
/// method or by closing/dropping (if and only if the fd was never dup()'d/fork()'d) the `fd`.
|
||||
/// Failure to do so will cause the `wait` method to always return immediately, causing ~100%
|
||||
/// CPU load.
|
||||
pub fn delete(&self, fd: &AsRawFd) -> Result<()> {
|
||||
self.epoll_ctx.delete(fd)?;
|
||||
self.hangups.set(0);
|
||||
self.max_hangups.set(self.max_hangups.get() - 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This method determines if the the user of wait is misusing the `PollContext` by leaving FDs
|
||||
// in this `PollContext` that have been shutdown or hungup on. Such an FD will cause `wait` to
|
||||
// return instantly with a hungup event. If that FD is perpetually left in this context, a busy
|
||||
// loop burning ~100% of one CPU will silently occur with no human visible malfunction.
|
||||
//
|
||||
// How do we know if the client of this context is ignoring hangups? A naive implementation
|
||||
// would trigger if consecutive wait calls yield hangup events, but there are legitimate cases
|
||||
// for this, such as two distinct sockets becoming hungup across two consecutive wait calls. A
|
||||
// smarter implementation would only trigger if `delete` wasn't called between waits that
|
||||
// yielded hangups. Sadly `delete` isn't the only way to remove an FD from this context. The
|
||||
// other way is for the client to close the hungup FD, which automatically removes it from this
|
||||
// context. Assuming that the client always uses close, this implementation would too eagerly
|
||||
// trigger.
|
||||
//
|
||||
// The implementation used here keeps an upper bound of FDs in this context using a counter
|
||||
// hooked into add/delete (which is imprecise because close can also remove FDs without us
|
||||
// knowing). The number of consecutive (no add or delete in between) hangups yielded by wait
|
||||
// calls is counted and compared to the upper bound. If the upper bound is exceeded by the
|
||||
// consecutive hangups, the implementation triggers the check and logs.
|
||||
//
|
||||
// This implementation has false negatives because the upper bound can be completely too high,
|
||||
// in the worst case caused by only using close instead of delete. However, this method has the
|
||||
// advantage of always triggering eventually genuine busy loop cases, requires no dynamic
|
||||
// allocations, is fast and constant time to compute, and has no false positives.
|
||||
fn check_for_hungup_busy_loop(&self, new_hangups: usize) {
|
||||
let old_hangups = self.hangups.get();
|
||||
let max_hangups = self.max_hangups.get();
|
||||
if old_hangups <= max_hangups && old_hangups + new_hangups > max_hangups {
|
||||
warn!(
|
||||
"busy poll wait loop with hungup FDs detected on thread {}",
|
||||
thread::current().name().unwrap_or("")
|
||||
);
|
||||
// This panic is helpful for tests of this functionality.
|
||||
#[cfg(test)]
|
||||
panic!("hungup busy loop detected");
|
||||
}
|
||||
self.hangups.set(old_hangups + new_hangups);
|
||||
}
|
||||
|
||||
/// Waits for any events to occur in FDs that were previously added to this context.
|
||||
///
|
||||
/// The events are level-triggered, meaning that if any events are unhandled (i.e. not reading
|
||||
/// for readable events and not closing for hungup events), subsequent calls to `wait` will
|
||||
/// return immediately. The consequence of not handling an event perpetually while calling
|
||||
/// `wait` is that the callers loop will degenerated to busy loop polling, pinning a CPU to
|
||||
/// ~100% usage.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the returned `PollEvents` structure is not dropped before subsequent `wait` calls.
|
||||
pub fn wait(&self) -> Result<PollEvents<T>> {
|
||||
self.wait_timeout(Duration::new(i64::MAX as u64, 0))
|
||||
}
|
||||
|
||||
/// Like `wait` except will only block for a maximum of the given `timeout`.
|
||||
///
|
||||
/// This may return earlier than `timeout` with zero events if the duration indicated exceeds
|
||||
/// system limits.
|
||||
pub fn wait_timeout(&self, timeout: Duration) -> Result<PollEvents<T>> {
|
||||
let events = self.epoll_ctx.wait_timeout(&self.events, timeout)?;
|
||||
let hangups = events.iter_hungup().count();
|
||||
self.check_for_hungup_busy_loop(hangups);
|
||||
Ok(events)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PollToken> AsRawFd for PollContext<T> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.epoll_ctx.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PollToken> IntoRawFd for PollContext<T> {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.epoll_ctx.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::eventfd::EventFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::time::Instant;
|
||||
|
||||
#[test]
|
||||
fn test_poll_context() {
|
||||
let evt1 = EventFd::new(0).unwrap();
|
||||
let evt2 = EventFd::new(0).unwrap();
|
||||
evt1.write(1).unwrap();
|
||||
evt2.write(1).unwrap();
|
||||
let ctx: PollContext<u32> = PollContext::new().unwrap();
|
||||
ctx.add(&evt1, 1).unwrap();
|
||||
ctx.add(&evt2, 2).unwrap();
|
||||
|
||||
let mut evt_count = 0;
|
||||
while evt_count < 2 {
|
||||
for event in ctx.wait().unwrap().iter_readable() {
|
||||
evt_count += 1;
|
||||
match event.token() {
|
||||
1 => {
|
||||
evt1.read().unwrap();
|
||||
ctx.delete(&evt1).unwrap();
|
||||
}
|
||||
2 => {
|
||||
evt2.read().unwrap();
|
||||
ctx.delete(&evt2).unwrap();
|
||||
}
|
||||
_ => panic!("unexpected token"),
|
||||
};
|
||||
}
|
||||
}
|
||||
assert_eq!(evt_count, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_context_overflow() {
|
||||
const EVT_COUNT: usize = POLL_CONTEXT_MAX_EVENTS * 2 + 1;
|
||||
let ctx: PollContext<usize> = PollContext::new().unwrap();
|
||||
let mut evts = Vec::with_capacity(EVT_COUNT);
|
||||
for i in 0..EVT_COUNT {
|
||||
let evt = EventFd::new(0).unwrap();
|
||||
evt.write(1).unwrap();
|
||||
ctx.add(&evt, i).unwrap();
|
||||
evts.push(evt);
|
||||
}
|
||||
let mut evt_count = 0;
|
||||
while evt_count < EVT_COUNT {
|
||||
for event in ctx.wait().unwrap().iter_readable() {
|
||||
evts[event.token()].read().unwrap();
|
||||
evt_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_poll_context_hungup() {
|
||||
let (s1, s2) = UnixStream::pair().unwrap();
|
||||
let ctx: PollContext<u32> = PollContext::new().unwrap();
|
||||
ctx.add(&s1, 1).unwrap();
|
||||
|
||||
// Causes s1 to receive hangup events, which we purposefully ignore to trip the detection
|
||||
// logic in `PollContext`.
|
||||
drop(s2);
|
||||
|
||||
// Should easily panic within this many iterations.
|
||||
for _ in 0..1000 {
|
||||
ctx.wait().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_context_timeout() {
|
||||
let ctx: PollContext<u32> = PollContext::new().unwrap();
|
||||
let dur = Duration::from_millis(10);
|
||||
let start_inst = Instant::now();
|
||||
ctx.wait_timeout(dur).unwrap();
|
||||
assert!(start_inst.elapsed() >= dur);
|
||||
}
|
||||
|
||||
}
|
@ -1,215 +0,0 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Error, Result};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
#[cfg(target_env = "musl")]
|
||||
use libc::{c_int, lseek64, ENXIO};
|
||||
|
||||
#[cfg(target_env = "gnu")]
|
||||
use libc::{lseek64, ENXIO, SEEK_DATA, SEEK_HOLE};
|
||||
|
||||
/// A trait for seeking to the next hole or non-hole position in a file.
|
||||
pub trait SeekHole {
|
||||
/// Seek to the first hole in a file at a position greater than or equal to `offset`.
|
||||
/// If no holes exist after `offset`, the seek position will be set to the end of the file.
|
||||
/// If `offset` is at or after the end of the file, the seek position is unchanged, and None is returned.
|
||||
/// Returns the current seek position after the seek or an error.
|
||||
fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>>;
|
||||
|
||||
/// Seek to the first data in a file at a position greater than or equal to `offset`.
|
||||
/// If no data exists after `offset`, the seek position is unchanged, and None is returned.
|
||||
/// Returns the current offset after the seek or an error.
|
||||
fn seek_data(&mut self, offset: u64) -> Result<Option<u64>>;
|
||||
}
|
||||
|
||||
#[cfg(target_env = "musl")]
|
||||
pub const SEEK_DATA: c_int = 3;
|
||||
#[cfg(target_env = "musl")]
|
||||
pub const SEEK_HOLE: c_int = 4;
|
||||
|
||||
/// Safe wrapper for `libc::lseek64()`
|
||||
fn lseek(file: &mut File, offset: i64, whence: i32) -> Result<Option<u64>> {
|
||||
// This is safe because we pass a known-good file descriptor.
|
||||
let res = unsafe { lseek64(file.as_raw_fd(), offset, whence) };
|
||||
|
||||
if res < 0 {
|
||||
// Convert ENXIO into None; pass any other error as-is.
|
||||
let err = Error::last_os_error();
|
||||
if let Some(errno) = Error::raw_os_error(&err) {
|
||||
if errno == ENXIO {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(Some(res as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl SeekHole for File {
|
||||
fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>> {
|
||||
lseek(self, offset as i64, SEEK_HOLE)
|
||||
}
|
||||
|
||||
fn seek_data(&mut self, offset: u64) -> Result<Option<u64>> {
|
||||
lseek(self, offset as i64, SEEK_DATA)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::TempDir;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn seek_cur(file: &mut File) -> u64 {
|
||||
file.seek(SeekFrom::Current(0)).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seek_data() {
|
||||
let tempdir = TempDir::new("/tmp/seek_data_test").unwrap();
|
||||
let mut path = PathBuf::from(tempdir.as_path().unwrap());
|
||||
path.push("test_file");
|
||||
let mut file = File::create(&path).unwrap();
|
||||
|
||||
// Empty file
|
||||
assert_eq!(file.seek_data(0).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
|
||||
// File with non-zero length consisting entirely of a hole
|
||||
file.set_len(0x10000).unwrap();
|
||||
assert_eq!(file.seek_data(0).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
|
||||
// seek_data at or after the end of the file should return None
|
||||
assert_eq!(file.seek_data(0x10000).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
assert_eq!(file.seek_data(0x10001).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
|
||||
// Write some data to [0x10000, 0x20000)
|
||||
let b = [0x55u8; 0x10000];
|
||||
file.seek(SeekFrom::Start(0x10000)).unwrap();
|
||||
file.write_all(&b).unwrap();
|
||||
assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
|
||||
assert_eq!(seek_cur(&mut file), 0x10000);
|
||||
|
||||
// seek_data within data should return the same offset
|
||||
assert_eq!(file.seek_data(0x10000).unwrap(), Some(0x10000));
|
||||
assert_eq!(seek_cur(&mut file), 0x10000);
|
||||
assert_eq!(file.seek_data(0x10001).unwrap(), Some(0x10001));
|
||||
assert_eq!(seek_cur(&mut file), 0x10001);
|
||||
assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
|
||||
assert_eq!(seek_cur(&mut file), 0x1FFFF);
|
||||
|
||||
// Extend the file to add another hole after the data
|
||||
file.set_len(0x30000).unwrap();
|
||||
assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
|
||||
assert_eq!(seek_cur(&mut file), 0x10000);
|
||||
assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
|
||||
assert_eq!(seek_cur(&mut file), 0x1FFFF);
|
||||
assert_eq!(file.seek_data(0x20000).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0x1FFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::cyclomatic_complexity)]
|
||||
fn seek_hole() {
|
||||
let tempdir = TempDir::new("/tmp/seek_hole_test").unwrap();
|
||||
let mut path = PathBuf::from(tempdir.as_path().unwrap());
|
||||
path.push("test_file");
|
||||
let mut file = File::create(&path).unwrap();
|
||||
|
||||
// Empty file
|
||||
assert_eq!(file.seek_hole(0).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
|
||||
// File with non-zero length consisting entirely of a hole
|
||||
file.set_len(0x10000).unwrap();
|
||||
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
|
||||
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
||||
|
||||
// seek_hole at or after the end of the file should return None
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x10000).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
assert_eq!(file.seek_hole(0x10001).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
|
||||
// Write some data to [0x10000, 0x20000)
|
||||
let b = [0x55u8; 0x10000];
|
||||
file.seek(SeekFrom::Start(0x10000)).unwrap();
|
||||
file.write_all(&b).unwrap();
|
||||
|
||||
// seek_hole within a hole should return the same offset
|
||||
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
|
||||
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
||||
|
||||
// seek_hole within data should return the next hole (EOF)
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
|
||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x10001).unwrap(), Some(0x20000));
|
||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
|
||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||
|
||||
// seek_hole at EOF after data should return None
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x20000).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
|
||||
// Extend the file to add another hole after the data
|
||||
file.set_len(0x30000).unwrap();
|
||||
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
|
||||
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
|
||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
|
||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x20000));
|
||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x20001));
|
||||
assert_eq!(seek_cur(&mut file), 0x20001);
|
||||
|
||||
// seek_hole at EOF after a hole should return None
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
|
||||
// Write some data to [0x20000, 0x30000)
|
||||
file.seek(SeekFrom::Start(0x20000)).unwrap();
|
||||
file.write_all(&b).unwrap();
|
||||
|
||||
// seek_hole within [0x20000, 0x30000) should now find the hole at EOF
|
||||
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x30000));
|
||||
assert_eq!(seek_cur(&mut file), 0x30000);
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x30000));
|
||||
assert_eq!(seek_cur(&mut file), 0x30000);
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
}
|
||||
}
|
@ -1,420 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use libc::{
|
||||
c_int, c_void, pthread_kill, pthread_sigmask, pthread_t, sigaction, sigaddset, sigemptyset,
|
||||
siginfo_t, sigismember, sigpending, sigset_t, sigtimedwait, timespec, EAGAIN, EINTR, EINVAL,
|
||||
SIGHUP, SIGSYS, SIG_BLOCK, SIG_UNBLOCK,
|
||||
};
|
||||
|
||||
use errno;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::os::unix::thread::JoinHandleExt;
|
||||
use std::ptr::{null, null_mut};
|
||||
use std::result;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Couldn't create a sigset.
|
||||
CreateSigset(errno::Error),
|
||||
/// The wrapped signal has already been blocked.
|
||||
SignalAlreadyBlocked(c_int),
|
||||
/// Failed to check if the requested signal is in the blocked set already.
|
||||
CompareBlockedSignals(errno::Error),
|
||||
/// The signal could not be blocked.
|
||||
BlockSignal(errno::Error),
|
||||
/// The signal mask could not be retrieved.
|
||||
RetrieveSignalMask(i32),
|
||||
/// The signal could not be unblocked.
|
||||
UnblockSignal(errno::Error),
|
||||
/// Failed to wait for given signal.
|
||||
ClearWaitPending(errno::Error),
|
||||
/// Failed to get pending signals.
|
||||
ClearGetPending(errno::Error),
|
||||
/// Failed to check if given signal is in the set of pending signals.
|
||||
ClearCheckPending(errno::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
match self {
|
||||
CreateSigset(e) => write!(f, "couldn't create a sigset: {}", e),
|
||||
SignalAlreadyBlocked(num) => write!(f, "signal {} already blocked", num),
|
||||
CompareBlockedSignals(e) => write!(
|
||||
f,
|
||||
"failed to check whether requested signal is in the blocked set: {}",
|
||||
e,
|
||||
),
|
||||
BlockSignal(e) => write!(f, "signal could not be blocked: {}", e),
|
||||
RetrieveSignalMask(errno) => write!(
|
||||
f,
|
||||
"failed to retrieve signal mask: {}",
|
||||
io::Error::from_raw_os_error(*errno),
|
||||
),
|
||||
UnblockSignal(e) => write!(f, "signal could not be unblocked: {}", e),
|
||||
ClearWaitPending(e) => write!(f, "failed to wait for given signal: {}", e),
|
||||
ClearGetPending(e) => write!(f, "failed to get pending signals: {}", e),
|
||||
ClearCheckPending(e) => write!(
|
||||
f,
|
||||
"failed to check whether given signal is in the pending set: {}",
|
||||
e,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type SignalResult<T> = result::Result<T, Error>;
|
||||
type SiginfoHandler = extern "C" fn(num: c_int, info: *mut siginfo_t, _unused: *mut c_void) -> ();
|
||||
|
||||
pub enum SignalHandler {
|
||||
Siginfo(SiginfoHandler),
|
||||
// TODO add a`SimpleHandler` when `libc` adds `sa_handler` support to `sigaction`.
|
||||
}
|
||||
|
||||
impl SignalHandler {
|
||||
fn set_flags(act: &mut sigaction, flag: c_int) {
|
||||
act.sa_flags = flag;
|
||||
}
|
||||
}
|
||||
/// Fills a `sigaction` structure from of the signal handler.
|
||||
/// Refer to http://man7.org/linux/man-pages/man7/signal.7.html
|
||||
impl Into<sigaction> for SignalHandler {
|
||||
fn into(self) -> sigaction {
|
||||
let mut act: sigaction = unsafe { mem::zeroed() };
|
||||
match self {
|
||||
SignalHandler::Siginfo(function) => {
|
||||
act.sa_sigaction = function as *const () as usize;
|
||||
}
|
||||
}
|
||||
act
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn __libc_current_sigrtmin() -> c_int;
|
||||
fn __libc_current_sigrtmax() -> c_int;
|
||||
}
|
||||
|
||||
/// Returns the minimum (inclusive) real-time signal number.
|
||||
#[allow(non_snake_case)]
|
||||
fn SIGRTMIN() -> c_int {
|
||||
unsafe { __libc_current_sigrtmin() }
|
||||
}
|
||||
|
||||
/// Returns the maximum (inclusive) real-time signal number.
|
||||
#[allow(non_snake_case)]
|
||||
fn SIGRTMAX() -> c_int {
|
||||
unsafe { __libc_current_sigrtmax() }
|
||||
}
|
||||
|
||||
/// Verifies that a signal number is valid: for VCPU signals, it needs to be enclosed within the OS
|
||||
/// limits for realtime signals, and the remaining ones need to be between the minimum (SIGHUP) and
|
||||
/// maximum (SIGSYS) values.
|
||||
pub fn validate_signal_num(num: c_int, for_vcpu: bool) -> errno::Result<c_int> {
|
||||
if for_vcpu {
|
||||
let actual_num = num + SIGRTMIN();
|
||||
if actual_num <= SIGRTMAX() {
|
||||
return Ok(actual_num);
|
||||
}
|
||||
} else if SIGHUP <= num && num <= SIGSYS {
|
||||
return Ok(num);
|
||||
}
|
||||
Err(errno::Error::new(EINVAL))
|
||||
}
|
||||
|
||||
/// Registers `handler` as the signal handler of signum `num`.
|
||||
///
|
||||
/// Uses `sigaction` to register the handler.
|
||||
///
|
||||
/// This is considered unsafe because the given handler will be called asynchronously, interrupting
|
||||
/// whatever the thread was doing and therefore must only do async-signal-safe operations.
|
||||
/// flags: SA_SIGINFO or SA_RESTART if wants to restart after signal received.
|
||||
pub unsafe fn register_signal_handler(
|
||||
num: i32,
|
||||
handler: SignalHandler,
|
||||
for_vcpu: bool,
|
||||
flag: c_int,
|
||||
) -> errno::Result<()> {
|
||||
let num = validate_signal_num(num, for_vcpu)?;
|
||||
let mut act: sigaction = handler.into();
|
||||
SignalHandler::set_flags(&mut act, flag);
|
||||
match sigaction(num, &act, null_mut()) {
|
||||
0 => Ok(()),
|
||||
_ => errno::errno_result(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates `sigset` from an array of signal numbers.
|
||||
///
|
||||
/// This is a helper function used when we want to manipulate signals.
|
||||
pub fn create_sigset(signals: &[c_int]) -> errno::Result<sigset_t> {
|
||||
// sigset will actually be initialized by sigemptyset below.
|
||||
let mut sigset: sigset_t = unsafe { mem::zeroed() };
|
||||
|
||||
// Safe - return value is checked.
|
||||
let ret = unsafe { sigemptyset(&mut sigset) };
|
||||
if ret < 0 {
|
||||
return errno::errno_result();
|
||||
}
|
||||
|
||||
for signal in signals {
|
||||
// Safe - return value is checked.
|
||||
let ret = unsafe { sigaddset(&mut sigset, *signal) };
|
||||
if ret < 0 {
|
||||
return errno::errno_result();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(sigset)
|
||||
}
|
||||
|
||||
/// Retrieves the signal mask of the current thread as a vector of c_ints.
|
||||
pub fn get_blocked_signals() -> SignalResult<Vec<c_int>> {
|
||||
let mut mask = Vec::new();
|
||||
|
||||
// Safe - return values are checked.
|
||||
unsafe {
|
||||
let mut old_sigset: sigset_t = mem::zeroed();
|
||||
let ret = pthread_sigmask(SIG_BLOCK, null(), &mut old_sigset as *mut sigset_t);
|
||||
if ret < 0 {
|
||||
return Err(Error::RetrieveSignalMask(ret));
|
||||
}
|
||||
|
||||
for num in 0..=SIGRTMAX() {
|
||||
if sigismember(&old_sigset, num) > 0 {
|
||||
mask.push(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mask)
|
||||
}
|
||||
|
||||
/// Masks given signal.
|
||||
///
|
||||
/// If signal is already blocked the call will fail with Error::SignalAlreadyBlocked
|
||||
/// result.
|
||||
pub fn block_signal(num: c_int) -> SignalResult<()> {
|
||||
let sigset = create_sigset(&[num]).map_err(Error::CreateSigset)?;
|
||||
|
||||
// Safe - return values are checked.
|
||||
unsafe {
|
||||
let mut old_sigset: sigset_t = mem::zeroed();
|
||||
let ret = pthread_sigmask(SIG_BLOCK, &sigset, &mut old_sigset as *mut sigset_t);
|
||||
if ret < 0 {
|
||||
return Err(Error::BlockSignal(errno::Error::last()));
|
||||
}
|
||||
let ret = sigismember(&old_sigset, num);
|
||||
if ret < 0 {
|
||||
return Err(Error::CompareBlockedSignals(errno::Error::last()));
|
||||
} else if ret > 0 {
|
||||
return Err(Error::SignalAlreadyBlocked(num));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unmasks given signal.
|
||||
pub fn unblock_signal(num: c_int) -> SignalResult<()> {
|
||||
let sigset = create_sigset(&[num]).map_err(Error::CreateSigset)?;
|
||||
|
||||
// Safe - return value is checked.
|
||||
let ret = unsafe { pthread_sigmask(SIG_UNBLOCK, &sigset, null_mut()) };
|
||||
if ret < 0 {
|
||||
return Err(Error::UnblockSignal(errno::Error::last()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clears pending signal.
|
||||
pub fn clear_signal(num: c_int) -> SignalResult<()> {
|
||||
let sigset = create_sigset(&[num]).map_err(Error::CreateSigset)?;
|
||||
|
||||
while {
|
||||
// This is safe as we are rigorously checking return values
|
||||
// of libc calls.
|
||||
unsafe {
|
||||
let mut siginfo: siginfo_t = mem::zeroed();
|
||||
let ts = timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
// Attempt to consume one instance of pending signal. If signal
|
||||
// is not pending, the call will fail with EAGAIN or EINTR.
|
||||
let ret = sigtimedwait(&sigset, &mut siginfo, &ts);
|
||||
if ret < 0 {
|
||||
let e = errno::Error::last();
|
||||
match e.errno() {
|
||||
EAGAIN | EINTR => {}
|
||||
_ => {
|
||||
return Err(Error::ClearWaitPending(errno::Error::last()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This sigset will be actually filled with `sigpending` call.
|
||||
let mut chkset: sigset_t = mem::zeroed();
|
||||
// See if more instances of the signal are pending.
|
||||
let ret = sigpending(&mut chkset);
|
||||
if ret < 0 {
|
||||
return Err(Error::ClearGetPending(errno::Error::last()));
|
||||
}
|
||||
|
||||
let ret = sigismember(&chkset, num);
|
||||
if ret < 0 {
|
||||
return Err(Error::ClearCheckPending(errno::Error::last()));
|
||||
}
|
||||
|
||||
// This is do-while loop condition.
|
||||
ret != 0
|
||||
}
|
||||
} {}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Trait for threads that can be signalled via `pthread_kill`.
|
||||
///
|
||||
/// Note that this is only useful for signals between SIGRTMIN and SIGRTMAX because these are
|
||||
/// guaranteed to not be used by the C runtime.
|
||||
///
|
||||
/// This is marked unsafe because the implementation of this trait must guarantee that the returned
|
||||
/// pthread_t is valid and has a lifetime at least that of the trait object.
|
||||
pub unsafe trait Killable {
|
||||
fn pthread_handle(&self) -> pthread_t;
|
||||
|
||||
/// Sends the signal `num + SIGRTMIN` to this killable thread.
|
||||
///
|
||||
/// The value of `num + SIGRTMIN` must not exceed `SIGRTMAX`.
|
||||
fn kill(&self, num: i32) -> errno::Result<()> {
|
||||
let num = validate_signal_num(num, true)?;
|
||||
|
||||
// Safe because we ensure we are using a valid pthread handle, a valid signal number, and
|
||||
// check the return result.
|
||||
let ret = unsafe { pthread_kill(self.pthread_handle(), num) };
|
||||
if ret < 0 {
|
||||
return errno::errno_result();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Safe because we fulfill our contract of returning a genuine pthread handle.
|
||||
unsafe impl<T> Killable for JoinHandle<T> {
|
||||
fn pthread_handle(&self) -> pthread_t {
|
||||
self.as_pthread_t()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libc::SA_SIGINFO;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
static mut SIGNAL_HANDLER_CALLED: bool = false;
|
||||
|
||||
extern "C" fn handle_signal(_: c_int, _: *mut siginfo_t, _: *mut c_void) {
|
||||
unsafe {
|
||||
SIGNAL_HANDLER_CALLED = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_signal_handler() {
|
||||
unsafe {
|
||||
// testing bad value
|
||||
assert!(register_signal_handler(
|
||||
SIGRTMAX(),
|
||||
SignalHandler::Siginfo(handle_signal),
|
||||
true,
|
||||
SA_SIGINFO
|
||||
)
|
||||
.is_err());
|
||||
format!(
|
||||
"{:?}",
|
||||
register_signal_handler(
|
||||
SIGRTMAX(),
|
||||
SignalHandler::Siginfo(handle_signal),
|
||||
true,
|
||||
SA_SIGINFO
|
||||
)
|
||||
);
|
||||
assert!(register_signal_handler(
|
||||
0,
|
||||
SignalHandler::Siginfo(handle_signal),
|
||||
true,
|
||||
SA_SIGINFO
|
||||
)
|
||||
.is_ok());
|
||||
assert!(register_signal_handler(
|
||||
libc::SIGSYS,
|
||||
SignalHandler::Siginfo(handle_signal),
|
||||
false,
|
||||
SA_SIGINFO
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::empty_loop)]
|
||||
fn test_killing_thread() {
|
||||
let killable = thread::spawn(|| thread::current().id());
|
||||
let killable_id = killable.join().unwrap();
|
||||
assert_ne!(killable_id, thread::current().id());
|
||||
|
||||
// We install a signal handler for the specified signal; otherwise the whole process will
|
||||
// be brought down when the signal is received, as part of the default behaviour. Signal
|
||||
// handlers are global, so we install this before starting the thread.
|
||||
unsafe {
|
||||
register_signal_handler(0, SignalHandler::Siginfo(handle_signal), true, SA_SIGINFO)
|
||||
.expect("failed to register vcpu signal handler");
|
||||
}
|
||||
|
||||
let killable = thread::spawn(|| loop {});
|
||||
|
||||
let res = killable.kill(SIGRTMAX());
|
||||
assert!(res.is_err());
|
||||
format!("{:?}", res);
|
||||
|
||||
unsafe {
|
||||
assert!(!SIGNAL_HANDLER_CALLED);
|
||||
}
|
||||
|
||||
assert!(killable.kill(0).is_ok());
|
||||
|
||||
// We're waiting to detect that the signal handler has been called.
|
||||
const MAX_WAIT_ITERS: u32 = 20;
|
||||
let mut iter_count = 0;
|
||||
loop {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
||||
if unsafe { SIGNAL_HANDLER_CALLED } {
|
||||
break;
|
||||
}
|
||||
|
||||
iter_count += 1;
|
||||
// timeout if we wait too long
|
||||
assert!(iter_count <= MAX_WAIT_ITERS);
|
||||
}
|
||||
|
||||
// Our signal handler doesn't do anything which influences the killable thread, so the
|
||||
// previous signal is effectively ignored. If we were to join killable here, we would block
|
||||
// forever as the loop keeps running. Since we don't join, the thread will become detached
|
||||
// as the handle is dropped, and will be killed when the process/main thread exits.
|
||||
}
|
||||
}
|
@ -1,643 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
//! Facilities for sending log message to syslog.
|
||||
//!
|
||||
//! Every function exported by this module is thread-safe. Each function will silently fail until
|
||||
//! `syslog::init()` is called and returns `Ok`.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! #[macro_use]
|
||||
//! extern crate vmm_sys_util;
|
||||
//!
|
||||
//! use vmm_sys_util::syslog::init;
|
||||
//! fn main() {
|
||||
//! if let Err(e) = init() {
|
||||
//! println!("failed to initiailize syslog: {}", e);
|
||||
//! return;
|
||||
//! }
|
||||
//! warn!("this is your {} warning", "final");
|
||||
//! error!("something went horribly wrong: {}", "out of RAMs");
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{stderr, Cursor, ErrorKind, Write};
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::net::UnixDatagram;
|
||||
use std::path::PathBuf;
|
||||
use std::ptr::null;
|
||||
use std::str::from_utf8;
|
||||
use std::sync::{Mutex as StdMutex, MutexGuard, Once, ONCE_INIT};
|
||||
|
||||
use libc::{
|
||||
c_char, c_long, closelog, fcntl, gethostname, localtime_r, openlog, pid_t, syscall, time,
|
||||
time_t, tm, F_GETFD, LOG_NDELAY, LOG_PERROR, LOG_PID, LOG_USER,
|
||||
};
|
||||
|
||||
/// Temporary define linux-x86_64 syscall value here.
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const SYS_getpid: c_long = 39;
|
||||
|
||||
const SYSLOG_PATH: &str = "/dev/log";
|
||||
|
||||
/// The priority (i.e. severity) of a syslog message.
|
||||
///
|
||||
/// See syslog man pages for information on their semantics.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Priority {
|
||||
Emergency = 0,
|
||||
Alert = 1,
|
||||
Critical = 2,
|
||||
Error = 3,
|
||||
Warning = 4,
|
||||
Notice = 5,
|
||||
Info = 6,
|
||||
Debug = 7,
|
||||
}
|
||||
|
||||
impl fmt::Display for Priority {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Priority::Emergency => write!(f, "EMERGENCY"),
|
||||
Priority::Alert => write!(f, "ALERT"),
|
||||
Priority::Critical => write!(f, "CRITICAL"),
|
||||
Priority::Error => write!(f, "ERROR"),
|
||||
Priority::Warning => write!(f, "WARNING"),
|
||||
Priority::Notice => write!(f, "NOTICE"),
|
||||
Priority::Info => write!(f, "INFO"),
|
||||
Priority::Debug => write!(f, "DEBUG"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The facility of a syslog message.
|
||||
///
|
||||
/// See syslog man pages for information on their semantics.
|
||||
pub enum Facility {
|
||||
Kernel = 0,
|
||||
User = 1 << 3,
|
||||
Mail = 2 << 3,
|
||||
Daemon = 3 << 3,
|
||||
Auth = 4 << 3,
|
||||
Syslog = 5 << 3,
|
||||
Lpr = 6 << 3,
|
||||
News = 7 << 3,
|
||||
Uucp = 8 << 3,
|
||||
Local0 = 16 << 3,
|
||||
Local1 = 17 << 3,
|
||||
Local2 = 18 << 3,
|
||||
Local3 = 19 << 3,
|
||||
Local4 = 20 << 3,
|
||||
Local5 = 21 << 3,
|
||||
Local6 = 22 << 3,
|
||||
Local7 = 23 << 3,
|
||||
}
|
||||
|
||||
/// Errors returned by `syslog::init()`.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Initialization was never attempted.
|
||||
NeverInitialized,
|
||||
/// Initialization has previously failed and can not be retried.
|
||||
Poisoned,
|
||||
/// Error while creating socket.
|
||||
Socket(io::Error),
|
||||
/// Error while attempting to connect socket.
|
||||
Connect(io::Error),
|
||||
// There was an error using `open` to get the lowest file descriptor.
|
||||
GetLowestFd(io::Error),
|
||||
// The guess of libc's file descriptor for the syslog connection was invalid.
|
||||
InvalidFd,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
match self {
|
||||
NeverInitialized => write!(f, "initialization was never attempted"),
|
||||
Poisoned => write!(f, "initialization previously failed and cannot be retried"),
|
||||
Socket(e) => write!(f, "failed to create socket: {}", e),
|
||||
Connect(e) => write!(f, "failed to connect socket: {}", e),
|
||||
GetLowestFd(e) => write!(f, "failed to get lowest file descriptor: {}", e),
|
||||
InvalidFd => write!(f, "guess of fd for syslog connection was invalid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hostname() -> Result<String, ()> {
|
||||
let mut hostname: [u8; 256] = [b'\0'; 256];
|
||||
// Safe because we give a valid pointer to a buffer of the indicated length and check for the
|
||||
// result.
|
||||
let ret = unsafe { gethostname(hostname.as_mut_ptr() as *mut c_char, hostname.len()) };
|
||||
if ret == -1 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let len = hostname.iter().position(|&v| v == b'\0').ok_or(())?;
|
||||
|
||||
Ok(from_utf8(&hostname[..len]).map_err(|_| ())?.to_string())
|
||||
}
|
||||
|
||||
fn get_proc_name() -> Option<String> {
|
||||
env::args_os()
|
||||
.next()
|
||||
.map(PathBuf::from)
|
||||
.and_then(|s| s.file_name().map(OsStr::to_os_string))
|
||||
.map(OsString::into_string)
|
||||
.and_then(Result::ok)
|
||||
}
|
||||
|
||||
// Uses libc's openlog function to get a socket to the syslogger. By getting the socket this way, as
|
||||
// opposed to connecting to the syslogger directly, libc's internal state gets initialized for other
|
||||
// libraries (e.g. minijail) that make use of libc's syslog function. Note that this function
|
||||
// depends on no other threads or signal handlers being active in this process because they might
|
||||
// create FDs.
|
||||
//
|
||||
// TODO(zachr): Once https://android-review.googlesource.com/470998 lands, there won't be any
|
||||
// libraries in use that hard depend on libc's syslogger. Remove this and go back to making the
|
||||
// connection directly once minjail is ready.
|
||||
fn openlog_and_get_socket() -> Result<UnixDatagram, Error> {
|
||||
// closelog first in case there was already a file descriptor open. Safe because it takes no
|
||||
// arguments and just closes an open file descriptor. Does nothing if the file descriptor
|
||||
// was not already open.
|
||||
unsafe {
|
||||
closelog();
|
||||
}
|
||||
|
||||
let file_path = CString::new("/dev/null").unwrap();
|
||||
unsafe {
|
||||
// Ordinarily libc's FD for the syslog connection can't be accessed, but we can guess that the
|
||||
// FD that openlog will be getting is the lowest unused FD. To guarantee that an FD is opened in
|
||||
// this function we use the LOG_NDELAY to tell openlog to connect to the syslog now. To get the
|
||||
// lowest unused FD, we open a dummy file (which the manual says will always return the lowest
|
||||
// fd), and then close that fd. Voilà, we now know the lowest numbered FD. The call to openlog
|
||||
// will make use of that FD, and then we just wrap a `UnixDatagram` around it for ease of use.
|
||||
let fd = libc::open(file_path.as_ptr(), libc::O_RDONLY);
|
||||
if fd < 0 {
|
||||
let err = io::Error::last_os_error();
|
||||
return Err(Error::GetLowestFd(err));
|
||||
}
|
||||
|
||||
// Safe because openlog accesses no pointers because `ident` is null, only valid flags are
|
||||
// used, and it returns no error.
|
||||
openlog(null(), LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER);
|
||||
// For safety, ensure the fd we guessed is valid. The `fcntl` call itself only reads the
|
||||
// file descriptor table of the current process, which is trivially safe.
|
||||
if fcntl(fd, F_GETFD) >= 0 {
|
||||
Ok(UnixDatagram::from_raw_fd(fd))
|
||||
} else {
|
||||
Err(Error::InvalidFd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
stderr: bool,
|
||||
socket: Option<UnixDatagram>,
|
||||
file: Option<File>,
|
||||
hostname: Option<String>,
|
||||
proc_name: Option<String>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> Result<State, Error> {
|
||||
let s = openlog_and_get_socket()?;
|
||||
Ok(State {
|
||||
stderr: true,
|
||||
socket: Some(s),
|
||||
file: None,
|
||||
hostname: get_hostname().ok(),
|
||||
proc_name: get_proc_name(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
static STATE_ONCE: Once = ONCE_INIT;
|
||||
static mut STATE: *const StdMutex<State> = 0 as *const _;
|
||||
|
||||
fn new_mutex_ptr<T>(inner: T) -> *const StdMutex<T> {
|
||||
Box::into_raw(Box::new(StdMutex::new(inner)))
|
||||
}
|
||||
|
||||
/// Initialize the syslog connection and internal variables.
|
||||
///
|
||||
/// This should only be called once per process before any other threads have been spawned or any
|
||||
/// signal handlers have been registered. Every call made after the first will have no effect
|
||||
/// besides return `Ok` or `Err` appropriately.
|
||||
pub fn init() -> Result<(), Error> {
|
||||
let mut err = Error::Poisoned;
|
||||
STATE_ONCE.call_once(|| match State::new() {
|
||||
// Safe because STATE mutation is guarded by `Once`.
|
||||
Ok(state) => unsafe { STATE = new_mutex_ptr(state) },
|
||||
Err(e) => err = e,
|
||||
});
|
||||
|
||||
if unsafe { STATE.is_null() } {
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn lock() -> Result<MutexGuard<'static, State>, Error> {
|
||||
// Safe because we assume that STATE is always in either a valid or NULL state.
|
||||
let state_ptr = unsafe { STATE };
|
||||
if state_ptr.is_null() {
|
||||
return Err(Error::NeverInitialized);
|
||||
}
|
||||
// Safe because STATE only mutates once and we checked for NULL.
|
||||
let state = unsafe { &*state_ptr };
|
||||
let guard = match state.lock() {
|
||||
Ok(guard) => guard,
|
||||
_ => panic!("mutex is poisoned"),
|
||||
};
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
// Attempts to lock and retrieve the state. Returns from the function silently on failure.
|
||||
macro_rules! lock {
|
||||
() => {
|
||||
match lock() {
|
||||
Ok(s) => s,
|
||||
_ => return,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/// Replaces the hostname reported in each syslog message.
|
||||
///
|
||||
/// The default hostname is whatever `gethostname()` returned when `vmm_sys_util::syslog::init()` was first
|
||||
/// called.
|
||||
///
|
||||
/// Does nothing if syslog was never initialized.
|
||||
pub fn set_hostname<T: Into<String>>(hostname: T) {
|
||||
let mut state = lock!();
|
||||
state.hostname = Some(hostname.into());
|
||||
}
|
||||
|
||||
/// Replaces the process name reported in each syslog message.
|
||||
///
|
||||
/// The default process name is the _file name_ of `argv[0]`. For example, if this program was
|
||||
/// invoked as
|
||||
///
|
||||
/// ```bash
|
||||
/// $ path/to/app --delete everything
|
||||
/// ```
|
||||
///
|
||||
/// the default process name would be _app_.
|
||||
///
|
||||
/// Does nothing if syslog was never initialized.
|
||||
pub fn set_proc_name<T: Into<String>>(proc_name: T) {
|
||||
let mut state = lock!();
|
||||
state.proc_name = Some(proc_name.into());
|
||||
}
|
||||
|
||||
/// Enables or disables echoing log messages to the syslog.
|
||||
///
|
||||
/// The default behavior is **enabled**.
|
||||
///
|
||||
/// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
|
||||
/// reopened if `enable` is set to `true` after it became `false`.
|
||||
///
|
||||
/// Returns an error if syslog was never initialized or the syslog connection failed to be
|
||||
/// established.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
|
||||
pub fn echo_syslog(enable: bool) -> Result<(), Error> {
|
||||
let state_ptr = unsafe { STATE };
|
||||
if state_ptr.is_null() {
|
||||
return Err(Error::NeverInitialized);
|
||||
}
|
||||
let mut state = lock().map_err(|_| Error::Poisoned)?;
|
||||
|
||||
match state.socket.take() {
|
||||
Some(_) if enable => {}
|
||||
Some(s) => {
|
||||
// Because `openlog_and_get_socket` actually just "borrows" the syslog FD, this module
|
||||
// does not own the syslog connection and therefore should not destroy it.
|
||||
mem::forget(s);
|
||||
}
|
||||
None if enable => {
|
||||
let s = openlog_and_get_socket()?;
|
||||
state.socket = Some(s);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replaces the optional `File` to echo log messages to.
|
||||
///
|
||||
/// The default behavior is to not echo to a file. Passing `None` to this function restores that
|
||||
/// behavior.
|
||||
///
|
||||
/// Does nothing if syslog was never initialized.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `file` - `Some(file)` to echo to `file`, `None` to disable echoing to the file previously passed to `echo_file`.
|
||||
pub fn echo_file(file: Option<File>) {
|
||||
let mut state = lock!();
|
||||
state.file = file;
|
||||
}
|
||||
|
||||
/// Enables or disables echoing log messages to the `std::io::stderr()`.
|
||||
///
|
||||
/// The default behavior is **enabled**.
|
||||
///
|
||||
/// Does nothing if syslog was never initialized.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `enable` - `true` to enable echoing to stderr, `false` to disable echoing to stderr.
|
||||
pub fn echo_stderr(enable: bool) {
|
||||
let mut state = lock!();
|
||||
state.stderr = enable;
|
||||
}
|
||||
|
||||
/// Retrieves the file descriptors owned by the global syslogger.
|
||||
///
|
||||
/// Does nothing if syslog was never initialized. If their are any file descriptors, they will be
|
||||
/// pushed into `fds`.
|
||||
///
|
||||
/// Note that the `stderr` file descriptor is never added, as it is not owned by syslog.
|
||||
#[allow(clippy::redundant_closure)]
|
||||
pub fn push_fds(fds: &mut Vec<RawFd>) {
|
||||
let state = lock!();
|
||||
fds.extend(state.socket.iter().map(|s| s.as_raw_fd()));
|
||||
fds.extend(state.file.iter().map(|f| f.as_raw_fd()));
|
||||
}
|
||||
|
||||
/// Should only be called after `init()` was called.
|
||||
fn send_buf(socket: &UnixDatagram, buf: &[u8]) {
|
||||
const SEND_RETRY: usize = 2;
|
||||
|
||||
for _ in 0..SEND_RETRY {
|
||||
match socket.send(&buf[..]) {
|
||||
Ok(_) => break,
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::ConnectionRefused
|
||||
| ErrorKind::ConnectionReset
|
||||
| ErrorKind::ConnectionAborted
|
||||
| ErrorKind::NotConnected => {
|
||||
let res = socket.connect(SYSLOG_PATH);
|
||||
if res.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_localtime() -> tm {
|
||||
unsafe {
|
||||
// Safe because tm is just a struct of plain data.
|
||||
let mut tm: tm = mem::zeroed();
|
||||
let mut now: time_t = 0;
|
||||
// Safe because we give time a valid pointer and can never fail.
|
||||
time(&mut now as *mut _);
|
||||
// Safe because we give localtime_r valid pointers and can never fail.
|
||||
localtime_r(&now, &mut tm as *mut _);
|
||||
tm
|
||||
}
|
||||
}
|
||||
|
||||
/// Records a log message with the given details.
|
||||
///
|
||||
/// Note that this will fail silently if syslog was not initialized.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `pri` - The `Priority` (i.e. severity) of the log message.
|
||||
/// * `fac` - The `Facility` of the log message. Usually `Facility::User` should be used.
|
||||
/// * `file_name` - Name of the file that generated the log.
|
||||
/// * `line` - Line number within `file_name` that generated the log.
|
||||
/// * `args` - The log's message to record, in the form of `format_args!()` return value
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use vmm_sys_util::syslog::{init, log, Priority, Facility};
|
||||
/// # fn main() {
|
||||
/// # if let Err(e) = init() {
|
||||
/// # println!("failed to initiailize syslog: {}", e);
|
||||
/// # return;
|
||||
/// # }
|
||||
/// log(Priority::Error,
|
||||
/// Facility::User,
|
||||
/// file!(),
|
||||
/// line!(),
|
||||
/// format_args!("hello syslog"));
|
||||
/// # }
|
||||
/// ```
|
||||
#[allow(clippy::redundant_closure)]
|
||||
pub fn log(pri: Priority, fac: Facility, file_name: &str, line: u32, args: fmt::Arguments) {
|
||||
const MONTHS: [&str; 12] = [
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
||||
];
|
||||
|
||||
let mut state = lock!();
|
||||
let mut buf = [0u8; 1024];
|
||||
if let Some(ref socket) = state.socket {
|
||||
let tm = get_localtime();
|
||||
let prifac = (pri as u8) | (fac as u8);
|
||||
let (res, len) = {
|
||||
let mut buf_cursor = Cursor::new(&mut buf[..]);
|
||||
(
|
||||
write!(
|
||||
&mut buf_cursor,
|
||||
"<{}>{} {:02} {:02}:{:02}:{:02} {} {}[{}]: [{}:{}] {}",
|
||||
prifac,
|
||||
MONTHS[tm.tm_mon as usize],
|
||||
tm.tm_mday,
|
||||
tm.tm_hour,
|
||||
tm.tm_min,
|
||||
tm.tm_sec,
|
||||
state.hostname.as_ref().map(|s| s.as_ref()).unwrap_or("-"),
|
||||
state.proc_name.as_ref().map(|s| s.as_ref()).unwrap_or("-"),
|
||||
unsafe { syscall(SYS_getpid as c_long) as pid_t },
|
||||
//getpid(),
|
||||
file_name,
|
||||
line,
|
||||
args
|
||||
),
|
||||
buf_cursor.position() as usize,
|
||||
)
|
||||
};
|
||||
|
||||
if res.is_ok() {
|
||||
send_buf(&socket, &buf[..len]);
|
||||
}
|
||||
}
|
||||
|
||||
let (res, len) = {
|
||||
let mut buf_cursor = Cursor::new(&mut buf[..]);
|
||||
(
|
||||
writeln!(&mut buf_cursor, "[{}:{}:{}] {}", pri, file_name, line, args),
|
||||
buf_cursor.position() as usize,
|
||||
)
|
||||
};
|
||||
if res.is_ok() {
|
||||
if let Some(ref mut file) = state.file {
|
||||
let _ = file.write_all(&buf[..len]);
|
||||
}
|
||||
if state.stderr {
|
||||
let _ = stderr().write_all(&buf[..len]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A macro for logging at an arbitrary priority level.
|
||||
///
|
||||
/// Note that this will fail silently if syslog was not initialized.
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($pri:expr, $($args:tt)+) => ({
|
||||
$crate::syslog::log($pri, $crate::syslog::Facility::User, file!(), line!(), format_args!($($args)+))
|
||||
})
|
||||
}
|
||||
|
||||
/// A macro for logging an error.
|
||||
///
|
||||
/// Note that this will fail silently if syslog was not initialized.
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($args:tt)+) => (log!($crate::syslog::Priority::Error, $($args)*))
|
||||
}
|
||||
|
||||
/// A macro for logging a warning.
|
||||
///
|
||||
/// Note that this will fail silently if syslog was not initialized.
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($args:tt)+) => (log!($crate::syslog::Priority::Warning, $($args)*))
|
||||
}
|
||||
|
||||
/// A macro for logging info.
|
||||
///
|
||||
/// Note that this will fail silently if syslog was not initialized.
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($($args:tt)+) => (log!($crate::syslog::Priority::Info, $($args)*))
|
||||
}
|
||||
|
||||
/// A macro for logging debug information.
|
||||
///
|
||||
/// Note that this will fail silently if syslog was not initialized.
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($args:tt)+) => (log!($crate::syslog::Priority::Debug, $($args)*))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libc::{shm_open, shm_unlink, O_CREAT, O_EXCL, O_RDWR};
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
#[test]
|
||||
fn test_init_syslog() {
|
||||
init().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fds() {
|
||||
init().unwrap();
|
||||
let mut fds = Vec::new();
|
||||
push_fds(&mut fds);
|
||||
assert!(!fds.is_empty());
|
||||
for fd in fds {
|
||||
assert!(fd >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syslog_log() {
|
||||
init().unwrap();
|
||||
log(
|
||||
Priority::Error,
|
||||
Facility::User,
|
||||
file!(),
|
||||
line!(),
|
||||
format_args!("hello syslog"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proc_name() {
|
||||
init().unwrap();
|
||||
log(
|
||||
Priority::Error,
|
||||
Facility::User,
|
||||
file!(),
|
||||
line!(),
|
||||
format_args!("before proc name"),
|
||||
);
|
||||
set_proc_name("sys_util-test");
|
||||
log(
|
||||
Priority::Error,
|
||||
Facility::User,
|
||||
file!(),
|
||||
line!(),
|
||||
format_args!("after proc name"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::zero_prefixed_literal)]
|
||||
fn test_syslog_file() {
|
||||
init().unwrap();
|
||||
let shm_name = CStr::from_bytes_with_nul(b"/crosvm_shm\0").unwrap();
|
||||
let mut file = unsafe {
|
||||
shm_unlink(shm_name.as_ptr());
|
||||
let fd = shm_open(shm_name.as_ptr(), O_RDWR | O_CREAT | O_EXCL, 0666);
|
||||
assert!(fd >= 0, "error creating shared memory;");
|
||||
File::from_raw_fd(fd)
|
||||
};
|
||||
|
||||
let syslog_file = file.try_clone().expect("error cloning shared memory file");
|
||||
echo_file(Some(syslog_file));
|
||||
|
||||
const TEST_STR: &str = "hello shared memory file";
|
||||
log(
|
||||
Priority::Error,
|
||||
Facility::User,
|
||||
file!(),
|
||||
line!(),
|
||||
format_args!("{}", TEST_STR),
|
||||
);
|
||||
|
||||
file.seek(SeekFrom::Start(0))
|
||||
.expect("error seeking shared memory file");
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)
|
||||
.expect("error reading shared memory file");
|
||||
assert!(buf.contains(TEST_STR));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macros() {
|
||||
init().unwrap();
|
||||
error!("this is an error {}", 3);
|
||||
warn!("this is a warning {}", "uh oh");
|
||||
info!("this is info {}", true);
|
||||
debug!("this is debug info {:?}", Some("helpful stuff"));
|
||||
}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use libc;
|
||||
|
||||
use crate::{errno_result, Result};
|
||||
|
||||
/// Create and remove a temporary directory. The directory will be maintained for the lifetime of
|
||||
/// the `TempDir` object.
|
||||
pub struct TempDir {
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl TempDir {
|
||||
/// Creates a new tempory directory.
|
||||
/// The directory will be removed when the object goes out of scope.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use std::path::Path;
|
||||
/// # use std::path::PathBuf;
|
||||
/// # use vmm_sys_util::TempDir;
|
||||
/// # fn test_create_temp_dir() -> Result<(), ()> {
|
||||
/// let t = TempDir::new("/tmp/testdir").map_err(|_| ())?;
|
||||
/// assert!(t.as_path().unwrap().exists());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new<P: AsRef<OsStr>>(prefix: P) -> Result<TempDir> {
|
||||
let mut dir_string = prefix.as_ref().to_os_string();
|
||||
dir_string.push("XXXXXX");
|
||||
// unwrap this result as the internal bytes can't have a null with a valid path.
|
||||
let dir_name = CString::new(dir_string.into_vec()).unwrap();
|
||||
let mut dir_bytes = dir_name.into_bytes_with_nul();
|
||||
let ret = unsafe {
|
||||
// Creating the directory isn't unsafe. The fact that it modifies the guts of the path
|
||||
// is also OK because it only overwrites the last 6 Xs added above.
|
||||
libc::mkdtemp(dir_bytes.as_mut_ptr() as *mut libc::c_char)
|
||||
};
|
||||
if ret.is_null() {
|
||||
return errno_result();
|
||||
}
|
||||
dir_bytes.pop(); // Remove the null becasue from_vec can't handle it.
|
||||
Ok(TempDir {
|
||||
path: Some(PathBuf::from(OsString::from_vec(dir_bytes))),
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes the temporary directory. Calling this is optional as dropping a `TempDir` object
|
||||
/// will also remove the directory. Calling remove explicitly allows for better error handling.
|
||||
pub fn remove(mut self) -> Result<()> {
|
||||
let path = self.path.take();
|
||||
path.map_or(Ok(()), fs::remove_dir_all)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the path to the tempdir if it is currently valid
|
||||
pub fn as_path(&self) -> Option<&Path> {
|
||||
self.path.as_ref().map(|ref p| p.as_path())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
if let Some(ref p) = self.path {
|
||||
// Nothing can be done here if this returns an error.
|
||||
let _ = fs::remove_dir_all(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_dir() {
|
||||
let t = TempDir::new("/tmp/asdf").unwrap();
|
||||
let path = t.as_path().unwrap();
|
||||
assert!(path.exists());
|
||||
assert!(path.is_dir());
|
||||
assert!(path.starts_with("/tmp/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_dir() {
|
||||
let t = TempDir::new("/tmp/asdf").unwrap();
|
||||
let path = t.as_path().unwrap().to_owned();
|
||||
assert!(t.remove().is_ok());
|
||||
assert!(!path.exists());
|
||||
}
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::io::StdinLock;
|
||||
use std::mem::zeroed;
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use libc::{
|
||||
c_int, fcntl, isatty, read, tcgetattr, tcsetattr, termios, ECHO, F_GETFL, F_SETFL, ICANON,
|
||||
ISIG, O_NONBLOCK, STDIN_FILENO, TCSANOW,
|
||||
};
|
||||
|
||||
use crate::errno::{errno_result, Result};
|
||||
|
||||
fn modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()> {
|
||||
// Safe because we check the return value of isatty.
|
||||
if unsafe { isatty(fd) } != 1 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// The following pair are safe because termios gets totally overwritten by tcgetattr and we
|
||||
// check the return result.
|
||||
let mut termios: termios = unsafe { zeroed() };
|
||||
let ret = unsafe { tcgetattr(fd, &mut termios as *mut _) };
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
let mut new_termios = termios;
|
||||
f(&mut new_termios);
|
||||
// Safe because the syscall will only read the extent of termios and we check the return result.
|
||||
let ret = unsafe { tcsetattr(fd, TCSANOW, &new_termios as *const _) };
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_flags(fd: RawFd) -> Result<c_int> {
|
||||
// Safe because no third parameter is expected and we check the return result.
|
||||
let ret = unsafe { fcntl(fd, F_GETFL) };
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn set_flags(fd: RawFd, flags: c_int) -> Result<()> {
|
||||
// Safe because we supply the third parameter and we check the return result.
|
||||
let ret = unsafe { fcntl(fd, F_SETFL, flags) };
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Trait for file descriptors that are TTYs, according to `isatty(3)`.
|
||||
///
|
||||
/// This is marked unsafe because the implementation must promise that the returned RawFd is a valid
|
||||
/// fd and that the lifetime of the returned fd is at least that of the trait object.
|
||||
pub unsafe trait Terminal {
|
||||
/// Gets the file descriptor of the TTY.
|
||||
fn tty_fd(&self) -> RawFd;
|
||||
|
||||
/// Set this terminal's mode to canonical mode (`ICANON | ECHO | ISIG`).
|
||||
fn set_canon_mode(&self) -> Result<()> {
|
||||
modify_mode(self.tty_fd(), |t| t.c_lflag |= ICANON | ECHO | ISIG)
|
||||
}
|
||||
|
||||
/// Set this terminal's mode to raw mode (`!(ICANON | ECHO | ISIG)`).
|
||||
fn set_raw_mode(&self) -> Result<()> {
|
||||
modify_mode(self.tty_fd(), |t| t.c_lflag &= !(ICANON | ECHO | ISIG))
|
||||
}
|
||||
|
||||
/// Sets the non-blocking mode of this terminal's file descriptor.
|
||||
///
|
||||
/// If `non_block` is `true`, then `read_raw` will not block. If `non_block` is `false`, then
|
||||
/// `read_raw` may block if there is nothing to read.
|
||||
fn set_non_block(&self, non_block: bool) -> Result<()> {
|
||||
let old_flags = get_flags(self.tty_fd())?;
|
||||
let new_flags = if non_block {
|
||||
old_flags | O_NONBLOCK
|
||||
} else {
|
||||
old_flags & !O_NONBLOCK
|
||||
};
|
||||
if new_flags != old_flags {
|
||||
set_flags(self.tty_fd(), new_flags)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads up to `out.len()` bytes from this terminal without any buffering.
|
||||
///
|
||||
/// This may block, depending on if non-blocking was enabled with `set_non_block` or if there
|
||||
/// are any bytes to read. If there is at least one byte that is readable, this will not block.
|
||||
fn read_raw(&self, out: &mut [u8]) -> Result<usize> {
|
||||
// Safe because read will only modify the pointer up to the length we give it and we check
|
||||
// the return result.
|
||||
let ret = unsafe { read(self.tty_fd(), out.as_mut_ptr() as *mut _, out.len()) };
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
|
||||
Ok(ret as usize)
|
||||
}
|
||||
}
|
||||
|
||||
// Safe because we return a genuine terminal fd that never changes and shares our lifetime.
|
||||
unsafe impl<'a> Terminal for StdinLock<'a> {
|
||||
fn tty_fd(&self) -> RawFd {
|
||||
STDIN_FILENO
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
|
||||
unsafe impl Terminal for File {
|
||||
fn tty_fd(&self) -> RawFd {
|
||||
self.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a_tty() {
|
||||
let stdin_handle = io::stdin();
|
||||
let stdin = stdin_handle.lock();
|
||||
|
||||
assert!(stdin.set_canon_mode().is_ok());
|
||||
assert!(stdin.set_raw_mode().is_ok());
|
||||
assert!(stdin.set_raw_mode().is_ok());
|
||||
assert!(stdin.set_canon_mode().is_ok());
|
||||
assert!(stdin.set_non_block(true).is_ok());
|
||||
let mut out = [0u8; 0];
|
||||
assert!(stdin.read_raw(&mut out[..]).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a_non_tty() {
|
||||
let file = File::open(Path::new("/dev/zero")).unwrap();
|
||||
assert!(file.set_canon_mode().is_ok());
|
||||
}
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-clause file.
|
||||
|
||||
use std::fs::File;
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::ptr;
|
||||
use std::time::Duration;
|
||||
|
||||
use libc::{self, timerfd_create, timerfd_gettime, timerfd_settime, CLOCK_MONOTONIC, TFD_CLOEXEC};
|
||||
|
||||
use crate::errno::{errno_result, Result};
|
||||
|
||||
/// A safe wrapper around a Linux timerfd (man 2 timerfd_create).
|
||||
pub struct TimerFd(File);
|
||||
|
||||
impl TimerFd {
|
||||
/// Creates a new [`TimerFd`](struct.TimerFd.html).
|
||||
///
|
||||
/// The timer is initally disarmed and must be armed by calling [`reset`](fn.reset.html).
|
||||
pub fn new() -> Result<TimerFd> {
|
||||
// Safe because this doesn't modify any memory and we check the return value.
|
||||
let ret = unsafe { timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC) };
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
|
||||
// Safe because we uniquely own the file descriptor.
|
||||
Ok(TimerFd(unsafe { File::from_raw_fd(ret) }))
|
||||
}
|
||||
|
||||
/// Sets the timer to expire after `dur`.
|
||||
///
|
||||
/// If `interval` is not `None` it represents the period for repeated expirations after the
|
||||
/// initial expiration. Otherwise the timer will expire just once. Cancels any existing duration and repeating interval.
|
||||
pub fn reset(&mut self, dur: Duration, interval: Option<Duration>) -> Result<()> {
|
||||
// Safe because we are zero-initializing a struct with only primitive member fields.
|
||||
let mut spec: libc::itimerspec = unsafe { mem::zeroed() };
|
||||
spec.it_value.tv_sec = dur.as_secs() as libc::time_t;
|
||||
// nsec always fits in i32 because subsec_nanos is defined to be less than one billion.
|
||||
let nsec = dur.subsec_nanos() as i32;
|
||||
spec.it_value.tv_nsec = libc::c_long::from(nsec);
|
||||
|
||||
if let Some(int) = interval {
|
||||
spec.it_interval.tv_sec = int.as_secs() as libc::time_t;
|
||||
// nsec always fits in i32 because subsec_nanos is defined to be less than one billion.
|
||||
let nsec = int.subsec_nanos() as i32;
|
||||
spec.it_interval.tv_nsec = libc::c_long::from(nsec);
|
||||
}
|
||||
|
||||
// Safe because this doesn't modify any memory and we check the return value.
|
||||
let ret = unsafe { timerfd_settime(self.as_raw_fd(), 0, &spec, ptr::null_mut()) };
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits until the timer expires.
|
||||
///
|
||||
/// The return value represents the number of times the timer
|
||||
/// has expired since the last time `wait` was called. If the timer has not yet expired once
|
||||
/// this call will block until it does.
|
||||
pub fn wait(&mut self) -> Result<u64> {
|
||||
let mut count = 0u64;
|
||||
|
||||
// Safe because this will only modify |buf| and we check the return value.
|
||||
let ret = unsafe {
|
||||
libc::read(
|
||||
self.as_raw_fd(),
|
||||
&mut count as *mut _ as *mut libc::c_void,
|
||||
mem::size_of_val(&count),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
|
||||
// The bytes in the buffer are guaranteed to be in native byte-order so we don't need to
|
||||
// use from_le or from_be.
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Returns `true` if the timer is currently armed.
|
||||
pub fn is_armed(&self) -> Result<bool> {
|
||||
// Safe because we are zero-initializing a struct with only primitive member fields.
|
||||
let mut spec: libc::itimerspec = unsafe { mem::zeroed() };
|
||||
|
||||
// Safe because timerfd_gettime is trusted to only modify `spec`.
|
||||
let ret = unsafe { timerfd_gettime(self.as_raw_fd(), &mut spec) };
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
|
||||
Ok(spec.it_value.tv_sec != 0 || spec.it_value.tv_nsec != 0)
|
||||
}
|
||||
|
||||
/// Disarms the timer.
|
||||
pub fn clear(&mut self) -> Result<()> {
|
||||
// Safe because we are zero-initializing a struct with only primitive member fields.
|
||||
let spec: libc::itimerspec = unsafe { mem::zeroed() };
|
||||
|
||||
// Safe because this doesn't modify any memory and we check the return value.
|
||||
let ret = unsafe { timerfd_settime(self.as_raw_fd(), 0, &spec, ptr::null_mut()) };
|
||||
if ret < 0 {
|
||||
return errno_result();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for TimerFd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRawFd for TimerFd {
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||
TimerFd(File::from_raw_fd(fd))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for TimerFd {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.0.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[test]
|
||||
fn test_one_shot() {
|
||||
let mut tfd = TimerFd::new().expect("failed to create timerfd");
|
||||
assert_eq!(tfd.is_armed().unwrap(), false);
|
||||
|
||||
let dur = Duration::from_millis(200);
|
||||
let now = Instant::now();
|
||||
tfd.reset(dur, None).expect("failed to arm timer");
|
||||
|
||||
assert_eq!(tfd.is_armed().unwrap(), true);
|
||||
|
||||
let count = tfd.wait().expect("unable to wait for timer");
|
||||
|
||||
assert_eq!(count, 1);
|
||||
assert!(now.elapsed() >= dur);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repeating() {
|
||||
let mut tfd = TimerFd::new().expect("failed to create timerfd");
|
||||
|
||||
let dur = Duration::from_millis(200);
|
||||
let interval = Duration::from_millis(100);
|
||||
tfd.reset(dur, Some(interval)).expect("failed to arm timer");
|
||||
|
||||
sleep(dur * 3);
|
||||
|
||||
let count = tfd.wait().expect("unable to wait for timer");
|
||||
assert!(count >= 5, "count = {}", count);
|
||||
}
|
||||
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
use std::cmp::min;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::fallocate;
|
||||
use crate::FallocateMode;
|
||||
|
||||
/// A trait for deallocating space in a file.
|
||||
pub trait PunchHole {
|
||||
/// Replace a range of bytes with a hole.
|
||||
fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl PunchHole for File {
|
||||
fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
|
||||
fallocate(self, FallocateMode::PunchHole, true, offset, length as u64)
|
||||
.map_err(|e| io::Error::from_raw_os_error(e.errno()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for writing zeroes to a stream.
|
||||
pub trait WriteZeroes {
|
||||
/// Write `length` bytes of zeroes to the stream, returning how many bytes were written.
|
||||
fn write_zeroes(&mut self, length: usize) -> io::Result<usize>;
|
||||
}
|
||||
|
||||
impl<T: PunchHole + Seek + Write> WriteZeroes for T {
|
||||
fn write_zeroes(&mut self, length: usize) -> io::Result<usize> {
|
||||
// Try to punch a hole first.
|
||||
let offset = self.seek(SeekFrom::Current(0))?;
|
||||
if let Ok(()) = self.punch_hole(offset, length as u64) {
|
||||
// Advance the seek cursor as if we had done a real write().
|
||||
self.seek(SeekFrom::Current(length as i64))?;
|
||||
return Ok(length);
|
||||
}
|
||||
|
||||
// fall back to write()
|
||||
|
||||
// punch_hole() failed; fall back to writing a buffer of zeroes
|
||||
// until we have written up to length.
|
||||
let buf_size = min(length, 0x10000);
|
||||
let buf = vec![0u8; buf_size];
|
||||
let mut nwritten: usize = 0;
|
||||
while nwritten < length {
|
||||
let remaining = length - nwritten;
|
||||
let write_size = min(remaining, buf_size);
|
||||
nwritten += self.write(&buf[0..write_size])?;
|
||||
}
|
||||
Ok(length)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unused_io_amount)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::TempDir;
|
||||
|
||||
#[test]
|
||||
fn simple_test() {
|
||||
let tempdir = TempDir::new("/tmp/write_zeroes_test").unwrap();
|
||||
let mut path = PathBuf::from(tempdir.as_path().unwrap());
|
||||
path.push("file");
|
||||
let mut f = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)
|
||||
.unwrap();
|
||||
f.set_len(16384).unwrap();
|
||||
|
||||
// Write buffer of non-zero bytes to offset 1234
|
||||
let orig_data = [0x55u8; 5678];
|
||||
f.seek(SeekFrom::Start(1234)).unwrap();
|
||||
f.write(&orig_data).unwrap();
|
||||
|
||||
// Read back the data plus some overlap on each side
|
||||
let mut readback = [0u8; 16384];
|
||||
f.seek(SeekFrom::Start(0)).unwrap();
|
||||
f.read(&mut readback).unwrap();
|
||||
// Bytes before the write should still be 0
|
||||
for read in readback[0..1234].iter() {
|
||||
assert_eq!(*read, 0);
|
||||
}
|
||||
// Bytes that were just written should be 0x55
|
||||
for read in readback[1234..(1234 + 5678)].iter() {
|
||||
assert_eq!(*read, 0x55);
|
||||
}
|
||||
// Bytes after the written area should still be 0
|
||||
for read in readback[(1234 + 5678)..].iter() {
|
||||
assert_eq!(*read, 0);
|
||||
}
|
||||
|
||||
// Overwrite some of the data with zeroes
|
||||
f.seek(SeekFrom::Start(2345)).unwrap();
|
||||
f.write_zeroes(4321).expect("write_zeroes failed");
|
||||
// Verify seek position after write_zeroes()
|
||||
assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 2345 + 4321);
|
||||
|
||||
// Read back the data and verify that it is now zero
|
||||
f.seek(SeekFrom::Start(0)).unwrap();
|
||||
f.read(&mut readback).unwrap();
|
||||
// Bytes before the write should still be 0
|
||||
for read in readback[0..1234].iter() {
|
||||
assert_eq!(*read, 0);
|
||||
}
|
||||
// Original data should still exist before the write_zeroes region
|
||||
for read in readback[1234..2345].iter() {
|
||||
assert_eq!(*read, 0x55);
|
||||
}
|
||||
// The write_zeroes region should now be zero
|
||||
for read in readback[2345..(2345 + 4321)].iter() {
|
||||
assert_eq!(*read, 0);
|
||||
}
|
||||
// Original data should still exist after the write_zeroes region
|
||||
for read in readback[(2345 + 4321)..(1234 + 5678)].iter() {
|
||||
assert_eq!(*read, 0x55);
|
||||
}
|
||||
// The rest of the file should still be 0
|
||||
for read in readback[(1234 + 5678)..].iter() {
|
||||
assert_eq!(*read, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_write_zeroes() {
|
||||
let tempdir = TempDir::new("/tmp/write_zeroes_test").unwrap();
|
||||
let mut path = PathBuf::from(tempdir.as_path().unwrap());
|
||||
path.push("file");
|
||||
let mut f = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)
|
||||
.unwrap();
|
||||
f.set_len(16384).unwrap();
|
||||
|
||||
// Write buffer of non-zero bytes
|
||||
let orig_data = [0x55u8; 0x20000];
|
||||
f.seek(SeekFrom::Start(0)).unwrap();
|
||||
f.write(&orig_data).unwrap();
|
||||
|
||||
// Overwrite some of the data with zeroes
|
||||
f.seek(SeekFrom::Start(0)).unwrap();
|
||||
f.write_zeroes(0x10001).expect("write_zeroes failed");
|
||||
// Verify seek position after write_zeroes()
|
||||
assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 0x10001);
|
||||
|
||||
// Read back the data and verify that it is now zero
|
||||
let mut readback = [0u8; 0x20000];
|
||||
f.seek(SeekFrom::Start(0)).unwrap();
|
||||
f.read(&mut readback).unwrap();
|
||||
// The write_zeroes region should now be zero
|
||||
for read in readback[0..0x10001].iter() {
|
||||
assert_eq!(*read, 0);
|
||||
}
|
||||
// Original data should still exist after the write_zeroes region
|
||||
for read in readback[0x10001..0x20000].iter() {
|
||||
assert_eq!(*read, 0x55);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ pci = { path = "../pci" }
|
||||
tempfile = ">=3.0.2"
|
||||
virtio-bindings = { path = "../virtio-bindings" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vmm-sys-util = { git = "https://github.com/liujing2/vmm-sys-util" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
|
@ -18,7 +18,7 @@ qcow = { path = "../qcow" }
|
||||
linux-loader = { git = "https://github.com/bjzhjing/linux-loader" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vmm-sys-util = { git = "https://github.com/liujing2/vmm-sys-util" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
|
Loading…
Reference in New Issue
Block a user