From fb3e4e8c6b17235502d2da8df847b5c7cba2b18d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 28 May 2023 17:19:03 +0200 Subject: [PATCH 01/98] initial commit Signed-off-by: Andrea Righi --- virtme_ng_init/LICENSE | 674 +++++++++++++++++++++++++++++++++++++++ virtme_ng_init/README.md | 2 + 2 files changed, 676 insertions(+) create mode 100644 virtme_ng_init/LICENSE create mode 100644 virtme_ng_init/README.md diff --git a/virtme_ng_init/LICENSE b/virtme_ng_init/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/virtme_ng_init/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/virtme_ng_init/README.md b/virtme_ng_init/README.md new file mode 100644 index 0000000..d12fb43 --- /dev/null +++ b/virtme_ng_init/README.md @@ -0,0 +1,2 @@ +# virtme-ng-init +Fast init process for virtme-ng From 48c872b8f66f1791a730fde6ad0b834e805fdc5c Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sat, 27 May 2023 10:29:40 +0200 Subject: [PATCH 02/98] virtme-ng-init: initial implementation in Rust Signed-off-by: Andrea Righi --- virtme_ng_init/.gitignore | 1 + virtme_ng_init/Cargo.lock | 46 +++ virtme_ng_init/Cargo.toml | 9 + virtme_ng_init/src/main.rs | 773 ++++++++++++++++++++++++++++++++++++ virtme_ng_init/src/utils.rs | 115 ++++++ 5 files changed, 944 insertions(+) create mode 100644 virtme_ng_init/.gitignore create mode 100644 virtme_ng_init/Cargo.lock create mode 100644 virtme_ng_init/Cargo.toml create mode 100644 virtme_ng_init/src/main.rs create mode 100644 virtme_ng_init/src/utils.rs diff --git a/virtme_ng_init/.gitignore b/virtme_ng_init/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/virtme_ng_init/.gitignore @@ -0,0 +1 @@ +/target diff --git a/virtme_ng_init/Cargo.lock b/virtme_ng_init/Cargo.lock new file mode 100644 index 0000000..9b231fc --- /dev/null +++ b/virtme_ng_init/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/virtme_ng_init/Cargo.toml b/virtme_ng_init/Cargo.toml new file mode 100644 index 0000000..e81b8e8 --- /dev/null +++ b/virtme_ng_init/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/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs new file mode 100644 index 0000000..2f98649 --- /dev/null +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs new file mode 100644 index 0000000..697c611 --- /dev/null +++ b/virtme_ng_init/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 82b527e5c73ff71664d70240275778642a37fedb Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 30 May 2023 10:42:07 +0200 Subject: [PATCH 03/98] doc: add a proper README.md Signed-off-by: Andrea Righi --- virtme_ng_init/README.md | 61 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/README.md b/virtme_ng_init/README.md index d12fb43..1b9fc3b 100644 --- a/virtme_ng_init/README.md +++ b/virtme_ng_init/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 c65dc58070324df3fdb23979a1414a7a42b65b14 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 30 May 2023 10:58:20 +0200 Subject: [PATCH 04/98] create rust.yml Setup basic building and testing automation. Signed-off-by: Andrea Righi --- virtme_ng_init/.github/workflows/rust.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 virtme_ng_init/.github/workflows/rust.yml diff --git a/virtme_ng_init/.github/workflows/rust.yml b/virtme_ng_init/.github/workflows/rust.yml new file mode 100644 index 0000000..31000a2 --- /dev/null +++ b/virtme_ng_init/.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 00c9ddc1b4f1db4129c5b3d7254130a510255794 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 31 May 2023 16:09:55 +0200 Subject: [PATCH 05/98] properly handle GUI support Allow to run graphical applications from virtme-ng. Signed-off-by: Andrea Righi --- virtme_ng_init/src/main.rs | 129 +++++++++++++++++++++--------------- virtme_ng_init/src/utils.rs | 26 +++++--- 2 files changed, 95 insertions(+), 60 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 2f98649..269e218 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 697c611..dbb1330 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 01e7ec74145de58a38ba66471538c317bb98b173 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 1 Jun 2023 17:36:53 +0200 Subject: [PATCH 06/98] workaround to be able to start xinit directly from a console session Signed-off-by: Andrea Righi --- virtme_ng_init/src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 269e218..4204c94 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 941ad9c05d4cd05271e9fad1a452d9db9ee75024 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 14 Jun 2023 07:08:54 +0200 Subject: [PATCH 07/98] 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 --- virtme_ng_init/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/virtme_ng_init/README.md b/virtme_ng_init/README.md index 1b9fc3b..bc21fac 100644 --- a/virtme_ng_init/README.md +++ b/virtme_ng_init/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 b98d3766c97b113a73470f5abce3be121a86e72d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 5 Jun 2023 18:13:58 +0200 Subject: [PATCH 08/98] 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 --- virtme_ng_init/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 4204c94..33133cf 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 a32583a0e1750f9eecba560624d32100b413e455 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 5 Jun 2023 16:21:46 +0200 Subject: [PATCH 09/98] 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 --- virtme_ng_init/src/main.rs | 57 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 33133cf..efe3203 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 419fa0e1b838c7ea603d9b51878efbdd9e4c29ca Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 5 Jun 2023 10:14:26 +0200 Subject: [PATCH 10/98] 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 --- virtme_ng_init/src/main.rs | 50 +++++++++++++++++++++++++++++++++++++ virtme_ng_init/src/utils.rs | 8 ++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index efe3203..69c003f 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index dbb1330..878d154 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 cb582e685393d986805463478c01d97ed647846d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 8 Jun 2023 08:39:58 +0200 Subject: [PATCH 11/98] 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 --- virtme_ng_init/src/main.rs | 65 ++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 69c003f..cf1dddf 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 aba228a869b29e5b7f5faa1e941521669eff7566 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sat, 10 Jun 2023 21:13:20 +0200 Subject: [PATCH 12/98] 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 --- virtme_ng_init/Cargo.toml | 3 ++- virtme_ng_init/src/main.rs | 17 +++++++++++++++++ virtme_ng_init/src/utils.rs | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/virtme_ng_init/Cargo.toml b/virtme_ng_init/Cargo.toml index e81b8e8..8a61313 100644 --- a/virtme_ng_init/Cargo.toml +++ b/virtme_ng_init/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/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index cf1dddf..af10c04 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 878d154..f1dd166 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 7fcbdf141c5c4a296f85dbb6ccf7012791626316 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 8 Jun 2023 09:25:31 +0200 Subject: [PATCH 13/98] 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 --- virtme_ng_init/src/main.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index af10c04..42bad96 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 64533694c17bca170c78eec962855082a820ea3d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 14 Jun 2023 19:13:04 +0200 Subject: [PATCH 14/98] 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 --- virtme_ng_init/src/main.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 42bad96..bcf7932 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 297c225678ca82fed30fa386750bdb712dcbab41 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 18 Jun 2023 16:41:24 +0200 Subject: [PATCH 15/98] 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 --- virtme_ng_init/src/main.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index bcf7932..3aed65f 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 f0def1d50c1e0a8dd125538a37c0cf19482237f1 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 18 Jun 2023 19:30:14 +0200 Subject: [PATCH 16/98] 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 --- virtme_ng_init/src/main.rs | 34 +++++++++++++++++++++++++++++----- virtme_ng_init/src/utils.rs | 13 ------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 3aed65f..73251c2 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index f1dd166..4cff6cb 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 83ccbc3065f3815e71db1a200c8c2c24f82ae9af Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 19 Jun 2023 18:50:12 +0200 Subject: [PATCH 17/98] 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 --- virtme_ng_init/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 73251c2..190ae27 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 2bae60200f8e1bbbcae232da90579e1da7a7fdd9 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 20 Jun 2023 07:51:38 +0200 Subject: [PATCH 18/98] 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 --- virtme_ng_init/src/main.rs | 43 +++++++++++++++++-------------------- virtme_ng_init/src/utils.rs | 4 ++-- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 190ae27..4757ae7 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 4cff6cb..304d4d9 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 1b034f35a253d5589764770553b804c44bc6876e Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 20 Jun 2023 09:57:15 +0200 Subject: [PATCH 19/98] virtme-ng-init: minor coding style fixes Signed-off-by: Andrea Righi --- virtme_ng_init/src/main.rs | 4 +++- virtme_ng_init/src/utils.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 4757ae7..8d5652a 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 304d4d9..cfffce8 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 be36362a19138802f8492fd6c241678df91675e0 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 20 Jun 2023 10:42:39 +0200 Subject: [PATCH 20/98] 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 --- virtme_ng_init/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/Cargo.toml b/virtme_ng_init/Cargo.toml index 8a61313..5004eff 100644 --- a/virtme_ng_init/Cargo.toml +++ b/virtme_ng_init/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 027a7b82c8043a815f04507b32af037fc70b27f4 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 21 Jun 2023 08:07:49 +0200 Subject: [PATCH 21/98] remove Cargo.lock Signed-off-by: Andrea Righi --- virtme_ng_init/Cargo.lock | 46 --------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 virtme_ng_init/Cargo.lock diff --git a/virtme_ng_init/Cargo.lock b/virtme_ng_init/Cargo.lock deleted file mode 100644 index 9b231fc..0000000 --- a/virtme_ng_init/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 788e1f12db256d87abd94295b1d9e23ab52f2d45 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 21 Jun 2023 08:08:29 +0200 Subject: [PATCH 22/98] update README.md Signed-off-by: Andrea Righi --- virtme_ng_init/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/virtme_ng_init/README.md b/virtme_ng_init/README.md index bc21fac..1b9fc3b 100644 --- a/virtme_ng_init/README.md +++ b/virtme_ng_init/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 be3bf05af98ab532cdf8ffb719fc92b587eb100b Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 21 Jun 2023 08:42:19 +0200 Subject: [PATCH 23/98] github: add coding style checks to the workflows Also run the workflows on any push and pull request. Signed-off-by: Andrea Righi --- virtme_ng_init/.github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/.github/workflows/rust.yml b/virtme_ng_init/.github/workflows/rust.yml index 31000a2..9100db9 100644 --- a/virtme_ng_init/.github/workflows/rust.yml +++ b/virtme_ng_init/.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 3aedbce54a961e9bb075bccb2b34fb56b6c76560 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 28 Jun 2023 08:44:55 +0200 Subject: [PATCH 24/98] 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 --- virtme_ng_init/Cargo.toml | 1 + virtme_ng_init/src/main.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/Cargo.toml b/virtme_ng_init/Cargo.toml index 5004eff..5d92489 100644 --- a/virtme_ng_init/Cargo.toml +++ b/virtme_ng_init/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] nix = "0" users = "0" +base64 = "0.21" diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 8d5652a..fc23e9d 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 42ab46e091fa891896be7ca90366281667fdbb97 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 20 Sep 2023 12:10:59 +0200 Subject: [PATCH 25/98] 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 --- virtme_ng_init/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/Cargo.toml b/virtme_ng_init/Cargo.toml index 5d92489..882f1b6 100644 --- a/virtme_ng_init/Cargo.toml +++ b/virtme_ng_init/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 79f9eea5631b15c1ae89555ef7c6014172150b1d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Fri, 6 Oct 2023 07:26:48 +0200 Subject: [PATCH 26/98] 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 --- virtme_ng_init/src/utils.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index cfffce8..6213a34 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 5e8cfecda24603b7ca35c40eb667eb65e5013ecb Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 7 Nov 2023 19:19:30 +0200 Subject: [PATCH 27/98] 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 --- virtme_ng_init/src/main.rs | 46 ++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index fc23e9d..26f714a 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 81960225d76cf9db198cfd3a649309bf52c7755b Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 8 Nov 2023 11:24:22 +0200 Subject: [PATCH 28/98] virtme-ng-init: make virtme-script a constant Signed-off-by: Andrea Righi --- virtme_ng_init/src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 26f714a..2abc54d 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 67a5e2daf6254fece9667bc0191bf2a2d34afa2a Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 12 Nov 2023 07:40:34 -0500 Subject: [PATCH 29/98] 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 --- virtme_ng_init/src/main.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 2abc54d..1bb8cc5 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 b955c4624681d35e7499ce5ba5157a9ee5142924 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:41:59 +0100 Subject: [PATCH 30/98] 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 --- virtme_ng_init/src/main.rs | 75 +++++++++++++++++-------------------- virtme_ng_init/src/utils.rs | 5 +-- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 1bb8cc5..60b6a80 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 6213a34..c3b62cd 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 57ccbcd65f465b7a70e7b68b349affb3c149c39f Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:48:12 +0100 Subject: [PATCH 31/98] 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 --- virtme_ng_init/src/main.rs | 3 +++ virtme_ng_init/src/test.rs | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 virtme_ng_init/src/test.rs diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 60b6a80..ad6560c 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/test.rs b/virtme_ng_init/src/test.rs new file mode 100644 index 0000000..24c8cc3 --- /dev/null +++ b/virtme_ng_init/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 2f3f23e4ea24081ceccd9ad32b3454483da2f221 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:51:11 +0100 Subject: [PATCH 32/98] 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 --- virtme_ng_init/src/main.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index ad6560c..b0424da 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 09ba2686c9dbc89912eb6f5504a1f352202b8024 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:54:33 +0100 Subject: [PATCH 33/98] 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 --- virtme_ng_init/src/main.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index b0424da..c55161c 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 cae3f9238262827307aac1b6695bb62b5294a1fa Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 09:56:20 +0100 Subject: [PATCH 34/98] 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 --- virtme_ng_init/src/main.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index c55161c..54d2b92 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 0a67aab1394e81ff5c4c7df202789aa058cda467 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:00:19 +0100 Subject: [PATCH 35/98] 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 --- virtme_ng_init/src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 54d2b92..e197f3e 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 4b662f05eb661526030552dae2c60c9160743485 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:06:58 +0100 Subject: [PATCH 36/98] 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 --- virtme_ng_init/src/main.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index e197f3e..1cd6fb4 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 5791802eb6029afbd8fa787f47fa7d64f8c2e6f7 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:10:28 +0100 Subject: [PATCH 37/98] 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 --- virtme_ng_init/src/main.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 1cd6fb4..fb20d2e 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 3e81dabcc910bbe997995b6ba2719f4674a442bb Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:12:34 +0100 Subject: [PATCH 38/98] 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 --- virtme_ng_init/src/main.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index fb20d2e..5c1ce08 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 26dbacb397ce03bdb5d6f457bc965193a9ed2c5f Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:14:14 +0100 Subject: [PATCH 39/98] 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 --- virtme_ng_init/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 5c1ce08..2d78621 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 1342325ad267451d4c84f0444da70609217c54f2 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:15:44 +0100 Subject: [PATCH 40/98] 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 --- virtme_ng_init/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index c3b62cd..84643c2 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 3837d68ece61974263582dc49679c56865cf0df7 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:20:20 +0100 Subject: [PATCH 41/98] 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 --- virtme_ng_init/src/utils.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 84643c2..3c85b02 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 af941a04dacd5bd8df27cbea1638515f5bacd96d Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:23:17 +0100 Subject: [PATCH 42/98] 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 --- virtme_ng_init/src/main.rs | 4 ++-- virtme_ng_init/src/utils.rs | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 2d78621..e6510ad 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 3c85b02..a9aa976 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 2ba3590f629d8c4935a07ac91b9a1ed993e1bf57 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:27:01 +0100 Subject: [PATCH 43/98] 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 --- virtme_ng_init/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index e6510ad..4efc0af 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 cd445fa57a10f50a9af4d6c8834fbafc5d308546 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:28:02 +0100 Subject: [PATCH 44/98] 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 --- virtme_ng_init/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 4efc0af..fa2905e 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 f7f408bf7f0321c2f1a9506c7620223c65121afd Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:30:54 +0100 Subject: [PATCH 45/98] 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 --- virtme_ng_init/src/main.rs | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index fa2905e..62d8a90 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 b527b1f3da47528478aac2fcf981e1c6925c865b Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:32:34 +0100 Subject: [PATCH 46/98] 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 --- virtme_ng_init/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 62d8a90..3e1754a 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 4c58a8a7556ff070b713250fb71074f853cd8605 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:33:55 +0100 Subject: [PATCH 47/98] 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 --- virtme_ng_init/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 3e1754a..5708cf0 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 8dc22f7b3508bfcc3092f5593b58ddac9472426a Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:35:22 +0100 Subject: [PATCH 48/98] 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 --- virtme_ng_init/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 5708cf0..c7dbc16 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 a54c55b2d937ceb5cd8dcb6f54f07b96cca9f1e5 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:43:52 +0100 Subject: [PATCH 49/98] 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 --- virtme_ng_init/src/main.rs | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index c7dbc16..0623788 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 1b4080042a7599cb9a984a760408e6b9e4c8b511 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:51:54 +0100 Subject: [PATCH 50/98] 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 --- virtme_ng_init/src/main.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 0623788..17ee03f 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 8ab6190fad8e981e2eaabdc8b969f5ccdc0418df Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:56:35 +0100 Subject: [PATCH 51/98] 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 --- virtme_ng_init/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 17ee03f..a12786b 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 c2ccb7c6b518fec6ed6f775f30bd55a8a43030bd Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 10:58:13 +0100 Subject: [PATCH 52/98] 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 --- virtme_ng_init/src/main.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index a12786b..b45f921 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 611637c6a0012c0f45cd5389db9c706acb18429f Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:00:11 +0100 Subject: [PATCH 53/98] 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 --- virtme_ng_init/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index b45f921..418d26e 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 1bcb58e96c4be452107f35c861246c899b66f139 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:08:33 +0100 Subject: [PATCH 54/98] Remove an unnecessary Vec An array is just as good as a Vec here and avoids a memory allocation. Signed-off-by: Uli Schlachter --- virtme_ng_init/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 418d26e..7865709 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 63a360cf069494a522b1ac46c75a84ad4624ff05 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:23:22 +0100 Subject: [PATCH 55/98] 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 --- virtme_ng_init/src/main.rs | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 7865709..7e7ec8b 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 cbb5be61903f1c4f1a9c8e6a23eab67917799ebd Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:31:08 +0100 Subject: [PATCH 56/98] 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 --- virtme_ng_init/src/main.rs | 4 ++-- virtme_ng_init/src/utils.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 7e7ec8b..a0aa5a9 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index a9aa976..46b5a17 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 7e117fc7d513a3bf4c1435c9d247635e5220e357 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:48:11 +0100 Subject: [PATCH 57/98] 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 --- virtme_ng_init/src/main.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index a0aa5a9..d5f689b 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 291c4762efe7eb0e8a8c9c3491746e756df4b17d Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 11:50:57 +0100 Subject: [PATCH 58/98] Use nix' flags instead of libc's in one more place Signed-off-by: Uli Schlachter --- virtme_ng_init/src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index d5f689b..92a7191 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 9f551a4a8b42363d676fb5d57829fb3fec5e81d8 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 13:05:41 +0100 Subject: [PATCH 59/98] 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 --- virtme_ng_init/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 92a7191..5ecce47 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 0cf69d0a053d44e09daf4ff8932ff7da7129ce20 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 17:09:00 +0100 Subject: [PATCH 60/98] 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 --- virtme_ng_init/src/main.rs | 43 ++++++++++++++++++++----------------- virtme_ng_init/src/utils.rs | 28 ++++++++++++++---------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 5ecce47..41436f6 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 46b5a17..ac49a9f 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 f71b2f04e52e8b7f9a133af4cd4b6cd04940e0cf Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 17:29:09 +0100 Subject: [PATCH 61/98] (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 --- virtme_ng_init/src/utils.rs | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index ac49a9f..c8b6f19 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 6c6380c932ebbbade72e89e50e4ff83c8539e0d7 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 17:31:06 +0100 Subject: [PATCH 62/98] 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 --- virtme_ng_init/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index c8b6f19..8559f6a 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 70023b0c18ec08d5600e83270aa14809b8d35c07 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 18 Nov 2023 17:43:56 +0100 Subject: [PATCH 63/98] 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 --- virtme_ng_init/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 41436f6..14d34f0 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 a5605049367b7e0d7c26d30fb57b6ad2e8aae34a Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Fri, 17 Nov 2023 18:10:10 -0800 Subject: [PATCH 64/98] 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 --- virtme_ng_init/src/utils.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 8559f6a..f59c186 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 bb1c2ff2ab0eb57fd1b3a3ffaedbc148c02a260a Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Fri, 17 Nov 2023 18:30:24 -0800 Subject: [PATCH 65/98] Don't try to run systemd-tmpfiles on non-systemd systems Signed-off-by: Zev Weiss --- virtme_ng_init/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 14d34f0..a436b2c 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 7554a2c04a516b66ee41df6349566ca4df4355f1 Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Sat, 18 Nov 2023 16:14:00 -0800 Subject: [PATCH 66/98] 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 --- virtme_ng_init/src/main.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index a436b2c..63494aa 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 4cbde3a8304432cd71c330a9f68777b481e2631a Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 19 Nov 2023 09:59:13 +0100 Subject: [PATCH 67/98] 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 --- virtme_ng_init/src/main.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 63494aa..1824849 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 2d5e04bf0bedfd9687e69b4f0337036931ea6868 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 19 Nov 2023 10:08:40 +0100 Subject: [PATCH 68/98] 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 --- virtme_ng_init/src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 1824849..49494f9 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 c5ec4690fd991c43d7ed41e72f85ed7d29a40ba1 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 19 Nov 2023 11:03:46 +0100 Subject: [PATCH 69/98] 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 --- virtme_ng_init/src/main.rs | 48 ++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 49494f9..289079d 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 dae0ab8572ba375d0f5270eb6013b816335e2208 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 22 Nov 2023 08:59:03 +0100 Subject: [PATCH 70/98] 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 --- virtme_ng_init/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 289079d..6a58ee9 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 f99e78ea39a8fe4448559bfe90ef70d9702418cd Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 23 Nov 2023 22:30:06 +0100 Subject: [PATCH 71/98] 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 --- virtme_ng_init/src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 6a58ee9..d9e1d62 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 2a0f072b9f26244a198f11fa19872a0bc9df6254 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Fri, 1 Dec 2023 15:37:19 +0100 Subject: [PATCH 72/98] 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 --- virtme_ng_init/src/main.rs | 6 +++--- virtme_ng_init/src/utils.rs | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index d9e1d62..76ca676 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index f59c186..20b3e10 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 bd763bf5c42ecd21a3c3416cc692abb7e036deae Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 21 Dec 2023 10:27:54 +0100 Subject: [PATCH 73/98] 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 --- virtme_ng_init/src/main.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 76ca676..c673553 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 787fd07daf23fd41121573aa3f7141c762998e17 Mon Sep 17 00:00:00 2001 From: Neill Kapron Date: Tue, 2 Jan 2024 21:13:38 +0000 Subject: [PATCH 74/98] 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 --- virtme_ng_init/src/main.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index c673553..d27fab9 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 0b66655cc0d87a1c70785d9227b219e3d4f0718e Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 31 Jan 2024 10:45:06 +0100 Subject: [PATCH 75/98] 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 --- virtme_ng_init/src/main.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index d27fab9..c1b8e8d 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 c4d5846b9f826939c08f3d4f91455d76c6f83fc8 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sun, 4 Feb 2024 10:35:41 +0100 Subject: [PATCH 76/98] 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 --- virtme_ng_init/src/main.rs | 41 +++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index c1b8e8d..6abca10 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 c44e0d081114222ce5d294e2a991dc14b69d548a Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 13 Feb 2024 16:39:18 +0100 Subject: [PATCH 77/98] 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 --- virtme_ng_init/src/main.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 6abca10..0942178 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 c82a3711b7b789b539821afffeb2c86140994b6f Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 22 Feb 2024 09:57:52 +0100 Subject: [PATCH 78/98] 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 --- virtme_ng_init/src/main.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 0942178..5c5a5aa 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 76d0b028be9c9a72a8430a2c71c27ca2fc16f845 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 22 Feb 2024 10:11:09 +0100 Subject: [PATCH 79/98] 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 --- virtme_ng_init/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 5c5a5aa..6a1bc08 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 ce756076d3d309f7b7193c2f7cd75bf57586f7f9 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 22 Feb 2024 11:03:57 +0100 Subject: [PATCH 80/98] 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 --- virtme_ng_init/src/main.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 6a1bc08..19e4949 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 19b69b25c24a3d22a1965e9f5edb32ca339d1675 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 22 Feb 2024 19:23:16 +0100 Subject: [PATCH 81/98] 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 --- virtme_ng_init/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 19e4949..bb8c3eb 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 f733e04e0790373ad3205037f67546db2c7191cd Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sat, 2 Mar 2024 14:50:57 +0100 Subject: [PATCH 82/98] 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 --- virtme_ng_init/src/main.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index bb8c3eb..2b065c5 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 ae75ff3b82f86f5fbe54d5936dbcd109a186ac52 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Thu, 14 Mar 2024 18:51:15 +0100 Subject: [PATCH 83/98] 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: bd763bf ("virtme-ng-init: docker host support") Signed-off-by: Matthieu Baerts (NGI0) --- virtme_ng_init/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 2b065c5..b22db05 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 a855bdea6f902b3a6bde6236ebe2d14fbbcd7637 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Fri, 15 Mar 2024 07:49:12 +0100 Subject: [PATCH 84/98] 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 --- virtme_ng_init/src/main.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index b22db05..8cb72d3 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 3f069c94ee55093baae75355a3357d82ffae4811 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 20 Mar 2024 07:24:13 +0100 Subject: [PATCH 85/98] 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 --- virtme_ng_init/Cargo.lock | 86 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 virtme_ng_init/Cargo.lock diff --git a/virtme_ng_init/Cargo.lock b/virtme_ng_init/Cargo.lock new file mode 100644 index 0000000..981d3c5 --- /dev/null +++ b/virtme_ng_init/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 b6252af9ec61153684bfd0734a20c5a3c18caa06 Mon Sep 17 00:00:00 2001 From: Thibault Ferrante Date: Fri, 24 May 2024 17:09:02 +0200 Subject: [PATCH 86/98] README.md: add building and installation section Signed-off-by: Thibault Ferrante --- virtme_ng_init/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/virtme_ng_init/README.md b/virtme_ng_init/README.md index 1b9fc3b..e8d1d6e 100644 --- a/virtme_ng_init/README.md +++ b/virtme_ng_init/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 3759cc8c46e5fd9883ac4bfd9905ff29adf8fe38 Mon Sep 17 00:00:00 2001 From: Thibault Ferrante Date: Fri, 24 May 2024 16:59:54 +0200 Subject: [PATCH 87/98] 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 --- virtme_ng_init/src/main.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 8cb72d3..089475b 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 ceee7005f17ede24d0167d629caff3436bb8e923 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sat, 25 May 2024 09:54:25 +0200 Subject: [PATCH 88/98] 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 --- virtme_ng_init/src/main.rs | 52 +++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 089475b..83651e7 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 b5b669d33a3792162de9d34e5cef20f4661f3e20 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Thu, 20 Jun 2024 15:34:20 +0200 Subject: [PATCH 89/98] 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 --- virtme_ng_init/src/main.rs | 10 +++++++++- virtme_ng_init/src/utils.rs | 14 +++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 83651e7..046ea19 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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/virtme_ng_init/src/utils.rs b/virtme_ng_init/src/utils.rs index 20b3e10..2de74c5 100644 --- a/virtme_ng_init/src/utils.rs +++ b/virtme_ng_init/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 95955856898f708b2a6e24a0e4312dcce3d77186 Mon Sep 17 00:00:00 2001 From: Marcos Paulo de Souza Date: Fri, 18 Oct 2024 11:09:31 -0300 Subject: [PATCH 90/98] 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 --- virtme_ng_init/src/main.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 046ea19..cec3edc 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 2320739cfa92a81b6d4c15655df65f313be24984 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 27 Nov 2024 12:20:24 +0100 Subject: [PATCH 91/98] 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) --- virtme_ng_init/src/main.rs | 70 +++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index cec3edc..446bb12 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 a4d6ebc6fb9ab52e35e25c838caf8a6d83b60739 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 27 Nov 2024 12:35:45 +0100 Subject: [PATCH 92/98] 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) --- virtme_ng_init/src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 446bb12..0585385 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 0d430a7308a53f50100b452e5bb721a4d959bb9f Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 27 Nov 2024 12:56:32 +0100 Subject: [PATCH 93/98] 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) --- virtme_ng_init/src/main.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 0585385..82e3cb6 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 97e5dd7adf5c8d35f71306b19d854d5ab59e3387 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 29 Nov 2024 18:58:43 +0100 Subject: [PATCH 94/98] 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) --- virtme_ng_init/src/main.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 82e3cb6..8198d0b 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 c4bc8377dda3800923b6637250a67edbed7200bc Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 2 Dec 2024 14:41:20 +0100 Subject: [PATCH 95/98] 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) --- virtme_ng_init/src/main.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 8198d0b..38d7fd4 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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 e33b56fdb3def82cf4444308f88b997389e81dc4 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 23 Dec 2024 08:24:26 +0100 Subject: [PATCH 96/98] 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 --- virtme_ng_init/src/main.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/virtme_ng_init/src/main.rs b/virtme_ng_init/src/main.rs index 38d7fd4..85f990b 100644 --- a/virtme_ng_init/src/main.rs +++ b/virtme_ng_init/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(); }) } From d3aba35df4da0eaba0b774314f864eb2a9c08081 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 6 Jan 2025 11:09:56 +0100 Subject: [PATCH 97/98] virtme-ng-init: remove the submodule It looks like the submodule is causing more issues than helping: - Modifying this subproject requires two separated commits: - It might delay things - When only the init part is modified, it might be unclear how the modifications will be used, abd discussions about changing something on the main project can be easily forgotten - Small modifications might not be done because it needs more work, - People using the git repo often forget to sync, and have "strange" issues - It might be harder to package - Etc. In this commit, the submodule is removed. The code will be re-imported after this commit. Signed-off-by: Matthieu Baerts (NGI0) --- .gitmodules | 3 --- virtme_ng_init | 1 - 2 files changed, 4 deletions(-) delete mode 160000 virtme_ng_init diff --git a/.gitmodules b/.gitmodules index 1288885..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "virtme_ng_init"] - path = virtme_ng_init - url = https://github.com/arighi/virtme-ng-init.git diff --git a/virtme_ng_init b/virtme_ng_init deleted file mode 160000 index 9b1e02a..0000000 --- a/virtme_ng_init +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9b1e02a0deb094a36741b6172fb7ea8dc7dd8ddb From 360894c9e7094ce35780d0fdb4a17552bcee2367 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 6 Jan 2025 11:21:23 +0100 Subject: [PATCH 98/98] gh: import and adapt rust workflow Two modifications have been done: - Restrict the build on push to the main branch only, similar to what was done in commit 4da53c3 ("github: restrict builds on push to the main branch") - Change the working directory to ./virtme_ng_init, because the root directory is now different. Signed-off-by: Matthieu Baerts (NGI0) --- {virtme_ng_init/.github => .github}/workflows/rust.yml | 6 ++++++ 1 file changed, 6 insertions(+) rename {virtme_ng_init/.github => .github}/workflows/rust.yml (76%) diff --git a/virtme_ng_init/.github/workflows/rust.yml b/.github/workflows/rust.yml similarity index 76% rename from virtme_ng_init/.github/workflows/rust.yml rename to .github/workflows/rust.yml index 9100db9..3bcf0e3 100644 --- a/virtme_ng_init/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,6 +2,8 @@ name: Rust on: push: + branches: + - main pull_request: env: @@ -12,6 +14,10 @@ jobs: runs-on: ubuntu-latest + defaults: + run: + working-directory: ./virtme_ng_init + steps: - uses: actions/checkout@v3 - name: Coding style