procfs-0.17.0/.cargo_vcs_info.json0000644000000001440000000000100124140ustar { "git": { "sha1": "e303757cd3e21c763e339f6f7e4c9cb4e435ff83" }, "path_in_vcs": "procfs" }procfs-0.17.0/COPYRIGHT.txt000064400000000000000000000562751046102023000133350ustar 00000000000000The source code for the procfs library is copyright by Andrew Chin, 2019, and other contributors. It is licensed 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.17.0/Cargo.lock0000644000000572650000000000100104070ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[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 = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "shlex", ] [[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.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "windows-targets", ] [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[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 1.0.109", "synstructure", ] [[package]] name = "flate2" version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "half" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[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.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "is-terminal" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] [[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 = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] [[package]] name = "nom" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "procfs" version = "0.17.0" dependencies = [ "backtrace", "bitflags", "chrono", "criterion", "failure", "flate2", "hex", "libc", "procfs-core", "procinfo", "rustix", "serde", ] [[package]] name = "procfs-core" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ "backtrace", "bitflags", "chrono", "hex", "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.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "regex" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[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.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[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 = "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.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn 2.0.79", ] [[package]] name = "serde_json" version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 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 1.0.109", "unicode-xid", ] [[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.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasm-bindgen" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.79", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" procfs-0.17.0/Cargo.toml0000644000000063770000000000100104300ustar # 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.17.0" authors = ["Andrew Chin "] build = "build.rs" autobins = false autoexamples = false autotests = false autobenches = false 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 [lib] name = "procfs" path = "src/lib.rs" [[example]] name = "crypto" path = "examples/crypto.rs" [[example]] name = "diskstat" path = "examples/diskstat.rs" [[example]] name = "dump" path = "examples/dump.rs" [[example]] name = "interface_stats" path = "examples/interface_stats.rs" [[example]] name = "iomem" path = "examples/iomem.rs" [[example]] name = "kpagecount" path = "examples/kpagecount.rs" [[example]] name = "lslocks" path = "examples/lslocks.rs" [[example]] name = "lsmod" path = "examples/lsmod.rs" [[example]] name = "mountinfo" path = "examples/mountinfo.rs" [[example]] name = "mounts" path = "examples/mounts.rs" [[example]] name = "netstat" path = "examples/netstat.rs" [[example]] name = "partitions" path = "examples/partitions.rs" [[example]] name = "pfn" path = "examples/pfn.rs" [[example]] name = "pressure" path = "examples/pressure.rs" [[example]] name = "process_hierarchy" path = "examples/process_hierarchy.rs" [[example]] name = "process_kpageflags" path = "examples/process_kpageflags.rs" [[example]] name = "ps" path = "examples/ps.rs" [[example]] name = "self_memory" path = "examples/self_memory.rs" [[example]] name = "shm" path = "examples/shm.rs" [[bench]] name = "cpuinfo" path = "benches/cpuinfo.rs" harness = false [dependencies.backtrace] version = "0.3" optional = true [dependencies.bitflags] version = "2.0" default-features = false [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.procfs-core] version = "0.17.0" default-features = false [dependencies.rustix] version = "0.38.19" features = [ "fs", "process", "param", "system", "thread", ] [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dev-dependencies.criterion] version = "0.5" [dev-dependencies.failure] version = "0.1" [dev-dependencies.libc] version = "0.2.139" [dev-dependencies.procinfo] version = "0.4.2" [features] backtrace = [ "dep:backtrace", "procfs-core/backtrace", ] default = [ "chrono", "flate2", "procfs-core/default", ] serde1 = [ "serde", "procfs-core/serde1", ] procfs-0.17.0/Cargo.toml.orig000064400000000000000000000023261046102023000140770ustar 00000000000000[package] name = "procfs" documentation = "https://docs.rs/procfs/" description = "Interface to the linux procfs pseudo-filesystem" readme = "../README.md" version.workspace = true authors.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true license.workspace = true edition.workspace = true rust-version.workspace = true [features] backtrace = ["dep:backtrace", "procfs-core/backtrace"] default = ["chrono", "flate2", "procfs-core/default"] serde1 = ["serde", "procfs-core/serde1"] [dependencies] procfs-core = { path = "../procfs-core", version = "0.17.0", default-features = false } rustix = { version = "0.38.19", features = ["fs", "process", "param", "system", "thread"] } bitflags = { version = "2.0", default-features = false } chrono = {version = "0.4.20", optional = true, features = ["clock"], default-features = false } 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.5" procinfo = "0.4.2" failure = "0.1" libc = "0.2.139" [package.metadata.docs.rs] all-features = true [[bench]] name = "cpuinfo" harness = false procfs-0.17.0/LICENSE-APACHE000064400000000000000000000261361046102023000131410ustar 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.17.0/LICENSE-MIT000064400000000000000000000020511046102023000126370ustar 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.17.0/README.md000064400000000000000000000101321046102023000124610ustar 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/procfs/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(); 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 * 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/procfs/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 is only tested against the latest stable rustc compiler, but may work with older compilers. See [msrv.md](msrv.md) for more details. ## 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.17.0/benches/cpuinfo.rs000064400000000000000000000013671046102023000146340ustar 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.17.0/build.rs000064400000000000000000000007061046102023000126550ustar 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.17.0/examples/README.md000064400000000000000000000160501046102023000143040ustar 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 ... ``` ## pfn.rs List memory mapping, and physical address for each virtual address. Must be run as root, see [pagemap.txt](https://www.kernel.org/doc/Documentation/vm/pagemap.txt) ```text Memory mapping MemoryMap { address: (140561525968896, 140561525972992), perms: "r--p", offset: 884736, dev: (252, 0), inode: 18221539, pathname: Path("/usr/lib64/libm.so.6") } virt_mem: 0x7fd707d31000, pfn: 0x1fd37d, phys_addr: 0x1fd37d000 Memory mapping MemoryMap { address: (140561525972992, 140561525977088), perms: "rw-p", offset: 888832, dev: (252, 0), inode: 18221539, pathname: Path("/usr/lib64/libm.so.6") } virt_mem: 0x7fd707d32000, pfn: 0x1fcb97, phys_addr: 0x1fcb97000 ``` ## process_kpageflags.rs Search for a pointer (virtual address) in physical memory. Display physical page flags Requires root or sudo ```text Virtual address of `variable`: 0x7ffd2de4708f Found memory mapping MemoryMap { address: (140725373272064, 140725373407232), perms: "rw-p", offset: 0, dev: (0, 0), inode: 0, pathname: Stack } Found page virt_mem: 0x7ffd2de47000, pfn: 0x107b06, phys_addr: 0x107b06000, flags: UPTODATE | LRU | MMAP | ANON | SWAPBACKED ``` ## kpagecount List physical memory pages by reading /proc/iomem, and find the page with the most references Require root or CAP_SYS_ADMIN ```text Found RAM here: 0x1000-0x9fbff Lots of references to this locations: addr=0x9d000, pfn=157, refs=0 Found RAM here: 0x100000-0xdffeffff Lots of references to this locations: addr=0x81ba3000, pfn=531363, refs=128 Found RAM here: 0x100000000-0x11fffffff Lots of references to this locations: addr=0x1b575000, pfn=111989, refs=134 ``` ## Crypto List available crypto algorithms, along with details. Passing an algorithm as an argument will show only that algorithms implementations (this can potentially be multiple). Partial arguments (i.e "sha") will return all algorithms that match. ```text Type: sha256 Name: sha256 Driver: sha256-avx2 Module: sha256_ssse3 Priority: 170 Ref Count: 2 Self Test: Passed Internal: false fips enabled: false Type Details: Shash(Shash { block_size: 64, digest_size: 32 }) ``` procfs-0.17.0/examples/crypto.rs000064400000000000000000000020301046102023000147040ustar 00000000000000use std::env::args; use procfs::crypto; pub fn main() { let crypto = crypto().expect("Was not able to access current crypto"); let name_arg = args().nth(1); for (name, entries) in crypto.crypto_blocks { if let Some(ref name_find) = name_arg { if !name.contains(name_find) { continue; } } println!("Type: {name}"); for block in entries { println!("{:>14}: {}", "Name", block.name); println!("{:>14}: {}", "Driver", block.driver); println!("{:>14}: {}", "Module", block.module); println!("{:>14}: {}", "Priority", block.priority); println!("{:>14}: {}", "Ref Count", block.ref_count); println!("{:>14}: {:?}", "Self Test", block.self_test); println!("{:>14}: {}", "Internal", block.internal); println!("{:>14}: {}", "fips enabled", block.fips_enabled); println!("{:>14}: {:?}", "Type Details", block.crypto_type); println!(); } } } procfs-0.17.0/examples/diskstat.rs000064400000000000000000000022741046102023000152240ustar 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.17.0/examples/dump.rs000064400000000000000000000007721046102023000143440ustar 00000000000000extern crate procfs; use procfs::prelude::*; 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().get()); } procfs-0.17.0/examples/interface_stats.rs000064400000000000000000000026561046102023000165600ustar 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.17.0/examples/iomem.rs000064400000000000000000000012611046102023000144770ustar 00000000000000// // Print physical location of system RAM // This requires CAP_SYS_ADMIN privilege, or root, otherwise physical memory addresses will be zero // fn main() { if !rustix::process::geteuid().is_root() { println!("WARNING: Access to /proc/iomem requires root, re-run with sudo"); } let iomem = procfs::iomem().expect("Can't read /proc/iomem"); for (_indent, map) in iomem.iter() { if map.name == "System RAM" { println!("Found RAM here: 0x{:x}-0x{:x}", map.address.0, map.address.1); } } if !rustix::process::geteuid().is_root() { println!("\n\nWARNING: Access to /proc/iomem requires root, re-run with sudo"); } } procfs-0.17.0/examples/kpagecount.rs000064400000000000000000000036121046102023000155330ustar 00000000000000// // Print the physical memory page with the most references // // Require CAP_SYS_ADMIN privilege, or root // // Sample output: // // Found RAM here: 0x1000-0x9fbff // Lots of references to this locations: addr=0x9d000, pfn=157, refs=0 // Found RAM here: 0x100000-0xdffeffff // Lots of references to this locations: addr=0x81ba3000, pfn=531363, refs=128 // Found RAM here: 0x100000000-0x11fffffff // Lots of references to this locations: addr=0x1b575000, pfn=111989, refs=134 // use procfs::prelude::*; fn main() { if !rustix::process::geteuid().is_root() { panic!("ERROR: Access to /proc/iomem requires root, re-run with sudo"); } let page_size = procfs::page_size(); // /proc/iomem contain a list of memory mapping, but we're only interested in RAM mapping let iomem = procfs::iomem().expect("Can't open /proc/iomem"); let ram = iomem .iter() .filter_map(|(_, map)| if map.name == "System RAM" { Some(map) } else { None }); let mut kpagecount = procfs::KPageCount::new().expect("Can't open /proc/kpagecount"); for map in ram { println!("Found RAM here: 0x{:x}-0x{:x}", map.address.0, map.address.1); // Physical memory is divided into pages of `page_size` bytes (usually 4kiB) // Each page is referenced by its Page Fram Number (PFN) let (start_pfn, end_pfn) = map.get_range().get(); let page_references = kpagecount .get_count_in_range(start_pfn, end_pfn) .expect("Can't read from /proc/kpagecount"); // find the page with most references let (pfn, refs) = page_references .iter() .enumerate() .max_by(|(_, a), (_, b)| a.cmp(b)) .unwrap(); println!( "Lots of references to this locations: addr=0x{:x}, pfn={}, refs={}", pfn * page_size as usize, pfn, refs ); } } procfs-0.17.0/examples/lslocks.rs000064400000000000000000000047121046102023000150470ustar 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(); println!("{:18}{:13}{:13}{:13}{:12} Path", "Process", "PID", "Lock Type", "Mode", "Kind"); println!("{}", "=".repeat(74)); 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.17.0/examples/lsmod.rs000064400000000000000000000017071046102023000145140ustar 00000000000000use procfs::prelude::*; use 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 procfs::KernelModules(modules) = Current::current().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.17.0/examples/mountinfo.rs000064400000000000000000000014671046102023000154170ustar 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.17.0/examples/mounts.rs000064400000000000000000000012561046102023000147220ustar 00000000000000// List mountpoints listed in /proc/mounts fn main() { let width = 15; for mount_entry in procfs::mounts().unwrap() { println!("Device: {}", mount_entry.fs_spec); println!("{:>width$}: {}", "Mount point", mount_entry.fs_file); println!("{:>width$}: {}","FS type", mount_entry.fs_vfstype); println!("{:>width$}: {}", "Dump", mount_entry.fs_freq); println!("{:>width$}: {}", "Check", mount_entry.fs_passno); print!("{:>width$}: ", "Options"); for (name, entry) in mount_entry.fs_mntops { if let Some(entry) = entry { print!("{name}: {entry} "); } } println!(""); } } procfs-0.17.0/examples/netstat.rs000064400000000000000000000033031046102023000150520ustar 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.17.0/examples/partitions.rs000064400000000000000000000002351046102023000155650ustar 00000000000000// List partitions listed in /proc/partitions fn main() { for part_entry in procfs::partitions().unwrap() { println!("{part_entry:?}"); } } procfs-0.17.0/examples/pfn.rs000064400000000000000000000045051046102023000141600ustar 00000000000000// // Print physical memory location for each page for each memory mapping // This requires CAP_SYS_ADMIN privilege, or root, otherwise physical memory addresses will be zero // // Vocabulary // VA = Virtual Address: memory address from a process point of view // VPN = Virtual Page Number: page number of the a Virtual Memory address // PA = Physical Address: memory address in physical memory // PFN = Page Frame Number: page number of a Physical Address // use procfs::process::MMapPath; use procfs::process::Process; fn main() { if !rustix::process::geteuid().is_root() { println!("WARNING: Access to /proc//pagemap requires root, re-run with sudo"); } let page_size = procfs::page_size(); let process = Process::myself().expect("Unable to load myself!"); let mut pagemap = process.pagemap().unwrap(); let mem_map = process.maps().unwrap(); for memory_map in mem_map { let va_start = memory_map.address.0; let va_end = memory_map.address.1; let vpn_start = (va_start / page_size) as usize; let vpn_end = (va_end / page_size) as usize; // can't scan Vsyscall, so skip it if memory_map.pathname == MMapPath::Vsyscall { continue; } println!("Memory mapping {:?}", memory_map); for vpn in vpn_start..vpn_end { let va = vpn * page_size as usize; let page_info = pagemap.get_info(vpn).unwrap(); match page_info { procfs::process::PageInfo::MemoryPage(memory_page) => { let pfn = memory_page.get_page_frame_number(); let pa = pfn.0 * page_size; println!("virt_mem: 0x{:x}, pfn: 0x{:x}, phys_addr: 0x{:x}", va, pfn, pa); } procfs::process::PageInfo::SwapPage(swap_page_flags) => { let swap_type = swap_page_flags.get_swap_type(); let swap_offset = swap_page_flags.get_swap_offset(); println!( "virt_mem: 0x{:x}, swap: {:}, offset: 0x{:x}", va, swap_type, swap_offset ); } } } } if !rustix::process::geteuid().is_root() { println!("\n\nWARNING: Access to /proc//pagemap requires root, re-run with sudo"); } } procfs-0.17.0/examples/pressure.rs000064400000000000000000000021071046102023000152410ustar 00000000000000use procfs::{prelude::*, CpuPressure, IoPressure, MemoryPressure, PressureRecord}; /// A basic example of /proc/pressure/ usage. fn main() { if let Ok(pressure) = MemoryPressure::current() { println!("Memory Pressure:"); println!("{:>10}:", "Some"); print_pressure(pressure.some, 20); println!("{:>10}:", "Full"); print_pressure(pressure.full, 20); } if let Ok(pressure) = CpuPressure::current() { println!("CPU Pressure:"); print_pressure(pressure.some, 20); } if let Ok(pressure) = IoPressure::current() { println!("IO Pressure:"); println!("{:>10}:", "Some"); print_pressure(pressure.some, 20); println!("{:>10}:", "Full"); print_pressure(pressure.full, 20); } } fn print_pressure(pressure: PressureRecord, width: usize) { println!("{:>width$}: {}", "Average 10", pressure.avg10); println!("{:>width$}: {}", "Average 60", pressure.avg60); println!("{:>width$}: {}", "Average 300", pressure.avg300); println!("{:>width$}: {}", "Total", pressure.total); } procfs-0.17.0/examples/process_hierarchy.rs000064400000000000000000000043771046102023000171200ustar 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.17.0/examples/process_kpageflags.rs000064400000000000000000000101411046102023000172300ustar 00000000000000// // Look for a value in the virtual memory of a process, and physical memory, then prints memory page details // This shows how to go from virtual address to mapping, and from mapping to physical address. // // This requires CAP_SYS_ADMIN privilege, or root // // Sample output: // // Virtual address of `variable`: 0x7ffd2de4708f // Found memory mapping // MemoryMap { address: (140725373272064, 140725373407232), perms: "rw-p", offset: 0, dev: (0, 0), inode: 0, pathname: Stack } // Found page // virt_mem: 0x7ffd2de47000, pfn: 0x107b06, phys_addr: 0x107b06000, flags: UPTODATE | LRU | MMAP | ANON | SWAPBACKED // use procfs::process::Process; use procfs::KPageFlags; fn main() { if !rustix::process::geteuid().is_root() { // KpageFlags::new().unwrap() will panic either way panic!("ERROR: Access to /proc/kpageflags requires root, re-run with sudo"); } let page_size = procfs::page_size(); // We will inspect this process's own memory let process = Process::myself().expect("Unable to load myself!"); let mut kpageflags = KPageFlags::new().expect("Can't open /proc/kpageflags"); let mut pagemap = process.pagemap().unwrap(); // The memory maps are read now, so the value we look for must already exist in RAM when we it this line // In this case it works, because the variables already exist in the executable // You probably want to put this right above the "for memory_map" loop let mem_map = process.maps().unwrap(); // We allocate memory for a value. This is a trick to get a semi random value // The goal is to find this value in physical memory let chrono = std::time::Instant::now(); let variable: u8 = chrono.elapsed().as_nanos() as u8; // We could do the same with a constant, the compiler will place this value in a different memory mapping with different properties //let constant = 42u8; // `ptr` is the virtual address we are looking for let ptr = &variable as *const u8; println!("Virtual address of `variable`: {:p}", ptr); for memory_map in mem_map { let mem_start = memory_map.address.0; let mem_end = memory_map.address.1; if (ptr as u64) < mem_start || (ptr as u64) >= mem_end { // pointer is not in this memory mapping continue; } // found the memory mapping where the value is stored println!("Found memory mapping\n{:?}", memory_map); // memory is split into pages (usually 4 kiB) let index_start = (mem_start / page_size) as usize; let index_end = (mem_end / page_size) as usize; for index in index_start..index_end { // we search for the exact page inside the memory mapping let virt_mem = index * page_size as usize; // ptr must be reside between this page and the next one if (ptr as usize) < virt_mem || (ptr as usize) >= virt_mem + page_size as usize { continue; } // we found the exact page where the value resides let page_info = pagemap.get_info(index).unwrap(); match page_info { procfs::process::PageInfo::MemoryPage(memory_page) => { let pfn = memory_page.get_page_frame_number(); let phys_addr = pfn.0 * page_size; let physical_page_info = kpageflags.get_info(pfn).expect("Can't get kpageflags info"); println!( "Found page\nvirt_mem: 0x{:x}, pfn: 0x{:x}, phys_addr: 0x{:x}, flags: {:?}", virt_mem, pfn, phys_addr, physical_page_info ); } procfs::process::PageInfo::SwapPage(swap_page_flags) => { let swap_type = swap_page_flags.get_swap_type(); let swap_offset = swap_page_flags.get_swap_offset(); println!( "Found page\nvirt_mem: 0x{:x}, swap_type: {:}, swap_offset: 0x{:x}, flags: {:?}", virt_mem, swap_type, swap_offset, swap_page_flags ); } } } } } procfs-0.17.0/examples/ps.rs000064400000000000000000000015541046102023000140200ustar 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(); 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.17.0/examples/self_memory.rs000064400000000000000000000033461046102023000157200ustar 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(); 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 * 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.17.0/examples/shm.rs000064400000000000000000000020201046102023000141520ustar 00000000000000extern crate procfs; use procfs::prelude::*; /// List processes using posix shared memory segments fn main() { let shared_memory_vec = procfs::SharedMemorySegments::current().unwrap(); for shared_memory in &shared_memory_vec.0 { 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 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.17.0/src/cgroups.rs000064400000000000000000000020371046102023000140260ustar 00000000000000use super::process::Process; use crate::{Current, ProcResult}; use procfs_core::CGroupControllers; pub use procfs_core::ProcessCGroups; impl Current for CGroupControllers { const PATH: &'static str = "/proc/cgroups"; } /// Information about the cgroup controllers that are compiled into the kernel /// /// (since Linux 2.6.24) pub fn cgroups() -> ProcResult { CGroupControllers::current() } 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 { self.read("cgroup") } } #[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.17.0/src/crypto.rs000064400000000000000000000007711046102023000136670ustar 00000000000000 use procfs_core::ProcResult; pub use procfs_core::CryptoTable; use crate::Current; impl Current for CryptoTable { const PATH: &'static str = "/proc/crypto"; } pub fn crypto() -> ProcResult { CryptoTable::current() } #[cfg(test)] mod tests { use super::*; #[test] fn read_crypto() { let table = crypto(); let table = table.expect("CrytoTable should have been read"); assert!(!table.crypto_blocks.is_empty(), "Crypto table was empty"); } }procfs-0.17.0/src/iomem.rs000064400000000000000000000006351046102023000134540ustar 00000000000000use super::ProcResult; use crate::Current; use procfs_core::{Iomem, PhysicalMemoryMap}; impl Current for Iomem { const PATH: &'static str = "/proc/iomem"; } /// Reads and parses the `/proc/iomem`, returning an error if there are problems. /// /// Requires root, otherwise every memory address will be zero. pub fn iomem() -> ProcResult> { Iomem::current().map(|v| v.0) } procfs-0.17.0/src/keyring.rs000064400000000000000000000016731046102023000140210ustar 00000000000000use super::{Current, ProcResult}; use procfs_core::keyring::*; use std::collections::HashMap; impl Current for Keys { const PATH: &'static str = "/proc/keys"; } /// Returns a list of the keys for which the reading thread has **view** permission, providing /// various information about each key. pub fn keys() -> ProcResult> { Keys::current().map(|k| k.0) } impl Current for KeyUsers { const PATH: &'static str = "/proc/key-users"; } /// Get various information for each user ID that has at least one key on the system. pub fn key_users() -> ProcResult> { KeyUsers::current().map(|k| k.0) } #[cfg(test)] mod tests { use super::*; #[test] fn test_keys() { for key in keys().unwrap() { println!("{:#?}", key); } } #[test] fn test_key_users() { for (_user, data) in key_users().unwrap() { println!("{:#?}", data); } } } procfs-0.17.0/src/kpagecount.rs000064400000000000000000000045371046102023000145130ustar 00000000000000use std::{ io::{BufReader, Read, Seek, SeekFrom}, mem::size_of, path::Path, }; use crate::{process::Pfn, FileWrapper}; use super::ProcResult; /// Parse physical memory references accessing `/proc/kpagecount` /// /// Require root or CAP_SYS_ADMIN pub struct KPageCount { reader: BufReader, } impl KPageCount { /// Get a parser from default `/proc/kpagecount` /// /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN pub fn new() -> ProcResult { Self::from_custom_root("/proc") } /// Get a parser from custom `/proc` /// /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN pub fn from_custom_root>(root: P) -> ProcResult { let mut path = root.as_ref().to_path_buf(); path.push("kpagecount"); let reader = BufReader::new(FileWrapper::open(path)?); Ok(Self { reader }) } /// Get the number of references to physical memory at `pfn` /// /// Return Err if pfn is not in RAM. See [crate::iomem()] for a list of valid physical RAM addresses /// /// See [crate::process::Process::pagemap] and [crate::process::MemoryPageFlags::get_page_frame_number] pub fn get_count_at_pfn(&mut self, pfn: Pfn) -> ProcResult { self.get_count_in_range(pfn, Pfn(pfn.0 + 1)) .map(|mut vec| vec.pop().unwrap()) } /// Get the number of references to physical memory at for PFNs within `start` and `end` PFNs, `end` is excluded /// /// Return Err if any pfn is not in RAM. See [crate::iomem()] for a list of valid physical RAM addresses /// /// See [crate::process::Process::pagemap] and [crate::process::MemoryPageFlags::get_page_frame_number] pub fn get_count_in_range(&mut self, start: Pfn, end: Pfn) -> ProcResult> { let mut result = Vec::with_capacity((end.0 - start.0) as usize); let start_position = start.0 * size_of::() as u64; self.reader.seek(SeekFrom::Start(start_position))?; for _pfn in start.0..end.0 { // Each entry is a 64 bits counter let mut buf = [0; size_of::()]; self.reader.read_exact(&mut buf)?; let page_references: u64 = u64::from_le_bytes(buf); result.push(page_references); } Ok(result) } } procfs-0.17.0/src/kpageflags.rs000064400000000000000000000052671046102023000144600ustar 00000000000000use crate::{process::Pfn, FileWrapper, ProcResult}; use std::{ io::{BufReader, Read, Seek, SeekFrom}, mem::size_of, path::Path, }; pub use procfs_core::PhysicalPageFlags; /// Parse physical memory flags accessing `/proc/kpageflags`. /// /// Require root or CAP_SYS_ADMIN pub struct KPageFlags { reader: BufReader, } impl KPageFlags { /// Get a parser from default `/proc/kpageflags` /// /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN pub fn new() -> ProcResult { Self::from_custom_root("/proc") } /// Get a parser from custom `/proc` /// /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN pub fn from_custom_root>(root: P) -> ProcResult { let mut path = root.as_ref().to_path_buf(); path.push("kpageflags"); let reader = BufReader::new(FileWrapper::open(path)?); Ok(Self { reader }) } /// Retrieve information in the page table entry for the PFN (page frame number) at index `page_index`. /// If you need to retrieve multiple PFNs, opt for [Self::get_range_info()] instead. /// /// Return Err if the PFN is not in RAM (see [crate::iomem()]): /// Io(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }, None) pub fn get_info(&mut self, pfn: Pfn) -> ProcResult { self.get_range_info(pfn, Pfn(pfn.0 + 1)) .map(|mut vec| vec.pop().unwrap()) } /// Retrieve information in the page table entry for the PFNs within range `start` (included) and `end` (excluded) PFNs. /// /// Return Err if any PFN is not in RAM (see [crate::iomem()]): /// Io(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }, None) pub fn get_range_info(&mut self, start: Pfn, end: Pfn) -> ProcResult> { let start_position = start.0 * size_of::() as u64; self.reader.seek(SeekFrom::Start(start_position))?; let mut page_infos = Vec::with_capacity((end.0 - start.0) as usize); for _ in start.0..end.0 { let mut info_bytes = [0; size_of::()]; self.reader.read_exact(&mut info_bytes)?; page_infos.push(PhysicalPageFlags::parse_info(u64::from_ne_bytes(info_bytes))); } Ok(page_infos) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_kpageflags_parsing() { let pagemap_entry: u64 = 0b0000000000000000000000000000000000000000000000000000000000000001; let info = PhysicalPageFlags::parse_info(pagemap_entry); assert!(info == PhysicalPageFlags::LOCKED); } } procfs-0.17.0/src/lib.rs000064400000000000000000000741701046102023000131210ustar 00000000000000#![allow(unknown_lints)] // The suggested fix with `str::parse` removes support for Rust 1.48 #![allow(clippy::from_str_radix_10)] #![deny(rustdoc::broken_intra_doc_links, rustdoc::invalid_html_tags)] //! 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/procfs/examples) folder of the code repository. //! pub use procfs_core::*; use bitflags::bitflags; use rustix::fd::AsFd; use std::collections::HashMap; use std::fmt; use std::fs::File; use std::io::{self, BufReader, Read, Seek}; use std::path::{Path, PathBuf}; use std::str::FromStr; #[cfg(feature = "chrono")] use chrono::DateTime; const PROC_CONFIG_GZ: &str = "/proc/config.gz"; const BOOT_CONFIG: &str = "/boot/config"; /// Allows associating a specific file to parse. pub trait Current: FromRead { const PATH: &'static str; /// Parse the current value using the system file. fn current() -> ProcResult { Self::from_file(Self::PATH) } } /// A type for accessing data about the currently running machine /// /// For more details, see the [SystemInfoInterface] trait. pub struct LocalSystemInfo; impl SystemInfoInterface for LocalSystemInfo { fn boot_time_secs(&self) -> ProcResult { crate::boot_time_secs() } fn ticks_per_second(&self) -> u64 { crate::ticks_per_second() } fn page_size(&self) -> u64 { crate::page_size() } fn is_little_endian(&self) -> bool { u16::from_ne_bytes([0, 1]).to_le_bytes() == [0, 1] } } const LOCAL_SYSTEM_INFO: LocalSystemInfo = LocalSystemInfo; /// The current [SystemInfo]. pub fn current_system_info() -> &'static SystemInfo { &LOCAL_SYSTEM_INFO } /// Allows associating a specific file to parse with system information. pub trait CurrentSI: FromReadSI { const PATH: &'static str; /// Parse the current value using the system file and the current system info. fn current() -> ProcResult { Self::current_with_system_info(current_system_info()) } /// Parse the current value using the system file and the provided system info. fn current_with_system_info(si: &SystemInfo) -> ProcResult { Self::from_file(Self::PATH, si) } } /// Allows `impl WithSystemInfo` to use the current system info. pub trait WithCurrentSystemInfo<'a>: WithSystemInfo<'a> + Sized { /// Get the value using the current system info. fn get(self) -> Self::Output { self.with_system_info(current_system_info()) } } impl<'a, T: WithSystemInfo<'a>> WithCurrentSystemInfo<'a> for T {} /// Extension traits useful for importing wholesale. pub mod prelude { pub use super::{Current, CurrentSI, WithCurrentSystemInfo}; pub use procfs_core::prelude::*; } 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: e.into(), }, )) } } }; } pub(crate) fn read_file>(path: P) -> ProcResult { std::fs::read_to_string(path.as_ref()) .map_err(|e| e.into()) .error_path(path.as_ref()) } pub(crate) fn write_file, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()> { std::fs::write(path.as_ref(), buf) .map_err(|e| e.into()) .error_path(path.as_ref()) } pub(crate) fn read_value(path: P) -> ProcResult where P: AsRef, T: FromStr, ProcError: From, { read_file(path).and_then(|s| s.trim().parse().map_err(ProcError::from)) } pub(crate) fn write_value, T: fmt::Display>(path: P, value: T) -> ProcResult<()> { write_file(path, value.to_string().as_bytes()) } mod cgroups; pub use crate::cgroups::*; mod crypto; pub use crate::crypto::*; pub mod keyring; mod iomem; pub use iomem::*; mod kpageflags; pub use kpageflags::*; mod kpagecount; pub use kpagecount::*; pub mod net; pub mod process; 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; /// 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)) } } /// 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() -> u64 { rustix::param::clock_ticks_per_second() } /// 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()?; let date_time = expect!(chrono::Local.timestamp_opt(secs as i64, 0).single()); Ok(date_time) } /// 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 { // KernelStats doesn't call `boot_time_secs()`, so it's safe to call // KernelStats::current() (which uses the local system info). let stat = KernelStats::current()?; *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() -> u64 { rustix::param::page_size() as u64 } impl Current for LoadAverage { const PATH: &'static str = "/proc/loadavg"; } impl Current for KernelConfig { const PATH: &'static str = PROC_CONFIG_GZ; /// 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")] fn current() -> 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(decoder) } #[cfg(not(feature = "flate2"))] { unreachable!("flate2 feature not enabled") } } else { let kernel = rustix::system::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(file) } _ => return Err(e.into()), }, } }; Self::from_read(reader) } } /// 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). pub fn kernel_config() -> ProcResult> { KernelConfig::current().map(|c| c.0) } impl CurrentSI for KernelStats { const PATH: &'static str = "/proc/stat"; } impl Current for VmStat { const PATH: &'static str = "/proc/vmstat"; } /// 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> { VmStat::current().map(|s| s.0) } impl Current for KernelModules { const PATH: &'static str = "/proc/modules"; } /// Get a list of loaded kernel modules /// /// This corresponds to the data in `/proc/modules`. pub fn modules() -> ProcResult> { KernelModules::current().map(|m| m.0) } impl Current for KernelCmdline { const PATH: &'static str = "/proc/cmdline"; } /// 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> { KernelCmdline::current().map(|c| c.0) } impl Current for CpuInfo { const PATH: &'static str = "/proc/cpuinfo"; } impl Current for Devices { const PATH: &'static str = "/proc/devices"; } impl Current for DiskStats { const PATH: &'static str = "/proc/diskstats"; } /// Get disk IO stat info from /proc/diskstats pub fn diskstats() -> ProcResult> { DiskStats::current().map(|d| d.0) } impl Current for Vec { const PATH: &'static str = "/proc/mounts"; } /// Get a list of mountpoints from `/proc/mounts` pub fn mounts() -> ProcResult> { Vec::::current() } impl Current for Vec { const PATH: &'static str = "/proc/partitions"; } /// Get a list of partitions from `/proc/partitions` pub fn partitions() -> ProcResult> { Vec::::current() } impl Current for Locks { const PATH: &'static str = "/proc/locks"; } /// 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> { Locks::current().map(|l| l.0) } impl Current for Meminfo { const PATH: &'static str = "/proc/meminfo"; } impl Current for CpuPressure { const PATH: &'static str = "/proc/pressure/cpu"; } impl Current for MemoryPressure { const PATH: &'static str = "/proc/pressure/memory"; } impl Current for IoPressure { const PATH: &'static str = "/proc/pressure/io"; } impl Current for SharedMemorySegments { const PATH: &'static str = "/proc/sysvipc/shm"; } impl Current for Uptime { const PATH: &'static str = "/proc/uptime"; } #[cfg(test)] mod tests { use super::*; #[test] fn test_statics() { println!("{:?}", crate::sys::kernel::Version::cached()); } #[test] fn test_loadavg() { let load = LoadAverage::current().unwrap(); println!("{:?}", load); } #[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 = KernelConfig::current().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_kernel_stat() { let stat = KernelStats::current().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::current().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::current().unwrap(); println!("{:?}", stat); } #[test] fn test_modules() { let KernelModules(mods) = KernelModules::current().unwrap(); for module in mods.values() { println!("{:?}", module); } } #[test] fn tests_tps() { let tps = ticks_per_second(); println!("{} ticks per second", tps); } #[test] fn test_cmdline() { let KernelCmdline(cmdline) = KernelCmdline::current().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::current()?; 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 } /// Test that an ESRCH error gets mapped into a ProcError::NotFound #[test] fn test_esrch() { let mut command = std::process::Command::new("sleep") .arg("10000") .spawn() .expect("Failed to start sleep"); let p = crate::process::Process::new(command.id() as i32).expect("Failed to create Process"); command.kill().expect("Failed to kill sleep"); command.wait().expect("Failed to wait for sleep"); let e = p.stat().unwrap_err(); println!("{:?}", e); assert!(matches!(e, ProcError::NotFound(_))); } #[test] fn test_cpuinfo() { let info = CpuInfo::current().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_devices() { let devices = Devices::current().unwrap(); println!("{:#?}", devices); } #[test] fn test_diskstats() { for disk in super::diskstats().unwrap() { println!("{:?}", disk); } } #[test] fn test_locks() { 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); } } } #[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 = KernelConfig::current().ok(); let meminfo = Meminfo::current().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(KernelConfig(ref config)) = config { assert!(config.get("CONFIG_UNEVICTABLE_LRU").is_some()); } } if kernel >= KernelVersion::new(2, 6, 19) && config .as_ref() .map_or(false, |KernelConfig(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, |KernelConfig(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(), |KernelConfig(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, |KernelConfig(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, |KernelConfig(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, |KernelConfig(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, |KernelConfig(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, |KernelConfig(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, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) { assert!(meminfo.hugepages_surp.is_some()); } else { assert!(meminfo.hugepages_surp.is_none()); } } #[allow(clippy::manual_range_contains)] fn valid_percentage(value: f32) -> bool { value >= 0.00 && value < 100.0 } #[test] fn test_mem_pressure() { if !Path::new("/proc/pressure/memory").exists() { return; } let mem_psi = MemoryPressure::current().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::current().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::current().unwrap(); assert!(valid_percentage(cpu_psi.some.avg10)); assert!(valid_percentage(cpu_psi.some.avg60)); assert!(valid_percentage(cpu_psi.some.avg300)); } } procfs-0.17.0/src/net.rs000064400000000000000000000202471046102023000131350ustar 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 Ok(process) = p else { //! // process vanished //! continue; //! }; //! 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::ProcResult; use crate::{current_system_info, Current}; pub use procfs_core::net::*; use procfs_core::FromReadSI; use std::collections::HashMap; /// Reads the tcp socket table /// /// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::tcp()](crate::process::Process::tcp()) pub fn tcp() -> ProcResult> { TcpNetEntries::from_file("/proc/net/tcp", current_system_info()).map(|e| e.0) } /// Reads the tcp6 socket table /// /// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::tcp6()](crate::process::Process::tcp6()) pub fn tcp6() -> ProcResult> { TcpNetEntries::from_file("/proc/net/tcp6", current_system_info()).map(|e| e.0) } /// Reads the udp socket table /// /// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::udp()](crate::process::Process::udp()) pub fn udp() -> ProcResult> { UdpNetEntries::from_file("/proc/net/udp", current_system_info()).map(|e| e.0) } /// Reads the udp6 socket table /// /// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::udp6()](crate::process::Process::udp6()) pub fn udp6() -> ProcResult> { UdpNetEntries::from_file("/proc/net/udp6", current_system_info()).map(|e| e.0) } impl Current for UnixNetEntries { const PATH: &'static str = "/proc/net/unix"; } /// Reads the unix socket table /// /// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::unix()](crate::process::Process::unix()) pub fn unix() -> ProcResult> { UnixNetEntries::current().map(|e| e.0) } impl super::Current for ArpEntries { const PATH: &'static str = "/proc/net/arp"; } /// Reads the ARP table /// /// Note that this is the ARP table for the current progress. If you want to /// see the ARP table for another process, then see [Process::arp()](crate::process::Process::arp()) pub fn arp() -> ProcResult> { ArpEntries::current().map(|e| e.0) } impl super::Current for InterfaceDeviceStatus { const PATH: &'static str = "/proc/net/dev"; } /// 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. /// /// Note that this returns information from the networking namespace of the /// current process. If you want information for some otherr process, see /// [Process::dev_status()](crate::process::Process::dev_status()) pub fn dev_status() -> ProcResult> { InterfaceDeviceStatus::current().map(|e| e.0) } impl super::Current for RouteEntries { const PATH: &'static str = "/proc/net/route"; } /// Reads the ipv4 route table /// /// This data is from the `/proc/net/route` file /// /// Note that this returns information from the networking namespace of the /// current process. If you want information for some other process, see /// [Process::route()](crate::process::Process::route()) pub fn route() -> ProcResult> { RouteEntries::current().map(|r| r.0) } impl super::Current for Snmp { const PATH: &'static str = "/proc/net/snmp"; } /// Reads the network management information by Simple Network Management Protocol /// /// This data is from the `/proc/net/snmp` file and for IPv4 Protocol /// /// Note that this returns information from the networking namespace of the /// current process. If you want information for some other process, see /// [Process::snmp()](crate::process::Process::snmp()) pub fn snmp() -> ProcResult { Snmp::current() } impl super::Current for Snmp6 { const PATH: &'static str = "/proc/net/snmp6"; } /// Reads the network management information of IPv6 by Simple Network Management Protocol /// /// This data is from the `/proc/net/snmp6` file and for IPv6 Protocol /// /// Note that this returns information from the networking namespace of the /// current process. If you want information for some other process, see /// [Process::snmp6()](crate::process::Process::snmp6()) pub fn snmp6() -> ProcResult { Snmp6::current() } #[cfg(test)] mod tests { use super::*; #[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); } } #[test] fn test_snmp() { let snmp = snmp().unwrap(); println!("{:?}", snmp); } #[test] fn test_snmp6() { let snmp6 = snmp6().unwrap(); println!("{:?}", snmp6); } } procfs-0.17.0/src/process/mod.rs000064400000000000000000001117361046102023000146100ustar 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(); //! //! 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() { //! let Ok(prc) = prc else { //! // process vanished //! continue; //! }; //! let Ok(stat) = prc.stat() else { //! // process vanished //! continue; //! }; //! if let Ok(stat) = prc.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(); //! //! 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::net::{TcpNetEntry, UdpNetEntry}; use crate::sys::kernel::Version; pub use procfs_core::process::*; 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::os::unix::ffi::OsStringExt; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use std::str::FromStr; mod namespaces; pub use namespaces::*; mod task; pub use task::*; mod pagemap; pub use pagemap::*; #[cfg(test)] mod tests; 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))] #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] 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; } } /// 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); // for 2.6.39 <= kernel < 3.6 fstat doesn't support O_PATH see https://github.com/eminence/procfs/issues/265 let flags = match Version::cached() { Ok(v) if v < KernelVersion::new(3, 6, 0) => OFlags::NOFOLLOW | OFlags::CLOEXEC, Ok(_) => OFlags::NOFOLLOW | OFlags::PATH | OFlags::CLOEXEC, Err(_) => OFlags::NOFOLLOW | OFlags::PATH | OFlags::CLOEXEC, }; let file = wrap_io_error!(root, rustix::fs::openat(dirfd, p, flags, 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 { // for 2.6.39 <= kernel < 3.6 fstat doesn't support O_PATH see https://github.com/eminence/procfs/issues/265 let flags = match Version::cached() { Ok(v) if v < KernelVersion::new(3, 6, 0) => OFlags::DIRECTORY | OFlags::CLOEXEC, Ok(_) => OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC, Err(_) => OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC, }; let file = wrap_io_error!(root, rustix::fs::openat(rustix::fs::CWD, &root, flags, 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 { FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "io")?) } /// 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 { FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "maps")?) } /// 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 { FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "smaps")?) } /// 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 { FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "smaps_rollup")?) } /// Returns the [MountStat] data for this process's mount namespace. pub fn mountstats(&self) -> ProcResult { self.read("mountstats") } /// Returns info about the mountpoints in this this process's mount namespace. /// /// This data is taken from the `/proc/[pid]/mountinfo` file /// /// # 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 /// ); /// } /// ``` /// /// (Since Linux 2.6.26) pub fn mountinfo(&self) -> ProcResult { self.read("mountinfo") } /// 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 { // Use fast path if available (Linux v6.2): https://github.com/torvalds/linux/commit/f1f1f2569901 let stat = wrap_io_error!( self.root.join("fd"), rustix::fs::statat(&self.fd, "fd", AtFlags::empty()) )?; if stat.st_size > 0 { return Ok(stat.st_size as usize); } 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> { 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); let mut buf = 0usize.to_ne_bytes(); loop { file.read_exact(&mut buf)?; let key = usize::from_ne_bytes(buf) as u64; file.read_exact(&mut buf)?; let value = usize::from_ne_bytes(buf) 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 { self.read("status") } /// Returns the status info from `/proc/[pid]/stat`. pub fn stat(&self) -> ProcResult { self.read("stat") } /// Return the limits for this process pub fn limits(&self) -> ProcResult { self.read("limits") } /// 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. /// /// Values range from 0 (never kill) to 1000 (always kill) inclusive. /// /// (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!(u16, oom.trim())) } /// Adjust score value is added to the oom score before choosing processes to kill. /// /// Values range from -1000 (never kill) to 1000 (always kill) inclusive. pub fn oom_score_adj(&self) -> ProcResult { let mut file = FileWrapper::open_at(&self.root, &self.fd, "oom_score_adj")?; let mut oom = String::new(); file.read_to_string(&mut oom)?; Ok(from_str!(i16, oom.trim())) } pub fn set_oom_score_adj(&self, new_oom_score_adj: i16) -> ProcResult<()> { let path = self.root.join("oom_score_adj"); write_value(path, new_oom_score_adj) } /// Set process memory information /// /// Much of this data is the same as the data from `stat()` and `status()` pub fn statm(&self) -> ProcResult { self.read("statm") } /// 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 thread based on a specified TID 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 { self.read("schedstat") } /// 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 task_path = self.root.join("task"); let dir_fd = wrap_io_error!( &task_path, rustix::fs::openat( &self.fd, "task", OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, Mode::empty() ) )?; let dir = wrap_io_error!(&task_path, rustix::fs::Dir::read_from(&dir_fd))?; Ok(TasksIter { pid: self.pid, inner: dir, inner_fd: dir_fd, root: task_path, }) } /// Reads the tcp socket table from the process net namespace pub fn tcp(&self) -> ProcResult> { self.read_si("net/tcp").map(|net::TcpNetEntries(e)| e) } /// Reads the tcp6 socket table from the process net namespace pub fn tcp6(&self) -> ProcResult> { self.read_si("net/tcp6").map(|net::TcpNetEntries(e)| e) } /// Reads the udp socket table from the process net namespace pub fn udp(&self) -> ProcResult> { self.read_si("net/udp").map(|net::UdpNetEntries(e)| e) } /// Reads the udp6 socket table from the process net namespace pub fn udp6(&self) -> ProcResult> { self.read_si("net/udp6").map(|net::UdpNetEntries(e)| e) } /// Returns basic network device statistics for all interfaces in the process net namespace /// /// See also the [dev_status()](crate::net::dev_status()) function. pub fn dev_status(&self) -> ProcResult> { self.read("net/dev").map(|net::InterfaceDeviceStatus(e)| e) } /// Reads the unix socket table pub fn unix(&self) -> ProcResult> { self.read("net/unix").map(|net::UnixNetEntries(e)| e) } /// Reads the ARP table from the process net namespace pub fn arp(&self) -> ProcResult> { self.read("net/arp").map(|net::ArpEntries(e)| e) } /// Reads the ipv4 route table from the process net namespace pub fn route(&self) -> ProcResult> { self.read("net/route").map(|net::RouteEntries(e)| e) } /// Reads the network management information by Simple Network Management Protocol from the /// process net namespace pub fn snmp(&self) -> ProcResult { self.read("net/snmp") } /// Reads the network management information of IPv6 by Simple Network Management Protocol from /// the process net namespace pub fn snmp6(&self) -> ProcResult { self.read("net/snmp6") } /// 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()) } /// Parse a file relative to the process proc structure. pub fn read(&self, path: &str) -> ProcResult { FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, path)?) } /// Parse a file relative to the process proc structure. pub fn read_si(&self, path: &str) -> ProcResult { FromReadSI::from_read( FileWrapper::open_at(&self.root, &self.fd, path)?, crate::current_system_info(), ) } /// Clear reference bits /// /// See [ClearRefs] and [Process::pagemap()] pub fn clear_refs(&self, clear: ClearRefs) -> ProcResult<()> { write_value(self.root.join("clear_refs"), clear) } } /// 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 will be returned as an `Err(ProcError)` /// /// See also some important docs on the [ProcessesIter] struct. /// /// Error handling example /// ``` /// # use procfs::process::Process; /// let all_processes: Vec = procfs::process::all_processes() /// .expect("Can't read /proc") /// .filter_map(|p| match p { /// Ok(p) => Some(p), // happy path /// Err(e) => match e { /// procfs::ProcError::NotFound(_) => None, // process vanished during iteration, ignore it /// procfs::ProcError::Io(e, path) => None, // can match on path to decide if we can continue /// x => { /// println!("Can't read process due to error {x:?}"); // some unknown error /// None /// } /// }, /// }) /// .collect(); /// ``` pub fn all_processes() -> ProcResult { all_processes_with_root("/proc") } /// Return a list of all processes based on a specified `/proc` path /// /// See [all_processes] for details and examples /// /// 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()) { break Some(Process::new_with_root(self.root.join(pid.to_string()))); } } Some(Err(e)) => break Some(Err(io::Error::from(e).into())), None => break None, } } } } procfs-0.17.0/src/process/namespaces.rs000064400000000000000000000044451046102023000161460ustar 00000000000000use super::Process; use crate::{build_internal_error, ProcResult}; use procfs_core::process::{Namespace, Namespaces}; use rustix::fs::{AtFlags, Mode, OFlags}; use std::{collections::HashMap, ffi::OsString}; 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(namespaces)) } } #[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.17.0/src/process/pagemap.rs000064400000000000000000000043471046102023000154420ustar 00000000000000use crate::{FileWrapper, ProcResult}; use procfs_core::process::PageInfo; use std::{ io::{BufReader, Read, Seek, SeekFrom}, mem::size_of, ops::{Bound, RangeBounds}, }; impl super::Process { /// 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)) } } /// 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`. /// /// Some mappings are not accessible, and will return an Err: `vsyscall` 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() 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) } } procfs-0.17.0/src/process/task.rs000064400000000000000000000215361046102023000147710ustar 00000000000000use std::io::Read; use std::path::{Path, PathBuf}; use super::{FileWrapper, Io, Schedstat, Stat, Status}; use crate::{ProcError, ProcResult}; use procfs_core::FromRead; use rustix::fd::{BorrowedFd, OwnedFd}; /// 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 { self.read("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 { self.read("status") } /// Thread IO info from `/proc//task//io` /// /// This data will be unique per task. pub fn io(&self) -> ProcResult { self.read("io") } /// Thread scheduler info from `/proc//task//schedstat` /// /// This data will be unique per task. pub fn schedstat(&self) -> ProcResult { self.read("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() } /// Deliberately generate an IO error #[cfg(test)] pub(crate) fn generate_error(&self) -> ProcResult<()> { let _ = FileWrapper::open_at(&self.root, &self.fd, "does_not_exist")?; Ok(()) } /// Parse a file relative to the task proc structure. pub fn read(&self, path: &str) -> ProcResult { FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, path)?) } } #[cfg(test)] mod tests { use crate::process::Io; use crate::ProcError; 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()) 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(); } #[test] fn test_error_msg() { let myself = crate::process::Process::myself().unwrap(); // let mytask = myself.task_main_thread().unwrap(); for task in myself.tasks().unwrap() { let task = task.unwrap(); let err = task.generate_error().unwrap_err(); // make sure the contained path in the error is correct if let ProcError::NotFound(Some(p)) = err { assert!( p.display() .to_string() .ends_with(&format!("/task/{}/does_not_exist", task.tid)), "NotFound path({:?}) doesn't look right", p ); } else { panic!("Unexpected error from task.generate_error()"); } } } } procfs-0.17.0/src/process/tests.rs000064400000000000000000000566161046102023000152000ustar 00000000000000use super::*; use rustix::process::Resource; 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().get()); 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 = KernelConfig::current() .ok() .and_then(|KernelConfig(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().get().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(oom_score_adj) = check_unwrap(&prc, prc.oom_score_adj()) { assert!(oom_score_adj >= -1000 && oom_score_adj <= 1000); check_unwrap(&prc, prc.set_oom_score_adj(oom_score_adj)); } 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() 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_count_runsinglethread() { let myself = Process::myself().unwrap(); let before = myself.fd_count().unwrap(); let one = File::open("/proc/self").unwrap(); let two = File::open("/proc/self/status").unwrap(); let after = myself.fd_count().unwrap(); assert_eq!( before + 2, after, "opened two files and expected {} open fds, saw {}", before + 2, after ); drop(one); drop(two); let after_closing = myself.fd_count().unwrap(); assert_eq!(before, after_closing); } #[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()); } 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); } 33 => { println!("Base addr of vDSO: 0x{:x}", v); // confirm that this base addr from the aux vector matches what our maps file says: let maps = myself.maps().unwrap(); let vsdo = maps .iter() .find(|map| map.pathname == MMapPath::Vdso) .expect("Failed to find mapping for the vdso"); assert_eq!(vsdo.address.0, v); } k => println!("Unknown key {}: {:x}", k, v), } if k != 16 { // for reasons i do not understand, getauxval(AT_HWCAP) doesn't return the expected // value assert_eq!(v, unsafe { libc::getauxval(k) }); } } } #[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")); } #[test] fn test_network_stuff() { let myself = Process::myself().unwrap(); let _tcp = myself.tcp().unwrap(); let _tcp6 = myself.tcp().unwrap(); let _udp = myself.udp().unwrap(); let _udp6 = myself.udp6().unwrap(); let _arp = myself.arp().unwrap(); let _route = myself.route().unwrap(); let _dev = myself.dev_status().unwrap(); let _unix = myself.unix().unwrap(); } trait LimitValueAsLimit { fn as_limit(&self) -> Option; } impl LimitValueAsLimit for LimitValue { fn as_limit(&self) -> Option { match self { LimitValue::Unlimited => None, LimitValue::Value(v) => Some(*v), } } } #[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()); } #[test] fn test_mountinfo_live() { let me = Process::myself().unwrap(); let MountInfos(mounts) = me.mountinfo().unwrap(); println!("{:#?}", mounts); } #[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 MountStats(stats) = FromRead::from_file("/proc/self/mountstats").unwrap(); for stat in stats { println!("{:#?}", stat); if let Some(nfs) = stat.statistics { println!(" {:?}", nfs.server_caps().unwrap()); } } } #[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.17.0/src/sys/fs/binfmt_misc.rs000064400000000000000000000224231046102023000160650ustar 00000000000000use bitflags::bitflags; use std::path::Path; use crate::{build_internal_error, from_str, 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 #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] 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.17.0/src/sys/fs/epoll.rs000064400000000000000000000021211046102023000146770ustar 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.17.0/src/sys/fs/mod.rs000064400000000000000000000055401046102023000143530ustar 00000000000000//! This modules contains functions for kernel variables related to filesystems use crate::{expect, from_str, 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.17.0/src/sys/kernel/keys.rs000064400000000000000000000072611046102023000154210ustar 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.17.0/src/sys/kernel/mod.rs000064400000000000000000000456651046102023000152370ustar 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 std::sync::atomic::{AtomicU32, Ordering}; use bitflags::bitflags; use crate::{read_value, write_value, ProcError, ProcResult}; pub mod keys; pub mod random; /// Represents a kernel version, in major.minor.release version. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 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") } /// Cached version of the kernel version. pub(crate) fn cached() -> ProcResult { const SENTINEL: u32 = 0; static KERNEL: AtomicU32 = AtomicU32::new(SENTINEL); // Try to load the kernel version. let mut kernel = KERNEL.load(Ordering::Relaxed); // If we haven't loaded the kernel version yet, try to here. if kernel == SENTINEL { kernel = Self::current()?.to_u32(); // Try to store it in the cache. KERNEL.store(kernel, Ordering::Release); } Ok(Self::from_u32(kernel)) } /// Convert kernel version to an arbitrary `u32` value. fn to_u32(self) -> u32 { let [lo, hi] = u16::to_ne_bytes(self.patch); u32::from_ne_bytes([self.major, self.minor, lo, hi]) } /// Convert kernel version from an arbitrary `u32` value. fn from_u32(val: u32) -> Self { let [major, minor, lo, hi] = u32::to_ne_bytes(val); Self { major, minor, patch: u16::from_ne_bytes([lo, hi]) } } /// 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 #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] 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) = Version::cached() { 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.17.0/src/sys/kernel/random.rs000064400000000000000000000072471046102023000157320ustar 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 std::path::Path; const RANDOM_ROOT: &str = "/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(Path::new(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(Path::new(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(Path::new(RANDOM_ROOT).join("read_wakeup_threshold")) { Ok(val) => Ok(val), Err(err) => match err { ProcError::NotFound(_) => read_value(Path::new(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(Path::new(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(Path::new(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(Path::new(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.17.0/src/sys/mod.rs000064400000000000000000000007411046102023000137410ustar 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.17.0/src/sys/vm.rs000064400000000000000000000104201046102023000135770ustar 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); } } }