procfs-0.14.2/.cargo_vcs_info.json0000644000000001360000000000100124140ustar { "git": { "sha1": "6f495a8fe71064dfc94221d1177286f85e7a1e6c" }, "path_in_vcs": "" }procfs-0.14.2/.github/workflows/rust.yml000064400000000000000000000044321046102023000163240ustar 00000000000000name: Rust on: push: pull_request: schedule: - cron: 5 16 * * 3 jobs: build: strategy: fail-fast: false matrix: toolchain: ["1.48.0", "stable", "beta", "nightly"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install toolchains uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} - name: Build run: cargo +${{ matrix.toolchain }} build --verbose - name: Pin plotters if: matrix.toolchain == '1.48.0' run: cargo update -p plotters --precise 0.3.1 - name: Pin once_cell if: matrix.toolchain == '1.48.0' run: cargo update -p once_cell --precise 1.14.0 - name: Pin rayon if: matrix.toolchain == '1.48.0' run: cargo update -p rayon --precise 1.5.3 - name: Pin rayon-core if: matrix.toolchain == '1.48.0' run: cargo update -p rayon-core --precise 1.9.3 - name: Run tests run: cargo +${{ matrix.toolchain }} test --all-features --verbose -- --skip _runsinglethread - name: Run tests (single-threaded tests) run: cargo +${{ matrix.toolchain }} test --all-features --verbose -- _runsinglethread --test-threads 1 - name: Build docs (all features) run: cargo +${{ matrix.toolchain }} doc --all-features - name: Build docs run: cargo +${{ matrix.toolchain }} doc check: strategy: fail-fast: false matrix: toolchain: ["1.48.0", "stable", "beta", "nightly"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install toolchains (aarch64) uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} target: aarch64-linux-android - name: Install toolchains (i686) uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} target: i686-unknown-linux-gnu - name: Pin once_cell if: matrix.toolchain == '1.48.0' run: cargo update -p once_cell --precise 1.14.0 - name: cargo check (aarch64) run: cargo +${{ matrix.toolchain }} check --target aarch64-linux-android --all-features - name: cargo check (i686) run: cargo +${{ matrix.toolchain }} check --target i686-unknown-linux-gnu --all-features procfs-0.14.2/.gitignore000064400000000000000000000000601046102023000131700ustar 00000000000000/target **/*.rs.bk Cargo.lock .idea/ lcov.info procfs-0.14.2/COPYRIGHT.txt000064400000000000000000000562741046102023000133330ustar 00000000000000The source code for the procfs library is copyright by Andrew Chin, 2019, and other contributors. It is icensed under either of * Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0 * MIT license, http://opensource.org/licenses/MIT at your option. The documentation of this library is derived from documentation written by others: * The proc(5) man page: Copyright (C) 1994, 1995 by Daniel Quinlan (quinlan@yggdrasil.com) and Copyright (C) 2002-2008,2017 Michael Kerrisk with networking additions from Alan Cox (A.Cox@swansea.ac.uk) and scsi additions from Michael Neuffer (neuffer@mail.uni-mainz.de) and sysctl additions from Andries Brouwer (aeb@cwi.nl) and System V IPC (as well as various other) additions from Michael Kerrisk Under the GPL Free Documentation License (reproduced below). * Other manual pages: Copyright (c) 2006, 2008 by Michael Kerrisk Under the following license: Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies. Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one. Since the Linux kernel and libraries are constantly changing, this manual page may be incorrect or out-of-date. The author(s) assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein. The author(s) may not have taken the same level of care in the production of this manual, which is licensed free of charge, as they might when working professionally. Formatted or processed versions of this manual, if unaccompanied by the source, must acknowledge the copyright and authors of this work. * The Linux Documentation Project: Copyright 2003 Binh Nguyen Under the GPL Free Documenation License. See: http://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/ln14.html ================================== Below is a copy of the GPL license: This is free documentation; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU General Public License's references to "object code" and "executables" are to be interpreted as the output of any document formatting or typesetting system, including intermediate and printed output. This manual is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this manual; if not, see . ================================== A full copy of the GNU Free Documentation License, version 1.2, can be found here: https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt Below is a copy of this license: GNU Free Documentation License Version 1.2, November 2002 Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 0. PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. 1. APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. 2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. 3. COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. 4. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modified Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. H. Include an unaltered copy of this License. I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version. N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. 5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements". 6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate. 8. TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. 9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See https://www.gnu.org/licenses/. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. ADDENDUM: How to use this License for your documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this: with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. procfs-0.14.2/Cargo.lock0000644000000641040000000000100103740ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide 0.5.4", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "num-integer", "num-traits", "winapi", ] [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", "textwrap", "unicode-width", ] [[package]] name = "codespan-reporting" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", "unicode-width", ] [[package]] name = "core-foundation-sys" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "criterion" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", "clap", "criterion-plot", "csv", "itertools", "lazy_static", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_cbor", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-channel" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", ] [[package]] name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", "itoa 0.4.8", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ "memchr", ] [[package]] name = "cxx" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" dependencies = [ "cc", "cxxbridge-flags", "cxxbridge-macro", "link-cplusplus", ] [[package]] name = "cxx-build" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" dependencies = [ "cc", "codespan-reporting", "once_cell", "proc-macro2", "quote", "scratch", "syn", ] [[package]] name = "cxxbridge-flags" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" [[package]] name = "cxxbridge-macro" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", "winapi", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "failure" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" dependencies = [ "backtrace", "failure_derive", ] [[package]] name = "failure_derive" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "flate2" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide 0.6.2", ] [[package]] name = "gimli" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "iana-time-zone" version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "winapi", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ "cxx", "cxx-build", ] [[package]] name = "io-lifetimes" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" dependencies = [ "libc", "windows-sys", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "link-cplusplus" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" dependencies = [ "cc", ] [[package]] name = "linux-raw-sys" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] [[package]] name = "miniz_oxide" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "nom" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "object" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "plotters" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" dependencies = [ "plotters-backend", ] [[package]] name = "proc-macro2" version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] [[package]] name = "procfs" version = "0.14.2" dependencies = [ "backtrace", "bitflags", "byteorder", "chrono", "criterion", "failure", "flate2", "hex", "lazy_static", "procinfo", "rustix", "serde", ] [[package]] name = "procinfo" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab1427f3d2635891f842892dda177883dca0639e05fe66796a62c9d2f23b49c" dependencies = [ "byteorder", "libc", "nom", "rustc_version", ] [[package]] name = "quote" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" dependencies = [ "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "regex" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "regex-syntax", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "rustc-demangle" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ "semver-parser", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" dependencies = [ "serde_derive", ] [[package]] name = "serde_cbor" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", "serde", ] [[package]] name = "serde_derive" version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa 1.0.4", "ryu", "serde", ] [[package]] name = "syn" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", "syn", "unicode-xid", ] [[package]] name = "termcolor" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "walkdir" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi", "winapi-util", ] [[package]] name = "wasm-bindgen" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" procfs-0.14.2/Cargo.toml0000644000000034410000000000100104140ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.48" name = "procfs" version = "0.14.2" authors = ["Andrew Chin "] description = "Interface to the linux procfs pseudo-filesystem" documentation = "https://docs.rs/procfs/" readme = "README.md" keywords = [ "procfs", "proc", "linux", "process", ] categories = [ "os::unix-apis", "filesystem", ] license = "MIT OR Apache-2.0" repository = "https://github.com/eminence/procfs" [package.metadata.docs.rs] all-features = true [[bench]] name = "cpuinfo" harness = false [dependencies.backtrace] version = "0.3" optional = true [dependencies.bitflags] version = "1.2" [dependencies.byteorder] version = "1.2.3" features = ["i128"] [dependencies.chrono] version = "0.4.20" features = ["clock"] optional = true default-features = false [dependencies.flate2] version = "1.0.3" optional = true [dependencies.hex] version = "0.4" [dependencies.lazy_static] version = "1.0.2" [dependencies.rustix] version = "0.36.0" features = [ "fs", "process", "param", "thread", ] [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dev-dependencies.criterion] version = "0.3" [dev-dependencies.failure] version = "0.1" [dev-dependencies.procinfo] version = "0.4.2" [features] default = [ "chrono", "flate2", ] serde1 = ["serde"] procfs-0.14.2/Cargo.toml.orig000064400000000000000000000021401046102023000140700ustar 00000000000000[package] name = "procfs" version = "0.14.2" authors = ["Andrew Chin "] repository = "https://github.com/eminence/procfs" documentation = "https://docs.rs/procfs/" description = "Interface to the linux procfs pseudo-filesystem" readme = "README.md" keywords = ["procfs", "proc", "linux", "process"] categories = ["os::unix-apis", "filesystem"] license = "MIT OR Apache-2.0" edition = "2018" rust-version = "1.48" [features] default = ["chrono", "flate2"] serde1 = ["serde"] [dependencies] rustix = { version = "0.36.0", features = ["fs", "process", "param", "thread"] } bitflags = "1.2" lazy_static = "1.0.2" chrono = {version = "0.4.20", optional = true, features = ["clock"], default-features = false } byteorder = {version="1.2.3", features=["i128"]} hex = "0.4" flate2 = { version = "1.0.3", optional = true } backtrace = { version = "0.3", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] criterion = "0.3" procinfo = "0.4.2" failure = "0.1" [package.metadata.docs.rs] all-features = true [[bench]] name = "cpuinfo" harness = false procfs-0.14.2/LICENSE-APACHE000064400000000000000000000261361046102023000131400ustar 00000000000000 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. procfs-0.14.2/LICENSE-MIT000064400000000000000000000020511046102023000126360ustar 00000000000000Copyright (c) 2015 The procfs Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. procfs-0.14.2/README.md000064400000000000000000000077701046102023000124760ustar 00000000000000procfs ====== [![Crate](https://img.shields.io/crates/v/procfs.svg)](https://crates.io/crates/procfs) [![Docs](https://docs.rs/procfs/badge.svg)](https://docs.rs/procfs) [![Minimum rustc version](https://img.shields.io/badge/rustc-1.48+-lightgray.svg)](https://github.com/eminence/procfs#minimum-rust-version) This crate is an interface to the `proc` pseudo-filesystem on linux, which is normally mounted as `/proc`. Long-term, this crate aims to be fairly feature complete, but at the moment not all files are exposed. See the docs for info on what's supported, or view the [support.md](https://github.com/eminence/procfs/blob/master/support.md) file in the code repository. ## Examples There are several examples in the docs and in the [examples folder](https://github.com/eminence/procfs/tree/master/examples) of the code repository. Here's a small example that prints out all processes that are running on the same tty as the calling process. This is very similar to what "ps" does in its default mode: ```rust fn main() { let me = procfs::process::Process::myself().unwrap(); let me_stat = me.stat().unwrap(); let tps = procfs::ticks_per_second().unwrap(); println!("{: >5} {: <8} {: >8} {}", "PID", "TTY", "TIME", "CMD"); let tty = format!("pty/{}", me_stat.tty_nr().1); for prc in procfs::process::all_processes().unwrap() { let prc = prc.unwrap(); let stat = prc.stat().unwrap(); if stat.tty_nr == me_stat.tty_nr { // total_time is in seconds let total_time = (stat.utime + stat.stime) as f32 / (tps as f32); println!( "{: >5} {: <8} {: >8} {}", stat.pid, tty, total_time, stat.comm ); } } } ``` Here's another example that shows how to get the current memory usage of the current process: ```rust use procfs::process::Process; fn main() { let me = Process::myself().unwrap(); let me_stat = me.stat().unwrap(); println!("PID: {}", me.pid); let page_size = procfs::page_size().unwrap() as u64; println!("Memory page size: {}", page_size); println!("== Data from /proc/self/stat:"); println!("Total virtual memory used: {} bytes", me_stat.vsize); println!("Total resident set: {} pages ({} bytes)", me_stat.rss, me_stat.rss as u64 * page_size); } ``` There are a few ways to get this data, so also checkout the longer [self_memory](https://github.com/eminence/procfs/blob/master/examples/self_memory.rs) example for more details. ## Cargo features The following cargo features are available: * `chrono` -- Default. Optional. This feature enables a few methods that return values as `DateTime` objects. * `flate2` -- Default. Optional. This feature enables parsing gzip compressed `/proc/config.gz` file via the `procfs::kernel_config` method. * `backtrace` -- Optional. This feature lets you get a stack trace whenever an `InternalError` is raised. * `serde1` -- Optional. This feature allows most structs to be serialized and deserialized using serde 1.0. Note, this feature requires a version of rust newer than 1.48.0 (which is the MSRV for procfs). The exact version required is not specified here, since serde does not not have an MSRV policy. ## Minimum Rust Version This crate requires a minimum rust version of 1.48.0 (2020-11-19). ## License The procfs library is licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. For additional copyright information regarding documentation, please also see the COPYRIGHT.txt file. ### Contribution Contributions are welcome, especially in the areas of documentation and testing on older kernels. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. procfs-0.14.2/benches/cpuinfo.rs000064400000000000000000000013671046102023000146330ustar 00000000000000use criterion::{black_box, criterion_group, criterion_main, Criterion}; use procfs::CpuInfo; fn bench_cpuinfo(c: &mut Criterion) { c.bench_function("CpuInfo::new", |b| b.iter(|| black_box(CpuInfo::new().unwrap()))); let cpuinfo = black_box(CpuInfo::new().unwrap()); c.bench_function("CpuInfo::get_info", |b| b.iter(|| black_box(cpuinfo.get_info(0)))); c.bench_function("CpuInfo::model_name", |b| b.iter(|| cpuinfo.model_name(0))); c.bench_function("CpuInfo::vendor_id", |b| b.iter(|| cpuinfo.vendor_id(0))); c.bench_function("CpuInfo::physical_id", |b| b.iter(|| cpuinfo.physical_id(0))); c.bench_function("CpuInfo::flags", |b| b.iter(|| cpuinfo.flags(0))); } criterion_group!(benches, bench_cpuinfo); criterion_main!(benches); procfs-0.14.2/build.rs000064400000000000000000000007061046102023000126540ustar 00000000000000fn main() { // Filters are extracted from `libc` filters let target_os = std::env::var("CARGO_CFG_TARGET_OS").expect("Missing CARGO_CFG_TARGET_OS envvar"); if !["android", "linux", "l4re"].contains(&target_os.as_str()) { eprintln!("Building procfs on an for a unsupported platform. Currently only linux and android are supported"); eprintln!("(Your current target_os is {})", target_os); std::process::exit(1) } } procfs-0.14.2/clippy.toml000064400000000000000000000000151046102023000133750ustar 00000000000000msrv = "1.48"procfs-0.14.2/examples/README.md000064400000000000000000000117051046102023000143050ustar 00000000000000# Examples These examples can be run by running `cargo run --example example_name` ## dump.rs Prints out details about the current process (the dumper itself), or a process specifed by PID ## interface_stats.rs Runs continually and prints out how many bytes/packets are sent/received. Press ctrl-c to exit the example: ```text Interface: bytes recv bytes sent ================ ==================== ==================== br-883c4c992deb: 823307769 0.2 kbps 1537694158 0.5 kbps br-d73af6e6d094: 9137600399 0.9 kbps 2334717319 0.4 kbps docker0: 2938964881 0.6 kbps 19291691656 11.4 kbps docker_gwbridge: 1172300 0.0 kbps 15649536 0.0 kbps enp5s0f0: 44643307888420 5599.8 kbps 1509415976135 99.0 kbps enp5s0f1: 0 0.0 kbps 0 0.0 kbps lo: 161143108162 0.4 kbps 161143108162 0.4 kbps veth3154ff3: 3809619534 1.0 kbps 867529906 0.4 kbps veth487bc9b: 2650532684 0.8 kbps 2992458899 0.9 kbps veth8cb8ca8: 3234030733 0.7 kbps 16921098378 11.4 kbps vethbadbe14: 12007615348 3.8 kbps 15583195644 5.0 kbps vethc152f93: 978828 0.0 kbps 3839134 0.0 kbps vethe481f30: 1637142 0.0 kbps 15805768 0.0 kbps vethfac2e83: 19445827683 6.2 kbps 16194181515 5.1 kbps ``` ## netstat.rs Prints out all open and listening TCP/UDP sockets, along with the owning process. The output format is very similar to the standard `netstat` linux utility: ```text Local address Remote address State Inode PID/Program name 0.0.0.0:53 0.0.0.0:0 Listen 30883 1409/pdns_server 0.0.0.0:51413 0.0.0.0:0 Listen 24263 927/transmission-da 0.0.0.0:35445 0.0.0.0:0 Listen 21777 942/rpc.mountd 0.0.0.0:22 0.0.0.0:0 Listen 27973 1149/sshd 0.0.0.0:25 0.0.0.0:0 Listen 28295 1612/master ``` ## pressure.rs Prints out CPU/IO/Memory pressure information ## ps.rs Prints out all processes that share the same tty as the current terminal. This is very similar to the standard `ps` utility on linux when run with no arguments: ```text PID TTY TIME CMD 8369 pty/13 4.05 bash 23124 pty/13 0.23 basic-http-serv 24206 pty/13 0.11 ps ``` ## self_memory.rs Shows several ways to get the current memory usage of the current process ```text PID: 21867 Memory page size: 4096 == Data from /proc/self/stat: Total virtual memory used: 3436544 bytes Total resident set: 220 pages (901120 bytes) == Data from /proc/self/statm: Total virtual memory used: 839 pages (3436544 bytes) Total resident set: 220 pages (901120 byte)s Total shared memory: 191 pages (782336 bytes) == Data from /proc/self/status: Total virtual memory used: 3436544 bytes Total resident set: 901120 bytes Total shared memory: 782336 bytes ``` ## lsmod.rs This lists all the loaded kernel modules, in a simple tree format. ## diskstat.rs Lists IO information for local disks: ```text sda1 mounted on /: total reads: 7325390 (13640070 ms) total writes: 124191552 (119109541 ms) total flushes: 0 (0 ms) ``` Note: only local disks will be shown (not NFS mounts, and disks used for ZFS will not be shown either). ## lslocks.rs Shows current file locks in a format that is similiar to the `lslocks` utility. ## mountinfo.rs Lists all mountpoints, along with their type and options: ```text sysfs on /sys type sysfs (noexec,relatime,nodev,rw,nosuid) proc on /proc type proc (noexec,rw,nodev,relatime,nosuid) udev on /dev type devtmpfs (rw,nosuid,relatime) mode = 755 nr_inodes = 4109298 size = 16437192k devpts on /dev/pts type devpts (nosuid,rw,noexec,relatime) gid = 5 ptmxmode = 000 mode = 620 tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime) size = 3291852k mode = 755 /dev/sda1 on / type ext4 (rw,relatime) errors = remount-ro ``` ## process_hierarchy.rs Lists all processes as a tree. Sub-processes will be hierarchically ordered beneath their parents. ```text 1 /usr/lib/systemd/systemd --system --deserialize 54 366 /usr/lib/systemd/systemd-journald 375 /usr/lib/systemd/systemd-udevd 383 /usr/bin/lvmetad -f 525 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only 529 /usr/bin/syncthing -no-browser -no-restart -logflags=0 608 /usr/bin/syncthing -no-browser -no-restart -logflags=0 530 /usr/lib/systemd/systemd-logind ... ``` procfs-0.14.2/examples/diskstat.rs000064400000000000000000000022741046102023000152230ustar 00000000000000use procfs::{diskstats, process::Process, DiskStat}; use std::collections::HashMap; use std::iter::FromIterator; fn main() { let me = Process::myself().unwrap(); let mounts = me.mountinfo().unwrap(); // Get a list of all disks that we have IO stat info on let disk_stats: HashMap<(i32, i32), DiskStat> = HashMap::from_iter(diskstats().unwrap().into_iter().map(|i| ((i.major, i.minor), i))); for mount in mounts { // parse the majmin string (something like "0:3") into an (i32, i32) tuple let (maj, min): (i32, i32) = { let mut s = mount.majmin.split(':'); (s.next().unwrap().parse().unwrap(), s.next().unwrap().parse().unwrap()) }; if let Some(stat) = disk_stats.get(&(maj, min)) { println!("{} mounted on {}:", stat.name, mount.mount_point.display()); println!(" total reads: {} ({} ms)", stat.reads, stat.time_reading); println!(" total writes: {} ({} ms)", stat.writes, stat.time_writing); println!( " total flushes: {} ({} ms)", stat.flushes.unwrap_or(0), stat.time_flushing.unwrap_or(0) ); } } } procfs-0.14.2/examples/dump.rs000064400000000000000000000007451046102023000143430ustar 00000000000000extern crate procfs; fn main() { let pid = std::env::args().nth(1).and_then(|s| s.parse::().ok()); let prc = if let Some(pid) = pid { println!("Info for pid={}", pid); procfs::process::Process::new(pid).unwrap() } else { procfs::process::Process::myself().unwrap() }; println!("{:#?}", prc); let stat = prc.stat().unwrap(); println!("State: {:?}", stat.state()); println!("RSS: {} bytes", stat.rss_bytes().unwrap()); } procfs-0.14.2/examples/interface_stats.rs000064400000000000000000000026561046102023000165570ustar 00000000000000//! For each interface, display the number of bytes sent and received, along with a data rate fn main() { let delay = std::time::Duration::from_secs(2); let mut prev_stats = procfs::net::dev_status().unwrap(); let mut prev_now = std::time::Instant::now(); loop { std::thread::sleep(delay); let now = std::time::Instant::now(); let dev_stats = procfs::net::dev_status().unwrap(); // calculate diffs from previous let dt = (now - prev_now).as_millis() as f32 / 1000.0; let mut stats: Vec<_> = dev_stats.values().collect(); stats.sort_by_key(|s| &s.name); println!(); println!( "{:>16}: {:<20} {:<20} ", "Interface", "bytes recv", "bytes sent" ); println!( "{:>16} {:<20} {:<20}", "================", "====================", "====================" ); for stat in stats { println!( "{:>16}: {:<20} {:>6.1} kbps {:<20} {:>6.1} kbps ", stat.name, stat.recv_bytes, (stat.recv_bytes - prev_stats.get(&stat.name).unwrap().recv_bytes) as f32 / dt / 1000.0, stat.sent_bytes, (stat.sent_bytes - prev_stats.get(&stat.name).unwrap().sent_bytes) as f32 / dt / 1000.0 ); } prev_stats = dev_stats; prev_now = now; } } procfs-0.14.2/examples/lslocks.rs000064400000000000000000000045121046102023000150440ustar 00000000000000use procfs::process::{FDTarget, Process}; use rustix::fs::AtFlags; use std::path::Path; fn main() { let myself = Process::myself().unwrap(); let mountinfo = myself.mountinfo().unwrap(); for lock in procfs::locks().unwrap() { lock.pid .and_then(|pid| Process::new(pid).ok()) .and_then(|proc| proc.cmdline().ok()) .and_then(|mut cmd| cmd.drain(..).next()) .map_or_else( || { print!("{:18}", "(undefined)"); }, |s| { let p = Path::new(&s); print!("{:18}", p.file_name().unwrap_or(p.as_os_str()).to_string_lossy()); }, ); print!("{:<12} ", lock.pid.unwrap_or(-1)); print!("{:12} ", lock.lock_type.as_str()); print!("{:12} ", lock.mode.as_str()); print!("{:12} ", lock.kind.as_str()); // try to find the path for this inode let mut found = false; if let Some(pid) = lock.pid { if let Ok(fds) = Process::new(pid).and_then(|p| p.fd()) { for f in fds { let fd = f.unwrap(); if let FDTarget::Path(p) = fd.target { if let Ok(stat) = rustix::fs::statat(&rustix::fs::cwd(), &p, AtFlags::empty()) { if stat.st_ino as u64 == lock.inode { print!("{}", p.display()); found = true; break; } } } } } } if !found { // we don't have a PID or we don't have permission to inspect the processes files, but we still have the device and inode // There's no way to look up a path from an inode, so just bring the device mount point for mount in &mountinfo { if format!("{}:{}", lock.devmaj, lock.devmin) == mount.majmin { print!("{}...", mount.mount_point.display()); found = true; break; } } } if !found { // still not found? print the device print!("{}:{}", lock.devmaj, lock.devmin); } println!(); } } procfs-0.14.2/examples/lsmod.rs000064400000000000000000000016271046102023000145140ustar 00000000000000use std::collections::HashMap; fn print(name: &str, indent: usize, mods: &HashMap<&str, Vec<&str>>) { println!("{}{} {}", if indent == 0 { "-" } else { " " }, " ".repeat(indent), name); if let Some(uses_list) = mods.get(name) { for name in uses_list { print(name, indent + 2, mods); } } } fn main() { let modules = procfs::modules().unwrap(); // each module has a list of what other modules use it. Let's invert this and create a list of the modules used by each module. // This maps a module name to a list of modules that it uses let mut map: HashMap<&str, Vec<&str>> = HashMap::new(); for module in modules.values() { for name in &module.used_by { map.entry(name).or_default().push(&module.name); } } // println!("{:?}", map["xt_policy"]); for modname in map.keys() { print(modname, 0, &map); } } procfs-0.14.2/examples/mountinfo.rs000064400000000000000000000014671046102023000154160ustar 00000000000000use procfs::process::Process; use std::collections::HashSet; fn main() { for mount in Process::myself().unwrap().mountinfo().unwrap() { let (a, b): (HashSet<_>, HashSet<_>) = mount .mount_options .into_iter() .chain(mount.super_options) .partition(|&(_, ref m)| m.is_none()); println!( "{} on {} type {} ({})", mount.mount_source.unwrap_or_else(|| "None".to_string()), mount.mount_point.display(), mount.fs_type, a.into_iter().map(|(k, _)| k).collect::>().join(",") ); for (opt, val) in b { if let Some(val) = val { println!(" {} = {}", opt, val); } else { println!(" {}", opt); } } } } procfs-0.14.2/examples/netstat.rs000064400000000000000000000033031046102023000150510ustar 00000000000000#![allow(clippy::print_literal)] extern crate procfs; use procfs::process::{FDTarget, Stat}; use std::collections::HashMap; fn main() { // get all processes let all_procs = procfs::process::all_processes().unwrap(); // build up a map between socket inodes and processes: let mut map: HashMap = HashMap::new(); for p in all_procs { let process = p.unwrap(); if let (Ok(stat), Ok(fds)) = (process.stat(), process.fd()) { for fd in fds { if let FDTarget::Socket(inode) = fd.unwrap().target { map.insert(inode, stat.clone()); } } } } // get the tcp table let tcp = procfs::net::tcp().unwrap(); let tcp6 = procfs::net::tcp6().unwrap(); println!( "{:<26} {:<26} {:<15} {:<8} {}", "Local address", "Remote address", "State", "Inode", "PID/Program name" ); for entry in tcp.into_iter().chain(tcp6) { // find the process (if any) that has an open FD to this entry's inode let local_address = format!("{}", entry.local_address); let remote_addr = format!("{}", entry.remote_address); let state = format!("{:?}", entry.state); if let Some(stat) = map.get(&entry.inode) { println!( "{:<26} {:<26} {:<15} {:<12} {}/{}", local_address, remote_addr, state, entry.inode, stat.pid, stat.comm ); } else { // We might not always be able to find the process assocated with this socket println!( "{:<26} {:<26} {:<15} {:<12} -", local_address, remote_addr, state, entry.inode ); } } } procfs-0.14.2/examples/pressure.rs000064400000000000000000000004041046102023000152360ustar 00000000000000/// A basic example of /proc/pressure/ usage. fn main() { println!("memory pressure: {:#?}", procfs::MemoryPressure::new()); println!("cpu pressure: {:#?}", procfs::CpuPressure::new()); println!("io pressure: {:#?}", procfs::IoPressure::new()); } procfs-0.14.2/examples/process_hierarchy.rs000064400000000000000000000043771046102023000171170ustar 00000000000000use procfs::process::{all_processes, Stat}; struct ProcessEntry { stat: Stat, cmdline: Option>, } /// Print all processes as a tree. /// The tree reflects the hierarchical relationship between parent and child processes. fn main() { // Get all processes let processes: Vec = match all_processes() { Err(err) => { println!("Failed to read all processes: {}", err); return; } Ok(processes) => processes, } .filter_map(|v| { v.and_then(|p| { let stat = p.stat()?; let cmdline = p.cmdline().ok(); Ok(ProcessEntry { stat, cmdline }) }) .ok() }) .collect(); // Iterate through all processes and start with top-level processes. // Those can be identified by checking if their parent PID is zero. for process in &processes { if process.stat.ppid == 0 { print_process(process, &processes, 0); } } } /// Take a process, print its command and recursively list all child processes. /// This function will call itself until no further children can be found. /// It's a depth-first tree exploration. /// /// depth: The hierarchical depth of the process fn print_process(process: &ProcessEntry, all_processes: &Vec, depth: usize) { let cmdline = match &process.cmdline { Some(cmdline) => cmdline.join(" "), None => "zombie process".into(), }; // Some processes seem to have an empty cmdline. if cmdline.is_empty() { return; } // 10 characters width for the pid let pid_length = 8; let mut pid = process.stat.pid.to_string(); pid.push_str(&" ".repeat(pid_length - pid.len())); let padding = " ".repeat(4 * depth); println!("{}{}{}", pid, padding, cmdline); let children = get_children(process.stat.pid, all_processes); for child in &children { print_process(child, all_processes, depth + 1); } } /// Get all children of a specific process, by iterating through all processes and /// checking their parent pid. fn get_children(pid: i32, all_processes: &[ProcessEntry]) -> Vec<&ProcessEntry> { all_processes .iter() .filter(|process| process.stat.ppid == pid) .collect() } procfs-0.14.2/examples/ps.rs000064400000000000000000000015651046102023000140210ustar 00000000000000#![allow(clippy::print_literal)] extern crate procfs; /// A very basic clone of `ps` on Linux, in the simple no-argument mode. /// It shows all the processes that share the same tty as our self fn main() { let mestat = procfs::process::Process::myself().unwrap().stat().unwrap(); let tps = procfs::ticks_per_second().unwrap(); println!("{: >10} {: <8} {: >8} {}", "PID", "TTY", "TIME", "CMD"); let tty = format!("pty/{}", mestat.tty_nr().1); for p in procfs::process::all_processes().unwrap() { let prc = p.unwrap(); if let Ok(stat) = prc.stat() { if stat.tty_nr == mestat.tty_nr { // total_time is in seconds let total_time = (stat.utime + stat.stime) as f32 / (tps as f32); println!("{: >10} {: <8} {: >8} {}", stat.pid, tty, total_time, stat.comm); } } } } procfs-0.14.2/examples/self_memory.rs000064400000000000000000000034361046102023000157170ustar 00000000000000use procfs::process::Process; fn main() { let me = Process::myself().expect("Unable to load myself!"); println!("PID: {}", me.pid); let page_size = procfs::page_size().expect("Unable to determinte page size!") as u64; println!("Memory page size: {}", page_size); // Note: when comparing the below values to what "top" will display, note that "top" will use // base-2 units (kibibytes), not base-10 units (kilobytes). if let Ok(stat) = me.stat() { println!("== Data from /proc/self/stat:"); println!("Total virtual memory used: {} bytes", stat.vsize); println!( "Total resident set: {} pages ({} bytes)", stat.rss, stat.rss as u64 * page_size ); println!(); } if let Ok(statm) = me.statm() { println!("== Data from /proc/self/statm:"); println!( "Total virtual memory used: {} pages ({} bytes)", statm.size, statm.size * page_size ); println!( "Total resident set: {} pages ({} byte)s", statm.resident, statm.resident * page_size ); println!( "Total shared memory: {} pages ({} bytes)", statm.shared, statm.shared * page_size ); println!(); } if let Ok(status) = me.status() { println!("== Data from /proc/self/status:"); println!( "Total virtual memory used: {} bytes", status.vmsize.expect("vmsize") * 1024 ); println!("Total resident set: {} bytes", status.vmrss.expect("vmrss") * 1024); println!( "Total shared memory: {} bytes", status.rssfile.expect("rssfile") * 1024 + status.rssshmem.expect("rssshmem") * 1024 ); } } procfs-0.14.2/examples/shm.rs000064400000000000000000000017651046102023000141700ustar 00000000000000extern crate procfs; /// List processes using posix shared memory segments fn main() { let shared_memory_vec = procfs::Shm::new().unwrap(); for shared_memory in &shared_memory_vec { println!("key: {}, shmid: {}", shared_memory.key, shared_memory.shmid); println!("============"); for prc in procfs::process::all_processes().unwrap() { let prc = prc.unwrap(); match prc.smaps() { Ok(memory_maps) => { for (memory_map, _memory_map_data) in &memory_maps { if let procfs::process::MMapPath::Vsys(key) = memory_map.pathname { if key == shared_memory.key && memory_map.inode == shared_memory.shmid { println!("{}: {:?}", prc.pid, prc.cmdline().unwrap()); } } } } Err(_) => continue, } } println!(); } } procfs-0.14.2/rustfmt.toml000064400000000000000000000000201046102023000135750ustar 00000000000000max_width = 120 procfs-0.14.2/src/cgroups.rs000064400000000000000000000107171046102023000140310ustar 00000000000000use crate::ProcResult; use super::process::Process; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] /// Container group controller information. /// /// See also the [cgroups()] method. pub struct CGroupController { /// The name of the controller. pub name: String, /// The unique ID of the cgroup hierarchy on which this controller is mounted. /// /// If multiple cgroups v1 controllers are bound to the same hierarchy, then each will show /// the same hierarchy ID in this field. The value in this field will be 0 if: /// /// * the controller is not mounted on a cgroups v1 hierarchy; /// * the controller is bound to the cgroups v2 single unified hierarchy; or /// * the controller is disabled (see below). pub hierarchy: u32, /// The number of control groups in this hierarchy using this controller. pub num_cgroups: u32, /// This field contains the value `true` if this controller is enabled, or `false` if it has been disabled pub enabled: bool, } /// Information about the cgroup controllers that are compiled into the kernel /// /// (since Linux 2.6.24) // This is returning a vector, but if each subsystem name is unique, maybe this can be a hashmap // instead pub fn cgroups() -> ProcResult> { use std::fs::File; use std::io::{BufRead, BufReader}; let file = File::open("/proc/cgroups")?; let reader = BufReader::new(file); let mut vec = Vec::new(); for line in reader.lines() { let line = line?; if line.starts_with('#') { continue; } let mut s = line.split_whitespace(); let name = expect!(s.next(), "name").to_owned(); let hierarchy = from_str!(u32, expect!(s.next(), "hierarchy")); let num_cgroups = from_str!(u32, expect!(s.next(), "num_cgroups")); let enabled = expect!(s.next(), "enabled") == "1"; vec.push(CGroupController { name, hierarchy, num_cgroups, enabled, }); } Ok(vec) } /// Information about a process cgroup /// /// See also the [Process::cgroups()] method. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct ProcessCgroup { /// For cgroups version 1 hierarchies, this field contains a unique hierarchy ID number /// that can be matched to a hierarchy ID in /proc/cgroups. For the cgroups version 2 /// hierarchy, this field contains the value 0. pub hierarchy: u32, /// For cgroups version 1 hierarchies, this field contains a comma-separated list of the /// controllers bound to the hierarchy. /// /// For the cgroups version 2 hierarchy, this field is empty. pub controllers: Vec, /// This field contains the pathname of the control group in the hierarchy to which the process /// belongs. /// /// This pathname is relative to the mount point of the hierarchy. pub pathname: String, } impl Process { /// Describes control groups to which the process with the corresponding PID belongs. /// /// The displayed information differs for cgroupsversion 1 and version 2 hierarchies. pub fn cgroups(&self) -> ProcResult> { use std::io::{BufRead, BufReader}; let file = self.open_relative("cgroup")?; let reader = BufReader::new(file); let mut vec = Vec::new(); for line in reader.lines() { let line = line?; if line.starts_with('#') { continue; } let mut s = line.splitn(3, ':'); let hierarchy = from_str!(u32, expect!(s.next(), "hierarchy")); let controllers = expect!(s.next(), "controllers") .split(',') .map(|s| s.to_owned()) .collect(); let pathname = expect!(s.next(), "path").to_owned(); vec.push(ProcessCgroup { hierarchy, controllers, pathname, }); } Ok(vec) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_cgroups() { let groups = cgroups().unwrap(); println!("{:?}", groups); } #[test] fn test_process_cgroups() { let myself = Process::myself().unwrap(); let groups = myself.cgroups(); println!("{:?}", groups); } } procfs-0.14.2/src/cpuinfo.rs000064400000000000000000000163721046102023000140150ustar 00000000000000use crate::{FileWrapper, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; use std::{collections::HashMap, io::Read}; /// Represents the data from `/proc/cpuinfo`. /// /// The `fields` field stores the fields that are common among all CPUs. The `cpus` field stores /// CPU-specific info. /// /// For common fields, there are methods that will return the data, converted to a more appropriate /// data type. These methods will all return `None` if the field doesn't exist, or is in some /// unexpected format (in that case, you'll have to access the string data directly). #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct CpuInfo { /// This stores fields that are common among all CPUs pub fields: HashMap, pub cpus: Vec>, } impl CpuInfo { /// Get CpuInfo from a custom Read instead of the default `/proc/cpuinfo`. pub fn from_reader(r: R) -> ProcResult { use std::io::{BufRead, BufReader}; let reader = BufReader::new(r); let mut list = Vec::new(); let mut map = Some(HashMap::new()); // the first line of a cpu block must start with "processor" let mut found_first = false; for line in reader.lines().flatten() { if !line.is_empty() { let mut s = line.split(':'); let key = expect!(s.next()); if !found_first && key.trim() == "processor" { found_first = true; } if !found_first { continue; } if let Some(value) = s.next() { let key = key.trim().to_owned(); let value = value.trim().to_owned(); map.get_or_insert(HashMap::new()).insert(key, value); } } else if let Some(map) = map.take() { list.push(map); found_first = false; } } if let Some(map) = map.take() { list.push(map); } // find properties that are the same for all cpus assert!(!list.is_empty()); let common_fields: Vec = list[0] .iter() .filter_map(|(key, val)| { if list.iter().all(|map| map.get(key).map_or(false, |v| v == val)) { Some(key.clone()) } else { None } }) .collect(); let mut common_map = HashMap::new(); for (k, v) in &list[0] { if common_fields.contains(k) { common_map.insert(k.clone(), v.clone()); } } for map in &mut list { map.retain(|k, _| !common_fields.contains(k)); } Ok(CpuInfo { fields: common_map, cpus: list, }) } pub fn new() -> ProcResult { let file = FileWrapper::open("/proc/cpuinfo")?; CpuInfo::from_reader(file) } /// Get the total number of cpu cores. /// /// This is the number of entries in the `/proc/cpuinfo` file. pub fn num_cores(&self) -> usize { self.cpus.len() } /// Get info for a specific cpu. /// /// This will merge the common fields with the cpu-specific fields. /// /// Returns None if the requested cpu index is not found. pub fn get_info(&self, cpu_num: usize) -> Option> { self.cpus.get(cpu_num).map(|info| { self.fields .iter() .chain(info.iter()) .map(|(k, v)| (k.as_ref(), v.as_ref())) .collect() }) } /// Get the content of a specific field associated to a CPU /// /// Returns None if the requested cpu index is not found. pub fn get_field(&self, cpu_num: usize, field_name: &str) -> Option<&str> { self.cpus.get(cpu_num).and_then(|cpu_fields| { cpu_fields .get(field_name) .or_else(|| self.fields.get(field_name)) .map(|s| s.as_ref()) }) } pub fn model_name(&self, cpu_num: usize) -> Option<&str> { self.get_field(cpu_num, "model name") } pub fn vendor_id(&self, cpu_num: usize) -> Option<&str> { self.get_field(cpu_num, "vendor_id") } /// May not be available on some older 2.6 kernels pub fn physical_id(&self, cpu_num: usize) -> Option { self.get_field(cpu_num, "physical id").and_then(|s| s.parse().ok()) } pub fn flags(&self, cpu_num: usize) -> Option> { self.get_field(cpu_num, "flags") .map(|flags| flags.split_whitespace().collect()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_cpuinfo() { let info = CpuInfo::new().unwrap(); println!("{:#?}", info.flags(0)); for num in 0..info.num_cores() { info.model_name(num).unwrap(); info.vendor_id(num).unwrap(); // May not be available on some old kernels: info.physical_id(num); } //assert_eq!(info.num_cores(), 8); } #[test] fn test_cpuinfo_rpi() { // My rpi system includes some stuff at the end of /proc/cpuinfo that we shouldn't parse let data = r#"processor : 0 model name : ARMv7 Processor rev 4 (v7l) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 processor : 1 model name : ARMv7 Processor rev 4 (v7l) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 processor : 2 model name : ARMv7 Processor rev 4 (v7l) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 processor : 3 model name : ARMv7 Processor rev 4 (v7l) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 Hardware : BCM2835 Revision : a020d3 Serial : 0000000012345678 Model : Raspberry Pi 3 Model B Plus Rev 1.3 "#; let r = std::io::Cursor::new(data.as_bytes()); let info = CpuInfo::from_reader(r).unwrap(); assert_eq!(info.num_cores(), 4); let info = info.get_info(0).unwrap(); assert!(info.get("model name").is_some()); assert!(info.get("BogoMIPS").is_some()); assert!(info.get("Features").is_some()); assert!(info.get("CPU implementer").is_some()); assert!(info.get("CPU architecture").is_some()); assert!(info.get("CPU variant").is_some()); assert!(info.get("CPU part").is_some()); assert!(info.get("CPU revision").is_some()); } } procfs-0.14.2/src/diskstats.rs000064400000000000000000000113551046102023000143570ustar 00000000000000use crate::{FileWrapper, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; use std::io::{BufRead, BufReader}; /// Disk IO stat information /// /// To fully understand these fields, please see the [iostats.txt](https://www.kernel.org/doc/Documentation/iostats.txt) /// kernel documentation. /// /// For an example, see the [diskstats.rs](https://github.com/eminence/procfs/tree/master/examples) /// example in the source repo. // Doc reference: https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats // Doc reference: https://www.kernel.org/doc/Documentation/iostats.txt #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct DiskStat { /// The device major number pub major: i32, /// The device minor number pub minor: i32, /// Device name pub name: String, /// Reads completed successfully /// /// This is the total number of reads completed successfully pub reads: u64, /// Reads merged /// /// The number of adjacent reads that have been merged for efficiency. pub merged: u64, /// Sectors read successfully /// /// This is the total number of sectors read successfully. pub sectors_read: u64, /// Time spent reading (ms) pub time_reading: u64, /// writes completed pub writes: u64, /// writes merged /// /// The number of adjacent writes that have been merged for efficiency. pub writes_merged: u64, /// Sectors written successfully pub sectors_written: u64, /// Time spent writing (ms) pub time_writing: u64, /// I/Os currently in progress pub in_progress: u64, /// Time spent doing I/Os (ms) pub time_in_progress: u64, /// Weighted time spent doing I/Os (ms) pub weighted_time_in_progress: u64, /// Discards completed successfully /// /// (since kernel 4.18) pub discards: Option, /// Discards merged pub discards_merged: Option, /// Sectors discarded pub sectors_discarded: Option, /// Time spent discarding pub time_discarding: Option, /// Flush requests completed successfully /// /// (since kernel 5.5) pub flushes: Option, /// Time spent flushing pub time_flushing: Option, } /// Get disk IO stat info from /proc/diskstats pub fn diskstats() -> ProcResult> { let file = FileWrapper::open("/proc/diskstats")?; let reader = BufReader::new(file); let mut v = Vec::new(); for line in reader.lines() { let line = line?; v.push(DiskStat::from_line(&line)?); } Ok(v) } impl DiskStat { pub fn from_line(line: &str) -> ProcResult { let mut s = line.split_whitespace(); let major = from_str!(i32, expect!(s.next())); let minor = from_str!(i32, expect!(s.next())); let name = expect!(s.next()).to_string(); let reads = from_str!(u64, expect!(s.next())); let merged = from_str!(u64, expect!(s.next())); let sectors_read = from_str!(u64, expect!(s.next())); let time_reading = from_str!(u64, expect!(s.next())); let writes = from_str!(u64, expect!(s.next())); let writes_merged = from_str!(u64, expect!(s.next())); let sectors_written = from_str!(u64, expect!(s.next())); let time_writing = from_str!(u64, expect!(s.next())); let in_progress = from_str!(u64, expect!(s.next())); let time_in_progress = from_str!(u64, expect!(s.next())); let weighted_time_in_progress = from_str!(u64, expect!(s.next())); let discards = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); let discards_merged = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); let sectors_discarded = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); let time_discarding = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); let flushes = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); let time_flushing = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); Ok(DiskStat { major, minor, name, reads, merged, sectors_read, time_reading, writes, writes_merged, sectors_written, time_writing, in_progress, time_in_progress, weighted_time_in_progress, discards, discards_merged, sectors_discarded, time_discarding, flushes, time_flushing, }) } } #[cfg(test)] mod tests { #[test] fn diskstat() { for disk in super::diskstats().unwrap() { println!("{:?}", disk); } } } procfs-0.14.2/src/keyring.rs000064400000000000000000000356361046102023000140260ustar 00000000000000//! Functions related to the in-kernel key management and retention facility //! //! For more details on this facility, see the `keyrings(7)` man page. //! //! Additional functions can be found in the [kernel::keys](crate::sys::kernel::keys) module. use crate::{FileWrapper, ProcResult}; use bitflags::bitflags; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, io::{BufRead, BufReader}, time::Duration, }; bitflags! { /// Various key flags #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct KeyFlags: u32 { /// The key has been instantiated const INSTANTIATED = 0x01; /// THe key has been revoked const REVOKED = 0x02; /// The key is dead /// /// I.e. the key type has been unregistered. A key may be briefly in this state during garbage collection. const DEAD = 0x04; /// The key contributes to the user's quota const QUOTA = 0x08; /// The key is under construction via a callback to user space const UNDER_CONSTRUCTION = 0x10; /// The key is negatively instantiated const NEGATIVE = 0x20; /// The key has been invalidated const INVALID = 0x40; } } bitflags! { /// Bitflags that represent the permissions for a key #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct PermissionFlags: u32 { /// The attributes of the key may be read /// /// This includes the type, description, and access rights (excluding the security label) const VIEW = 0x01; /// For a key: the payload of the key may be read. For a keyring: the list of serial numbers (keys) to which the keyring has links may be read. const READ = 0x02; /// The payload of the key may be updated and the key may be revoked. /// /// For a keyring, links may be added to or removed from the keyring, and the keyring /// may be cleared completely (all links are removed). const WRITE = 0x04; /// The key may be found by a search. /// /// For keyrings: keys and keyrings that are linked to by the keyring may be searched. const SEARCH = 0x08; /// Links may be created from keyrings to the key. /// /// The initial link to a key that is established when the key is created doesn't require this permission. const LINK = 0x10; /// The ownership details and security label of the key may be changed, the key's expiration /// time may be set, and the key may be revoked. const SETATTR = 0x20; const ALL = Self::VIEW.bits | Self::READ.bits | Self::WRITE.bits | Self::SEARCH.bits | Self::LINK.bits | Self::SETATTR.bits; } } impl KeyFlags { fn from_str(s: &str) -> KeyFlags { let mut me = KeyFlags::empty(); let mut chars = s.chars(); match chars.next() { Some(c) if c == 'I' => me.insert(KeyFlags::INSTANTIATED), _ => {} } match chars.next() { Some(c) if c == 'R' => me.insert(KeyFlags::REVOKED), _ => {} } match chars.next() { Some(c) if c == 'D' => me.insert(KeyFlags::DEAD), _ => {} } match chars.next() { Some(c) if c == 'Q' => me.insert(KeyFlags::QUOTA), _ => {} } match chars.next() { Some(c) if c == 'U' => me.insert(KeyFlags::UNDER_CONSTRUCTION), _ => {} } match chars.next() { Some(c) if c == 'N' => me.insert(KeyFlags::NEGATIVE), _ => {} } match chars.next() { Some(c) if c == 'i' => me.insert(KeyFlags::INVALID), _ => {} } me } } #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Permissions { pub possessor: PermissionFlags, pub user: PermissionFlags, pub group: PermissionFlags, pub other: PermissionFlags, } impl Permissions { fn from_str(s: &str) -> ProcResult { let possessor = PermissionFlags::from_bits(from_str!(u32, &s[0..2], 16)) .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; let user = PermissionFlags::from_bits(from_str!(u32, &s[2..4], 16)) .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; let group = PermissionFlags::from_bits(from_str!(u32, &s[4..6], 16)) .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; let other = PermissionFlags::from_bits(from_str!(u32, &s[6..8], 16)) .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; Ok(Permissions { possessor, user, group, other, }) } } #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum KeyTimeout { Permanent, Expired, Timeout(Duration), } impl KeyTimeout { fn from_str(s: &str) -> ProcResult { if s == "perm" { Ok(KeyTimeout::Permanent) } else if s == "expd" { Ok(KeyTimeout::Expired) } else { let (val, unit) = s.split_at(s.len() - 1); let val = from_str!(u64, val); match unit { "s" => Ok(KeyTimeout::Timeout(Duration::from_secs(val))), "m" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60))), "h" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60))), "d" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60 * 24))), "w" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60 * 24 * 7))), _ => Err(build_internal_error!(format!("Unable to parse keytimeout of {:?}", s))), } } } } #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum KeyType { /// This is a general-purpose key type. /// /// The key is kept entirely within kernel memory. The payload may be read and updated by /// user-space applications. The payload for keys of this type is a blob of arbitrary /// data of up to 32,767 bytes. /// The description may be any valid string, though it is preferred that it start /// with a colon-delimited prefix representing the service to which the key is of /// interest (for instance "afs:mykey"). User, /// Keyrings are special keys which store a set of links to other keys (including /// other keyrings), analogous to a directory holding links to files. The main /// purpose of a keyring is to prevent other keys from being garbage collected /// because nothing refers to them. /// /// Keyrings with descriptions (names) that begin with a period ('.') are re‐ /// served to the implementation. Keyring, /// This key type is essentially the same as "user", but it does not provide /// reading (i.e., the keyctl(2) KEYCTL_READ operation), meaning that the key /// payload is never visible from user space. This is suitable for storing user‐ /// name-password pairs that should not be readable from user space. /// /// The description of a "logon" key must start with a non-empty colon-delimited /// prefix whose purpose is to identify the service to which the key belongs. /// (Note that this differs from keys of the "user" type, where the inclusion of /// a prefix is recommended but is not enforced.) Logon, /// This key type is similar to the "user" key type, but it may hold a payload of /// up to 1 MiB in size. This key type is useful for purposes such as holding /// Kerberos ticket caches. /// /// The payload data may be stored in a tmpfs filesystem, rather than in kernel /// memory, if the data size exceeds the overhead of storing the data in the /// filesystem. (Storing the data in a filesystem requires filesystem structures /// to be allocated in the kernel. The size of these structures determines the /// size threshold above which the tmpfs storage method is used.) Since Linux /// 4.8, the payload data is encrypted when stored in tmpfs, thereby preventing /// it from being written unencrypted into swap space. BigKey, /// Other specialized, but rare keys types Other(String), } impl KeyType { fn from_str(s: &str) -> KeyType { match s { "keyring" => KeyType::Keyring, "user" => KeyType::User, "logon" => KeyType::Logon, "big_key" => KeyType::BigKey, other => KeyType::Other(other.to_string()), } } } /// A key #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Key { /// The ID (serial number) of the key pub id: u64, /// A set of flags describing the state of the key pub flags: KeyFlags, /// Count of the number of kernel credential structures that are /// pinning the key (approximately: the number of threads and open file /// references that refer to this key). pub usage: u32, /// Key timeout pub timeout: KeyTimeout, /// Key permissions pub permissions: Permissions, /// The user ID of the key owner pub uid: u32, /// The group ID of the key. /// /// The value of `None` here means that the key has no group ID; this can occur in certain circumstances for /// keys created by the kernel. pub gid: Option, /// The type of key pub key_type: KeyType, /// The key description pub description: String, } impl Key { fn from_line(s: &str) -> ProcResult { let mut s = s.split_whitespace(); let id = from_str!(u64, expect!(s.next()), 16); let s_flags = expect!(s.next()); let usage = from_str!(u32, expect!(s.next())); let s_timeout = expect!(s.next()); let s_perms = expect!(s.next()); let uid = from_str!(u32, expect!(s.next())); let s_gid = expect!(s.next()); let s_type = expect!(s.next()); let desc: Vec<_> = s.collect(); Ok(Key { id, flags: KeyFlags::from_str(s_flags), usage, timeout: KeyTimeout::from_str(s_timeout)?, permissions: Permissions::from_str(s_perms)?, uid, gid: if s_gid == "-1" { None } else { Some(from_str!(u32, s_gid)) }, key_type: KeyType::from_str(s_type), description: desc.join(" "), }) } } /// Returns a list of the keys for which the reading thread has **view** permission, providing various information about each key. pub fn keys() -> ProcResult> { let file = FileWrapper::open("/proc/keys")?; let reader = BufReader::new(file); let mut v = Vec::new(); for line in reader.lines() { let line = line?; v.push(Key::from_line(&line)?); } Ok(v) } /// Information about a user with at least one key #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct KeyUser { /// The user that owns the key pub uid: u32, /// The kernel-internal usage count for the kernel structure used to record key users pub usage: u32, /// The total number of keys owned by the user pub nkeys: u32, /// THe number of keys that have been instantiated pub nikeys: u32, /// The number of keys owned by the user pub qnkeys: u32, /// The maximum number of keys that the user may own pub maxkeys: u32, /// The number of bytes consumed in playloads of the keys owned by this user pub qnbytes: u32, /// The upper limit on the number of bytes in key payloads for this user pub maxbytes: u32, } impl KeyUser { fn from_str(s: &str) -> ProcResult { let mut s = s.split_whitespace(); let uid = expect!(s.next()); let usage = from_str!(u32, expect!(s.next())); let keys = expect!(s.next()); let qkeys = expect!(s.next()); let qbytes = expect!(s.next()); let (nkeys, nikeys) = { let mut s = keys.split('/'); (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next()))) }; let (qnkeys, maxkeys) = { let mut s = qkeys.split('/'); (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next()))) }; let (qnbytes, maxbytes) = { let mut s = qbytes.split('/'); (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next()))) }; Ok(KeyUser { uid: from_str!(u32, &uid[0..uid.len() - 1]), usage, nkeys, nikeys, qnkeys, maxkeys, qnbytes, maxbytes, }) } } /// Get various information for each user ID that has at least one key on the system. pub fn key_users() -> ProcResult> { let file = FileWrapper::open("/proc/key-users")?; let reader = BufReader::new(file); let mut map = HashMap::new(); for line in reader.lines() { let line = line?; let user = KeyUser::from_str(&line)?; map.insert(user.uid, user); } Ok(map) } #[cfg(test)] mod tests { use super::*; #[test] fn key_flags() { assert_eq!(KeyFlags::from_str("I------"), KeyFlags::INSTANTIATED); assert_eq!(KeyFlags::from_str("IR"), KeyFlags::INSTANTIATED | KeyFlags::REVOKED); assert_eq!(KeyFlags::from_str("IRDQUNi"), KeyFlags::all()); } #[test] fn timeout() { assert_eq!(KeyTimeout::from_str("perm").unwrap(), KeyTimeout::Permanent); assert_eq!(KeyTimeout::from_str("expd").unwrap(), KeyTimeout::Expired); assert_eq!( KeyTimeout::from_str("2w").unwrap(), KeyTimeout::Timeout(Duration::from_secs(1209600)) ); assert_eq!( KeyTimeout::from_str("14d").unwrap(), KeyTimeout::Timeout(Duration::from_secs(1209600)) ); assert_eq!( KeyTimeout::from_str("336h").unwrap(), KeyTimeout::Timeout(Duration::from_secs(1209600)) ); assert_eq!( KeyTimeout::from_str("20160m").unwrap(), KeyTimeout::Timeout(Duration::from_secs(1209600)) ); assert_eq!( KeyTimeout::from_str("1209600s").unwrap(), KeyTimeout::Timeout(Duration::from_secs(1209600)) ); } #[test] fn live_keys() { for key in keys().unwrap() { println!("{:#?}", key); } } #[test] fn live_key_users() { for (_user, data) in key_users().unwrap() { println!("{:#?}", data); } } } procfs-0.14.2/src/lib.rs000064400000000000000000001252071046102023000131160ustar 00000000000000#![allow(unknown_lints)] // The suggested fix with `str::parse` removes support for Rust 1.48 #![allow(clippy::from_str_radix_10)] #![deny(broken_intra_doc_links)] //! This crate provides to an interface into the linux `procfs` filesystem, usually mounted at //! `/proc`. //! //! This is a pseudo-filesystem which is available on most every linux system and provides an //! interface to kernel data structures. //! //! //! # Kernel support //! //! Not all fields/data are available in each kernel. Some fields were added in specific kernel //! releases, and other fields are only present in certain kernel configuration options are //! enabled. These are represented as `Option` fields in this crate. //! //! This crate aims to support all 2.6 kernels (and newer). WSL2 is also supported. //! //! # Documentation //! //! In almost all cases, the documentation is taken from the //! [`proc.5`](http://man7.org/linux/man-pages/man5/proc.5.html) manual page. This means that //! sometimes the style of writing is not very "rusty", or may do things like reference related files //! (instead of referencing related structs). Contributions to improve this are welcome. //! //! # Panicing //! //! While previous versions of the library could panic, this current version aims to be panic-free //! in a many situations as possible. Whenever the procfs crate encounters a bug in its own //! parsing code, it will return an [`InternalError`](enum.ProcError.html#variant.InternalError) error. This should be considered a //! bug and should be [reported](https://github.com/eminence/procfs). If you encounter a panic, //! please report that as well. //! //! # Cargo features //! //! The following cargo features are available: //! //! * `chrono` -- Default. Optional. This feature enables a few methods that return values as `DateTime` objects. //! * `flate2` -- Default. Optional. This feature enables parsing gzip compressed `/proc/config.gz` file via the `procfs::kernel_config` method. //! * `backtrace` -- Optional. This feature lets you get a stack trace whenever an `InternalError` is raised. //! //! # Examples //! //! Examples can be found in the various modules shown below, or in the //! [examples](https://github.com/eminence/procfs/tree/master/examples) folder of the code repository. //! use bitflags::bitflags; use lazy_static::lazy_static; use rustix::fd::AsFd; use std::fmt; use std::fs::{File, OpenOptions}; use std::io::{self, BufRead, BufReader, Read, Seek, Write}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{collections::HashMap, time::Duration}; #[cfg(feature = "chrono")] use chrono::{DateTime, Local}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; const PROC_CONFIG_GZ: &str = "/proc/config.gz"; const BOOT_CONFIG: &str = "/boot/config"; trait IntoOption { fn into_option(t: Self) -> Option; } impl IntoOption for Option { fn into_option(t: Option) -> Option { t } } impl IntoOption for Result { fn into_option(t: Result) -> Option { t.ok() } } pub(crate) trait IntoResult { fn into(t: Self) -> Result; } macro_rules! build_internal_error { ($err: expr) => { crate::ProcError::InternalError(crate::InternalError { msg: format!("Internal Unwrap Error: {}", $err), file: file!(), line: line!(), #[cfg(feature = "backtrace")] backtrace: backtrace::Backtrace::new(), }) }; ($err: expr, $msg: expr) => { crate::ProcError::InternalError(crate::InternalError { msg: format!("Internal Unwrap Error: {}: {}", $msg, $err), file: file!(), line: line!(), #[cfg(feature = "backtrace")] backtrace: backtrace::Backtrace::new(), }) }; } // custom NoneError, since std::option::NoneError is nightly-only // See https://github.com/rust-lang/rust/issues/42327 struct NoneError; impl std::fmt::Display for NoneError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "NoneError") } } impl IntoResult for Option { fn into(t: Option) -> Result { t.ok_or(NoneError) } } impl IntoResult for Result { fn into(t: Result) -> Result { t } } #[allow(unused_macros)] macro_rules! proc_panic { ($e:expr) => { crate::IntoOption::into_option($e).unwrap_or_else(|| { panic!( "Failed to unwrap {}. Please report this as a procfs bug.", stringify!($e) ) }) }; ($e:expr, $msg:expr) => { crate::IntoOption::into_option($e).unwrap_or_else(|| { panic!( "Failed to unwrap {} ({}). Please report this as a procfs bug.", stringify!($e), $msg ) }) }; } macro_rules! expect { ($e:expr) => { match crate::IntoResult::into($e) { Ok(v) => v, Err(e) => return Err(build_internal_error!(e)), } }; ($e:expr, $msg:expr) => { match crate::IntoResult::into($e) { Ok(v) => v, Err(e) => return Err(build_internal_error!(e, $msg)), } }; } macro_rules! from_str { ($t:tt, $e:expr) => {{ let e = $e; expect!( $t::from_str_radix(e, 10), format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t),) ) }}; ($t:tt, $e:expr, $radix:expr) => {{ let e = $e; expect!( $t::from_str_radix(e, $radix), format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t)) ) }}; ($t:tt, $e:expr, $radix:expr, pid:$pid:expr) => {{ let e = $e; expect!( $t::from_str_radix(e, $radix), format!( "Failed to parse {} ({:?}) as a {} (pid {})", stringify!($e), e, stringify!($t), $pid ) ) }}; } macro_rules! wrap_io_error { ($path:expr, $expr:expr) => { match $expr { Ok(v) => Ok(v), Err(e) => { let kind = e.kind(); Err(::std::io::Error::new( kind, crate::IoErrorWrapper { path: $path.to_owned(), inner: Some(Box::new(e)), }, )) } } }; } pub(crate) fn read_file>(path: P) -> ProcResult { let mut f = FileWrapper::open(path)?; let mut buf = String::new(); f.read_to_string(&mut buf)?; Ok(buf) } pub(crate) fn write_file, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()> { let mut f = OpenOptions::new().read(false).write(true).open(path)?; f.write_all(buf.as_ref())?; Ok(()) } pub(crate) fn read_value(path: P) -> ProcResult where P: AsRef, T: FromStr, ProcError: From, { let val = read_file(path)?; Ok(::from_str(val.trim())?) //Ok(val.trim().parse()?) } pub(crate) fn write_value, T: fmt::Display>(path: P, value: T) -> ProcResult<()> { write_file(path, value.to_string().as_bytes()) } pub(crate) fn from_iter<'a, I, U>(i: I) -> ProcResult where I: IntoIterator, U: FromStr, { let mut iter = i.into_iter(); let val = expect!(iter.next()); match FromStr::from_str(val) { Ok(u) => Ok(u), Err(..) => Err(build_internal_error!("Failed to convert")), } } pub mod process; mod meminfo; pub use crate::meminfo::*; mod sysvipc_shm; pub use crate::sysvipc_shm::*; pub mod net; mod cpuinfo; pub use crate::cpuinfo::*; mod cgroups; pub use crate::cgroups::*; pub mod sys; pub use crate::sys::kernel::BuildInfo as KernelBuildInfo; pub use crate::sys::kernel::Type as KernelType; pub use crate::sys::kernel::Version as KernelVersion; mod pressure; pub use crate::pressure::*; mod diskstats; pub use diskstats::*; mod locks; pub use locks::*; pub mod keyring; mod uptime; pub use uptime::*; lazy_static! { /// The number of clock ticks per second. /// /// This is calculated from `sysconf(_SC_CLK_TCK)`. static ref TICKS_PER_SECOND: ProcResult = { Ok(ticks_per_second()?) }; /// The version of the currently running kernel. /// /// This is a lazily constructed static. You can also get this information via /// [KernelVersion::new()]. static ref KERNEL: ProcResult = { KernelVersion::current() }; /// Memory page size, in bytes. /// /// This is calculated from `sysconf(_SC_PAGESIZE)`. static ref PAGESIZE: ProcResult = { Ok(page_size()?) }; } fn convert_to_kibibytes(num: u64, unit: &str) -> ProcResult { match unit { "B" => Ok(num), "KiB" | "kiB" | "kB" | "KB" => Ok(num * 1024), "MiB" | "miB" | "MB" | "mB" => Ok(num * 1024 * 1024), "GiB" | "giB" | "GB" | "gB" => Ok(num * 1024 * 1024 * 1024), unknown => Err(build_internal_error!(format!("Unknown unit type {}", unknown))), } } trait FromStrRadix: Sized { fn from_str_radix(t: &str, radix: u32) -> Result; } impl FromStrRadix for u64 { fn from_str_radix(s: &str, radix: u32) -> Result { u64::from_str_radix(s, radix) } } impl FromStrRadix for i32 { fn from_str_radix(s: &str, radix: u32) -> Result { i32::from_str_radix(s, radix) } } fn split_into_num(s: &str, sep: char, radix: u32) -> ProcResult<(T, T)> { let mut s = s.split(sep); let a = expect!(FromStrRadix::from_str_radix(expect!(s.next()), radix)); let b = expect!(FromStrRadix::from_str_radix(expect!(s.next()), radix)); Ok((a, b)) } /// This is used to hold both an IO error as well as the path of the file that originated the error #[derive(Debug)] struct IoErrorWrapper { path: PathBuf, inner: Option>, } impl std::error::Error for IoErrorWrapper {} impl fmt::Display for IoErrorWrapper { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { if let Some(inner) = &self.inner { write!(f, "IO Error({}): {}", self.path.display(), inner) } else { write!(f, "IO Error({})", self.path.display()) } } } /// A wrapper around a `File` that remembers the name of the path struct FileWrapper { inner: File, path: PathBuf, } impl FileWrapper { fn open>(path: P) -> Result { let p = path.as_ref(); let f = wrap_io_error!(p, File::open(p))?; Ok(FileWrapper { inner: f, path: p.to_owned(), }) } fn open_at(root: P, dirfd: Fd, path: Q) -> Result where P: AsRef, Q: AsRef, { use rustix::fs::{Mode, OFlags}; let p = root.as_ref().join(path.as_ref()); let fd = wrap_io_error!( p, rustix::fs::openat(dirfd, path.as_ref(), OFlags::RDONLY | OFlags::CLOEXEC, Mode::empty()) )?; Ok(FileWrapper { inner: File::from(fd), path: p, }) } /// Returns the inner file fn inner(self) -> File { self.inner } } impl Read for FileWrapper { fn read(&mut self, buf: &mut [u8]) -> io::Result { wrap_io_error!(self.path, self.inner.read(buf)) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { wrap_io_error!(self.path, self.inner.read_to_end(buf)) } fn read_to_string(&mut self, buf: &mut String) -> io::Result { wrap_io_error!(self.path, self.inner.read_to_string(buf)) } fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { wrap_io_error!(self.path, self.inner.read_exact(buf)) } } impl Seek for FileWrapper { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { wrap_io_error!(self.path, self.inner.seek(pos)) } } /// The main error type for the procfs crate. /// /// For more info, see the [ProcError] type. pub type ProcResult = Result; /// The various error conditions in the procfs crate. /// /// Most of the variants have an `Option` component. If the error root cause was related /// to some operation on a file, the path of this file will be stored in this component. #[derive(Debug)] pub enum ProcError { /// A standard permission denied error. /// /// This will be a common error, since some files in the procfs filesystem are only readable by /// the root user. PermissionDenied(Option), /// This might mean that the process no longer exists, or that your kernel doesn't support the /// feature you are trying to use. NotFound(Option), /// This might mean that a procfs file has incomplete contents. /// /// If you encounter this error, consider retrying the operation. Incomplete(Option), /// Any other IO error (rare). Io(std::io::Error, Option), /// Any other non-IO error (very rare). Other(String), /// This error indicates that some unexpected error occurred. This is a bug. The inner /// [InternalError] struct will contain some more info. /// /// If you ever encounter this error, consider it a bug in the procfs crate and please report /// it on github. InternalError(InternalError), } /// An internal error in the procfs crate /// /// If you encounter this error, consider it a bug and please report it on /// [github](https://github.com/eminence/procfs). /// /// If you compile with the optional `backtrace` feature (disabled by default), /// you can gain access to a stack trace of where the error happened. #[cfg_attr(feature = "serde1", derive(Serialize))] pub struct InternalError { pub msg: String, pub file: &'static str, pub line: u32, #[cfg(feature = "backtrace")] #[cfg_attr(feature = "serde1", serde(skip))] pub backtrace: backtrace::Backtrace, } impl std::fmt::Debug for InternalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "bug at {}:{} (please report this procfs bug)\n{}", self.file, self.line, self.msg ) } } impl std::fmt::Display for InternalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "bug at {}:{} (please report this procfs bug)\n{}", self.file, self.line, self.msg ) } } impl From for ProcError { fn from(io: std::io::Error) -> Self { use std::io::ErrorKind; let kind = io.kind(); let path: Option = io .get_ref() .and_then(|inner| inner.downcast_ref::().map(|inner| inner.path.clone())); match kind { ErrorKind::PermissionDenied => ProcError::PermissionDenied(path), ErrorKind::NotFound => ProcError::NotFound(path), _other => ProcError::Io(io, path), } } } impl From<&'static str> for ProcError { fn from(val: &'static str) -> Self { ProcError::Other(val.to_owned()) } } impl From for ProcError { fn from(val: std::num::ParseIntError) -> Self { ProcError::Other(format!("ParseIntError: {}", val)) } } impl From for ProcError { fn from(e: std::string::ParseError) -> Self { match e {} } } impl std::fmt::Display for ProcError { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { match self { // Variants with paths: ProcError::PermissionDenied(Some(p)) => write!(f, "Permission Denied: {}", p.display()), ProcError::NotFound(Some(p)) => write!(f, "File not found: {}", p.display()), ProcError::Incomplete(Some(p)) => write!(f, "Data incomplete: {}", p.display()), ProcError::Io(inner, Some(p)) => { write!(f, "Unexpected IO error({}): {}", p.display(), inner) } // Variants without paths: ProcError::PermissionDenied(None) => write!(f, "Permission Denied"), ProcError::NotFound(None) => write!(f, "File not found"), ProcError::Incomplete(None) => write!(f, "Data incomplete"), ProcError::Io(inner, None) => write!(f, "Unexpected IO error: {}", inner), ProcError::Other(s) => write!(f, "Unknown error {}", s), ProcError::InternalError(e) => write!(f, "Internal error: {}", e), } } } impl std::error::Error for ProcError {} /// Load average figures. /// /// Load averages are calculated as the number of jobs in the run queue (state R) or waiting for /// disk I/O (state D) averaged over 1, 5, and 15 minutes. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct LoadAverage { /// The one-minute load average pub one: f32, /// The five-minute load average pub five: f32, /// The fifteen-minute load average pub fifteen: f32, /// The number of currently runnable kernel scheduling entities (processes, threads). pub cur: u32, /// The number of kernel scheduling entities that currently exist on the system. pub max: u32, /// The fifth field is the PID of the process that was most recently created on the system. pub latest_pid: u32, } impl LoadAverage { /// Reads load average info from `/proc/loadavg` pub fn new() -> ProcResult { LoadAverage::from_reader(FileWrapper::open("/proc/loadavg")?) } /// Get LoadAverage from a custom Read instead of the default `/proc/loadavg`. pub fn from_reader(r: R) -> ProcResult { let mut reader = BufReader::new(r); let mut line = String::new(); reader.read_to_string(&mut line)?; let mut s = line.split_whitespace(); let one = expect!(f32::from_str(expect!(s.next()))); let five = expect!(f32::from_str(expect!(s.next()))); let fifteen = expect!(f32::from_str(expect!(s.next()))); let curmax = expect!(s.next()); let latest_pid = expect!(u32::from_str(expect!(s.next()))); let mut s = curmax.split('/'); let cur = expect!(u32::from_str(expect!(s.next()))); let max = expect!(u32::from_str(expect!(s.next()))); Ok(LoadAverage { one, five, fifteen, cur, max, latest_pid, }) } } /// Return the number of ticks per second. /// /// This isn't part of the proc file system, but it's a useful thing to have, since several fields /// count in ticks. This is calculated from `sysconf(_SC_CLK_TCK)`. pub fn ticks_per_second() -> std::io::Result { if cfg!(unix) { Ok(rustix::param::clock_ticks_per_second()) } else { panic!("Not supported on non-unix platforms") } } /// The boot time of the system, as a `DateTime` object. /// /// This is calculated from `/proc/stat`. /// /// This function requires the "chrono" features to be enabled (which it is by default). #[cfg(feature = "chrono")] pub fn boot_time() -> ProcResult> { use chrono::TimeZone; let secs = boot_time_secs()?; Ok(chrono::Local.timestamp(secs as i64, 0)) } /// The boottime of the system, in seconds since the epoch /// /// This is calculated from `/proc/stat`. /// #[cfg_attr( not(feature = "chrono"), doc = "If you compile with the optional `chrono` feature, you can use the `boot_time()` method to get the boot time as a `DateTime` object." )] #[cfg_attr( feature = "chrono", doc = "See also [boot_time()] to get the boot time as a `DateTime`" )] pub fn boot_time_secs() -> ProcResult { BOOT_TIME.with(|x| { let mut btime = x.borrow_mut(); if let Some(btime) = *btime { Ok(btime) } else { let stat = KernelStats::new()?; *btime = Some(stat.btime); Ok(stat.btime) } }) } thread_local! { static BOOT_TIME : std::cell::RefCell> = std::cell::RefCell::new(None); } /// Memory page size, in bytes. /// /// This is calculated from `sysconf(_SC_PAGESIZE)`. pub fn page_size() -> std::io::Result { if cfg!(unix) { Ok(rustix::param::page_size() as u64) } else { panic!("Not supported on non-unix platforms") } } /// Possible values for a kernel config option #[derive(Debug, Clone, PartialEq, Eq)] pub enum ConfigSetting { Yes, Module, Value(String), } /// Returns a configuration options used to build the currently running kernel /// /// If CONFIG_KCONFIG_PROC is available, the config is read from `/proc/config.gz`. /// Else look in `/boot/config-$(uname -r)` or `/boot/config` (in that order). /// /// # Notes /// Reading the compress `/proc/config.gz` is only supported if the `flate2` feature is enabled /// (which it is by default). #[cfg_attr(feature = "flate2", doc = "The flate2 feature is currently enabled")] #[cfg_attr(not(feature = "flate2"), doc = "The flate2 feature is NOT currently enabled")] pub fn kernel_config() -> ProcResult> { let reader: Box = if Path::new(PROC_CONFIG_GZ).exists() && cfg!(feature = "flate2") { #[cfg(feature = "flate2")] { let file = FileWrapper::open(PROC_CONFIG_GZ)?; let decoder = flate2::read::GzDecoder::new(file); Box::new(BufReader::new(decoder)) } #[cfg(not(feature = "flate2"))] { unreachable!("flate2 feature not enabled") } } else { let kernel = rustix::process::uname(); let filename = format!("{}-{}", BOOT_CONFIG, kernel.release().to_string_lossy()); match FileWrapper::open(filename) { Ok(file) => Box::new(BufReader::new(file)), Err(e) => match e.kind() { io::ErrorKind::NotFound => { let file = FileWrapper::open(BOOT_CONFIG)?; Box::new(BufReader::new(file)) } _ => return Err(e.into()), }, } }; let mut map = HashMap::new(); for line in reader.lines() { let line = line?; if line.starts_with('#') { continue; } if line.contains('=') { let mut s = line.splitn(2, '='); let name = expect!(s.next()).to_owned(); let value = match expect!(s.next()) { "y" => ConfigSetting::Yes, "m" => ConfigSetting::Module, s => ConfigSetting::Value(s.to_owned()), }; map.insert(name, value); } } Ok(map) } /// The amount of time, measured in ticks, the CPU has been in specific states /// /// These fields are measured in ticks because the underlying data from the kernel is measured in ticks. /// The number of ticks per second can be returned by [`ticks_per_second()`](crate::ticks_per_second) /// and is generally 100 on most systems. /// To convert this value to seconds, you can divide by the tps. There are also convenience methods /// that you can use too. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct CpuTime { /// Ticks spent in user mode pub user: u64, /// Ticks spent in user mode with low priority (nice) pub nice: u64, /// Ticks spent in system mode pub system: u64, /// Ticks spent in the idle state pub idle: u64, /// Ticks waiting for I/O to complete /// /// This value is not reliable, for the following reasons: /// /// 1. The CPU will not wait for I/O to complete; iowait is the time that a /// task is waiting for I/O to complete. When a CPU goes into idle state /// for outstanding task I/O, another task will be scheduled on this CPU. /// /// 2. On a multi-core CPU, this task waiting for I/O to complete is not running /// on any CPU, so the iowait for each CPU is difficult to calculate. /// /// 3. The value in this field may *decrease* in certain conditions. /// /// (Since Linux 2.5.41) pub iowait: Option, /// Ticks servicing interrupts /// /// (Since Linux 2.6.0) pub irq: Option, /// Ticks servicing softirqs /// /// (Since Linux 2.6.0) pub softirq: Option, /// Ticks of stolen time. /// /// Stolen time is the time spent in other operating systems when running in /// a virtualized environment. /// /// (Since Linux 2.6.11) pub steal: Option, /// Ticks spent running a virtual CPU for guest operating systems under control /// of the linux kernel /// /// (Since Linux 2.6.24) pub guest: Option, /// Ticks spent running a niced guest /// /// (Since Linux 2.6.33) pub guest_nice: Option, tps: u64, } impl CpuTime { fn from_str(s: &str) -> ProcResult { let mut s = s.split_whitespace(); // Store this field in the struct so we don't have to attempt to unwrap ticks_per_second() when we convert // from ticks into other time units let tps = crate::ticks_per_second()?; s.next(); let user = from_str!(u64, expect!(s.next())); let nice = from_str!(u64, expect!(s.next())); let system = from_str!(u64, expect!(s.next())); let idle = from_str!(u64, expect!(s.next())); let iowait = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?; let irq = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?; let softirq = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?; let steal = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?; let guest = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?; let guest_nice = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?; Ok(CpuTime { user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, tps, }) } /// Milliseconds spent in user mode pub fn user_ms(&self) -> u64 { let ms_per_tick = 1000 / self.tps; self.user * ms_per_tick } /// Time spent in user mode pub fn user_duration(&self) -> Duration { Duration::from_millis(self.user_ms()) } /// Milliseconds spent in user mode with low priority (nice) pub fn nice_ms(&self) -> u64 { let ms_per_tick = 1000 / self.tps; self.nice * ms_per_tick } /// Time spent in user mode with low priority (nice) pub fn nice_duration(&self) -> Duration { Duration::from_millis(self.nice_ms()) } /// Milliseconds spent in system mode pub fn system_ms(&self) -> u64 { let ms_per_tick = 1000 / self.tps; self.system * ms_per_tick } /// Time spent in system mode pub fn system_duration(&self) -> Duration { Duration::from_millis(self.system_ms()) } /// Milliseconds spent in the idle state pub fn idle_ms(&self) -> u64 { let ms_per_tick = 1000 / self.tps; self.idle * ms_per_tick } /// Time spent in the idle state pub fn idle_duration(&self) -> Duration { Duration::from_millis(self.idle_ms()) } /// Milliseconds spent waiting for I/O to complete pub fn iowait_ms(&self) -> Option { let ms_per_tick = 1000 / self.tps; self.iowait.map(|io| io * ms_per_tick) } /// Time spent waiting for I/O to complete pub fn iowait_duration(&self) -> Option { self.iowait_ms().map(Duration::from_millis) } /// Milliseconds spent servicing interrupts pub fn irq_ms(&self) -> Option { let ms_per_tick = 1000 / self.tps; self.irq.map(|ms| ms * ms_per_tick) } /// Time spent servicing interrupts pub fn irq_duration(&self) -> Option { self.irq_ms().map(Duration::from_millis) } /// Milliseconds spent servicing softirqs pub fn softirq_ms(&self) -> Option { let ms_per_tick = 1000 / self.tps; self.softirq.map(|ms| ms * ms_per_tick) } /// Time spent servicing softirqs pub fn softirq_duration(&self) -> Option { self.softirq_ms().map(Duration::from_millis) } /// Milliseconds of stolen time pub fn steal_ms(&self) -> Option { let ms_per_tick = 1000 / self.tps; self.steal.map(|ms| ms * ms_per_tick) } /// Amount of stolen time pub fn steal_duration(&self) -> Option { self.steal_ms().map(Duration::from_millis) } /// Milliseconds spent running a virtual CPU for guest operating systems under control of the linux kernel pub fn guest_ms(&self) -> Option { let ms_per_tick = 1000 / self.tps; self.guest.map(|ms| ms * ms_per_tick) } /// Time spent running a virtual CPU for guest operating systems under control of the linux kernel pub fn guest_duration(&self) -> Option { self.guest_ms().map(Duration::from_millis) } /// Milliseconds spent running a niced guest pub fn guest_nice_ms(&self) -> Option { let ms_per_tick = 1000 / self.tps; self.guest_nice.map(|ms| ms * ms_per_tick) } /// Time spent running a niced guest pub fn guest_nice_duration(&self) -> Option { self.guest_nice_ms().map(Duration::from_millis) } } /// Kernel/system statistics, from `/proc/stat` #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct KernelStats { /// The amount of time the system spent in various states pub total: CpuTime, /// The amount of time that specific CPUs spent in various states pub cpu_time: Vec, /// The number of context switches that the system underwent pub ctxt: u64, /// Boot time, in number of seconds since the Epoch pub btime: u64, /// Number of forks since boot pub processes: u64, /// Number of processes in runnable state /// /// (Since Linux 2.5.45) pub procs_running: Option, /// Number of processes blocked waiting for I/O /// /// (Since Linux 2.5.45) pub procs_blocked: Option, } impl KernelStats { pub fn new() -> ProcResult { KernelStats::from_reader(FileWrapper::open("/proc/stat")?) } /// Get KernelStatus from a custom Read instead of the default `/proc/stat`. pub fn from_reader(r: R) -> ProcResult { let bufread = BufReader::new(r); let lines = bufread.lines(); let mut total_cpu = None; let mut cpus = Vec::new(); let mut ctxt = None; let mut btime = None; let mut processes = None; let mut procs_running = None; let mut procs_blocked = None; for line in lines { let line = line?; if line.starts_with("cpu ") { total_cpu = Some(CpuTime::from_str(&line)?); } else if line.starts_with("cpu") { cpus.push(CpuTime::from_str(&line)?); } else if let Some(stripped) = line.strip_prefix("ctxt ") { ctxt = Some(from_str!(u64, stripped)); } else if let Some(stripped) = line.strip_prefix("btime ") { btime = Some(from_str!(u64, stripped)); } else if let Some(stripped) = line.strip_prefix("processes ") { processes = Some(from_str!(u64, stripped)); } else if let Some(stripped) = line.strip_prefix("procs_running ") { procs_running = Some(from_str!(u32, stripped)); } else if let Some(stripped) = line.strip_prefix("procs_blocked ") { procs_blocked = Some(from_str!(u32, stripped)); } } Ok(KernelStats { total: expect!(total_cpu), cpu_time: cpus, ctxt: expect!(ctxt), btime: expect!(btime), processes: expect!(processes), procs_running, procs_blocked, }) } } /// Get various virtual memory statistics /// /// Since the exact set of statistics will vary from kernel to kernel, /// and because most of them are not well documented, this function /// returns a HashMap instead of a struct. Consult the kernel source /// code for more details of this data. /// /// This data is taken from the `/proc/vmstat` file. /// /// (since Linux 2.6.0) pub fn vmstat() -> ProcResult> { let file = FileWrapper::open("/proc/vmstat")?; let reader = BufReader::new(file); let mut map = HashMap::new(); for line in reader.lines() { let line = line?; let mut split = line.split_whitespace(); let name = expect!(split.next()); let val = from_str!(i64, expect!(split.next())); map.insert(name.to_owned(), val); } Ok(map) } /// Details about a loaded kernel module /// /// For an example, see the [lsmod.rs](https://github.com/eminence/procfs/tree/master/examples) /// example in the source repo. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct KernelModule { /// The name of the module pub name: String, /// The size of the module pub size: u32, /// The number of references in the kernel to this module. This can be -1 if the module is unloading pub refcount: i32, /// A list of modules that depend on this module. pub used_by: Vec, /// The module state /// /// This will probably always be "Live", but it could also be either "Unloading" or "Loading" pub state: String, } /// Get a list of loaded kernel modules /// /// This corresponds to the data in `/proc/modules`. pub fn modules() -> ProcResult> { // kernel reference: kernel/module.c m_show() let mut map = HashMap::new(); let file = FileWrapper::open("/proc/modules")?; let reader = BufReader::new(file); for line in reader.lines() { let line: String = line?; let mut s = line.split_whitespace(); let name = expect!(s.next()); let size = from_str!(u32, expect!(s.next())); let refcount = from_str!(i32, expect!(s.next())); let used_by: &str = expect!(s.next()); let state = expect!(s.next()); map.insert( name.to_string(), KernelModule { name: name.to_string(), size, refcount, used_by: if used_by == "-" { Vec::new() } else { used_by .split(',') .filter(|s| !s.is_empty()) .map(|s| s.to_string()) .collect() }, state: state.to_string(), }, ); } Ok(map) } /// Get a list of the arguments passed to the Linux kernel at boot time /// /// This corresponds to the data in `/proc/cmdline` pub fn cmdline() -> ProcResult> { let mut buf = String::new(); let mut f = FileWrapper::open("/proc/cmdline")?; f.read_to_string(&mut buf)?; Ok(buf .split(' ') .filter_map(|s| if !s.is_empty() { Some(s.to_string()) } else { None }) .collect()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_statics() { println!("{:?}", *TICKS_PER_SECOND); println!("{:?}", *KERNEL); println!("{:?}", *PAGESIZE); } #[test] fn test_kernel_from_str() { let k = KernelVersion::from_str("1.2.3").unwrap(); assert_eq!(k.major, 1); assert_eq!(k.minor, 2); assert_eq!(k.patch, 3); let k = KernelVersion::from_str("4.9.16-gentoo").unwrap(); assert_eq!(k.major, 4); assert_eq!(k.minor, 9); assert_eq!(k.patch, 16); let k = KernelVersion::from_str("4.9.266-0.1.ac.225.84.332.metal1.x86_64").unwrap(); assert_eq!(k.major, 4); assert_eq!(k.minor, 9); assert_eq!(k.patch, 266); } #[test] fn test_kernel_cmp() { let a = KernelVersion::from_str("1.2.3").unwrap(); let b = KernelVersion::from_str("1.2.3").unwrap(); let c = KernelVersion::from_str("1.2.4").unwrap(); let d = KernelVersion::from_str("1.5.4").unwrap(); let e = KernelVersion::from_str("2.5.4").unwrap(); assert_eq!(a, b); assert!(a < c); assert!(a < d); assert!(a < e); assert!(e > d); assert!(e > c); assert!(e > b); } #[test] fn test_loadavg() { let load = LoadAverage::new().unwrap(); println!("{:?}", load); } #[test] fn test_loadavg_from_reader() -> ProcResult<()> { let load_average = LoadAverage::from_reader("2.63 1.00 1.42 3/4280 2496732".as_bytes())?; assert_eq!(load_average.one, 2.63); assert_eq!(load_average.five, 1.00); assert_eq!(load_average.fifteen, 1.42); assert_eq!(load_average.max, 4280); assert_eq!(load_average.cur, 3); assert_eq!(load_average.latest_pid, 2496732); Ok(()) } #[test] fn test_from_str() -> ProcResult<()> { assert_eq!(from_str!(u8, "12"), 12); assert_eq!(from_str!(u8, "A", 16), 10); Ok(()) } #[test] fn test_from_str_fail() { fn inner() -> ProcResult<()> { let s = "four"; from_str!(u8, s); unreachable!() } assert!(inner().is_err()) } #[test] fn test_kernel_config() { // TRAVIS // we don't have access to the kernel_config on travis, so skip that test there match std::env::var("TRAVIS") { Ok(ref s) if s == "true" => return, _ => {} } if !Path::new(PROC_CONFIG_GZ).exists() && !Path::new(BOOT_CONFIG).exists() { return; } let config = kernel_config().unwrap(); println!("{:#?}", config); } #[test] fn test_file_io_errors() { fn inner>(p: P) -> Result<(), ProcError> { let mut file = FileWrapper::open(p)?; let mut buf = [0; 128]; file.read_exact(&mut buf[0..128])?; Ok(()) } let err = inner("/this_should_not_exist").unwrap_err(); println!("{}", err); match err { ProcError::NotFound(Some(p)) => { assert_eq!(p, Path::new("/this_should_not_exist")); } x => panic!("Unexpected return value: {:?}", x), } match inner("/proc/loadavg") { Err(ProcError::Io(_, Some(p))) => { assert_eq!(p, Path::new("/proc/loadavg")); } x => panic!("Unexpected return value: {:?}", x), } } #[test] fn test_nopanic() { fn _inner() -> ProcResult { let x: Option = None; let y: bool = expect!(x); Ok(y) } let r = _inner(); println!("{:?}", r); assert!(r.is_err()); fn _inner2() -> ProcResult { let _f: std::fs::File = expect!(std::fs::File::open("/doesnotexist")); Ok(true) } let r = _inner2(); println!("{:?}", r); assert!(r.is_err()); } #[cfg(feature = "backtrace")] #[test] fn test_backtrace() { fn _inner() -> ProcResult { let _f: std::fs::File = expect!(std::fs::File::open("/doesnotexist")); Ok(true) } let r = _inner(); println!("{:?}", r); } #[test] fn test_kernel_stat() { let stat = KernelStats::new().unwrap(); println!("{:#?}", stat); // the boottime from KernelStats should match the boottime from /proc/uptime let boottime = boot_time_secs().unwrap(); let diff = (boottime as i32 - stat.btime as i32).abs(); assert!(diff <= 1); let cpuinfo = CpuInfo::new().unwrap(); assert_eq!(cpuinfo.num_cores(), stat.cpu_time.len()); // the sum of each individual CPU should be equal to the total cpu entry // note: on big machines with 128 cores, it seems that the differences can be rather high, // especially when heavily loaded. So this test tolerates a 6000-tick discrepancy // (60 seconds in a 100-tick-per-second kernel) let user: u64 = stat.cpu_time.iter().map(|i| i.user).sum(); let nice: u64 = stat.cpu_time.iter().map(|i| i.nice).sum(); let system: u64 = stat.cpu_time.iter().map(|i| i.system).sum(); assert!( (stat.total.user as i64 - user as i64).abs() < 6000, "sum:{} total:{} diff:{}", stat.total.user, user, stat.total.user - user ); assert!( (stat.total.nice as i64 - nice as i64).abs() < 6000, "sum:{} total:{} diff:{}", stat.total.nice, nice, stat.total.nice - nice ); assert!( (stat.total.system as i64 - system as i64).abs() < 6000, "sum:{} total:{} diff:{}", stat.total.system, system, stat.total.system - system ); let diff = stat.total.idle as i64 - (stat.cpu_time.iter().map(|i| i.idle).sum::() as i64).abs(); assert!(diff < 1000, "idle time difference too high: {}", diff); } #[test] fn test_vmstat() { let stat = vmstat().unwrap(); println!("{:?}", stat); } #[test] fn test_modules() { let mods = modules().unwrap(); for module in mods.values() { println!("{:?}", module); } } #[test] fn tests_tps() { let tps = ticks_per_second().unwrap(); println!("{} ticks per second", tps); } #[test] fn test_cmdline() { let cmdline = cmdline().unwrap(); for argument in cmdline { println!("{}", argument); } } /// Test that our error type can be easily used with the `failure` crate #[test] fn test_failure() { fn inner() -> Result<(), failure::Error> { let _load = crate::LoadAverage::new()?; Ok(()) } let _ = inner(); fn inner2() -> Result<(), failure::Error> { let proc = crate::process::Process::new(1)?; let _io = proc.maps()?; Ok(()) } let _ = inner2(); // Unwrapping this failure should produce a message that looks like: // thread 'tests::test_failure' panicked at 'called `Result::unwrap()` on an `Err` value: PermissionDenied(Some("/proc/1/maps"))', src/libcore/result.rs:997:5 } } procfs-0.14.2/src/locks.rs000064400000000000000000000171451046102023000134640ustar 00000000000000use crate::{FileWrapper, ProcResult}; use std::io::{BufRead, BufReader}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// The type of a file lock #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum LockType { /// A BSD file lock created using `flock` FLock, /// A POSIX byte-range lock created with `fcntl` Posix, /// An Open File Description (ODF) lock created with `fnctl` ODF, /// Some other unknown lock type Other(String), } impl LockType { pub fn as_str(&self) -> &str { match self { LockType::FLock => "FLOCK", LockType::Posix => "POSIX", LockType::ODF => "ODF", LockType::Other(s) => s.as_ref(), } } } impl From<&str> for LockType { fn from(s: &str) -> LockType { match s { "FLOCK" => LockType::FLock, "OFDLCK" => LockType::ODF, "POSIX" => LockType::Posix, x => LockType::Other(x.to_string()), } } } /// The mode of a lock (advisory or mandatory) #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum LockMode { Advisory, Mandatory, /// Some other unknown lock mode Other(String), } impl LockMode { pub fn as_str(&self) -> &str { match self { LockMode::Advisory => "ADVISORY", LockMode::Mandatory => "MANDATORY", LockMode::Other(s) => s.as_ref(), } } } impl From<&str> for LockMode { fn from(s: &str) -> LockMode { match s { "ADVISORY" => LockMode::Advisory, "MANDATORY" => LockMode::Mandatory, x => LockMode::Other(x.to_string()), } } } /// The kind of a lock (read or write) #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum LockKind { /// A read lock (or BSD shared lock) Read, /// A write lock (or a BSD exclusive lock) Write, /// Some other unknown lock kind Other(String), } impl LockKind { pub fn as_str(&self) -> &str { match self { LockKind::Read => "READ", LockKind::Write => "WRITE", LockKind::Other(s) => s.as_ref(), } } } impl From<&str> for LockKind { fn from(s: &str) -> LockKind { match s { "READ" => LockKind::Read, "WRITE" => LockKind::Write, x => LockKind::Other(x.to_string()), } } } #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] /// Details about an individual file lock /// /// See the [`locks`] function. /// /// For an example, see the [lslocks.rs](https://github.com/eminence/procfs/tree/master/examples) /// example in the source repo. pub struct Lock { /// The type of lock pub lock_type: LockType, /// The lock mode (advisory or mandatory) pub mode: LockMode, /// The kind of lock (read or write) pub kind: LockKind, /// The process that owns the lock /// /// Because OFD locks are not owned by a single process (since multiple processes /// may have file descriptors that refer to the same FD), this field may be `None`. /// /// Before kernel 4.14 a bug meant that the PID of of the process that initially /// acquired the lock was displayed instead of `None`. pub pid: Option, /// The major ID of the device containing the FS that contains this lock pub devmaj: u32, /// The minor ID of the device containing the FS that contains this lock pub devmin: u32, /// The inode of the locked file pub inode: u64, /// The offset (in bytes) of the first byte of the lock. /// /// For BSD locks, this value is always 0. pub offset_first: u64, /// The offset (in bytes) of the last byte of the lock. /// /// `None` means the lock extends to the end of the file. For BSD locks, /// the value is always `None`. pub offset_last: Option, } impl Lock { fn from_line(line: &str) -> ProcResult { let mut s = line.split_whitespace(); let _ = expect!(s.next()); let typ = { let t = expect!(s.next()); if t == "->" { // some locks start a "->" which apparently means they are "blocked" (but i'm not sure what that actually means) From::from(expect!(s.next())) } else { From::from(t) } }; let mode = From::from(expect!(s.next())); let kind = From::from(expect!(s.next())); let pid = expect!(s.next()); let disk_inode = expect!(s.next()); let offset_first = from_str!(u64, expect!(s.next())); let offset_last = expect!(s.next()); let mut dis = disk_inode.split(':'); let devmaj = from_str!(u32, expect!(dis.next()), 16); let devmin = from_str!(u32, expect!(dis.next()), 16); let inode = from_str!(u64, expect!(dis.next())); Ok(Lock { lock_type: typ, mode, kind, pid: if pid == "-1" { None } else { Some(from_str!(i32, pid)) }, devmaj, devmin, inode, offset_first, offset_last: if offset_last == "EOF" { None } else { Some(from_str!(u64, offset_last)) }, }) } } /// Get a list of current file locks and leases /// /// Since Linux 4.9, the list of locks is filtered to show just the locks /// for the processes in the PID namespace for which the `/proc` filesystem /// was mounted. pub fn locks() -> ProcResult> { let file = FileWrapper::open("/proc/locks")?; let reader = BufReader::new(file); let mut v = Vec::new(); for line in reader.lines() { let line = line?; v.push(Lock::from_line(&line)?); } Ok(v) } #[cfg(test)] mod tests { use crate::{locks, LockKind, LockMode, LockType}; #[test] fn live() { for lock in locks().unwrap() { println!("{:?}", lock); if let LockType::Other(s) = lock.lock_type { panic!("Found an unknown lock type {:?}", s); } if let LockKind::Other(s) = lock.kind { panic!("Found an unknown lock kind {:?}", s); } if let LockMode::Other(s) = lock.mode { panic!("Found an unknown lock mode {:?}", s); } } } #[test] fn test_blocked() { let data = r#"1: POSIX ADVISORY WRITE 723 00:14:16845 0 EOF 2: FLOCK ADVISORY WRITE 652 00:14:16763 0 EOF 3: FLOCK ADVISORY WRITE 1594 fd:00:396528 0 EOF 4: FLOCK ADVISORY WRITE 1594 fd:00:396527 0 EOF 5: FLOCK ADVISORY WRITE 2851 fd:00:529372 0 EOF 6: POSIX ADVISORY WRITE 1280 00:14:16200 0 0 6: -> POSIX ADVISORY WRITE 1281 00:14:16200 0 0 6: -> POSIX ADVISORY WRITE 1279 00:14:16200 0 0 6: -> POSIX ADVISORY WRITE 1282 00:14:16200 0 0 6: -> POSIX ADVISORY WRITE 1283 00:14:16200 0 0 7: OFDLCK ADVISORY READ -1 00:06:1028 0 EOF 8: FLOCK ADVISORY WRITE 6471 fd:00:529426 0 EOF 9: FLOCK ADVISORY WRITE 6471 fd:00:529424 0 EOF 10: FLOCK ADVISORY WRITE 6471 fd:00:529420 0 EOF 11: FLOCK ADVISORY WRITE 6471 fd:00:529418 0 EOF 12: POSIX ADVISORY WRITE 1279 00:14:23553 0 EOF 13: FLOCK ADVISORY WRITE 6471 fd:00:393838 0 EOF 14: POSIX ADVISORY WRITE 655 00:14:16146 0 EOF"#; for line in data.lines() { super::Lock::from_line(line.trim()).unwrap(); } } } procfs-0.14.2/src/meminfo.rs000064400000000000000000000563121046102023000140020ustar 00000000000000use std::io; use super::{convert_to_kibibytes, FileWrapper, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// This struct reports statistics about memory usage on the system, based on /// the `/proc/meminfo` file. /// /// It is used by `free(1)` to report the amount of free and used memory (both /// physical and swap) on the system as well as the shared memory and /// buffers used by the kernel. Each struct member is generally reported in /// bytes, but a few are unitless values. /// /// Except as noted below, all of the fields have been present since at least /// Linux 2.6.0. Some fields are optional and are present only if the kernel /// was configured with various options; those dependencies are noted in the list. /// /// **Notes** /// /// While the file shows kilobytes (kB; 1 kB equals 1000 B), /// it is actually kibibytes (KiB; 1 KiB equals 1024 B). /// /// This imprecision in /proc/meminfo is known, /// but is not corrected due to legacy concerns - /// programs rely on /proc/meminfo to specify size with the "kB" string. /// /// New fields to this struct may be added at any time (even without a major or minor semver bump). #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[allow(non_snake_case)] #[non_exhaustive] pub struct Meminfo { /// Total usable RAM (i.e., physical RAM minus a few reserved bits and the kernel binary code). pub mem_total: u64, /// The sum of [LowFree](#structfield.low_free) + [HighFree](#structfield.high_free). pub mem_free: u64, /// An estimate of how much memory is available for starting new applications, without swapping. /// /// (since Linux 3.14) pub mem_available: Option, /// Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so). pub buffers: u64, /// In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached. pub cached: u64, /// Memory that once was swapped out, is swapped back in but still also is in the swap /// file. /// /// (If memory pressure is high, these pages don't need to be swapped out again /// because they are already in the swap file. This saves I/O.) pub swap_cached: u64, /// Memory that has been used more recently and usually not reclaimed unless absolutely /// necessary. pub active: u64, /// Memory which has been less recently used. It is more eligible to be reclaimed for other /// purposes. pub inactive: u64, /// [To be documented.] /// /// (since Linux 2.6.28) pub active_anon: Option, /// [To be documented.] /// /// (since Linux 2.6.28) pub inactive_anon: Option, /// [To be documented.] /// /// (since Linux 2.6.28) pub active_file: Option, /// [To be documented.] /// /// (since Linux 2.6.28) pub inactive_file: Option, /// [To be documented.] /// /// (From Linux 2.6.28 to 2.6.30, CONFIG_UNEVICTABLE_LRU was required.) pub unevictable: Option, /// [To be documented.] /// /// (From Linux 2.6.28 to 2.6.30, CONFIG_UNEVICTABLE_LRU was required.) pub mlocked: Option, /// Total amount of highmem. /// /// Highmem is all memory above ~860MB of physical memory. Highmem areas are for use by /// user-space programs, or for the page cache. The kernel must use tricks to access this /// memory, making it slower to access than lowmem. /// /// (Starting with Linux 2.6.19, CONFIG_HIGHMEM is required.) pub high_total: Option, /// Amount of free highmem. /// /// (Starting with Linux 2.6.19, CONFIG_HIGHMEM is required.) pub high_free: Option, /// Total amount of lowmem. /// /// Lowmem is memory which can be used for every thing that highmem can be used for, /// but it is also available for the kernel's use for its own data structures. /// Among many other things, it is where everything from Slab is allocated. /// Bad things happen when you're out of lowmem. /// /// (Starting with Linux 2.6.19, CONFIG_HIGHMEM is required.) pub low_total: Option, /// Amount of free lowmem. /// /// (Starting with Linux 2.6.19, CONFIG_HIGHMEM is required.) pub low_free: Option, /// [To be documented.] /// /// (since Linux 2.6.29. CONFIG_MMU is required.) pub mmap_copy: Option, /// Total amount of swap space available. pub swap_total: u64, /// Amount of swap space that is currently unused. pub swap_free: u64, /// Memory which is waiting to get written back to the disk. pub dirty: u64, /// Memory which is actively being written back to the disk. pub writeback: u64, /// Non-file backed pages mapped into user-space page tables. /// /// (since Linux 2.6.18) pub anon_pages: Option, /// Files which have been mapped into memory (with mmap(2)), such as libraries. pub mapped: u64, /// Amount of memory consumed in tmpfs(5) filesystems. /// /// (since Linux 2.6.32) pub shmem: Option, /// In-kernel data structures cache. pub slab: u64, /// Part of Slab, that cannot be reclaimed on memory pressure. /// /// (since Linux 2.6.19) pub s_reclaimable: Option, /// Part of Slab, that cannot be reclaimed on memory pressure. /// /// (since Linux 2.6.19) pub s_unreclaim: Option, /// Amount of memory allocated to kernel stacks. /// /// (since Linux 2.6.32) pub kernel_stack: Option, /// Amount of memory dedicated to the lowest level of page tables. /// /// (since Linux 2.6.18) pub page_tables: Option, /// [To be documented.] /// /// (CONFIG_QUICKLIST is required. Since Linux 2.6.27) pub quicklists: Option, /// NFS pages sent to the server, but not yet committed to stable storage. /// /// (since Linux 2.6.18) pub nfs_unstable: Option, /// Memory used for block device "bounce buffers". /// /// (since Linux 2.6.18) pub bounce: Option, /// Memory used by FUSE for temporary writeback buffers. /// /// (since Linux 2.6.26) pub writeback_tmp: Option, /// This is the total amount of memory currently available to be allocated on the system, /// expressed in bytes. /// /// This limit is adhered to only if strict overcommit /// accounting is enabled (mode 2 in /proc/sys/vm/overcommit_memory). The limit is calculated /// according to the formula described under /proc/sys/vm/overcommit_memory. For further /// details, see the kernel source file /// [Documentation/vm/overcommit-accounting](https://www.kernel.org/doc/Documentation/vm/overcommit-accounting). /// /// (since Linux 2.6.10) pub commit_limit: Option, /// The amount of memory presently allocated on the system. /// /// The committed memory is a sum of all of the memory which has been allocated /// by processes, even if it has not been "used" by them as of yet. A process which allocates 1GB of memory (using malloc(3) /// or similar), but touches only 300MB of that memory will show up as using only 300MB of memory even if it has the address space /// allocated for the entire 1GB. /// /// This 1GB is memory which has been "committed" to by the VM and can be used at any time by the allocating application. With /// strict overcommit enabled on the system (mode 2 in /proc/sys/vm/overcommit_memory), allocations which would exceed the Committed_AS /// mitLimit will not be permitted. This is useful if one needs to guarantee that processes will not fail due to lack of memory once /// that memory has been successfully allocated. pub committed_as: u64, /// Total size of vmalloc memory area. pub vmalloc_total: u64, /// Amount of vmalloc area which is used. pub vmalloc_used: u64, /// Largest contiguous block of vmalloc area which is free. pub vmalloc_chunk: u64, /// [To be documented.] /// /// (CONFIG_MEMORY_FAILURE is required. Since Linux 2.6.32) pub hardware_corrupted: Option, /// Non-file backed huge pages mapped into user-space page tables. /// /// (CONFIG_TRANSPARENT_HUGEPAGE is required. Since Linux 2.6.38) pub anon_hugepages: Option, /// Memory used by shared memory (shmem) and tmpfs(5) allocated with huge pages /// /// (CONFIG_TRANSPARENT_HUGEPAGE is required. Since Linux 4.8) pub shmem_hugepages: Option, /// Shared memory mapped into user space with huge pages. /// /// (CONFIG_TRANSPARENT_HUGEPAGE is required. Since Linux 4.8) pub shmem_pmd_mapped: Option, /// Total CMA (Contiguous Memory Allocator) pages. /// /// (CONFIG_CMA is required. Since Linux 3.1) pub cma_total: Option, /// Free CMA (Contiguous Memory Allocator) pages. /// /// (CONFIG_CMA is required. Since Linux 3.1) pub cma_free: Option, /// The size of the pool of huge pages. /// /// CONFIG_HUGETLB_PAGE is required.) pub hugepages_total: Option, /// The number of huge pages in the pool that are not yet allocated. /// /// (CONFIG_HUGETLB_PAGE is required.) pub hugepages_free: Option, /// This is the number of huge pages for which a commitment to allocate from the pool has been /// made, but no allocation has yet been made. /// /// These reserved huge pages guarantee that an application will be able to allocate a /// huge page from the pool of huge pages at fault time. /// /// (CONFIG_HUGETLB_PAGE is required. Since Linux 2.6.17) pub hugepages_rsvd: Option, /// This is the number of huge pages in the pool above the value in /proc/sys/vm/nr_hugepages. /// /// The maximum number of surplus huge pages is controlled by /proc/sys/vm/nr_overcommit_hugepages. /// /// (CONFIG_HUGETLB_PAGE is required. Since Linux 2.6.24) pub hugepages_surp: Option, /// The size of huge pages. /// /// (CONFIG_HUGETLB_PAGE is required.) pub hugepagesize: Option, /// Number of bytes of RAM linearly mapped by kernel in 4kB pages. (x86.) /// /// (since Linux 2.6.27) pub direct_map_4k: Option, /// Number of bytes of RAM linearly mapped by kernel in 4MB pages. /// /// (x86 with CONFIG_X86_64 or CONFIG_X86_PAE enabled. Since Linux 2.6.27) pub direct_map_4M: Option, /// Number of bytes of RAM linearly mapped by kernel in 2MB pages. /// /// (x86 with neither CONFIG_X86_64 nor CONFIG_X86_PAE enabled. Since Linux 2.6.27) pub direct_map_2M: Option, /// (x86 with CONFIG_X86_64 and CONFIG_X86_DIRECT_GBPAGES enabled. Since Linux 2.6.27) pub direct_map_1G: Option, /// needs documentation pub hugetlb: Option, /// Memory allocated to the per-cpu alloctor used to back per-cpu allocations. /// /// This stat excludes the cost of metadata. pub per_cpu: Option, /// Kernel allocations that the kernel will attempt to reclaim under memory pressure. /// /// Includes s_reclaimable, and other direct allocations with a shrinker. pub k_reclaimable: Option, /// Undocumented field /// /// (CONFIG_TRANSPARENT_HUGEPAGE is requried. Since Linux 5.4) pub file_pmd_mapped: Option, /// Undocumented field /// /// (CONFIG_TRANSPARENT_HUGEPAGE is required. Since Linux 5.4) pub file_huge_pages: Option, } impl Meminfo { /// Reads and parses the `/proc/meminfo`, returning an error if there are problems. pub fn new() -> ProcResult { let f = FileWrapper::open("/proc/meminfo")?; Meminfo::from_reader(f) } /// Get Meminfo from a custom Read instead of the default `/proc/meminfo`. pub fn from_reader(r: R) -> ProcResult { use std::collections::HashMap; use std::io::{BufRead, BufReader}; let reader = BufReader::new(r); let mut map = HashMap::new(); for line in reader.lines() { let line = expect!(line); if line.is_empty() { continue; } let mut s = line.split_whitespace(); let field = expect!(s.next(), "no field"); let value = expect!(s.next(), "no value"); let unit = s.next(); // optional let value = from_str!(u64, value); let value = if let Some(unit) = unit { convert_to_kibibytes(value, unit)? } else { value }; map.insert(field[..field.len() - 1].to_string(), value); } // use 'remove' to move the value out of the hashmap // if there's anything still left in the map at the end, that // means we probably have a bug/typo, or are out-of-date let meminfo = Meminfo { mem_total: expect!(map.remove("MemTotal")), mem_free: expect!(map.remove("MemFree")), mem_available: map.remove("MemAvailable"), buffers: expect!(map.remove("Buffers")), cached: expect!(map.remove("Cached")), swap_cached: expect!(map.remove("SwapCached")), active: expect!(map.remove("Active")), inactive: expect!(map.remove("Inactive")), active_anon: map.remove("Active(anon)"), inactive_anon: map.remove("Inactive(anon)"), active_file: map.remove("Active(file)"), inactive_file: map.remove("Inactive(file)"), unevictable: map.remove("Unevictable"), mlocked: map.remove("Mlocked"), high_total: map.remove("HighTotal"), high_free: map.remove("HighFree"), low_total: map.remove("LowTotal"), low_free: map.remove("LowFree"), mmap_copy: map.remove("MmapCopy"), swap_total: expect!(map.remove("SwapTotal")), swap_free: expect!(map.remove("SwapFree")), dirty: expect!(map.remove("Dirty")), writeback: expect!(map.remove("Writeback")), anon_pages: map.remove("AnonPages"), mapped: expect!(map.remove("Mapped")), shmem: map.remove("Shmem"), slab: expect!(map.remove("Slab")), s_reclaimable: map.remove("SReclaimable"), s_unreclaim: map.remove("SUnreclaim"), kernel_stack: map.remove("KernelStack"), page_tables: map.remove("PageTables"), quicklists: map.remove("Quicklists"), nfs_unstable: map.remove("NFS_Unstable"), bounce: map.remove("Bounce"), writeback_tmp: map.remove("WritebackTmp"), commit_limit: map.remove("CommitLimit"), committed_as: expect!(map.remove("Committed_AS")), vmalloc_total: expect!(map.remove("VmallocTotal")), vmalloc_used: expect!(map.remove("VmallocUsed")), vmalloc_chunk: expect!(map.remove("VmallocChunk")), hardware_corrupted: map.remove("HardwareCorrupted"), anon_hugepages: map.remove("AnonHugePages"), shmem_hugepages: map.remove("ShmemHugePages"), shmem_pmd_mapped: map.remove("ShmemPmdMapped"), cma_total: map.remove("CmaTotal"), cma_free: map.remove("CmaFree"), hugepages_total: map.remove("HugePages_Total"), hugepages_free: map.remove("HugePages_Free"), hugepages_rsvd: map.remove("HugePages_Rsvd"), hugepages_surp: map.remove("HugePages_Surp"), hugepagesize: map.remove("Hugepagesize"), direct_map_4k: map.remove("DirectMap4k"), direct_map_4M: map.remove("DirectMap4M"), direct_map_2M: map.remove("DirectMap2M"), direct_map_1G: map.remove("DirectMap1G"), k_reclaimable: map.remove("KReclaimable"), per_cpu: map.remove("Percpu"), hugetlb: map.remove("Hugetlb"), file_pmd_mapped: map.remove("FilePmdMapped"), file_huge_pages: map.remove("FileHugePages"), }; if cfg!(test) { assert!(map.is_empty(), "meminfo map is not empty: {:#?}", map); } Ok(meminfo) } } #[cfg(test)] mod test { use super::*; use crate::{kernel_config, KernelVersion}; #[allow(clippy::cognitive_complexity)] #[allow(clippy::blocks_in_if_conditions)] #[test] fn test_meminfo() { // TRAVIS // we don't have access to the kernel_config on travis, so skip that test there match ::std::env::var("TRAVIS") { Ok(ref s) if s == "true" => return, _ => {} } let kernel = KernelVersion::current().unwrap(); let config = kernel_config().ok(); let meminfo = Meminfo::new().unwrap(); println!("{:#?}", meminfo); // for the fields that are only present in some kernel versions, make sure our // actual kernel agrees if kernel >= KernelVersion::new(3, 14, 0) { assert!(meminfo.mem_available.is_some()); } if kernel >= KernelVersion::new(2, 6, 28) { assert!(meminfo.active_anon.is_some()); assert!(meminfo.inactive_anon.is_some()); assert!(meminfo.active_file.is_some()); assert!(meminfo.inactive_file.is_some()); } else { assert!(meminfo.active_anon.is_none()); assert!(meminfo.inactive_anon.is_none()); assert!(meminfo.active_file.is_none()); assert!(meminfo.inactive_file.is_none()); } if kernel >= KernelVersion::new(2, 6, 28) && kernel <= KernelVersion::new(2, 6, 30) && meminfo.unevictable.is_some() { if let Some(ref config) = config { assert!(config.get("CONFIG_UNEVICTABLE_LRU").is_some()); } } if kernel >= KernelVersion::new(2, 6, 19) && config.as_ref().map_or(false, |cfg| cfg.contains_key("CONFIG_HIGHMEM")) { assert!(meminfo.high_total.is_some()); assert!(meminfo.high_free.is_some()); assert!(meminfo.low_total.is_some()); assert!(meminfo.low_free.is_some()); } else { assert!(meminfo.high_total.is_none()); assert!(meminfo.high_free.is_none()); assert!(meminfo.low_total.is_none()); assert!(meminfo.low_free.is_none()); } // Possible bug in procfs documentation: // The man page says that MmapCopy requires CONFIG_MMU, but if you look at the // source, MmapCopy is only included if CONFIG_MMU is *missing*: // https://github.com/torvalds/linux/blob/v4.17/fs/proc/meminfo.c#L80 //if kernel >= KernelVersion::new(2, 6, 29) && config.contains_key("CONFIG_MMU") { // assert!(meminfo.mmap_copy.is_some()); //} else { // assert!(meminfo.mmap_copy.is_none()); //} if kernel >= KernelVersion::new(2, 6, 18) { assert!(meminfo.anon_pages.is_some()); assert!(meminfo.page_tables.is_some()); assert!(meminfo.nfs_unstable.is_some()); assert!(meminfo.bounce.is_some()); } else { assert!(meminfo.anon_pages.is_none()); assert!(meminfo.page_tables.is_none()); assert!(meminfo.nfs_unstable.is_none()); assert!(meminfo.bounce.is_none()); } if kernel >= KernelVersion::new(2, 6, 32) { assert!(meminfo.shmem.is_some()); assert!(meminfo.kernel_stack.is_some()); } else { assert!(meminfo.shmem.is_none()); assert!(meminfo.kernel_stack.is_none()); } if kernel >= KernelVersion::new(2, 6, 19) { assert!(meminfo.s_reclaimable.is_some()); assert!(meminfo.s_unreclaim.is_some()); } else { assert!(meminfo.s_reclaimable.is_none()); assert!(meminfo.s_unreclaim.is_none()); } if kernel >= KernelVersion::new(2, 6, 27) && config .as_ref() .map_or(false, |cfg| cfg.contains_key("CONFIG_QUICKLIST")) { assert!(meminfo.quicklists.is_some()); } else { assert!(meminfo.quicklists.is_none()); } if kernel >= KernelVersion::new(2, 6, 26) { assert!(meminfo.writeback_tmp.is_some()); } else { assert!(meminfo.writeback_tmp.is_none()); } if kernel >= KernelVersion::new(2, 6, 10) { assert!(meminfo.commit_limit.is_some()); } else { assert!(meminfo.commit_limit.is_none()); } if kernel >= KernelVersion::new(2, 6, 32) && config .as_ref() .map_or(std::path::Path::new("/proc/kpagecgroup").exists(), |cfg| { cfg.contains_key("CONFIG_MEMORY_FAILURE") }) { assert!(meminfo.hardware_corrupted.is_some()); } else { assert!(meminfo.hardware_corrupted.is_none()); } if kernel >= KernelVersion::new(2, 6, 38) && config .as_ref() .map_or(false, |cfg| cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE")) { assert!(meminfo.anon_hugepages.is_some()); } else { // SOme distributions may backport this option into older kernels // assert!(meminfo.anon_hugepages.is_none()); } if kernel >= KernelVersion::new(4, 8, 0) && config .as_ref() .map_or(true, |cfg| cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE")) { assert!(meminfo.shmem_hugepages.is_some()); assert!(meminfo.shmem_pmd_mapped.is_some()); } else { assert!(meminfo.shmem_hugepages.is_none()); assert!(meminfo.shmem_pmd_mapped.is_none()); } if kernel >= KernelVersion::new(3, 1, 0) && config.as_ref().map_or(true, |cfg| cfg.contains_key("CONFIG_CMA")) { assert!(meminfo.cma_total.is_some()); assert!(meminfo.cma_free.is_some()); } else { assert!(meminfo.cma_total.is_none()); assert!(meminfo.cma_free.is_none()); } if config .as_ref() .map_or(true, |cfg| cfg.contains_key("CONFIG_HUGETLB_PAGE")) { assert!(meminfo.hugepages_total.is_some()); assert!(meminfo.hugepages_free.is_some()); assert!(meminfo.hugepagesize.is_some()); } else { assert!(meminfo.hugepages_total.is_none()); assert!(meminfo.hugepages_free.is_none()); assert!(meminfo.hugepagesize.is_none()); } if kernel >= KernelVersion::new(2, 6, 17) && config .as_ref() .map_or(true, |cfg| cfg.contains_key("CONFIG_HUGETLB_PAGE")) { assert!(meminfo.hugepages_rsvd.is_some()); } else { assert!(meminfo.hugepages_rsvd.is_none()); } if kernel >= KernelVersion::new(2, 6, 24) && config .as_ref() .map_or(true, |cfg| cfg.contains_key("CONFIG_HUGETLB_PAGE")) { assert!(meminfo.hugepages_surp.is_some()); } else { assert!(meminfo.hugepages_surp.is_none()); } } } procfs-0.14.2/src/net.rs000064400000000000000000000624211046102023000131340ustar 00000000000000// Don't throw clippy warnings for manual string stripping. // The suggested fix with `strip_prefix` removes support for Rust 1.33 and 1.38 #![allow(clippy::manual_strip)] //! Information about the networking layer. //! //! This module corresponds to the `/proc/net` directory and contains various information about the //! networking layer. //! //! # Example //! //! Here's an example that will print out all of the open and listening TCP sockets, and their //! corresponding processes, if know. This mimics the "netstat" utility, but for TCP only. You //! can run this example yourself with: //! //! > cargo run --example=netstat //! //! ```rust //! # use procfs::process::{FDTarget, Stat}; //! # use std::collections::HashMap; //! let all_procs = procfs::process::all_processes().unwrap(); //! //! // build up a map between socket inodes and process stat info: //! let mut map: HashMap = HashMap::new(); //! for p in all_procs { //! let process = p.unwrap(); //! if let (Ok(stat), Ok(fds)) = (process.stat(), process.fd()) { //! for fd in fds { //! if let FDTarget::Socket(inode) = fd.unwrap().target { //! map.insert(inode, stat.clone()); //! } //! } //! } //! } //! //! // get the tcp table //! let tcp = procfs::net::tcp().unwrap(); //! let tcp6 = procfs::net::tcp6().unwrap(); //! println!("{:<26} {:<26} {:<15} {:<8} {}", "Local address", "Remote address", "State", "Inode", "PID/Program name"); //! for entry in tcp.into_iter().chain(tcp6) { //! // find the process (if any) that has an open FD to this entry's inode //! let local_address = format!("{}", entry.local_address); //! let remote_addr = format!("{}", entry.remote_address); //! let state = format!("{:?}", entry.state); //! if let Some(stat) = map.get(&entry.inode) { //! println!("{:<26} {:<26} {:<15} {:<12} {}/{}", local_address, remote_addr, state, entry.inode, stat.pid, stat.comm); //! } else { //! // We might not always be able to find the process associated with this socket //! println!("{:<26} {:<26} {:<15} {:<12} -", local_address, remote_addr, state, entry.inode); //! } //! } use crate::from_iter; use crate::ProcResult; use std::collections::HashMap; use crate::FileWrapper; use bitflags::bitflags; use byteorder::{ByteOrder, NativeEndian, NetworkEndian}; use std::io::{BufRead, BufReader, Read}; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::{path::PathBuf, str::FromStr}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum TcpState { Established = 1, SynSent, SynRecv, FinWait1, FinWait2, TimeWait, Close, CloseWait, LastAck, Listen, Closing, NewSynRecv, } impl TcpState { pub fn from_u8(num: u8) -> Option { match num { 0x01 => Some(TcpState::Established), 0x02 => Some(TcpState::SynSent), 0x03 => Some(TcpState::SynRecv), 0x04 => Some(TcpState::FinWait1), 0x05 => Some(TcpState::FinWait2), 0x06 => Some(TcpState::TimeWait), 0x07 => Some(TcpState::Close), 0x08 => Some(TcpState::CloseWait), 0x09 => Some(TcpState::LastAck), 0x0A => Some(TcpState::Listen), 0x0B => Some(TcpState::Closing), 0x0C => Some(TcpState::NewSynRecv), _ => None, } } pub fn to_u8(&self) -> u8 { match self { TcpState::Established => 0x01, TcpState::SynSent => 0x02, TcpState::SynRecv => 0x03, TcpState::FinWait1 => 0x04, TcpState::FinWait2 => 0x05, TcpState::TimeWait => 0x06, TcpState::Close => 0x07, TcpState::CloseWait => 0x08, TcpState::LastAck => 0x09, TcpState::Listen => 0x0A, TcpState::Closing => 0x0B, TcpState::NewSynRecv => 0x0C, } } } #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum UdpState { Established = 1, Close = 7, } impl UdpState { pub fn from_u8(num: u8) -> Option { match num { 0x01 => Some(UdpState::Established), 0x07 => Some(UdpState::Close), _ => None, } } pub fn to_u8(&self) -> u8 { match self { UdpState::Established => 0x01, UdpState::Close => 0x07, } } } #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum UnixState { UNCONNECTED = 1, CONNECTING = 2, CONNECTED = 3, DISCONNECTING = 4, } impl UnixState { pub fn from_u8(num: u8) -> Option { match num { 0x01 => Some(UnixState::UNCONNECTED), 0x02 => Some(UnixState::CONNECTING), 0x03 => Some(UnixState::CONNECTED), 0x04 => Some(UnixState::DISCONNECTING), _ => None, } } pub fn to_u8(&self) -> u8 { match self { UnixState::UNCONNECTED => 0x01, UnixState::CONNECTING => 0x02, UnixState::CONNECTED => 0x03, UnixState::DISCONNECTING => 0x04, } } } /// An entry in the TCP socket table #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct TcpNetEntry { pub local_address: SocketAddr, pub remote_address: SocketAddr, pub state: TcpState, pub rx_queue: u32, pub tx_queue: u32, pub inode: u64, } /// An entry in the UDP socket table #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct UdpNetEntry { pub local_address: SocketAddr, pub remote_address: SocketAddr, pub state: UdpState, pub rx_queue: u32, pub tx_queue: u32, pub inode: u64, } /// An entry in the Unix socket table #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct UnixNetEntry { /// The number of users of the socket pub ref_count: u32, /// The socket type. /// /// Possible values are `SOCK_STREAM`, `SOCK_DGRAM`, or `SOCK_SEQPACKET`. These constants can /// be found in the libc crate. pub socket_type: u16, /// The state of the socket pub state: UnixState, /// The inode number of the socket pub inode: u64, /// The bound pathname (if any) of the socket. /// /// Sockets in the abstract namespace are included, and are shown with a path that commences /// with the '@' character. pub path: Option, } /// Parses an address in the form 00010203:1234 /// /// Also supports IPv6 fn parse_addressport_str(s: &str) -> ProcResult { let mut las = s.split(':'); let ip_part = expect!(las.next(), "ip_part"); let port = expect!(las.next(), "port"); let port = from_str!(u16, port, 16); if ip_part.len() == 8 { let bytes = expect!(hex::decode(ip_part)); let ip_u32 = NetworkEndian::read_u32(&bytes); let ip = Ipv4Addr::new( (ip_u32 & 0xff) as u8, ((ip_u32 & 0xff << 8) >> 8) as u8, ((ip_u32 & 0xff << 16) >> 16) as u8, ((ip_u32 & 0xff << 24) >> 24) as u8, ); Ok(SocketAddr::V4(SocketAddrV4::new(ip, port))) } else if ip_part.len() == 32 { let bytes = expect!(hex::decode(ip_part)); let ip_a = NativeEndian::read_u32(&bytes[0..]); let ip_b = NativeEndian::read_u32(&bytes[4..]); let ip_c = NativeEndian::read_u32(&bytes[8..]); let ip_d = NativeEndian::read_u32(&bytes[12..]); let ip = Ipv6Addr::new( ((ip_a >> 16) & 0xffff) as u16, (ip_a & 0xffff) as u16, ((ip_b >> 16) & 0xffff) as u16, (ip_b & 0xffff) as u16, ((ip_c >> 16) & 0xffff) as u16, (ip_c & 0xffff) as u16, ((ip_d >> 16) & 0xffff) as u16, (ip_d & 0xffff) as u16, ); Ok(SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0))) } else { Err(build_internal_error!(format!( "Unable to parse {:?} as an address:port", s ))) } } /// Reads TCP socket table from the provided `reader`. pub fn read_tcp_table(reader: BufReader) -> ProcResult> { let mut vec = Vec::new(); // first line is a header we need to skip for line in reader.lines().skip(1) { let line = line?; let mut s = line.split_whitespace(); s.next(); let local_address = expect!(s.next(), "tcp::local_address"); let rem_address = expect!(s.next(), "tcp::rem_address"); let state = expect!(s.next(), "tcp::st"); let mut tx_rx_queue = expect!(s.next(), "tcp::tx_queue:rx_queue").splitn(2, ':'); let tx_queue = from_str!(u32, expect!(tx_rx_queue.next(), "tcp::tx_queue"), 16); let rx_queue = from_str!(u32, expect!(tx_rx_queue.next(), "tcp::rx_queue"), 16); s.next(); // skip tr and tm->when s.next(); // skip retrnsmt s.next(); // skip uid s.next(); // skip timeout let inode = expect!(s.next(), "tcp::inode"); vec.push(TcpNetEntry { local_address: parse_addressport_str(local_address)?, remote_address: parse_addressport_str(rem_address)?, rx_queue, tx_queue, state: expect!(TcpState::from_u8(from_str!(u8, state, 16))), inode: from_str!(u64, inode), }); } Ok(vec) } /// Reads UDP socket table from the provided `reader`. pub fn read_udp_table(reader: BufReader) -> ProcResult> { let mut vec = Vec::new(); // first line is a header we need to skip for line in reader.lines().skip(1) { let line = line?; let mut s = line.split_whitespace(); s.next(); let local_address = expect!(s.next(), "udp::local_address"); let rem_address = expect!(s.next(), "udp::rem_address"); let state = expect!(s.next(), "udp::st"); let mut tx_rx_queue = expect!(s.next(), "udp::tx_queue:rx_queue").splitn(2, ':'); let tx_queue: u32 = from_str!(u32, expect!(tx_rx_queue.next(), "udp::tx_queue"), 16); let rx_queue: u32 = from_str!(u32, expect!(tx_rx_queue.next(), "udp::rx_queue"), 16); s.next(); // skip tr and tm->when s.next(); // skip retrnsmt s.next(); // skip uid s.next(); // skip timeout let inode = expect!(s.next(), "udp::inode"); vec.push(UdpNetEntry { local_address: parse_addressport_str(local_address)?, remote_address: parse_addressport_str(rem_address)?, rx_queue, tx_queue, state: expect!(UdpState::from_u8(from_str!(u8, state, 16))), inode: from_str!(u64, inode), }); } Ok(vec) } /// Reads the tcp socket table pub fn tcp() -> ProcResult> { let file = FileWrapper::open("/proc/net/tcp")?; read_tcp_table(BufReader::new(file)) } /// Reads the tcp6 socket table pub fn tcp6() -> ProcResult> { let file = FileWrapper::open("/proc/net/tcp6")?; read_tcp_table(BufReader::new(file)) } /// Reads the udp socket table pub fn udp() -> ProcResult> { let file = FileWrapper::open("/proc/net/udp")?; read_udp_table(BufReader::new(file)) } /// Reads the udp6 socket table pub fn udp6() -> ProcResult> { let file = FileWrapper::open("/proc/net/udp6")?; read_udp_table(BufReader::new(file)) } /// Reads the unix socket table pub fn unix() -> ProcResult> { let file = FileWrapper::open("/proc/net/unix")?; let reader = BufReader::new(file); let mut vec = Vec::new(); // first line is a header we need to skip for line in reader.lines().skip(1) { let line = line?; let mut s = line.split_whitespace(); s.next(); // skip table slot number let ref_count = from_str!(u32, expect!(s.next()), 16); s.next(); // skip protocol, always zero s.next(); // skip internal kernel flags let socket_type = from_str!(u16, expect!(s.next()), 16); let state = from_str!(u8, expect!(s.next()), 16); let inode = from_str!(u64, expect!(s.next())); let path = s.next().map(PathBuf::from); vec.push(UnixNetEntry { ref_count, socket_type, inode, state: expect!(UnixState::from_u8(state)), path, }); } Ok(vec) } /// An entry in the ARP table #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct ARPEntry { /// IPv4 address pub ip_address: Ipv4Addr, /// Hardware type /// /// This will almost always be ETHER (or maybe INFINIBAND) pub hw_type: ARPHardware, /// Internal kernel flags pub flags: ARPFlags, /// MAC Address pub hw_address: Option<[u8; 6]>, /// Device name pub device: String, } bitflags! { /// Hardware type for an ARP table entry. // source: include/uapi/linux/if_arp.h #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct ARPHardware: u32 { /// NET/ROM pseudo const NETROM = 0; /// Ethernet const ETHER = 1; /// Experimental ethernet const EETHER = 2; /// AX.25 Level 2 const AX25 = 3; /// PROnet token ring const PRONET = 4; /// Chaosnet const CHAOS = 5; /// IEEE 802.2 Ethernet/TR/TB const IEEE802 = 6; /// Arcnet const ARCNET = 7; /// APPLEtalk const APPLETLK = 8; /// Frame Relay DLCI const DLCI = 15; /// ATM const ATM = 19; /// Metricom STRIP const METRICOM = 23; //// IEEE 1394 IPv4 - RFC 2734 const IEEE1394 = 24; /// EUI-64 const EUI64 = 27; /// InfiniBand const INFINIBAND = 32; } } bitflags! { /// Flags for ARP entries // source: include/uapi/linux/if_arp.h #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct ARPFlags: u32 { /// Completed entry const COM = 0x02; /// Permanent entry const PERM = 0x04; /// Publish entry const PUBL = 0x08; /// Has requested trailers const USETRAILERS = 0x10; /// Want to use a netmask (only for proxy entries) const NETMASK = 0x20; // Don't answer this address const DONTPUB = 0x40; } } /// Reads the ARP table pub fn arp() -> ProcResult> { let file = FileWrapper::open("/proc/net/arp")?; let reader = BufReader::new(file); let mut vec = Vec::new(); // First line is a header we need to skip for line in reader.lines().skip(1) { // Check if there might have been an IO error. let line = line?; let mut line = line.split_whitespace(); let ip_address = expect!(Ipv4Addr::from_str(expect!(line.next()))); let hw = from_str!(u32, &expect!(line.next())[2..], 16); let hw = ARPHardware::from_bits_truncate(hw); let flags = from_str!(u32, &expect!(line.next())[2..], 16); let flags = ARPFlags::from_bits_truncate(flags); let mac = expect!(line.next()); let mut mac: Vec> = mac.split(':').map(|s| Ok(from_str!(u8, s, 16))).collect(); let mac = if mac.len() == 6 { let mac_block_f = mac.pop().unwrap()?; let mac_block_e = mac.pop().unwrap()?; let mac_block_d = mac.pop().unwrap()?; let mac_block_c = mac.pop().unwrap()?; let mac_block_b = mac.pop().unwrap()?; let mac_block_a = mac.pop().unwrap()?; if mac_block_a == 0 && mac_block_b == 0 && mac_block_c == 0 && mac_block_d == 0 && mac_block_e == 0 && mac_block_f == 0 { None } else { Some([ mac_block_a, mac_block_b, mac_block_c, mac_block_d, mac_block_e, mac_block_f, ]) } } else { None }; // mask is always "*" let _mask = expect!(line.next()); let dev = expect!(line.next()); vec.push(ARPEntry { ip_address, hw_type: hw, flags, hw_address: mac, device: dev.to_string(), }) } Ok(vec) } /// General statistics for a network interface/device /// /// For an example, see the [interface_stats.rs](https://github.com/eminence/procfs/tree/master/examples) /// example in the source repo. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct DeviceStatus { /// Name of the interface pub name: String, /// Total bytes received pub recv_bytes: u64, /// Total packets received pub recv_packets: u64, /// Bad packets received pub recv_errs: u64, /// Packets dropped pub recv_drop: u64, /// Fifo overrun pub recv_fifo: u64, /// Frame alignment errors pub recv_frame: u64, /// Number of compressed packets received pub recv_compressed: u64, /// Number of multicast packets received pub recv_multicast: u64, /// Total bytes transmitted pub sent_bytes: u64, /// Total packets transmitted pub sent_packets: u64, /// Number of transmission errors pub sent_errs: u64, /// Number of packets dropped during transmission pub sent_drop: u64, pub sent_fifo: u64, /// Number of collisions pub sent_colls: u64, /// Number of packets not sent due to carrier errors pub sent_carrier: u64, /// Number of compressed packets transmitted pub sent_compressed: u64, } impl DeviceStatus { fn from_str(s: &str) -> ProcResult { let mut split = s.split_whitespace(); let name: String = expect!(from_iter(&mut split)); let recv_bytes = expect!(from_iter(&mut split)); let recv_packets = expect!(from_iter(&mut split)); let recv_errs = expect!(from_iter(&mut split)); let recv_drop = expect!(from_iter(&mut split)); let recv_fifo = expect!(from_iter(&mut split)); let recv_frame = expect!(from_iter(&mut split)); let recv_compressed = expect!(from_iter(&mut split)); let recv_multicast = expect!(from_iter(&mut split)); let sent_bytes = expect!(from_iter(&mut split)); let sent_packets = expect!(from_iter(&mut split)); let sent_errs = expect!(from_iter(&mut split)); let sent_drop = expect!(from_iter(&mut split)); let sent_fifo = expect!(from_iter(&mut split)); let sent_colls = expect!(from_iter(&mut split)); let sent_carrier = expect!(from_iter(&mut split)); let sent_compressed = expect!(from_iter(&mut split)); Ok(DeviceStatus { name: name.trim_end_matches(':').to_owned(), recv_bytes, recv_packets, recv_errs, recv_drop, recv_fifo, recv_frame, recv_compressed, recv_multicast, sent_bytes, sent_packets, sent_errs, sent_drop, sent_fifo, sent_colls, sent_carrier, sent_compressed, }) } } /// Returns basic network device statistics for all interfaces /// /// This data is from the `/proc/net/dev` file. /// /// For an example, see the [interface_stats.rs](https://github.com/eminence/procfs/tree/master/examples) /// example in the source repo. pub fn dev_status() -> ProcResult> { let file = FileWrapper::open("/proc/net/dev")?; let buf = BufReader::new(file); let mut map = HashMap::new(); // the first two lines are headers, so skip them for line in buf.lines().skip(2) { let dev = DeviceStatus::from_str(&line?)?; map.insert(dev.name.clone(), dev); } Ok(map) } /// An entry in the ipv4 route table #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct RouteEntry { /// Interface to which packets for this route will be sent pub iface: String, /// The destination network or destination host pub destination: Ipv4Addr, pub gateway: Ipv4Addr, pub flags: u16, /// Number of references to this route pub refcnt: u16, /// Count of lookups for the route pub in_use: u16, /// The 'distance' to the target (usually counted in hops) pub metrics: u32, pub mask: Ipv4Addr, /// Default maximum transmission unit for TCP connections over this route pub mtu: u32, /// Default window size for TCP connections over this route pub window: u32, /// Initial RTT (Round Trip Time) pub irtt: u32, } /// Reads the ipv4 route table /// /// This data is from the `/proc/net/route` file pub fn route() -> ProcResult> { let file = FileWrapper::open("/proc/net/route")?; let reader = BufReader::new(file); let mut vec = Vec::new(); // First line is a header we need to skip for line in reader.lines().skip(1) { // Check if there might have been an IO error. let line = line?; let mut line = line.split_whitespace(); // network interface name, e.g. eth0 let iface = expect!(line.next()); let destination = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into(); let gateway = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into(); let flags = from_str!(u16, expect!(line.next()), 16); let refcnt = from_str!(u16, expect!(line.next()), 10); let in_use = from_str!(u16, expect!(line.next()), 10); let metrics = from_str!(u32, expect!(line.next()), 10); let mask = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into(); let mtu = from_str!(u32, expect!(line.next()), 10); let window = from_str!(u32, expect!(line.next()), 10); let irtt = from_str!(u32, expect!(line.next()), 10); vec.push(RouteEntry { iface: iface.to_string(), destination, gateway, flags, refcnt, in_use, metrics, mask, mtu, window, irtt, }); } Ok(vec) } #[cfg(test)] mod tests { use super::*; use std::net::IpAddr; #[test] fn test_parse_ipaddr() { use std::str::FromStr; let addr = parse_addressport_str("0100007F:1234").unwrap(); assert_eq!(addr.port(), 0x1234); match addr.ip() { IpAddr::V4(addr) => assert_eq!(addr, Ipv4Addr::new(127, 0, 0, 1)), _ => panic!("Not IPv4"), } // When you connect to [2a00:1450:4001:814::200e]:80 (ipv6.google.com) the entry with // 5014002A14080140000000000E200000:0050 remote endpoint is created in /proc/net/tcp6 // on Linux 4.19. let addr = parse_addressport_str("5014002A14080140000000000E200000:0050").unwrap(); assert_eq!(addr.port(), 80); match addr.ip() { IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::from_str("2a00:1450:4001:814::200e").unwrap()), _ => panic!("Not IPv6"), } // IPv6 test case from https://stackoverflow.com/questions/41940483/parse-ipv6-addresses-from-proc-net-tcp6-python-2-7/41948004#41948004 let addr = parse_addressport_str("B80D01200000000067452301EFCDAB89:0").unwrap(); assert_eq!(addr.port(), 0); match addr.ip() { IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::from_str("2001:db8::123:4567:89ab:cdef").unwrap()), _ => panic!("Not IPv6"), } let addr = parse_addressport_str("1234:1234"); assert!(addr.is_err()); } #[test] fn test_tcpstate_from() { assert_eq!(TcpState::from_u8(0xA).unwrap(), TcpState::Listen); } #[test] fn test_tcp() { for entry in tcp().unwrap() { println!("{:?}", entry); assert_eq!(entry.state, TcpState::from_u8(entry.state.to_u8()).unwrap()); } } #[test] fn test_tcp6() { for entry in tcp6().unwrap() { println!("{:?}", entry); assert_eq!(entry.state, TcpState::from_u8(entry.state.to_u8()).unwrap()); } } #[test] fn test_udp() { for entry in udp().unwrap() { println!("{:?}", entry); assert_eq!(entry.state, UdpState::from_u8(entry.state.to_u8()).unwrap()); } } #[test] fn test_udp6() { for entry in udp6().unwrap() { println!("{:?}", entry); } } #[test] fn test_unix() { for entry in unix().unwrap() { println!("{:?}", entry); } } #[test] fn test_dev_status() { let status = dev_status().unwrap(); println!("{:#?}", status); } #[test] fn test_arp() { for entry in arp().unwrap() { println!("{:?}", entry); } } #[test] fn test_route() { for entry in route().unwrap() { println!("{:?}", entry); } } } procfs-0.14.2/src/pressure.rs000064400000000000000000000156721046102023000142240ustar 00000000000000//! Pressure stall information retreived from `/proc/pressure/cpu`, //! `/proc/pressure/memory` and `/proc/pressure/io` //! may not be available on kernels older than 4.20.0 //! For reference: //! //! See also: use crate::{ProcError, ProcResult}; use std::collections::HashMap; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// Pressure stall information for either CPU, memory, or IO. /// /// See also: #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct PressureRecord { /// 10 second window /// /// The percentage of time, over a 10 second window, that either some or all tasks were stalled /// waiting for a resource. pub avg10: f32, /// 60 second window /// /// The percentage of time, over a 60 second window, that either some or all tasks were stalled /// waiting for a resource. pub avg60: f32, /// 300 second window /// /// The percentage of time, over a 300 second window, that either some or all tasks were stalled /// waiting for a resource. pub avg300: f32, /// Total stall time (in microseconds). pub total: u64, } /// CPU pressure information #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct CpuPressure { pub some: PressureRecord, } impl CpuPressure { /// Get CPU pressure information pub fn new() -> ProcResult { use std::fs::File; use std::io::{BufRead, BufReader}; let file = File::open("/proc/pressure/cpu")?; let mut reader = BufReader::new(file); let mut some = String::new(); reader.read_line(&mut some)?; Ok(CpuPressure { some: parse_pressure_record(&some)?, }) } } /// Memory pressure information #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct MemoryPressure { /// This record indicates the share of time in which at least some tasks are stalled pub some: PressureRecord, /// This record indicates this share of time in which all non-idle tasks are stalled /// simultaneously. pub full: PressureRecord, } impl MemoryPressure { /// Get memory pressure information pub fn new() -> ProcResult { let (some, full) = get_pressure("memory")?; Ok(MemoryPressure { some, full }) } } /// IO pressure information #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct IoPressure { /// This record indicates the share of time in which at least some tasks are stalled pub some: PressureRecord, /// This record indicates this share of time in which all non-idle tasks are stalled /// simultaneously. pub full: PressureRecord, } impl IoPressure { /// Get IO pressure information pub fn new() -> ProcResult { let (some, full) = get_pressure("io")?; Ok(IoPressure { some, full }) } } fn get_f32(map: &HashMap<&str, &str>, value: &str) -> ProcResult { map.get(value).map_or_else( || Err(ProcError::Incomplete(None)), |v| v.parse::().map_err(|_| ProcError::Incomplete(None)), ) } fn get_total(map: &HashMap<&str, &str>) -> ProcResult { map.get("total").map_or_else( || Err(ProcError::Incomplete(None)), |v| v.parse::().map_err(|_| ProcError::Incomplete(None)), ) } fn parse_pressure_record(line: &str) -> ProcResult { let mut parsed = HashMap::new(); if !line.starts_with("some") && !line.starts_with("full") { return Err(ProcError::Incomplete(None)); } let values = &line[5..]; for kv_str in values.split_whitespace() { let kv_split = kv_str.split('='); let vec: Vec<&str> = kv_split.collect(); if vec.len() == 2 { parsed.insert(vec[0], vec[1]); } } Ok(PressureRecord { avg10: get_f32(&parsed, "avg10")?, avg60: get_f32(&parsed, "avg60")?, avg300: get_f32(&parsed, "avg300")?, total: get_total(&parsed)?, }) } fn get_pressure(pressure_file: &str) -> ProcResult<(PressureRecord, PressureRecord)> { use std::fs::File; use std::io::{BufRead, BufReader}; let file = File::open(format!("/proc/pressure/{}", pressure_file))?; let mut reader = BufReader::new(file); let mut some = String::new(); reader.read_line(&mut some)?; let mut full = String::new(); reader.read_line(&mut full)?; Ok((parse_pressure_record(&some)?, parse_pressure_record(&full)?)) } #[cfg(test)] mod test { use super::*; use std::f32::EPSILON; use std::path::Path; #[allow(clippy::manual_range_contains)] fn valid_percentage(value: f32) -> bool { value >= 0.00 && value < 100.0 } #[test] fn test_parse_pressure_record() { let record = parse_pressure_record("full avg10=2.10 avg60=0.12 avg300=0.00 total=391926").unwrap(); assert!(record.avg10 - 2.10 < EPSILON); assert!(record.avg60 - 0.12 < EPSILON); assert!(record.avg300 - 0.00 < EPSILON); assert_eq!(record.total, 391_926); } #[test] fn test_parse_pressure_record_errs() { assert!(parse_pressure_record("avg10=2.10 avg60=0.12 avg300=0.00 total=391926").is_err()); assert!(parse_pressure_record("some avg10=2.10 avg300=0.00 total=391926").is_err()); assert!(parse_pressure_record("some avg10=2.10 avg60=0.00 avg300=0.00").is_err()); } #[test] fn test_mem_pressure() { if !Path::new("/proc/pressure/memory").exists() { return; } let mem_psi = MemoryPressure::new().unwrap(); assert!(valid_percentage(mem_psi.some.avg10)); assert!(valid_percentage(mem_psi.some.avg60)); assert!(valid_percentage(mem_psi.some.avg300)); assert!(valid_percentage(mem_psi.full.avg10)); assert!(valid_percentage(mem_psi.full.avg60)); assert!(valid_percentage(mem_psi.full.avg300)); } #[test] fn test_io_pressure() { if !Path::new("/proc/pressure/io").exists() { return; } let io_psi = IoPressure::new().unwrap(); assert!(valid_percentage(io_psi.some.avg10)); assert!(valid_percentage(io_psi.some.avg60)); assert!(valid_percentage(io_psi.some.avg300)); assert!(valid_percentage(io_psi.full.avg10)); assert!(valid_percentage(io_psi.full.avg60)); assert!(valid_percentage(io_psi.full.avg300)); } #[test] fn test_cpu_pressure() { if !Path::new("/proc/pressure/cpu").exists() { return; } let cpu_psi = CpuPressure::new().unwrap(); assert!(valid_percentage(cpu_psi.some.avg10)); assert!(valid_percentage(cpu_psi.some.avg60)); assert!(valid_percentage(cpu_psi.some.avg300)); } } procfs-0.14.2/src/process/limit.rs000064400000000000000000000272301046102023000151410ustar 00000000000000use crate::{FileWrapper, ProcError, ProcResult}; use std::collections::HashMap; use std::io::{BufRead, BufReader, Read}; use std::str::FromStr; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; impl crate::process::Process { /// Return the limits for this process pub fn limits(&self) -> ProcResult { let file = FileWrapper::open_at(&self.root, &self.fd, "limits")?; Limits::from_reader(file) } } /// Process limits /// /// For more details about each of these limits, see the `getrlimit` man page. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Limits { /// Max Cpu Time /// /// This is a limit, in seconds, on the amount of CPU time that the process can consume. pub max_cpu_time: Limit, /// Max file size /// /// This is the maximum size in bytes of files that the process may create. pub max_file_size: Limit, /// Max data size /// /// This is the maximum size of the process's data segment (initialized data, uninitialized /// data, and heap). pub max_data_size: Limit, /// Max stack size /// /// This is the maximum size of the process stack, in bytes. pub max_stack_size: Limit, /// Max core file size /// /// This is the maximum size of a *core* file in bytes that the process may dump. pub max_core_file_size: Limit, /// Max resident set /// /// This is a limit (in bytes) on the process's resident set (the number of virtual pages /// resident in RAM). pub max_resident_set: Limit, /// Max processes /// /// This is a limit on the number of extant process (or, more precisely on Linux, threads) for /// the real user rID of the calling process. pub max_processes: Limit, /// Max open files /// /// This specifies a value one greater than the maximum file descriptor number that can be /// opened by this process. pub max_open_files: Limit, /// Max locked memory /// /// This is the maximum number of bytes of memory that may be locked into RAM. pub max_locked_memory: Limit, /// Max address space /// /// This is the maximum size of the process's virtual memory (address space). pub max_address_space: Limit, /// Max file locks /// /// This is a limit on the combined number of flock locks and fcntl leases that this process /// may establish. pub max_file_locks: Limit, /// Max pending signals /// /// This is a limit on the number of signals that may be queued for the real user rID of the /// calling process. pub max_pending_signals: Limit, /// Max msgqueue size /// /// This is a limit on the number of bytes that can be allocated for POSIX message queues for /// the real user rID of the calling process. pub max_msgqueue_size: Limit, /// Max nice priority /// /// This specifies a ceiling to which the process's nice value can be raised using /// `setpriority` or `nice`. pub max_nice_priority: Limit, /// Max realtime priority /// /// This specifies a ceiling on the real-time priority that may be set for this process using /// `sched_setscheduler` and `sched_setparam`. pub max_realtime_priority: Limit, /// Max realtime timeout /// /// This is a limit (in microseconds) on the amount of CPU time that a process scheduled under /// a real-time scheduling policy may consume without making a blocking system call. pub max_realtime_timeout: Limit, } impl Limits { fn from_reader(r: R) -> ProcResult { let bufread = BufReader::new(r); let mut lines = bufread.lines(); let mut map = HashMap::new(); while let Some(Ok(line)) = lines.next() { let line = line.trim(); if line.starts_with("Limit") { continue; } let s: Vec<_> = line.split_whitespace().collect(); let l = s.len(); let (hard_limit, soft_limit, name) = if line.starts_with("Max nice priority") || line.starts_with("Max realtime priority") { // these two limits don't have units, and so need different offsets: let hard_limit = expect!(s.get(l - 1)).to_owned(); let soft_limit = expect!(s.get(l - 2)).to_owned(); let name = s[0..l - 2].join(" "); (hard_limit, soft_limit, name) } else { let hard_limit = expect!(s.get(l - 2)).to_owned(); let soft_limit = expect!(s.get(l - 3)).to_owned(); let name = s[0..l - 3].join(" "); (hard_limit, soft_limit, name) }; let _units = expect!(s.get(l - 1)); map.insert(name.to_owned(), (soft_limit.to_owned(), hard_limit.to_owned())); } let limits = Limits { max_cpu_time: Limit::from_pair(expect!(map.remove("Max cpu time")))?, max_file_size: Limit::from_pair(expect!(map.remove("Max file size")))?, max_data_size: Limit::from_pair(expect!(map.remove("Max data size")))?, max_stack_size: Limit::from_pair(expect!(map.remove("Max stack size")))?, max_core_file_size: Limit::from_pair(expect!(map.remove("Max core file size")))?, max_resident_set: Limit::from_pair(expect!(map.remove("Max resident set")))?, max_processes: Limit::from_pair(expect!(map.remove("Max processes")))?, max_open_files: Limit::from_pair(expect!(map.remove("Max open files")))?, max_locked_memory: Limit::from_pair(expect!(map.remove("Max locked memory")))?, max_address_space: Limit::from_pair(expect!(map.remove("Max address space")))?, max_file_locks: Limit::from_pair(expect!(map.remove("Max file locks")))?, max_pending_signals: Limit::from_pair(expect!(map.remove("Max pending signals")))?, max_msgqueue_size: Limit::from_pair(expect!(map.remove("Max msgqueue size")))?, max_nice_priority: Limit::from_pair(expect!(map.remove("Max nice priority")))?, max_realtime_priority: Limit::from_pair(expect!(map.remove("Max realtime priority")))?, max_realtime_timeout: Limit::from_pair(expect!(map.remove("Max realtime timeout")))?, }; if cfg!(test) { assert!(map.is_empty(), "Map isn't empty: {:?}", map); } Ok(limits) } } #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Limit { pub soft_limit: LimitValue, pub hard_limit: LimitValue, } impl Limit { fn from_pair(l: (String, String)) -> ProcResult { let (soft, hard) = l; Ok(Limit { soft_limit: LimitValue::from_str(&soft)?, hard_limit: LimitValue::from_str(&hard)?, }) } } #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum LimitValue { Unlimited, Value(u64), } impl LimitValue { #[cfg(test)] pub(crate) fn as_limit(&self) -> Option { match self { LimitValue::Unlimited => None, LimitValue::Value(v) => Some(*v), } } } impl FromStr for LimitValue { type Err = ProcError; fn from_str(s: &str) -> Result { if s == "unlimited" { Ok(LimitValue::Unlimited) } else { Ok(LimitValue::Value(from_str!(u64, s))) } } } #[cfg(test)] mod tests { use crate::*; use rustix::process::Resource; #[test] fn test_limits() { let me = process::Process::myself().unwrap(); let limits = me.limits().unwrap(); println!("{:#?}", limits); // Max cpu time let lim = rustix::process::getrlimit(Resource::Cpu); assert_eq!(lim.current, limits.max_cpu_time.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_cpu_time.hard_limit.as_limit()); // Max file size let lim = rustix::process::getrlimit(Resource::Fsize); assert_eq!(lim.current, limits.max_file_size.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_file_size.hard_limit.as_limit()); // Max data size let lim = rustix::process::getrlimit(Resource::Data); assert_eq!(lim.current, limits.max_data_size.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_data_size.hard_limit.as_limit()); // Max stack size let lim = rustix::process::getrlimit(Resource::Stack); assert_eq!(lim.current, limits.max_stack_size.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_stack_size.hard_limit.as_limit()); // Max core file size let lim = rustix::process::getrlimit(Resource::Core); assert_eq!(lim.current, limits.max_core_file_size.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_core_file_size.hard_limit.as_limit()); // Max resident set let lim = rustix::process::getrlimit(Resource::Rss); assert_eq!(lim.current, limits.max_resident_set.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_resident_set.hard_limit.as_limit()); // Max processes let lim = rustix::process::getrlimit(Resource::Nproc); assert_eq!(lim.current, limits.max_processes.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_processes.hard_limit.as_limit()); // Max open files let lim = rustix::process::getrlimit(Resource::Nofile); assert_eq!(lim.current, limits.max_open_files.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_open_files.hard_limit.as_limit()); // Max locked memory let lim = rustix::process::getrlimit(Resource::Memlock); assert_eq!(lim.current, limits.max_locked_memory.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_locked_memory.hard_limit.as_limit()); // Max address space let lim = rustix::process::getrlimit(Resource::As); assert_eq!(lim.current, limits.max_address_space.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_address_space.hard_limit.as_limit()); // Max file locks let lim = rustix::process::getrlimit(Resource::Locks); assert_eq!(lim.current, limits.max_file_locks.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_file_locks.hard_limit.as_limit()); // Max pending signals let lim = rustix::process::getrlimit(Resource::Sigpending); assert_eq!(lim.current, limits.max_pending_signals.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_pending_signals.hard_limit.as_limit()); // Max msgqueue size let lim = rustix::process::getrlimit(Resource::Msgqueue); assert_eq!(lim.current, limits.max_msgqueue_size.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_msgqueue_size.hard_limit.as_limit()); // Max nice priority let lim = rustix::process::getrlimit(Resource::Nice); assert_eq!(lim.current, limits.max_nice_priority.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_nice_priority.hard_limit.as_limit()); // Max realtime priority let lim = rustix::process::getrlimit(Resource::Rtprio); assert_eq!(lim.current, limits.max_realtime_priority.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_realtime_priority.hard_limit.as_limit()); // Max realtime timeout let lim = rustix::process::getrlimit(Resource::Rttime); assert_eq!(lim.current, limits.max_realtime_timeout.soft_limit.as_limit()); assert_eq!(lim.maximum, limits.max_realtime_timeout.hard_limit.as_limit()); } } procfs-0.14.2/src/process/mod.rs000064400000000000000000001617161046102023000146120ustar 00000000000000//! Functions and structs related to process information //! //! The primary source of data for functions in this module is the files in a `/proc//` //! directory. If you have a process ID, you can use //! [`Process::new(pid)`](struct.Process.html#method.new), otherwise you can get a //! list of all running processes using [`all_processes()`](fn.all_processes.html). //! //! In case you have procfs filesystem mounted to a location other than `/proc`, //! use [`Process::new_with_root()`](struct.Process.html#method.new_with_root). //! //! # Examples //! //! Here's a small example that prints out all processes that are running on the same tty as the calling //! process. This is very similar to what "ps" does in its default mode. You can run this example //! yourself with: //! //! > cargo run --example=ps //! //! ```rust //! let me = procfs::process::Process::myself().unwrap(); //! let me_stat = me.stat().unwrap(); //! let tps = procfs::ticks_per_second().unwrap(); //! //! println!("{: >10} {: <8} {: >8} {}", "PID", "TTY", "TIME", "CMD"); //! //! let tty = format!("pty/{}", me_stat.tty_nr().1); //! for prc in procfs::process::all_processes().unwrap() { //! if let Ok(stat) = prc.unwrap().stat() { //! if stat.tty_nr == me_stat.tty_nr { //! // total_time is in seconds //! let total_time = //! (stat.utime + stat.stime) as f32 / (tps as f32); //! println!( //! "{: >10} {: <8} {: >8} {}", //! stat.pid, tty, total_time, stat.comm //! ); //! } //! } //! } //! ``` //! //! Here's a simple example of how you could get the total memory used by the current process. //! There are several ways to do this. For a longer example, see the `examples/self_memory.rs` //! file in the git repository. You can run this example with: //! //! > cargo run --example=self_memory //! //! ```rust //! # use procfs::process::Process; //! let me = Process::myself().unwrap(); //! let me_stat = me.stat().unwrap(); //! let page_size = procfs::page_size().unwrap() as u64; //! //! println!("== Data from /proc/self/stat:"); //! println!("Total virtual memory used: {} bytes", me_stat.vsize); //! println!("Total resident set: {} pages ({} bytes)", me_stat.rss, me_stat.rss as u64 * page_size); //! ``` use super::*; use crate::from_iter; use crate::net::{read_tcp_table, read_udp_table, TcpNetEntry, UdpNetEntry}; use rustix::fd::{AsFd, BorrowedFd, OwnedFd, RawFd}; use rustix::fs::{AtFlags, Mode, OFlags, RawMode}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; use std::ffi::OsStr; use std::ffi::OsString; use std::fs::read_link; use std::io::{self, Read}; use std::mem; use std::os::unix::ffi::OsStringExt; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use std::str::FromStr; mod limit; pub use limit::*; mod stat; pub use stat::*; mod mount; pub use mount::*; mod namespaces; pub use namespaces::*; mod status; pub use status::*; mod schedstat; pub use schedstat::*; mod smaps_rollup; pub use smaps_rollup::*; mod task; pub use task::*; mod pagemap; pub use pagemap::*; bitflags! { /// Kernel flags for a process /// /// See also the [Stat::flags()] method. #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct StatFlags: u32 { /// I am an IDLE thread const PF_IDLE = 0x0000_0002; /// Getting shut down const PF_EXITING = 0x0000_0004; /// PI exit done on shut down const PF_EXITPIDONE = 0x0000_0008; /// I'm a virtual CPU const PF_VCPU = 0x0000_0010; /// I'm a workqueue worker const PF_WQ_WORKER = 0x0000_0020; /// Forked but didn't exec const PF_FORKNOEXEC = 0x0000_0040; /// Process policy on mce errors; const PF_MCE_PROCESS = 0x0000_0080; /// Used super-user privileges const PF_SUPERPRIV = 0x0000_0100; /// Dumped core const PF_DUMPCORE = 0x0000_0200; /// Killed by a signal const PF_SIGNALED = 0x0000_0400; ///Allocating memory const PF_MEMALLOC = 0x0000_0800; /// set_user() noticed that RLIMIT_NPROC was exceeded const PF_NPROC_EXCEEDED = 0x0000_1000; /// If unset the fpu must be initialized before use const PF_USED_MATH = 0x0000_2000; /// Used async_schedule*(), used by module init const PF_USED_ASYNC = 0x0000_4000; /// This thread should not be frozen const PF_NOFREEZE = 0x0000_8000; /// Frozen for system suspend const PF_FROZEN = 0x0001_0000; /// I am kswapd const PF_KSWAPD = 0x0002_0000; /// All allocation requests will inherit GFP_NOFS const PF_MEMALLOC_NOFS = 0x0004_0000; /// All allocation requests will inherit GFP_NOIO const PF_MEMALLOC_NOIO = 0x0008_0000; /// Throttle me less: I clean memory const PF_LESS_THROTTLE = 0x0010_0000; /// I am a kernel thread const PF_KTHREAD = 0x0020_0000; /// Randomize virtual address space const PF_RANDOMIZE = 0x0040_0000; /// Allowed to write to swap const PF_SWAPWRITE = 0x0080_0000; /// Stalled due to lack of memory const PF_MEMSTALL = 0x0100_0000; /// I'm an Usermodehelper process const PF_UMH = 0x0200_0000; /// Userland is not allowed to meddle with cpus_allowed const PF_NO_SETAFFINITY = 0x0400_0000; /// Early kill for mce process policy const PF_MCE_EARLY = 0x0800_0000; /// All allocation request will have _GFP_MOVABLE cleared const PF_MEMALLOC_NOCMA = 0x1000_0000; /// Thread belongs to the rt mutex tester const PF_MUTEX_TESTER = 0x2000_0000; /// Freezer should not count it as freezable const PF_FREEZER_SKIP = 0x4000_0000; /// This thread called freeze_processes() and should not be frozen const PF_SUSPEND_TASK = 0x8000_0000; } } bitflags! { /// See the [coredump_filter()](struct.Process.html#method.coredump_filter) method. #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct CoredumpFlags: u32 { const ANONYMOUS_PRIVATE_MAPPINGS = 0x01; const ANONYMOUS_SHARED_MAPPINGS = 0x02; const FILEBACKED_PRIVATE_MAPPINGS = 0x04; const FILEBACKED_SHARED_MAPPINGS = 0x08; const ELF_HEADERS = 0x10; const PROVATE_HUGEPAGES = 0x20; const SHARED_HUGEPAGES = 0x40; const PRIVATE_DAX_PAGES = 0x80; const SHARED_DAX_PAGES = 0x100; } } bitflags! { /// The mode (read/write permissions) for an open file descriptor /// /// This is represented as `u16` since the values of these bits are /// [documented] to be within the `u16` range. /// /// [documented]: https://man7.org/linux/man-pages/man2/chmod.2.html#DESCRIPTION #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct FDPermissions: u16 { const READ = Mode::RUSR.bits() as u16; const WRITE = Mode::WUSR.bits() as u16; const EXECUTE = Mode::XUSR.bits() as u16; } } bitflags! { /// Represents the kernel flags associated with the virtual memory area. /// The names of these flags are just those you'll find in the man page, but in upper case. #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct VmFlags: u32 { /// Invalid flags const INVALID = 0; /// Readable const RD = 1 << 0; /// Writable const WR = 1 << 1; /// Executable const EX = 1 << 2; /// Shared const SH = 1 << 3; /// May read const MR = 1 << 4; /// May write const MW = 1 << 5; /// May execute const ME = 1 << 6; /// May share const MS = 1 << 7; /// Stack segment grows down const GD = 1 << 8; /// Pure PFN range const PF = 1 << 9; /// Disable write to the mapped file const DW = 1 << 10; /// Pages are locked in memory const LO = 1 << 11; /// Memory mapped I/O area const IO = 1 << 12; /// Sequential read advise provided const SR = 1 << 13; /// Random read provided const RR = 1 << 14; /// Do not copy area on fork const DC = 1 << 15; /// Do not expand area on remapping const DE = 1 << 16; /// Area is accountable const AC = 1 << 17; /// Swap space is not reserved for the area const NR = 1 << 18; /// Area uses huge TLB pages const HT = 1 << 19; /// Perform synchronous page faults (since Linux 4.15) const SF = 1 << 20; /// Non-linear mapping (removed in Linux 4.0) const NL = 1 << 21; /// Architecture specific flag const AR = 1 << 22; /// Wipe on fork (since Linux 4.14) const WF = 1 << 23; /// Do not include area into core dump const DD = 1 << 24; /// Soft-dirty flag (since Linux 3.13) const SD = 1 << 25; /// Mixed map area const MM = 1 << 26; /// Huge page advise flag const HG = 1 << 27; /// No-huge page advise flag const NH = 1 << 28; /// Mergeable advise flag const MG = 1 << 29; /// Userfaultfd missing pages tracking (since Linux 4.3) const UM = 1 << 30; /// Userfaultfd wprotect pages tracking (since Linux 4.3) const UW = 1 << 31; } } impl VmFlags { fn from_str(flag: &str) -> Option { if flag.len() != 2 { return None; } match flag { "rd" => Some(VmFlags::RD), "wr" => Some(VmFlags::WR), "ex" => Some(VmFlags::EX), "sh" => Some(VmFlags::SH), "mr" => Some(VmFlags::MR), "mw" => Some(VmFlags::MW), "me" => Some(VmFlags::ME), "ms" => Some(VmFlags::MS), "gd" => Some(VmFlags::GD), "pf" => Some(VmFlags::PF), "dw" => Some(VmFlags::DW), "lo" => Some(VmFlags::LO), "io" => Some(VmFlags::IO), "sr" => Some(VmFlags::SR), "rr" => Some(VmFlags::RR), "dc" => Some(VmFlags::DC), "de" => Some(VmFlags::DE), "ac" => Some(VmFlags::AC), "nr" => Some(VmFlags::NR), "ht" => Some(VmFlags::HT), "sf" => Some(VmFlags::SF), "nl" => Some(VmFlags::NL), "ar" => Some(VmFlags::AR), "wf" => Some(VmFlags::WF), "dd" => Some(VmFlags::DD), "sd" => Some(VmFlags::SD), "mm" => Some(VmFlags::MM), "hg" => Some(VmFlags::HG), "nh" => Some(VmFlags::NH), "mg" => Some(VmFlags::MG), "um" => Some(VmFlags::UM), "uw" => Some(VmFlags::UW), _ => None, } } } //impl<'a, 'b, T> ProcFrom<&'b mut T> for u32 where T: Iterator + Sized, 'a: 'b { // fn from(i: &'b mut T) -> u32 { // let s = i.next().unwrap(); // u32::from_str_radix(s, 10).unwrap() // } //} //impl<'a> ProcFrom<&'a str> for u32 { // fn from(s: &str) -> Self { // u32::from_str_radix(s, 10).unwrap() // } //} //fn from_iter<'a, I: Iterator>(i: &mut I) -> u32 { // u32::from_str_radix(i.next().unwrap(), 10).unwrap() //} /// Represents the state of a process. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum ProcState { /// Running (R) Running, /// Sleeping in an interruptible wait (S) Sleeping, /// Waiting in uninterruptible disk sleep (D) Waiting, /// Zombie (Z) Zombie, /// Stopped (on a signal) (T) /// /// Or before Linux 2.6.33, trace stopped Stopped, /// Tracing stop (t) (Linux 2.6.33 onward) Tracing, /// Dead (X) Dead, /// Wakekill (K) (Linux 2.6.33 to 3.13 only) Wakekill, /// Waking (W) (Linux 2.6.33 to 3.13 only) Waking, /// Parked (P) (Linux 3.9 to 3.13 only) Parked, /// Idle (I) Idle, } impl ProcState { pub fn from_char(c: char) -> Option { match c { 'R' => Some(ProcState::Running), 'S' => Some(ProcState::Sleeping), 'D' => Some(ProcState::Waiting), 'Z' => Some(ProcState::Zombie), 'T' => Some(ProcState::Stopped), 't' => Some(ProcState::Tracing), 'X' | 'x' => Some(ProcState::Dead), 'K' => Some(ProcState::Wakekill), 'W' => Some(ProcState::Waking), 'P' => Some(ProcState::Parked), 'I' => Some(ProcState::Idle), _ => None, } } } impl FromStr for ProcState { type Err = ProcError; fn from_str(s: &str) -> Result { ProcState::from_char(expect!(s.chars().next(), "empty string")) .ok_or_else(|| build_internal_error!("failed to convert")) } } //impl<'a, 'b, T> ProcFrom<&'b mut T> for ProcState where T: Iterator, 'a: 'b { // fn from(s: &'b mut T) -> ProcState { // ProcState::from_str(s.next().unwrap()).unwrap() // } //} /// This struct contains I/O statistics for the process, built from `/proc//io` /// /// To construct this structure, see [Process::io()]. /// /// # Note /// /// In the current implementation, things are a bit racy on 32-bit systems: if process A /// reads process B's `/proc//io` while process B is updating one of these 64-bit /// counters, process A could see an intermediate result. #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Io { /// Characters read /// /// The number of bytes which this task has caused to be read from storage. This is simply the /// sum of bytes which this process passed to read(2) and similar system calls. It includes /// things such as terminal I/O and is unaffected by whether or not actual physical disk I/O /// was required (the read might have been satisfied from pagecache). pub rchar: u64, /// characters written /// /// The number of bytes which this task has caused, or shall cause to be written to disk. /// Similar caveats apply here as with rchar. pub wchar: u64, /// read syscalls /// /// Attempt to count the number of write I/O operations—that is, system calls such as write(2) /// and pwrite(2). pub syscr: u64, /// write syscalls /// /// Attempt to count the number of write I/O operations—that is, system calls such as write(2) /// and pwrite(2). pub syscw: u64, /// bytes read /// /// Attempt to count the number of bytes which this process really did cause to be fetched from /// the storage layer. This is accurate for block-backed filesystems. pub read_bytes: u64, /// bytes written /// /// Attempt to count the number of bytes which this process caused to be sent to the storage layer. pub write_bytes: u64, /// Cancelled write bytes. /// /// The big inaccuracy here is truncate. If a process writes 1MB to a file and then deletes /// the file, it will in fact perform no write‐ out. But it will have been accounted as having /// caused 1MB of write. In other words: this field represents the number of bytes which this /// process caused to not happen, by truncating pagecache. A task can cause "negative" I/O too. /// If this task truncates some dirty pagecache, some I/O which another task has been accounted /// for (in its write_bytes) will not be happening. pub cancelled_write_bytes: u64, } #[derive(Debug, PartialEq, Eq, Clone, Hash)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum MMapPath { /// The file that is backing the mapping. Path(PathBuf), /// The process's heap. Heap, /// The initial process's (also known as the main thread's) stack. Stack, /// A thread's stack (where the `` is a thread ID). It corresponds to the /// `/proc//task//` path. /// /// (since Linux 3.4) TStack(u32), /// The virtual dynamically linked shared object. Vdso, /// Shared kernel variables Vvar, /// obsolete virtual syscalls, succeeded by vdso Vsyscall, /// An anonymous mapping as obtained via mmap(2). Anonymous, /// Shared memory segment Vsys(i32), /// Some other pseudo-path Other(String), } impl MMapPath { /// Needed for MemoryMap::new(). fn new() -> MMapPath { MMapPath::Anonymous } fn from(path: &str) -> ProcResult { Ok(match path.trim() { "" => MMapPath::Anonymous, "[heap]" => MMapPath::Heap, "[stack]" => MMapPath::Stack, "[vdso]" => MMapPath::Vdso, "[vvar]" => MMapPath::Vvar, "[vsyscall]" => MMapPath::Vsyscall, x if x.starts_with("[stack:") => { let mut s = x[1..x.len() - 1].split(':'); let tid = from_str!(u32, expect!(s.nth(1))); MMapPath::TStack(tid) } x if x.starts_with('[') && x.ends_with(']') => MMapPath::Other(x[1..x.len() - 1].to_string()), x if x.starts_with("/SYSV") => MMapPath::Vsys(u32::from_str_radix(&x[5..13], 16)? as i32), // 32bits signed hex. /SYSVaabbccdd (deleted) x => MMapPath::Path(PathBuf::from(x)), }) } } /// Represents an entry in a `/proc//maps` file. /// /// To construct this structure, see [Process::maps()] and [Process::smaps()]. #[derive(Debug, PartialEq, Eq, Clone, Hash)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct MemoryMap { /// The address space in the process that the mapping occupies. pub address: (u64, u64), pub perms: String, /// The offset into the file/whatever pub offset: u64, /// The device (major, minor) pub dev: (i32, i32), /// The inode on that device /// /// 0 indicates that no inode is associated with the memory region, as would be the case with /// BSS (uninitialized data). pub inode: u64, pub pathname: MMapPath, } impl MemoryMap { /// Used internally in Process::smaps() as a "default value" thing fn new() -> Self { Self { address: (0, 0), perms: "".into(), offset: 0, dev: (0, 0), inode: 0, pathname: MMapPath::new(), } } fn from_line(line: &str) -> ProcResult { let mut s = line.splitn(6, ' '); let address = expect!(s.next()); let perms = expect!(s.next()); let offset = expect!(s.next()); let dev = expect!(s.next()); let inode = expect!(s.next()); let path = expect!(s.next()); Ok(MemoryMap { address: split_into_num(address, '-', 16)?, perms: perms.to_string(), offset: from_str!(u64, offset, 16), dev: split_into_num(dev, ':', 16)?, inode: from_str!(u64, inode), pathname: MMapPath::from(path)?, }) } } /// Represents the information about a specific mapping as presented in /proc//smaps /// /// To construct this structure, see [Process::smaps()] #[derive(Default, Debug)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct MemoryMapData { /// Key-Value pairs that may represent statistics about memory usage, or other interesting things, /// such a "ProtectionKey"(if you're on X86 and that kernel config option was specified). /// /// Note that should a Key-Value pair represent a memory usage statistic, it will be in bytes. /// /// Check your manpage for more information pub map: HashMap, /// Kernel flags associated with the virtual memory area /// /// (since Linux 3.8) pub vm_flags: Option, } impl Io { pub fn from_reader(r: R) -> ProcResult { let mut map = HashMap::new(); let reader = BufReader::new(r); for line in reader.lines() { let line = line?; if line.is_empty() || !line.contains(' ') { continue; } let mut s = line.split_whitespace(); let field = expect!(s.next()); let value = expect!(s.next()); let value = from_str!(u64, value); map.insert(field[..field.len() - 1].to_string(), value); } let io = Io { rchar: expect!(map.remove("rchar")), wchar: expect!(map.remove("wchar")), syscr: expect!(map.remove("syscr")), syscw: expect!(map.remove("syscw")), read_bytes: expect!(map.remove("read_bytes")), write_bytes: expect!(map.remove("write_bytes")), cancelled_write_bytes: expect!(map.remove("cancelled_write_bytes")), }; assert!(!cfg!(test) || map.is_empty(), "io map is not empty: {:#?}", map); Ok(io) } } /// Describes a file descriptor opened by a process. /// /// See also the [Process::fd()] method. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum FDTarget { /// A file or device Path(PathBuf), /// A socket type, with an inode Socket(u64), Net(u64), Pipe(u64), /// A file descriptor that have no corresponding inode. AnonInode(String), /// A memfd file descriptor with a name. MemFD(String), /// Some other file descriptor type, with an inode. Other(String, u64), } impl FromStr for FDTarget { type Err = ProcError; fn from_str(s: &str) -> Result { // helper function that removes the first and last character fn strip_first_last(s: &str) -> ProcResult<&str> { if s.len() > 2 { let mut c = s.chars(); // remove the first and last characters let _ = c.next(); let _ = c.next_back(); Ok(c.as_str()) } else { Err(ProcError::Incomplete(None)) } } if !s.starts_with('/') && s.contains(':') { let mut s = s.split(':'); let fd_type = expect!(s.next()); match fd_type { "socket" => { let inode = expect!(s.next(), "socket inode"); let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); Ok(FDTarget::Socket(inode)) } "net" => { let inode = expect!(s.next(), "net inode"); let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); Ok(FDTarget::Net(inode)) } "pipe" => { let inode = expect!(s.next(), "pipe inode"); let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); Ok(FDTarget::Pipe(inode)) } "anon_inode" => Ok(FDTarget::AnonInode(expect!(s.next(), "anon inode").to_string())), "" => Err(ProcError::Incomplete(None)), x => { let inode = expect!(s.next(), "other inode"); let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); Ok(FDTarget::Other(x.to_string(), inode)) } } } else if let Some(s) = s.strip_prefix("/memfd:") { Ok(FDTarget::MemFD(s.to_string())) } else { Ok(FDTarget::Path(PathBuf::from(s))) } } } /// See the [Process::fd()] method #[derive(Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct FDInfo { /// The file descriptor pub fd: i32, /// The permission bits for this FD /// /// **Note**: this field is only the owner read/write/execute bits. All the other bits /// (include filetype bits) are masked out. See also the `mode()` method. pub mode: u16, pub target: FDTarget, } impl FDInfo { /// Gets a file descriptor from a raw fd pub fn from_raw_fd(pid: i32, raw_fd: i32) -> ProcResult { Self::from_raw_fd_with_root("/proc", pid, raw_fd) } /// Gets a file descriptor from a raw fd based on a specified `/proc` path pub fn from_raw_fd_with_root(root: impl AsRef, pid: i32, raw_fd: i32) -> ProcResult { let path = root.as_ref().join(pid.to_string()).join("fd").join(raw_fd.to_string()); let link = wrap_io_error!(path, read_link(&path))?; let md = wrap_io_error!(path, path.symlink_metadata())?; let link_os: &OsStr = link.as_ref(); Ok(Self { fd: raw_fd, mode: ((md.mode() as RawMode) & Mode::RWXU.bits()) as u16, target: expect!(FDTarget::from_str(expect!(link_os.to_str()))), }) } /// Gets a file descriptor from a directory fd and a path relative to it. /// /// `base` is the path to the directory fd, and is used for error messages. fn from_process_at, Q: AsRef>( base: P, dirfd: BorrowedFd, path: Q, fd: i32, ) -> ProcResult { let p = path.as_ref(); let root = base.as_ref().join(p); let file = wrap_io_error!( root, rustix::fs::openat( dirfd, p, OFlags::NOFOLLOW | OFlags::PATH | OFlags::CLOEXEC, Mode::empty() ) )?; let link = rustix::fs::readlinkat(&file, "", Vec::new()).map_err(io::Error::from)?; let md = rustix::fs::statat(&file, "", AtFlags::SYMLINK_NOFOLLOW | AtFlags::EMPTY_PATH).map_err(io::Error::from)?; let link_os = link.to_string_lossy(); let target = FDTarget::from_str(link_os.as_ref())?; Ok(FDInfo { fd, mode: (md.st_mode & Mode::RWXU.bits()) as u16, target, }) } /// Gets the read/write mode of this file descriptor as a bitfield pub fn mode(&self) -> FDPermissions { FDPermissions::from_bits_truncate(self.mode) } } impl std::fmt::Debug for FDInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "FDInfo {{ fd: {:?}, mode: 0{:o}, target: {:?} }}", &self.fd, self.mode, self.target ) } } /// Represents a process in `/proc/`. /// /// **Note** The `Process` struct holds an open file descriptor to its `/proc/` directory. /// This makes it possible to construct a `Process` object and then later call the various methods /// on it without a risk of inadvertently getting information from the wrong process (due to PID /// reuse). /// /// However the downside is that holding a lot of `Process` objects might cause the process to run /// out of file descriptors. /// /// For use cases that don't involve holding a lot of `Process` objects, no special handler is /// needed. But if you do hold a lot of these objects (for example if you're writing a `ps` /// or `top` -like program), you'll likely want to gather all of the necessary info from `Process` /// object into a new struct and then drop the `Process` object /// #[derive(Debug)] pub struct Process { fd: OwnedFd, pub pid: i32, pub(crate) root: PathBuf, } /// Methods for constructing a new `Process` object. impl Process { /// Returns a `Process` based on a specified PID. /// /// This can fail if the process doesn't exist, or if you don't have permission to access it. pub fn new(pid: i32) -> ProcResult { let root = PathBuf::from("/proc").join(pid.to_string()); Self::new_with_root(root) } /// Returns a `Process` based on a specified `/proc/` path. pub fn new_with_root(root: PathBuf) -> ProcResult { let file = wrap_io_error!( root, rustix::fs::openat( rustix::fs::cwd(), &root, OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC, Mode::empty() ) )?; let pidres = root .as_path() .components() .last() .and_then(|c| match c { std::path::Component::Normal(s) => Some(s), _ => None, }) .and_then(|s| s.to_string_lossy().parse::().ok()) .or_else(|| { rustix::fs::readlinkat(rustix::fs::cwd(), &root, Vec::new()) .ok() .and_then(|s| s.to_string_lossy().parse::().ok()) }); let pid = match pidres { Some(pid) => pid, None => return Err(ProcError::NotFound(Some(root))), }; Ok(Process { fd: file, pid, root }) } /// Returns a `Process` for the currently running process. /// /// This is done by using the `/proc/self` symlink pub fn myself() -> ProcResult { let root = PathBuf::from("/proc/self"); Self::new_with_root(root) } } impl Process { /// Returns the complete command line for the process, unless the process is a zombie. /// /// pub fn cmdline(&self) -> ProcResult> { let mut buf = String::new(); let mut f = FileWrapper::open_at(&self.root, &self.fd, "cmdline")?; f.read_to_string(&mut buf)?; Ok(buf .split('\0') .filter_map(|s| if !s.is_empty() { Some(s.to_string()) } else { None }) .collect()) } /// Returns the process ID for this process, if the process was created from an ID. Otherwise /// use stat().pid. pub fn pid(&self) -> i32 { self.pid } /// Is this process still alive? /// /// Processes in the Zombie or Dead state are not considered alive. pub fn is_alive(&self) -> bool { if let Ok(stat) = self.stat() { stat.state != 'Z' && stat.state != 'X' } else { false } } /// What user owns this process? pub fn uid(&self) -> ProcResult { Ok(self.metadata()?.st_uid) } fn metadata(&self) -> ProcResult { Ok(rustix::fs::fstat(&self.fd).map_err(io::Error::from)?) } /// Retrieves current working directory of the process by dereferencing `/proc//cwd` symbolic link. /// /// This method has the following caveats: /// /// * if the pathname has been unlinked, the symbolic link will contain the string " (deleted)" /// appended to the original pathname /// /// * in a multithreaded process, the contents of this symbolic link are not available if the /// main thread has already terminated (typically by calling `pthread_exit(3)`) /// /// * permission to dereference or read this symbolic link is governed by a /// `ptrace(2)` access mode `PTRACE_MODE_READ_FSCREDS` check pub fn cwd(&self) -> ProcResult { Ok(PathBuf::from(OsString::from_vec( wrap_io_error!( self.root.join("cwd"), rustix::fs::readlinkat(&self.fd, "cwd", Vec::new()) )? .into_bytes(), ))) } /// Retrieves current root directory of the process by dereferencing `/proc//root` symbolic link. /// /// This method has the following caveats: /// /// * if the pathname has been unlinked, the symbolic link will contain the string " (deleted)" /// appended to the original pathname /// /// * in a multithreaded process, the contents of this symbolic link are not available if the /// main thread has already terminated (typically by calling `pthread_exit(3)`) /// /// * permission to dereference or read this symbolic link is governed by a /// `ptrace(2)` access mode `PTRACE_MODE_READ_FSCREDS` check pub fn root(&self) -> ProcResult { Ok(PathBuf::from(OsString::from_vec( wrap_io_error!( self.root.join("root"), rustix::fs::readlinkat(&self.fd, "root", Vec::new()) )? .into_bytes(), ))) } /// Gets the current environment for the process. This is done by reading the /// `/proc/pid/environ` file. pub fn environ(&self) -> ProcResult> { use std::os::unix::ffi::OsStrExt; let mut map = HashMap::new(); let mut file = FileWrapper::open_at(&self.root, &self.fd, "environ")?; let mut buf = Vec::new(); file.read_to_end(&mut buf)?; for slice in buf.split(|b| *b == 0) { // slice will be in the form key=var, so split on the first equals sign let mut split = slice.splitn(2, |b| *b == b'='); if let (Some(k), Some(v)) = (split.next(), split.next()) { map.insert(OsStr::from_bytes(k).to_os_string(), OsStr::from_bytes(v).to_os_string()); }; //let env = OsStr::from_bytes(slice); } Ok(map) } /// Retrieves the actual path of the executed command by dereferencing `/proc//exe` symbolic link. /// /// This method has the following caveats: /// /// * if the pathname has been unlinked, the symbolic link will contain the string " (deleted)" /// appended to the original pathname /// /// * in a multithreaded process, the contents of this symbolic link are not available if the /// main thread has already terminated (typically by calling `pthread_exit(3)`) /// /// * permission to dereference or read this symbolic link is governed by a /// `ptrace(2)` access mode `PTRACE_MODE_READ_FSCREDS` check pub fn exe(&self) -> ProcResult { Ok(PathBuf::from(OsString::from_vec( wrap_io_error!( self.root.join("exe"), rustix::fs::readlinkat(&self.fd, "exe", Vec::new()) )? .into_bytes(), ))) } /// Return the Io stats for this process, based on the `/proc/pid/io` file. /// /// (since kernel 2.6.20) pub fn io(&self) -> ProcResult { let file = FileWrapper::open_at(&self.root, &self.fd, "io")?; Io::from_reader(file) } /// Return a list of the currently mapped memory regions and their access permissions, based on /// the `/proc/pid/maps` file. pub fn maps(&self) -> ProcResult> { let file = FileWrapper::open_at(&self.root, &self.fd, "maps")?; let reader = BufReader::new(file); let mut vec = Vec::new(); for line in reader.lines() { let line = line.map_err(|_| ProcError::Incomplete(Some(self.root.join("maps"))))?; vec.push(MemoryMap::from_line(&line)?); } Ok(vec) } /// Returns a list of currently mapped memory regions and verbose information about them, /// such as memory consumption per mapping, based on the `/proc/pid/smaps` file. /// /// (since Linux 2.6.14 and requires CONFIG_PROG_PAGE_MONITOR) pub fn smaps(&self) -> ProcResult> { let file = FileWrapper::open_at(&self.root, &self.fd, "smaps")?; let reader = BufReader::new(file); let mut vec: Vec<(MemoryMap, MemoryMapData)> = Vec::new(); let mut current_mapping = MemoryMap::new(); let mut current_data = Default::default(); for line in reader.lines() { let line = line.map_err(|_| ProcError::Incomplete(Some(self.root.join("smaps"))))?; if let Ok(mapping) = MemoryMap::from_line(&line) { vec.push((current_mapping, current_data)); current_mapping = mapping; current_data = Default::default(); } else { // This is probably an attribute if line.starts_with("VmFlags") { let flags = line.split_ascii_whitespace(); let flags = flags.skip(1); // Skips the `VmFlags:` part since we don't need it. let flags = flags .map(|v| match VmFlags::from_str(v) { None => VmFlags::INVALID, Some(v) => v, }) .fold(VmFlags::INVALID, |a, b| a | b); current_data.vm_flags = Some(flags); } else { let mut parts = line.split_ascii_whitespace(); let key = parts.next(); let value = parts.next(); if let (Some(k), Some(v)) = (key, value) { // While most entries do have one, not all of them do. let size_suffix = parts.next(); // Limited poking at /proc//smaps and then checking if "MB", "GB", and "TB" appear in the C file that is // supposedly responsible for creating smaps, has lead me to believe that the only size suffixes we'll ever encounter // "kB", which is most likely kibibytes. Actually checking if the size suffix is any of the above is a way to // future-proof the code, but I am not sure it is worth doing so. let size_multiplier = if size_suffix.is_some() { 1024 } else { 1 }; let v = v.parse::().map_err(|_| { ProcError::Other("Value in `Key: Value` pair was not actually a number".into()) })?; // This ignores the case when our Key: Value pairs are really Key Value pairs. Is this a good idea? let k = k.trim_end_matches(':'); current_data.map.insert(k.into(), v * size_multiplier); } } } } Ok(vec) } /// This is the sum of all the smaps data but it is much more performant to get it this way. /// /// Since 4.14 and requires CONFIG_PROC_PAGE_MONITOR. pub fn smaps_rollup(&self) -> ProcResult { let file = FileWrapper::open_at(&self.root, &self.fd, "smaps_rollup")?; SmapsRollup::from_reader(file) } /// Returns a struct that can be used to access information in the `/proc/pid/pagemap` file. pub fn pagemap(&self) -> ProcResult { let path = self.root.join("pagemap"); let file = FileWrapper::open(&path)?; Ok(PageMap::from_file_wrapper(file)) } /// Gets the number of open file descriptors for a process /// /// Calling this function is more efficient than calling `fd().unwrap().count()` pub fn fd_count(&self) -> ProcResult { let fds = wrap_io_error!( self.root.join("fd"), rustix::fs::openat( &self.fd, "fd", OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, Mode::empty() ) )?; let fds = wrap_io_error!(self.root.join("fd"), rustix::fs::Dir::read_from(fds))?; Ok(fds.count()) } /// Gets a iterator of open file descriptors for a process pub fn fd(&self) -> ProcResult { let dir_fd = wrap_io_error!( self.root.join("fd"), rustix::fs::openat( &self.fd, "fd", OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, Mode::empty() ) )?; let dir = wrap_io_error!(self.root.join("fd"), rustix::fs::Dir::read_from(&dir_fd))?; Ok(FDsIter { inner: dir, inner_fd: dir_fd, root: self.root.clone(), }) } pub fn fd_from_fd(&self, fd: i32) -> ProcResult { let path = PathBuf::from("fd").join(fd.to_string()); FDInfo::from_process_at(&self.root, self.fd.as_fd(), path, fd) } /// Lists which memory segments are written to the core dump in the event that a core dump is performed. /// /// By default, the following bits are set: /// 0, 1, 4 (if the CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS kernel configuration option is enabled), and 5. /// This default can be modified at boot time using the core dump_filter boot option. /// /// This function will return `Err(ProcError::NotFound)` if the `coredump_filter` file can't be /// found. If it returns `Ok(None)` then the process has no coredump_filter pub fn coredump_filter(&self) -> ProcResult> { let mut file = FileWrapper::open_at(&self.root, &self.fd, "coredump_filter")?; let mut s = String::new(); file.read_to_string(&mut s)?; if s.trim().is_empty() { return Ok(None); } let flags = from_str!(u32, &s.trim(), 16, pid: self.pid); Ok(Some(expect!(CoredumpFlags::from_bits(flags)))) } /// Gets the process's autogroup membership /// /// (since Linux 2.6.38 and requires CONFIG_SCHED_AUTOGROUP) pub fn autogroup(&self) -> ProcResult { let mut s = String::new(); let mut file = FileWrapper::open_at(&self.root, &self.fd, "autogroup")?; file.read_to_string(&mut s)?; Ok(s) } /// Get the process's auxiliary vector /// /// (since 2.6.0-test7) pub fn auxv(&self) -> ProcResult> { use byteorder::{NativeEndian, ReadBytesExt}; let mut file = FileWrapper::open_at(&self.root, &self.fd, "auxv")?; let mut map = HashMap::new(); let mut buf = Vec::new(); let bytes_read = file.read_to_end(&mut buf)?; if bytes_read == 0 { // some kernel processes won't have any data for their auxv file return Ok(map); } buf.truncate(bytes_read); let mut file = std::io::Cursor::new(buf); loop { let key = file.read_uint::(mem::size_of::())? as u64; let value = file.read_uint::(mem::size_of::())? as u64; if key == 0 && value == 0 { break; } map.insert(key, value); } Ok(map) } /// Gets the symbolic name corresponding to the location in the kernel where the process is sleeping. /// /// (since Linux 2.6.0) pub fn wchan(&self) -> ProcResult { let mut s = String::new(); let mut file = FileWrapper::open_at(&self.root, &self.fd, "wchan")?; file.read_to_string(&mut s)?; Ok(s) } /// Return the `Status` for this process, based on the `/proc/[pid]/status` file. pub fn status(&self) -> ProcResult { let file = FileWrapper::open_at(&self.root, &self.fd, "status")?; Status::from_reader(file) } /// Returns the status info from `/proc/[pid]/stat`. pub fn stat(&self) -> ProcResult { let file = FileWrapper::open_at(&self.root, &self.fd, "stat")?; let stat = Stat::from_reader(file)?; Ok(stat) } /// Gets the process' login uid. May not be available. pub fn loginuid(&self) -> ProcResult { let mut uid = String::new(); let mut file = FileWrapper::open_at(&self.root, &self.fd, "loginuid")?; file.read_to_string(&mut uid)?; Status::parse_uid_gid(&uid, 0) } /// The current score that the kernel gives to this process for the purpose of selecting a /// process for the OOM-killer /// /// A higher score means that the process is more likely to be selected by the OOM-killer. /// The basis for this score is the amount of memory used by the process, plus other factors. /// /// (Since linux 2.6.11) pub fn oom_score(&self) -> ProcResult { let mut file = FileWrapper::open_at(&self.root, &self.fd, "oom_score")?; let mut oom = String::new(); file.read_to_string(&mut oom)?; Ok(from_str!(u32, oom.trim())) } /// Set process memory information /// /// Much of this data is the same as the data from `stat()` and `status()` pub fn statm(&self) -> ProcResult { let file = FileWrapper::open_at(&self.root, &self.fd, "statm")?; StatM::from_reader(file) } /// Return a task for the main thread of this process pub fn task_main_thread(&self) -> ProcResult { self.task_from_tid(self.pid) } /// Return a task for the main thread of this process pub fn task_from_tid(&self, tid: i32) -> ProcResult { let path = PathBuf::from("task").join(tid.to_string()); Task::from_process_at(&self.root, self.fd.as_fd(), path, self.pid, tid) } /// Return the `Schedstat` for this process, based on the `/proc//schedstat` file. /// /// (Requires CONFIG_SCHED_INFO) pub fn schedstat(&self) -> ProcResult { let file = FileWrapper::open_at(&self.root, &self.fd, "schedstat")?; Schedstat::from_reader(file) } /// Iterate over all the [`Task`]s (aka Threads) in this process /// /// Note that the iterator does not receive a snapshot of tasks, it is a /// lazy iterator over whatever happens to be running when the iterator /// gets there, see the examples below. /// /// # Examples /// /// ## Simple iteration over subtasks /// /// If you want to get the info that most closely matches what was running /// when you call `tasks` you should collect them as quikcly as possible, /// and then run processing over that collection: /// /// ``` /// # use std::thread; /// # use std::sync::mpsc::channel; /// # use procfs::process::Process; /// # fn main() -> Result<(), Box> { /// # let (finish_tx, finish_rx) = channel(); /// # let (start_tx, start_rx) = channel(); /// let name = "testing:example"; /// let t = thread::Builder::new().name(name.to_string()) /// .spawn(move || { // do work /// # start_tx.send(()).unwrap(); /// # finish_rx.recv().expect("valid channel"); /// })?; /// # start_rx.recv()?; /// /// let proc = Process::myself()?; /// /// // Collect a snapshot /// let threads: Vec<_> = proc.tasks()?.flatten().map(|t| t.stat().unwrap().comm).collect(); /// threads.iter().find(|s| &**s == name).expect("thread should exist"); /// /// # finish_tx.send(()); /// # t.join().unwrap(); /// # Ok(()) /// # } /// ``` /// /// ## The TaskIterator is lazy /// /// This means both that tasks that stop before you get to them in /// iteration will not be there, and that new tasks that are created after /// you start the iterator *will* appear. /// /// ``` /// # use std::thread; /// # use std::sync::mpsc::channel; /// # use procfs::process::Process; /// # fn main() -> Result<(), Box> { /// let proc = Process::myself()?; /// /// // Task iteration is lazy /// let mut task_iter = proc.tasks()?.flatten().map(|t| t.stat().unwrap().comm); /// /// # let (finish_tx, finish_rx) = channel(); /// # let (start_tx, start_rx) = channel(); /// let name = "testing:lazy"; /// let t = thread::Builder::new().name(name.to_string()) /// .spawn(move || { // do work /// # start_tx.send(()).unwrap(); /// # finish_rx.recv().expect("valid channel"); /// })?; /// # start_rx.recv()?; /// /// task_iter.find(|s| &**s == name).expect("thread should exist"); /// /// # finish_tx.send(()); /// # t.join().unwrap(); /// # Ok(()) /// # } /// ``` /// /// Tasks that stop while you're iterating may or may not appear: /// /// ``` /// # use std::thread; /// # use std::sync::mpsc::channel; /// # use procfs::process::Process; /// # fn main() -> Result<(), Box> { /// # let (finish_tx, finish_rx) = channel(); /// # let (start_tx, start_rx) = channel(); /// let name = "testing:stopped"; /// let t = thread::Builder::new().name(name.to_string()) /// .spawn(move || { // do work /// # start_tx.send(()).unwrap(); /// # finish_rx.recv().expect("valid channel"); /// })?; /// # start_rx.recv()?; /// /// let proc = Process::myself()?; /// /// // Task iteration is lazy /// let mut task_iter = proc.tasks()?.flatten().map(|t| t.stat().unwrap().comm); /// /// # finish_tx.send(()); /// t.join().unwrap(); /// /// // It's impossible to know if this is going to be gone /// let _ = task_iter.find(|s| &**s == name).is_some(); /// # Ok(()) /// # } /// ``` pub fn tasks(&self) -> ProcResult { let dir_fd = wrap_io_error!( self.root.join("task"), rustix::fs::openat( &self.fd, "task", OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, Mode::empty() ) )?; let dir = wrap_io_error!(self.root.join("task"), rustix::fs::Dir::read_from(&dir_fd))?; Ok(TasksIter { pid: self.pid, inner: dir, inner_fd: dir_fd, root: self.root.clone(), }) } /// Reads the tcp socket table from the process net namespace pub fn tcp(&self) -> ProcResult> { let file = FileWrapper::open_at(&self.root, &self.fd, "net/tcp")?; read_tcp_table(BufReader::new(file)) } /// Reads the tcp6 socket table from the process net namespace pub fn tcp6(&self) -> ProcResult> { let file = FileWrapper::open_at(&self.root, &self.fd, "net/tcp6")?; read_tcp_table(BufReader::new(file)) } /// Reads the udp socket table from the process net namespace pub fn udp(&self) -> ProcResult> { let file = FileWrapper::open_at(&self.root, &self.fd, "net/udp")?; read_udp_table(BufReader::new(file)) } /// Reads the udp6 socket table from the process net namespace pub fn udp6(&self) -> ProcResult> { let file = FileWrapper::open_at(&self.root, &self.fd, "net/udp6")?; read_udp_table(BufReader::new(file)) } /// Opens a file to the process's memory (`/proc//mem`). /// /// Note: you cannot start reading from the start of the file. You must first seek to /// a mapped page. See [Process::maps]. /// /// Permission to access this file is governed by a ptrace access mode PTRACE_MODE_ATTACH_FSCREDS check /// /// # Example /// /// Find the offset of the "hello" string in the process's stack, and compare it to the /// pointer of the variable containing "hello" /// /// ```rust /// # use std::io::{Read, Seek, SeekFrom}; /// # use procfs::process::{MMapPath, Process}; /// let me = Process::myself().unwrap(); /// let mut mem = me.mem().unwrap(); /// let maps = me.maps().unwrap(); /// /// let hello = "hello".to_string(); /// /// for map in maps { /// if map.pathname == MMapPath::Heap { /// mem.seek(SeekFrom::Start(map.address.0)).unwrap(); /// let mut buf = vec![0; (map.address.1 - map.address.0) as usize]; /// mem.read_exact(&mut buf).unwrap(); /// let idx = buf.windows(5).position(|p| p == b"hello").unwrap(); /// assert_eq!(map.address.0 + idx as u64, hello.as_ptr() as u64); /// } /// } /// ``` pub fn mem(&self) -> ProcResult { let file = FileWrapper::open_at(&self.root, &self.fd, "mem")?; Ok(file.inner()) } /// Returns a file which is part of the process proc structure pub fn open_relative(&self, path: &str) -> ProcResult { let file = FileWrapper::open_at(&self.root, &self.fd, path)?; Ok(file.inner()) } } /// The result of [`Process::fd`], iterates over all fds in a process #[derive(Debug)] pub struct FDsIter { inner: rustix::fs::Dir, inner_fd: rustix::fd::OwnedFd, root: PathBuf, } impl std::iter::Iterator for FDsIter { type Item = ProcResult; fn next(&mut self) -> Option> { loop { match self.inner.next() { Some(Ok(entry)) => { let name = entry.file_name().to_string_lossy(); if let Ok(fd) = RawFd::from_str(&name) { if let Ok(info) = FDInfo::from_process_at(&self.root, self.inner_fd.as_fd(), name.as_ref(), fd) { break Some(Ok(info)); } } } Some(Err(e)) => break Some(Err(io::Error::from(e).into())), None => break None, } } } } /// The result of [`Process::tasks`], iterates over all tasks in a process #[derive(Debug)] pub struct TasksIter { pid: i32, inner: rustix::fs::Dir, inner_fd: rustix::fd::OwnedFd, root: PathBuf, } impl std::iter::Iterator for TasksIter { type Item = ProcResult; fn next(&mut self) -> Option> { loop { match self.inner.next() { Some(Ok(tp)) => { if let Ok(tid) = i32::from_str(&tp.file_name().to_string_lossy()) { if let Ok(task) = Task::from_process_at(&self.root, self.inner_fd.as_fd(), tid.to_string(), self.pid, tid) { break Some(Ok(task)); } } } Some(Err(e)) => break Some(Err(io::Error::from(e).into())), None => break None, } } } } /// Return a iterator of all processes /// /// If a process can't be constructed for some reason, it won't be returned in the iterator. /// /// See also some important docs on the [ProcessesIter] struct. pub fn all_processes() -> ProcResult { all_processes_with_root("/proc") } /// Return a list of all processes based on a specified `/proc` path /// /// If a process can't be constructed for some reason, it won't be returned in the list. /// /// See also some important docs on the [ProcessesIter] struct. pub fn all_processes_with_root(root: impl AsRef) -> ProcResult { let root = root.as_ref(); let dir = wrap_io_error!( root, rustix::fs::openat( rustix::fs::cwd(), root, OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, Mode::empty() ) )?; let dir = wrap_io_error!(root, rustix::fs::Dir::read_from(dir))?; Ok(ProcessesIter { root: PathBuf::from(root), inner: dir, }) } /// An iterator over all processes in the system. /// /// **Note** This is a *lazy* iterator (like most iterators in rust). You will likely want to consume /// this iterator as quickly as possible if you want a "snapshot" of the system (though it won't be a /// true snapshot). Another important thing to keep in mind is that the [`Process`] struct holds an /// open file descriptor to its corresponding `/proc/` directory. See the docs for [`Process`] /// for more information. #[derive(Debug)] pub struct ProcessesIter { root: PathBuf, inner: rustix::fs::Dir, } impl std::iter::Iterator for ProcessesIter { type Item = ProcResult; fn next(&mut self) -> Option> { loop { match self.inner.next() { Some(Ok(entry)) => { if let Ok(pid) = i32::from_str(&entry.file_name().to_string_lossy()) { if let Ok(proc) = Process::new_with_root(self.root.join(pid.to_string())) { break Some(Ok(proc)); } } } Some(Err(e)) => break Some(Err(io::Error::from(e).into())), None => break None, } } } } /// Provides information about memory usage, measured in pages. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct StatM { /// Total program size, measured in pages /// /// (same as VmSize in /proc//status) pub size: u64, /// Resident set size, measured in pages /// /// (same as VmRSS in /proc//status) pub resident: u64, /// number of resident shared pages (i.e., backed by a file) /// /// (same as RssFile+RssShmem in /proc//status) pub shared: u64, /// Text (code) pub text: u64, /// library (unused since Linux 2.6; always 0) pub lib: u64, /// data + stack pub data: u64, /// dirty pages (unused since Linux 2.6; always 0) pub dt: u64, } impl StatM { fn from_reader(mut r: R) -> ProcResult { let mut line = String::new(); r.read_to_string(&mut line)?; let mut s = line.split_whitespace(); let size = expect!(from_iter(&mut s)); let resident = expect!(from_iter(&mut s)); let shared = expect!(from_iter(&mut s)); let text = expect!(from_iter(&mut s)); let lib = expect!(from_iter(&mut s)); let data = expect!(from_iter(&mut s)); let dt = expect!(from_iter(&mut s)); if cfg!(test) { assert!(s.next().is_none()); } Ok(StatM { size, resident, shared, text, lib, data, dt, }) } } #[cfg(test)] mod tests; procfs-0.14.2/src/process/mount.rs000064400000000000000000000611411046102023000151640ustar 00000000000000use bitflags::bitflags; use crate::{from_iter, FileWrapper, ProcResult}; use std::collections::HashMap; use std::io::{BufRead, BufReader, Lines, Read}; use std::path::PathBuf; use std::time::Duration; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; bitflags! { #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct NFSServerCaps: u32 { const NFS_CAP_READDIRPLUS = 1; const NFS_CAP_HARDLINKS = (1 << 1); const NFS_CAP_SYMLINKS = (1 << 2); const NFS_CAP_ACLS = (1 << 3); const NFS_CAP_ATOMIC_OPEN = (1 << 4); const NFS_CAP_LGOPEN = (1 << 5); const NFS_CAP_FILEID = (1 << 6); const NFS_CAP_MODE = (1 << 7); const NFS_CAP_NLINK = (1 << 8); const NFS_CAP_OWNER = (1 << 9); const NFS_CAP_OWNER_GROUP = (1 << 10); const NFS_CAP_ATIME = (1 << 11); const NFS_CAP_CTIME = (1 << 12); const NFS_CAP_MTIME = (1 << 13); const NFS_CAP_POSIX_LOCK = (1 << 14); const NFS_CAP_UIDGID_NOMAP = (1 << 15); const NFS_CAP_STATEID_NFSV41 = (1 << 16); const NFS_CAP_ATOMIC_OPEN_V1 = (1 << 17); const NFS_CAP_SECURITY_LABEL = (1 << 18); const NFS_CAP_SEEK = (1 << 19); const NFS_CAP_ALLOCATE = (1 << 20); const NFS_CAP_DEALLOCATE = (1 << 21); const NFS_CAP_LAYOUTSTATS = (1 << 22); const NFS_CAP_CLONE = (1 << 23); const NFS_CAP_COPY = (1 << 24); const NFS_CAP_OFFLOAD_CANCEL = (1 << 25); } } impl super::Process { /// Returns the [MountStat] data for this processes mount namespace. pub fn mountstats(&self) -> ProcResult> { let file = FileWrapper::open_at(&self.root, &self.fd, "mountstats")?; MountStat::from_reader(file) } /// Returns info about the mountpoints in this this process's mount namespace /// /// This data is taken from the `/proc/[pid]/mountinfo` file /// /// (Since Linux 2.6.26) pub fn mountinfo(&self) -> ProcResult> { let file = FileWrapper::open_at(&self.root, &self.fd, "mountinfo")?; let bufread = BufReader::new(file); let lines = bufread.lines(); let mut vec = Vec::new(); for line in lines { vec.push(MountInfo::from_line(&line?)?); } Ok(vec) } } /// Information about a specific mount in a process's mount namespace. /// /// This data is taken from the `/proc/[pid]/mountinfo` file. /// /// For an example, see the [mountinfo.rs](https://github.com/eminence/procfs/tree/master/examples) /// example in the source repo. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct MountInfo { /// Mount ID. A unique ID for the mount (but may be reused after `unmount`) pub mnt_id: i32, /// Parent mount ID. The ID of the parent mount (or of self for the root of the mount /// namespace's mount tree). /// /// If the parent mount point lies outside the process's root directory, the ID shown here /// won't have a corresponding record in mountinfo whose mount ID matches this parent mount /// ID (because mount points that lie outside the process's root directory are not shown in /// mountinfo). As a special case of this point, the process's root mount point may have a /// parent mount (for the initramfs filesystem) that lies outside the process's root /// directory, and an entry for that mount point will not appear in mountinfo. pub pid: i32, /// The value of `st_dev` for files on this filesystem pub majmin: String, /// The pathname of the directory in the filesystem which forms the root of this mount. pub root: String, /// The pathname of the mount point relative to the process's root directory. pub mount_point: PathBuf, /// Per-mount options pub mount_options: HashMap>, /// Optional fields pub opt_fields: Vec, /// Filesystem type pub fs_type: String, /// Mount source pub mount_source: Option, /// Per-superblock options. pub super_options: HashMap>, } impl MountInfo { pub(crate) fn from_line(line: &str) -> ProcResult { let mut split = line.split_whitespace(); let mnt_id = expect!(from_iter(&mut split)); let pid = expect!(from_iter(&mut split)); let majmin: String = expect!(from_iter(&mut split)); let root = expect!(from_iter(&mut split)); let mount_point = expect!(from_iter(&mut split)); let mount_options = { let mut map = HashMap::new(); let all_opts = expect!(split.next()); for opt in all_opts.split(',') { let mut s = opt.splitn(2, '='); let opt_name = expect!(s.next()); map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned())); } map }; let mut opt_fields = Vec::new(); loop { let f = expect!(split.next()); if f == "-" { break; } let mut s = f.split(':'); let opt = match expect!(s.next()) { "shared" => { let val = expect!(from_iter(&mut s)); MountOptFields::Shared(val) } "master" => { let val = expect!(from_iter(&mut s)); MountOptFields::Master(val) } "propagate_from" => { let val = expect!(from_iter(&mut s)); MountOptFields::PropagateFrom(val) } "unbindable" => MountOptFields::Unbindable, _ => continue, }; opt_fields.push(opt); } let fs_type: String = expect!(from_iter(&mut split)); let mount_source = match expect!(split.next()) { "none" => None, x => Some(x.to_owned()), }; let super_options = { let mut map = HashMap::new(); let all_opts = expect!(split.next()); for opt in all_opts.split(',') { let mut s = opt.splitn(2, '='); let opt_name = expect!(s.next()); map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned())); } map }; Ok(MountInfo { mnt_id, pid, majmin, root, mount_point, mount_options, opt_fields, fs_type, mount_source, super_options, }) } } /// Optional fields used in [MountInfo] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum MountOptFields { /// This mount point is shared in peer group. Each peer group has a unique ID that is /// automatically generated by the kernel, and all mount points in the same peer group will /// show the same ID Shared(u32), /// THis mount is a slave to the specified shared peer group. Master(u32), /// This mount is a slave and receives propagation from the shared peer group PropagateFrom(u32), /// This is an unbindable mount Unbindable, } /// Mount information from `/proc//mountstats`. /// /// # Example: /// /// ``` /// # use procfs::process::Process; /// let stats = Process::myself().unwrap().mountstats().unwrap(); /// /// for mount in stats { /// println!("{} mounted on {} wth type {}", /// mount.device.unwrap_or("??".to_owned()), /// mount.mount_point.display(), /// mount.fs /// ); /// } /// ``` #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct MountStat { /// The name of the mounted device pub device: Option, /// The mountpoint within the filesystem tree pub mount_point: PathBuf, /// The filesystem type pub fs: String, /// If the mount is NFS, this will contain various NFS statistics pub statistics: Option, } impl MountStat { pub fn from_reader(r: R) -> ProcResult> { let mut v = Vec::new(); let bufread = BufReader::new(r); let mut lines = bufread.lines(); while let Some(Ok(line)) = lines.next() { if line.starts_with("device ") { // line will be of the format: // device proc mounted on /proc with fstype proc let mut s = line.split_whitespace(); let device = Some(expect!(s.nth(1)).to_owned()); let mount_point = PathBuf::from(expect!(s.nth(2))); let fs = expect!(s.nth(2)).to_owned(); let statistics = match s.next() { Some(stats) if stats.starts_with("statvers=") => { Some(MountNFSStatistics::from_lines(&mut lines, &stats[9..])?) } _ => None, }; v.push(MountStat { device, mount_point, fs, statistics, }); } } Ok(v) } } /// Only NFS mounts provide additional statistics in `MountStat` entries. // // Thank you to Chris Siebenmann for their helpful work in documenting these structures: // https://utcc.utoronto.ca/~cks/space/blog/linux/NFSMountstatsIndex #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct MountNFSStatistics { /// The version of the NFS statistics block. Either "1.0" or "1.1". pub version: String, /// The mount options. /// /// The meaning of these can be found in the manual pages for mount(5) and nfs(5) pub opts: Vec, /// Duration the NFS mount has been in existence. pub age: Duration, // * fsc (?) // * impl_id (NFSv4): Option> /// NFS Capabilities. /// /// See `include/linux/nfs_fs_sb.h` /// /// Some known values: /// * caps: server capabilities. See [NFSServerCaps]. /// * wtmult: server disk block size /// * dtsize: readdir size /// * bsize: server block size pub caps: Vec, // * nfsv4 (NFSv4): Option> pub sec: Vec, pub events: NFSEventCounter, pub bytes: NFSByteCounter, // * RPC iostats version: // * xprt // * per-op statistics pub per_op_stats: NFSPerOpStats, } impl MountNFSStatistics { // Keep reading lines until we get to a blank line fn from_lines(r: &mut Lines, statsver: &str) -> ProcResult { let mut parsing_per_op = false; let mut opts: Option> = None; let mut age = None; let mut caps = None; let mut sec = None; let mut bytes = None; let mut events = None; let mut per_op = HashMap::new(); while let Some(Ok(line)) = r.next() { let line = line.trim(); if line.trim() == "" { break; } if !parsing_per_op { if let Some(stripped) = line.strip_prefix("opts:") { opts = Some(stripped.trim().split(',').map(|s| s.to_string()).collect()); } else if let Some(stripped) = line.strip_prefix("age:") { age = Some(Duration::from_secs(from_str!(u64, stripped.trim()))); } else if let Some(stripped) = line.strip_prefix("caps:") { caps = Some(stripped.trim().split(',').map(|s| s.to_string()).collect()); } else if let Some(stripped) = line.strip_prefix("sec:") { sec = Some(stripped.trim().split(',').map(|s| s.to_string()).collect()); } else if let Some(stripped) = line.strip_prefix("bytes:") { bytes = Some(NFSByteCounter::from_str(stripped.trim())?); } else if let Some(stripped) = line.strip_prefix("events:") { events = Some(NFSEventCounter::from_str(stripped.trim())?); } if line == "per-op statistics" { parsing_per_op = true; } } else { let mut split = line.split(':'); let name = expect!(split.next()).to_string(); let stats = NFSOperationStat::from_str(expect!(split.next()))?; per_op.insert(name, stats); } } Ok(MountNFSStatistics { version: statsver.to_string(), opts: expect!(opts, "Failed to find opts field in nfs stats"), age: expect!(age, "Failed to find age field in nfs stats"), caps: expect!(caps, "Failed to find caps field in nfs stats"), sec: expect!(sec, "Failed to find sec field in nfs stats"), events: expect!(events, "Failed to find events section in nfs stats"), bytes: expect!(bytes, "Failed to find bytes section in nfs stats"), per_op_stats: per_op, }) } /// Attempts to parse the caps= value from the [caps](struct.MountNFSStatistics.html#structfield.caps) field. pub fn server_caps(&self) -> ProcResult> { for data in &self.caps { if let Some(stripped) = data.strip_prefix("caps=0x") { let val = from_str!(u32, stripped, 16); return Ok(NFSServerCaps::from_bits(val)); } } Ok(None) } } /// Represents NFS data from `/proc//mountstats` under the section `events`. /// /// The underlying data structure in the kernel can be found under *fs/nfs/iostat.h* `nfs_iostat`. /// The fields are documented in the kernel source only under *include/linux/nfs_iostat.h* `enum /// nfs_stat_eventcounters`. #[derive(Debug, Copy, Clone)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct NFSEventCounter { pub inode_revalidate: u64, pub deny_try_revalidate: u64, pub data_invalidate: u64, pub attr_invalidate: u64, pub vfs_open: u64, pub vfs_lookup: u64, pub vfs_access: u64, pub vfs_update_page: u64, pub vfs_read_page: u64, pub vfs_read_pages: u64, pub vfs_write_page: u64, pub vfs_write_pages: u64, pub vfs_get_dents: u64, pub vfs_set_attr: u64, pub vfs_flush: u64, pub vfs_fs_sync: u64, pub vfs_lock: u64, pub vfs_release: u64, pub congestion_wait: u64, pub set_attr_trunc: u64, pub extend_write: u64, pub silly_rename: u64, pub short_read: u64, pub short_write: u64, pub delay: u64, pub pnfs_read: u64, pub pnfs_write: u64, } impl NFSEventCounter { fn from_str(s: &str) -> ProcResult { let mut s = s.split_whitespace(); Ok(NFSEventCounter { inode_revalidate: from_str!(u64, expect!(s.next())), deny_try_revalidate: from_str!(u64, expect!(s.next())), data_invalidate: from_str!(u64, expect!(s.next())), attr_invalidate: from_str!(u64, expect!(s.next())), vfs_open: from_str!(u64, expect!(s.next())), vfs_lookup: from_str!(u64, expect!(s.next())), vfs_access: from_str!(u64, expect!(s.next())), vfs_update_page: from_str!(u64, expect!(s.next())), vfs_read_page: from_str!(u64, expect!(s.next())), vfs_read_pages: from_str!(u64, expect!(s.next())), vfs_write_page: from_str!(u64, expect!(s.next())), vfs_write_pages: from_str!(u64, expect!(s.next())), vfs_get_dents: from_str!(u64, expect!(s.next())), vfs_set_attr: from_str!(u64, expect!(s.next())), vfs_flush: from_str!(u64, expect!(s.next())), vfs_fs_sync: from_str!(u64, expect!(s.next())), vfs_lock: from_str!(u64, expect!(s.next())), vfs_release: from_str!(u64, expect!(s.next())), congestion_wait: from_str!(u64, expect!(s.next())), set_attr_trunc: from_str!(u64, expect!(s.next())), extend_write: from_str!(u64, expect!(s.next())), silly_rename: from_str!(u64, expect!(s.next())), short_read: from_str!(u64, expect!(s.next())), short_write: from_str!(u64, expect!(s.next())), delay: from_str!(u64, expect!(s.next())), pnfs_read: from_str!(u64, expect!(s.next())), pnfs_write: from_str!(u64, expect!(s.next())), }) } } /// Represents NFS data from `/proc//mountstats` under the section `bytes`. /// /// The underlying data structure in the kernel can be found under *fs/nfs/iostat.h* `nfs_iostat`. /// The fields are documented in the kernel source only under *include/linux/nfs_iostat.h* `enum /// nfs_stat_bytecounters` #[derive(Debug, Copy, Clone)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct NFSByteCounter { pub normal_read: u64, pub normal_write: u64, pub direct_read: u64, pub direct_write: u64, pub server_read: u64, pub server_write: u64, pub pages_read: u64, pub pages_write: u64, } impl NFSByteCounter { fn from_str(s: &str) -> ProcResult { let mut s = s.split_whitespace(); Ok(NFSByteCounter { normal_read: from_str!(u64, expect!(s.next())), normal_write: from_str!(u64, expect!(s.next())), direct_read: from_str!(u64, expect!(s.next())), direct_write: from_str!(u64, expect!(s.next())), server_read: from_str!(u64, expect!(s.next())), server_write: from_str!(u64, expect!(s.next())), pages_read: from_str!(u64, expect!(s.next())), pages_write: from_str!(u64, expect!(s.next())), }) } } /// Represents NFS data from `/proc//mountstats` under the section of `per-op statistics`. /// /// Here is what the Kernel says about the attributes: /// /// Regarding `operations`, `transmissions` and `major_timeouts`: /// /// > These counters give an idea about how many request /// > transmissions are required, on average, to complete that /// > particular procedure. Some procedures may require more /// > than one transmission because the server is unresponsive, /// > the client is retransmitting too aggressively, or the /// > requests are large and the network is congested. /// /// Regarding `bytes_sent` and `bytes_recv`: /// /// > These count how many bytes are sent and received for a /// > given RPC procedure type. This indicates how much load a /// > particular procedure is putting on the network. These /// > counts include the RPC and ULP headers, and the request /// > payload. /// /// Regarding `cum_queue_time`, `cum_resp_time` and `cum_total_req_time`: /// /// > The length of time an RPC request waits in queue before /// > transmission, the network + server latency of the request, /// > and the total time the request spent from init to release /// > are measured. /// /// (source: *include/linux/sunrpc/metrics.h* `struct rpc_iostats`) #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct NFSOperationStat { /// Count of rpc operations. pub operations: u64, /// Count of rpc transmissions pub transmissions: u64, /// Count of rpc major timeouts pub major_timeouts: u64, /// Count of bytes send. Does not only include the RPC payload but the RPC headers as well. pub bytes_sent: u64, /// Count of bytes received as `bytes_sent`. pub bytes_recv: u64, /// How long all requests have spend in the queue before being send. pub cum_queue_time: Duration, /// How long it took to get a response back. pub cum_resp_time: Duration, /// How long all requests have taken from beeing queued to the point they where completely /// handled. pub cum_total_req_time: Duration, } impl NFSOperationStat { fn from_str(s: &str) -> ProcResult { let mut s = s.split_whitespace(); let operations = from_str!(u64, expect!(s.next())); let transmissions = from_str!(u64, expect!(s.next())); let major_timeouts = from_str!(u64, expect!(s.next())); let bytes_sent = from_str!(u64, expect!(s.next())); let bytes_recv = from_str!(u64, expect!(s.next())); let cum_queue_time_ms = from_str!(u64, expect!(s.next())); let cum_resp_time_ms = from_str!(u64, expect!(s.next())); let cum_total_req_time_ms = from_str!(u64, expect!(s.next())); Ok(NFSOperationStat { operations, transmissions, major_timeouts, bytes_sent, bytes_recv, cum_queue_time: Duration::from_millis(cum_queue_time_ms), cum_resp_time: Duration::from_millis(cum_resp_time_ms), cum_total_req_time: Duration::from_millis(cum_total_req_time_ms), }) } } pub type NFSPerOpStats = HashMap; #[cfg(test)] mod tests { use crate::process::*; use std::time::Duration; #[test] fn test_mountinfo() { let s = "25 0 8:1 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro"; let stat = MountInfo::from_line(s).unwrap(); println!("{:?}", stat); } #[test] fn test_mountinfo_live() { let me = Process::myself().unwrap(); let mounts = me.mountinfo().unwrap(); println!("{:#?}", mounts); } #[test] fn test_proc_mountstats() { let simple = MountStat::from_reader( "device /dev/md127 mounted on /boot with fstype ext2 device /dev/md124 mounted on /home with fstype ext4 device tmpfs mounted on /run/user/0 with fstype tmpfs " .as_bytes(), ) .unwrap(); let simple_parsed = vec![ MountStat { device: Some("/dev/md127".to_string()), mount_point: PathBuf::from("/boot"), fs: "ext2".to_string(), statistics: None, }, MountStat { device: Some("/dev/md124".to_string()), mount_point: PathBuf::from("/home"), fs: "ext4".to_string(), statistics: None, }, MountStat { device: Some("tmpfs".to_string()), mount_point: PathBuf::from("/run/user/0"), fs: "tmpfs".to_string(), statistics: None, }, ]; assert_eq!(simple, simple_parsed); let mountstats = MountStat::from_reader("device elwe:/space mounted on /srv/elwe/space with fstype nfs4 statvers=1.1 opts: rw,vers=4.1,rsize=131072,wsize=131072,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=krb5,clientaddr=10.0.1.77,local_lock=none age: 3542 impl_id: name='',domain='',date='0,0' caps: caps=0x3ffdf,wtmult=512,dtsize=32768,bsize=0,namlen=255 nfsv4: bm0=0xfdffbfff,bm1=0x40f9be3e,bm2=0x803,acl=0x3,sessions,pnfs=not configured sec: flavor=6,pseudoflavor=390003 events: 114 1579 5 3 132 20 3019 1 2 3 4 5 115 1 4 1 2 4 3 4 5 6 7 8 9 0 1 bytes: 1 2 3 4 5 6 7 8 RPC iostats version: 1.0 p/v: 100003/4 (nfs) xprt: tcp 909 0 1 0 2 294 294 0 294 0 2 0 0 per-op statistics NULL: 0 0 0 0 0 0 0 0 READ: 1 2 3 4 5 6 7 8 WRITE: 0 0 0 0 0 0 0 0 COMMIT: 0 0 0 0 0 0 0 0 OPEN: 1 1 0 320 420 0 124 124 ".as_bytes()).unwrap(); let nfs_v4 = &mountstats[0]; match &nfs_v4.statistics { Some(stats) => { assert_eq!("1.1".to_string(), stats.version, "mountstats version wrongly parsed."); assert_eq!(Duration::from_secs(3542), stats.age); assert_eq!(1, stats.bytes.normal_read); assert_eq!(114, stats.events.inode_revalidate); assert!(stats.server_caps().unwrap().is_some()); } None => { panic!("Failed to retrieve nfs statistics"); } } } #[test] fn test_proc_mountstats_live() { // this tries to parse a live mountstats file // there are no assertions, but we still want to check for parsing errors (which can // cause panics) let stats = MountStat::from_reader(FileWrapper::open("/proc/self/mountstats").unwrap()).unwrap(); for stat in stats { println!("{:#?}", stat); if let Some(nfs) = stat.statistics { println!(" {:?}", nfs.server_caps().unwrap()); } } } } procfs-0.14.2/src/process/namespaces.rs000064400000000000000000000057501046102023000161450ustar 00000000000000use rustix::fs::{AtFlags, Mode, OFlags}; use std::{collections::HashMap, ffi::OsString, path::PathBuf}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; use crate::ProcResult; use super::Process; impl Process { /// Describes namespaces to which the process with the corresponding PID belongs. /// Doc reference: /// The namespace type is the key for the HashMap, i.e 'net', 'user', etc. pub fn namespaces(&self) -> ProcResult> { let mut namespaces = HashMap::new(); let dir_ns = wrap_io_error!( self.root.join("ns"), rustix::fs::openat( &self.fd, "ns", OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, Mode::empty() ) )?; let dir = wrap_io_error!(self.root.join("ns"), rustix::fs::Dir::read_from(&dir_ns))?; for entry in dir { let entry = entry.map_err(|_| build_internal_error!(format!("Unable to get ns dir entry")))?; match entry.file_name().to_bytes() { b"." | b".." => continue, _ => {} }; let path = self.root.join("ns").join(entry.file_name().to_string_lossy().as_ref()); let ns_type = OsString::from(entry.file_name().to_string_lossy().as_ref()); let stat = rustix::fs::statat(&dir_ns, entry.file_name(), AtFlags::empty()) .map_err(|_| build_internal_error!(format!("Unable to stat {:?}", path)))?; if let Some(n) = namespaces.insert( ns_type.clone(), Namespace { ns_type, path, identifier: stat.st_ino, device_id: stat.st_dev, }, ) { return Err(build_internal_error!(format!( "NsType appears more than once {:?}", n.ns_type ))); } } Ok(namespaces) } } /// Information about a namespace /// /// See also the [Process::namespaces()] method #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Namespace { /// Namespace type pub ns_type: OsString, /// Handle to the namespace pub path: PathBuf, /// Namespace identifier (inode number) pub identifier: u64, /// Device id of the namespace pub device_id: u64, } impl PartialEq for Namespace { fn eq(&self, other: &Self) -> bool { // see https://lore.kernel.org/lkml/87poky5ca9.fsf@xmission.com/ self.identifier == other.identifier && self.device_id == other.device_id } } impl Eq for Namespace {} #[cfg(test)] mod tests { use crate::process::Process; #[test] fn test_namespaces() { let myself = Process::myself().unwrap(); let namespaces = myself.namespaces().unwrap(); print!("{:?}", namespaces); } } procfs-0.14.2/src/process/pagemap.rs000064400000000000000000000140251046102023000154330ustar 00000000000000use crate::{FileWrapper, ProcResult}; use bitflags::bitflags; use std::{ io::{BufReader, Read, Seek, SeekFrom}, mem::size_of, ops::{Bound, RangeBounds}, }; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; const fn genmask(high: usize, low: usize) -> u64 { let mask_bits = size_of::() * 8; (!0 - (1 << low) + 1) & (!0 >> (mask_bits - 1 - high)) } // source: include/linux/swap.h const MAX_SWAPFILES_SHIFT: usize = 5; // source: fs/proc/task_mmu.c bitflags! { /// Represents the fields and flags in a page table entry for a swapped page. #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct SwapPageFlags: u64 { /// Swap type if swapped #[doc(hidden)] const SWAP_TYPE = genmask(MAX_SWAPFILES_SHIFT - 1, 0); /// Swap offset if swapped #[doc(hidden)] const SWAP_OFFSET = genmask(54, MAX_SWAPFILES_SHIFT); /// PTE is soft-dirty const SOFT_DIRTY = 1 << 55; /// Page is exclusively mapped const MMAP_EXCLUSIVE = 1 << 56; /// Page is file-page or shared-anon const FILE = 1 << 61; /// Page is swapped #[doc(hidden)] const SWAP = 1 << 62; /// Page is present const PRESENT = 1 << 63; } } impl SwapPageFlags { /// Returns the swap type recorded in this entry. pub fn get_swap_type(&self) -> u64 { (*self & Self::SWAP_TYPE).bits() } /// Returns the swap offset recorded in this entry. pub fn get_swap_offset(&self) -> u64 { (*self & Self::SWAP_OFFSET).bits() >> MAX_SWAPFILES_SHIFT } } bitflags! { /// Represents the fields and flags in a page table entry for a memory page. #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct MemoryPageFlags: u64 { /// Page frame number if present #[doc(hidden)] const PFN = genmask(54, 0); /// PTE is soft-dirty const SOFT_DIRTY = 1 << 55; /// Page is exclusively mapped const MMAP_EXCLUSIVE = 1 << 56; /// Page is file-page or shared-anon const FILE = 1 << 61; /// Page is swapped #[doc(hidden)] const SWAP = 1 << 62; /// Page is present const PRESENT = 1 << 63; } } impl MemoryPageFlags { /// Returns the page frame number recorded in this entry. pub fn get_page_frame_number(&self) -> u64 { (*self & Self::PFN).bits() } } /// Represents a page table entry in `/proc//pagemap`. #[derive(Debug)] pub enum PageInfo { /// Entry referring to a memory page MemoryPage(MemoryPageFlags), /// Entry referring to a swapped page SwapPage(SwapPageFlags), } impl PageInfo { pub(crate) fn parse_info(info: u64) -> Self { let flags = MemoryPageFlags::from_bits_truncate(info); if flags.contains(MemoryPageFlags::SWAP) { Self::SwapPage(SwapPageFlags::from_bits_truncate(info)) } else { Self::MemoryPage(flags) } } } /// Parses page table entries accessing `/proc//pagemap`. pub struct PageMap { reader: BufReader, } impl PageMap { pub(crate) fn from_file_wrapper(file: FileWrapper) -> Self { Self { reader: BufReader::new(file), } } /// Retrieves information in the page table entry for the page at index `page_index`. pub fn get_info(&mut self, page_index: usize) -> ProcResult { self.get_range_info(page_index..page_index + 1) .map(|mut vec| vec.pop().unwrap()) } /// Retrieves information in the page table entry for the pages with index in range `page_range`. pub fn get_range_info(&mut self, page_range: impl RangeBounds) -> ProcResult> { // `start` is always included let start = match page_range.start_bound() { Bound::Included(v) => *v, Bound::Excluded(v) => *v + 1, Bound::Unbounded => 0, }; // `end` is always excluded let end = match page_range.end_bound() { Bound::Included(v) => *v + 1, Bound::Excluded(v) => *v, Bound::Unbounded => std::usize::MAX / crate::page_size().unwrap() as usize, }; let start_position = (start * size_of::()) as u64; self.reader.seek(SeekFrom::Start(start_position))?; let mut page_infos = Vec::with_capacity((end - start) as usize); for _ in start..end { let mut info_bytes = [0; size_of::()]; self.reader.read_exact(&mut info_bytes)?; page_infos.push(PageInfo::parse_info(u64::from_ne_bytes(info_bytes))); } Ok(page_infos) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_genmask() { let mask = genmask(3, 1); assert_eq!(mask, 0b1110); let mask = genmask(3, 0); assert_eq!(mask, 0b1111); let mask = genmask(63, 62); assert_eq!(mask, 0b11 << 62); } #[test] fn test_page_info() { let pagemap_entry: u64 = 0b1000000110000000000000000000000000000000000000000000000000000011; let info = PageInfo::parse_info(pagemap_entry); if let PageInfo::MemoryPage(memory_flags) = info { assert!(memory_flags .contains(MemoryPageFlags::PRESENT | MemoryPageFlags::MMAP_EXCLUSIVE | MemoryPageFlags::SOFT_DIRTY)); assert_eq!(memory_flags.get_page_frame_number(), 0b11); } else { panic!("Wrong SWAP decoding"); } let pagemap_entry: u64 = 0b1100000110000000000000000000000000000000000000000000000001100010; let info = PageInfo::parse_info(pagemap_entry); if let PageInfo::SwapPage(swap_flags) = info { assert!( swap_flags.contains(SwapPageFlags::PRESENT | SwapPageFlags::MMAP_EXCLUSIVE | SwapPageFlags::SOFT_DIRTY) ); assert_eq!(swap_flags.get_swap_type(), 0b10); assert_eq!(swap_flags.get_swap_offset(), 0b11); } else { panic!("Wrong SWAP decoding"); } } } procfs-0.14.2/src/process/schedstat.rs000064400000000000000000000021511046102023000160000ustar 00000000000000use crate::from_iter; use crate::ProcResult; use std::io::Read; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// Provides scheduler statistics of the process, based on the `/proc//schedstat` file. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Schedstat { /// Time spent on the cpu. /// /// Measured in nanoseconds. pub sum_exec_runtime: u64, /// Time spent waiting on a runqueue. /// /// Measured in nanoseconds. pub run_delay: u64, /// \# of timeslices run on this cpu. pub pcount: u64, } impl Schedstat { pub fn from_reader(mut r: R) -> ProcResult { let mut line = String::new(); r.read_to_string(&mut line)?; let mut s = line.split_whitespace(); let schedstat = Schedstat { sum_exec_runtime: expect!(from_iter(&mut s)), run_delay: expect!(from_iter(&mut s)), pcount: expect!(from_iter(&mut s)), }; if cfg!(test) { assert!(s.next().is_none()); } Ok(schedstat) } } procfs-0.14.2/src/process/smaps_rollup.rs000064400000000000000000000043101046102023000165350ustar 00000000000000use super::{MemoryMap, MemoryMapData}; use crate::{ProcError, ProcResult}; use std::io::{BufRead, BufReader, Read}; #[derive(Debug)] pub struct SmapsRollup { pub memory_map: MemoryMap, pub memory_map_data: MemoryMapData, } impl SmapsRollup { // this implemenation is similar but not identical to Process::smaps() pub fn from_reader(r: R) -> ProcResult { let reader = BufReader::new(r); let mut memory_map = MemoryMap::new(); let mut memory_map_data: MemoryMapData = Default::default(); let mut first = true; for line in reader.lines() { let line = line.map_err(|_| ProcError::Incomplete(None))?; if first { memory_map = MemoryMap::from_line(&line)?; first = false; continue; } let mut parts = line.split_ascii_whitespace(); let key = parts.next(); let value = parts.next(); if let (Some(k), Some(v)) = (key, value) { // While most entries do have one, not all of them do. let size_suffix = parts.next(); // Limited poking at /proc//smaps and then checking if "MB", "GB", and "TB" appear in the C file that is // supposedly responsible for creating smaps, has lead me to believe that the only size suffixes we'll ever encounter // "kB", which is most likely kibibytes. Actually checking if the size suffix is any of the above is a way to // future-proof the code, but I am not sure it is worth doing so. let size_multiplier = if size_suffix.is_some() { 1024 } else { 1 }; let v = v .parse::() .map_err(|_| ProcError::Other("Value in `Key: Value` pair was not actually a number".into()))?; // This ignores the case when our Key: Value pairs are really Key Value pairs. Is this a good idea? let k = k.trim_end_matches(':'); memory_map_data.map.insert(k.into(), v * size_multiplier); } } Ok(SmapsRollup { memory_map, memory_map_data, }) } } procfs-0.14.2/src/process/stat.rs000064400000000000000000000425371046102023000150050ustar 00000000000000use super::ProcState; use super::StatFlags; #[cfg(feature = "chrono")] use crate::TICKS_PER_SECOND; use crate::{from_iter, KernelVersion, ProcResult}; use crate::{ProcError, KERNEL, PAGESIZE}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; use std::io::Read; use std::str::FromStr; macro_rules! since_kernel { ($a:tt, $b:tt, $c:tt, $e:expr) => { if let Ok(kernel) = *KERNEL { if kernel >= KernelVersion::new($a, $b, $c) { Some($e) } else { None } } else { None } }; } /// Status information about the process, based on the `/proc//stat` file. /// /// To construct one of these structures, you have to first create a [Process](crate::process::Process). /// /// Not all fields are available in every kernel. These fields have `Option` types. /// /// New fields to this struct may be added at any time (even without a major or minor semver bump). #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[non_exhaustive] pub struct Stat { /// The process ID. pub pid: i32, /// The filename of the executable, in parentheses. /// /// This is visible whether or not the executable is swapped out. /// /// Note that if the actual comm field contains invalid UTF-8 characters, they will be replaced /// here by the U+FFFD replacement character. pub comm: String, /// Process State. /// /// See [state()](#method.state) to get the process state as an enum. pub state: char, /// The PID of the parent of this process. pub ppid: i32, /// The process group ID of the process. pub pgrp: i32, /// The session ID of the process. pub session: i32, /// The controlling terminal of the process. /// /// The minor device number is contained in the combination of bits 31 to 20 and 7 to 0; /// the major device number is in bits 15 to 8. /// /// See [tty_nr()](#method.tty_nr) to get this value decoded into a (major, minor) tuple pub tty_nr: i32, /// The ID of the foreground process group of the controlling terminal of the process. pub tpgid: i32, /// The kernel flags word of the process. /// /// For bit meanings, see the PF_* defines in the Linux kernel source file /// [`include/linux/sched.h`](https://github.com/torvalds/linux/blob/master/include/linux/sched.h). /// /// See [flags()](#method.flags) to get a [`StatFlags`](struct.StatFlags.html) bitfield object. pub flags: u32, /// The number of minor faults the process has made which have not required loading a memory /// page from disk. pub minflt: u64, /// The number of minor faults that the process's waited-for children have made. pub cminflt: u64, /// The number of major faults the process has made which have required loading a memory page /// from disk. pub majflt: u64, /// The number of major faults that the process's waited-for children have made. pub cmajflt: u64, /// Amount of time that this process has been scheduled in user mode, measured in clock ticks /// (divide by [`ticks_per_second()`](crate::ticks_per_second). /// /// This includes guest time, guest_time (time spent running a virtual CPU, see below), so that /// applications that are not aware of the guest time field do not lose that time from their /// calculations. pub utime: u64, /// Amount of time that this process has been scheduled in kernel mode, measured in clock ticks /// (divide by [`ticks_per_second()`](crate::ticks_per_second)). pub stime: u64, /// Amount of time that this process's waited-for children have been scheduled in /// user mode, measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)). /// /// This includes guest time, cguest_time (time spent running a virtual CPU, see below). pub cutime: i64, /// Amount of time that this process's waited-for children have been scheduled in kernel /// mode, measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)). pub cstime: i64, /// For processes running a real-time scheduling policy (policy below; see sched_setscheduler(2)), /// this is the negated scheduling priority, minus one; /// /// That is, a number in the range -2 to -100, /// corresponding to real-time priority 1 to 99. For processes running under a non-real-time /// scheduling policy, this is the raw nice value (setpriority(2)) as represented in the kernel. /// The kernel stores nice values as numbers in the range 0 (high) to 39 (low), corresponding /// to the user-visible nice range of -20 to 19. /// (This explanation is for Linux 2.6) /// /// Before Linux 2.6, this was a scaled value based on the scheduler weighting given to this process. pub priority: i64, /// The nice value (see `setpriority(2)`), a value in the range 19 (low priority) to -20 (high priority). pub nice: i64, /// Number of threads in this process (since Linux 2.6). Before kernel 2.6, this field was /// hard coded to 0 as a placeholder for an earlier removed field. pub num_threads: i64, /// The time in jiffies before the next SIGALRM is sent to the process due to an interval /// timer. /// /// Since kernel 2.6.17, this field is no longer maintained, and is hard coded as 0. pub itrealvalue: i64, /// The time the process started after system boot. /// /// In kernels before Linux 2.6, this value was expressed in jiffies. Since Linux 2.6, the /// value is expressed in clock ticks (divide by `sysconf(_SC_CLK_TCK)`). /// #[cfg_attr( feature = "chrono", doc = "See also the [Stat::starttime()] method to get the starttime as a `DateTime` object" )] #[cfg_attr( not(feature = "chrono"), doc = "If you compile with the optional `chrono` feature, you can use the `starttime()` method to get the starttime as a `DateTime` object" )] pub starttime: u64, /// Virtual memory size in bytes. pub vsize: u64, /// Resident Set Size: number of pages the process has in real memory. /// /// This is just the pages which count toward text, data, or stack space. /// This does not include pages which have not been demand-loaded in, or which are swapped out. pub rss: u64, /// Current soft limit in bytes on the rss of the process; see the description of RLIMIT_RSS in /// getrlimit(2). pub rsslim: u64, /// The address above which program text can run. pub startcode: u64, /// The address below which program text can run. pub endcode: u64, /// The address of the start (i.e., bottom) of the stack. pub startstack: u64, /// The current value of ESP (stack pointer), as found in the kernel stack page for the /// process. pub kstkesp: u64, /// The current EIP (instruction pointer). pub kstkeip: u64, /// The bitmap of pending signals, displayed as a decimal number. Obsolete, because it does /// not provide information on real-time signals; use `/proc//status` instead. pub signal: u64, /// The bitmap of blocked signals, displayed as a decimal number. Obsolete, because it does /// not provide information on real-time signals; use `/proc//status` instead. pub blocked: u64, /// The bitmap of ignored signals, displayed as a decimal number. Obsolete, because it does /// not provide information on real-time signals; use `/proc//status` instead. pub sigignore: u64, /// The bitmap of caught signals, displayed as a decimal number. Obsolete, because it does not /// provide information on real-time signals; use `/proc//status` instead. pub sigcatch: u64, /// This is the "channel" in which the process is waiting. It is the address of a location /// in the kernel where the process is sleeping. The corresponding symbolic name can be found in /// `/proc//wchan`. pub wchan: u64, /// Number of pages swapped **(not maintained)**. pub nswap: u64, /// Cumulative nswap for child processes **(not maintained)**. pub cnswap: u64, /// Signal to be sent to parent when we die. /// /// (since Linux 2.1.22) pub exit_signal: Option, /// CPU number last executed on. /// /// (since Linux 2.2.8) pub processor: Option, /// Real-time scheduling priority /// /// Real-time scheduling priority, a number in the range 1 to 99 for processes scheduled under a real-time policy, or 0, for non-real-time processes /// /// (since Linux 2.5.19) pub rt_priority: Option, /// Scheduling policy (see sched_setscheduler(2)). /// /// Decode using the `SCHED_*` constants in `linux/sched.h`. /// /// (since Linux 2.5.19) pub policy: Option, /// Aggregated block I/O delays, measured in clock ticks (centiseconds). /// /// (since Linux 2.6.18) pub delayacct_blkio_ticks: Option, /// Guest time of the process (time spent running a virtual CPU for a guest operating system), /// measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)) /// /// (since Linux 2.6.24) pub guest_time: Option, /// Guest time of the process's children, measured in clock ticks (divide by /// [`ticks_per_second()`](crate::ticks_per_second)). /// /// (since Linux 2.6.24) pub cguest_time: Option, /// Address above which program initialized and uninitialized (BSS) data are placed. /// /// (since Linux 3.3) pub start_data: Option, /// Address below which program initialized and uninitialized (BSS) data are placed. /// /// (since Linux 3.3) pub end_data: Option, /// Address above which program heap can be expanded with brk(2). /// /// (since Linux 3.3) pub start_brk: Option, /// Address above which program command-line arguments (argv) are placed. /// /// (since Linux 3.5) pub arg_start: Option, /// Address below program command-line arguments (argv) are placed. /// /// (since Linux 3.5) pub arg_end: Option, /// Address above which program environment is placed. /// /// (since Linux 3.5) pub env_start: Option, /// Address below which program environment is placed. /// /// (since Linux 3.5) pub env_end: Option, /// The thread's exit status in the form reported by waitpid(2). /// /// (since Linux 3.5) pub exit_code: Option, } impl Stat { #[allow(clippy::cognitive_complexity)] pub fn from_reader(mut r: R) -> ProcResult { // read in entire thing, this is only going to be 1 line let mut buf = Vec::with_capacity(512); r.read_to_end(&mut buf)?; let line = String::from_utf8_lossy(&buf); let buf = line.trim(); // find the first opening paren, and split off the first part (pid) let start_paren = expect!(buf.find('(')); let end_paren = expect!(buf.rfind(')')); let pid_s = &buf[..start_paren - 1]; let comm = buf[start_paren + 1..end_paren].to_string(); let rest = &buf[end_paren + 2..]; let pid = expect!(FromStr::from_str(pid_s)); let mut rest = rest.split(' '); let state = expect!(expect!(rest.next()).chars().next()); let ppid = expect!(from_iter(&mut rest)); let pgrp = expect!(from_iter(&mut rest)); let session = expect!(from_iter(&mut rest)); let tty_nr = expect!(from_iter(&mut rest)); let tpgid = expect!(from_iter(&mut rest)); let flags = expect!(from_iter(&mut rest)); let minflt = expect!(from_iter(&mut rest)); let cminflt = expect!(from_iter(&mut rest)); let majflt = expect!(from_iter(&mut rest)); let cmajflt = expect!(from_iter(&mut rest)); let utime = expect!(from_iter(&mut rest)); let stime = expect!(from_iter(&mut rest)); let cutime = expect!(from_iter(&mut rest)); let cstime = expect!(from_iter(&mut rest)); let priority = expect!(from_iter(&mut rest)); let nice = expect!(from_iter(&mut rest)); let num_threads = expect!(from_iter(&mut rest)); let itrealvalue = expect!(from_iter(&mut rest)); let starttime = expect!(from_iter(&mut rest)); let vsize = expect!(from_iter(&mut rest)); let rss = expect!(from_iter(&mut rest)); let rsslim = expect!(from_iter(&mut rest)); let startcode = expect!(from_iter(&mut rest)); let endcode = expect!(from_iter(&mut rest)); let startstack = expect!(from_iter(&mut rest)); let kstkesp = expect!(from_iter(&mut rest)); let kstkeip = expect!(from_iter(&mut rest)); let signal = expect!(from_iter(&mut rest)); let blocked = expect!(from_iter(&mut rest)); let sigignore = expect!(from_iter(&mut rest)); let sigcatch = expect!(from_iter(&mut rest)); let wchan = expect!(from_iter(&mut rest)); let nswap = expect!(from_iter(&mut rest)); let cnswap = expect!(from_iter(&mut rest)); let exit_signal = since_kernel!(2, 1, 22, expect!(from_iter(&mut rest))); let processor = since_kernel!(2, 2, 8, expect!(from_iter(&mut rest))); let rt_priority = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest))); let policy = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest))); let delayacct_blkio_ticks = since_kernel!(2, 6, 18, expect!(from_iter(&mut rest))); let guest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest))); let cguest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest))); let start_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); let end_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); let start_brk = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); let arg_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); let arg_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); let env_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); let env_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); let exit_code = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); Ok(Stat { pid, comm, state, ppid, pgrp, session, tty_nr, tpgid, flags, minflt, cminflt, majflt, cmajflt, utime, stime, cutime, cstime, priority, nice, num_threads, itrealvalue, starttime, vsize, rss, rsslim, startcode, endcode, startstack, kstkesp, kstkeip, signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap, exit_signal, processor, rt_priority, policy, delayacct_blkio_ticks, guest_time, cguest_time, start_data, end_data, start_brk, arg_start, arg_end, env_start, env_end, exit_code, }) } pub fn state(&self) -> ProcResult { ProcState::from_char(self.state) .ok_or_else(|| build_internal_error!(format!("{:?} is not a recognized process state", self.state))) } pub fn tty_nr(&self) -> (i32, i32) { // minor is bits 31-20 and 7-0 // major is 15-8 // mmmmmmmmmmmm____MMMMMMMMmmmmmmmm // 11111111111100000000000000000000 let major = (self.tty_nr & 0xfff00) >> 8; let minor = (self.tty_nr & 0x000ff) | ((self.tty_nr >> 12) & 0xfff00); (major, minor) } /// The kernel flags word of the process, as a bitfield /// /// See also the [Stat::flags](struct.Stat.html#structfield.flags) field. pub fn flags(&self) -> ProcResult { StatFlags::from_bits(self.flags) .ok_or_else(|| build_internal_error!(format!("Can't construct flags bitfield from {:?}", self.flags))) } /// Get the starttime of the process as a `DateTime` object. /// /// See also the [`starttime`](struct.Stat.html#structfield.starttime) field. /// /// This function requires the "chrono" features to be enabled (which it is by default). #[cfg(feature = "chrono")] pub fn starttime(&self) -> ProcResult> { let tts = TICKS_PER_SECOND .as_ref() .map_err(|e| ProcError::Other(format!("Failed to get ticks_per_second: {:?}", e)))?; let seconds_since_boot = self.starttime as f32 / *tts as f32; let boot_time = crate::boot_time()?; Ok(boot_time + chrono::Duration::milliseconds((seconds_since_boot * 1000.0) as i64)) } /// Gets the Resident Set Size (in bytes) /// /// The `rss` field will return the same value in pages pub fn rss_bytes(&self) -> ProcResult { let pagesize = PAGESIZE .as_ref() .map_err(|e| ProcError::Other(format!("Failed to get pagesize: {:?}", e)))?; Ok(self.rss * *pagesize) } } procfs-0.14.2/src/process/status.rs000064400000000000000000000414521046102023000153500ustar 00000000000000use crate::{FromStrRadix, ProcResult}; use std::collections::HashMap; use std::io::{BufRead, BufReader, Read}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// Status information about the process, based on the `/proc//status` file. /// /// To construct this structure, see [Process::status()](crate::process::Process::status). /// /// Not all fields are available in every kernel. These fields have `Option` types. /// In general, the current kernel version will tell you what fields you can expect, but this /// isn't totally reliable, since some kernels might backport certain fields, or fields might /// only be present if certain kernel configuration options are enabled. Be prepared to /// handle `None` values. /// /// New fields to this struct may be added at any time (even without a major or minor semver bump). #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[non_exhaustive] pub struct Status { /// Command run by this process. pub name: String, /// Process umask, expressed in octal with a leading zero; see umask(2). (Since Linux 4.7.) pub umask: Option, /// Current state of the process. pub state: String, /// Thread group ID (i.e., Process ID). pub tgid: i32, /// NUMA group ID (0 if none; since Linux 3.13). pub ngid: Option, /// Thread ID (see gettid(2)). pub pid: i32, /// PID of parent process. pub ppid: i32, /// PID of process tracing this process (0 if not being traced). pub tracerpid: i32, /// Real UID. pub ruid: u32, /// Effective UID. pub euid: u32, /// Saved set UID. pub suid: u32, /// Filesystem UID. pub fuid: u32, /// Real GID. pub rgid: u32, /// Effective GID. pub egid: u32, /// Saved set GID. pub sgid: u32, /// Filesystem GID. pub fgid: u32, /// Number of file descriptor slots currently allocated. pub fdsize: u32, /// Supplementary group list. pub groups: Vec, /// Thread group ID (i.e., PID) in each of the PID /// namespaces of which (pid)[struct.Status.html#structfield.pid] is a member. The leftmost entry /// shows the value with respect to the PID namespace of the /// reading process, followed by the value in successively /// nested inner namespaces. (Since Linux 4.1.) pub nstgid: Option>, /// Thread ID in each of the PID namespaces of which /// (pid)[struct.Status.html#structfield.pid] is a member. The fields are ordered as for NStgid. /// (Since Linux 4.1.) pub nspid: Option>, /// Process group ID in each of the PID namespaces of /// which (pid)[struct.Status.html#structfield.pid] is a member. The fields are ordered as for NStgid. (Since Linux 4.1.) pub nspgid: Option>, /// NSsid: descendant namespace session ID hierarchy Session ID /// in each of the PID namespaces of which (pid)[struct.Status.html#structfield.pid] is a member. /// The fields are ordered as for NStgid. (Since Linux 4.1.) pub nssid: Option>, /// Peak virtual memory size by kibibytes. pub vmpeak: Option, /// Virtual memory size by kibibytes. pub vmsize: Option, /// Locked memory size by kibibytes (see mlock(3)). pub vmlck: Option, /// Pinned memory size by kibibytes (since Linux 3.2). These are /// pages that can't be moved because something needs to /// directly access physical memory. pub vmpin: Option, /// Peak resident set size by kibibytes ("high water mark"). pub vmhwm: Option, /// Resident set size by kibibytes. Note that the value here is the /// sum of RssAnon, RssFile, and RssShmem. pub vmrss: Option, /// Size of resident anonymous memory by kibibytes. (since Linux 4.5). pub rssanon: Option, /// Size of resident file mappings by kibibytes. (since Linux 4.5). pub rssfile: Option, /// Size of resident shared memory by kibibytes (includes System V /// shared memory, mappings from tmpfs(5), and shared anonymous /// mappings). (since Linux 4.5). pub rssshmem: Option, /// Size of data by kibibytes. pub vmdata: Option, /// Size of stack by kibibytes. pub vmstk: Option, /// Size of text seg‐ments by kibibytes. pub vmexe: Option, /// Shared library code size by kibibytes. pub vmlib: Option, /// Page table entries size by kibibytes (since Linux 2.6.10). pub vmpte: Option, /// Swapped-out virtual memory size by anonymous private /// pages by kibibytes; shmem swap usage is not included (since Linux 2.6.34). pub vmswap: Option, /// Size of hugetlb memory portions by kB. (since Linux 4.4). pub hugetlbpages: Option, /// Number of threads in process containing this thread. pub threads: u64, /// This field contains two slash-separated numbers that /// relate to queued signals for the real user ID of this /// process. The first of these is the number of currently /// queued signals for this real user ID, and the second is the /// resource limit on the number of queued signals for this /// process (see the description of RLIMIT_SIGPENDING in /// getrlimit(2)). pub sigq: (u64, u64), /// Number of signals pending for thread (see pthreads(7) and signal(7)). pub sigpnd: u64, /// Number of signals pending for process as a whole (see pthreads(7) and signal(7)). pub shdpnd: u64, /// Masks indicating signals being blocked (see signal(7)). pub sigblk: u64, /// Masks indicating signals being ignored (see signal(7)). pub sigign: u64, /// Masks indicating signals being caught (see signal(7)). pub sigcgt: u64, /// Masks of capabilities enabled in inheritable sets (see capabilities(7)). pub capinh: u64, /// Masks of capabilities enabled in permitted sets (see capabilities(7)). pub capprm: u64, /// Masks of capabilities enabled in effective sets (see capabilities(7)). pub capeff: u64, /// Capability Bounding set (since Linux 2.6.26, see capabilities(7)). pub capbnd: Option, /// Ambient capability set (since Linux 4.3, see capabilities(7)). pub capamb: Option, /// Value of the no_new_privs bit (since Linux 4.10, see prctl(2)). pub nonewprivs: Option, /// Seccomp mode of the process (since Linux 3.8, see /// seccomp(2)). 0 means SECCOMP_MODE_DISABLED; 1 means SEC‐ /// COMP_MODE_STRICT; 2 means SECCOMP_MODE_FILTER. This field /// is provided only if the kernel was built with the CON‐ /// FIG_SECCOMP kernel configuration option enabled. pub seccomp: Option, /// Speculative store bypass mitigation status. pub speculation_store_bypass: Option, /// Mask of CPUs on which this process may run (since Linux 2.6.24, see cpuset(7)). pub cpus_allowed: Option>, /// Same as previous, but in "list format" (since Linux 2.6.26, see cpuset(7)). pub cpus_allowed_list: Option>, /// Mask of memory nodes allowed to this process (since Linux 2.6.24, see cpuset(7)). pub mems_allowed: Option>, /// Same as previous, but in "list format" (since Linux 2.6.26, see cpuset(7)). pub mems_allowed_list: Option>, /// Number of voluntary context switches (since Linux 2.6.23). pub voluntary_ctxt_switches: Option, /// Number of involuntary context switches (since Linux 2.6.23). pub nonvoluntary_ctxt_switches: Option, /// Contains true if the process is currently dumping core. /// /// This information can be used by a monitoring process to avoid killing a processing that is /// currently dumping core, which could result in a corrupted core dump file. /// /// (Since Linux 4.15) pub core_dumping: Option, /// Contains true if the process is allowed to use THP /// /// (Since Linux 5.0) pub thp_enabled: Option, } impl Status { pub fn from_reader(r: R) -> ProcResult { let mut map = HashMap::new(); let reader = BufReader::new(r); for line in reader.lines() { let line = line?; if line.is_empty() { continue; } let mut s = line.split(':'); let field = expect!(s.next()); let value = expect!(s.next()).trim(); map.insert(field.to_string(), value.to_string()); } let status = Status { name: expect!(map.remove("Name")), umask: map.remove("Umask").map(|x| Ok(from_str!(u32, &x, 8))).transpose()?, state: expect!(map.remove("State")), tgid: from_str!(i32, &expect!(map.remove("Tgid"))), ngid: map.remove("Ngid").map(|x| Ok(from_str!(i32, &x))).transpose()?, pid: from_str!(i32, &expect!(map.remove("Pid"))), ppid: from_str!(i32, &expect!(map.remove("PPid"))), tracerpid: from_str!(i32, &expect!(map.remove("TracerPid"))), ruid: expect!(Status::parse_uid_gid(expect!(map.get("Uid")), 0)), euid: expect!(Status::parse_uid_gid(expect!(map.get("Uid")), 1)), suid: expect!(Status::parse_uid_gid(expect!(map.get("Uid")), 2)), fuid: expect!(Status::parse_uid_gid(&expect!(map.remove("Uid")), 3)), rgid: expect!(Status::parse_uid_gid(expect!(map.get("Gid")), 0)), egid: expect!(Status::parse_uid_gid(expect!(map.get("Gid")), 1)), sgid: expect!(Status::parse_uid_gid(expect!(map.get("Gid")), 2)), fgid: expect!(Status::parse_uid_gid(&expect!(map.remove("Gid")), 3)), fdsize: from_str!(u32, &expect!(map.remove("FDSize"))), groups: Status::parse_list(&expect!(map.remove("Groups")))?, nstgid: map.remove("NStgid").map(|x| Status::parse_list(&x)).transpose()?, nspid: map.remove("NSpid").map(|x| Status::parse_list(&x)).transpose()?, nspgid: map.remove("NSpgid").map(|x| Status::parse_list(&x)).transpose()?, nssid: map.remove("NSsid").map(|x| Status::parse_list(&x)).transpose()?, vmpeak: Status::parse_with_kb(map.remove("VmPeak"))?, vmsize: Status::parse_with_kb(map.remove("VmSize"))?, vmlck: Status::parse_with_kb(map.remove("VmLck"))?, vmpin: Status::parse_with_kb(map.remove("VmPin"))?, vmhwm: Status::parse_with_kb(map.remove("VmHWM"))?, vmrss: Status::parse_with_kb(map.remove("VmRSS"))?, rssanon: Status::parse_with_kb(map.remove("RssAnon"))?, rssfile: Status::parse_with_kb(map.remove("RssFile"))?, rssshmem: Status::parse_with_kb(map.remove("RssShmem"))?, vmdata: Status::parse_with_kb(map.remove("VmData"))?, vmstk: Status::parse_with_kb(map.remove("VmStk"))?, vmexe: Status::parse_with_kb(map.remove("VmExe"))?, vmlib: Status::parse_with_kb(map.remove("VmLib"))?, vmpte: Status::parse_with_kb(map.remove("VmPTE"))?, vmswap: Status::parse_with_kb(map.remove("VmSwap"))?, hugetlbpages: Status::parse_with_kb(map.remove("HugetlbPages"))?, threads: from_str!(u64, &expect!(map.remove("Threads"))), sigq: expect!(Status::parse_sigq(&expect!(map.remove("SigQ")))), sigpnd: from_str!(u64, &expect!(map.remove("SigPnd")), 16), shdpnd: from_str!(u64, &expect!(map.remove("ShdPnd")), 16), sigblk: from_str!(u64, &expect!(map.remove("SigBlk")), 16), sigign: from_str!(u64, &expect!(map.remove("SigIgn")), 16), sigcgt: from_str!(u64, &expect!(map.remove("SigCgt")), 16), capinh: from_str!(u64, &expect!(map.remove("CapInh")), 16), capprm: from_str!(u64, &expect!(map.remove("CapPrm")), 16), capeff: from_str!(u64, &expect!(map.remove("CapEff")), 16), capbnd: map.remove("CapBnd").map(|x| Ok(from_str!(u64, &x, 16))).transpose()?, capamb: map.remove("CapAmb").map(|x| Ok(from_str!(u64, &x, 16))).transpose()?, nonewprivs: map.remove("NoNewPrivs").map(|x| Ok(from_str!(u64, &x))).transpose()?, seccomp: map.remove("Seccomp").map(|x| Ok(from_str!(u32, &x))).transpose()?, speculation_store_bypass: map.remove("Speculation_Store_Bypass"), cpus_allowed: map .remove("Cpus_allowed") .map(|x| Status::parse_allowed(&x)) .transpose()?, cpus_allowed_list: map .remove("Cpus_allowed_list") .and_then(|x| Status::parse_allowed_list(&x).ok()), mems_allowed: map .remove("Mems_allowed") .map(|x| Status::parse_allowed(&x)) .transpose()?, mems_allowed_list: map .remove("Mems_allowed_list") .and_then(|x| Status::parse_allowed_list(&x).ok()), voluntary_ctxt_switches: map .remove("voluntary_ctxt_switches") .map(|x| Ok(from_str!(u64, &x))) .transpose()?, nonvoluntary_ctxt_switches: map .remove("nonvoluntary_ctxt_switches") .map(|x| Ok(from_str!(u64, &x))) .transpose()?, core_dumping: map.remove("CoreDumping").map(|x| x == "1"), thp_enabled: map.remove("THP_enabled").map(|x| x == "1"), }; if cfg!(test) && !map.is_empty() { // This isn't an error because different kernels may put different data here, and distros // may backport these changes into older kernels. Too hard to keep track of eprintln!("Warning: status map is not empty: {:#?}", map); } Ok(status) } fn parse_with_kb(s: Option) -> ProcResult> { if let Some(s) = s { Ok(Some(from_str!(T, &s.replace(" kB", "")))) } else { Ok(None) } } pub(crate) fn parse_uid_gid(s: &str, i: usize) -> ProcResult { Ok(from_str!(u32, expect!(s.split_whitespace().nth(i)))) } fn parse_sigq(s: &str) -> ProcResult<(u64, u64)> { let mut iter = s.split('/'); let first = from_str!(u64, expect!(iter.next())); let second = from_str!(u64, expect!(iter.next())); Ok((first, second)) } fn parse_list(s: &str) -> ProcResult> { let mut ret = Vec::new(); for i in s.split_whitespace() { ret.push(from_str!(T, i)); } Ok(ret) } fn parse_allowed(s: &str) -> ProcResult> { let mut ret = Vec::new(); for i in s.split(',') { ret.push(from_str!(u32, i, 16)); } Ok(ret) } fn parse_allowed_list(s: &str) -> ProcResult> { let mut ret = Vec::new(); for s in s.split(',') { if s.contains('-') { let mut s = s.split('-'); let beg = from_str!(u32, expect!(s.next())); if let Some(x) = s.next() { let end = from_str!(u32, x); ret.push((beg, end)); } } else { let beg = from_str!(u32, s); let end = from_str!(u32, s); ret.push((beg, end)); } } Ok(ret) } } #[cfg(test)] mod tests { use crate::process::*; #[test] fn test_proc_status() { let myself = Process::myself().unwrap(); let stat = myself.stat().unwrap(); let status = myself.status().unwrap(); println!("{:?}", status); assert_eq!(status.name, stat.comm); assert_eq!(status.pid, stat.pid); assert_eq!(status.ppid, stat.ppid); } #[test] fn test_proc_status_for_kthreadd() { // when running in a container, pid2 probably isn't kthreadd, so check let kthreadd = match process::Process::new(2) { Ok(p) => p, Err(ProcError::NotFound(_)) => { return; // ok we can ignore } Err(e) => { panic!("{}", e); } }; let status = kthreadd.status().unwrap(); println!("{:?}", status); assert_eq!(status.pid, 2); assert_eq!(status.vmpeak, None); assert_eq!(status.vmsize, None); assert_eq!(status.vmlck, None); assert_eq!(status.vmpin, None); assert_eq!(status.vmhwm, None); assert_eq!(status.vmrss, None); assert_eq!(status.rssanon, None); assert_eq!(status.rssfile, None); assert_eq!(status.rssshmem, None); assert_eq!(status.vmdata, None); assert_eq!(status.vmstk, None); assert_eq!(status.vmexe, None); assert_eq!(status.vmlib, None); assert_eq!(status.vmpte, None); assert_eq!(status.vmswap, None); assert_eq!(status.hugetlbpages, None); } } procfs-0.14.2/src/process/task.rs000064400000000000000000000174341046102023000147720ustar 00000000000000use std::io::Read; use std::path::{Path, PathBuf}; use super::{FileWrapper, Io, Schedstat, Stat, Status}; use crate::{ProcError, ProcResult}; use rustix::fd::{OwnedFd, BorrowedFd}; /// A task (aka Thread) inside of a [`Process`](crate::process::Process) /// /// Created by [`Process::tasks`](crate::process::Process::tasks), tasks in /// general are similar to Processes and should have mostly the same fields. #[derive(Debug)] pub struct Task { fd: OwnedFd, /// The ID of the process that this task belongs to pub pid: i32, /// The task ID pub tid: i32, /// Task root: `/proc//task/` pub(crate) root: PathBuf, } impl Task { /// Create a new `Task` inside of the process /// /// This API is designed to be ergonomic from inside of [`TasksIter`](super::TasksIter) pub(crate) fn from_process_at, Q: AsRef>( base: P, dirfd: BorrowedFd, path: Q, pid: i32, tid: i32, ) -> ProcResult { use rustix::fs::{Mode, OFlags}; let p = path.as_ref(); let root = base.as_ref().join(p); let fd = wrap_io_error!( root, rustix::fs::openat( dirfd, p, OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC, Mode::empty() ) )?; Ok(Task { fd, pid, tid, root }) } /// Thread info from `/proc//task//stat` /// /// Many of the returned fields will be the same as the parent process, but some fields like `utime` and `stime` will be per-task pub fn stat(&self) -> ProcResult { Stat::from_reader(FileWrapper::open_at(&self.root, &self.fd, "stat")?) } /// Thread info from `/proc//task//status` /// /// Many of the returned fields will be the same as the parent process pub fn status(&self) -> ProcResult { Status::from_reader(FileWrapper::open_at(&self.root, &self.fd, "status")?) } /// Thread IO info from `/proc//task//io` /// /// This data will be unique per task. pub fn io(&self) -> ProcResult { Io::from_reader(FileWrapper::open_at(&self.root, &self.fd, "io")?) } /// Thread scheduler info from `/proc//task//schedstat` /// /// This data will be unique per task. pub fn schedstat(&self) -> ProcResult { Schedstat::from_reader(FileWrapper::open_at(&self.root, &self.fd, "schedstat")?) } /// Thread children from `/proc//task//children` /// /// WARNING: /// This interface is not reliable unless all the child processes are stoppped or frozen. /// If a child task exits while the file is being read, non-exiting children may be omitted. /// See the procfs(5) man page for more information. /// /// This data will be unique per task. pub fn children(&self) -> ProcResult> { let mut buf = String::new(); let mut file = FileWrapper::open_at(&self.root, &self.fd, "children")?; file.read_to_string(&mut buf)?; buf.split_whitespace() .map(|child| { child .parse() .map_err(|_| ProcError::Other("Failed to parse task's child PIDs".to_string())) }) .collect() } } #[cfg(test)] mod tests { use crate::process::Io; use rustix; use std::process; use std::sync::{Arc, Barrier}; #[test] #[cfg(not(tarpaulin))] // this test is unstable under tarpaulin, and i'm yet sure why // When this test runs in CI, run it single-threaded fn test_task_runsinglethread() { use std::io::Read; let me = crate::process::Process::myself().unwrap(); let (work_barrier, w_a, w_b) = { let b = Arc::new(Barrier::new(3)); (b.clone(), b.clone(), b) }; let (done_barrier, d_a, d_b) = { let b = Arc::new(Barrier::new(3)); (b.clone(), b.clone(), b) }; let bytes_to_read = 2_000_000; // create a new task to do some work let join_a = std::thread::Builder::new() .name("one".to_owned()) .spawn(move || { let mut vec = Vec::new(); let zero = std::fs::File::open("/dev/zero").unwrap(); zero.take(bytes_to_read).read_to_end(&mut vec).unwrap(); assert_eq!(vec.len(), bytes_to_read as usize); // spin for about 52 ticks (utime accounting isn't perfectly accurate) let dur = std::time::Duration::from_millis(52 * (1000 / crate::ticks_per_second().unwrap()) as u64); let start = std::time::Instant::now(); while start.elapsed() <= dur { // spin } w_a.wait(); d_a.wait() }) .unwrap(); // create a new task that does nothing let join_b = std::thread::Builder::new() .name("two".to_owned()) .spawn(move || { w_b.wait(); d_b.wait(); }) .unwrap(); work_barrier.wait(); let mut found_one = false; let mut found_two = false; let mut summed_io = Io { rchar: 0, wchar: 0, syscr: 0, syscw: 0, read_bytes: 0, write_bytes: 0, cancelled_write_bytes: 0, }; for task in me.tasks().unwrap() { let task = task.unwrap(); let stat = task.stat().unwrap(); let status = task.status().unwrap(); let io = task.io().unwrap(); summed_io.rchar += io.rchar; summed_io.wchar += io.wchar; summed_io.syscr += io.syscr; summed_io.syscw += io.syscw; summed_io.read_bytes += io.read_bytes; summed_io.write_bytes += io.write_bytes; summed_io.cancelled_write_bytes += io.cancelled_write_bytes; if stat.comm == "one" && status.name == "one" { found_one = true; assert!(io.rchar >= bytes_to_read); assert!(stat.utime >= 50, "utime({}) too small", stat.utime); } if stat.comm == "two" && status.name == "two" { found_two = true; // The process might read miscellaneous things from procfs or // things like /sys/devices/system/cpu/online; allow some small // reads. assert!(io.rchar < bytes_to_read); assert_eq!(io.wchar, 0); assert_eq!(stat.utime, 0); } } let proc_io = me.io().unwrap(); // these should be mostly the same (though creating the IO struct in the above line will cause some IO to occur) println!("{:?}", summed_io); println!("{:?}", proc_io); // signal the threads to exit done_barrier.wait(); join_a.join().unwrap(); join_b.join().unwrap(); assert!(found_one); assert!(found_two); } #[test] fn test_task_children() { // Use tail -f /dev/null to create two infinite processes let mut command = process::Command::new("tail"); command.arg("-f").arg("/dev/null"); let (mut child1, mut child2) = (command.spawn().unwrap(), command.spawn().unwrap()); let tid = rustix::thread::gettid(); let children = crate::process::Process::myself() .unwrap() .task_from_tid(tid.as_raw_nonzero().get() as i32) .unwrap() .children() .unwrap(); assert!(children.contains(&child1.id())); assert!(children.contains(&child2.id())); child1.kill().unwrap(); child2.kill().unwrap(); } } procfs-0.14.2/src/process/tests.rs000064400000000000000000000367061046102023000151750ustar 00000000000000use super::*; fn check_unwrap(prc: &Process, val: ProcResult) -> Option { match val { Ok(t) => Some(t), Err(ProcError::PermissionDenied(_)) if !rustix::process::geteuid().is_root() => { // we are not root, and so a permission denied error is OK None } Err(ProcError::NotFound(path)) => { // a common reason for this error is that the process isn't running anymore if prc.is_alive() { panic!("{:?} not found", path) } None } Err(err) => panic!("check_unwrap error for {} {:?}", prc.pid, err), } } fn check_unwrap_task(prc: &Process, val: ProcResult) -> Option { match val { Ok(t) => Some(t), Err(ProcError::PermissionDenied(_)) if !rustix::process::geteuid().is_root() => { // we are not root, and so a permission denied error is OK None } Err(ProcError::NotFound(_path)) => { // tasks can be more short-lived thanks processes, and it seems that accessing // the /status and /stat files for tasks is quite unreliable None } Err(err) => panic!("check_unwrap error for {} {:?}", prc.pid, err), } } #[test] fn test_main_thread_task() { let myself = Process::myself().unwrap(); let task = myself.task_main_thread().unwrap(); check_unwrap(&myself, task.stat()); } #[allow(clippy::cognitive_complexity)] #[test] fn test_self_proc() { let myself = Process::myself().unwrap().stat().unwrap(); println!("{:#?}", myself); println!("state: {:?}", myself.state()); println!("tty: {:?}", myself.tty_nr()); println!("flags: {:?}", myself.flags()); #[cfg(feature = "chrono")] println!("starttime: {:#?}", myself.starttime()); let kernel = KernelVersion::current().unwrap(); if kernel >= KernelVersion::new(2, 1, 22) { assert!(myself.exit_signal.is_some()); } else { assert!(myself.exit_signal.is_none()); } if kernel >= KernelVersion::new(2, 2, 8) { assert!(myself.processor.is_some()); } else { assert!(myself.processor.is_none()); } if kernel >= KernelVersion::new(2, 5, 19) { assert!(myself.rt_priority.is_some()); } else { assert!(myself.rt_priority.is_none()); } if kernel >= KernelVersion::new(2, 5, 19) { assert!(myself.rt_priority.is_some()); assert!(myself.policy.is_some()); } else { assert!(myself.rt_priority.is_none()); assert!(myself.policy.is_none()); } if kernel >= KernelVersion::new(2, 6, 18) { assert!(myself.delayacct_blkio_ticks.is_some()); } else { assert!(myself.delayacct_blkio_ticks.is_none()); } if kernel >= KernelVersion::new(2, 6, 24) { assert!(myself.guest_time.is_some()); assert!(myself.cguest_time.is_some()); } else { assert!(myself.guest_time.is_none()); assert!(myself.cguest_time.is_none()); } if kernel >= KernelVersion::new(3, 3, 0) { assert!(myself.start_data.is_some()); assert!(myself.end_data.is_some()); assert!(myself.start_brk.is_some()); } else { assert!(myself.start_data.is_none()); assert!(myself.end_data.is_none()); assert!(myself.start_brk.is_none()); } if kernel >= KernelVersion::new(3, 5, 0) { assert!(myself.arg_start.is_some()); assert!(myself.arg_end.is_some()); assert!(myself.env_start.is_some()); assert!(myself.env_end.is_some()); assert!(myself.exit_code.is_some()); } else { assert!(myself.arg_start.is_none()); assert!(myself.arg_end.is_none()); assert!(myself.env_start.is_none()); assert!(myself.env_end.is_none()); assert!(myself.exit_code.is_none()); } } #[test] fn test_all() { let is_wsl2 = kernel_config() .ok() .and_then(|cfg| { cfg.get("CONFIG_LOCALVERSION").and_then(|ver| { if let ConfigSetting::Value(s) = ver { Some(s == "\"-microsoft-standard\"") } else { None } }) }) .unwrap_or(false); for p in all_processes().unwrap() { // note: this test doesn't unwrap, since some of this data requires root to access // so permission denied errors are common. The check_unwrap helper function handles // this. let prc = p.unwrap(); let stat = prc.stat().unwrap(); println!("{} {}", prc.pid(), stat.comm); stat.flags().unwrap(); stat.state().unwrap(); #[cfg(feature = "chrono")] stat.starttime().unwrap(); // if this process is defunct/zombie, don't try to read any of the below data // (some might be successful, but not all) if stat.state().unwrap() == ProcState::Zombie { continue; } check_unwrap(&prc, prc.cmdline()); check_unwrap(&prc, prc.environ()); check_unwrap(&prc, prc.fd()); check_unwrap(&prc, prc.io()); check_unwrap(&prc, prc.maps()); check_unwrap(&prc, prc.coredump_filter()); // The WSL2 kernel doesn't have autogroup, even though this should be present since linux // 2.6.36 if is_wsl2 { assert!(prc.autogroup().is_err()); } else { check_unwrap(&prc, prc.autogroup()); } check_unwrap(&prc, prc.auxv()); check_unwrap(&prc, prc.cgroups()); check_unwrap(&prc, prc.wchan()); check_unwrap(&prc, prc.status()); check_unwrap(&prc, prc.mountinfo()); check_unwrap(&prc, prc.mountstats()); check_unwrap(&prc, prc.oom_score()); if let Some(tasks) = check_unwrap(&prc, prc.tasks()) { for task in tasks { let task = task.unwrap(); check_unwrap_task(&prc, task.stat()); check_unwrap_task(&prc, task.status()); check_unwrap_task(&prc, task.io()); check_unwrap_task(&prc, task.schedstat()); } } } } #[test] fn test_smaps() { let me = Process::myself().unwrap(); let smaps = match me.smaps() { Ok(x) => x, Err(ProcError::NotFound(_)) => { // ignored because not all kernerls have smaps return; } Err(e) => panic!("{}", e), }; println!("{:#?}", smaps); } #[test] fn test_smaps_rollup() { let me = Process::myself().unwrap(); let smaps_rollup = match me.smaps_rollup() { Ok(x) => x, Err(ProcError::NotFound(_)) => { // ignored because not all kernerls have smaps_rollup return; } Err(e) => panic!("{}", e), }; println!("{:#?}", smaps_rollup); } #[test] fn test_proc_alive() { let myself = Process::myself().unwrap(); assert!(myself.is_alive()); // zombies should not be considered alive let mut command = std::process::Command::new("sleep"); command .arg("0") .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()); let mut child = command.spawn().unwrap(); let child_pid = child.id() as i32; // sleep very briefly to allow the child to start and then exit std::thread::sleep(std::time::Duration::from_millis(30)); let child_proc = Process::new(child_pid).unwrap(); assert!(!child_proc.is_alive(), "Child state is: {:?}", child_proc.stat()); assert!(child_proc.stat().unwrap().state().unwrap() == ProcState::Zombie); child.wait().unwrap(); assert!(Process::new(child_pid).is_err()); assert!(!child_proc.is_alive(), "Child state is: {:?}", child_proc.stat()); } #[test] fn test_proc_environ() { let myself = Process::myself().unwrap(); let proc_environ = myself.environ().unwrap(); let std_environ: HashMap<_, _> = std::env::vars_os().collect(); assert_eq!(proc_environ, std_environ); } #[test] fn test_error_handling() { // getting the proc struct should be OK let init = Process::new(1).unwrap(); let i_have_access = rustix::process::geteuid().as_raw() == init.uid().unwrap(); if !i_have_access { // but accessing data should result in an error (unless we are running as root!) assert!(init.cwd().is_err()); assert!(init.environ().is_err()); } } #[test] fn test_proc_exe() { let myself = Process::myself().unwrap(); let proc_exe = myself.exe().unwrap(); let std_exe = std::env::current_exe().unwrap(); assert_eq!(proc_exe, std_exe); } #[test] fn test_proc_io() { let myself = Process::myself().unwrap(); let kernel = KernelVersion::current().unwrap(); let io = myself.io(); println!("{:?}", io); if io.is_ok() { assert!(kernel >= KernelVersion::new(2, 6, 20)); } } #[test] fn test_proc_maps() { let myself = Process::myself().unwrap(); let maps = myself.maps().unwrap(); for map in maps { println!("{:?}", map); } } #[test] fn test_proc_pagemap() { let myself = Process::myself().unwrap(); let maps = myself.maps().unwrap(); let stack_map = maps.iter().find(|m| matches!(m.pathname, MMapPath::Stack)).unwrap(); let page_size = crate::page_size().unwrap() as usize; let start_page = stack_map.address.0 as usize / page_size; let end_page = stack_map.address.1 as usize / page_size; let mut pagemap = myself.pagemap().unwrap(); let page_infos = pagemap.get_range_info(start_page..end_page).unwrap(); let present_pages = page_infos.iter().filter(|info| { if let PageInfo::MemoryPage(flags) = info { flags.contains(MemoryPageFlags::PRESENT) } else { false } }); for present_page in present_pages { println!("{:?}", present_page); } } #[test] fn test_mmap_path() { assert_eq!(MMapPath::from("[stack]").unwrap(), MMapPath::Stack); assert_eq!(MMapPath::from("[foo]").unwrap(), MMapPath::Other("foo".to_owned())); assert_eq!(MMapPath::from("").unwrap(), MMapPath::Anonymous); assert_eq!(MMapPath::from("[stack:154]").unwrap(), MMapPath::TStack(154)); assert_eq!( MMapPath::from("/lib/libfoo.so").unwrap(), MMapPath::Path(PathBuf::from("/lib/libfoo.so")) ); } #[test] fn test_proc_fds() { let myself = Process::myself().unwrap(); for f in myself.fd().unwrap() { let fd = f.unwrap(); println!("{:?} {:?}", fd, fd.mode()); } } #[test] fn test_proc_fd() { let myself = Process::myself().unwrap(); let raw_fd = myself.fd().unwrap().next().unwrap().unwrap().fd as i32; let fd = FDInfo::from_raw_fd(myself.pid, raw_fd).unwrap(); println!("{:?} {:?}", fd, fd.mode()); } #[test] fn test_proc_coredump() { let myself = Process::myself().unwrap(); let flags = myself.coredump_filter(); println!("{:?}", flags); } #[test] fn test_proc_auxv() { let myself = Process::myself().unwrap(); let auxv = myself.auxv().unwrap(); println!("{:?}", auxv); for (k, v) in auxv { // See bits/auxv.h match k { 2 => println!("File descriptor of program: {}", v), 3 => println!("Address of the program headers of the executable: 0x{:x}", v), 4 => println!("Size of program header entry: {}", v), 5 => println!("Number of program headers: {}", v), 6 => { println!("System page size: {}", v); assert!(v > 0); } 7 => { println!("Base address: 0x{:x}", v); assert!(v > 0); } 8 => println!("Flags: 0x{:x}", v), 9 => { println!("Entry address of the executable: 0x{:x}", v); assert!(v > 0); } 11 => { println!("Real UID: {}", v); assert_eq!(v as u32, rustix::process::getuid().as_raw()); } 12 => { println!("Effective UID: {}", v); assert!(v > 0); } 13 => { println!("Real GID: {}", v); assert!(v > 0); } 14 => { println!("Effective GID: {}", v); assert!(v > 0); } 15 => { println!("Platform string address: 0x{:x}", v); let platform = unsafe { std::ffi::CStr::from_ptr(v as *const _) }; println!("Platform string: {:?}", platform); } 16 => println!("HW Cap: 0x{:x}", v), 17 => { println!("Clock ticks per second: {}", v); assert_eq!(v, crate::ticks_per_second().unwrap()); } 19 => println!("Data cache block size: {}", v), 23 => println!("Run as setuid?: {}", v), 25 => println!("Address of 16 random bytes: 0x{:x}", v), 26 => println!("HW Cap2: 0x{:x}", v), 31 => { println!("argv[0] address: 0x{:x}", v); let argv0 = unsafe { std::ffi::CStr::from_ptr(v as *const _) }; println!("argv[0]: {:?}", argv0); } k => println!("Unknown key {}: {:x}", k, v), } } } #[test] fn test_proc_wchan() { let myself = Process::myself().unwrap(); let wchan = myself.wchan().unwrap(); println!("{:?}", wchan); } #[test] fn test_proc_loginuid() { if !Path::new("/proc/self/loginuid").exists() { return; } let myself = Process::myself().unwrap(); let loginuid = myself.loginuid().unwrap(); println!("{:?}", loginuid); } #[test] fn test_nopanic() { fn inner() -> ProcResult { let a = vec!["xyz"]; from_iter(a) } assert!(inner().is_err()); } #[test] fn test_procinfo() { // test to see that this crate and procinfo give mostly the same results fn diff_mem(a: f32, b: f32) { let diff = (a - b).abs(); assert!(diff < 20000.0, "diff:{}", diff); } // take a pause to let things "settle" before getting data. By default, cargo will run // tests in parallel, which can cause disturbences std::thread::sleep(std::time::Duration::from_secs(1)); let procinfo_stat = procinfo::pid::stat_self().unwrap(); let me = Process::myself().unwrap(); let me_stat = me.stat().unwrap(); diff_mem(procinfo_stat.vsize as f32, me_stat.vsize as f32); assert_eq!(me_stat.priority, procinfo_stat.priority as i64); assert_eq!(me_stat.nice, procinfo_stat.nice as i64); // flags seem to change during runtime, with PF_FREEZER_SKIP coming and going... //assert_eq!(me_stat.flags, procinfo_stat.flags, "procfs:{:?} procinfo:{:?}", crate::StatFlags::from_bits(me_stat.flags), crate::StatFlags::from_bits(procinfo_stat.flags)); assert_eq!(me_stat.pid, procinfo_stat.pid); assert_eq!(me_stat.ppid, procinfo_stat.ppid); } #[test] fn test_statm() { let me = Process::myself().unwrap(); let statm = me.statm().unwrap(); println!("{:#?}", statm); } #[test] fn test_schedstat() { let me = Process::myself().unwrap(); let schedstat = me.schedstat().unwrap(); println!("{:#?}", schedstat); } #[test] fn test_fdtarget() { // none of these values are valid, but were found by a fuzzer to crash procfs. this // test ensures that the crashes have been fixed let _ = FDTarget::from_str(":"); let _ = FDTarget::from_str("n:ǟF"); let _ = FDTarget::from_str("pipe:"); } #[test] fn test_fdtarget_memfd() { let memfd = FDTarget::from_str("/memfd:test").unwrap(); assert!(matches!(memfd, FDTarget::MemFD(s) if s == "test")); } procfs-0.14.2/src/sys/fs/binfmt_misc.rs000064400000000000000000000222531046102023000160650ustar 00000000000000use bitflags::bitflags; use std::path::Path; use crate::{read_value, ProcResult}; /// Returns true if the miscellaneous Binary Formats system is enabled. pub fn enabled() -> ProcResult { let val: String = read_value("/proc/sys/fs/binfmt_misc/status")?; Ok(val == "enabled") } fn hex_to_vec(hex: &str) -> ProcResult> { if hex.len() % 2 != 0 { return Err(build_internal_error!(format!( "Hex string {:?} has non-even length", hex ))); } let mut idx = 0; let mut data = Vec::new(); while idx < hex.len() { let byte = from_str!(u8, &hex[idx..idx + 2], 16); data.push(byte); idx += 2; } Ok(data) } #[derive(Debug, Clone)] pub enum BinFmtData { /// A BinFmt entry based on a file extension (does not include the period) Extension(String), /// A BinFmt entry based on magic string matching Magic { offset: u8, magic: Vec, mask: Vec }, } /// A registered binary format entry /// /// For more info, see the kernel doc Documentation/admin-guide/binfmt-misc.rst #[derive(Debug, Clone)] pub struct BinFmtEntry { /// The name of the entry /// /// Corresponds to a file in /proc/sys/fs/binfmt_misc/ pub name: String, /// Is the entry enabled or not pub enabled: bool, /// Full path to the interpreter to run this entry pub interpreter: String, /// pub flags: BinFmtFlags, pub data: BinFmtData, } impl BinFmtEntry { pub(crate) fn from_string(name: String, data: &str) -> ProcResult { let mut enabled = false; let mut interpreter = String::new(); let mut ext = None; let mut offset = 0; let mut magic = Vec::new(); let mut mask = Vec::new(); let mut flags = BinFmtFlags::empty(); for line in data.lines() { if line == "enabled" { enabled = true; } else if let Some(stripped) = line.strip_prefix("interpreter ") { interpreter = stripped.to_string(); } else if let Some(stripped) = line.strip_prefix("flags:") { flags = BinFmtFlags::from_str(stripped); } else if let Some(stripped) = line.strip_prefix("extension .") { ext = Some(stripped.to_string()); } else if let Some(stripped) = line.strip_prefix("offset ") { offset = from_str!(u8, stripped); } else if let Some(stripped) = line.strip_prefix("magic ") { let hex = stripped; magic = hex_to_vec(dbg!(hex))?; } else if let Some(stripped) = line.strip_prefix("mask ") { let hex = stripped; mask = hex_to_vec(hex)?; } } if !magic.is_empty() && mask.is_empty() { mask.resize(magic.len(), 0xff); } Ok(BinFmtEntry { name, enabled, interpreter, flags, data: if let Some(ext) = ext { BinFmtData::Extension(ext) } else { BinFmtData::Magic { magic, mask, offset } }, }) } } bitflags! { /// Various key flags pub struct BinFmtFlags: u8 { /// Preserve Argv[0] /// /// Legacy behavior of binfmt_misc is to overwrite the original argv[0] with the full path to the binary. When /// this flag is included, binfmt_misc will add an argument to the argument vector for this purpose, thus /// preserving the original `argv[0]`. /// /// For example, If your interp is set to `/bin/foo` and you run `blah` (which is in `/usr/local/bin`), /// then the kernel will execute `/bin/foo` with `argv[]` set to `["/bin/foo", "/usr/local/bin/blah", "blah"]`. /// /// The interp has to be aware of this so it can execute `/usr/local/bin/blah` with `argv[]` set to `["blah"]`. const P = 0x01; /// Open Binary /// /// Legacy behavior of binfmt_misc is to pass the full path /// of the binary to the interpreter as an argument. When this flag is /// included, binfmt_misc will open the file for reading and pass its /// descriptor as an argument, instead of the full path, thus allowing /// the interpreter to execute non-readable binaries. This feature /// should be used with care - the interpreter has to be trusted not to //// emit the contents of the non-readable binary. const O = 0x02; /// Credentials /// /// Currently, the behavior of binfmt_misc is to calculate /// the credentials and security token of the new process according to /// the interpreter. When this flag is included, these attributes are /// calculated according to the binary. It also implies the `O` flag. /// This feature should be used with care as the interpreter /// will run with root permissions when a setuid binary owned by root /// is run with binfmt_misc. const C = 0x04; /// Fix binary /// /// The usual behaviour of binfmt_misc is to spawn the /// binary lazily when the misc format file is invoked. However, /// this doesn't work very well in the face of mount namespaces and /// changeroots, so the `F` mode opens the binary as soon as the /// emulation is installed and uses the opened image to spawn the /// emulator, meaning it is always available once installed, /// regardless of how the environment changes. const F = 0x08; } } impl BinFmtFlags { pub(crate) fn from_str(s: &str) -> Self { s.chars() .filter_map(|c| match c { 'P' => Some(BinFmtFlags::P), 'O' => Some(BinFmtFlags::O), 'C' => Some(BinFmtFlags::C), 'F' => Some(BinFmtFlags::F), _ => None, }) .fold(BinFmtFlags::empty(), |a, b| a | b) } } pub fn list() -> ProcResult> { let path = Path::new("/proc/sys/fs/binfmt_misc/"); let mut v = Vec::new(); for entry in wrap_io_error!(path, path.read_dir())? { let entry = entry?; if entry.file_name() == "status" || entry.file_name() == "register" { // these entries do not represent real entries continue; } let name = entry.file_name().to_string_lossy().to_string(); let data = std::fs::read_to_string(entry.path())?; v.push(BinFmtEntry::from_string(name, &data)?); } Ok(v) } #[cfg(test)] mod tests { use super::*; #[test] fn test_enabled() { match enabled() { Ok(_) => {} Err(crate::ProcError::NotFound(_)) => {} Err(e) => panic!("{}", e), } } #[test] fn parse_magic() { let mask = "7f454c460201010000000000000000000200f300"; let data = hex_to_vec(mask).unwrap(); println!("{:?}", data); assert_eq!(data.len(), 20); assert_eq!(data[0], 0x7f); assert_eq!(data[1], 0x45); assert!(hex_to_vec("a").is_err()); assert!(hex_to_vec("zz").is_err()); } #[test] fn flags_parsing() { assert!(BinFmtFlags::from_str("").is_empty()); let flags = BinFmtFlags::from_str("F"); assert_eq!(flags, BinFmtFlags::F); let flags = BinFmtFlags::from_str("OCF"); assert_eq!(flags, BinFmtFlags::F | BinFmtFlags::C | BinFmtFlags::O); } #[test] fn binfmt() { let data = r#"enabled interpreter /usr/bin/qemu-riscv64-static flags: OCF offset 12 magic 7f454c460201010000000000000000000200f300 mask ffffffffffffff00fffffffffffffffffeffffff"#; let entry = BinFmtEntry::from_string("test".to_owned(), data).unwrap(); println!("{:#?}", entry); assert_eq!(entry.flags, BinFmtFlags::F | BinFmtFlags::C | BinFmtFlags::O); assert!(entry.enabled); assert_eq!(entry.interpreter, "/usr/bin/qemu-riscv64-static"); if let BinFmtData::Magic { offset, magic, mask } = entry.data { assert_eq!(offset, 12); assert_eq!(magic.len(), mask.len()); assert_eq!( magic, vec![ 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xf3, 0x00 ] ); } else { panic!("Unexpected data"); } let data = r#"enabled interpreter /bin/hello flags: extension .hello"#; let entry = BinFmtEntry::from_string("test".to_owned(), data).unwrap(); println!("{:#?}", entry); assert_eq!(entry.flags, BinFmtFlags::empty()); assert!(entry.enabled); assert_eq!(entry.interpreter, "/bin/hello"); if let BinFmtData::Extension(ext) = entry.data { assert_eq!(ext, "hello"); } else { panic!("Unexpected data"); } } #[test] fn live() { for entry in super::list().unwrap() { println!("{:?}", entry); } } } procfs-0.14.2/src/sys/fs/epoll.rs000064400000000000000000000021211046102023000146760ustar 00000000000000use crate::{read_value, write_value, ProcResult}; /// Get the limit on the total number of file descriptors that a user can register across all epoll instances. /// /// The limit is per real user ID. Each registered file descriptor costs roughtly 90 bytes on a 32-bit kernel, /// and roughly 160 bytes on a 64-bit kernel. Currently, the default value for `max_user_watches` is 1/25 (4%) /// of the available low memory, divided by the registration cost in bytes. /// /// (Since Linux 2.6.28) pub fn max_user_watches() -> ProcResult { read_value("/proc/sys/fs/epoll/max_user_watches") } /// Sets the limit on the total number of file descriptors that a user can register across all epoll instances. pub fn set_max_user_watches(val: u64) -> ProcResult<()> { write_value("/proc/sys/fs/epoll/max_user_watches", val) } #[cfg(test)] mod tests { use super::*; use crate::KernelVersion; #[test] fn test_max_user_watches() { if KernelVersion::current().unwrap() >= KernelVersion::new(2, 6, 28) { println!("{}", max_user_watches().unwrap()); } } } procfs-0.14.2/src/sys/fs/mod.rs000064400000000000000000000055161046102023000143550ustar 00000000000000//! This modules contains functions for kernel variables related to filesystems use crate::{read_file, read_value, write_value, ProcResult}; use std::time::Duration; pub mod binfmt_misc; pub mod epoll; /// Information about the status of the directory cache (dcache) #[derive(Debug, Clone)] pub struct DEntryState { /// The number of allocated dentries (dcache entries) /// /// Unused in Linux 2.2 pub nr_dentry: u32, /// The number of unused dentries. pub nr_unused: u32, /// The age after which dcache entries can be reclaimied when memory is short pub age_limit: Duration, /// Is true when the kernel has called `shrink_dcache_pages()` and the dcache isn't pruned yet. pub want_pages: bool, } impl DEntryState { fn from_str(s: &str) -> ProcResult { let mut s = s.split_whitespace(); let nr_dentry = from_str!(u32, expect!(s.next())); let nr_unused = from_str!(u32, expect!(s.next())); let age_limit_sec = from_str!(u32, expect!(s.next())); let want_pages = from_str!(u32, expect!(s.next())); Ok(DEntryState { nr_dentry, nr_unused, age_limit: Duration::from_secs(age_limit_sec as u64), want_pages: want_pages != 0, }) } } /// Get information about the status of the directory cache (dcache) /// /// Linux Linux 2.2 pub fn dentry_state() -> ProcResult { let s: String = read_file("/proc/sys/fs/dentry-state")?; DEntryState::from_str(&s) } /// Get the system-wide limit on the number of open files for all processes. /// /// System calls that fail when encoun‐ tering this limit fail with the error `ENFILE`. pub fn file_max() -> ProcResult { read_value("/proc/sys/fs/file-max") } /// Set the system-wide limit on the number of open files for all processes. pub fn set_file_max(max: usize) -> ProcResult<()> { write_value("/proc/sys/fs/file-max", max) } #[derive(Debug, Clone)] pub struct FileState { /// The number of allocated file handles. /// /// (i.e. the number of files presently opened) pub allocated: u64, /// The number of free file handles. pub free: u64, /// The maximum number of file handles. /// /// This may be u64::MAX pub max: u64, } pub fn file_nr() -> ProcResult { let s = read_file("/proc/sys/fs/file-nr")?; let mut s = s.split_whitespace(); let allocated = from_str!(u64, expect!(s.next())); let free = from_str!(u64, expect!(s.next())); let max = from_str!(u64, expect!(s.next())); Ok(FileState { allocated, free, max }) } #[cfg(test)] mod tests { use super::*; #[test] fn dentry() { let d = dentry_state().unwrap(); println!("{:?}", d); } #[test] fn filenr() { let f = file_nr().unwrap(); println!("{:?}", f); } } procfs-0.14.2/src/sys/kernel/keys.rs000064400000000000000000000072611046102023000154200ustar 00000000000000//! Functions related to the in-kernel key management and retention facility //! //! For more details on this facility, see the `keyrings(7)` man page. //! //! Additional functions can be found in the [keyring](crate::keyring) module. use crate::{read_value, write_value, ProcResult}; /// GC Delay /// /// The value in this file specifies the interval, in seconds, /// after which revoked and expired keys will be garbage collected. /// The purpose of having such an interval is so that /// there is a window of time where user space can see an error /// (respectively EKEYREVOKED and EKEYEXPIRED) that indicates what /// happened to the key. /// /// The default value in this file is 300 (i.e., 5 minutes). /// /// (since Linux 2.6.32) pub fn gc_delay() -> ProcResult { read_value("/proc/sys/kernel/keys/gc_delay") } /// Persistent Keyring Expiry /// /// This file defines an interval, in seconds, to which the persistent /// keyring's expiration timer is reset each time the /// keyring is accessed (via keyctl_get_persistent(3) or the /// keyctl(2) KEYCTL_GET_PERSISTENT operation.) /// /// The default value in this file is 259200 (i.e., 3 days). /// /// (Since Linux 3.13) pub fn persistent_keyring_expiry() -> ProcResult { read_value("/proc/sys/kernel/keys/persistent_keyring_expiry") } /// Max bytes /// /// This is the maximum number of bytes of data that a nonroot /// user can hold in the payloads of the keys owned by the user. /// /// The default value in this file is 20,000. /// /// (since linux 2.6.26) pub fn maxbytes() -> ProcResult { read_value("/proc/sys/kernel/keys/maxbytes") } /// Set max bytes pub fn set_maxbytes(bytes: u32) -> ProcResult<()> { write_value("/proc/sys/kernel/keys/maxbytes", bytes) } /// Max keys /// /// This is the maximum number of keys that a nonroot user may own. /// /// (since linux 2.6.26) pub fn maxkeys() -> ProcResult { read_value("/proc/sys/kernel/keys/maxkeys") } /// Set max keys pub fn set_maxkeys(keys: u32) -> ProcResult<()> { write_value("/proc/sys/kernel/keys/maxkeys", keys) } /// Root maxbytes /// /// This is the maximum number of bytes of data that the root user /// (UID 0 in the root user namespace) can hold in the payloads of /// the keys owned by root. /// /// The default value in this file is 25,000,000 (20,000 before Linux 3.17). /// /// (since Linux 2.6.26) pub fn root_maxbytes() -> ProcResult { read_value("/proc/sys/kernel/keys/root_maxbytes") } /// Set root maxbytes pub fn set_root_maxbytes(bytes: u32) -> ProcResult<()> { write_value("/proc/sys/kernel/keys/root_maxbytes", bytes) } /// Root maxkeys /// /// This is the maximum number of keys that the root user (UID 0 in the root user namespace) may own. /// /// The default value in this file is 1,000,000 (200 before Linux 3.17). /// (since Linux 2.6.26) pub fn root_maxkeys() -> ProcResult { read_value("/proc/sys/kernel/keys/root_maxkeys") } /// Set root maxkeys pub fn set_root_maxkeys(keys: u32) -> ProcResult<()> { write_value("/proc/sys/kernel/keys/root_maxkeys", keys) } #[cfg(test)] mod tests { use crate::{ProcError, ProcResult}; fn check_unwrap(val: ProcResult) { match val { Ok(_) => {} Err(ProcError::NotFound(_)) => { // ok to ignore } Err(e) => { panic!("Unexpected proc error: {:?}", e); } } } #[test] fn test_keys() { check_unwrap(super::gc_delay()); check_unwrap(super::persistent_keyring_expiry()); check_unwrap(super::maxbytes()); check_unwrap(super::maxkeys()); check_unwrap(super::root_maxbytes()); check_unwrap(super::root_maxkeys()); } } procfs-0.14.2/src/sys/kernel/mod.rs000064400000000000000000000433771046102023000152340ustar 00000000000000//! Global kernel info / tuning miscellaneous stuff //! //! The files in this directory can be used to tune and monitor miscellaneous //! and general things in the operation of the Linux kernel. use std::cmp; use std::collections::HashSet; use std::str::FromStr; use bitflags::bitflags; use crate::{read_value, write_value, ProcError, ProcResult, KERNEL}; pub mod keys; pub mod random; /// Represents a kernel version, in major.minor.release version. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct Version { pub major: u8, pub minor: u8, pub patch: u16, } impl Version { pub fn new(major: u8, minor: u8, patch: u16) -> Version { Version { major, minor, patch } } /// Returns the kernel version of the currently running kernel. /// /// This is taken from `/proc/sys/kernel/osrelease`; pub fn current() -> ProcResult { read_value("/proc/sys/kernel/osrelease") } /// Parses a kernel version string, in major.minor.release syntax. /// /// Note that any extra information (stuff after a dash) is ignored. /// /// # Example /// /// ``` /// # use procfs::KernelVersion; /// let a = KernelVersion::from_str("3.16.0-6-amd64").unwrap(); /// let b = KernelVersion::new(3, 16, 0); /// assert_eq!(a, b); /// /// ``` #[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Result { let pos = s.find(|c: char| c != '.' && !c.is_ascii_digit()); let kernel = if let Some(pos) = pos { let (s, _) = s.split_at(pos); s } else { s }; let mut kernel_split = kernel.split('.'); let major = kernel_split.next().ok_or("Missing major version component")?; let minor = kernel_split.next().ok_or("Missing minor version component")?; let patch = kernel_split.next().ok_or("Missing patch version component")?; let major = major.parse().map_err(|_| "Failed to parse major version")?; let minor = minor.parse().map_err(|_| "Failed to parse minor version")?; let patch = patch.parse().map_err(|_| "Failed to parse patch version")?; Ok(Version { major, minor, patch }) } } impl FromStr for Version { type Err = &'static str; /// Parses a kernel version string, in major.minor.release syntax. /// /// Note that any extra information (stuff after a dash) is ignored. /// /// # Example /// /// ``` /// # use procfs::KernelVersion; /// let a: KernelVersion = "3.16.0-6-amd64".parse().unwrap(); /// let b = KernelVersion::new(3, 16, 0); /// assert_eq!(a, b); /// /// ``` fn from_str(s: &str) -> Result { Version::from_str(s) } } impl cmp::Ord for Version { fn cmp(&self, other: &Self) -> cmp::Ordering { match self.major.cmp(&other.major) { cmp::Ordering::Equal => match self.minor.cmp(&other.minor) { cmp::Ordering::Equal => self.patch.cmp(&other.patch), x => x, }, x => x, } } } impl cmp::PartialOrd for Version { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } /// Represents a kernel type #[derive(Debug, Clone, Eq, PartialEq)] pub struct Type { pub sysname: String, } impl Type { pub fn new(sysname: String) -> Type { Type { sysname } } /// Read the kernel type from current running kernel /// /// Defined in `include/linux/uts.h` as UTS_SYSNAME, default is "Linux". /// The file is located at `/proc/sys/kernel/ostype`. pub fn current() -> ProcResult { read_value("/proc/sys/kernel/ostype") } } impl FromStr for Type { type Err = &'static str; /// Parse a kernel type string /// /// Notice that in Linux source code, it is defined as a single string. fn from_str(s: &str) -> Result { Ok(Type::new(s.to_string())) } } /// Represents a kernel build information #[derive(Debug, Clone, Eq, PartialEq)] pub struct BuildInfo { pub version: String, pub flags: HashSet, /// This field contains any extra data from the /proc/sys/kernel/version file. It generally contains the build date of the kernel, but the format of the date can vary. /// /// A method named `extra_date` is provided which would try to parse some date formats. When the date format is not supported, an error will be returned. It depends on chrono feature. pub extra: String, } impl BuildInfo { pub fn new(version: &str, flags: HashSet, extra: String) -> BuildInfo { BuildInfo { version: version.to_string(), flags, extra, } } /// Read the kernel build information from current running kernel /// /// Generated by `scripts/mkcompile_h` when building the kernel. /// The file is located at `/proc/sys/kernel/version`. pub fn current() -> ProcResult { read_value("/proc/sys/kernel/version") } /// Check if SMP is ON pub fn smp(&self) -> bool { self.flags.contains("SMP") } /// Check if PREEMPT is ON pub fn preempt(&self) -> bool { self.flags.contains("PREEMPT") } /// Check if PREEMPTRT is ON pub fn preemptrt(&self) -> bool { self.flags.contains("PREEMPTRT") } /// Return version number /// /// This would parse number from first digits of version string. For example, #21~1 to 21. pub fn version_number(&self) -> ProcResult { let mut version_str = String::new(); for c in self.version.chars() { if c.is_ascii_digit() { version_str.push(c); } else { break; } } let version_number: u32 = version_str.parse().map_err(|_| "Failed to parse version number")?; Ok(version_number) } /// Parse extra field to `DateTime` object /// /// This function may fail as TIMESTAMP can be various formats. #[cfg(feature = "chrono")] pub fn extra_date(&self) -> ProcResult> { if let Ok(dt) = chrono::DateTime::parse_from_str(&format!("{} +0000", &self.extra), "%a %b %d %H:%M:%S UTC %Y %z") { return Ok(dt.with_timezone(&chrono::Local)); } if let Ok(dt) = chrono::DateTime::parse_from_str(&self.extra, "%a, %d %b %Y %H:%M:%S %z") { return Ok(dt.with_timezone(&chrono::Local)); } Err(ProcError::Other("Failed to parse extra field to date".to_string())) } } impl FromStr for BuildInfo { type Err = &'static str; /// Parse a kernel build information string fn from_str(s: &str) -> Result { let mut version = String::new(); let mut flags: HashSet = HashSet::new(); let mut extra: String = String::new(); let mut splited = s.split(' '); let version_str = splited.next(); if let Some(version_str) = version_str { if let Some(stripped) = version_str.strip_prefix('#') { version.push_str(stripped); } else { return Err("Failed to parse kernel build version"); } } else { return Err("Failed to parse kernel build version"); } for s in &mut splited { if s.chars().all(char::is_uppercase) { flags.insert(s.to_string()); } else { extra.push_str(s); extra.push(' '); break; } } let remains: Vec<&str> = splited.collect(); extra.push_str(&remains.join(" ")); Ok(BuildInfo { version, flags, extra }) } } /// Returns the maximum process ID number. /// /// This is taken from `/proc/sys/kernel/pid_max`. /// /// # Example /// /// ``` /// let pid_max = procfs::sys::kernel::pid_max().unwrap(); /// /// let pid = 42; // e.g. from user input, CLI args, etc. /// /// if pid > pid_max { /// eprintln!("bad process ID: {}", pid) /// } else { /// println!("good process ID: {}", pid); /// } /// ``` pub fn pid_max() -> ProcResult { read_value("/proc/sys/kernel/pid_max") } #[derive(Debug, PartialEq, Eq, Copy, Clone)] /// Represents the data from `/proc/sys/kernel/sem` pub struct SemaphoreLimits { /// The maximum semaphores per semaphore set pub semmsl: u64, /// A system-wide limit on the number of semaphores in all semaphore sets pub semmns: u64, /// The maximum number of operations that may be specified in a semop(2) call pub semopm: u64, /// A system-wide limit on the maximum number of semaphore identifiers pub semmni: u64, } impl SemaphoreLimits { pub fn new() -> ProcResult { read_value("/proc/sys/kernel/sem") } fn from_str(s: &str) -> Result { let mut s = s.split_ascii_whitespace(); let semmsl = s.next().ok_or("Missing SEMMSL")?; let semmns = s.next().ok_or("Missing SEMMNS")?; let semopm = s.next().ok_or("Missing SEMOPM")?; let semmni = s.next().ok_or("Missing SEMMNI")?; let semmsl = semmsl.parse().map_err(|_| "Failed to parse SEMMSL")?; let semmns = semmns.parse().map_err(|_| "Failed to parse SEMMNS")?; let semopm = semopm.parse().map_err(|_| "Failed to parse SEMOPM")?; let semmni = semmni.parse().map_err(|_| "Failed to parse SEMMNI")?; Ok(SemaphoreLimits { semmsl, semmns, semopm, semmni, }) } } impl FromStr for SemaphoreLimits { type Err = &'static str; fn from_str(s: &str) -> Result { SemaphoreLimits::from_str(s) } } /// Returns the system-wide limit on the total number of pages of System V shared memory /// /// This is taken from `/proc/sys/kernel/shmall` pub fn shmall() -> ProcResult { read_value("/proc/sys/kernel/shmall") } /// Returns the limit on the maximum (System V IPC) shared memory segment size that can be created. /// The value defaults to SHMMAX /// /// See also [set_shmmax](crate::sys::kernel::set_shmmax) /// /// This is taken from `/proc/sys/kernel/shmmax` pub fn shmmax() -> ProcResult { read_value("/proc/sys/kernel/shmmax") } /// Sets the limit on the maximum (System V IPC) shared memory segment size. /// /// See also [shmmax](crate::sys::kernel::shmmax) pub fn set_shmmax(new_value: u64) -> ProcResult<()> { write_value("/proc/sys/kernel/shmmax", new_value) } /// Returns the system-wide maximum number of System V shared memory segments that can be created /// /// This is taken from `/proc/sys/kernel/shmmni` pub fn shmmni() -> ProcResult { read_value("/proc/sys/kernel/shmmni") } bitflags! { /// Flags representing allowed sysrq functions pub struct AllowedFunctions : u16 { /// Enable control of console log level const ENABLE_CONTROL_LOG_LEVEL = 2; /// Enable control of keyboard (SAK, unraw) const ENABLE_CONTROL_KEYBOARD = 4; /// Enable debugging dumps of processes etc const ENABLE_DEBUGGING_DUMPS = 8; /// Enable sync command const ENABLE_SYNC_COMMAND = 16; /// Enable remound read-only const ENABLE_REMOUNT_READ_ONLY = 32; /// Enable signaling of processes (term, kill, oom-kill) const ENABLE_SIGNALING_PROCESSES = 64; /// Allow reboot/poweroff const ALLOW_REBOOT_POWEROFF = 128; /// Allow nicing of all real-time tasks const ALLOW_NICING_REAL_TIME_TASKS = 256; } } /// Values controlling functions allowed to be invoked by the SysRq key /// /// To construct this enum, see [sysrq](crate::sys::kernel::sysrq) #[derive(Copy, Clone, Debug)] pub enum SysRq { /// Disable sysrq completely Disable, /// Enable all functions of sysrq Enable, /// Bitmask of allowed sysrq functions AllowedFunctions(AllowedFunctions), } impl SysRq { fn to_number(self) -> u16 { match self { SysRq::Disable => 0, SysRq::Enable => 1, SysRq::AllowedFunctions(allowed) => allowed.bits, } } fn from_str(s: &str) -> ProcResult { match s.parse::()? { 0 => Ok(SysRq::Disable), 1 => Ok(SysRq::Enable), x => match AllowedFunctions::from_bits(x) { Some(allowed) => Ok(SysRq::AllowedFunctions(allowed)), None => Err("Invalid value".into()), }, } } } impl FromStr for SysRq { type Err = ProcError; fn from_str(s: &str) -> Result { SysRq::from_str(s) } } /// Return functions allowed to be invoked by the SysRq key /// /// This is taken from `/proc/sys/kernel/sysrq` pub fn sysrq() -> ProcResult { read_value("/proc/sys/kernel/sysrq") } /// Set functions allowed to be invoked by the SysRq key pub fn set_sysrq(new: SysRq) -> ProcResult<()> { write_value("/proc/sys/kernel/sysrq", new.to_number()) } /// The minimum value that can be written to `/proc/sys/kernel/threads-max` on Linux 4.1 or later pub const THREADS_MIN: u32 = 20; /// The maximum value that can be written to `/proc/sys/kernel/threads-max` on Linux 4.1 or later pub const THREADS_MAX: u32 = 0x3fff_ffff; /// Returns the system-wide limit on the number of threads (tasks) that can be created on the system. /// /// This is taken from `/proc/sys/kernel/threads-max` pub fn threads_max() -> ProcResult { read_value("/proc/sys/kernel/threads-max") } /// Sets the system-wide limit on the number of threads (tasks) that can be created on the system. /// /// Since Linux 4.1, this value is bounded, and must be in the range [THREADS_MIN]..=[THREADS_MAX]. /// This function will return an error if that is not the case. pub fn set_threads_max(new_limit: u32) -> ProcResult<()> { if let Ok(kernel) = *KERNEL { if kernel.major >= 4 && kernel.minor >= 1 && !(THREADS_MIN..=THREADS_MAX).contains(&new_limit) { return Err(ProcError::Other(format!( "{} is outside the THREADS_MIN..=THREADS_MAX range", new_limit ))); } } write_value("/proc/sys/kernel/threads-max", new_limit) } #[cfg(test)] mod tests { use super::*; #[test] fn test_version() { let a = Version::from_str("3.16.0-6-amd64").unwrap(); let b = Version::new(3, 16, 0); assert_eq!(a, b); let a = Version::from_str("3.16.0").unwrap(); let b = Version::new(3, 16, 0); assert_eq!(a, b); let a = Version::from_str("3.16.0_1").unwrap(); let b = Version::new(3, 16, 0); assert_eq!(a, b); } #[test] fn test_type() { let a = Type::from_str("Linux").unwrap(); assert_eq!(a.sysname, "Linux"); } #[test] fn test_build_info() { // For Ubuntu, Manjaro, CentOS and others: let a = BuildInfo::from_str("#1 SMP PREEMPT Thu Sep 30 15:29:01 UTC 2021").unwrap(); let mut flags: HashSet = HashSet::new(); flags.insert("SMP".to_string()); flags.insert("PREEMPT".to_string()); assert_eq!(a.version, "1"); assert_eq!(a.version_number().unwrap(), 1); assert_eq!(a.flags, flags); assert!(a.smp()); assert!(a.preempt()); assert!(!a.preemptrt()); assert_eq!(a.extra, "Thu Sep 30 15:29:01 UTC 2021"); #[cfg(feature = "chrono")] let _ = a.extra_date().unwrap(); // For Arch and others: let b = BuildInfo::from_str("#1 SMP PREEMPT Fri, 12 Nov 2021 19:22:10 +0000").unwrap(); assert_eq!(b.version, "1"); assert_eq!(b.version_number().unwrap(), 1); assert_eq!(b.flags, flags); assert_eq!(b.extra, "Fri, 12 Nov 2021 19:22:10 +0000"); assert!(b.smp()); assert!(b.preempt()); assert!(!b.preemptrt()); #[cfg(feature = "chrono")] let _ = b.extra_date().unwrap(); // For Debian and others: let c = BuildInfo::from_str("#1 SMP Debian 5.10.46-4 (2021-08-03)").unwrap(); let mut flags: HashSet = HashSet::new(); flags.insert("SMP".to_string()); assert_eq!(c.version, "1"); assert_eq!(c.version_number().unwrap(), 1); assert_eq!(c.flags, flags); assert_eq!(c.extra, "Debian 5.10.46-4 (2021-08-03)"); assert!(c.smp()); assert!(!c.preempt()); assert!(!c.preemptrt()); // Skip the date parsing for now } #[test] fn test_current() { let _ = Version::current().unwrap(); let _ = Type::current().unwrap(); let _ = BuildInfo::current().unwrap(); } #[test] fn test_pid_max() { assert!(pid_max().is_ok()); } #[test] fn test_semaphore_limits() { // Note that the below string has tab characters in it. Make sure to not remove them. let a = SemaphoreLimits::from_str("32000 1024000000 500 32000").unwrap(); let b = SemaphoreLimits { semmsl: 32_000, semmns: 1_024_000_000, semopm: 500, semmni: 32_000, }; assert_eq!(a, b); let a = SemaphoreLimits::from_str("1"); assert!(a.is_err() && a.err().unwrap() == "Missing SEMMNS"); let a = SemaphoreLimits::from_str("1 string 500 3200"); assert!(a.is_err() && a.err().unwrap() == "Failed to parse SEMMNS"); } #[test] fn test_sem() { let _ = SemaphoreLimits::new().unwrap(); } #[test] fn test_shmall() { let _ = shmall().unwrap(); } #[test] fn test_shmmax() { let _ = shmmax().unwrap(); } #[test] fn test_shmmni() { let _ = shmmni().unwrap(); } #[test] fn test_sysrq() { let sys_rq = sysrq().unwrap(); println!("{:?}", sys_rq) } #[test] fn test_threads_max() { let _ = threads_max().unwrap(); } } procfs-0.14.2/src/sys/kernel/random.rs000064400000000000000000000072441046102023000157260ustar 00000000000000//! These files provide additional information about the /dev/random device //! //! Note that some of these entries are only documented in random(4), while some are also documented under proc(5) use crate::{read_value, write_value, ProcError, ProcResult}; use lazy_static::lazy_static; lazy_static! { static ref RANDOM_ROOT: std::path::PathBuf = std::path::PathBuf::from("/proc/sys/kernel/random"); } /// This read-only file gives the available entropy, in bits. This will be a number in the range /// 0 to 4096 pub fn entropy_avail() -> ProcResult { read_value(RANDOM_ROOT.join("entropy_avail")) } /// This file gives the size of the entropy pool /// /// The semantics of this file are different on kernel versions older than 2.6, however, since /// Linux 2.6 it is read-only, and gives the size of the entropy pool in bits, containing the value 4096. /// /// See `man random(4)` for more information pub fn poolsize() -> ProcResult { read_value(RANDOM_ROOT.join("poolsize")) } /// This file contains the number of bits of entropy required for waking up processes that sleep waiting /// for entropy from /dev/random /// /// The default is 64. /// /// This will first attempt to read from `/proc/sys/kernel/random/read_wakeup_threshold` but it /// will fallback to `/proc/sys/kernel/random/write_wakeup_threshold` if the former file is not found. pub fn read_wakeup_threshold() -> ProcResult { match read_value(RANDOM_ROOT.join("read_wakeup_threshold")) { Ok(val) => Ok(val), Err(err) => match err { ProcError::NotFound(_) => read_value(RANDOM_ROOT.join("write_wakeup_threshold")), err => Err(err), }, } } /// This file contains the number of bits of entropy below which we wake up processes that do a /// select(2) or poll(2) for write access to /dev/random. These values can be changed by writing to the file. pub fn write_wakeup_threshold(new_value: u32) -> ProcResult<()> { write_value(RANDOM_ROOT.join("write_wakeup_threshold"), new_value) } /// This read-only file randomly generates a fresh 128-bit UUID on each read pub fn uuid() -> ProcResult { read_value(RANDOM_ROOT.join("uuid")) } /// This is a read-only file containing a 128-bit UUID generated at boot pub fn boot_id() -> ProcResult { read_value(RANDOM_ROOT.join("boot_id")) } #[cfg(test)] mod tests { use super::*; #[test] fn test_entropy_avail() { let entropy = entropy_avail().unwrap(); assert!(entropy <= 4096); } #[test] fn test_poolsize() { // The kernel support section in the root lib.rs file says that we only aim to support >= 2.6 kernels, // so only test that case let _poolsize = poolsize().unwrap(); } #[test] fn test_read_wakeup_threshold() { let threshold = read_wakeup_threshold().unwrap(); println!("{}", threshold); } #[test] fn test_write_wakeup_threshold() { let old_threshold = read_wakeup_threshold().unwrap(); match write_wakeup_threshold(1024) { Ok(_) => (), Err(err) => match err { ProcError::PermissionDenied(_) => { // This is ok, not everyone wants to run our tests as root return; } err => panic!("test_write_wakeup_threshold error: {:?}", err), }, } // If we got here, let's restore the old threshold let _ = write_wakeup_threshold(old_threshold); } #[test] fn test_uuid_fns() { let uuid = uuid().unwrap(); let boot_id = boot_id().unwrap(); println!("UUID: {}", uuid); println!("boot UUID: {}", boot_id); } } procfs-0.14.2/src/sys/mod.rs000064400000000000000000000007411046102023000137400ustar 00000000000000//! Sysctl is a means of configuring certain aspects of the kernel at run-time, //! and the `/proc/sys/` directory is there so that you don't even need special tools to do it! //! //! This directory (present since 1.3.57) contains a number of files //! and subdirectories corresponding to kernel variables. //! These variables can be read and sometimes modified using the `/proc` filesystem, //! and the (deprecated) sysctl(2) system call. pub mod fs; pub mod kernel; pub mod vm; procfs-0.14.2/src/sys/vm.rs000064400000000000000000000104201046102023000135760ustar 00000000000000//! Memory management tuning buffer and cache management //! //! The files in this directory can be used to tune //! the operation of the virtual memory (VM) subsystem of the Linux kernel //! and the write out of dirty data to disk. use std::fmt; use std::str; use crate::{read_value, write_value, ProcResult}; /// The amount of free memory in the system that should be reserved for users with the capability cap_sys_admin. /// /// # Example /// /// ``` /// use procfs::sys::vm::admin_reserve_kbytes; /// /// assert_ne!(admin_reserve_kbytes().unwrap(), 0); /// ``` pub fn admin_reserve_kbytes() -> ProcResult { read_value("/proc/sys/vm/admin_reserve_kbytes") } /// Set the amount of free memory in the system that should be reserved for users with the capability cap_sys_admin. pub fn set_admin_reserve_kbytes(kbytes: usize) -> ProcResult<()> { write_value("/proc/sys/vm/admin_reserve_kbytes", kbytes) } /// Force all zones are compacted such that free memory is available in contiguous blocks where possible. /// /// This can be important for example in the allocation of huge pages /// although processes will also directly compact memory as required. /// /// Present only if the kernel was configured with CONFIG_COMPACTION. pub fn compact_memory() -> ProcResult<()> { write_value("/proc/sys/vm/compact_memory", 1) } /// drop clean caches, dentries, and inodes from memory, causing that memory to become free. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DropCache { /// default Default = 0, /// free pagecache PageCache = 1, /// free dentries and inodes Inodes = 2, /// free pagecache, dentries and inodes All = 3, /// disable Disable = 4, } impl fmt::Display for DropCache { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{}", match self { DropCache::Default => 0, DropCache::PageCache => 1, DropCache::Inodes => 2, DropCache::All => 3, DropCache::Disable => 4, } ) } } impl str::FromStr for DropCache { type Err = &'static str; fn from_str(s: &str) -> Result { s.parse().map_err(|_| "Fail to parse drop cache").and_then(|n| match n { 0 => Ok(DropCache::Default), 1 => Ok(DropCache::PageCache), 2 => Ok(DropCache::Inodes), 3 => Ok(DropCache::All), 4 => Ok(DropCache::Disable), _ => Err("Unknown drop cache value"), }) } } /// Causes the kernel to drop clean caches, dentries, and inodes from memory, /// causing that memory to become free. /// /// This can be useful for memory management testing and performing reproducible filesystem benchmarks. /// Because writing to this file causes the benefits of caching to be lost, /// it can degrade overall system performance. pub fn drop_caches(drop: DropCache) -> ProcResult<()> { write_value("/proc/sys/vm/drop_caches", drop) } /// The maximum number of memory map areas a process may have. /// /// Memory map areas are used as a side-effect of calling malloc, /// directly by mmap, mprotect, and madvise, and also when loading shared libraries. /// /// # Example /// /// ``` /// use procfs::sys::vm::max_map_count; /// /// assert_ne!(max_map_count().unwrap(), 0); /// ``` pub fn max_map_count() -> ProcResult { read_value("/proc/sys/vm/max_map_count") } /// Set the maximum number of memory map areas a process may have. /// /// Memory map areas are used as a side-effect of calling malloc, /// directly by mmap, mprotect, and madvise, and also when loading shared libraries. pub fn set_max_map_count(count: u64) -> ProcResult<()> { write_value("/proc/sys/vm/max_map_count", count) } #[cfg(test)] mod tests { use super::*; use std::str::FromStr; #[test] fn test() { use std::path::Path; if Path::new("/proc/sys/vm/admin_reserve_kbytes").exists() { admin_reserve_kbytes().unwrap(); } if Path::new("/proc/sys/vm/max_map_count").exists() { max_map_count().unwrap(); } for v in 0..5 { let s = format!("{}", v); let dc = DropCache::from_str(&s).unwrap(); assert_eq!(format!("{}", dc), s); } } } procfs-0.14.2/src/sysvipc_shm.rs000064400000000000000000000066341046102023000147210ustar 00000000000000use std::io; use super::{FileWrapper, ProcResult}; use std::str::FromStr; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// A shared memory segment parsed from `/proc/sysvipc/shm` /// Relation with `[crate::process::process::MMapPath::Vsys]` #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[allow(non_snake_case)] pub struct Shm { /// Segment key pub key: i32, /// Segment ID, unique pub shmid: u64, /// Access permissions, as octal pub perms: u16, /// Size in bytes pub size: u32, /// Creator PID pub cpid: i32, /// Last operator PID pub lpid: i32, /// Number of attached processes pub nattch: u32, /// User ID pub uid: u16, /// Group ID pub gid: u16, /// Creator UID pub cuid: u16, /// Creator GID pub cgid: u16, /// Time of last `shmat` (attach), epoch pub atime: u64, /// Time of last `shmdt` (detach), epoch pub dtime: u64, /// Time of last permission change, epoch pub ctime: u64, /// Current part of the shared memory resident in memory pub rss: u64, /// Current part of the shared memory in SWAP pub swap: u64, } impl Shm { /// Reads and parses the `/proc/sysvipc/shm`, returning an error if there are problems. pub fn new() -> ProcResult> { let f = FileWrapper::open("/proc/sysvipc/shm")?; Shm::from_reader(f) } /// Get Meminfo from a custom Read instead of the default `/proc/sysvipc/shm`. pub fn from_reader(r: R) -> ProcResult> { use std::io::{BufRead, BufReader}; let reader = BufReader::new(r); let mut vec = Vec::new(); // See printing code here: // https://elixir.bootlin.com/linux/latest/source/ipc/shm.c#L1737 for line in reader.lines().skip(1) { let line = expect!(line); let mut s = line.split_whitespace(); let key = expect!(i32::from_str(expect!(s.next()))); let shmid = expect!(u64::from_str(expect!(s.next()))); let perms = expect!(u16::from_str(expect!(s.next()))); let size = expect!(u32::from_str(expect!(s.next()))); let cpid = expect!(i32::from_str(expect!(s.next()))); let lpid = expect!(i32::from_str(expect!(s.next()))); let nattch = expect!(u32::from_str(expect!(s.next()))); let uid = expect!(u16::from_str(expect!(s.next()))); let gid = expect!(u16::from_str(expect!(s.next()))); let cuid = expect!(u16::from_str(expect!(s.next()))); let cgid = expect!(u16::from_str(expect!(s.next()))); let atime = expect!(u64::from_str(expect!(s.next()))); let dtime = expect!(u64::from_str(expect!(s.next()))); let ctime = expect!(u64::from_str(expect!(s.next()))); let rss = expect!(u64::from_str(expect!(s.next()))); let swap = expect!(u64::from_str(expect!(s.next()))); let shm = Shm { key, shmid, perms, size, cpid, lpid, nattch, uid, gid, cuid, cgid, atime, dtime, ctime, rss, swap, }; vec.push(shm); } Ok(vec) } } procfs-0.14.2/src/uptime.rs000064400000000000000000000037201046102023000136460ustar 00000000000000use crate::{FileWrapper, ProcResult}; use std::io::Read; use std::str::FromStr; use std::time::Duration; /// The uptime of the system, based on the `/proc/uptime` file. #[derive(Debug, Clone)] #[non_exhaustive] pub struct Uptime { /// The uptime of the system (including time spent in suspend). pub uptime: f64, /// The sum of how much time each core has spent idle. pub idle: f64, } impl Uptime { pub fn new() -> ProcResult { let file = FileWrapper::open("/proc/uptime")?; Uptime::from_reader(file) } pub fn from_reader(mut r: R) -> ProcResult { let mut buf = Vec::with_capacity(128); r.read_to_end(&mut buf)?; let line = String::from_utf8_lossy(&buf); let buf = line.trim(); let mut s = buf.split(' '); let uptime = expect!(f64::from_str(expect!(s.next()))); let idle = expect!(f64::from_str(expect!(s.next()))); Ok(Uptime { uptime, idle }) } /// The uptime of the system (including time spent in suspend). pub fn uptime_duration(&self) -> Duration { let secs = self.uptime.trunc() as u64; let csecs = (self.uptime.fract() * 100.0).round() as u32; let nsecs = csecs * 10_000_000; Duration::new(secs, nsecs) } /// The sum of how much time each core has spent idle. pub fn idle_duration(&self) -> Duration { let secs = self.idle.trunc() as u64; let csecs = (self.idle.fract() * 100.0).round() as u32; let nsecs = csecs * 10_000_000; Duration::new(secs, nsecs) } } #[cfg(test)] mod tests { use super::*; use std::io::Cursor; #[test] fn test_uptime() { let reader = Cursor::new(b"2578790.61 1999230.98\n"); let uptime = Uptime::from_reader(reader).unwrap(); assert_eq!(uptime.uptime_duration(), Duration::new(2578790, 610_000_000)); assert_eq!(uptime.idle_duration(), Duration::new(1999230, 980_000_000)); } } procfs-0.14.2/support.md000064400000000000000000000200411046102023000132370ustar 00000000000000# Supported features This is an approximate list of all the files under the `/proc` mount, and an indication if that feature/file is supported by the `procfs` crate. Help is needed to keep this file up-to-date, so please open an issue or pull request if you spot something that's not right. * [ ] `/proc/[pid]` * [ ] `/proc/[pid]/attr` * [ ] `/proc/[pid]/attr/current` * [ ] `/proc/[pid]/attr/exec` * [ ] `/proc/[pid]/attr/fscreate` * [ ] `/proc/[pid]/attr/keycreate` * [ ] `/proc/[pid]/attr/prev` * [ ] `/proc/[pid]/attr/socketcreate` * [x] `/proc/[pid]/autogroup` * [x] `/proc/[pid]/auxv` * [x] `/proc/[pid]/cgroup` * [ ] `/proc/[pid]/clear_refs` * [x] `/proc/[pid]/cmdline` * [x] `/proc/[pid]/comm` * [x] `/proc/[pid]/coredump_filter` * [ ] `/proc/[pid]/cpuset` * [x] `/proc/[pid]/cwd` * [x] `/proc/[pid]/environ` * [x] `/proc/[pid]/exe` * [x] `/proc/[pid]/fd/` * [ ] `/proc/[pid]/fdinfo/` * [ ] `/proc/[pid]/gid_map` * [x] `/proc/[pid]/io` * [x] `/proc/[pid]/limits` * [ ] `/proc/[pid]/map_files/` * [x] `/proc/[pid]/maps` * [x] `/proc/[pid]/mem` * [x] `/proc/[pid]/mountinfo` * [ ] `/proc/[pid]/mounts` * [x] `/proc/[pid]/mountstats` * [x] `/proc/[pid]/ns/` * [ ] `/proc/[pid]/numa_maps` * [ ] `/proc/[pid]/oom_adj` * [x] `/proc/[pid]/oom_score` * [ ] `/proc/[pid]/oom_score_adj` * [ ] `/proc/[pid]/pagemap` * [ ] `/proc/[pid]/personality` * [x] `/proc/[pid]/root` * [ ] `/proc/[pid]/seccomp` * [ ] `/proc/[pid]/setgroups` * [ ] `/proc/[pid]/sched_autogroup_enabled` * [x] `/proc/[pid]/smaps` * [x] `/proc/[pid]/smaps_rollup` * [ ] `/proc/[pid]/stack` * [x] `/proc/[pid]/stat` * [x] `/proc/[pid]/statm` * [x] `/proc/[pid]/status` * [ ] `/proc/[pid]/syscall` * [ ] `/proc/[pid]/task` * [x] `/proc/[pid]/task/[tid]/stat` * [x] `/proc/[pid]/task/[tid]/status` * [x] `/proc/[pid]/task/[tid]/io` * [x] `/proc/[pid]/task/[tid]/children` * [ ] `/proc/[pid]/timers` * [ ] `/proc/[pid]/timerslack_ns` * [ ] `/proc/[pid]/uid_map` * [ ] `/proc/[pid]/gid_map` * [x] `/proc/[pid]/wchan` * [ ] `/proc/apm` * [ ] `/proc/buddyinfo` * [ ] `/proc/bus` * [ ] `/proc/bus/pccard` * [ ] `/proc/bus/pccard/drivers` * [ ] `/proc/bus/pci` * [ ] `/proc/bus/pci/devices` * [x] `/proc/cmdline` * [ ] `/proc/config.gz` * [ ] `/proc/crypto` * [ ] `/proc/cpuinfo` * [ ] `/proc/devices` * [x] `/proc/diskstats` * [ ] `/proc/dma` * [ ] `/proc/driver` * [ ] `/proc/execdomains` * [ ] `/proc/fb` * [ ] `/proc/filesystems` * [ ] `/proc/fs` * [ ] `/proc/ide` * [ ] `/proc/interrupts` * [ ] `/proc/iomem` * [ ] `/proc/ioports` * [ ] `/proc/kallsyms` * [ ] `/proc/kcore` * [x] `/proc/keys` * [x] `/proc/key-users` * [ ] `/proc/kmsg` * [ ] `/proc/kpagecgroup` * [ ] `/proc/kpagecgroup` * [ ] `/proc/kpagecount` * [ ] `/proc/kpageflags` * [ ] `/proc/ksyms` * [x] `/proc/loadavg` * [x] `/proc/locks` * [ ] `/proc/malloc` * [x] `/proc/meminfo` * [x] `/proc/modules` * [ ] `/proc/mounts` * [ ] `/proc/mtrr` * [ ] `/proc/net` * [x] `/proc/net/arp` * [x] `/proc/net/dev` * [ ] `/proc/net/dev_mcast` * [ ] `/proc/net/igmp` * [ ] `/proc/net/ipv6_route` * [ ] `/proc/net/rarp` * [ ] `/proc/net/raw` * [x] `/proc/net/route` * [ ] `/proc/net/snmp` * [x] `/proc/net/tcp` * [x] `/proc/net/udp` * [x] `/proc/net/unix` * [ ] `/proc/net/netfilter/nfnetlink_queue` * [ ] `/proc/partitions` * [ ] `/proc/pci` * [x] `/proc/pressure` * [x] `/proc/pressure/cpu` * [x] `/proc/pressure/io` * [x] `/proc/pressure/memory` * [ ] `/proc/profile` * [ ] `/proc/scsi` * [ ] `/proc/scsi/scsi` * [ ] `/proc/scsi/[drivername]` * [ ] `/proc/self` * [ ] `/proc/slabinfo` * [x] `/proc/stat` * [ ] `/proc/swaps` * [ ] `/proc/sys` * [ ] `/proc/sys/abi` * [ ] `/proc/sys/debug` * [ ] `/proc/sys/dev` * [ ] `/proc/sys/fs` * [x] `/proc/sys/fs/binfmt_misc` * [x] `/proc/sys/fs/dentry-state` * [ ] `/proc/sys/fs/dir-notify-enable` * [ ] `/proc/sys/fs/dquot-max` * [ ] `/proc/sys/fs/dquot-nr` * [x] `/proc/sys/fs/epoll` * [x] `/proc/sys/fs/file-max` * [x] `/proc/sys/fs/file-nr` * [ ] `/proc/sys/fs/inode-max` * [ ] `/proc/sys/fs/inode-nr` * [ ] `/proc/sys/fs/inode-state` * [ ] `/proc/sys/fs/inotify` * [ ] `/proc/sys/fs/lease-break-time` * [ ] `/proc/sys/fs/leases-enable` * [ ] `/proc/sys/fs/mount-max` * [ ] `/proc/sys/fs/mqueue` * [ ] `/proc/sys/fs/nr_open` * [ ] `/proc/sys/fs/overflowgid` * [ ] `/proc/sys/fs/overflowuid` * [ ] `/proc/sys/fs/pipe-max-size` * [ ] `/proc/sys/fs/pipe-user-pages-hard` * [ ] `/proc/sys/fs/pipe-user-pages-soft` * [ ] `/proc/sys/fs/protected_hardlinks` * [ ] `/proc/sys/fs/protected_symlinks` * [ ] `/proc/sys/fs/suid_dumpable` * [ ] `/proc/sys/fs/super-max` * [ ] `/proc/sys/fs/super-nr` * [ ] `/proc/sys/kernel` * [ ] `/proc/sys/kernel/acct` * [ ] `/proc/sys/kernel/auto_msgmni` * [ ] `/proc/sys/kernel/cap_last_cap` * [ ] `/proc/sys/kernel/cap-bound` * [ ] `/proc/sys/kernel/core_pattern` * [ ] `/proc/sys/kernel/core_pipe_limit` * [ ] `/proc/sys/kernel/core_uses_pid` * [ ] `/proc/sys/kernel/ctrl-alt-del` * [ ] `/proc/sys/kernel/dmesg_restrict` * [ ] `/proc/sys/kernel/domainname` * [ ] `/proc/sys/kernel/hostname` * [ ] `/proc/sys/kernel/hotplug` * [ ] `/proc/sys/kernel/htab-reclaim` * [x] `/proc/sys/kernel/keys/\*` * [ ] `/proc/sys/kernel/kptr_restrict` * [ ] `/proc/sys/kernel/l2cr` * [ ] `/proc/sys/kernel/modprobe` * [ ] `/proc/sys/kernel/modules_disabled` * [ ] `/proc/sys/kernel/msgmax` * [ ] `/proc/sys/kernel/msgmni` * [ ] `/proc/sys/kernel/msgmnb` * [ ] `/proc/sys/kernel/ngroups_max` * [ ] `/proc/sys/kernel/ns_last_pid` * [x] `/proc/sys/kernel/ostype` * [x] `/proc/sys/kernel/osrelease` * [ ] `/proc/sys/kernel/overflowgid` * [ ] `/proc/sys/kernel/overflowuid` * [ ] `/proc/sys/kernel/panic` * [ ] `/proc/sys/kernel/panic_on_oops` * [x] `/proc/sys/kernel/pid_max` * [ ] `/proc/sys/kernel/powersave-nap` * [ ] `/proc/sys/kernel/printk` * [ ] `/proc/sys/kernel/pty` * [ ] `/proc/sys/kernel/pty/max` * [ ] `/proc/sys/kernel/pty/nr` * [x] `/proc/sys/kernel/random` * [x] `/proc/sys/kernel/random/entropy_avail` * [x] `/proc/sys/kernel/random/poolsize` * [x] `/proc/sys/kernel/random/read_wakeup_threshold` * [x] `/proc/sys/kernel/random/write_wakeup_threshold` * [x] `/proc/sys/kernel/random/uuid` * [x] `/proc/sys/kernel/random/boot_id` * [ ] `/proc/sys/kernel/randomize_va_space` * [ ] `/proc/sys/kernel/real-root-dev` * [ ] `/proc/sys/kernel/reboot-cmd` * [ ] `/proc/sys/kernel/rtsig-max` * [ ] `/proc/sys/kernel/rtsig-nr` * [ ] `/proc/sys/kernel/sched_child_runs_first` * [ ] `/proc/sys/kernel/sched_rr_timeslice_ms` * [ ] `/proc/sys/kernel/sched_rt_period_us` * [ ] `/proc/sys/kernel/sched_rt_runtime_us` * [ ] `/proc/sys/kernel/seccomp` * [x] `/proc/sys/kernel/sem` * [ ] `/proc/sys/kernel/sg-big-buff` * [ ] `/proc/sys/kernel/shm_rmid_forced` * [x] `/proc/sys/kernel/shmall` * [x] `/proc/sys/kernel/shmmax` * [x] `/proc/sys/kernel/shmmni` * [ ] `/proc/sys/kernel/sysctl_writes_strict` * [x] `/proc/sys/kernel/sysrq` * [x] `/proc/sys/kernel/version` * [x] `/proc/sys/kernel/threads-max` * [ ] `/proc/sys/kernel/yama/ptrace_scope` * [ ] `/proc/sys/kernel/zero-paged` * [ ] `/proc/sys/net` * [ ] `/proc/sys/net/core/bpf_jit_enable` * [ ] `/proc/sys/net/core/somaxconn` * [ ] `/proc/sys/proc` * [ ] `/proc/sys/sunrpc` * [ ] `/proc/sys/user` * [ ] `/proc/sys/vm` * [x] `/proc/sys/vm/admin_reserve_kbytes` * [ ] `/proc/sys/vm/compact_memory` * [x] `/proc/sys/vm/drop_caches` * [ ] `/proc/sys/vm/legacy_va_layout` * [ ] `/proc/sys/vm/memory_failure_early_kill` * [ ] `/proc/sys/vm/memory_failure_recovery` * [ ] `/proc/sys/vm/oom_dump_tasks` * [ ] `/proc/sys/vm/oom_kill_allocating_task` * [ ] `/proc/sys/vm/overcommit_kbytes` * [x] `/proc/sys/vm/overcommit_memory` * [ ] `/proc/sys/vm/overcommit_ratio` * [ ] `/proc/sys/vm/panic_on_oom` * [ ] `/proc/sys/vm/swappiness` * [ ] `/proc/sys/vm/user_reserve_kbytes` * [ ] `/proc/sysrq-trigger` * [ ] `/proc/sysvipc` * [ ] `/proc/thread-self` * [ ] `/proc/timer_list` * [ ] `/proc/timer_stats` * [ ] `/proc/tty` * [x] `/proc/uptime` * [ ] `/proc/version` * [x] `/proc/vmstat` * [ ] `/proc/zoneinfo`