From decb25bbc139ecebe1841a50baaf6a1319345ff3 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 28 May 2023 17:19:03 +0200 Subject: [PATCH 01/96] initial commit Signed-off-by: Andrea Righi --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 2 files changed, 676 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public 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. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: 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 3 of the License, or + (at your option) any later version. + + This program 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 program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..d12fb437 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# virtme-ng-init +Fast init process for virtme-ng From 7f66e03c392fcf48b6f6ac19fd724f4b3174ae36 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sat, 27 May 2023 10:29:40 +0200 Subject: [PATCH 02/96] virtme-ng-init: initial implementation in Rust Signed-off-by: Andrea Righi --- .gitignore | 1 + Cargo.lock | 46 +++ Cargo.toml | 9 + src/main.rs | 773 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.rs | 115 ++++++++ 5 files changed, 944 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/utils.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..9b231fc8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "nix" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", +] + +[[package]] +name = "virtme-ng-init" +version = "0.1.0" +dependencies = [ + "nix", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..e81b8e80 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "virtme-ng-init" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nix = "0.19" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..2f98649e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: GPL-3.0 + +//! virtme-ng-init +//! +//! This program serves as an extremely lightweight init process for `virtme-ng` in order to speed +//! up boot time. +//! +//! Its primary purpose is to perform any necessary initialization in the virtualized environment, +//! such as mounting filesystems, starting essential services, and configuring the system before +//! handing over control to the main user-space processes (typicall a shell session). +//! +//! Author: Andrea Righi + +use libc::{uname, utsname}; +use nix::fcntl::{open, OFlag}; +use nix::libc; +use nix::sys::reboot; +use nix::sys::stat::Mode; +use nix::unistd::sethostname; +use std::collections::HashMap; +use std::env; +use std::ffi::CStr; +use std::fs::{File, OpenOptions}; +use std::io::{self, BufRead, BufReader, BufWriter, Write}; +use std::mem; +use std::os::unix::process::CommandExt; +use std::path::{Path, PathBuf}; +use std::process::{exit, id, Command, Stdio}; +use std::thread; +mod utils; + +struct MountInfo { + source: &'static str, + target: &'static str, + fs_type: &'static str, + flags: u64, + fsdata: &'static str, +} + +const KERNEL_MOUNTS: &[MountInfo] = &[ + MountInfo { + source: "sys", + target: "/sys", + fs_type: "sysfs", + flags: libc::MS_NOSUID | libc::MS_NOEXEC | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "proc", + target: "/proc", + fs_type: "proc", + flags: libc::MS_NOSUID | libc::MS_NOEXEC | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/tmp", + fs_type: "tmpfs", + flags: 0, + fsdata: "", + }, + MountInfo { + source: "devtmpfs", + target: "/dev", + fs_type: "devtmpfs", + flags: libc::MS_NOSUID | libc::MS_NOEXEC, + fsdata: "", + }, + MountInfo { + source: "configfs", + target: "/sys/kernel/config", + fs_type: "configfs", + flags: 0, + fsdata: "", + }, + MountInfo { + source: "debugfs", + target: "/sys/kernel/debug", + fs_type: "debugfs", + flags: 0, + fsdata: "", + }, + MountInfo { + source: "tracefs", + target: "/sys/kernel/tracing", + fs_type: "tracefs", + flags: 0, + fsdata: "", + }, + MountInfo { + source: "cgroup2", + target: "/sys/fs/cgroup", + fs_type: "cgroup2", + flags: 0, + fsdata: "", + }, +]; + +const SYSTEM_MOUNTS: &[MountInfo] = &[ + MountInfo { + source: "devpts", + target: "/dev/pts", + fs_type: "devpts", + flags: libc::MS_NOSUID | libc::MS_NOEXEC, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/dev/shm", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/var/log", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/var/tmp", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/var/spool/rsyslog", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/var/lib/portables", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/var/lib/machines", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/var/lib/private", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/var/lib/apt", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, + MountInfo { + source: "tmpfs", + target: "/var/cache", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, +]; + +fn check_init_pid() { + if id() != 1 { + utils::log(&format!("must be run as PID 1")); + exit(1); + } +} + +fn poweroff() { + unsafe { + libc::sync(); + } + if let Err(err) = reboot::reboot(reboot::RebootMode::RB_POWER_OFF) { + utils::log(&format!("error powering off: {}", err)); + exit(1); + } + exit(0); +} + +fn configure_environment() { + env::set_var("PATH", "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin"); +} + +fn get_kernel_version(show_machine: bool) -> String { + unsafe { + let mut utsname: utsname = mem::zeroed(); + if uname(&mut utsname) == -1 { + return String::from("None"); + } + let release = CStr::from_ptr(utsname.release.as_ptr()) + .to_string_lossy() + .into_owned(); + if show_machine { + let machine = CStr::from_ptr(utsname.machine.as_ptr()) + .to_string_lossy() + .into_owned(); + format!("{} {}", release, machine) + } else { + release + } + } +} + +fn get_active_console() -> Option { + // See Documentation/filesystems/proc.rst for /proc/consoles documentation. + match File::open("/proc/consoles") { + Ok(file) => { + let reader = BufReader::new(file); + + for line in reader.lines() { + if let Ok(line) = line { + if line.chars().nth(27) == Some('C') { + let console = line.split(' ').next()?.to_string(); + return Some(format!("/dev/{}", console)); + } + } + } + None + } + Err(error) => { + utils::log(&format!("failed to open /proc/consoles: {}", error)); + None + } + } +} + +fn configure_hostname() { + if let Ok(hostname) = env::var("virtme_hostname") { + if let Err(err) = sethostname(hostname) { + utils::log(&format!("failed to change hostname: {}", err)); + } + } else { + utils::log(&format!("virtme_hostname is not defined")); + } +} + +fn run_systemd_tmpfiles() { + let args: &[&str] = &[ + "--create", + "--boot", + "--exclude-prefix=/dev", + "--exclude-prefix=/root", + ]; + utils::run_cmd("systemd-tmpfiles", args); +} + +fn generate_fstab() -> io::Result<()> { + utils::do_touch("/tmp/fstab", 0o0664); + utils::do_mount("/tmp/fstab", "/etc/fstab", "", libc::MS_BIND, ""); + Ok(()) +} + +fn generate_shadow() -> io::Result<()> { + utils::do_touch("/tmp/shadow", 0o0644); + + let input_file = File::open("/etc/passwd")?; + let output_file = File::create("/tmp/shadow")?; + + let reader = BufReader::new(input_file); + let mut writer = BufWriter::new(output_file); + + for line in reader.lines() { + let line = line?; + let parts: Vec<&str> = line.split(':').collect(); + + if !parts.is_empty() { + let username = parts[0]; + writeln!(writer, "{}:!:::::::", username)?; + } + } + utils::do_mount("/tmp/shadow", "/etc/shadow", "", libc::MS_BIND, ""); + + Ok(()) +} + +fn generate_sudoers() -> io::Result<()> { + if let Ok(user) = env::var("virtme_user") { + let fname = "/tmp/sudoers"; + utils::do_touch(fname, 0o0440); + let mut file = File::create(fname)?; + let content = format!( + "root ALL = (ALL) NOPASSWD: ALL\n{} ALL = (ALL) NOPASSWD: ALL\n", + user + ); + file.write_all(content.as_bytes())?; + utils::do_mount(fname, "/etc/sudoers", "", libc::MS_BIND, ""); + } + Ok(()) +} + +fn override_system_files() { + generate_fstab().ok(); + generate_shadow().ok(); + generate_sudoers().ok(); +} + +fn set_cwd() { + if let Ok(dir) = env::var("virtme_chdir") { + if let Err(err) = env::set_current_dir(dir) { + utils::log(&format!("error changing directory: {}", err)); + } + } +} + +fn symlink_fds() { + let fd_links: HashMap<&str, &str> = vec![ + ("/proc/self/fd", "/dev/fd"), + ("/proc/self/fd/0", "/dev/stdin"), + ("/proc/self/fd/1", "/dev/stdout"), + ("/proc/self/fd/2", "/dev/stderr"), + ] + .into_iter() + .collect(); + + // Install /proc/self/fd symlinks into /dev if not already present. + for (src, dst) in fd_links.iter() { + if !std::path::Path::new(dst).exists() { + utils::do_symlink(src, dst); + } + } +} + +fn mount_kernel_filesystems() { + for mount_info in KERNEL_MOUNTS { + utils::do_mount( + mount_info.source, + mount_info.target, + mount_info.fs_type, + mount_info.flags, + mount_info.fsdata, + ) + } +} + +fn mount_virtme_overlays() { + for (key, path) in env::vars() { + if key.starts_with("virtme_rw_overlay") { + let dir = &format!("/tmp/{}", key); + let upperdir = &format!("{}/upper", dir); + let workdir = &format!("{}/work", dir); + let mnt_opts = &format!( + "xino=off,lowerdir={},upperdir={},workdir={}", + path, upperdir, workdir + ); + utils::do_mkdir(dir); + utils::do_mkdir(upperdir); + utils::do_mkdir(workdir); + utils::do_mount(&key, &path, "overlay", 0, mnt_opts); + } + } +} + +fn mount_virtme_initmounts() { + for (key, path) in env::vars() { + if key.starts_with("virtme_initmount") { + utils::do_mkdir(&path); + utils::do_mount( + &key.replace("_", "."), + &path, + "9p", + 0, + "version=9p2000.L,trans=virtio,access=any", + ); + } + } +} + +fn mount_kernel_modules() { + let kver = get_kernel_version(false); + let mod_dir = format!("/lib/modules/{}", kver); + + if env::var("virtme_root_mods").is_ok() { + // /lib/modules is already set up. + } else if let Ok(dir) = env::var("virtme_link_mods") { + utils::do_mount("none", "/lib/modules/", "tmpfs", 0, ""); + utils::do_symlink(&dir, &mod_dir); + } else if Path::new(&mod_dir).exists() { + // We have mismatched modules. Mask them off. + utils::do_mount("disallow_kmod", &mod_dir, "tmpfs", 0, "ro,mode=0000"); + } +} + +fn mount_sys_filesystems() { + utils::do_mkdir("/dev/pts"); + utils::do_mkdir("/dev/shm"); + utils::do_mkdir("/run/dbus"); + + for mount_info in SYSTEM_MOUNTS { + utils::do_mount( + mount_info.source, + mount_info.target, + mount_info.fs_type, + mount_info.flags, + mount_info.fsdata, + ) + } +} + +fn fix_dpkg_locks() { + if !Path::new("/var/lib/dpkg").exists() { + return; + } + let lock_files = vec![ + "/var/lib/dpkg/lock", + "/var/lib/dpkg/lock-frontend", + "/var/lib/dpkg/triggers/Lock", + ]; + for path in &lock_files { + let fname = Path::new(path) + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or(""); + if fname.is_empty() { + continue; + } + let src_file = format!("/tmp/{}", fname); + utils::do_touch(&src_file, 0o0640); + utils::do_mount(&src_file, path, "", libc::MS_BIND, ""); + } +} + +fn fix_packaging_files() { + fix_dpkg_locks(); +} + +fn disable_uevent_helper() { + let uevent_helper_path = "/sys/kernel/uevent_helper"; + + if Path::new(uevent_helper_path).exists() { + // This kills boot performance. + utils::log("you have CONFIG_UEVENT_HELPER on, turn it off"); + let mut file = OpenOptions::new().write(true).open(uevent_helper_path).ok(); + match &mut file { + Some(file) => { + write!(file, "").ok(); + } + None => { + utils::log(&format!("error opening {}", uevent_helper_path)); + } + } + } +} + +fn find_udevd() -> Option { + let mut udevd = PathBuf::new(); + + if PathBuf::from("/usr/lib/systemd/systemd-udevd").exists() { + udevd = PathBuf::from("/usr/lib/systemd/systemd-udevd"); + } else if PathBuf::from("/lib/systemd/systemd-udevd").exists() { + udevd = PathBuf::from("/lib/systemd/systemd-udevd"); + } else if let Ok(path) = env::var("PATH") { + for dir in path.split(':') { + let udevd_path = PathBuf::from(dir).join("udevd"); + if udevd_path.exists() { + udevd = udevd_path; + break; + } + } + } + if udevd.exists() { + Some(udevd) + } else { + None + } +} + +fn run_udevd() -> Option> { + if let Some(udevd_path) = find_udevd() { + let handle = thread::spawn(move || { + disable_uevent_helper(); + let args: &[&str] = &["--daemon", "--resolve-names=never"]; + utils::run_cmd(&udevd_path.to_string_lossy(), args); + utils::log("triggering udev coldplug"); + utils::run_cmd("udevadm", &["trigger", "--type=subsystems", "--action=add"]); + utils::run_cmd("udevadm", &["trigger", "--type=devices", "--action=add"]); + utils::log("waiting for udev to settle"); + utils::run_cmd("udevadm", &["settle"]); + utils::log("udev is done"); + }); + Some(handle) + } else { + utils::log("unable to find udevd, skip udev."); + None + } +} + +fn get_guest_tools_dir() -> Option { + if let Ok(current_exe) = env::current_exe() { + if let Some(parent_dir) = current_exe.parent() { + if let Some(dir) = parent_dir.to_str() { + return Some(dir.to_string()); + } + } + } + None +} + +fn get_network_device() -> Option { + let virtio_net_dir = "/sys/bus/virtio/drivers/virtio_net"; + if let Ok(entries) = std::fs::read_dir(virtio_net_dir) { + // Sort and get the first entry in this directory + let mut sorted_entries: Vec<_> = entries + .filter_map(|entry| entry.ok()) + .map(|entry| entry.path()) + .filter(|path| path.is_dir()) + .collect(); + sorted_entries.sort(); + + if let Some(first_entry) = sorted_entries.first() { + let net_dir = first_entry.join("net"); + if let Ok(net_entries) = std::fs::read_dir(net_dir) { + if let Some(first_net_entry) = net_entries + .filter_map(|entry| entry.ok()) + .map(|entry| entry.file_name()) + .filter_map(|name| name.to_str().map(|s| s.to_owned())) + .next() + { + return Some(first_net_entry); + } + } + } + } + None +} + +fn setup_network() -> Option> { + utils::run_cmd("ip", &["link", "set", "dev", "lo", "up"]); + if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { + if cmdline.contains("virtme.dhcp") { + if let Some(guest_tools_dir) = get_guest_tools_dir() { + if let Some(network_device) = get_network_device() { + let handle = thread::spawn(move || { + let args = [ + "udhcpc", + "-i", + &network_device, + "-n", + "-q", + "-f", + "-s", + &format!("{}/virtme-udhcpc-script", guest_tools_dir), + ]; + utils::run_cmd("busybox", &args); + }); + return Some(handle); + } + } + } + } + None +} + +fn run_script() { + if !utils::is_file_executable("/run/virtme/data/script") { + return; + } + if !std::path::Path::new("/dev/virtio-ports/virtme.stdin").exists() + || !std::path::Path::new("/dev/virtio-ports/virtme.stdout").exists() + || !std::path::Path::new("/dev/virtio-ports/virtme.stderr").exists() + || !std::path::Path::new("/dev/virtio-ports/virtme.dev_stdout").exists() + || !std::path::Path::new("/dev/virtio-ports/virtme.dev_stderr").exists() + { + utils::log( + "virtme-init: cannot find script I/O ports; make sure virtio-serial is available", + ); + } else { + // Re-create stdout/stderr to connect to the virtio-serial ports. + let io_files: HashMap<&str, &str> = vec![ + ("/dev/virtio-ports/virtme.dev_stdin", "/dev/stdin"), + ("/dev/virtio-ports/virtme.dev_stdout", "/dev/stdout"), + ("/dev/virtio-ports/virtme.dev_stderr", "/dev/stderr"), + ] + .into_iter() + .collect(); + for (src, dst) in io_files.iter() { + if std::path::Path::new(dst).exists() { + utils::do_unlink(dst); + } + utils::do_symlink(src, dst); + } + + // Detach the process from the controlling terminal + let flags = libc::O_RDWR; + let mode = Mode::empty(); + let tty_in = open( + "/dev/virtio-ports/virtme.stdin", + OFlag::from_bits_truncate(flags), + mode, + ) + .expect("failed to open console."); + let tty_out = open( + "/dev/virtio-ports/virtme.stdout", + OFlag::from_bits_truncate(flags), + mode, + ) + .expect("failed to open console."); + let tty_err = open( + "/dev/virtio-ports/virtme.stderr", + OFlag::from_bits_truncate(flags), + mode, + ) + .expect("failed to open console."); + + clear_virtme_envs(); + unsafe { + Command::new("/run/virtme/data/script") + .pre_exec(move || { + nix::libc::setsid(); + libc::close(libc::STDIN_FILENO); + libc::close(libc::STDOUT_FILENO); + libc::close(libc::STDERR_FILENO); + // Make stdin a controlling tty. + let stdin_fd = libc::dup2(tty_in, libc::STDIN_FILENO); + nix::libc::ioctl(stdin_fd, libc::TIOCSCTTY, 1); + libc::dup2(tty_out, libc::STDOUT_FILENO); + libc::dup2(tty_err, libc::STDERR_FILENO); + Ok(()) + }) + .output() + .expect("Failed to execute script"); + } + } + poweroff(); +} + +fn setup_root_home() { + utils::do_mkdir("/tmp/roothome"); + utils::do_mount("/tmp/roothome", "/root", "", libc::MS_BIND, ""); + env::set_var("HOME", "/tmp/roothome"); +} + +fn clear_virtme_envs() { + // Parameters that start with virtme_* shouldn't pollute the environment. + for (key, _) in env::vars() { + if key.starts_with("virtme_") { + env::remove_var(key); + } + } +} + +fn configure_terminal(consdev: &str) { + if let Ok(params) = env::var("virtme_stty_con") { + let output = Command::new("stty") + .args(params.split_whitespace()) + .stdin(std::fs::File::open(consdev).unwrap()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + // Replace the current init process with a shell session. + .output(); + utils::log(String::from_utf8_lossy(&output.unwrap().stderr).trim_end_matches('\n')); + } +} + +fn detach_from_terminal(tty_fd: libc::c_int) { + // Detach the process from the controlling terminal + unsafe { + nix::libc::setsid(); + libc::close(libc::STDIN_FILENO); + libc::close(libc::STDOUT_FILENO); + libc::close(libc::STDERR_FILENO); + let stdin_fd = libc::dup2(tty_fd, libc::STDIN_FILENO); + nix::libc::ioctl(stdin_fd, libc::TIOCSCTTY, 1); + libc::dup2(tty_fd, libc::STDOUT_FILENO); + libc::dup2(tty_fd, libc::STDERR_FILENO); + } +} + +fn run_shell() { + if let Some(consdev) = get_active_console() { + configure_terminal(consdev.as_str()); + + let flags = libc::O_RDWR | libc::O_NONBLOCK; + let mode = Mode::empty(); + let tty_fd = open(consdev.as_str(), OFlag::from_bits_truncate(flags), mode) + .expect("Failed to open console."); + + let mut args: Vec<&str> = vec!["-l"]; + let user_cmd: String; + + if let Ok(user) = env::var("virtme_user") { + user_cmd = format!("su {}", user); + args.push("-c"); + args.push(&user_cmd); + } + + clear_virtme_envs(); + unsafe { + Command::new("bash") + .args(args) + .pre_exec(move || { + detach_from_terminal(tty_fd); + Ok(()) + }) + .output() + .expect("Failed to start shell session"); + } + } else { + utils::log("Failed to determine console"); + Command::new("bash").arg("-l").exec(); + } +} + +fn print_logo() { + let logo = r#" + _ _ + __ _(_)_ __| |_ _ __ ___ ___ _ __ __ _ + \ \ / / | __| __| _ _ \ / _ \_____| _ \ / _ | + \ V /| | | | |_| | | | | | __/_____| | | | (_| | + \_/ |_|_| \__|_| |_| |_|\___| |_| |_|\__ | + |___/"#; + println!("{}", logo.trim_start_matches("\n")); + println!(" kernel version: {}\n", get_kernel_version(true)); +} + +fn start_session() { + utils::log("initialization done"); + print_logo(); + setup_root_home(); + run_shell(); +} + +fn run_misc_services() -> Option> { + let handle = thread::spawn(move || { + symlink_fds(); + mount_virtme_initmounts(); + fix_packaging_files(); + override_system_files(); + }); + Some(handle) +} + +fn main() { + // Make sure to always run as PID 1. + check_init_pid(); + + // Basic system initialization (order is important here). + configure_environment(); + configure_hostname(); + mount_kernel_filesystems(); + mount_virtme_overlays(); + mount_sys_filesystems(); + mount_kernel_modules(); + run_systemd_tmpfiles(); + + // Service initialization (some services can be parallelized here). + let mut handles: Vec>> = Vec::new(); + handles.push(run_udevd()); + handles.push(setup_network()); + handles.push(run_misc_services()); + + // Wait for the completion of the detached services. + for handle in handles.into_iter().flatten() { + handle.join().unwrap(); + } + + // Start user session (batch or interactive). + set_cwd(); + run_script(); + start_session(); + + // Shutdown the system. + poweroff(); +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..697c6110 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-3.0 + +//! virtme-ng-init: generic helper functions +//! +//! Author: Andrea Righi + +use nix::libc; +use nix::mount::{mount, MsFlags}; +use nix::sys::stat::Mode; +use std::ffi::CString; +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::os::unix::fs; +use std::os::unix::fs::PermissionsExt; +use std::process::{Command, Stdio}; + +static PROG_NAME: &'static str = "virtme-ng-init"; + +pub fn log(msg: &str) { + if msg.is_empty() { + return; + } + let msg = format!("{}: {}", PROG_NAME, msg.trim_end_matches('\n')); + let mut file = OpenOptions::new().write(true).open("/dev/kmsg").ok(); + match &mut file { + Some(file) => { + let msg = format!("<6>{}\n", msg); + file.write(msg.as_bytes()).ok(); + } + None => { + println!("{}", msg); + } + } +} + +pub fn do_mkdir(path: &str) { + let dmask = libc::S_IRWXU | libc::S_IRGRP | libc::S_IXGRP | libc::S_IROTH | libc::S_IXOTH; + nix::unistd::mkdir(path, Mode::from_bits_truncate(dmask as u32)).ok(); +} + +pub fn do_touch(path: &str, mode: u32) { + fn _do_touch(path: &str, mode: u32) -> std::io::Result<()> { + let file = File::create(path)?; + let permissions = std::fs::Permissions::from_mode(mode); + file.set_permissions(permissions)?; + + Ok(()) + } + if let Err(err) = _do_touch(path, mode) { + log(&format!("error creating file: {}", err)); + } +} + +pub fn do_unlink(path: &str) { + match std::fs::remove_file(path) { + Ok(_) => (), + Err(err) => { + log(&format!("failed to unlink file {}: {}", path, err)); + } + } +} + +pub fn do_symlink(src: &str, dst: &str) { + match fs::symlink(src, dst) { + Ok(_) => (), + Err(err) => { + log(&format!( + "failed to create symlink {} -> {}: {}", + src, dst, err + )); + } + } +} + +pub fn is_file_executable(file_path: &str) -> bool { + if let Ok(metadata) = std::fs::metadata(file_path) { + let permissions = metadata.permissions(); + permissions.mode() & 0o111 != 0 + } else { + false + } +} + +pub fn do_mount(source: &str, target: &str, fstype: &str, flags: u64, fsdata: &str) { + let source_cstr = CString::new(source).expect("CString::new failed"); + let fstype_cstr = CString::new(fstype).expect("CString::new failed"); + let fsdata_cstr = CString::new(fsdata).expect("CString::new failed"); + + let result = mount( + Some(source_cstr.as_ref()), + target, + Some(fstype_cstr.as_ref()), + MsFlags::from_bits_truncate(flags), + Some(fsdata_cstr.as_ref()), + ); + if let Err(err) = result { + log(&format!( + "mount {} -> {}: {}", + source, + target, + err.to_string() + )); + } +} + +pub fn run_cmd(cmd: &str, args: &[&str]) { + let output = Command::new(cmd) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .ok(); + log(String::from_utf8_lossy(&output.unwrap().stderr).trim_end_matches('\n')); +} From 86829941fc95c1aa89fbf2525be0e42c3668bef3 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 30 May 2023 10:42:07 +0200 Subject: [PATCH 03/96] doc: add a proper README.md Signed-off-by: Andrea Righi --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d12fb437..1b9fc3b1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,59 @@ -# virtme-ng-init -Fast init process for virtme-ng +# virtme-ng-init: fast init process for virtme-ng + +virtme-ng-init is an extremely lightweight init process for virtme-ng [1] +implemented in Rust. + +Its primary goal is to speed up the boot time of virtme-ng instances. + +virtme-ng-init is able to perform any necessary initialization in the +virtualized environment, such as mounting filesystems, starting essential +services, and configuring the system before handing over control to the main +user-space processes (typicall a shell session). + +[1] https://github.com/arighi/virtme-ng + +# Result + + - virtme-init (bash implementation): +``` +$ time virtme-ng --exec 'uname -r' +6.4.0-rc3-virtme + +real 0m1.146s +user 0m0.829s +sys 0m1.048s + +$ time virtme-ng --net user --exec 'ip addr show dev eth0' +2: eth0: mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000 + link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff + inet 10.0.2.15/24 scope global eth0 + valid_lft forever preferred_lft forever + +real 0m1.282s +user 0m0.930s +sys 0m1.219s +``` + + - virtme-ng-init (Rust implementation): +``` +$ time virtme-ng --exec 'uname -r' +6.4.0-rc3-virtme + +real 0m0.906s +user 0m0.654s +sys 0m0.684s + +$ time virtme-ng --net user --exec 'ip addr show dev eth0' +2: eth0: mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000 + link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff + inet 10.0.2.15/24 scope global eth0 + valid_lft forever preferred_lft forever + +real 0m0.972s +user 0m0.736s +sys 0m0.795s +``` + +# Credits + +Author: Andrea Righi From a02fab733f3b76d8a1b40f66e54cc1944aedc2f3 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 30 May 2023 10:58:20 +0200 Subject: [PATCH 04/96] create rust.yml Setup basic building and testing automation. Signed-off-by: Andrea Righi --- .github/workflows/rust.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..31000a27 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose From e6814849b7e458d9c90af5ef19f84004426b97f8 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 31 May 2023 16:09:55 +0200 Subject: [PATCH 05/96] properly handle GUI support Allow to run graphical applications from virtme-ng. Signed-off-by: Andrea Righi --- src/main.rs | 129 ++++++++++++++++++++++++++++++--------------------- src/utils.rs | 26 +++++++---- 2 files changed, 95 insertions(+), 60 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2f98649e..269e2181 100644 --- a/src/main.rs +++ b/src/main.rs @@ -255,13 +255,13 @@ fn run_systemd_tmpfiles() { } fn generate_fstab() -> io::Result<()> { - utils::do_touch("/tmp/fstab", 0o0664); + utils::create_file("/tmp/fstab", 0o0664, "").ok(); utils::do_mount("/tmp/fstab", "/etc/fstab", "", libc::MS_BIND, ""); Ok(()) } fn generate_shadow() -> io::Result<()> { - utils::do_touch("/tmp/shadow", 0o0644); + utils::create_file("/tmp/shadow", 0o0644, "").ok(); let input_file = File::open("/etc/passwd")?; let output_file = File::create("/tmp/shadow")?; @@ -286,7 +286,7 @@ fn generate_shadow() -> io::Result<()> { fn generate_sudoers() -> io::Result<()> { if let Ok(user) = env::var("virtme_user") { let fname = "/tmp/sudoers"; - utils::do_touch(fname, 0o0440); + utils::create_file(fname, 0o0440, "").ok(); let mut file = File::create(fname)?; let content = format!( "root ALL = (ALL) NOPASSWD: ALL\n{} ALL = (ALL) NOPASSWD: ALL\n", @@ -424,7 +424,7 @@ fn fix_dpkg_locks() { continue; } let src_file = format!("/tmp/{}", fname); - utils::do_touch(&src_file, 0o0640); + utils::create_file(&src_file, 0o0640, "").ok(); utils::do_mount(&src_file, path, "", libc::MS_BIND, ""); } } @@ -560,10 +560,7 @@ fn setup_network() -> Option> { None } -fn run_script() { - if !utils::is_file_executable("/run/virtme/data/script") { - return; - } +fn run_user_script() { if !std::path::Path::new("/dev/virtio-ports/virtme.stdin").exists() || !std::path::Path::new("/dev/virtio-ports/virtme.stdout").exists() || !std::path::Path::new("/dev/virtio-ports/virtme.stderr").exists() @@ -630,7 +627,6 @@ fn run_script() { .expect("Failed to execute script"); } } - poweroff(); } fn setup_root_home() { @@ -675,58 +671,71 @@ fn detach_from_terminal(tty_fd: libc::c_int) { } } -fn run_shell() { - if let Some(consdev) = get_active_console() { - configure_terminal(consdev.as_str()); +fn run_shell(tty_fd: libc::c_int, args: Vec) { + unsafe { + Command::new("bash") + .args(args.into_iter()) + .pre_exec(move || { + detach_from_terminal(tty_fd); + Ok(()) + }) + .output() + .expect("Failed to start shell session"); + } +} - let flags = libc::O_RDWR | libc::O_NONBLOCK; - let mode = Mode::empty(); - let tty_fd = open(consdev.as_str(), OFlag::from_bits_truncate(flags), mode) - .expect("Failed to open console."); +fn run_user_gui(tty_fd: libc::c_int, app: &str) { + // Generate a bare minimum xinitrc + let xinitrc = "/tmp/.xinitrc"; + if let Err(err) = utils::create_file(xinitrc, 0o0644, &format!("exec {}", app)) { + utils::log(&format!("failed to generate {}: {}", xinitrc, err)); + return; + } + // Run graphical app using xinit directly + let mut args: Vec = vec!["-l".to_owned(), "-c".to_owned()]; + if let Ok(user) = env::var("virtme_user") { + args.push(format!("su - {} -c 'xinit /tmp/.xinitrc'", user)); + } else { + args.push("xinit /tmp/.xinitrc".to_owned()); + } + run_shell(tty_fd, args); +} - let mut args: Vec<&str> = vec!["-l"]; - let user_cmd: String; +fn run_user_shell(tty_fd: libc::c_int) { + let mut args: Vec = vec!["-l".to_owned()]; + if let Ok(user) = env::var("virtme_user") { + args.push("-c".to_owned()); + args.push(format!("su - {}", user)); + } + run_shell(tty_fd, args); +} - if let Ok(user) = env::var("virtme_user") { - user_cmd = format!("su {}", user); - args.push("-c"); - args.push(&user_cmd); +fn run_user_session() { + let consdev = match get_active_console() { + Some(console) => console, + None => { + utils::log("failed to determine console"); + Command::new("bash").arg("-l").exec(); + return; } + }; + configure_terminal(consdev.as_str()); - clear_virtme_envs(); - unsafe { - Command::new("bash") - .args(args) - .pre_exec(move || { - detach_from_terminal(tty_fd); - Ok(()) - }) - .output() - .expect("Failed to start shell session"); - } - } else { - utils::log("Failed to determine console"); - Command::new("bash").arg("-l").exec(); - } -} + let flags = libc::O_RDWR | libc::O_NONBLOCK; + let mode = Mode::empty(); + let tty_fd = open(consdev.as_str(), OFlag::from_bits_truncate(flags), mode) + .expect("failed to open console"); -fn print_logo() { - let logo = r#" - _ _ - __ _(_)_ __| |_ _ __ ___ ___ _ __ __ _ - \ \ / / | __| __| _ _ \ / _ \_____| _ \ / _ | - \ V /| | | | |_| | | | | | __/_____| | | | (_| | - \_/ |_|_| \__|_| |_| |_|\___| |_| |_|\__ | - |___/"#; - println!("{}", logo.trim_start_matches("\n")); - println!(" kernel version: {}\n", get_kernel_version(true)); + if let Ok(app) = env::var("virtme_graphics") { + run_user_gui(tty_fd, &app); + } + run_user_shell(tty_fd); } -fn start_session() { +fn setup_user_session() { utils::log("initialization done"); print_logo(); setup_root_home(); - run_shell(); } fn run_misc_services() -> Option> { @@ -739,6 +748,18 @@ fn run_misc_services() -> Option> { Some(handle) } +fn print_logo() { + let logo = r#" + _ _ + __ _(_)_ __| |_ _ __ ___ ___ _ __ __ _ + \ \ / / | __| __| _ _ \ / _ \_____| _ \ / _ | + \ V /| | | | |_| | | | | | __/_____| | | | (_| | + \_/ |_|_| \__|_| |_| |_|\___| |_| |_|\__ | + |___/"#; + println!("{}", logo.trim_start_matches("\n")); + println!(" kernel version: {}\n", get_kernel_version(true)); +} + fn main() { // Make sure to always run as PID 1. check_init_pid(); @@ -765,8 +786,12 @@ fn main() { // Start user session (batch or interactive). set_cwd(); - run_script(); - start_session(); + if utils::is_file_executable("/run/virtme/data/script") { + run_user_script(); + } else { + setup_user_session(); + run_user_session(); + } // Shutdown the system. poweroff(); diff --git a/src/utils.rs b/src/utils.rs index 697c6110..dbb13305 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,7 +9,7 @@ use nix::mount::{mount, MsFlags}; use nix::sys::stat::Mode; use std::ffi::CString; use std::fs::{File, OpenOptions}; -use std::io::Write; +use std::io::{self, Write}; use std::os::unix::fs; use std::os::unix::fs::PermissionsExt; use std::process::{Command, Stdio}; @@ -38,7 +38,16 @@ pub fn do_mkdir(path: &str) { nix::unistd::mkdir(path, Mode::from_bits_truncate(dmask as u32)).ok(); } -pub fn do_touch(path: &str, mode: u32) { +pub fn do_unlink(path: &str) { + match std::fs::remove_file(path) { + Ok(_) => (), + Err(err) => { + log(&format!("failed to unlink file {}: {}", path, err)); + } + } +} + +fn do_touch(path: &str, mode: u32) { fn _do_touch(path: &str, mode: u32) -> std::io::Result<()> { let file = File::create(path)?; let permissions = std::fs::Permissions::from_mode(mode); @@ -51,13 +60,14 @@ pub fn do_touch(path: &str, mode: u32) { } } -pub fn do_unlink(path: &str) { - match std::fs::remove_file(path) { - Ok(_) => (), - Err(err) => { - log(&format!("failed to unlink file {}: {}", path, err)); - } +pub fn create_file(fname: &str, mode: u32, content: &str) -> io::Result<()> { + do_touch(fname, mode); + if !content.is_empty() { + let mut file = File::create(fname)?; + file.write_all(content.as_bytes())?; } + + Ok(()) } pub fn do_symlink(src: &str, dst: &str) { From 4e7e53250070003c3ac746f4349a660a590ede69 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 1 Jun 2023 17:36:53 +0200 Subject: [PATCH 06/96] workaround to be able to start xinit directly from a console session Signed-off-by: Andrea Righi --- src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.rs b/src/main.rs index 269e2181..4204c94b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -694,6 +694,11 @@ fn run_user_gui(tty_fd: libc::c_int, app: &str) { // Run graphical app using xinit directly let mut args: Vec = vec!["-l".to_owned(), "-c".to_owned()]; if let Ok(user) = env::var("virtme_user") { + // Try to fix permissions on the virtual consoles, we are starting X + // directly here so we may need extra permissions on the tty devices. + utils::run_cmd("bash", &["-c", &format!("chown {} /dev/char/*", user)]); + + // Start xinit directly. args.push(format!("su - {} -c 'xinit /tmp/.xinitrc'", user)); } else { args.push("xinit /tmp/.xinitrc".to_owned()); From 8a4e63fa32bfa646e556f8b2135805ccd6dbd9a7 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 14 Jun 2023 07:08:54 +0200 Subject: [PATCH 07/96] doc: README.md: project merged into virtme-ng Update README.md with a note that this project has been merged into virtme-ng. Signed-off-by: Andrea Righi --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1b9fc3b1..bc21facb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# This project has been merged into virtme-ng + +This project has been merged into: https://github.com/arighi/virtme-ng. + +From now on all the development will continue in virtme-ng. + # virtme-ng-init: fast init process for virtme-ng virtme-ng-init is an extremely lightweight init process for virtme-ng [1] From 180cfab68782f32ce2db688ae02d59829204d842 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 5 Jun 2023 18:13:58 +0200 Subject: [PATCH 08/96] virtme-ng-init: preserve host path Always use `su` instead of `su -` when switching to the target user in the guest, to preserve the same path of the host (unless specified otherwise by command line arguments). Signed-off-by: Andrea Righi --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4204c94b..33133cf8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -699,7 +699,7 @@ fn run_user_gui(tty_fd: libc::c_int, app: &str) { utils::run_cmd("bash", &["-c", &format!("chown {} /dev/char/*", user)]); // Start xinit directly. - args.push(format!("su - {} -c 'xinit /tmp/.xinitrc'", user)); + args.push(format!("su {} -c 'xinit /tmp/.xinitrc'", user)); } else { args.push("xinit /tmp/.xinitrc".to_owned()); } @@ -710,7 +710,7 @@ fn run_user_shell(tty_fd: libc::c_int) { let mut args: Vec = vec!["-l".to_owned()]; if let Ok(user) = env::var("virtme_user") { args.push("-c".to_owned()); - args.push(format!("su - {}", user)); + args.push(format!("su {}", user)); } run_shell(tty_fd, args); } From 9c58811d84348ed3f1003f6fa234feffdb412de1 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 5 Jun 2023 16:21:46 +0200 Subject: [PATCH 09/96] virtme_ng_init: properly synchronize udev and network startup There is a potential race between udev and network startup, the latter could run too early, failing to detect a proper network interface and failing to setup networking. If that happens we need to wait a little bit to make sure udev has registered corrected the virtio network device, and try again to setup the network. Moreover, refactor the networking code a little bit to make it more readable. Signed-off-by: Andrea Righi --- src/main.rs | 57 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/main.rs b/src/main.rs index 33133cf8..efe32037 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process::{exit, id, Command, Stdio}; use std::thread; +use std::time::Duration; mod utils; struct MountInfo { @@ -496,7 +497,7 @@ fn run_udevd() -> Option> { fn get_guest_tools_dir() -> Option { if let Ok(current_exe) = env::current_exe() { - if let Some(parent_dir) = current_exe.parent() { + if let Some(parent_dir) = current_exe.parent()?.parent() { if let Some(dir) = parent_dir.to_str() { return Some(dir.to_string()); } @@ -505,32 +506,39 @@ fn get_guest_tools_dir() -> Option { None } +fn _get_network_device_from_entries(entries: std::fs::ReadDir) -> Option { + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if !path.is_dir() { + continue; + } + if let Ok(net_entries) = std::fs::read_dir(path.join("net")) { + for entry in net_entries { + if let Ok(entry) = entry { + let path = entry.path().file_name()?.to_string_lossy().to_string(); + return Some(path); + } + } + } + } + } + return None; +} + fn get_network_device() -> Option { let virtio_net_dir = "/sys/bus/virtio/drivers/virtio_net"; - if let Ok(entries) = std::fs::read_dir(virtio_net_dir) { - // Sort and get the first entry in this directory - let mut sorted_entries: Vec<_> = entries - .filter_map(|entry| entry.ok()) - .map(|entry| entry.path()) - .filter(|path| path.is_dir()) - .collect(); - sorted_entries.sort(); - - if let Some(first_entry) = sorted_entries.first() { - let net_dir = first_entry.join("net"); - if let Ok(net_entries) = std::fs::read_dir(net_dir) { - if let Some(first_net_entry) = net_entries - .filter_map(|entry| entry.ok()) - .map(|entry| entry.file_name()) - .filter_map(|name| name.to_str().map(|s| s.to_owned())) - .next() - { - return Some(first_net_entry); - } + loop { + match std::fs::read_dir(virtio_net_dir) { + Ok(entries) => { + return _get_network_device_from_entries(entries); + } + Err(_) => { + // Wait a bit to make sure virtio-net is properly registered in the system. + thread::sleep(Duration::from_secs_f32(0.25)); } } } - None } fn setup_network() -> Option> { @@ -538,12 +546,13 @@ fn setup_network() -> Option> { if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { if cmdline.contains("virtme.dhcp") { if let Some(guest_tools_dir) = get_guest_tools_dir() { - if let Some(network_device) = get_network_device() { + if let Some(network_dev) = get_network_device() { + utils::log(&format!("setting up network device {}", network_dev)); let handle = thread::spawn(move || { let args = [ "udhcpc", "-i", - &network_device, + &network_dev, "-n", "-q", "-f", From 5bcde6b753f068b38e62be2e52cec3903306f853 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 5 Jun 2023 10:14:26 +0200 Subject: [PATCH 10/96] virtme-ng: allow to run snaps inside virtme-ng instances Enable all the required (kernel and user-space) features to start snapd and execute snaps directly inside a virtme-ng instance. The snap support is activated only if snapd is installed in the host system. This won't introduce any additional overhead in systems that don't have snap available. In systems that have snapd available, virmte-ng will automatically start it at boot (tricking it a bit into believing that it's running in a standard systemd-enabled environment) and at this point we can start any snap as regular system binaries inside virtme-ng. This fixes issue #21. Open issues =========== The main issue at the moment is that the guest requires read access to /var/lib/snapd/state.json that has 0600 permissions by default. Since we are running the guest as unprivileged user, by default it won't have access to the snapd state, therefore snapd cannot be started in the guest. For this reason in order to properly support snapd inside virtme-ng we need to change the permissions mask of this file and make it readable by everyone (unless we figure out a better solution to this). virtme-ng will print a warning in this case, to inform the user to run chmod manually (sad...) to change the permission of state.json. However, with this change applied we can run any kind of snap inside virtme-ng, even graphic applications (using `--graphics`), such as videogames, web browsers, etc. Signed-off-by: Andrea Righi --- src/main.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.rs | 8 ++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index efe32037..69c003fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,6 +88,13 @@ const KERNEL_MOUNTS: &[MountInfo] = &[ flags: 0, fsdata: "", }, + MountInfo { + source: "securityfs", + target: "/sys/kernel/security", + fs_type: "securityfs", + flags: 0, + fsdata: "", + }, MountInfo { source: "cgroup2", target: "/sys/fs/cgroup", @@ -168,6 +175,13 @@ const SYSTEM_MOUNTS: &[MountInfo] = &[ flags: libc::MS_NOSUID | libc::MS_NODEV, fsdata: "", }, + MountInfo { + source: "tmpfs", + target: "/var/lib/snapd/cookie", + fs_type: "tmpfs", + flags: libc::MS_NOSUID | libc::MS_NODEV, + fsdata: "", + }, ]; fn check_init_pid() { @@ -752,12 +766,48 @@ fn setup_user_session() { setup_root_home(); } +fn run_snapd() { + // If snapd is present in the system try to start it, to properly support snaps. + let snapd_bin = "/usr/lib/snapd/snapd"; + if !Path::new(snapd_bin).exists() { + return; + } + let snapd_state = "/var/lib/snapd/state.json"; + if !Path::new(snapd_state).exists() { + return; + } + if !utils::check_file_permissions(snapd_state, 0o004) { + return; + } + if let Some(guest_tools_dir) = get_guest_tools_dir() { + utils::run_cmd(&format!("{}/virtme-snapd-script", guest_tools_dir), &[]); + } + Command::new(snapd_bin) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .ok(); + let snapd_apparmor_bin = "/usr/lib/snapd/snapd-apparmor"; + if Path::new(snapd_apparmor_bin).exists() { + Command::new(snapd_apparmor_bin) + .arg("start") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .output() + .ok(); + + } +} + fn run_misc_services() -> Option> { let handle = thread::spawn(move || { symlink_fds(); mount_virtme_initmounts(); fix_packaging_files(); override_system_files(); + run_snapd(); }); Some(handle) } diff --git a/src/utils.rs b/src/utils.rs index dbb13305..878d154f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -82,15 +82,19 @@ pub fn do_symlink(src: &str, dst: &str) { } } -pub fn is_file_executable(file_path: &str) -> bool { +pub fn check_file_permissions(file_path: &str, mask: u32) -> bool { if let Ok(metadata) = std::fs::metadata(file_path) { let permissions = metadata.permissions(); - permissions.mode() & 0o111 != 0 + permissions.mode() & mask != 0 } else { false } } +pub fn is_file_executable(file_path: &str) -> bool { + check_file_permissions(file_path, 0o111) +} + pub fn do_mount(source: &str, target: &str, fstype: &str, flags: u64, fsdata: &str) { let source_cstr = CString::new(source).expect("CString::new failed"); let fstype_cstr = CString::new(fstype).expect("CString::new failed"); From 71a4942ac4cd2f622cbede2acb93aec70e666e00 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 8 Jun 2023 08:39:58 +0200 Subject: [PATCH 11/96] virtme-ng: provide --enable-snaps Considering that snap support is still experimental, provide an option to enable it, instead of enabling it implicitly by default. Signed-off-by: Andrea Righi --- src/main.rs | 65 ++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/main.rs b/src/main.rs index 69c003fd..cf1dddf9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -767,37 +767,40 @@ fn setup_user_session() { } fn run_snapd() { - // If snapd is present in the system try to start it, to properly support snaps. - let snapd_bin = "/usr/lib/snapd/snapd"; - if !Path::new(snapd_bin).exists() { - return; - } - let snapd_state = "/var/lib/snapd/state.json"; - if !Path::new(snapd_state).exists() { - return; - } - if !utils::check_file_permissions(snapd_state, 0o004) { - return; - } - if let Some(guest_tools_dir) = get_guest_tools_dir() { - utils::run_cmd(&format!("{}/virtme-snapd-script", guest_tools_dir), &[]); - } - Command::new(snapd_bin) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .ok(); - let snapd_apparmor_bin = "/usr/lib/snapd/snapd-apparmor"; - if Path::new(snapd_apparmor_bin).exists() { - Command::new(snapd_apparmor_bin) - .arg("start") - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .output() - .ok(); - + if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { + if cmdline.contains("virtme.snapd") { + // If snapd is present in the system try to start it, to properly support snaps. + let snapd_bin = "/usr/lib/snapd/snapd"; + if !Path::new(snapd_bin).exists() { + return; + } + let snapd_state = "/var/lib/snapd/state.json"; + if !Path::new(snapd_state).exists() { + return; + } + if !utils::check_file_permissions(snapd_state, 0o004) { + return; + } + if let Some(guest_tools_dir) = get_guest_tools_dir() { + utils::run_cmd(&format!("{}/virtme-snapd-script", guest_tools_dir), &[]); + } + Command::new(snapd_bin) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .ok(); + let snapd_apparmor_bin = "/usr/lib/snapd/snapd-apparmor"; + if Path::new(snapd_apparmor_bin).exists() { + Command::new(snapd_apparmor_bin) + .arg("start") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .output() + .ok(); + } + } } } From 6e47e41719bc00027400d8c72a6cc4c72a25e4ea Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sat, 10 Jun 2023 21:13:20 +0200 Subject: [PATCH 12/96] virtme-ng-init: initialize XDG_RUNTIME_DIR XDG_RUNTIME_DIR is an environment variable that specifies the path to a directory where user-specific runtime files should be stored. It is defined by the X Desktop Group (XDG) Base Directory Specification, which is a set of guidelines for storing user-specific files in a standardized manner. Initialize this working directory and the corresponding environment variable when the graphic environment is enabled. This allows better compatibility with certain graphic applications (such as web browsers, videogames, etc.). Signed-off-by: Andrea Righi --- Cargo.toml | 3 ++- src/main.rs | 17 +++++++++++++++++ src/utils.rs | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e81b8e80..8a613130 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nix = "0.19" +nix = "0.21" +users = "0.11" diff --git a/src/main.rs b/src/main.rs index cf1dddf9..af10c045 100644 --- a/src/main.rs +++ b/src/main.rs @@ -707,13 +707,30 @@ fn run_shell(tty_fd: libc::c_int, args: Vec) { } } +fn init_xdg_runtime_dir() { + // Initialize XDG_RUNTIME_DIR (required to provide a better compatibility with graphic apps). + let mut uid = 0; + if let Ok(user) = env::var("virtme_user") { + if let Some(virtme_uid) = utils::get_user_id(&user) { + uid = virtme_uid; + } + } + let dir = format!("/run/user/{}", uid); + utils::do_mkdir(&dir); + utils::do_chown(&dir, uid, uid).ok(); + env::set_var("XDG_RUNTIME_DIR", dir); +} + fn run_user_gui(tty_fd: libc::c_int, app: &str) { + init_xdg_runtime_dir(); + // Generate a bare minimum xinitrc let xinitrc = "/tmp/.xinitrc"; if let Err(err) = utils::create_file(xinitrc, 0o0644, &format!("exec {}", app)) { utils::log(&format!("failed to generate {}: {}", xinitrc, err)); return; } + // Run graphical app using xinit directly let mut args: Vec = vec!["-l".to_owned(), "-c".to_owned()]; if let Ok(user) = env::var("virtme_user") { diff --git a/src/utils.rs b/src/utils.rs index 878d154f..f1dd1667 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,6 +7,8 @@ use nix::libc; use nix::mount::{mount, MsFlags}; use nix::sys::stat::Mode; +use nix::unistd::{chown, Gid, Uid}; +use users::{get_user_by_name}; use std::ffi::CString; use std::fs::{File, OpenOptions}; use std::io::{self, Write}; @@ -33,6 +35,20 @@ pub fn log(msg: &str) { } } +pub fn get_user_id(username: &str) -> Option { + if let Some(user) = get_user_by_name(username) { + return Some(user.uid()); + } + None +} + +pub fn do_chown(path: &str, uid: u32, gid: u32) -> io::Result<()> { + chown(path, Some(Uid::from_raw(uid)), Some(Gid::from_raw(gid))) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + + Ok(()) +} + pub fn do_mkdir(path: &str) { let dmask = libc::S_IRWXU | libc::S_IRGRP | libc::S_IXGRP | libc::S_IROTH | libc::S_IXOTH; nix::unistd::mkdir(path, Mode::from_bits_truncate(dmask as u32)).ok(); From 33b161ba798aa6027be6e9439cf443de63e12c65 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 8 Jun 2023 09:25:31 +0200 Subject: [PATCH 13/96] guest-tools: add a script to automatically start the sound subsystem Introduce helper script `virtme-sound-script` to guest tools to automatically detect and start a valid sound subsystem when sound is enabled (`--sound`). NOTE: only pipewire is supported for now, support for other sound subsystems can be added later modifying this script. Signed-off-by: Andrea Righi --- src/main.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index af10c045..42bad96a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -726,7 +726,17 @@ fn run_user_gui(tty_fd: libc::c_int, app: &str) { // Generate a bare minimum xinitrc let xinitrc = "/tmp/.xinitrc"; - if let Err(err) = utils::create_file(xinitrc, 0o0644, &format!("exec {}", app)) { + + // Check if we need to start the sound system. + let mut pre_exec_cmd: String = String::new(); + if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { + if cmdline.contains("virtme.sound") { + if let Some(guest_tools_dir) = get_guest_tools_dir() { + pre_exec_cmd = format!("{}/virtme-sound-script", guest_tools_dir); + } + } + } + if let Err(err) = utils::create_file(xinitrc, 0o0644, &format!("{}\nexec {}", pre_exec_cmd, app)) { utils::log(&format!("failed to generate {}: {}", xinitrc, err)); return; } From fbabf672fc7d9d4af8fe26dd821e3c63b6fae6ab Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 14 Jun 2023 19:13:04 +0200 Subject: [PATCH 14/96] virtme-ng: try to automatically fix permissions of snapd state.json Instead of showing a warning suggesting to run `chmod +r /var/lib/snapd/state.json` when snaps are enabled we may try to automatically give read access to state.json to the current user using POSIX ACL (via getfacl / setfacl). It's still potentially an unsafe operation in the system, so make sure to print a big warning before changing any permission. Even if it's still an ugly workaround to support snaps, it seems better than asking users to run a `chmod +r` on a system file. Signed-off-by: Andrea Righi --- src/main.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 42bad96a..bcf79325 100644 --- a/src/main.rs +++ b/src/main.rs @@ -805,9 +805,6 @@ fn run_snapd() { if !Path::new(snapd_state).exists() { return; } - if !utils::check_file_permissions(snapd_state, 0o004) { - return; - } if let Some(guest_tools_dir) = get_guest_tools_dir() { utils::run_cmd(&format!("{}/virtme-snapd-script", guest_tools_dir), &[]); } From d66f500c08c51d534630144962bcdbe9b3f14213 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 18 Jun 2023 16:41:24 +0200 Subject: [PATCH 15/96] virtme: init process refactoring Move all the commands required to initialize the system into virtme-ng-init (or virtme-init). Moreover, when running scripts inside the guest don't use /run as a temporary place to store the script that needs to be executed, use /mnt instead, because /run needs to be re-mounted with tmpfs by virtme-ng-init or virtme-init. This is the first step to provide an initramfs-less way of executing scripts inside the guest, that should help to improve boot time even more (for the script execution case). Signed-off-by: Andrea Righi --- src/main.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index bcf79325..3aed65f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,16 +40,16 @@ struct MountInfo { const KERNEL_MOUNTS: &[MountInfo] = &[ MountInfo { - source: "sys", - target: "/sys", - fs_type: "sysfs", + source: "proc", + target: "/proc", + fs_type: "proc", flags: libc::MS_NOSUID | libc::MS_NOEXEC | libc::MS_NODEV, fsdata: "", }, MountInfo { - source: "proc", - target: "/proc", - fs_type: "proc", + source: "sys", + target: "/sys", + fs_type: "sysfs", flags: libc::MS_NOSUID | libc::MS_NOEXEC | libc::MS_NODEV, fsdata: "", }, @@ -60,6 +60,13 @@ const KERNEL_MOUNTS: &[MountInfo] = &[ flags: 0, fsdata: "", }, + MountInfo { + source: "run", + target: "/run", + fs_type: "tmpfs", + flags: 0, + fsdata: "", + }, MountInfo { source: "devtmpfs", target: "/dev", @@ -633,7 +640,7 @@ fn run_user_script() { clear_virtme_envs(); unsafe { - Command::new("/run/virtme/data/script") + Command::new("/mnt/virtme/data/script") .pre_exec(move || { nix::libc::setsid(); libc::close(libc::STDIN_FILENO); @@ -877,7 +884,7 @@ fn main() { // Start user session (batch or interactive). set_cwd(); - if utils::is_file_executable("/run/virtme/data/script") { + if utils::is_file_executable("/mnt/virtme/data/script") { run_user_script(); } else { setup_user_session(); From fd6856423c4da79561cfe086584cc481edc6d4dc Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 18 Jun 2023 19:30:14 +0200 Subject: [PATCH 16/96] virtme-ng: pass --exec command via /proc/cmdline Instead creating an initramfs on-the-fly to store the --exec command(s) that need to be executed in the guest, pass them to the guest via /proc/cmdline, using the kernel boot parameter virtme.exec=``. This allows to save some extra boot time, because we don't have to create an initramfs on-the-fly on the host and use it to boot the guest. Signed-off-by: Andrea Righi --- src/main.rs | 34 +++++++++++++++++++++++++++++----- src/utils.rs | 13 ------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3aed65f7..73251c24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -590,7 +590,22 @@ fn setup_network() -> Option> { None } -fn run_user_script() { +fn extract_user_script(virtme_script: &str) -> Option { + let start_marker = "virtme.exec=`"; + let end_marker = "`"; + + if let Some(start_index) = virtme_script.find(start_marker) { + let start_index = start_index + start_marker.len(); + if let Some(end_index) = virtme_script[start_index..].find(end_marker) { + let cmd = &virtme_script[start_index..start_index + end_index]; + return Some(cmd.to_string()); + } + } + + None +} + +fn do_run_user_script(cmd: &str) { if !std::path::Path::new("/dev/virtio-ports/virtme.stdin").exists() || !std::path::Path::new("/dev/virtio-ports/virtme.stdout").exists() || !std::path::Path::new("/dev/virtio-ports/virtme.stderr").exists() @@ -640,7 +655,8 @@ fn run_user_script() { clear_virtme_envs(); unsafe { - Command::new("/mnt/virtme/data/script") + Command::new("/bin/sh") + .args(["-c", cmd]) .pre_exec(move || { nix::libc::setsid(); libc::close(libc::STDIN_FILENO); @@ -659,6 +675,16 @@ fn run_user_script() { } } +fn run_user_script() -> bool { + if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { + if let Some(cmd) = extract_user_script(&cmdline) { + do_run_user_script(&cmd); + return true; + } + } + return false; +} + fn setup_root_home() { utils::do_mkdir("/tmp/roothome"); utils::do_mount("/tmp/roothome", "/root", "", libc::MS_BIND, ""); @@ -884,9 +910,7 @@ fn main() { // Start user session (batch or interactive). set_cwd(); - if utils::is_file_executable("/mnt/virtme/data/script") { - run_user_script(); - } else { + if !run_user_script() { setup_user_session(); run_user_session(); } diff --git a/src/utils.rs b/src/utils.rs index f1dd1667..4cff6cb9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -98,19 +98,6 @@ pub fn do_symlink(src: &str, dst: &str) { } } -pub fn check_file_permissions(file_path: &str, mask: u32) -> bool { - if let Ok(metadata) = std::fs::metadata(file_path) { - let permissions = metadata.permissions(); - permissions.mode() & mask != 0 - } else { - false - } -} - -pub fn is_file_executable(file_path: &str) -> bool { - check_file_permissions(file_path, 0o111) -} - pub fn do_mount(source: &str, target: &str, fstype: &str, flags: u64, fsdata: &str) { let source_cstr = CString::new(source).expect("CString::new failed"); let fstype_cstr = CString::new(fstype).expect("CString::new failed"); From 37f1631a90609eb1fb2785869514d6ae85fbee78 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 19 Jun 2023 18:50:12 +0200 Subject: [PATCH 17/96] virtme-ng-init: support 32-bit architectures The flags in MountInfo should be a 32-bit unsigned on 32-bit architectures and 64-bit unsigned on 64-bit architectures. Redefine it accordingly. Signed-off-by: Andrea Righi --- src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.rs b/src/main.rs index 73251c24..190ae278 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,10 @@ struct MountInfo { source: &'static str, target: &'static str, fs_type: &'static str, + #[cfg(target_pointer_width = "64")] flags: u64, + #[cfg(not(target_pointer_width = "64"))] + flags: u32, fsdata: &'static str, } From ff1569ceb9737b2b32c18aac6f14727171ef54e8 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 20 Jun 2023 07:51:38 +0200 Subject: [PATCH 18/96] virtme-ng-init: use usize instead of u64 in do_mount() This allows to build virtme-ng-init also on 32-bit architectures. Signed-off-by: Andrea Righi --- src/main.rs | 43 ++++++++++++++++++++----------------------- src/utils.rs | 4 ++-- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index 190ae278..4757ae7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,10 +34,7 @@ struct MountInfo { source: &'static str, target: &'static str, fs_type: &'static str, - #[cfg(target_pointer_width = "64")] - flags: u64, - #[cfg(not(target_pointer_width = "64"))] - flags: u32, + flags: usize, fsdata: &'static str, } @@ -46,14 +43,14 @@ const KERNEL_MOUNTS: &[MountInfo] = &[ source: "proc", target: "/proc", fs_type: "proc", - flags: libc::MS_NOSUID | libc::MS_NOEXEC | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NOEXEC | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "sys", target: "/sys", fs_type: "sysfs", - flags: libc::MS_NOSUID | libc::MS_NOEXEC | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NOEXEC | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { @@ -74,7 +71,7 @@ const KERNEL_MOUNTS: &[MountInfo] = &[ source: "devtmpfs", target: "/dev", fs_type: "devtmpfs", - flags: libc::MS_NOSUID | libc::MS_NOEXEC, + flags: (libc::MS_NOSUID | libc::MS_NOEXEC) as usize, fsdata: "", }, MountInfo { @@ -119,77 +116,77 @@ const SYSTEM_MOUNTS: &[MountInfo] = &[ source: "devpts", target: "/dev/pts", fs_type: "devpts", - flags: libc::MS_NOSUID | libc::MS_NOEXEC, + flags: (libc::MS_NOSUID | libc::MS_NOEXEC) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/dev/shm", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/var/log", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/var/tmp", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/var/spool/rsyslog", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/var/lib/portables", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/var/lib/machines", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/var/lib/private", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/var/lib/apt", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/var/cache", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, MountInfo { source: "tmpfs", target: "/var/lib/snapd/cookie", fs_type: "tmpfs", - flags: libc::MS_NOSUID | libc::MS_NODEV, + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, ]; @@ -281,7 +278,7 @@ fn run_systemd_tmpfiles() { fn generate_fstab() -> io::Result<()> { utils::create_file("/tmp/fstab", 0o0664, "").ok(); - utils::do_mount("/tmp/fstab", "/etc/fstab", "", libc::MS_BIND, ""); + utils::do_mount("/tmp/fstab", "/etc/fstab", "", libc::MS_BIND as usize, ""); Ok(()) } @@ -303,7 +300,7 @@ fn generate_shadow() -> io::Result<()> { writeln!(writer, "{}:!:::::::", username)?; } } - utils::do_mount("/tmp/shadow", "/etc/shadow", "", libc::MS_BIND, ""); + utils::do_mount("/tmp/shadow", "/etc/shadow", "", libc::MS_BIND as usize, ""); Ok(()) } @@ -318,7 +315,7 @@ fn generate_sudoers() -> io::Result<()> { user ); file.write_all(content.as_bytes())?; - utils::do_mount(fname, "/etc/sudoers", "", libc::MS_BIND, ""); + utils::do_mount(fname, "/etc/sudoers", "", libc::MS_BIND as usize, ""); } Ok(()) } @@ -450,7 +447,7 @@ fn fix_dpkg_locks() { } let src_file = format!("/tmp/{}", fname); utils::create_file(&src_file, 0o0640, "").ok(); - utils::do_mount(&src_file, path, "", libc::MS_BIND, ""); + utils::do_mount(&src_file, path, "", libc::MS_BIND as usize, ""); } } @@ -690,7 +687,7 @@ fn run_user_script() -> bool { fn setup_root_home() { utils::do_mkdir("/tmp/roothome"); - utils::do_mount("/tmp/roothome", "/root", "", libc::MS_BIND, ""); + utils::do_mount("/tmp/roothome", "/root", "", libc::MS_BIND as usize, ""); env::set_var("HOME", "/tmp/roothome"); } diff --git a/src/utils.rs b/src/utils.rs index 4cff6cb9..304d4d91 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -98,7 +98,7 @@ pub fn do_symlink(src: &str, dst: &str) { } } -pub fn do_mount(source: &str, target: &str, fstype: &str, flags: u64, fsdata: &str) { +pub fn do_mount(source: &str, target: &str, fstype: &str, flags: usize, fsdata: &str) { let source_cstr = CString::new(source).expect("CString::new failed"); let fstype_cstr = CString::new(fstype).expect("CString::new failed"); let fsdata_cstr = CString::new(fsdata).expect("CString::new failed"); @@ -107,7 +107,7 @@ pub fn do_mount(source: &str, target: &str, fstype: &str, flags: u64, fsdata: &s Some(source_cstr.as_ref()), target, Some(fstype_cstr.as_ref()), - MsFlags::from_bits_truncate(flags), + MsFlags::from_bits_truncate(flags.try_into().unwrap()), Some(fsdata_cstr.as_ref()), ); if let Err(err) = result { From 231d4a0d1dba13b0b8b57575d9f331025641942c Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 20 Jun 2023 09:57:15 +0200 Subject: [PATCH 19/96] virtme-ng-init: minor coding style fixes Signed-off-by: Andrea Righi --- src/main.rs | 4 +++- src/utils.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4757ae7c..8d5652ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -769,7 +769,9 @@ fn run_user_gui(tty_fd: libc::c_int, app: &str) { } } } - if let Err(err) = utils::create_file(xinitrc, 0o0644, &format!("{}\nexec {}", pre_exec_cmd, app)) { + if let Err(err) = + utils::create_file(xinitrc, 0o0644, &format!("{}\nexec {}", pre_exec_cmd, app)) + { utils::log(&format!("failed to generate {}: {}", xinitrc, err)); return; } diff --git a/src/utils.rs b/src/utils.rs index 304d4d91..cfffce85 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,13 +8,13 @@ use nix::libc; use nix::mount::{mount, MsFlags}; use nix::sys::stat::Mode; use nix::unistd::{chown, Gid, Uid}; -use users::{get_user_by_name}; use std::ffi::CString; use std::fs::{File, OpenOptions}; use std::io::{self, Write}; use std::os::unix::fs; use std::os::unix::fs::PermissionsExt; use std::process::{Command, Stdio}; +use users::get_user_by_name; static PROG_NAME: &'static str = "virtme-ng-init"; @@ -43,8 +43,8 @@ pub fn get_user_id(username: &str) -> Option { } pub fn do_chown(path: &str, uid: u32, gid: u32) -> io::Result<()> { - chown(path, Some(Uid::from_raw(uid)), Some(Gid::from_raw(gid))) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + chown(path, Some(Uid::from_raw(uid)), Some(Gid::from_raw(gid))) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; Ok(()) } From 0ce6a90ef3847682b797c88eeb1f7f80d21130ad Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 20 Jun 2023 10:42:39 +0200 Subject: [PATCH 20/96] virtme-ng-init: relax 'nix' and 'users' crate dependencies We don't require any specific version of nix and users crate, so let's just relax their version constraints. Signed-off-by: Andrea Righi --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8a613130..5004eff6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nix = "0.21" -users = "0.11" +nix = "0" +users = "0" From 7360251103b59119ae89081929dd8180bcd8d23d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 21 Jun 2023 08:07:49 +0200 Subject: [PATCH 21/96] remove Cargo.lock Signed-off-by: Andrea Righi --- Cargo.lock | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 9b231fc8..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,46 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "libc" -version = "0.2.144" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" - -[[package]] -name = "nix" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", -] - -[[package]] -name = "virtme-ng-init" -version = "0.1.0" -dependencies = [ - "nix", -] From e434248259e95674b3fbb113ea5e4ae18ca93184 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 21 Jun 2023 08:08:29 +0200 Subject: [PATCH 22/96] update README.md Signed-off-by: Andrea Righi --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index bc21facb..1b9fc3b1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -# This project has been merged into virtme-ng - -This project has been merged into: https://github.com/arighi/virtme-ng. - -From now on all the development will continue in virtme-ng. - # virtme-ng-init: fast init process for virtme-ng virtme-ng-init is an extremely lightweight init process for virtme-ng [1] From cafc551ce651b23e30c69a22a2d26a5d89757490 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 21 Jun 2023 08:42:19 +0200 Subject: [PATCH 23/96] github: add coding style checks to the workflows Also run the workflows on any push and pull request. Signed-off-by: Andrea Righi --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 31000a27..9100db94 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,7 @@ name: Rust on: push: - branches: [ "main" ] pull_request: - branches: [ "main" ] env: CARGO_TERM_COLOR: always @@ -16,6 +14,8 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Coding style + run: cargo fmt -- --check - name: Build run: cargo build --verbose - name: Run tests From 382363277009035edd56f62b2a0963b9249d33a5 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 28 Jun 2023 08:44:55 +0200 Subject: [PATCH 24/96] virtme-ng-init: support base64 encoded commands Decode base64 shell commands passed via virtme.exec=... Encoding the shell command in base64 allows to pass commands that contain special characters, such as double quotes, single quotes, etc. Signed-off-by: Andrea Righi --- Cargo.toml | 1 + src/main.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5004eff6..5d92489b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] nix = "0" users = "0" +base64 = "0.21" diff --git a/src/main.rs b/src/main.rs index 8d5652ad..fc23e9d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,9 @@ //! //! Author: Andrea Righi +use base64::engine::general_purpose::STANDARD as BASE64; +use base64::engine::Engine as _; + use libc::{uname, utsname}; use nix::fcntl::{open, OFlag}; use nix::libc; @@ -597,8 +600,12 @@ fn extract_user_script(virtme_script: &str) -> Option { if let Some(start_index) = virtme_script.find(start_marker) { let start_index = start_index + start_marker.len(); if let Some(end_index) = virtme_script[start_index..].find(end_marker) { - let cmd = &virtme_script[start_index..start_index + end_index]; - return Some(cmd.to_string()); + let encoded_cmd = &virtme_script[start_index..start_index + end_index]; + if let Ok(decoded_bytes) = BASE64.decode(encoded_cmd) { + if let Ok(decoded_string) = String::from_utf8(decoded_bytes) { + return Some(decoded_string); + } + } } } From 9d5ed510e64a188c96363736175376c24fe0f289 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 20 Sep 2023 12:10:59 +0200 Subject: [PATCH 25/96] cargo: use fixed versions for nix and users crate Use fixed version of dependent crates to make sure virtme-ng-init always builds. Signed-off-by: Andrea Righi --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5d92489b..882f1b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nix = "0" -users = "0" +nix = "0.26" +users = "0.11" base64 = "0.21" From 2b45725fbf15f03ad3ad514a00929eb069107ad2 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Fri, 6 Oct 2023 07:26:48 +0200 Subject: [PATCH 26/96] virtme-ng-init: fail gracefully when executing external commands Prevent crashing init when an external command cannot be executed, instead fail gracefully logging a warning. Signed-off-by: Andrea Righi --- src/utils.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index cfffce85..6213a348 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -126,7 +126,20 @@ pub fn run_cmd(cmd: &str, args: &[&str]) { .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .output() - .ok(); - log(String::from_utf8_lossy(&output.unwrap().stderr).trim_end_matches('\n')); + .output(); + + match output { + Ok(output) => { + if !output.stderr.is_empty() { + log(String::from_utf8_lossy(&output.stderr).trim_end_matches('\n')); + } + } + Err(_) => { + log(&format!( + "WARNING: failed to run: {} {}", + cmd, + args.join(" ") + )); + } + } } From af4cfe12036168c6909e41d5c1e494d3cb795ca2 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 7 Nov 2023 19:19:30 +0200 Subject: [PATCH 27/96] virtme-ng-init: support long commands in graphic mode Support long commands using the "--" syntax also when running virtme-ng in graphic mode. Signed-off-by: Andrea Righi --- src/main.rs | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/main.rs b/src/main.rs index fc23e9d4..26f714ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -612,7 +612,7 @@ fn extract_user_script(virtme_script: &str) -> Option { None } -fn do_run_user_script(cmd: &str) { +fn run_user_script() { if !std::path::Path::new("/dev/virtio-ports/virtme.stdin").exists() || !std::path::Path::new("/dev/virtio-ports/virtme.stdout").exists() || !std::path::Path::new("/dev/virtio-ports/virtme.stderr").exists() @@ -663,7 +663,7 @@ fn do_run_user_script(cmd: &str) { clear_virtme_envs(); unsafe { Command::new("/bin/sh") - .args(["-c", cmd]) + .args(["/tmp/.virtme-script"]) .pre_exec(move || { nix::libc::setsid(); libc::close(libc::STDIN_FILENO); @@ -679,17 +679,27 @@ fn do_run_user_script(cmd: &str) { .output() .expect("Failed to execute script"); } + poweroff(); } } -fn run_user_script() -> bool { +fn create_user_script(cmd: &str) { + let file_path = "/tmp/.virtme-script"; + let mut file = File::create(file_path).expect("Failed to create virtme-script file"); + file.write_all(cmd.as_bytes()) + .expect("Failed to write data to virtme-script file"); +} + +fn setup_user_script() { if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { if let Some(cmd) = extract_user_script(&cmdline) { - do_run_user_script(&cmd); - return true; + create_user_script(&cmd); + if let Ok(_) = env::var("virtme_graphics") { + return; + } + run_user_script(); } } - return false; } fn setup_root_home() { @@ -761,7 +771,7 @@ fn init_xdg_runtime_dir() { env::set_var("XDG_RUNTIME_DIR", dir); } -fn run_user_gui(tty_fd: libc::c_int, app: &str) { +fn run_user_gui(tty_fd: libc::c_int) { init_xdg_runtime_dir(); // Generate a bare minimum xinitrc @@ -776,9 +786,11 @@ fn run_user_gui(tty_fd: libc::c_int, app: &str) { } } } - if let Err(err) = - utils::create_file(xinitrc, 0o0644, &format!("{}\nexec {}", pre_exec_cmd, app)) - { + if let Err(err) = utils::create_file( + xinitrc, + 0o0644, + &format!("{}\n/bin/bash /tmp/.virtme-script", pre_exec_cmd), + ) { utils::log(&format!("failed to generate {}: {}", xinitrc, err)); return; } @@ -823,10 +835,11 @@ fn run_user_session() { let tty_fd = open(consdev.as_str(), OFlag::from_bits_truncate(flags), mode) .expect("failed to open console"); - if let Ok(app) = env::var("virtme_graphics") { - run_user_gui(tty_fd, &app); + if let Ok(_) = env::var("virtme_graphics") { + run_user_gui(tty_fd); + } else { + run_user_shell(tty_fd); } - run_user_shell(tty_fd); } fn setup_user_session() { @@ -919,10 +932,9 @@ fn main() { // Start user session (batch or interactive). set_cwd(); - if !run_user_script() { - setup_user_session(); - run_user_session(); - } + setup_user_script(); + setup_user_session(); + run_user_session(); // Shutdown the system. poweroff(); From 2f0d249702ef5ee487e313578a1833427b26bffe Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 8 Nov 2023 11:24:22 +0200 Subject: [PATCH 28/96] virtme-ng-init: make virtme-script a constant Signed-off-by: Andrea Righi --- src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 26f714ad..2abc54d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -194,6 +194,8 @@ const SYSTEM_MOUNTS: &[MountInfo] = &[ }, ]; +const USER_SCRIPT: &str = "/tmp/.virtme-script"; + fn check_init_pid() { if id() != 1 { utils::log(&format!("must be run as PID 1")); @@ -663,7 +665,7 @@ fn run_user_script() { clear_virtme_envs(); unsafe { Command::new("/bin/sh") - .args(["/tmp/.virtme-script"]) + .args([USER_SCRIPT]) .pre_exec(move || { nix::libc::setsid(); libc::close(libc::STDIN_FILENO); @@ -684,8 +686,7 @@ fn run_user_script() { } fn create_user_script(cmd: &str) { - let file_path = "/tmp/.virtme-script"; - let mut file = File::create(file_path).expect("Failed to create virtme-script file"); + let mut file = File::create(USER_SCRIPT).expect("Failed to create virtme-script file"); file.write_all(cmd.as_bytes()) .expect("Failed to write data to virtme-script file"); } @@ -789,7 +790,7 @@ fn run_user_gui(tty_fd: libc::c_int) { if let Err(err) = utils::create_file( xinitrc, 0o0644, - &format!("{}\n/bin/bash /tmp/.virtme-script", pre_exec_cmd), + &format!("{}\n/bin/bash {}", pre_exec_cmd, USER_SCRIPT), ) { utils::log(&format!("failed to generate {}: {}", xinitrc, err)); return; From f20306df2a97a83683db94e8427a6e0d4b6b6ddc Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 12 Nov 2023 07:40:34 -0500 Subject: [PATCH 29/96] virtme-ng: honor virtme_user when running user script When virtme_user is specified in the boot options we should also honor it when running scripts, instead of just using it in interactive mode. Signed-off-by: Andrea Righi --- src/main.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2abc54d0..1bb8cc5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -662,10 +662,26 @@ fn run_user_script() { ) .expect("failed to open console."); + // Determine if we need to switch to a different user, or if we can run the script as root. + let cmd: String; + let args: Vec<&str>; + let user: String; + if let Ok(virtme_user) = env::var("virtme_user") { + user = virtme_user; + } else { + user = String::new(); + } + if !user.is_empty() { + cmd = "su".to_string(); + args = vec![&user, "-c", USER_SCRIPT]; + } else { + cmd = "/bin/sh".to_string(); + args = vec![USER_SCRIPT]; + } clear_virtme_envs(); unsafe { - Command::new("/bin/sh") - .args([USER_SCRIPT]) + Command::new(&cmd) + .args(&args) .pre_exec(move || { nix::libc::setsid(); libc::close(libc::STDIN_FILENO); @@ -686,9 +702,7 @@ fn run_user_script() { } fn create_user_script(cmd: &str) { - let mut file = File::create(USER_SCRIPT).expect("Failed to create virtme-script file"); - file.write_all(cmd.as_bytes()) - .expect("Failed to write data to virtme-script file"); + utils::create_file(USER_SCRIPT, 0o0755, cmd).expect("Failed to create virtme-script file"); } fn setup_user_script() { From 7d49859e8439807b8490c5fdd8e44f1d4d8282b0 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:41:59 +0100 Subject: [PATCH 30/96] Use ? on Option where possible I looked for functions that returned Option like get_user_id() before this commit. This function calls some other function, check if it returns a value (Option::Some), and then does something with this value. If a None was seen, a None is explicitly returned. This can be simplified with the question mark operator. By using ? on the Option, the function automatically returns None if a None is seen. I think this is a bit more readable (although perhaps less beginner friendly since this might read like magic). Some functions did similar things but with a Result. Here, I used .ok()? to get the desired behaviour. I still feel like this is simpler. Signed-off-by: Uli Schlachter --- src/main.rs | 75 ++++++++++++++++++++++++---------------------------- src/utils.rs | 5 +--- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1bb8cc5e..60b6a80e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -522,14 +522,14 @@ fn run_udevd() -> Option> { } fn get_guest_tools_dir() -> Option { - if let Ok(current_exe) = env::current_exe() { - if let Some(parent_dir) = current_exe.parent()?.parent() { - if let Some(dir) = parent_dir.to_str() { - return Some(dir.to_string()); - } - } - } - None + Some( + env::current_exe() + .ok()? + .parent()? + .parent()? + .to_str()? + .to_string(), + ) } fn _get_network_device_from_entries(entries: std::fs::ReadDir) -> Option { @@ -569,26 +569,25 @@ fn get_network_device() -> Option { fn setup_network() -> Option> { utils::run_cmd("ip", &["link", "set", "dev", "lo", "up"]); - if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { - if cmdline.contains("virtme.dhcp") { - if let Some(guest_tools_dir) = get_guest_tools_dir() { - if let Some(network_dev) = get_network_device() { - utils::log(&format!("setting up network device {}", network_dev)); - let handle = thread::spawn(move || { - let args = [ - "udhcpc", - "-i", - &network_dev, - "-n", - "-q", - "-f", - "-s", - &format!("{}/virtme-udhcpc-script", guest_tools_dir), - ]; - utils::run_cmd("busybox", &args); - }); - return Some(handle); - } + let cmdline = std::fs::read_to_string("/proc/cmdline").ok()?; + if cmdline.contains("virtme.dhcp") { + if let Some(guest_tools_dir) = get_guest_tools_dir() { + if let Some(network_dev) = get_network_device() { + utils::log(&format!("setting up network device {}", network_dev)); + let handle = thread::spawn(move || { + let args = [ + "udhcpc", + "-i", + &network_dev, + "-n", + "-q", + "-f", + "-s", + &format!("{}/virtme-udhcpc-script", guest_tools_dir), + ]; + utils::run_cmd("busybox", &args); + }); + return Some(handle); } } } @@ -599,19 +598,13 @@ fn extract_user_script(virtme_script: &str) -> Option { let start_marker = "virtme.exec=`"; let end_marker = "`"; - if let Some(start_index) = virtme_script.find(start_marker) { - let start_index = start_index + start_marker.len(); - if let Some(end_index) = virtme_script[start_index..].find(end_marker) { - let encoded_cmd = &virtme_script[start_index..start_index + end_index]; - if let Ok(decoded_bytes) = BASE64.decode(encoded_cmd) { - if let Ok(decoded_string) = String::from_utf8(decoded_bytes) { - return Some(decoded_string); - } - } - } - } - - None + let start_index = virtme_script.find(start_marker)?; + let start_index = start_index + start_marker.len(); + let end_index = virtme_script[start_index..].find(end_marker)?; + let encoded_cmd = &virtme_script[start_index..start_index + end_index]; + let decoded_bytes = BASE64.decode(encoded_cmd).ok()?; + let decoded_string = String::from_utf8(decoded_bytes).ok()?; + Some(decoded_string) } fn run_user_script() { diff --git a/src/utils.rs b/src/utils.rs index 6213a348..c3b62cdf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -36,10 +36,7 @@ pub fn log(msg: &str) { } pub fn get_user_id(username: &str) -> Option { - if let Some(user) = get_user_by_name(username) { - return Some(user.uid()); - } - None + Some(get_user_by_name(username)?.uid()) } pub fn do_chown(path: &str, uid: u32, gid: u32) -> io::Result<()> { From 9c429bc4e78a3659e544d3da80e99f6387b82c90 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:48:12 +0100 Subject: [PATCH 31/96] Add a simple test for extract_user_script() I want to change this function next. To make sure I am not breaking anything, I am adding a simple test for it. Signed-off-by: Uli Schlachter --- src/main.rs | 3 +++ src/test.rs | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 src/test.rs diff --git a/src/main.rs b/src/main.rs index 60b6a80e..ad6560c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,9 @@ use std::thread; use std::time::Duration; mod utils; +#[cfg(test)] +mod test; + struct MountInfo { source: &'static str, target: &'static str, diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 00000000..24c8cc35 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0 + +#[test] +fn test_extract_user_script() { + let input = "other=stuff virtme.exec=`SGVsbG8K` is=ignored"; + assert_eq!( + super::extract_user_script(input), + Some("Hello\n".to_string()) + ); +} From 99b099277f54889f5efe42d98cd5838702241e97 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:51:11 +0100 Subject: [PATCH 32/96] Simplify extract_user_script() This function used find() and manual indexing to split up an input string. This is all fine, but not terribly obvious to read. In this commit, I change the code to use split_once() instead. This finds the first occurrence of some search string and returns the substrings before and after this. I think this makes the function much more obvious. Additionally, I changed end_marker from a string to a char. Both split_once() and the previously used find() can get a string or a character as argument. I guess a single character can be slightly more efficient, but I do not actually know. It just seemed to be the right thing. Signed-off-by: Uli Schlachter --- src/main.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index ad6560c4..b0424da6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -599,15 +599,11 @@ fn setup_network() -> Option> { fn extract_user_script(virtme_script: &str) -> Option { let start_marker = "virtme.exec=`"; - let end_marker = "`"; - - let start_index = virtme_script.find(start_marker)?; - let start_index = start_index + start_marker.len(); - let end_index = virtme_script[start_index..].find(end_marker)?; - let encoded_cmd = &virtme_script[start_index..start_index + end_index]; - let decoded_bytes = BASE64.decode(encoded_cmd).ok()?; - let decoded_string = String::from_utf8(decoded_bytes).ok()?; - Some(decoded_string) + let end_marker = '`'; + + let (_before, remaining) = virtme_script.split_once(start_marker)?; + let (encoded_cmd, _after) = remaining.split_once(end_marker)?; + Some(String::from_utf8(BASE64.decode(encoded_cmd).ok()?).ok()?) } fn run_user_script() { From a1cf62f6ae8e7cf054fe3ffe8e807d025e7cc037 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:54:33 +0100 Subject: [PATCH 33/96] Remove use of HashMap The code here created a Vec, then turned that into a HashMap, then iterated over the HashMap. Instead, this could just iterate over the Vec directly. In fact, a plain array would suffice and the extra allocation for the Vec is not needed. Signed-off-by: Uli Schlachter --- src/main.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index b0424da6..c55161c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,6 @@ use nix::libc; use nix::sys::reboot; use nix::sys::stat::Mode; use nix::unistd::sethostname; -use std::collections::HashMap; use std::env; use std::ffi::CStr; use std::fs::{File, OpenOptions}; @@ -343,14 +342,12 @@ fn set_cwd() { } fn symlink_fds() { - let fd_links: HashMap<&str, &str> = vec![ + let fd_links = [ ("/proc/self/fd", "/dev/fd"), ("/proc/self/fd/0", "/dev/stdin"), ("/proc/self/fd/1", "/dev/stdout"), ("/proc/self/fd/2", "/dev/stderr"), - ] - .into_iter() - .collect(); + ]; // Install /proc/self/fd symlinks into /dev if not already present. for (src, dst) in fd_links.iter() { @@ -618,13 +615,11 @@ fn run_user_script() { ); } else { // Re-create stdout/stderr to connect to the virtio-serial ports. - let io_files: HashMap<&str, &str> = vec![ + let io_files = [ ("/dev/virtio-ports/virtme.dev_stdin", "/dev/stdin"), ("/dev/virtio-ports/virtme.dev_stdout", "/dev/stdout"), ("/dev/virtio-ports/virtme.dev_stderr", "/dev/stderr"), - ] - .into_iter() - .collect(); + ]; for (src, dst) in io_files.iter() { if std::path::Path::new(dst).exists() { utils::do_unlink(dst); From 44b9d9591bb32e0cf893ed6ce49a6d7f371d7b25 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:56:20 +0100 Subject: [PATCH 34/96] Remove Option return from run_misc_services() THis function cannot fail and the Option is basically dead code. Additionally, I removed the "move" on this lambda since the lambda does not capture anything, so nothing is actually moved. Signed-off-by: Uli Schlachter --- src/main.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index c55161c0..54d2b92b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -885,15 +885,14 @@ fn run_snapd() { } } -fn run_misc_services() -> Option> { - let handle = thread::spawn(move || { +fn run_misc_services() -> thread::JoinHandle<()> { + thread::spawn(|| { symlink_fds(); mount_virtme_initmounts(); fix_packaging_files(); override_system_files(); run_snapd(); - }); - Some(handle) + }) } fn print_logo() { @@ -925,7 +924,7 @@ fn main() { let mut handles: Vec>> = Vec::new(); handles.push(run_udevd()); handles.push(setup_network()); - handles.push(run_misc_services()); + handles.push(Some(run_misc_services())); // Wait for the completion of the detached services. for handle in handles.into_iter().flatten() { From ffa33fba9998c77fc2a19bf0848c1a9fe3a15b6c Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:00:19 +0100 Subject: [PATCH 35/96] Simplify an env var check Instead of checking if the env variable is set and then early-returning, this commit reverses the order so that the function is only run if the variable is not present. Also, instead of pattern matching with "if let", this uses is_err() to check if the variable is not set. Signed-off-by: Uli Schlachter --- src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 54d2b92b..e197f3ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -696,10 +696,9 @@ fn setup_user_script() { if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { if let Some(cmd) = extract_user_script(&cmdline) { create_user_script(&cmd); - if let Ok(_) = env::var("virtme_graphics") { - return; + if env::var("virtme_graphics").is_err() { + run_user_script(); } - run_user_script(); } } } From 125349dd8802f222b48c9b9631da43fb2d0bc725 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:06:58 +0100 Subject: [PATCH 36/96] Simplify(?) some environment lookup The code previously initiated the "uid" variable to a default value and then had two nested "if"s that both must succeed for another value to be set. In this commit, I change that to a processing chain on Option: If env::var() produces a value, Option::and_then() is used to call another function. If either of these two steps produces a None, the result will be a None. .unwrap_or() is then used to replace the None with a default value. This allows to get rid of the "mut" on the variable. Signed-off-by: Uli Schlachter --- src/main.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index e197f3ea..1cd6fb42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -760,12 +760,10 @@ fn run_shell(tty_fd: libc::c_int, args: Vec) { fn init_xdg_runtime_dir() { // Initialize XDG_RUNTIME_DIR (required to provide a better compatibility with graphic apps). - let mut uid = 0; - if let Ok(user) = env::var("virtme_user") { - if let Some(virtme_uid) = utils::get_user_id(&user) { - uid = virtme_uid; - } - } + let uid = env::var("virtme_user") + .ok() + .and_then(|user| utils::get_user_id(&user)) + .unwrap_or(0); let dir = format!("/run/user/{}", uid); utils::do_mkdir(&dir); utils::do_chown(&dir, uid, uid).ok(); From 12759fea0fbb74055a0436d07c28972cef31f667 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:10:28 +0100 Subject: [PATCH 37/96] Simplify lookup of $virtme_user This uses unwrap_or_else() to construct an empty string in case the virtme_user env var is not used. Signed-off-by: Uli Schlachter --- src/main.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1cd6fb42..fb20d2e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -652,12 +652,7 @@ fn run_user_script() { // Determine if we need to switch to a different user, or if we can run the script as root. let cmd: String; let args: Vec<&str>; - let user: String; - if let Ok(virtme_user) = env::var("virtme_user") { - user = virtme_user; - } else { - user = String::new(); - } + let user = env::var("virtme_user").unwrap_or_else(|_| String::new()); if !user.is_empty() { cmd = "su".to_string(); args = vec![&user, "-c", USER_SCRIPT]; From f1f16aa7ac9b155fcc18fea537283f400f03224c Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:12:34 +0100 Subject: [PATCH 38/96] Simplify(?) some command construction "if"s are expressions, too, and can be used to set variables. This commit refactors some code to use this to set the "cmd" and "args" variables based on a condition. Additionally, "cmd" is changed from a String to a &str since both cases just use a static string. Signed-off-by: Uli Schlachter --- src/main.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index fb20d2e5..5c1ce088 100644 --- a/src/main.rs +++ b/src/main.rs @@ -650,19 +650,15 @@ fn run_user_script() { .expect("failed to open console."); // Determine if we need to switch to a different user, or if we can run the script as root. - let cmd: String; - let args: Vec<&str>; let user = env::var("virtme_user").unwrap_or_else(|_| String::new()); - if !user.is_empty() { - cmd = "su".to_string(); - args = vec![&user, "-c", USER_SCRIPT]; + let (cmd, args) = if !user.is_empty() { + ("su", vec![&user, "-c", USER_SCRIPT]) } else { - cmd = "/bin/sh".to_string(); - args = vec![USER_SCRIPT]; - } + ("/bin/sh", vec![USER_SCRIPT]) + }; clear_virtme_envs(); unsafe { - Command::new(&cmd) + Command::new(cmd) .args(&args) .pre_exec(move || { nix::libc::setsid(); From 1d1dc13befe2c8d6de9f8e142d57f5a6998a32d5 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:14:14 +0100 Subject: [PATCH 39/96] Remove useless Some-wrapping Clippy reports that there is code here that takes an Option and produces the exact same Option from it. Fix that. warning: question mark operator is useless here --> src/main.rs:603:5 | 603 | Some(String::from_utf8(BASE64.decode(encoded_cmd).ok()?).ok()?) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try removing question mark and `Some()`: `String::from_utf8(BASE64.decode(encoded_cmd).ok()?).ok()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark = note: `#[warn(clippy::needless_question_mark)]` on by default Signed-off-by: Uli Schlachter --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 5c1ce088..2d786216 100644 --- a/src/main.rs +++ b/src/main.rs @@ -600,7 +600,7 @@ fn extract_user_script(virtme_script: &str) -> Option { let (_before, remaining) = virtme_script.split_once(start_marker)?; let (encoded_cmd, _after) = remaining.split_once(end_marker)?; - Some(String::from_utf8(BASE64.decode(encoded_cmd).ok()?).ok()?) + String::from_utf8(BASE64.decode(encoded_cmd).ok()?).ok() } fn run_user_script() { From 8a3c38123e71773e665cf660410ce615e883cbb0 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:15:44 +0100 Subject: [PATCH 40/96] Remove unnecessary 'static Clippy reports: warning: statics have by default a `'static` lifetime --> src/utils.rs:19:20 | 19 | static PROG_NAME: &'static str = "virtme-ng-init"; | -^^^^^^^---- help: consider removing `'static`: `&str` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes = note: `#[warn(clippy::redundant_static_lifetimes)]` on by default Signed-off-by: Uli Schlachter --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index c3b62cdf..84643c2f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -16,7 +16,7 @@ use std::os::unix::fs::PermissionsExt; use std::process::{Command, Stdio}; use users::get_user_by_name; -static PROG_NAME: &'static str = "virtme-ng-init"; +static PROG_NAME: &str = "virtme-ng-init"; pub fn log(msg: &str) { if msg.is_empty() { From 1a6705944034c6bdf0c2f8892fb1f7c26e1031ad Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:20:20 +0100 Subject: [PATCH 41/96] Replace e.g. libc::S_IRWXU with Mode::S_IRWXU This code uses numeric constants from libc and then converts the resulting number into a Mode. By instead directly starting with the Mode constants, we directly get a Mode. Signed-off-by: Uli Schlachter --- src/utils.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 84643c2f..3c85b022 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,7 +4,6 @@ //! //! Author: Andrea Righi -use nix::libc; use nix::mount::{mount, MsFlags}; use nix::sys::stat::Mode; use nix::unistd::{chown, Gid, Uid}; @@ -47,8 +46,8 @@ pub fn do_chown(path: &str, uid: u32, gid: u32) -> io::Result<()> { } pub fn do_mkdir(path: &str) { - let dmask = libc::S_IRWXU | libc::S_IRGRP | libc::S_IXGRP | libc::S_IROTH | libc::S_IXOTH; - nix::unistd::mkdir(path, Mode::from_bits_truncate(dmask as u32)).ok(); + let dmask = Mode::S_IRWXU | Mode::S_IRGRP | Mode::S_IXGRP | Mode::S_IROTH | Mode::S_IXOTH; + nix::unistd::mkdir(path, dmask).ok(); } pub fn do_unlink(path: &str) { From ea2c53451ecb3f985bb67b8ab5b67677969d7819 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:23:17 +0100 Subject: [PATCH 42/96] Fix clippy warnings around logging warning: `to_string` applied to a type that implements `Display` in `format!` args --> src/utils.rs:114:16 | 114 | err.to_string() | ^^^^^^^^^^^^ help: remove this | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args = note: `#[warn(clippy::to_string_in_format_args)]` on by default warning: useless use of `format!` --> src/main.rs:203:21 | 203 | utils::log(&format!("must be run as PID 1")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"must be run as PID 1".to_string()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_format = note: `#[warn(clippy::useless_format)]` on by default warning: useless use of `format!` --> src/main.rs:272:21 | 272 | utils::log(&format!("virtme_hostname is not defined")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"virtme_hostname is not defined".to_string()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_format I did not actually replace format!() with to_string() since utils::log() just needs a &str, so we can just use a string constant (I guess clippy would propose this if I implemented its first suggestion). Signed-off-by: Uli Schlachter --- src/main.rs | 4 ++-- src/utils.rs | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2d786216..e6510ad8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -200,7 +200,7 @@ const USER_SCRIPT: &str = "/tmp/.virtme-script"; fn check_init_pid() { if id() != 1 { - utils::log(&format!("must be run as PID 1")); + utils::log("must be run as PID 1"); exit(1); } } @@ -269,7 +269,7 @@ fn configure_hostname() { utils::log(&format!("failed to change hostname: {}", err)); } } else { - utils::log(&format!("virtme_hostname is not defined")); + utils::log("virtme_hostname is not defined"); } } diff --git a/src/utils.rs b/src/utils.rs index 3c85b022..a9aa976d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -107,12 +107,7 @@ pub fn do_mount(source: &str, target: &str, fstype: &str, flags: usize, fsdata: Some(fsdata_cstr.as_ref()), ); if let Err(err) = result { - log(&format!( - "mount {} -> {}: {}", - source, - target, - err.to_string() - )); + log(&format!("mount {} -> {}: {}", source, target, err)); } } From a74c0e648e3c2abd05ab022402bbb5384d42a74a Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:27:01 +0100 Subject: [PATCH 43/96] Fix clippy warnings about patterns warning: single-character string constant used as pattern --> src/main.rs:395:30 | 395 | &key.replace("_", "."), | ^^^ help: try using a `char` instead: `'_'` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern = note: `#[warn(clippy::single_char_pattern)]` on by default warning: single-character string constant used as pattern --> src/main.rs:894:44 | 894 | println!("{}", logo.trim_start_matches("\n")); | ^^^^ help: try using a `char` instead: `'\n'` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern Signed-off-by: Uli Schlachter --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index e6510ad8..4efc0af4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -392,7 +392,7 @@ fn mount_virtme_initmounts() { if key.starts_with("virtme_initmount") { utils::do_mkdir(&path); utils::do_mount( - &key.replace("_", "."), + &key.replace('_', "."), &path, "9p", 0, @@ -891,7 +891,7 @@ fn print_logo() { \ V /| | | | |_| | | | | | __/_____| | | | (_| | \_/ |_|_| \__|_| |_| |_|\___| |_| |_|\__ | |___/"#; - println!("{}", logo.trim_start_matches("\n")); + println!("{}", logo.trim_start_matches('\n')); println!(" kernel version: {}\n", get_kernel_version(true)); } From d6e495b730253ffd8f4ca5d40b4660f181d5b3d2 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:28:02 +0100 Subject: [PATCH 44/96] Fix clippy warning about unnecessary return warning: unneeded `return` statement --> src/main.rs:552:5 | 552 | return None; | ^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return = note: `#[warn(clippy::needless_return)]` on by default = help: remove `return` Signed-off-by: Uli Schlachter --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 4efc0af4..fa2905e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -549,7 +549,7 @@ fn _get_network_device_from_entries(entries: std::fs::ReadDir) -> Option } } } - return None; + None } fn get_network_device() -> Option { From 8e4452fca1c5825e37f3f97bcc9a3cb8704dee64 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:30:54 +0100 Subject: [PATCH 45/96] Fix clippy warnings about reading lines warning: unnecessary `if let` since only the `Ok` variant of the iterator element is used --> src/main.rs:249:13 | 249 | for line in reader.lines() { | ^ -------------- help: try: `reader.lines().flatten()` | _____________| | | 250 | | if let Ok(line) = line { 251 | | if line.chars().nth(27) == Some('C') { 252 | | let console = line.split(' ').next()?.to_string(); ... | 255 | | } 256 | | } | |_____________^ | help: ...and remove the `if let` statement in the for loop --> src/main.rs:250:17 | 250 | / if let Ok(line) = line { 251 | | if line.chars().nth(27) == Some('C') { 252 | | let console = line.split(' ').next()?.to_string(); 253 | | return Some(format!("/dev/{}", console)); 254 | | } 255 | | } | |_________________^ = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten = note: `#[warn(clippy::manual_flatten)]` on by default warning: unnecessary `if let` since only the `Ok` variant of the iterator element is used --> src/main.rs:536:5 | 536 | for entry in entries { | ^ ------- help: try: `entries.flatten()` | _____| | | 537 | | if let Ok(entry) = entry { 538 | | let path = entry.path(); 539 | | if !path.is_dir() { ... | 550 | | } 551 | | } | |_____^ | help: ...and remove the `if let` statement in the for loop --> src/main.rs:537:9 | 537 | / if let Ok(entry) = entry { 538 | | let path = entry.path(); 539 | | if !path.is_dir() { 540 | | continue; ... | 549 | | } 550 | | } | |_________^ = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten warning: unnecessary `if let` since only the `Ok` variant of the iterator element is used --> src/main.rs:543:17 | 543 | for entry in net_entries { | ^ ----------- help: try: `net_entries.flatten()` | _________________| | | 544 | | if let Ok(entry) = entry { 545 | | let path = entry.path().file_name()?.to_string_lossy().to_string(); 546 | | return Some(path); 547 | | } 548 | | } | |_________________^ | help: ...and remove the `if let` statement in the for loop --> src/main.rs:544:21 | 544 | / if let Ok(entry) = entry { 545 | | let path = entry.path().file_name()?.to_string_lossy().to_string(); 546 | | return Some(path); 547 | | } | |_____________________^ = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten Signed-off-by: Uli Schlachter --- src/main.rs | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index fa2905e7..62d8a90d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -246,12 +246,11 @@ fn get_active_console() -> Option { Ok(file) => { let reader = BufReader::new(file); - for line in reader.lines() { - if let Ok(line) = line { - if line.chars().nth(27) == Some('C') { - let console = line.split(' ').next()?.to_string(); - return Some(format!("/dev/{}", console)); - } + // .flatten() ignores lines with reading errors + for line in reader.lines().flatten() { + if line.chars().nth(27) == Some('C') { + let console = line.split(' ').next()?.to_string(); + return Some(format!("/dev/{}", console)); } } None @@ -533,19 +532,17 @@ fn get_guest_tools_dir() -> Option { } fn _get_network_device_from_entries(entries: std::fs::ReadDir) -> Option { - for entry in entries { - if let Ok(entry) = entry { - let path = entry.path(); - if !path.is_dir() { - continue; - } - if let Ok(net_entries) = std::fs::read_dir(path.join("net")) { - for entry in net_entries { - if let Ok(entry) = entry { - let path = entry.path().file_name()?.to_string_lossy().to_string(); - return Some(path); - } - } + // .flatten() ignores lines with reading errors + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_dir() { + continue; + } + if let Ok(net_entries) = std::fs::read_dir(path.join("net")) { + // .flatten() ignores lines with reading errors + for entry in net_entries.flatten() { + let path = entry.path().file_name()?.to_string_lossy().to_string(); + return Some(path); } } } From 140f98bf7a3a6882701c5044f8df48565f9bb4ee Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:32:34 +0100 Subject: [PATCH 46/96] Fix clippy warning about "loop never loops" This warning was introduced with the previous commit. error: this loop never actually loops --> src/main.rs:543:13 | 543 | / for entry in net_entries.flatten() { 544 | | let path = entry.path().file_name()?.to_string_lossy().to_string(); 545 | | return Some(path); 546 | | } | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#never_loop = note: `#[deny(clippy::never_loop)]` on by default help: if you need the first element of the iterator, try writing | 543 | if let Some(entry) = net_entries.flatten().next() { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Signed-off-by: Uli Schlachter --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 62d8a90d..3e1754ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -540,7 +540,7 @@ fn _get_network_device_from_entries(entries: std::fs::ReadDir) -> Option } if let Ok(net_entries) = std::fs::read_dir(path.join("net")) { // .flatten() ignores lines with reading errors - for entry in net_entries.flatten() { + if let Some(entry) = net_entries.flatten().next() { let path = entry.path().file_name()?.to_string_lossy().to_string(); return Some(path); } From ae7980f14b56e01994c915bf39c6c8887419a15f Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:33:55 +0100 Subject: [PATCH 47/96] Fix clippy warning about "push() after creation" I also got rid of the type annotation since it doesn't seem to be necessary. warning: calls to `push` immediately after creation --> src/main.rs:909:5 | 909 | / let mut handles: Vec>> = Vec::new(); 910 | | handles.push(run_udevd()); 911 | | handles.push(setup_network()); 912 | | handles.push(Some(run_misc_services())); | |____________________________________________^ help: consider using the `vec![]` macro: `let handles: Vec>> = vec![..];` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#vec_init_then_push = note: `#[warn(clippy::vec_init_then_push)]` on by default Signed-off-by: Uli Schlachter --- src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3e1754ac..5708cf0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -906,10 +906,7 @@ fn main() { run_systemd_tmpfiles(); // Service initialization (some services can be parallelized here). - let mut handles: Vec>> = Vec::new(); - handles.push(run_udevd()); - handles.push(setup_network()); - handles.push(Some(run_misc_services())); + let handles = vec![run_udevd(), setup_network(), Some(run_misc_services())]; // Wait for the completion of the detached services. for handle in handles.into_iter().flatten() { From e4fedd9892ebbe324734826880ac4897276691cc Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:35:22 +0100 Subject: [PATCH 48/96] Fix clippy warning about unnecessary matching warning: redundant pattern matching, consider using `is_ok()` --> src/main.rs:825:12 | 825 | if let Ok(_) = env::var("virtme_graphics") { | -------^^^^^------------------------------ help: try this: `if env::var("virtme_graphics").is_ok()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching = note: `#[warn(clippy::redundant_pattern_matching)]` on by default Signed-off-by: Uli Schlachter --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 5708cf0e..c7dbc164 100644 --- a/src/main.rs +++ b/src/main.rs @@ -822,7 +822,7 @@ fn run_user_session() { let tty_fd = open(consdev.as_str(), OFlag::from_bits_truncate(flags), mode) .expect("failed to open console"); - if let Ok(_) = env::var("virtme_graphics") { + if env::var("virtme_graphics").is_ok() { run_user_gui(tty_fd); } else { run_user_shell(tty_fd); From 4447d7edeac447c68aef5dd088fe424621d9d926 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:43:52 +0100 Subject: [PATCH 49/96] Simplify opening of TTYs Instead of repeating the same code three times, this adds a helper lambda function that does the repetitive part and calls it three times. Instead of using flags from libc and transferring them to nix, this directly uses nix's OFlag. Signed-off-by: Uli Schlachter --- src/main.rs | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index c7dbc164..06237888 100644 --- a/src/main.rs +++ b/src/main.rs @@ -625,26 +625,11 @@ fn run_user_script() { } // Detach the process from the controlling terminal - let flags = libc::O_RDWR; - let mode = Mode::empty(); - let tty_in = open( - "/dev/virtio-ports/virtme.stdin", - OFlag::from_bits_truncate(flags), - mode, - ) - .expect("failed to open console."); - let tty_out = open( - "/dev/virtio-ports/virtme.stdout", - OFlag::from_bits_truncate(flags), - mode, - ) - .expect("failed to open console."); - let tty_err = open( - "/dev/virtio-ports/virtme.stderr", - OFlag::from_bits_truncate(flags), - mode, - ) - .expect("failed to open console."); + let open_tty = + |path| open(path, OFlag::O_RDWR, Mode::empty()).expect("failed to open console."); + let tty_in = open_tty("/dev/virtio-ports/virtme.stdin"); + let tty_out = open_tty("/dev/virtio-ports/virtme.stdout"); + let tty_err = open_tty("/dev/virtio-ports/virtme.stderr"); // Determine if we need to switch to a different user, or if we can run the script as root. let user = env::var("virtme_user").unwrap_or_else(|_| String::new()); From 0cc1ad30c44bb82b6dcbcd4e65fbbc871c1351e5 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:51:54 +0100 Subject: [PATCH 50/96] Use uname() from nix instead of libc This allows to get rid of all the unsafety and the conversion to CStr (well, OsStr now) is handled by nix for us. I also delayed the call to into_owned() a bit. This is used to convert a Cow<'_, str> to String, but the "show_machine" case does not actually need it. Thus, this removes a memory allocation. Signed-off-by: Uli Schlachter --- src/main.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 06237888..17ee03fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,17 +14,15 @@ use base64::engine::general_purpose::STANDARD as BASE64; use base64::engine::Engine as _; -use libc::{uname, utsname}; use nix::fcntl::{open, OFlag}; use nix::libc; use nix::sys::reboot; use nix::sys::stat::Mode; +use nix::sys::utsname::uname; use nix::unistd::sethostname; use std::env; -use std::ffi::CStr; use std::fs::{File, OpenOptions}; use std::io::{self, BufRead, BufReader, BufWriter, Write}; -use std::mem; use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process::{exit, id, Command, Stdio}; @@ -221,22 +219,16 @@ fn configure_environment() { } fn get_kernel_version(show_machine: bool) -> String { - unsafe { - let mut utsname: utsname = mem::zeroed(); - if uname(&mut utsname) == -1 { - return String::from("None"); - } - let release = CStr::from_ptr(utsname.release.as_ptr()) - .to_string_lossy() - .into_owned(); - if show_machine { - let machine = CStr::from_ptr(utsname.machine.as_ptr()) - .to_string_lossy() - .into_owned(); - format!("{} {}", release, machine) - } else { - release - } + let utsname = match uname() { + Ok(utsname) => utsname, + Err(_) => return "None".to_string(), + }; + let release = utsname.release().to_string_lossy(); + if show_machine { + let machine = utsname.machine().to_string_lossy(); + format!("{} {}", release, machine) + } else { + release.into_owned() } } From f072e049b67edb284b5e08f7ffa66cb9c7be5f30 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:56:35 +0100 Subject: [PATCH 51/96] Remove useless to_string() The value is passed to format!() and this does not need a separate allocation, but is totally fine with getting a &str. Signed-off-by: Uli Schlachter --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 17ee03fc..a12786b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -241,7 +241,7 @@ fn get_active_console() -> Option { // .flatten() ignores lines with reading errors for line in reader.lines().flatten() { if line.chars().nth(27) == Some('C') { - let console = line.split(' ').next()?.to_string(); + let console = line.split(' ').next()?; return Some(format!("/dev/{}", console)); } } From 60740e731eae05a99dd4ae2c7f53c0a1f2cdf4da Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:58:13 +0100 Subject: [PATCH 52/96] Simplify /etc/shadow generation This function just needs to part until the first ':', so there is no need to split out all the fields and collect this into a Vec. Just use split_once() to get the first part. Signed-off-by: Uli Schlachter --- src/main.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index a12786b2..b45f9214 100644 --- a/src/main.rs +++ b/src/main.rs @@ -290,11 +290,7 @@ fn generate_shadow() -> io::Result<()> { let mut writer = BufWriter::new(output_file); for line in reader.lines() { - let line = line?; - let parts: Vec<&str> = line.split(':').collect(); - - if !parts.is_empty() { - let username = parts[0]; + if let Some((username, _)) = line?.split_once(':') { writeln!(writer, "{}:!:::::::", username)?; } } From 55dac47e4fafadfe65d7c25bda74e3f94860f9dc Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:00:11 +0100 Subject: [PATCH 53/96] Use utils::create_file() to write some file contents I don't see why this code uses create_file() with empty contents and then writes the expected contents itself. This commit just changes it to let create_file() write the expected contents. Signed-off-by: Uli Schlachter --- src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index b45f9214..418d26ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -302,13 +302,11 @@ fn generate_shadow() -> io::Result<()> { fn generate_sudoers() -> io::Result<()> { if let Ok(user) = env::var("virtme_user") { let fname = "/tmp/sudoers"; - utils::create_file(fname, 0o0440, "").ok(); - let mut file = File::create(fname)?; let content = format!( "root ALL = (ALL) NOPASSWD: ALL\n{} ALL = (ALL) NOPASSWD: ALL\n", user ); - file.write_all(content.as_bytes())?; + utils::create_file(fname, 0o0440, &content).ok(); utils::do_mount(fname, "/etc/sudoers", "", libc::MS_BIND as usize, ""); } Ok(()) From 992e5f868ba56393fbe12930ecdcb62e8d9dad81 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:08:33 +0100 Subject: [PATCH 54/96] Remove an unnecessary Vec An array is just as good as a Vec here and avoids a memory allocation. Signed-off-by: Uli Schlachter --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 418d26ea..78657095 100644 --- a/src/main.rs +++ b/src/main.rs @@ -422,12 +422,12 @@ fn fix_dpkg_locks() { if !Path::new("/var/lib/dpkg").exists() { return; } - let lock_files = vec![ + let lock_files = [ "/var/lib/dpkg/lock", "/var/lib/dpkg/lock-frontend", "/var/lib/dpkg/triggers/Lock", ]; - for path in &lock_files { + for path in lock_files { let fname = Path::new(path) .file_name() .and_then(|name| name.to_str()) From 457bc572954c5a9beeddbb1faf64b3ed4649af63 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:23:22 +0100 Subject: [PATCH 55/96] Refactor logic in find_udevd() The code checks a series of paths for existence. The first one that is found determines the function return value. First, two static candidates are checked. Then, for each entry in $PATH, it is checked whether "udevd" exists in that directory. This commit refactors that. The if-chain is turned into iterators. First, the static candidates are put into an array. Then, $PATH is checked and each entry is mapped to a PathBuf with "udevd" appended. These two iterators are then chained and the first entry that exists is returned. This also automatically produces None if no entry exists. This introduces new allocations for the iterator producing PathBufs. However, since iterators are lazy, these allocations only actually happen when the element is needed. Thus, this should be mostly equivalent to the previous code, except that $PATH is always looked up. Signed-off-by: Uli Schlachter --- src/main.rs | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index 78657095..7e7ec8bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -464,26 +464,17 @@ fn disable_uevent_helper() { } fn find_udevd() -> Option { - let mut udevd = PathBuf::new(); - - if PathBuf::from("/usr/lib/systemd/systemd-udevd").exists() { - udevd = PathBuf::from("/usr/lib/systemd/systemd-udevd"); - } else if PathBuf::from("/lib/systemd/systemd-udevd").exists() { - udevd = PathBuf::from("/lib/systemd/systemd-udevd"); - } else if let Ok(path) = env::var("PATH") { - for dir in path.split(':') { - let udevd_path = PathBuf::from(dir).join("udevd"); - if udevd_path.exists() { - udevd = udevd_path; - break; - } - } - } - if udevd.exists() { - Some(udevd) - } else { - None - } + let static_candidates = [ + PathBuf::from("/usr/lib/systemd/systemd-udevd"), + PathBuf::from("/lib/systemd/systemd-udevd"), + ]; + let path = env::var("PATH").unwrap_or_else(|_| String::new()); + let path_candidates = path.split(':').map(|dir| PathBuf::from(dir).join("udevd")); + + static_candidates + .into_iter() + .chain(path_candidates) + .find(|path| path.exists()) } fn run_udevd() -> Option> { From 6f25e5617d5ee90b81b879d0be2a6998eaac3956 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:31:08 +0100 Subject: [PATCH 56/96] Allow run_cmd() with non-utf8-strings The argument to Command::new() in the Rust standard library is anything that implements AsRef. However, the wrapper function run_cmd() so far only allows &str. This meant that run_udevd() had to use to_string_lossy() to turn a PathBuf into a String that could be passed to this function. This commit changes run_cmd() to accept an AsRef so that the call to to_string_lossy() is no longer necessary. The implementation then could no longer use format!() with {} to display the value since OsStr does not implement Display. Thus, I switched this to use the Debug formatting, but I did not actually check how that looks like. This change then triggered a clippy warning that I am fixing in this commit as well: warning: the borrowed expression implements the required traits --> src/main.rs:813:32 | 813 | utils::run_cmd(&format!("{}/virtme-snapd-script", guest_tools_dir), &[]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change this to: `format!("{}/virtme-snapd-script", guest_tools_dir)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default Signed-off-by: Uli Schlachter --- src/main.rs | 4 ++-- src/utils.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7e7ec8bf..a0aa5a95 100644 --- a/src/main.rs +++ b/src/main.rs @@ -482,7 +482,7 @@ fn run_udevd() -> Option> { let handle = thread::spawn(move || { disable_uevent_helper(); let args: &[&str] = &["--daemon", "--resolve-names=never"]; - utils::run_cmd(&udevd_path.to_string_lossy(), args); + utils::run_cmd(udevd_path, args); utils::log("triggering udev coldplug"); utils::run_cmd("udevadm", &["trigger", "--type=subsystems", "--action=add"]); utils::run_cmd("udevadm", &["trigger", "--type=devices", "--action=add"]); @@ -810,7 +810,7 @@ fn run_snapd() { return; } if let Some(guest_tools_dir) = get_guest_tools_dir() { - utils::run_cmd(&format!("{}/virtme-snapd-script", guest_tools_dir), &[]); + utils::run_cmd(format!("{}/virtme-snapd-script", guest_tools_dir), &[]); } Command::new(snapd_bin) .stdin(Stdio::null()) diff --git a/src/utils.rs b/src/utils.rs index a9aa976d..46b5a17a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,7 +7,7 @@ use nix::mount::{mount, MsFlags}; use nix::sys::stat::Mode; use nix::unistd::{chown, Gid, Uid}; -use std::ffi::CString; +use std::ffi::{CString, OsStr}; use std::fs::{File, OpenOptions}; use std::io::{self, Write}; use std::os::unix::fs; @@ -111,8 +111,8 @@ pub fn do_mount(source: &str, target: &str, fstype: &str, flags: usize, fsdata: } } -pub fn run_cmd(cmd: &str, args: &[&str]) { - let output = Command::new(cmd) +pub fn run_cmd(cmd: impl AsRef, args: &[&str]) { + let output = Command::new(&cmd) .args(args) .stdin(Stdio::null()) .stdout(Stdio::piped()) @@ -127,8 +127,8 @@ pub fn run_cmd(cmd: &str, args: &[&str]) { } Err(_) => { log(&format!( - "WARNING: failed to run: {} {}", - cmd, + "WARNING: failed to run: {:?} {}", + cmd.as_ref(), args.join(" ") )); } From 85a942eeceefafd52ade96375ba52891a03581b9 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:48:11 +0100 Subject: [PATCH 57/96] Avoid allocations for static strings for run_shell() So far, the code calling run_shell() constructed a Vec just because one of the entries needed to be a dynamic string. This commit changes that to be a Vec<&str>. The one dynamic argument is allocated to another variable and then just borrowed. This allows to get rid of some to_owned() calls, which IMO make the code more readable. Then, run_shell() does not actually need a Vec as its argument, so this is changed into a slice that can then also be passed directly to Commands::args(). Signed-off-by: Uli Schlachter --- src/main.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index a0aa5a95..d5f689b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -695,10 +695,10 @@ fn detach_from_terminal(tty_fd: libc::c_int) { } } -fn run_shell(tty_fd: libc::c_int, args: Vec) { +fn run_shell(tty_fd: libc::c_int, args: &[&str]) { unsafe { Command::new("bash") - .args(args.into_iter()) + .args(args) .pre_exec(move || { detach_from_terminal(tty_fd); Ok(()) @@ -745,27 +745,31 @@ fn run_user_gui(tty_fd: libc::c_int) { } // Run graphical app using xinit directly - let mut args: Vec = vec!["-l".to_owned(), "-c".to_owned()]; + let mut args = vec!["-l", "-c"]; + let storage; if let Ok(user) = env::var("virtme_user") { // Try to fix permissions on the virtual consoles, we are starting X // directly here so we may need extra permissions on the tty devices. utils::run_cmd("bash", &["-c", &format!("chown {} /dev/char/*", user)]); // Start xinit directly. - args.push(format!("su {} -c 'xinit /tmp/.xinitrc'", user)); + storage = format!("su {} -c 'xinit /tmp/.xinitrc'", user); + args.push(&storage); } else { - args.push("xinit /tmp/.xinitrc".to_owned()); + args.push("xinit /tmp/.xinitrc"); } - run_shell(tty_fd, args); + run_shell(tty_fd, &args); } fn run_user_shell(tty_fd: libc::c_int) { - let mut args: Vec = vec!["-l".to_owned()]; + let mut args = vec!["-l"]; + let storage; if let Ok(user) = env::var("virtme_user") { - args.push("-c".to_owned()); - args.push(format!("su {}", user)); + args.push("-c"); + storage = format!("su {}", user); + args.push(&storage); } - run_shell(tty_fd, args); + run_shell(tty_fd, &args); } fn run_user_session() { From 8cca65ecd9b6034ab5bb51696ee322a5585bf688 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:50:57 +0100 Subject: [PATCH 58/96] Use nix' flags instead of libc's in one more place Signed-off-by: Uli Schlachter --- src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index d5f689b7..92a71919 100644 --- a/src/main.rs +++ b/src/main.rs @@ -783,10 +783,9 @@ fn run_user_session() { }; configure_terminal(consdev.as_str()); - let flags = libc::O_RDWR | libc::O_NONBLOCK; + let flags = OFlag::O_RDWR | OFlag::O_NONBLOCK; let mode = Mode::empty(); - let tty_fd = open(consdev.as_str(), OFlag::from_bits_truncate(flags), mode) - .expect("failed to open console"); + let tty_fd = open(consdev.as_str(), flags, mode).expect("failed to open console"); if env::var("virtme_graphics").is_ok() { run_user_gui(tty_fd); From 6c5e94d37c70c7daba1d2199d3404a3c97b89bcf Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 13:05:41 +0100 Subject: [PATCH 59/96] Avoid temporary allocation for PathBuf PathBuf is to Path what String is to str. This code here only needs a Path since join() allocates a new PathBuf. Thus, this gets rid of a temporary allocation for the original PathBuf. Signed-off-by: Uli Schlachter --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 92a71919..5ecce47a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -469,7 +469,7 @@ fn find_udevd() -> Option { PathBuf::from("/lib/systemd/systemd-udevd"), ]; let path = env::var("PATH").unwrap_or_else(|_| String::new()); - let path_candidates = path.split(':').map(|dir| PathBuf::from(dir).join("udevd")); + let path_candidates = path.split(':').map(|dir| Path::new(dir).join("udevd")); static_candidates .into_iter() From 9944faa26de6ebafdcde347e4a354021f530f27d Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 17:09:00 +0100 Subject: [PATCH 60/96] Add a log!() macro There is lots of code looking like this: utils::log(&format!("error powering off: {}", err)); Simplify this by adding a log!() macro that hides the format!() dance and can be used directly like format!(). Signed-off-by: Uli Schlachter --- src/main.rs | 43 +++++++++++++++++++++++-------------------- src/utils.rs | 28 +++++++++++++++++----------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5ecce47a..41436f64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,8 @@ use std::path::{Path, PathBuf}; use std::process::{exit, id, Command, Stdio}; use std::thread; use std::time::Duration; + +#[macro_use] mod utils; #[cfg(test)] @@ -198,7 +200,7 @@ const USER_SCRIPT: &str = "/tmp/.virtme-script"; fn check_init_pid() { if id() != 1 { - utils::log("must be run as PID 1"); + log!("must be run as PID 1"); exit(1); } } @@ -208,7 +210,7 @@ fn poweroff() { libc::sync(); } if let Err(err) = reboot::reboot(reboot::RebootMode::RB_POWER_OFF) { - utils::log(&format!("error powering off: {}", err)); + log!("error powering off: {}", err); exit(1); } exit(0); @@ -248,7 +250,7 @@ fn get_active_console() -> Option { None } Err(error) => { - utils::log(&format!("failed to open /proc/consoles: {}", error)); + log!("failed to open /proc/consoles: {}", error); None } } @@ -257,10 +259,10 @@ fn get_active_console() -> Option { fn configure_hostname() { if let Ok(hostname) = env::var("virtme_hostname") { if let Err(err) = sethostname(hostname) { - utils::log(&format!("failed to change hostname: {}", err)); + log!("failed to change hostname: {}", err); } } else { - utils::log("virtme_hostname is not defined"); + log!("virtme_hostname is not defined"); } } @@ -321,7 +323,7 @@ fn override_system_files() { fn set_cwd() { if let Ok(dir) = env::var("virtme_chdir") { if let Err(err) = env::set_current_dir(dir) { - utils::log(&format!("error changing directory: {}", err)); + log!("error changing directory: {}", err); } } } @@ -450,14 +452,14 @@ fn disable_uevent_helper() { if Path::new(uevent_helper_path).exists() { // This kills boot performance. - utils::log("you have CONFIG_UEVENT_HELPER on, turn it off"); + log!("you have CONFIG_UEVENT_HELPER on, turn it off"); let mut file = OpenOptions::new().write(true).open(uevent_helper_path).ok(); match &mut file { Some(file) => { write!(file, "").ok(); } None => { - utils::log(&format!("error opening {}", uevent_helper_path)); + log!("error opening {}", uevent_helper_path); } } } @@ -483,16 +485,16 @@ fn run_udevd() -> Option> { disable_uevent_helper(); let args: &[&str] = &["--daemon", "--resolve-names=never"]; utils::run_cmd(udevd_path, args); - utils::log("triggering udev coldplug"); + log!("triggering udev coldplug"); utils::run_cmd("udevadm", &["trigger", "--type=subsystems", "--action=add"]); utils::run_cmd("udevadm", &["trigger", "--type=devices", "--action=add"]); - utils::log("waiting for udev to settle"); + log!("waiting for udev to settle"); utils::run_cmd("udevadm", &["settle"]); - utils::log("udev is done"); + log!("udev is done"); }); Some(handle) } else { - utils::log("unable to find udevd, skip udev."); + log!("unable to find udevd, skip udev."); None } } @@ -547,7 +549,7 @@ fn setup_network() -> Option> { if cmdline.contains("virtme.dhcp") { if let Some(guest_tools_dir) = get_guest_tools_dir() { if let Some(network_dev) = get_network_device() { - utils::log(&format!("setting up network device {}", network_dev)); + log!("setting up network device {}", network_dev); let handle = thread::spawn(move || { let args = [ "udhcpc", @@ -584,9 +586,7 @@ fn run_user_script() { || !std::path::Path::new("/dev/virtio-ports/virtme.dev_stdout").exists() || !std::path::Path::new("/dev/virtio-ports/virtme.dev_stderr").exists() { - utils::log( - "virtme-init: cannot find script I/O ports; make sure virtio-serial is available", - ); + log!("virtme-init: cannot find script I/O ports; make sure virtio-serial is available",); } else { // Re-create stdout/stderr to connect to the virtio-serial ports. let io_files = [ @@ -677,7 +677,10 @@ fn configure_terminal(consdev: &str) { .stderr(Stdio::inherit()) // Replace the current init process with a shell session. .output(); - utils::log(String::from_utf8_lossy(&output.unwrap().stderr).trim_end_matches('\n')); + log!( + "{}", + String::from_utf8_lossy(&output.unwrap().stderr).trim_end_matches('\n') + ); } } @@ -740,7 +743,7 @@ fn run_user_gui(tty_fd: libc::c_int) { 0o0644, &format!("{}\n/bin/bash {}", pre_exec_cmd, USER_SCRIPT), ) { - utils::log(&format!("failed to generate {}: {}", xinitrc, err)); + log!("failed to generate {}: {}", xinitrc, err); return; } @@ -776,7 +779,7 @@ fn run_user_session() { let consdev = match get_active_console() { Some(console) => console, None => { - utils::log("failed to determine console"); + log!("failed to determine console"); Command::new("bash").arg("-l").exec(); return; } @@ -795,7 +798,7 @@ fn run_user_session() { } fn setup_user_session() { - utils::log("initialization done"); + log!("initialization done"); print_logo(); setup_root_home(); } diff --git a/src/utils.rs b/src/utils.rs index 46b5a17a..ac49a9f6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -17,7 +17,13 @@ use users::get_user_by_name; static PROG_NAME: &str = "virtme-ng-init"; -pub fn log(msg: &str) { +macro_rules! log { + ($($arg:tt)*) => { + $crate::utils::log_impl(&std::format!($($arg)*)) + }; +} + +pub fn log_impl(msg: &str) { if msg.is_empty() { return; } @@ -54,7 +60,7 @@ pub fn do_unlink(path: &str) { match std::fs::remove_file(path) { Ok(_) => (), Err(err) => { - log(&format!("failed to unlink file {}: {}", path, err)); + log!("failed to unlink file {}: {}", path, err); } } } @@ -68,7 +74,7 @@ fn do_touch(path: &str, mode: u32) { Ok(()) } if let Err(err) = _do_touch(path, mode) { - log(&format!("error creating file: {}", err)); + log!("error creating file: {}", err); } } @@ -86,10 +92,7 @@ pub fn do_symlink(src: &str, dst: &str) { match fs::symlink(src, dst) { Ok(_) => (), Err(err) => { - log(&format!( - "failed to create symlink {} -> {}: {}", - src, dst, err - )); + log!("failed to create symlink {} -> {}: {}", src, dst, err); } } } @@ -107,7 +110,7 @@ pub fn do_mount(source: &str, target: &str, fstype: &str, flags: usize, fsdata: Some(fsdata_cstr.as_ref()), ); if let Err(err) = result { - log(&format!("mount {} -> {}: {}", source, target, err)); + log!("mount {} -> {}: {}", source, target, err); } } @@ -122,15 +125,18 @@ pub fn run_cmd(cmd: impl AsRef, args: &[&str]) { match output { Ok(output) => { if !output.stderr.is_empty() { - log(String::from_utf8_lossy(&output.stderr).trim_end_matches('\n')); + log!( + "{}", + String::from_utf8_lossy(&output.stderr).trim_end_matches('\n') + ); } } Err(_) => { - log(&format!( + log!( "WARNING: failed to run: {:?} {}", cmd.as_ref(), args.join(" ") - )); + ); } } } From 602529a1927b6a5c6b7e303927d5c0bbe0632eb2 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 17:29:09 +0100 Subject: [PATCH 61/96] (Needlessly) optimise logging Before this commit, the caller of log_impl() (= the macro log!()) already did a memory allocation for constructing a String containing the message to log. This was then borrowed when calling log_impl(). This function then did another memory allocation plus copy to prepend "virtme-ng-init:" to the message. Then, if /dev/kmsg exists, another allocation is done to add a kernel log level tag at the beginning and a newline at the end. This commit starts by changing log!() from format!() to format_args!() which constructs an instance of std::fmt::Arguments. This is just a specification of how to do the formatting, but nothing was actually done yet. This is then used in log_impl() to directly construct a string with "virtme-ng-init:" prepended to the message to log. Since I wanted to avoid memory allocations, this then replaces .trim_end_matches('\n') with a manual loop. That way, we still have a String and do not switch over to a &str for the following code. Additionally, the "is the message to log empty?"-check was reformulated to "was there nothing appended to my prefix?". This makes things slightly more unreadable, but I couldn't find a better way to check whether the Arguments instance is empty. There is a slight change in behaviour here: Previously, log!("\n") would log an empty line since the code first checked if the message is empty and only afterwards trimmed trailing newlines. This is now done the other way around. Finally, when writing to /dev/kmsg, the String that we already have is mutated in places instead of allocating another one and copying all data to it. Why? Just because. I doubt that this will have any measurable performance benefits, but somehow it felt like the right thing to do. Signed-off-by: Uli Schlachter --- src/utils.rs | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index ac49a9f6..c8b6f195 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,6 +8,7 @@ use nix::mount::{mount, MsFlags}; use nix::sys::stat::Mode; use nix::unistd::{chown, Gid, Uid}; use std::ffi::{CString, OsStr}; +use std::fmt::Arguments; use std::fs::{File, OpenOptions}; use std::io::{self, Write}; use std::os::unix::fs; @@ -15,27 +16,39 @@ use std::os::unix::fs::PermissionsExt; use std::process::{Command, Stdio}; use users::get_user_by_name; -static PROG_NAME: &str = "virtme-ng-init"; - macro_rules! log { ($($arg:tt)*) => { - $crate::utils::log_impl(&std::format!($($arg)*)) + $crate::utils::log_impl(std::format_args!($($arg)*)) }; } -pub fn log_impl(msg: &str) { - if msg.is_empty() { +pub fn log_impl(msg: Arguments<'_>) { + static PREFIX: &str = "<6>virtme-ng-init: "; + static LOG_LEVEL: &str = "<6>"; + + let mut msg = format!("{}{}", PREFIX, msg); + + // Remove all trailing \n + while msg.ends_with('\n') { + msg.pop(); + } + + // Was the message empty? If so, do not log anything + if PREFIX == msg { return; } - let msg = format!("{}: {}", PROG_NAME, msg.trim_end_matches('\n')); - let mut file = OpenOptions::new().write(true).open("/dev/kmsg").ok(); - match &mut file { - Some(file) => { - let msg = format!("<6>{}\n", msg); + + match OpenOptions::new().write(true).open("/dev/kmsg") { + Ok(mut file) => { + msg.push('\n'); file.write(msg.as_bytes()).ok(); } - None => { - println!("{}", msg); + Err(_) => { + println!( + "{}", + msg.strip_prefix(LOG_LEVEL) + .expect("The message should always start with the log level") + ); } } } From 5a0e4c0e7140f232fa32cc3796b6d99a896147ca Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 17:31:06 +0100 Subject: [PATCH 62/96] Use write_all() instead of write() write() is allowed to write only partial data. Good luck debugging why some part of the log message is missing. This commit changes the logging to use write_all() instead, which ensures everything is written. This might end up doing multiple writes to /dev/kmsg, which would turn into multiple log records. I think that is still better than silently discarding some data. Signed-off-by: Uli Schlachter --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index c8b6f195..8559f6a2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -41,7 +41,7 @@ pub fn log_impl(msg: Arguments<'_>) { match OpenOptions::new().write(true).open("/dev/kmsg") { Ok(mut file) => { msg.push('\n'); - file.write(msg.as_bytes()).ok(); + file.write_all(msg.as_bytes()).ok(); } Err(_) => { println!( From 6341eb087cea80b65740c95ff376a7397fb212f3 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 17:43:56 +0100 Subject: [PATCH 63/96] Remove unnecessary trim_end_matches('\n') The logging code already does trim_end_matches('\n') so we do not have to the that in the caller, too. Signed-off-by: Uli Schlachter --- src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 41436f64..14d34f04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -677,10 +677,7 @@ fn configure_terminal(consdev: &str) { .stderr(Stdio::inherit()) // Replace the current init process with a shell session. .output(); - log!( - "{}", - String::from_utf8_lossy(&output.unwrap().stderr).trim_end_matches('\n') - ); + log!("{}", String::from_utf8_lossy(&output.unwrap().stderr)); } } From f42b2e4a7a7099b17cb62494e838805ee9d906ef Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Fri, 17 Nov 2023 18:10:10 -0800 Subject: [PATCH 64/96] utils: Don't log mount failures due to ENOENT Many of the paths virtme-ng-init tries to mount are distro-specific and may legitimately not exist; reduce the log noise by only logging mount failures for reasons other than ENOENT. Signed-off-by: Zev Weiss --- src/utils.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 8559f6a2..f59c1868 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -123,7 +123,9 @@ pub fn do_mount(source: &str, target: &str, fstype: &str, flags: usize, fsdata: Some(fsdata_cstr.as_ref()), ); if let Err(err) = result { - log!("mount {} -> {}: {}", source, target, err); + if err != nix::errno::Errno::ENOENT { + log!("mount {} -> {}: {}", source, target, err); + } } } From 42b6f241d1fb2036d919504f732128dbf0644569 Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Fri, 17 Nov 2023 18:30:24 -0800 Subject: [PATCH 65/96] Don't try to run systemd-tmpfiles on non-systemd systems Signed-off-by: Zev Weiss --- src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.rs b/src/main.rs index 14d34f04..a436b2c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -267,6 +267,9 @@ fn configure_hostname() { } fn run_systemd_tmpfiles() { + if !Path::new("/etc/systemd").exists() { + return; + } let args: &[&str] = &[ "--create", "--boot", From a1eb25c6e180142d28d80f5007432519dd767c4e Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Sat, 18 Nov 2023 16:14:00 -0800 Subject: [PATCH 66/96] Add guest hostname to /etc/hosts Some programs (e.g. 'sudo' in some configurations) apparently get confused if they can't resolve the hostname. To avoid such problems, if a hostname for the guest has been specified, bind-mount a replacement version of /etc/hosts with a pair of entries (IPv4 and IPv6) added for the given hostname. Signed-off-by: Zev Weiss --- src/main.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main.rs b/src/main.rs index a436b2c8..63494aa1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -317,10 +317,24 @@ fn generate_sudoers() -> io::Result<()> { Ok(()) } +fn generate_hosts() -> io::Result<()> { + if let Ok(hostname) = env::var("virtme_hostname") { + std::fs::copy("/etc/hosts", "/tmp/hosts")?; + let mut h = OpenOptions::new() + .write(true) + .append(true) + .open("/tmp/hosts")?; + writeln!(h, "\n127.0.0.1 {}\n::1 {}", hostname, hostname)?; + utils::do_mount("/tmp/hosts", "/etc/hosts", "", libc::MS_BIND as usize, ""); + } + Ok(()) +} + fn override_system_files() { generate_fstab().ok(); generate_shadow().ok(); generate_sudoers().ok(); + generate_hosts().ok(); } fn set_cwd() { From 97070a756f615ad318fee13c806f2037feccdba2 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 19 Nov 2023 09:59:13 +0100 Subject: [PATCH 67/96] virtme-ng-init: always set XDG_RUNTIME_DIR XDG_RUNTIME_DIR is not strictly related to X (graphical) applications, according to [1]: XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and other file objects (such as sockets, named pipes, ...) should be stored. Always setting this variable doesn't really introduce any measurable overhead and it allows to potentially extend the variety of tests and applications running inside virtme-ng. Therefore make sure to set this variable and initialize the XDG_RUNTIME_DIR properly also when we are running in non-graphical mode. [1] https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables Signed-off-by: Andrea Righi --- src/main.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index 63494aa1..18248490 100644 --- a/src/main.rs +++ b/src/main.rs @@ -725,21 +725,7 @@ fn run_shell(tty_fd: libc::c_int, args: &[&str]) { } } -fn init_xdg_runtime_dir() { - // Initialize XDG_RUNTIME_DIR (required to provide a better compatibility with graphic apps). - let uid = env::var("virtme_user") - .ok() - .and_then(|user| utils::get_user_id(&user)) - .unwrap_or(0); - let dir = format!("/run/user/{}", uid); - utils::do_mkdir(&dir); - utils::do_chown(&dir, uid, uid).ok(); - env::set_var("XDG_RUNTIME_DIR", dir); -} - fn run_user_gui(tty_fd: libc::c_int) { - init_xdg_runtime_dir(); - // Generate a bare minimum xinitrc let xinitrc = "/tmp/.xinitrc"; @@ -778,6 +764,15 @@ fn run_user_gui(tty_fd: libc::c_int) { run_shell(tty_fd, &args); } +fn init_xdg_runtime_dir(uid: u32) { + // $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential + // runtime files and other file objects (such as sockets, named pipes, ...) should be stored. + let dir = format!("/run/user/{}", uid); + utils::do_mkdir(&dir); + utils::do_chown(&dir, uid, uid).ok(); + env::set_var("XDG_RUNTIME_DIR", dir); +} + fn run_user_shell(tty_fd: libc::c_int) { let mut args = vec!["-l"]; let storage; @@ -790,6 +785,11 @@ fn run_user_shell(tty_fd: libc::c_int) { } fn run_user_session() { + let uid = env::var("virtme_user") + .ok() + .and_then(|user| utils::get_user_id(&user)) + .unwrap_or(0); + let consdev = match get_active_console() { Some(console) => console, None => { @@ -800,6 +800,8 @@ fn run_user_session() { }; configure_terminal(consdev.as_str()); + init_xdg_runtime_dir(uid); + let flags = OFlag::O_RDWR | OFlag::O_NONBLOCK; let mode = Mode::empty(); let tty_fd = open(consdev.as_str(), flags, mode).expect("failed to open console"); From f6a7ef513cf93fc126d7fe8f619b76a8ea991f93 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 19 Nov 2023 10:08:40 +0100 Subject: [PATCH 68/96] virtme-ng-init: set proper ownership of the default console device Running a virtme-ng session as non-root may trigger some /dev/stdout permission errors, for example running a kselftest as a regular user: $ vng --user $USER -- make kselftest TARGETS=size make[3]: Entering directory '/home/arighi/src/linux/tools/testing/selftests/size' make[3]: Leaving directory '/home/arighi/src/linux/tools/testing/selftests/size' make[3]: Entering directory '/home/arighi/src/linux/tools/testing/selftests/size' TAP version 13 1..1 /bin/sh: 100: cannot create /dev/stdout: Permission denied # selftests: size: get_size /bin/sh: 127: cannot create /dev/stdout: Permission denied not ok 1 selftests: size: get_size # exit=2 make[3]: Leaving directory '/home/arighi/src/linux/tools/testing/selftests/size' This happens because /dev/stdout points to the default console device that has root:root ownership by default. Fix this by setting the proper ownership of the console device. This fixes issue #5. Signed-off-by: Andrea Righi --- src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 18248490..49494f95 100644 --- a/src/main.rs +++ b/src/main.rs @@ -685,7 +685,7 @@ fn clear_virtme_envs() { } } -fn configure_terminal(consdev: &str) { +fn configure_terminal(consdev: &str, uid: u32) { if let Ok(params) = env::var("virtme_stty_con") { let output = Command::new("stty") .args(params.split_whitespace()) @@ -695,6 +695,8 @@ fn configure_terminal(consdev: &str) { // Replace the current init process with a shell session. .output(); log!("{}", String::from_utf8_lossy(&output.unwrap().stderr)); + // Set proper user ownership on the default console device + utils::do_chown(&consdev, uid, uid).ok(); } } @@ -798,7 +800,7 @@ fn run_user_session() { return; } }; - configure_terminal(consdev.as_str()); + configure_terminal(consdev.as_str(), uid); init_xdg_runtime_dir(uid); From dcefa64504ce41f6da8d9d5ef31bb8d10a6ecf89 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 19 Nov 2023 11:03:46 +0100 Subject: [PATCH 69/96] virtme-ng-init: set the proper ownership on the virtio-ports devices Make sure to se the right ownership also on the virtio-ports devices that are used as default stdin/stdout/stderr when running scripts in non-interactive mode. This completes all the possible cases addressed in issue #5. Signed-off-by: Andrea Righi --- src/main.rs | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 49494f95..289079d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -596,7 +596,7 @@ fn extract_user_script(virtme_script: &str) -> Option { String::from_utf8(BASE64.decode(encoded_cmd).ok()?).ok() } -fn run_user_script() { +fn run_user_script(uid: u32) { if !std::path::Path::new("/dev/virtio-ports/virtme.stdin").exists() || !std::path::Path::new("/dev/virtio-ports/virtme.stdout").exists() || !std::path::Path::new("/dev/virtio-ports/virtme.stderr").exists() @@ -615,6 +615,7 @@ fn run_user_script() { if std::path::Path::new(dst).exists() { utils::do_unlink(dst); } + utils::do_chown(src, uid, uid).ok(); utils::do_symlink(src, dst); } @@ -659,12 +660,12 @@ fn create_user_script(cmd: &str) { utils::create_file(USER_SCRIPT, 0o0755, cmd).expect("Failed to create virtme-script file"); } -fn setup_user_script() { +fn setup_user_script(uid: u32) { if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { if let Some(cmd) = extract_user_script(&cmdline) { create_user_script(&cmd); if env::var("virtme_graphics").is_err() { - run_user_script(); + run_user_script(uid); } } } @@ -695,9 +696,9 @@ fn configure_terminal(consdev: &str, uid: u32) { // Replace the current init process with a shell session. .output(); log!("{}", String::from_utf8_lossy(&output.unwrap().stderr)); - // Set proper user ownership on the default console device - utils::do_chown(&consdev, uid, uid).ok(); } + // Set proper user ownership on the default console device + utils::do_chown(&consdev, uid, uid).ok(); } fn detach_from_terminal(tty_fd: libc::c_int) { @@ -783,10 +784,25 @@ fn run_user_shell(tty_fd: libc::c_int) { storage = format!("su {}", user); args.push(&storage); } + print_logo(); run_shell(tty_fd, &args); } -fn run_user_session() { +fn run_user_session(consdev: &str, uid: u32) { + let flags = OFlag::O_RDWR | OFlag::O_NONBLOCK; + let mode = Mode::empty(); + let tty_fd = open(consdev, flags, mode).expect("failed to open console"); + + setup_user_script(uid); + + if env::var("virtme_graphics").is_ok() { + run_user_gui(tty_fd); + } else { + run_user_shell(tty_fd); + } +} + +fn setup_user_session() { let uid = env::var("virtme_user") .ok() .and_then(|user| utils::get_user_id(&user)) @@ -801,24 +817,12 @@ fn run_user_session() { } }; configure_terminal(consdev.as_str(), uid); - init_xdg_runtime_dir(uid); + setup_root_home(); - let flags = OFlag::O_RDWR | OFlag::O_NONBLOCK; - let mode = Mode::empty(); - let tty_fd = open(consdev.as_str(), flags, mode).expect("failed to open console"); - - if env::var("virtme_graphics").is_ok() { - run_user_gui(tty_fd); - } else { - run_user_shell(tty_fd); - } -} - -fn setup_user_session() { log!("initialization done"); - print_logo(); - setup_root_home(); + + run_user_session(consdev.as_str(), uid); } fn run_snapd() { @@ -901,9 +905,7 @@ fn main() { // Start user session (batch or interactive). set_cwd(); - setup_user_script(); setup_user_session(); - run_user_session(); // Shutdown the system. poweroff(); From 2ed06413dc85877fe6c149366d0fbc482cd72d17 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 22 Nov 2023 08:59:03 +0100 Subject: [PATCH 70/96] virtme-ng-init: fix build error with older versions of rustc In older versions of rustc (such as 1.66.1) we can trigger the following build error: error[E0308]: mismatched types --> src/main.rs:632:20 | 632 | ("su", vec![&user, "-c", USER_SCRIPT]) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | expected slice, found array `[&str; 3]` | arguments to this function are incorrect | = note: expected struct `Box<[&String], _>` found struct `Box<[&str; 3], std::alloc::Global>` This happens becausee we are using a mixture of &String and &str types with the vec! macro. To prevent this make sure that the types match, by explicitly converting user to &str when creating the vector. Signed-off-by: Andrea Righi --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 289079d6..6a58ee94 100644 --- a/src/main.rs +++ b/src/main.rs @@ -629,7 +629,7 @@ fn run_user_script(uid: u32) { // Determine if we need to switch to a different user, or if we can run the script as root. let user = env::var("virtme_user").unwrap_or_else(|_| String::new()); let (cmd, args) = if !user.is_empty() { - ("su", vec![&user, "-c", USER_SCRIPT]) + ("su", vec![user.as_str(), "-c", USER_SCRIPT]) } else { ("/bin/sh", vec![USER_SCRIPT]) }; From 607150cef0adf49c3cbf5b2840ed517f75cba17c Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 23 Nov 2023 22:30:06 +0100 Subject: [PATCH 71/96] virtme-ng-init: add default secure_path to /etc/sudoers When executing vng as a non-privileged user without /sbin in their $PATH, attempting to run a command located in that directory with sudo will not succeed. Fix by defining a proper secure_path in /etc/sudoers. The same change has been applied to virtme-init in the virtme-ng repository, see commit: ac29432 ("virtme-init: Add default secure_path") Signed-off-by: Andrea Righi --- src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6a58ee94..d9e1d625 100644 --- a/src/main.rs +++ b/src/main.rs @@ -307,10 +307,11 @@ fn generate_shadow() -> io::Result<()> { fn generate_sudoers() -> io::Result<()> { if let Ok(user) = env::var("virtme_user") { let fname = "/tmp/sudoers"; - let content = format!( - "root ALL = (ALL) NOPASSWD: ALL\n{} ALL = (ALL) NOPASSWD: ALL\n", - user - ); + let content = "Defaults secure_path=\"/usr/sbin:/usr/bin:/sbin:/bin\"\n".to_string() + + &format!( + "root ALL = (ALL) NOPASSWD: ALL\n{} ALL = (ALL) NOPASSWD: ALL\n", + user + ); utils::create_file(fname, 0o0440, &content).ok(); utils::do_mount(fname, "/etc/sudoers", "", libc::MS_BIND as usize, ""); } From 875a6ca288e11b53d1be62c729e9041f11e79355 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Fri, 1 Dec 2023 15:37:19 +0100 Subject: [PATCH 72/96] virtme-ng-init: drop usage of group when chwon'ing Ignore group id with chown to make virtme-ng-init more portable across distributions. A similar change has been already applied to virtme-init, see: https://github.com/arighi/virtme-ng/commit/3cd03e04fe9af5e77bf4d11d094c3ed588d216e5 Signed-off-by: Andrea Righi --- src/main.rs | 6 +++--- src/utils.rs | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index d9e1d625..76ca676a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -616,7 +616,7 @@ fn run_user_script(uid: u32) { if std::path::Path::new(dst).exists() { utils::do_unlink(dst); } - utils::do_chown(src, uid, uid).ok(); + utils::do_chown(src, uid, None).ok(); utils::do_symlink(src, dst); } @@ -699,7 +699,7 @@ fn configure_terminal(consdev: &str, uid: u32) { log!("{}", String::from_utf8_lossy(&output.unwrap().stderr)); } // Set proper user ownership on the default console device - utils::do_chown(&consdev, uid, uid).ok(); + utils::do_chown(&consdev, uid, None).ok(); } fn detach_from_terminal(tty_fd: libc::c_int) { @@ -773,7 +773,7 @@ fn init_xdg_runtime_dir(uid: u32) { // runtime files and other file objects (such as sockets, named pipes, ...) should be stored. let dir = format!("/run/user/{}", uid); utils::do_mkdir(&dir); - utils::do_chown(&dir, uid, uid).ok(); + utils::do_chown(&dir, uid, None).ok(); env::set_var("XDG_RUNTIME_DIR", dir); } diff --git a/src/utils.rs b/src/utils.rs index f59c1868..20b3e102 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -57,9 +57,11 @@ pub fn get_user_id(username: &str) -> Option { Some(get_user_by_name(username)?.uid()) } -pub fn do_chown(path: &str, uid: u32, gid: u32) -> io::Result<()> { - chown(path, Some(Uid::from_raw(uid)), Some(Gid::from_raw(gid))) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; +pub fn do_chown(path: &str, uid: u32, gid: Option) -> std::io::Result<()> { + let gid_option = gid.map(|gid| Gid::from_raw(gid)); + + chown(path, Some(Uid::from_raw(uid)), gid_option) + .map_err(|err| io::Error::new(std::io::ErrorKind::Other, err))?; Ok(()) } From 6ed126b67fc82942f66bcf5c091487d97dae9b99 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 21 Dec 2023 10:27:54 +0100 Subject: [PATCH 73/96] virtme-ng-init: docker host support Minor changes required to run virtme-ng instances inside docker containers. Link: https://github.com/arighi/virtme-ng/issues/51 Signed-off-by: Andrea Righi --- src/main.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 76ca676a..c673553e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -411,6 +411,12 @@ fn mount_kernel_modules() { let kver = get_kernel_version(false); let mod_dir = format!("/lib/modules/{}", kver); + // Make sure to always have /lib/modules, otherwise we won't be able to configure kmod support + // properly (this can happen in some container environments, such as docker). + if !Path::new(&mod_dir).exists() { + utils::do_mkdir("/lib/modules"); + } + if env::var("virtme_root_mods").is_ok() { // /lib/modules is already set up. } else if let Ok(dir) = env::var("virtme_link_mods") { @@ -673,9 +679,12 @@ fn setup_user_script(uid: u32) { } fn setup_root_home() { - utils::do_mkdir("/tmp/roothome"); - utils::do_mount("/tmp/roothome", "/root", "", libc::MS_BIND as usize, ""); - env::set_var("HOME", "/tmp/roothome"); + // Set up a basic environment (unless virtme-ng is running as root on the host) + if let Err(_) = env::var("virtme_root_user") { + utils::do_mkdir("/tmp/roothome"); + utils::do_mount("/tmp/roothome", "/root", "", libc::MS_BIND as usize, ""); + env::set_var("HOME", "/tmp/roothome"); + } } fn clear_virtme_envs() { From a764cc3cadbac6b6b5a16c4ac344f2c42acfd9e5 Mon Sep 17 00:00:00 2001 From: Neill Kapron Date: Tue, 2 Jan 2024 21:13:38 +0000 Subject: [PATCH 74/96] virtme-ng-init: do not remount /run In the case where a rootfs is specified when launching virtme-ng, it mounts /run and /run/virtme/guesttools prior to executing virtme-ng-init. We do not want to re-mount /run, as we will lose access to guesttools, which is required for network setup. Note, get_test_tools_dir() relies on /proc, so that must be mounted before /run. Signed-off-by: Neill Kapron --- src/main.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main.rs b/src/main.rs index c673553e..d27fab9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -364,6 +364,21 @@ fn symlink_fds() { fn mount_kernel_filesystems() { for mount_info in KERNEL_MOUNTS { + // In the case where a rootfs is specified when launching virtme-ng, it + // mounts /run and /run/virtme/guesttools prior to executing + // virtme-ng-init. We do not want to re-mount /run, as we will lose + // access to guesttools, which is required for network setup. + // + // Note, get_test_tools_dir() relies on /proc, so that must be mounted + // prior to /run. + if mount_info.target == "/run" { + if let Some(guest_tools_dir) = get_guest_tools_dir() { + if guest_tools_dir.starts_with("/run") { + log!("/run previously mounted, skipping"); + continue; + } + } + } utils::do_mount( mount_info.source, mount_info.target, From b8cba09b3cef230cf80aa63fc8dec24f913809c9 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 31 Jan 2024 10:45:06 +0100 Subject: [PATCH 75/96] virtme-ng-init: channel the return code of a command to the host Send the return code of the command executed inside the guest to the host. This allows to run commands inside a guest and give users the impression that they are running on the host. This feature is particularly useful for automation with virtme-ng, since it easily allows to check the return code of a script executed inside virtme-ng as if it was running directly on the host. Signed-off-by: Andrea Righi --- src/main.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index d27fab9e..c1b8e8d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -629,11 +629,15 @@ fn run_user_script(uid: u32) { } else { // Re-create stdout/stderr to connect to the virtio-serial ports. let io_files = [ + ("/dev/virtio-ports/virtme.ret", "/dev/virtme.ret"), ("/dev/virtio-ports/virtme.dev_stdin", "/dev/stdin"), ("/dev/virtio-ports/virtme.dev_stdout", "/dev/stdout"), ("/dev/virtio-ports/virtme.dev_stderr", "/dev/stderr"), ]; for (src, dst) in io_files.iter() { + if !std::path::Path::new(src).exists() { + continue; + } if std::path::Path::new(dst).exists() { utils::do_unlink(dst); } @@ -657,7 +661,7 @@ fn run_user_script(uid: u32) { }; clear_virtme_envs(); unsafe { - Command::new(cmd) + let ret = Command::new(cmd) .args(&args) .pre_exec(move || { nix::libc::setsid(); @@ -673,6 +677,18 @@ fn run_user_script(uid: u32) { }) .output() .expect("Failed to execute script"); + + // Channel the return code to the host via /dev/virtme.ret + if let Ok(mut file) = OpenOptions::new().write(true).open("/dev/virtme.ret") { + // Write the value of output.status.code() to the file + if let Some(code) = ret.status.code() { + file.write_all(code.to_string().as_bytes()) + .expect("Failed to write to file"); + } else { + // Handle the case where output.status.code() is None + file.write_all(b"-1").expect("Failed to write to file"); + } + } } poweroff(); } From b1a0f1ea34e29f97c9046bd0c46fd21187ce91bf Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 4 Feb 2024 10:35:41 +0100 Subject: [PATCH 76/96] virtme-ng-init: allow virtme-ng to specify a console device If virtme-ng defines a specific virtme_console device in the kernel boot parameter use that console, instead of trying to autodetect one. Otherwise try to detect a valid console from /proc/consoles. This is required to implement kernel log redirection to stderr as disussed in https://github.com/arighi/virtme-ng/discussions/60. Signed-off-by: Andrea Righi --- src/main.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index c1b8e8d9..6abca100 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ use nix::unistd::sethostname; use std::env; use std::fs::{File, OpenOptions}; use std::io::{self, BufRead, BufReader, BufWriter, Write}; +use std::os::fd::{AsRawFd, IntoRawFd}; use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process::{exit, id, Command, Stdio}; @@ -234,7 +235,7 @@ fn get_kernel_version(show_machine: bool) -> String { } } -fn get_active_console() -> Option { +fn get_legacy_active_console() -> Option { // See Documentation/filesystems/proc.rst for /proc/consoles documentation. match File::open("/proc/consoles") { Ok(file) => { @@ -256,6 +257,14 @@ fn get_active_console() -> Option { } } +fn get_active_console() -> Option { + if let Ok(console) = env::var("virtme_console") { + Some(format!("/dev/{}", console)) + } else { + get_legacy_active_console() + } +} + fn configure_hostname() { if let Ok(hostname) = env::var("virtme_hostname") { if let Err(err) = sethostname(hostname) { @@ -727,6 +736,33 @@ fn clear_virtme_envs() { } } +// Redirect a file descriptor to another. +fn redirect_fd(src_fd: i32, dst_fd: i32) { + unsafe { + libc::dup2(src_fd, dst_fd); + } +} + +// Redirect stdout/stderr to a new console device. +fn redirect_console(consdev: &str) { + let file = OpenOptions::new() + .write(true) + .open(consdev) + .expect("Failed to open console device"); + + let fd = file.into_raw_fd(); + + let stdout = std::io::stdout(); + let handle = stdout.lock(); + let stdout_fd = handle.as_raw_fd(); + redirect_fd(fd, stdout_fd); + + let stderr = std::io::stderr(); + let handle = stderr.lock(); + let stderr_fd = handle.as_raw_fd(); + redirect_fd(fd, stderr_fd); +} + fn configure_terminal(consdev: &str, uid: u32) { if let Ok(params) = env::var("virtme_stty_con") { let output = Command::new("stty") @@ -740,6 +776,9 @@ fn configure_terminal(consdev: &str, uid: u32) { } // Set proper user ownership on the default console device utils::do_chown(&consdev, uid, None).ok(); + + // Redirect stdout/stderr to the new console device. + redirect_console(&consdev); } fn detach_from_terminal(tty_fd: libc::c_int) { From f75260d311eb4e5facb34fdb289effaee4a7ba11 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 13 Feb 2024 16:39:18 +0100 Subject: [PATCH 77/96] virtme-ng-init: always override sudoers Always the host sudoers with a local copy even when running vng with `--user root`, in this way root can also run `sudo` if needed. Signed-off-by: Andrea Righi --- src/main.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6abca100..09421787 100644 --- a/src/main.rs +++ b/src/main.rs @@ -314,16 +314,14 @@ fn generate_shadow() -> io::Result<()> { } fn generate_sudoers() -> io::Result<()> { + let fname = "/tmp/sudoers"; + let mut content = "Defaults secure_path=\"/usr/sbin:/usr/bin:/sbin:/bin\"\n".to_string(); + content += "root ALL = (ALL) NOPASSWD: ALL\n"; if let Ok(user) = env::var("virtme_user") { - let fname = "/tmp/sudoers"; - let content = "Defaults secure_path=\"/usr/sbin:/usr/bin:/sbin:/bin\"\n".to_string() - + &format!( - "root ALL = (ALL) NOPASSWD: ALL\n{} ALL = (ALL) NOPASSWD: ALL\n", - user - ); - utils::create_file(fname, 0o0440, &content).ok(); - utils::do_mount(fname, "/etc/sudoers", "", libc::MS_BIND as usize, ""); + content += &format!("{} ALL = (ALL) NOPASSWD: ALL\n", user); } + utils::create_file(fname, 0o0440, &content).ok(); + utils::do_mount(fname, "/etc/sudoers", "", libc::MS_BIND as usize, ""); Ok(()) } From b4a404d4044e8e8cc3b29937c69605532047f70a Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 22 Feb 2024 09:57:52 +0100 Subject: [PATCH 78/96] virtme-ng-init: handle command line option nr_open Handle kernel boot option `nr_open`, used to set the maximum amount of open files (/proc/sys/fs/nr_open) in the virtme-ng guest. Signed-off-by: Andrea Righi --- src/main.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.rs b/src/main.rs index 09421787..5c5a5aa1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -265,6 +265,15 @@ fn get_active_console() -> Option { } } +fn configure_limits() { + if let Ok(nr_open) = env::var("nr_open") { + if let Ok(mut file) = OpenOptions::new().write(true).open("/proc/sys/fs/nr_open") { + file.write_all(nr_open.as_bytes()) + .expect("Failed to write nr_open"); + } + } +} + fn configure_hostname() { if let Ok(hostname) = env::var("virtme_hostname") { if let Err(err) = sethostname(hostname) { @@ -966,6 +975,7 @@ fn main() { // Basic system initialization (order is important here). configure_environment(); + configure_limits(); configure_hostname(); mount_kernel_filesystems(); mount_virtme_overlays(); From 49615abce6beb3ae158893f1599b60b099ba987c Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 22 Feb 2024 10:11:09 +0100 Subject: [PATCH 79/96] virtme-ng-init: apply limits after mounting kernel filesystems We need to apply system/kernel limits after mounting all the required kernel filesystems (e.g., procfs, sysfs). Signed-off-by: Andrea Righi --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 5c5a5aa1..6a1bc085 100644 --- a/src/main.rs +++ b/src/main.rs @@ -975,9 +975,9 @@ fn main() { // Basic system initialization (order is important here). configure_environment(); - configure_limits(); configure_hostname(); mount_kernel_filesystems(); + configure_limits(); mount_virtme_overlays(); mount_sys_filesystems(); mount_kernel_modules(); From a0d6feef6d3fd6b8869b5eddd06134a5abd5187d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 22 Feb 2024 11:03:57 +0100 Subject: [PATCH 80/96] virtme-ng-init: hide additional sudo settings Try to keep sudo settings as simple as possible and rely only on our custom /etc/sudoers. This can help to prevent potential permissions errors while using sudo inside a virtme-ng guest. Signed-off-by: Andrea Righi --- src/main.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.rs b/src/main.rs index 6a1bc085..19e4949f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -174,6 +174,13 @@ const SYSTEM_MOUNTS: &[MountInfo] = &[ flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, fsdata: "", }, + MountInfo { + source: "tmpfs", + target: "/var/lib/sudo", + fs_type: "tmpfs", + flags: (libc::MS_NOSUID | libc::MS_NODEV) as usize, + fsdata: "", + }, MountInfo { source: "tmpfs", target: "/var/lib/apt", @@ -329,6 +336,9 @@ fn generate_sudoers() -> io::Result<()> { if let Ok(user) = env::var("virtme_user") { content += &format!("{} ALL = (ALL) NOPASSWD: ALL\n", user); } + if !Path::new("/etc/sudoers").exists() { + utils::create_file("/etc/sudoers", 0o0440, "").unwrap_or_else(|_| {}); + } utils::create_file(fname, 0o0440, &content).ok(); utils::do_mount(fname, "/etc/sudoers", "", libc::MS_BIND as usize, ""); Ok(()) From c4bad3d100aeb6867d3f7930186654739ffeccd4 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 22 Feb 2024 19:23:16 +0100 Subject: [PATCH 81/96] virtme-ng-init: print a hint to exit from virtme-ng Print a hint to use CTRL+d to exit from the current virtme-ng session and go back to the host. Signed-off-by: Andrea Righi --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 19e4949f..bb8c3ebb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -976,7 +976,8 @@ fn print_logo() { \_/ |_|_| \__|_| |_| |_|\___| |_| |_|\__ | |___/"#; println!("{}", logo.trim_start_matches('\n')); - println!(" kernel version: {}\n", get_kernel_version(true)); + println!(" kernel version: {}", get_kernel_version(true)); + println!(" (CTRL+d to exit)\n"); } fn main() { From 450872b50abb423c855f32994af32917a4f9db6a Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sat, 2 Mar 2024 14:50:57 +0100 Subject: [PATCH 82/96] virtme-ng-init: allow to mount legacy cgroupfs (v1) Allow to mount legacy cgroup when SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 is passed to the kernel boot command options. This mimics systemd's behavior, as specified in the systemd documentation: ``` CHANGES WITH 256 in spe: ... * Support for cgroup v1 ('legacy' and 'hybrid' hierarchies) is now considered obsolete and systemd by default will refuse to boot under it. To forcibly reenable cgroup v1 support, SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 must be set on kernel command line. ... ``` Moreover, with cgroup v1, only try to mount the same legacy cgroup subsystems supported by systemd (also from the systemd doc): ``` * on cgroup v1: `cpu`, `cpuacct`, `blkio`, `memory`, `devices`, `pids` ``` Signed-off-by: Andrea Righi --- src/main.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index bb8c3ebb..2b065c53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -108,13 +108,6 @@ const KERNEL_MOUNTS: &[MountInfo] = &[ flags: 0, fsdata: "", }, - MountInfo { - source: "cgroup2", - target: "/sys/fs/cgroup", - fs_type: "cgroup2", - flags: 0, - fsdata: "", - }, ]; const SYSTEM_MOUNTS: &[MountInfo] = &[ @@ -415,6 +408,25 @@ fn mount_kernel_filesystems() { } } +fn mount_cgroupfs() { + // If SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 is passed we can mimic systemd's behavior and mount + // the legacy cgroup v1 layout. + let cmdline = std::fs::read_to_string("/proc/cmdline").unwrap(); + if cmdline.contains("SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1") { + utils::do_mount("cgroup", "/sys/fs/cgroup", "tmpfs", 0, ""); + let subsystems = vec!["cpu", "cpuacct", "blkio", "memory", "devices", "pids"]; + for subsys in &subsystems { + let target = format!("/sys/fs/cgroup/{}", subsys); + utils::do_mkdir(&target); + // Don't treat failure as critical here, since the kernel may not + // support all the legacy cgroups. + utils::do_mount(subsys, &target, "cgroup", 0, subsys); + } + } else { + utils::do_mount("cgroup2", "/sys/fs/cgroup", "cgroup2", 0, ""); + } +} + fn mount_virtme_overlays() { for (key, path) in env::vars() { if key.starts_with("virtme_rw_overlay") { @@ -988,6 +1000,7 @@ fn main() { configure_environment(); configure_hostname(); mount_kernel_filesystems(); + mount_cgroupfs(); configure_limits(); mount_virtme_overlays(); mount_sys_filesystems(); From 6d8e66513294328e257a8ba4964cde5e9db985f3 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Thu, 14 Mar 2024 18:51:15 +0100 Subject: [PATCH 83/96] init: set the HOME env var if root When virtme-ng was running as root, the HOME dir was set to '/'. Now it is set to '/root', the expected value. Fixes: 6ed126b ("virtme-ng-init: docker host support") Signed-off-by: Matthieu Baerts (NGI0) --- src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.rs b/src/main.rs index 2b065c53..b22db05c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -753,6 +753,8 @@ fn setup_root_home() { utils::do_mkdir("/tmp/roothome"); utils::do_mount("/tmp/roothome", "/root", "", libc::MS_BIND as usize, ""); env::set_var("HOME", "/tmp/roothome"); + } else { + env::set_var("HOME", "/root"); } } From eff61a89905f3540067d193b0395e3e785d360e9 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Fri, 15 Mar 2024 07:49:12 +0100 Subject: [PATCH 84/96] virtme-ng-init: properly configure terminal line settings Make sure to redirect stdout/stderr to the right console before applying the proper terminal settings via stty. Link: https://github.com/arighi/virtme-ng/issues/90 Signed-off-by: Andrea Righi --- src/main.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index b22db05c..8cb72d3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -795,6 +795,12 @@ fn redirect_console(consdev: &str) { } fn configure_terminal(consdev: &str, uid: u32) { + // Set proper user ownership on the default console device + utils::do_chown(&consdev, uid, None).ok(); + + // Redirect stdout/stderr to the new console device. + redirect_console(&consdev); + if let Ok(params) = env::var("virtme_stty_con") { let output = Command::new("stty") .args(params.split_whitespace()) @@ -805,11 +811,6 @@ fn configure_terminal(consdev: &str, uid: u32) { .output(); log!("{}", String::from_utf8_lossy(&output.unwrap().stderr)); } - // Set proper user ownership on the default console device - utils::do_chown(&consdev, uid, None).ok(); - - // Redirect stdout/stderr to the new console device. - redirect_console(&consdev); } fn detach_from_terminal(tty_fd: libc::c_int) { From 956df719be9fd85d2fa061e21cbe7242dd45749a Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 20 Mar 2024 07:24:13 +0100 Subject: [PATCH 85/96] add Cargo.lock As mentioned in #8 having a Cargo.lock can be helpful to bisect potential issues caused by interactions with external dependencies. Having a Cargo.lock in the repo allows to achieve a more reproducible state at a certain point time. Link: https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control Suggested-by: @zyklotomic Signed-off-by: Andrea Righi --- Cargo.lock | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..981d3c57 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,86 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "virtme-ng-init" +version = "0.1.0" +dependencies = [ + "base64", + "nix", + "users", +] From 9994df436b623e559f3216241516343602b28fd6 Mon Sep 17 00:00:00 2001 From: Thibault Ferrante Date: Fri, 24 May 2024 17:09:02 +0200 Subject: [PATCH 86/96] README.md: add building and installation section Signed-off-by: Thibault Ferrante --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 1b9fc3b1..e8d1d6ec 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,23 @@ user 0m0.736s sys 0m0.795s ``` +# Building + +Static building is necessary as this binary is going to be executed +before the file system is up and running. + +``` +RUSTFLAGS='-C target-feature=+crt-static' cargo build -r +``` + +# Local installation + +Put the binary into virtme/guest/bin/. +e.g. when used as a submodule: +``` +cp target/release/virtme-ng-init ../virtme/guest/bin +``` + # Credits Author: Andrea Righi From 91d4387c31f97421633f8d028642c2edff3345fa Mon Sep 17 00:00:00 2001 From: Thibault Ferrante Date: Fri, 24 May 2024 16:59:54 +0200 Subject: [PATCH 87/96] virtme-ng-init: allow /tmp to be mounted from host Use /run to store overlays upper/workdir to be able to mount host tmp. Signed-off-by: Thibault Ferrante --- src/main.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8cb72d3d..089475b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,13 +59,6 @@ const KERNEL_MOUNTS: &[MountInfo] = &[ flags: (libc::MS_NOSUID | libc::MS_NOEXEC | libc::MS_NODEV) as usize, fsdata: "", }, - MountInfo { - source: "tmpfs", - target: "/tmp", - fs_type: "tmpfs", - flags: 0, - fsdata: "", - }, MountInfo { source: "run", target: "/run", @@ -428,9 +421,10 @@ fn mount_cgroupfs() { } fn mount_virtme_overlays() { + utils::do_mkdir("/run/tmp/"); for (key, path) in env::vars() { if key.starts_with("virtme_rw_overlay") { - let dir = &format!("/tmp/{}", key); + let dir = &format!("/run/tmp/{}", key); let upperdir = &format!("{}/upper", dir); let workdir = &format!("{}/work", dir); let mnt_opts = &format!( From eeaf7c53b16f6fb00a259cd04279c4d92f231853 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sat, 25 May 2024 09:54:25 +0200 Subject: [PATCH 88/96] virtme-ng-init: move all temp files to /run/tmp Now that we export the host /tmp inside the guest (#9) we may want to move all the temporary files to a different place to avoid mixing virmtme-ng's temporary files with the host's files. Therefore, move all the virtme-ng-init temporary files to /run/tmp. Signed-off-by: Andrea Righi --- src/main.rs | 52 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/main.rs b/src/main.rs index 089475b5..83651e7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -190,7 +190,7 @@ const SYSTEM_MOUNTS: &[MountInfo] = &[ }, ]; -const USER_SCRIPT: &str = "/tmp/.virtme-script"; +const USER_SCRIPT: &str = "/run/tmp/.virtme-script"; fn check_init_pid() { if id() != 1 { @@ -291,16 +291,22 @@ fn run_systemd_tmpfiles() { } fn generate_fstab() -> io::Result<()> { - utils::create_file("/tmp/fstab", 0o0664, "").ok(); - utils::do_mount("/tmp/fstab", "/etc/fstab", "", libc::MS_BIND as usize, ""); + utils::create_file("/run/tmp/fstab", 0o0664, "").ok(); + utils::do_mount( + "/run/tmp/fstab", + "/etc/fstab", + "", + libc::MS_BIND as usize, + "", + ); Ok(()) } fn generate_shadow() -> io::Result<()> { - utils::create_file("/tmp/shadow", 0o0644, "").ok(); + utils::create_file("/run/tmp/shadow", 0o0644, "").ok(); let input_file = File::open("/etc/passwd")?; - let output_file = File::create("/tmp/shadow")?; + let output_file = File::create("/run/tmp/shadow")?; let reader = BufReader::new(input_file); let mut writer = BufWriter::new(output_file); @@ -310,13 +316,19 @@ fn generate_shadow() -> io::Result<()> { writeln!(writer, "{}:!:::::::", username)?; } } - utils::do_mount("/tmp/shadow", "/etc/shadow", "", libc::MS_BIND as usize, ""); + utils::do_mount( + "/run/tmp/shadow", + "/etc/shadow", + "", + libc::MS_BIND as usize, + "", + ); Ok(()) } fn generate_sudoers() -> io::Result<()> { - let fname = "/tmp/sudoers"; + let fname = "/run/tmp/sudoers"; let mut content = "Defaults secure_path=\"/usr/sbin:/usr/bin:/sbin:/bin\"\n".to_string(); content += "root ALL = (ALL) NOPASSWD: ALL\n"; if let Ok(user) = env::var("virtme_user") { @@ -332,13 +344,19 @@ fn generate_sudoers() -> io::Result<()> { fn generate_hosts() -> io::Result<()> { if let Ok(hostname) = env::var("virtme_hostname") { - std::fs::copy("/etc/hosts", "/tmp/hosts")?; + std::fs::copy("/etc/hosts", "/run/tmp/hosts")?; let mut h = OpenOptions::new() .write(true) .append(true) - .open("/tmp/hosts")?; + .open("/run/tmp/hosts")?; writeln!(h, "\n127.0.0.1 {}\n::1 {}", hostname, hostname)?; - utils::do_mount("/tmp/hosts", "/etc/hosts", "", libc::MS_BIND as usize, ""); + utils::do_mount( + "/run/tmp/hosts", + "/etc/hosts", + "", + libc::MS_BIND as usize, + "", + ); } Ok(()) } @@ -508,7 +526,7 @@ fn fix_dpkg_locks() { if fname.is_empty() { continue; } - let src_file = format!("/tmp/{}", fname); + let src_file = format!("/run/tmp/{}", fname); utils::create_file(&src_file, 0o0640, "").ok(); utils::do_mount(&src_file, path, "", libc::MS_BIND as usize, ""); } @@ -744,9 +762,9 @@ fn setup_user_script(uid: u32) { fn setup_root_home() { // Set up a basic environment (unless virtme-ng is running as root on the host) if let Err(_) = env::var("virtme_root_user") { - utils::do_mkdir("/tmp/roothome"); - utils::do_mount("/tmp/roothome", "/root", "", libc::MS_BIND as usize, ""); - env::set_var("HOME", "/tmp/roothome"); + utils::do_mkdir("/run/tmp/roothome"); + utils::do_mount("/run/tmp/roothome", "/root", "", libc::MS_BIND as usize, ""); + env::set_var("HOME", "/run/tmp/roothome"); } else { env::set_var("HOME", "/root"); } @@ -836,7 +854,7 @@ fn run_shell(tty_fd: libc::c_int, args: &[&str]) { fn run_user_gui(tty_fd: libc::c_int) { // Generate a bare minimum xinitrc - let xinitrc = "/tmp/.xinitrc"; + let xinitrc = "/run/tmp/.xinitrc"; // Check if we need to start the sound system. let mut pre_exec_cmd: String = String::new(); @@ -865,10 +883,10 @@ fn run_user_gui(tty_fd: libc::c_int) { utils::run_cmd("bash", &["-c", &format!("chown {} /dev/char/*", user)]); // Start xinit directly. - storage = format!("su {} -c 'xinit /tmp/.xinitrc'", user); + storage = format!("su {} -c 'xinit /run/tmp/.xinitrc'", user); args.push(&storage); } else { - args.push("xinit /tmp/.xinitrc"); + args.push("xinit /run/tmp/.xinitrc"); } run_shell(tty_fd, &args); } From 6e2b515c5c178ba2ae3206933024ad24f658f368 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 20 Jun 2024 15:34:20 +0200 Subject: [PATCH 89/96] virtme-ng-init: overlayfs: fall back to mounting without xino option Older kernels don't support the 'xino' overlayfs mount option so we end up with no overlay mounts. Fall back to mounting without that option to work around that. It's not pretty as we end up with errors like: [ 0.380206] overlayfs: unrecognized mount option "xino=off" or missing value [ 0.383246] overlayfs: unrecognized mount option "xino=off" or missing value Apply to virtme-ng-init the same change applied by Juerg in virtme-init. Link: https://github.com/arighi/virtme-ng/pull/124 Reported-by: Juerg Haefliger Signed-off-by: Andrea Righi --- src/main.rs | 10 +++++++++- src/utils.rs | 14 +++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 83651e7b..046ea19d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -452,7 +452,15 @@ fn mount_virtme_overlays() { utils::do_mkdir(dir); utils::do_mkdir(upperdir); utils::do_mkdir(workdir); - utils::do_mount(&key, &path, "overlay", 0, mnt_opts); + let result = utils::do_mount_check(&key, &path, "overlay", 0, mnt_opts); + if let Err(_) = result { + // Old kernels don't support xino=on|off, re-try without this option. + let mnt_opts = &format!( + "lowerdir={},upperdir={},workdir={}", + path, upperdir, workdir + ); + utils::do_mount(&key, &path, "overlay", 0, mnt_opts); + } } } } diff --git a/src/utils.rs b/src/utils.rs index 20b3e102..2de74c5f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -112,7 +112,13 @@ pub fn do_symlink(src: &str, dst: &str) { } } -pub fn do_mount(source: &str, target: &str, fstype: &str, flags: usize, fsdata: &str) { +pub fn do_mount_check( + source: &str, + target: &str, + fstype: &str, + flags: usize, + fsdata: &str, +) -> Result<(), nix::Error> { let source_cstr = CString::new(source).expect("CString::new failed"); let fstype_cstr = CString::new(fstype).expect("CString::new failed"); let fsdata_cstr = CString::new(fsdata).expect("CString::new failed"); @@ -124,6 +130,12 @@ pub fn do_mount(source: &str, target: &str, fstype: &str, flags: usize, fsdata: MsFlags::from_bits_truncate(flags.try_into().unwrap()), Some(fsdata_cstr.as_ref()), ); + + result +} + +pub fn do_mount(source: &str, target: &str, fstype: &str, flags: usize, fsdata: &str) { + let result = do_mount_check(source, target, fstype, flags, fsdata); if let Err(err) = result { if err != nix::errno::Errno::ENOENT { log!("mount {} -> {}: {}", source, target, err); From 7cf03455427f793ad6ba6e9dd80a8a86b1fda0b2 Mon Sep 17 00:00:00 2001 From: Marcos Paulo de Souza Date: Fri, 18 Oct 2024 11:09:31 -0300 Subject: [PATCH 90/96] main.rs: Enable lvm usage Current /etc/lvm/ directories are restricted to root only (700), so just create an empty directory and bind mount over. This is enough to make commands like pvcreate to succeed. Signed-off-by: Marcos Paulo de Souza --- src/main.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main.rs b/src/main.rs index 046ea19d..cec3edc6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -342,6 +342,14 @@ fn generate_sudoers() -> io::Result<()> { Ok(()) } +// The /etc/lvm is usually only read/write by root. In order to allow commands like pvcreate to be +// run on rootless users just create a dummy directory and bind mount it in the same place. +fn generate_lvm() -> io::Result<()> { + utils::do_mkdir("/run/tmp/lvm"); + utils::do_mount("/run/tmp/lvm", "/etc/lvm/", "", libc::MS_BIND as usize, ""); + Ok(()) +} + fn generate_hosts() -> io::Result<()> { if let Ok(hostname) = env::var("virtme_hostname") { std::fs::copy("/etc/hosts", "/run/tmp/hosts")?; @@ -366,6 +374,7 @@ fn override_system_files() { generate_shadow().ok(); generate_sudoers().ok(); generate_hosts().ok(); + generate_lvm().ok(); } fn set_cwd() { From 11c479eb5a1e98456ea3145b18ff60a0ddc02176 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 27 Nov 2024 12:20:24 +0100 Subject: [PATCH 91/96] net: support multiple interfaces virtme-run supports multiple --net arguments to setup virtio-net devices, but the init program was only starting the DHCP client for one of them: the first one it discovered, which was not necessarily the first one by alphabetical order. Now, the DHCP client is started for each interface, so each of them can have an assigned IP address. Technically, instead of returning one network device, a vector of devices is returned. Same for the threads to start the DHCP clients. Signed-off-by: Matthieu Baerts (NGI0) --- src/main.rs | 70 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/src/main.rs b/src/main.rs index cec3edc6..446bb124 100644 --- a/src/main.rs +++ b/src/main.rs @@ -616,7 +616,9 @@ fn get_guest_tools_dir() -> Option { ) } -fn _get_network_device_from_entries(entries: std::fs::ReadDir) -> Option { +fn _get_network_devices_from_entries(entries: std::fs::ReadDir) -> Vec> { + let mut vec = Vec::new(); + // .flatten() ignores lines with reading errors for entry in entries.flatten() { let path = entry.path(); @@ -626,20 +628,21 @@ fn _get_network_device_from_entries(entries: std::fs::ReadDir) -> Option if let Ok(net_entries) = std::fs::read_dir(path.join("net")) { // .flatten() ignores lines with reading errors if let Some(entry) = net_entries.flatten().next() { - let path = entry.path().file_name()?.to_string_lossy().to_string(); - return Some(path); + if let Some(fname) = entry.path().file_name() { + vec.push(Some(fname.to_string_lossy().to_string())); + } } } } - None + vec } -fn get_network_device() -> Option { +fn get_network_devices() -> Vec> { let virtio_net_dir = "/sys/bus/virtio/drivers/virtio_net"; loop { match std::fs::read_dir(virtio_net_dir) { Ok(entries) => { - return _get_network_device_from_entries(entries); + return _get_network_devices_from_entries(entries); } Err(_) => { // Wait a bit to make sure virtio-net is properly registered in the system. @@ -649,31 +652,43 @@ fn get_network_device() -> Option { } } -fn setup_network() -> Option> { +fn get_network_handle( + network_dev: Option, + guest_tools_dir: Option, +) -> Option> { + let network_dev_str = network_dev.unwrap(); + log!("setting up network device {}", network_dev_str); + return Some(thread::spawn(move || { + let args = [ + "udhcpc", + "-i", + &network_dev_str, + "-n", + "-q", + "-f", + "-s", + &format!("{}/virtme-udhcpc-script", guest_tools_dir.unwrap()), + ]; + utils::run_cmd("busybox", &args); + })); +} + +fn setup_network() -> Vec>> { + let mut vec = Vec::new(); + utils::run_cmd("ip", &["link", "set", "dev", "lo", "up"]); - let cmdline = std::fs::read_to_string("/proc/cmdline").ok()?; + let cmdline = std::fs::read_to_string("/proc/cmdline").unwrap(); if cmdline.contains("virtme.dhcp") { if let Some(guest_tools_dir) = get_guest_tools_dir() { - if let Some(network_dev) = get_network_device() { - log!("setting up network device {}", network_dev); - let handle = thread::spawn(move || { - let args = [ - "udhcpc", - "-i", - &network_dev, - "-n", - "-q", - "-f", - "-s", - &format!("{}/virtme-udhcpc-script", guest_tools_dir), - ]; - utils::run_cmd("busybox", &args); - }); - return Some(handle); - } + get_network_devices().into_iter().for_each(|network_dev| { + vec.push(get_network_handle( + network_dev, + Some(guest_tools_dir.to_owned()), + )); + }); } } - None + vec } fn extract_user_script(virtme_script: &str) -> Option { @@ -1040,7 +1055,8 @@ fn main() { run_systemd_tmpfiles(); // Service initialization (some services can be parallelized here). - let handles = vec![run_udevd(), setup_network(), Some(run_misc_services())]; + let mut handles = vec![run_udevd(), Some(run_misc_services())]; + handles.append(&mut setup_network()); // Wait for the completion of the detached services. for handle in handles.into_iter().flatten() { From 05b4b6c33aae043bd105bca632fd0fd9cf3047b5 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 27 Nov 2024 12:35:45 +0100 Subject: [PATCH 92/96] net: setup the loopback iface in a thread Now that setup_network() returns a vector of threads, it is easy to add an extra one. Setting up the loopback interface should be quick. Still, it can be done in parallel if there is nothing else depending on it. Signed-off-by: Matthieu Baerts (NGI0) --- src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 446bb124..05853858 100644 --- a/src/main.rs +++ b/src/main.rs @@ -673,10 +673,15 @@ fn get_network_handle( })); } +fn setup_network_lo() -> Option> { + return Some(thread::spawn(move || { + utils::run_cmd("ip", &["link", "set", "dev", "lo", "up"]); + })); +} + fn setup_network() -> Vec>> { - let mut vec = Vec::new(); + let mut vec = vec![setup_network_lo()]; - utils::run_cmd("ip", &["link", "set", "dev", "lo", "up"]); let cmdline = std::fs::read_to_string("/proc/cmdline").unwrap(); if cmdline.contains("virtme.dhcp") { if let Some(guest_tools_dir) = get_guest_tools_dir() { From 4a5f664d976ed1ad0fa1bb32b1abf56564cca654 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 27 Nov 2024 12:56:32 +0100 Subject: [PATCH 93/96] poweroff: fix irrefutable 'if let' pattern warning This fixes the following warning: warning: irrefutable `if let` pattern --> src/main.rs:206:8 | 206 | if let Err(err) = reboot::reboot(reboot::RebootMode::RB_POWER_OFF) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this pattern will always match, so the `if let` is useless = help: consider replacing the `if let` with a `let` = note: `#[warn(irrefutable_let_patterns)]` on by default warning: `virtme-ng-init` (bin "virtme-ng-init") generated 1 warning Signed-off-by: Matthieu Baerts (NGI0) --- src/main.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 05853858..82e3cb63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -203,11 +203,13 @@ fn poweroff() { unsafe { libc::sync(); } - if let Err(err) = reboot::reboot(reboot::RebootMode::RB_POWER_OFF) { - log!("error powering off: {}", err); - exit(1); + match reboot::reboot(reboot::RebootMode::RB_POWER_OFF) { + Ok(_) => exit(0), + Err(err) => { + log!("error powering off: {}", err); + exit(1); + } } - exit(0); } fn configure_environment() { From bd01c91970128fb631a8bfeae5cf77a86a7ae4f5 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 29 Nov 2024 18:58:43 +0100 Subject: [PATCH 94/96] vsock: socat service for remote console When virtme.vsockexec=`` is set, socat is started in the background, listening to a VSock connection on the port 1024: once connected, a pty console is started with the given , e.g. 'bash -i'. This allows a simple remote control. Link: https://github.com/arighi/virtme-ng/discussions/151 Signed-off-by: Matthieu Baerts (NGI0) --- src/main.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/main.rs b/src/main.rs index 82e3cb63..8198d0b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1023,6 +1023,28 @@ fn run_snapd() { } } +fn extract_vsock_exec(cmdline: &str) -> Option { + let start_marker = "virtme.vsockexec=`"; + let end_marker = '`'; + + let (_before, remaining) = cmdline.split_once(start_marker)?; + let (encoded_cmd, _after) = remaining.split_once(end_marker)?; + Some(encoded_cmd.to_string()) +} + +fn setup_socat_console() { + if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { + if let Some(exec) = extract_vsock_exec(&cmdline) { + thread::spawn(move || { + let from = "VSOCK-LISTEN:1024,reuseaddr,fork"; + let to = format!("EXEC:\"{}\",pty,stderr,setsid,sigint,sane,echo=0", exec); + let args = vec![from, &to]; + utils::run_cmd("socat", &args); + }); + } + } +} + fn run_misc_services() -> thread::JoinHandle<()> { thread::spawn(|| { symlink_fds(); @@ -1061,6 +1083,9 @@ fn main() { mount_kernel_modules(); run_systemd_tmpfiles(); + // Service running in the background for later + setup_socat_console(); + // Service initialization (some services can be parallelized here). let mut handles = vec![run_udevd(), Some(run_misc_services())]; handles.append(&mut setup_network()); From c12db8879ab4583b8aa80664f1c5fd17d309b36e Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 2 Dec 2024 14:41:20 +0100 Subject: [PATCH 95/96] vsock: mount virtme_vsockmount if needed This will be used to get access to a script that will setup the remote shell: HOME dir, log with the right user, set tty, etc. This script will need to be created when a new connection is needed, to get access to the terminal dimension, and maybe more. Because of that, it is needed to have a way to share such info from the host to the VM. Signed-off-by: Matthieu Baerts (NGI0) --- src/main.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main.rs b/src/main.rs index 8198d0b1..38d7fd40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1036,6 +1036,19 @@ fn setup_socat_console() { if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { if let Some(exec) = extract_vsock_exec(&cmdline) { thread::spawn(move || { + log!("setting up vsock proxy executing {}", exec); + let key = "virtme_vsockmount"; + if let Ok(path) = env::var(&key) { + utils::do_mkdir(&path); + utils::do_mount( + &key.replace('_', "."), + &path, + "9p", + 0, + "version=9p2000.L,trans=virtio,access=any", + ); + } + let from = "VSOCK-LISTEN:1024,reuseaddr,fork"; let to = format!("EXEC:\"{}\",pty,stderr,setsid,sigint,sane,echo=0", exec); let args = vec![from, &to]; From da8d1c44e133e026697498c567c1fa289331fdc7 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 23 Dec 2024 08:24:26 +0100 Subject: [PATCH 96/96] ssh: support virtme.ssh Provide an option to start sshd in the guest. The sshd init script will be provided as a guest script by virtme-ng (if not present `virtme.ssh` will be simply ignored). Signed-off-by: Andrea Righi --- src/main.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main.rs b/src/main.rs index 38d7fd40..85f990b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -988,6 +988,16 @@ fn setup_user_session() { run_user_session(consdev.as_str(), uid); } +fn run_sshd() { + if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { + if cmdline.contains("virtme.ssh") { + if let Some(guest_tools_dir) = get_guest_tools_dir() { + utils::run_cmd(format!("{}/virtme-sshd-script", guest_tools_dir), &[]); + } + } + } +} + fn run_snapd() { if let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") { if cmdline.contains("virtme.snapd") { @@ -1064,6 +1074,7 @@ fn run_misc_services() -> thread::JoinHandle<()> { mount_virtme_initmounts(); fix_packaging_files(); override_system_files(); + run_sshd(); run_snapd(); }) }