diff --git a/LICENSE b/LICENSE
index 370af4f..f288702 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,674 @@
-MIT License
-
-Copyright (c) 2026 OpenATV SmallBox Wizard contributors
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README.md b/README.md
index 5d87741..b1fd773 100644
--- a/README.md
+++ b/README.md
@@ -267,4 +267,14 @@ Enigma2 first, it needs the framebuffer.
## License
-SmallBox Wizard is licensed under the MIT License. See [`LICENSE`](LICENSE).
+Copyright (C) 2026 OpenATV SmallBox Wizard contributors
+
+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, version 3, see [`LICENSE`](LICENSE).
+
+It contains, under their own licenses, which the About screen (INFO) shows:
+
+- LVGL (MIT, `lib/lvgl`).
+- The font, rendered from DejaVu Sans, and icons of Font Awesome Free
+ (SIL OFL 1.1), see `tools/fonts`.
diff --git a/include/licenses.h b/include/licenses.h
index 018b81d..2386ab9 100644
--- a/include/licenses.h
+++ b/include/licenses.h
@@ -8,28 +8,681 @@ struct license {
};
static const struct license licenses[] = {
-{"OE-Alliance SmallBox Wizard", "this application", "MIT",
- "MIT License\n"
- "\n"
- "Copyright (c) 2026 OpenATV SmallBox Wizard contributors\n"
- "\n"
- "Permission is hereby granted, free of charge, to any person obtaining a copy\n"
- "of this software and associated documentation files (the \"Software\"), to deal\n"
- "in the Software without restriction, including without limitation the rights\n"
- "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
- "copies of the Software, and to permit persons to whom the Software is\n"
- "furnished to do so, subject to the following conditions:\n"
- "\n"
- "The above copyright notice and this permission notice shall be included in all\n"
- "copies or substantial portions of the Software.\n"
- "\n"
- "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
- "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
- "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
- "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
- "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
- "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
- "SOFTWARE.\n"},
+{"OE-Alliance SmallBox Wizard", "this application", "GPL-3.0",
+ " GNU GENERAL PUBLIC LICENSE\n"
+ " Version 3, 29 June 2007\n"
+ "\n"
+ " Copyright (C) 2007 Free Software Foundation, Inc. \n"
+ " Everyone is permitted to copy and distribute verbatim copies\n"
+ " of this license document, but changing it is not allowed.\n"
+ "\n"
+ " Preamble\n"
+ "\n"
+ " The GNU General Public License is a free, copyleft license for\n"
+ "software and other kinds of works.\n"
+ "\n"
+ " The licenses for most software and other practical works are designed\n"
+ "to take away your freedom to share and change the works. By contrast,\n"
+ "the GNU General Public License is intended to guarantee your freedom to\n"
+ "share and change all versions of a program--to make sure it remains free\n"
+ "software for all its users. We, the Free Software Foundation, use the\n"
+ "GNU General Public License for most of our software; it applies also to\n"
+ "any other work released this way by its authors. You can apply it to\n"
+ "your programs, too.\n"
+ "\n"
+ " When we speak of free software, we are referring to freedom, not\n"
+ "price. Our General Public Licenses are designed to make sure that you\n"
+ "have the freedom to distribute copies of free software (and charge for\n"
+ "them if you wish), that you receive source code or can get it if you\n"
+ "want it, that you can change the software or use pieces of it in new\n"
+ "free programs, and that you know you can do these things.\n"
+ "\n"
+ " To protect your rights, we need to prevent others from denying you\n"
+ "these rights or asking you to surrender the rights. Therefore, you have\n"
+ "certain responsibilities if you distribute copies of the software, or if\n"
+ "you modify it: responsibilities to respect the freedom of others.\n"
+ "\n"
+ " For example, if you distribute copies of such a program, whether\n"
+ "gratis or for a fee, you must pass on to the recipients the same\n"
+ "freedoms that you received. You must make sure that they, too, receive\n"
+ "or can get the source code. And you must show them these terms so they\n"
+ "know their rights.\n"
+ "\n"
+ " Developers that use the GNU GPL protect your rights with two steps:\n"
+ "(1) assert copyright on the software, and (2) offer you this License\n"
+ "giving you legal permission to copy, distribute and/or modify it.\n"
+ "\n"
+ " For the developers' and authors' protection, the GPL clearly explains\n"
+ "that there is no warranty for this free software. For both users' and\n"
+ "authors' sake, the GPL requires that modified versions be marked as\n"
+ "changed, so that their problems will not be attributed erroneously to\n"
+ "authors of previous versions.\n"
+ "\n"
+ " Some devices are designed to deny users access to install or run\n"
+ "modified versions of the software inside them, although the manufacturer\n"
+ "can do so. This is fundamentally incompatible with the aim of\n"
+ "protecting users' freedom to change the software. The systematic\n"
+ "pattern of such abuse occurs in the area of products for individuals to\n"
+ "use, which is precisely where it is most unacceptable. Therefore, we\n"
+ "have designed this version of the GPL to prohibit the practice for those\n"
+ "products. If such problems arise substantially in other domains, we\n"
+ "stand ready to extend this provision to those domains in future versions\n"
+ "of the GPL, as needed to protect the freedom of users.\n"
+ "\n"
+ " Finally, every program is threatened constantly by software patents.\n"
+ "States should not allow patents to restrict development and use of\n"
+ "software on general-purpose computers, but in those that do, we wish to\n"
+ "avoid the special danger that patents applied to a free program could\n"
+ "make it effectively proprietary. To prevent this, the GPL assures that\n"
+ "patents cannot be used to render the program non-free.\n"
+ "\n"
+ " The precise terms and conditions for copying, distribution and\n"
+ "modification follow.\n"
+ "\n"
+ " TERMS AND CONDITIONS\n"
+ "\n"
+ " 0. Definitions.\n"
+ "\n"
+ " \"This License\" refers to version 3 of the GNU General Public License.\n"
+ "\n"
+ " \"Copyright\" also means copyright-like laws that apply to other kinds of\n"
+ "works, such as semiconductor masks.\n"
+ "\n"
+ " \"The Program\" refers to any copyrightable work licensed under this\n"
+ "License. Each licensee is addressed as \"you\". \"Licensees\" and\n"
+ "\"recipients\" may be individuals or organizations.\n"
+ "\n"
+ " To \"modify\" a work means to copy from or adapt all or part of the work\n"
+ "in a fashion requiring copyright permission, other than the making of an\n"
+ "exact copy. The resulting work is called a \"modified version\" of the\n"
+ "earlier work or a work \"based on\" the earlier work.\n"
+ "\n"
+ " A \"covered work\" means either the unmodified Program or a work based\n"
+ "on the Program.\n"
+ "\n"
+ " To \"propagate\" a work means to do anything with it that, without\n"
+ "permission, would make you directly or secondarily liable for\n"
+ "infringement under applicable copyright law, except executing it on a\n"
+ "computer or modifying a private copy. Propagation includes copying,\n"
+ "distribution (with or without modification), making available to the\n"
+ "public, and in some countries other activities as well.\n"
+ "\n"
+ " To \"convey\" a work means any kind of propagation that enables other\n"
+ "parties to make or receive copies. Mere interaction with a user through\n"
+ "a computer network, with no transfer of a copy, is not conveying.\n"
+ "\n"
+ " An interactive user interface displays \"Appropriate Legal Notices\"\n"
+ "to the extent that it includes a convenient and prominently visible\n"
+ "feature that (1) displays an appropriate copyright notice, and (2)\n"
+ "tells the user that there is no warranty for the work (except to the\n"
+ "extent that warranties are provided), that licensees may convey the\n"
+ "work under this License, and how to view a copy of this License. If\n"
+ "the interface presents a list of user commands or options, such as a\n"
+ "menu, a prominent item in the list meets this criterion.\n"
+ "\n"
+ " 1. Source Code.\n"
+ "\n"
+ " The \"source code\" for a work means the preferred form of the work\n"
+ "for making modifications to it. \"Object code\" means any non-source\n"
+ "form of a work.\n"
+ "\n"
+ " A \"Standard Interface\" means an interface that either is an official\n"
+ "standard defined by a recognized standards body, or, in the case of\n"
+ "interfaces specified for a particular programming language, one that\n"
+ "is widely used among developers working in that language.\n"
+ "\n"
+ " The \"System Libraries\" of an executable work include anything, other\n"
+ "than the work as a whole, that (a) is included in the normal form of\n"
+ "packaging a Major Component, but which is not part of that Major\n"
+ "Component, and (b) serves only to enable use of the work with that\n"
+ "Major Component, or to implement a Standard Interface for which an\n"
+ "implementation is available to the public in source code form. A\n"
+ "\"Major Component\", in this context, means a major essential component\n"
+ "(kernel, window system, and so on) of the specific operating system\n"
+ "(if any) on which the executable work runs, or a compiler used to\n"
+ "produce the work, or an object code interpreter used to run it.\n"
+ "\n"
+ " The \"Corresponding Source\" for a work in object code form means all\n"
+ "the source code needed to generate, install, and (for an executable\n"
+ "work) run the object code and to modify the work, including scripts to\n"
+ "control those activities. However, it does not include the work's\n"
+ "System Libraries, or general-purpose tools or generally available free\n"
+ "programs which are used unmodified in performing those activities but\n"
+ "which are not part of the work. For example, Corresponding Source\n"
+ "includes interface definition files associated with source files for\n"
+ "the work, and the source code for shared libraries and dynamically\n"
+ "linked subprograms that the work is specifically designed to require,\n"
+ "such as by intimate data communication or control flow between those\n"
+ "subprograms and other parts of the work.\n"
+ "\n"
+ " The Corresponding Source need not include anything that users\n"
+ "can regenerate automatically from other parts of the Corresponding\n"
+ "Source.\n"
+ "\n"
+ " The Corresponding Source for a work in source code form is that\n"
+ "same work.\n"
+ "\n"
+ " 2. Basic Permissions.\n"
+ "\n"
+ " All rights granted under this License are granted for the term of\n"
+ "copyright on the Program, and are irrevocable provided the stated\n"
+ "conditions are met. This License explicitly affirms your unlimited\n"
+ "permission to run the unmodified Program. The output from running a\n"
+ "covered work is covered by this License only if the output, given its\n"
+ "content, constitutes a covered work. This License acknowledges your\n"
+ "rights of fair use or other equivalent, as provided by copyright law.\n"
+ "\n"
+ " You may make, run and propagate covered works that you do not\n"
+ "convey, without conditions so long as your license otherwise remains\n"
+ "in force. You may convey covered works to others for the sole purpose\n"
+ "of having them make modifications exclusively for you, or provide you\n"
+ "with facilities for running those works, provided that you comply with\n"
+ "the terms of this License in conveying all material for which you do\n"
+ "not control copyright. Those thus making or running the covered works\n"
+ "for you must do so exclusively on your behalf, under your direction\n"
+ "and control, on terms that prohibit them from making any copies of\n"
+ "your copyrighted material outside their relationship with you.\n"
+ "\n"
+ " Conveying under any other circumstances is permitted solely under\n"
+ "the conditions stated below. Sublicensing is not allowed; section 10\n"
+ "makes it unnecessary.\n"
+ "\n"
+ " 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n"
+ "\n"
+ " No covered work shall be deemed part of an effective technological\n"
+ "measure under any applicable law fulfilling obligations under article\n"
+ "11 of the WIPO copyright treaty adopted on 20 December 1996, or\n"
+ "similar laws prohibiting or restricting circumvention of such\n"
+ "measures.\n"
+ "\n"
+ " When you convey a covered work, you waive any legal power to forbid\n"
+ "circumvention of technological measures to the extent such circumvention\n"
+ "is effected by exercising rights under this License with respect to\n"
+ "the covered work, and you disclaim any intention to limit operation or\n"
+ "modification of the work as a means of enforcing, against the work's\n"
+ "users, your or third parties' legal rights to forbid circumvention of\n"
+ "technological measures.\n"
+ "\n"
+ " 4. Conveying Verbatim Copies.\n"
+ "\n"
+ " You may convey verbatim copies of the Program's source code as you\n"
+ "receive it, in any medium, provided that you conspicuously and\n"
+ "appropriately publish on each copy an appropriate copyright notice;\n"
+ "keep intact all notices stating that this License and any\n"
+ "non-permissive terms added in accord with section 7 apply to the code;\n"
+ "keep intact all notices of the absence of any warranty; and give all\n"
+ "recipients a copy of this License along with the Program.\n"
+ "\n"
+ " You may charge any price or no price for each copy that you convey,\n"
+ "and you may offer support or warranty protection for a fee.\n"
+ "\n"
+ " 5. Conveying Modified Source Versions.\n"
+ "\n"
+ " You may convey a work based on the Program, or the modifications to\n"
+ "produce it from the Program, in the form of source code under the\n"
+ "terms of section 4, provided that you also meet all of these conditions:\n"
+ "\n"
+ " a) The work must carry prominent notices stating that you modified\n"
+ " it, and giving a relevant date.\n"
+ "\n"
+ " b) The work must carry prominent notices stating that it is\n"
+ " released under this License and any conditions added under section\n"
+ " 7. This requirement modifies the requirement in section 4 to\n"
+ " \"keep intact all notices\".\n"
+ "\n"
+ " c) You must license the entire work, as a whole, under this\n"
+ " License to anyone who comes into possession of a copy. This\n"
+ " License will therefore apply, along with any applicable section 7\n"
+ " additional terms, to the whole of the work, and all its parts,\n"
+ " regardless of how they are packaged. This License gives no\n"
+ " permission to license the work in any other way, but it does not\n"
+ " invalidate such permission if you have separately received it.\n"
+ "\n"
+ " d) If the work has interactive user interfaces, each must display\n"
+ " Appropriate Legal Notices; however, if the Program has interactive\n"
+ " interfaces that do not display Appropriate Legal Notices, your\n"
+ " work need not make them do so.\n"
+ "\n"
+ " A compilation of a covered work with other separate and independent\n"
+ "works, which are not by their nature extensions of the covered work,\n"
+ "and which are not combined with it such as to form a larger program,\n"
+ "in or on a volume of a storage or distribution medium, is called an\n"
+ "\"aggregate\" if the compilation and its resulting copyright are not\n"
+ "used to limit the access or legal rights of the compilation's users\n"
+ "beyond what the individual works permit. Inclusion of a covered work\n"
+ "in an aggregate does not cause this License to apply to the other\n"
+ "parts of the aggregate.\n"
+ "\n"
+ " 6. Conveying Non-Source Forms.\n"
+ "\n"
+ " You may convey a covered work in object code form under the terms\n"
+ "of sections 4 and 5, provided that you also convey the\n"
+ "machine-readable Corresponding Source under the terms of this License,\n"
+ "in one of these ways:\n"
+ "\n"
+ " a) Convey the object code in, or embodied in, a physical product\n"
+ " (including a physical distribution medium), accompanied by the\n"
+ " Corresponding Source fixed on a durable physical medium\n"
+ " customarily used for software interchange.\n"
+ "\n"
+ " b) Convey the object code in, or embodied in, a physical product\n"
+ " (including a physical distribution medium), accompanied by a\n"
+ " written offer, valid for at least three years and valid for as\n"
+ " long as you offer spare parts or customer support for that product\n"
+ " model, to give anyone who possesses the object code either (1) a\n"
+ " copy of the Corresponding Source for all the software in the\n"
+ " product that is covered by this License, on a durable physical\n"
+ " medium customarily used for software interchange, for a price no\n"
+ " more than your reasonable cost of physically performing this\n"
+ " conveying of source, or (2) access to copy the\n"
+ " Corresponding Source from a network server at no charge.\n"
+ "\n"
+ " c) Convey individual copies of the object code with a copy of the\n"
+ " written offer to provide the Corresponding Source. This\n"
+ " alternative is allowed only occasionally and noncommercially, and\n"
+ " only if you received the object code with such an offer, in accord\n"
+ " with subsection 6b.\n"
+ "\n"
+ " d) Convey the object code by offering access from a designated\n"
+ " place (gratis or for a charge), and offer equivalent access to the\n"
+ " Corresponding Source in the same way through the same place at no\n"
+ " further charge. You need not require recipients to copy the\n"
+ " Corresponding Source along with the object code. If the place to\n"
+ " copy the object code is a network server, the Corresponding Source\n"
+ " may be on a different server (operated by you or a third party)\n"
+ " that supports equivalent copying facilities, provided you maintain\n"
+ " clear directions next to the object code saying where to find the\n"
+ " Corresponding Source. Regardless of what server hosts the\n"
+ " Corresponding Source, you remain obligated to ensure that it is\n"
+ " available for as long as needed to satisfy these requirements.\n"
+ "\n"
+ " e) Convey the object code using peer-to-peer transmission, provided\n"
+ " you inform other peers where the object code and Corresponding\n"
+ " Source of the work are being offered to the general public at no\n"
+ " charge under subsection 6d.\n"
+ "\n"
+ " A separable portion of the object code, whose source code is excluded\n"
+ "from the Corresponding Source as a System Library, need not be\n"
+ "included in conveying the object code work.\n"
+ "\n"
+ " A \"User Product\" is either (1) a \"consumer product\", which means any\n"
+ "tangible personal property which is normally used for personal, family,\n"
+ "or household purposes, or (2) anything designed or sold for incorporation\n"
+ "into a dwelling. In determining whether a product is a consumer product,\n"
+ "doubtful cases shall be resolved in favor of coverage. For a particular\n"
+ "product received by a particular user, \"normally used\" refers to a\n"
+ "typical or common use of that class of product, regardless of the status\n"
+ "of the particular user or of the way in which the particular user\n"
+ "actually uses, or expects or is expected to use, the product. A product\n"
+ "is a consumer product regardless of whether the product has substantial\n"
+ "commercial, industrial or non-consumer uses, unless such uses represent\n"
+ "the only significant mode of use of the product.\n"
+ "\n"
+ " \"Installation Information\" for a User Product means any methods,\n"
+ "procedures, authorization keys, or other information required to install\n"
+ "and execute modified versions of a covered work in that User Product from\n"
+ "a modified version of its Corresponding Source. The information must\n"
+ "suffice to ensure that the continued functioning of the modified object\n"
+ "code is in no case prevented or interfered with solely because\n"
+ "modification has been made.\n"
+ "\n"
+ " If you convey an object code work under this section in, or with, or\n"
+ "specifically for use in, a User Product, and the conveying occurs as\n"
+ "part of a transaction in which the right of possession and use of the\n"
+ "User Product is transferred to the recipient in perpetuity or for a\n"
+ "fixed term (regardless of how the transaction is characterized), the\n"
+ "Corresponding Source conveyed under this section must be accompanied\n"
+ "by the Installation Information. But this requirement does not apply\n"
+ "if neither you nor any third party retains the ability to install\n"
+ "modified object code on the User Product (for example, the work has\n"
+ "been installed in ROM).\n"
+ "\n"
+ " The requirement to provide Installation Information does not include a\n"
+ "requirement to continue to provide support service, warranty, or updates\n"
+ "for a work that has been modified or installed by the recipient, or for\n"
+ "the User Product in which it has been modified or installed. Access to a\n"
+ "network may be denied when the modification itself materially and\n"
+ "adversely affects the operation of the network or violates the rules and\n"
+ "protocols for communication across the network.\n"
+ "\n"
+ " Corresponding Source conveyed, and Installation Information provided,\n"
+ "in accord with this section must be in a format that is publicly\n"
+ "documented (and with an implementation available to the public in\n"
+ "source code form), and must require no special password or key for\n"
+ "unpacking, reading or copying.\n"
+ "\n"
+ " 7. Additional Terms.\n"
+ "\n"
+ " \"Additional permissions\" are terms that supplement the terms of this\n"
+ "License by making exceptions from one or more of its conditions.\n"
+ "Additional permissions that are applicable to the entire Program shall\n"
+ "be treated as though they were included in this License, to the extent\n"
+ "that they are valid under applicable law. If additional permissions\n"
+ "apply only to part of the Program, that part may be used separately\n"
+ "under those permissions, but the entire Program remains governed by\n"
+ "this License without regard to the additional permissions.\n"
+ "\n"
+ " When you convey a copy of a covered work, you may at your option\n"
+ "remove any additional permissions from that copy, or from any part of\n"
+ "it. (Additional permissions may be written to require their own\n"
+ "removal in certain cases when you modify the work.) You may place\n"
+ "additional permissions on material, added by you to a covered work,\n"
+ "for which you have or can give appropriate copyright permission.\n"
+ "\n"
+ " Notwithstanding any other provision of this License, for material you\n"
+ "add to a covered work, you may (if authorized by the copyright holders of\n"
+ "that material) supplement the terms of this License with terms:\n"
+ "\n"
+ " a) Disclaiming warranty or limiting liability differently from the\n"
+ " terms of sections 15 and 16 of this License; or\n"
+ "\n"
+ " b) Requiring preservation of specified reasonable legal notices or\n"
+ " author attributions in that material or in the Appropriate Legal\n"
+ " Notices displayed by works containing it; or\n"
+ "\n"
+ " c) Prohibiting misrepresentation of the origin of that material, or\n"
+ " requiring that modified versions of such material be marked in\n"
+ " reasonable ways as different from the original version; or\n"
+ "\n"
+ " d) Limiting the use for publicity purposes of names of licensors or\n"
+ " authors of the material; or\n"
+ "\n"
+ " e) Declining to grant rights under trademark law for use of some\n"
+ " trade names, trademarks, or service marks; or\n"
+ "\n"
+ " f) Requiring indemnification of licensors and authors of that\n"
+ " material by anyone who conveys the material (or modified versions of\n"
+ " it) with contractual assumptions of liability to the recipient, for\n"
+ " any liability that these contractual assumptions directly impose on\n"
+ " those licensors and authors.\n"
+ "\n"
+ " All other non-permissive additional terms are considered \"further\n"
+ "restrictions\" within the meaning of section 10. If the Program as you\n"
+ "received it, or any part of it, contains a notice stating that it is\n"
+ "governed by this License along with a term that is a further\n"
+ "restriction, you may remove that term. If a license document contains\n"
+ "a further restriction but permits relicensing or conveying under this\n"
+ "License, you may add to a covered work material governed by the terms\n"
+ "of that license document, provided that the further restriction does\n"
+ "not survive such relicensing or conveying.\n"
+ "\n"
+ " If you add terms to a covered work in accord with this section, you\n"
+ "must place, in the relevant source files, a statement of the\n"
+ "additional terms that apply to those files, or a notice indicating\n"
+ "where to find the applicable terms.\n"
+ "\n"
+ " Additional terms, permissive or non-permissive, may be stated in the\n"
+ "form of a separately written license, or stated as exceptions;\n"
+ "the above requirements apply either way.\n"
+ "\n"
+ " 8. Termination.\n"
+ "\n"
+ " You may not propagate or modify a covered work except as expressly\n"
+ "provided under this License. Any attempt otherwise to propagate or\n"
+ "modify it is void, and will automatically terminate your rights under\n"
+ "this License (including any patent licenses granted under the third\n"
+ "paragraph of section 11).\n"
+ "\n"
+ " However, if you cease all violation of this License, then your\n"
+ "license from a particular copyright holder is reinstated (a)\n"
+ "provisionally, unless and until the copyright holder explicitly and\n"
+ "finally terminates your license, and (b) permanently, if the copyright\n"
+ "holder fails to notify you of the violation by some reasonable means\n"
+ "prior to 60 days after the cessation.\n"
+ "\n"
+ " Moreover, your license from a particular copyright holder is\n"
+ "reinstated permanently if the copyright holder notifies you of the\n"
+ "violation by some reasonable means, this is the first time you have\n"
+ "received notice of violation of this License (for any work) from that\n"
+ "copyright holder, and you cure the violation prior to 30 days after\n"
+ "your receipt of the notice.\n"
+ "\n"
+ " Termination of your rights under this section does not terminate the\n"
+ "licenses of parties who have received copies or rights from you under\n"
+ "this License. If your rights have been terminated and not permanently\n"
+ "reinstated, you do not qualify to receive new licenses for the same\n"
+ "material under section 10.\n"
+ "\n"
+ " 9. Acceptance Not Required for Having Copies.\n"
+ "\n"
+ " You are not required to accept this License in order to receive or\n"
+ "run a copy of the Program. Ancillary propagation of a covered work\n"
+ "occurring solely as a consequence of using peer-to-peer transmission\n"
+ "to receive a copy likewise does not require acceptance. However,\n"
+ "nothing other than this License grants you permission to propagate or\n"
+ "modify any covered work. These actions infringe copyright if you do\n"
+ "not accept this License. Therefore, by modifying or propagating a\n"
+ "covered work, you indicate your acceptance of this License to do so.\n"
+ "\n"
+ " 10. Automatic Licensing of Downstream Recipients.\n"
+ "\n"
+ " Each time you convey a covered work, the recipient automatically\n"
+ "receives a license from the original licensors, to run, modify and\n"
+ "propagate that work, subject to this License. You are not responsible\n"
+ "for enforcing compliance by third parties with this License.\n"
+ "\n"
+ " An \"entity transaction\" is a transaction transferring control of an\n"
+ "organization, or substantially all assets of one, or subdividing an\n"
+ "organization, or merging organizations. If propagation of a covered\n"
+ "work results from an entity transaction, each party to that\n"
+ "transaction who receives a copy of the work also receives whatever\n"
+ "licenses to the work the party's predecessor in interest had or could\n"
+ "give under the previous paragraph, plus a right to possession of the\n"
+ "Corresponding Source of the work from the predecessor in interest, if\n"
+ "the predecessor has it or can get it with reasonable efforts.\n"
+ "\n"
+ " You may not impose any further restrictions on the exercise of the\n"
+ "rights granted or affirmed under this License. For example, you may\n"
+ "not impose a license fee, royalty, or other charge for exercise of\n"
+ "rights granted under this License, and you may not initiate litigation\n"
+ "(including a cross-claim or counterclaim in a lawsuit) alleging that\n"
+ "any patent claim is infringed by making, using, selling, offering for\n"
+ "sale, or importing the Program or any portion of it.\n"
+ "\n"
+ " 11. Patents.\n"
+ "\n"
+ " A \"contributor\" is a copyright holder who authorizes use under this\n"
+ "License of the Program or a work on which the Program is based. The\n"
+ "work thus licensed is called the contributor's \"contributor version\".\n"
+ "\n"
+ " A contributor's \"essential patent claims\" are all patent claims\n"
+ "owned or controlled by the contributor, whether already acquired or\n"
+ "hereafter acquired, that would be infringed by some manner, permitted\n"
+ "by this License, of making, using, or selling its contributor version,\n"
+ "but do not include claims that would be infringed only as a\n"
+ "consequence of further modification of the contributor version. For\n"
+ "purposes of this definition, \"control\" includes the right to grant\n"
+ "patent sublicenses in a manner consistent with the requirements of\n"
+ "this License.\n"
+ "\n"
+ " Each contributor grants you a non-exclusive, worldwide, royalty-free\n"
+ "patent license under the contributor's essential patent claims, to\n"
+ "make, use, sell, offer for sale, import and otherwise run, modify and\n"
+ "propagate the contents of its contributor version.\n"
+ "\n"
+ " In the following three paragraphs, a \"patent license\" is any express\n"
+ "agreement or commitment, however denominated, not to enforce a patent\n"
+ "(such as an express permission to practice a patent or covenant not to\n"
+ "sue for patent infringement). To \"grant\" such a patent license to a\n"
+ "party means to make such an agreement or commitment not to enforce a\n"
+ "patent against the party.\n"
+ "\n"
+ " If you convey a covered work, knowingly relying on a patent license,\n"
+ "and the Corresponding Source of the work is not available for anyone\n"
+ "to copy, free of charge and under the terms of this License, through a\n"
+ "publicly available network server or other readily accessible means,\n"
+ "then you must either (1) cause the Corresponding Source to be so\n"
+ "available, or (2) arrange to deprive yourself of the benefit of the\n"
+ "patent license for this particular work, or (3) arrange, in a manner\n"
+ "consistent with the requirements of this License, to extend the patent\n"
+ "license to downstream recipients. \"Knowingly relying\" means you have\n"
+ "actual knowledge that, but for the patent license, your conveying the\n"
+ "covered work in a country, or your recipient's use of the covered work\n"
+ "in a country, would infringe one or more identifiable patents in that\n"
+ "country that you have reason to believe are valid.\n"
+ "\n"
+ " If, pursuant to or in connection with a single transaction or\n"
+ "arrangement, you convey, or propagate by procuring conveyance of, a\n"
+ "covered work, and grant a patent license to some of the parties\n"
+ "receiving the covered work authorizing them to use, propagate, modify\n"
+ "or convey a specific copy of the covered work, then the patent license\n"
+ "you grant is automatically extended to all recipients of the covered\n"
+ "work and works based on it.\n"
+ "\n"
+ " A patent license is \"discriminatory\" if it does not include within\n"
+ "the scope of its coverage, prohibits the exercise of, or is\n"
+ "conditioned on the non-exercise of one or more of the rights that are\n"
+ "specifically granted under this License. You may not convey a covered\n"
+ "work if you are a party to an arrangement with a third party that is\n"
+ "in the business of distributing software, under which you make payment\n"
+ "to the third party based on the extent of your activity of conveying\n"
+ "the work, and under which the third party grants, to any of the\n"
+ "parties who would receive the covered work from you, a discriminatory\n"
+ "patent license (a) in connection with copies of the covered work\n"
+ "conveyed by you (or copies made from those copies), or (b) primarily\n"
+ "for and in connection with specific products or compilations that\n"
+ "contain the covered work, unless you entered into that arrangement,\n"
+ "or that patent license was granted, prior to 28 March 2007.\n"
+ "\n"
+ " Nothing in this License shall be construed as excluding or limiting\n"
+ "any implied license or other defenses to infringement that may\n"
+ "otherwise be available to you under applicable patent law.\n"
+ "\n"
+ " 12. No Surrender of Others' Freedom.\n"
+ "\n"
+ " If conditions are imposed on you (whether by court order, agreement or\n"
+ "otherwise) that contradict the conditions of this License, they do not\n"
+ "excuse you from the conditions of this License. If you cannot convey a\n"
+ "covered work so as to satisfy simultaneously your obligations under this\n"
+ "License and any other pertinent obligations, then as a consequence you may\n"
+ "not convey it at all. For example, if you agree to terms that obligate you\n"
+ "to collect a royalty for further conveying from those to whom you convey\n"
+ "the Program, the only way you could satisfy both those terms and this\n"
+ "License would be to refrain entirely from conveying the Program.\n"
+ "\n"
+ " 13. Use with the GNU Affero General Public License.\n"
+ "\n"
+ " Notwithstanding any other provision of this License, you have\n"
+ "permission to link or combine any covered work with a work licensed\n"
+ "under version 3 of the GNU Affero General Public License into a single\n"
+ "combined work, and to convey the resulting work. The terms of this\n"
+ "License will continue to apply to the part which is the covered work,\n"
+ "but the special requirements of the GNU Affero General Public License,\n"
+ "section 13, concerning interaction through a network will apply to the\n"
+ "combination as such.\n"
+ "\n"
+ " 14. Revised Versions of this License.\n"
+ "\n"
+ " The Free Software Foundation may publish revised and/or new versions of\n"
+ "the GNU General Public License from time to time. Such new versions will\n"
+ "be similar in spirit to the present version, but may differ in detail to\n"
+ "address new problems or concerns.\n"
+ "\n"
+ " Each version is given a distinguishing version number. If the\n"
+ "Program specifies that a certain numbered version of the GNU General\n"
+ "Public License \"or any later version\" applies to it, you have the\n"
+ "option of following the terms and conditions either of that numbered\n"
+ "version or of any later version published by the Free Software\n"
+ "Foundation. If the Program does not specify a version number of the\n"
+ "GNU General Public License, you may choose any version ever published\n"
+ "by the Free Software Foundation.\n"
+ "\n"
+ " If the Program specifies that a proxy can decide which future\n"
+ "versions of the GNU General Public License can be used, that proxy's\n"
+ "public statement of acceptance of a version permanently authorizes you\n"
+ "to choose that version for the Program.\n"
+ "\n"
+ " Later license versions may give you additional or different\n"
+ "permissions. However, no additional obligations are imposed on any\n"
+ "author or copyright holder as a result of your choosing to follow a\n"
+ "later version.\n"
+ "\n"
+ " 15. Disclaimer of Warranty.\n"
+ "\n"
+ " THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\n"
+ "APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\n"
+ "HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\n"
+ "OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\n"
+ "THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n"
+ "PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\n"
+ "IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\n"
+ "ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n"
+ "\n"
+ " 16. Limitation of Liability.\n"
+ "\n"
+ " IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n"
+ "WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\n"
+ "THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\n"
+ "GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\n"
+ "USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\n"
+ "DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\n"
+ "PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\n"
+ "EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\n"
+ "SUCH DAMAGES.\n"
+ "\n"
+ " 17. Interpretation of Sections 15 and 16.\n"
+ "\n"
+ " If the disclaimer of warranty and limitation of liability provided\n"
+ "above cannot be given local legal effect according to their terms,\n"
+ "reviewing courts shall apply local law that most closely approximates\n"
+ "an absolute waiver of all civil liability in connection with the\n"
+ "Program, unless a warranty or assumption of liability accompanies a\n"
+ "copy of the Program in return for a fee.\n"
+ "\n"
+ " END OF TERMS AND CONDITIONS\n"
+ "\n"
+ " How to Apply These Terms to Your New Programs\n"
+ "\n"
+ " If you develop a new program, and you want it to be of the greatest\n"
+ "possible use to the public, the best way to achieve this is to make it\n"
+ "free software which everyone can redistribute and change under these terms.\n"
+ "\n"
+ " To do so, attach the following notices to the program. It is safest\n"
+ "to attach them to the start of each source file to most effectively\n"
+ "state the exclusion of warranty; and each file should have at least\n"
+ "the \"copyright\" line and a pointer to where the full notice is found.\n"
+ "\n"
+ " \n"
+ " Copyright (C) \n"
+ "\n"
+ " This program is free software: you can redistribute it and/or modify\n"
+ " it under the terms of the GNU General Public License as published by\n"
+ " the Free Software Foundation, either version 3 of the License, or\n"
+ " (at your option) any later version.\n"
+ "\n"
+ " This program is distributed in the hope that it will be useful,\n"
+ " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ " GNU General Public License for more details.\n"
+ "\n"
+ " You should have received a copy of the GNU General Public License\n"
+ " along with this program. If not, see .\n"
+ "\n"
+ "Also add information on how to contact you by electronic and paper mail.\n"
+ "\n"
+ " If the program does terminal interaction, make it output a short\n"
+ "notice like this when it starts in an interactive mode:\n"
+ "\n"
+ " Copyright (C) \n"
+ " This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n"
+ " This is free software, and you are welcome to redistribute it\n"
+ " under certain conditions; type `show c' for details.\n"
+ "\n"
+ "The hypothetical commands `show w' and `show c' should show the appropriate\n"
+ "parts of the General Public License. Of course, your program's commands\n"
+ "might be different; for a GUI interface, you would use an \"about box\".\n"
+ "\n"
+ " You should also get your employer (if you work as a programmer) or school,\n"
+ "if any, to sign a \"copyright disclaimer\" for the program, if necessary.\n"
+ "For more information on this, and how to apply and follow the GNU GPL, see\n"
+ ".\n"
+ "\n"
+ " The GNU General Public License does not permit incorporating your program\n"
+ "into proprietary programs. If your program is a subroutine library, you\n"
+ "may consider it more useful to permit linking proprietary applications with\n"
+ "the library. If this is what you want to do, use the GNU Lesser General\n"
+ "Public License instead of this License. But first, please read\n"
+ ".\n"},
{"LVGL 9.6.0", "the graphics library", "MIT",
"MIT licence\n"
"Copyright (c) 2025 LVGL Kft\n"
diff --git a/include/multiboot.h b/include/multiboot.h
index f1fd15d..03e5f8b 100644
--- a/include/multiboot.h
+++ b/include/multiboot.h
@@ -51,7 +51,7 @@ int multiboot_prepare(const struct storage_device *device,
void *opaque, struct multiboot_layout *layout, char *error,
size_t error_size);
int multiboot_install(const struct multiboot_config *config,
- struct multiboot_layout *layout, multiboot_progress_cb callback,
+ const struct multiboot_layout *layout, multiboot_progress_cb callback,
multiboot_display_clear_cb clear_display, void *opaque, char *error,
size_t error_size);
int multiboot_detect_active_slot(const struct multiboot_layout *layout,
diff --git a/sonar-project.properties b/sonar-project.properties
index 61cd485..63fdfbb 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -6,8 +6,8 @@ sonar.projectName=OE-Alliance SmallBox Wizard
sonar.sourceEncoding=UTF-8
-# LVGL is a submodule and the fonts are generated.
-sonar.exclusions=lib/**,src/fonts/**
+# LVGL is a submodule, the fonts and the license texts are generated.
+sonar.exclusions=lib/**,src/fonts/**,include/licenses.h
# The wizard has no unit tests, the coverage is not measured.
sonar.coverage.exclusions=**/*
diff --git a/src/about.c b/src/about.c
index 0d67abd..802a594 100644
--- a/src/about.c
+++ b/src/about.c
@@ -81,7 +81,7 @@ void about(const struct ui_context *ui, struct input_context *input, const volat
items[i] = rows[i];
}
snprintf(body, sizeof(body), "OE-Alliance SmallBox Wizard " SMALLBOX_WIZARD_VERSION "\n" COPYRIGHT
- "\n\nFree software under the MIT license. OK shows the full license of each part.");
+ "\n\nFree software under the GPLv3. OK shows the full license of each part.");
snprintf(header, sizeof(header), wide ? "Component\tPart\tLicense" : "Component\tLicense");
ui_keys(footer, sizeof(footer), &(struct ui_key_names){.arrows = "Select", .ok = "License",
.back = "Back"});
diff --git a/src/main.c b/src/main.c
index a7e643b..ada0464 100644
--- a/src/main.c
+++ b/src/main.c
@@ -110,8 +110,13 @@ static void step(enum step current)
static const char *const icons[STEP_COUNT] = {UI_ICON_WELCOME, UI_ICON_MODE, UI_ICON_STORAGE, UI_ICON_NETWORK,
UI_ICON_INSTALL, UI_ICON_FINISH};
char marks[STEP_COUNT];
- for (int i = 0; i < STEP_COUNT; ++i)
- marks[i] = i < (int)current ? 1 : i > (int)current ? 2 : 0;
+ for (int i = 0; i < STEP_COUNT; ++i) {
+ marks[i] = 0;
+ if (i < (int)current)
+ marks[i] = 1;
+ else if (i > (int)current)
+ marks[i] = 2;
+ }
ui_sidebar(&app.ui, names, icons, STEP_COUNT, marks);
ui_sidebar_select(&app.ui, current, 0);
}
@@ -277,13 +282,10 @@ static int demo_install_packages(void)
struct network_package_failure failure = {.package = "enigma2-plugin-demo", .detail =
"Demo: a package that cannot be downloaded.", .status = 255, .total = 663, .installed = 331, .remaining = 332};
demo_work(statuses, 2, 3000);
- switch (package_failure(&failure, NULL)) {
- case NETWORK_PACKAGE_ABORT:
+ if (package_failure(&failure, NULL) == NETWORK_PACKAGE_ABORT)
return 0;
- default:
- demo_work(statuses + 2, 3, 4000);
- return 1;
- }
+ demo_work(statuses + 2, 3, 4000);
+ return 1;
}
/* USB storage */
@@ -315,68 +317,94 @@ static int confirm_erase(const struct storage_device *device, int chkroot)
return choose("Final confirmation", NULL, warning, second, 2, 0, "Confirm") == 1;
}
-/* Chooses and prepares the USB device, with the layout for Chkroot. */
-static int configure_storage(const struct multiboot_config *config, int chkroot, char *uuid, size_t uuid_size,
- struct multiboot_layout *layout)
+/* The text above the list of USB devices. */
+static const char *storage_hint(int count, int chkroot)
+{
+ if (!count)
+ return chkroot ? "Connect a USB device with at least 2 GB, then scan again." :
+ "Connect a USB device with at least 1 GB, then scan again.";
+ if (chkroot)
+ return "It will hold the complete Enigma2 root file system. Only real USB block devices are shown.";
+ return "/usr of the receiver moves there. Only real USB block devices are shown.";
+}
+
+/* The USB devices with the fake one of the demo, at most UI_MAX_ITEMS - 1; -1 when the scan failed. */
+static int scan_devices(struct storage_device devices[STORAGE_MAX_DEVICES], char *error, size_t error_size)
+{
+ int count = storage_scan_usb(devices, STORAGE_MAX_DEVICES, error, error_size);
+ if (count < 0)
+ return -1;
+ if (app.demo && count < STORAGE_MAX_DEVICES)
+ devices[count++] = (struct storage_device){.name = "sdx", .path = "/dev/sdx",
+ .model = "Demo USB stick", .size_bytes = 32ULL << 30, .removable = 1};
+ return count > UI_MAX_ITEMS - 1 ? UI_MAX_ITEMS - 1 : count;
+}
+
+/* What the USB setup prepares: the UUID of FlashExpander or the layout of Chkroot. */
+struct storage_target {
+ const struct multiboot_config *config;
+ int chkroot;
+ char *uuid;
+ size_t uuid_size;
+ struct multiboot_layout *layout;
+};
+
+/* Partitions and prepares the chosen device, 1 when done. */
+static int prepare_device(const struct storage_device *device, const struct storage_target *target, char *error,
+ size_t error_size)
+{
+ int result;
+ app.busy = 1;
+ start_progress(target->chkroot ? "Setting up Chkroot on USB" : "Setting up the USB storage");
+ if (app.demo) {
+ static const char *const statuses[] = {"Creating the partitions", "Formatting", "Activating swap",
+ "Copying /usr"};
+ demo_work(statuses, target->chkroot ? 3 : 4, 5000);
+ snprintf(target->uuid, target->uuid_size, "demo");
+ result = 1;
+ } else if (target->chkroot)
+ result = multiboot_prepare(device, target->config, progress, NULL, target->layout, error, error_size);
+ else
+ result = storage_prepare(device, progress, NULL, target->uuid, target->uuid_size, error, error_size);
+ app.busy = 0;
+ return result;
+}
+
+/* Chooses and prepares the USB device. */
+static int configure_storage(const struct storage_target *target)
{
struct storage_device devices[STORAGE_MAX_DEVICES];
char labels[UI_MAX_ITEMS][192];
const char *items[UI_MAX_ITEMS];
char error[512];
- int count;
- int choice;
for (;;) {
- struct storage_device *device;
- int result;
- count = storage_scan_usb(devices, STORAGE_MAX_DEVICES, error, sizeof(error));
+ const struct storage_device *device;
+ char capacity[32];
+ int choice;
+ int count = scan_devices(devices, error, sizeof(error));
if (count < 0) {
show_error("USB detection failed", error);
return 0;
}
- if (app.demo && count < STORAGE_MAX_DEVICES)
- devices[count++] = (struct storage_device){.name = "sdx", .path = "/dev/sdx",
- .model = "Demo USB stick", .size_bytes = 32ULL << 30, .removable = 1};
- if (count > UI_MAX_ITEMS - 1)
- count = UI_MAX_ITEMS - 1;
for (int i = 0; i < count; ++i) {
device_text(&devices[i], labels[i], sizeof(labels[i]));
items[i] = labels[i];
}
items[count] = "Scan for USB devices again";
- choice = choose("Choose the USB device",
- count ? (chkroot ? "It will hold the complete Enigma2 root file system. Only real USB block devices "
- "are shown." : "/usr of the receiver moves there. Only real USB block devices are shown.") :
- (chkroot ? "Connect a USB device with at least 2 GB, then scan again." :
- "Connect a USB device with at least 1 GB, then scan again."),
- NULL, items, count + 1, 0, "Continue");
+ choice = choose("Choose the USB device", storage_hint(count, target->chkroot), NULL, items, count + 1, 0,
+ "Continue");
if (stop_requested || choice < 0)
return 0;
if (choice == count)
continue;
device = &devices[choice];
- {
- char capacity[32];
- storage_format_size(device->size_bytes, capacity, sizeof(capacity));
- snprintf(app.setup.usb, sizeof(app.setup.usb), "%.96s, %s", device->model, capacity);
- }
- if (!confirm_erase(device, chkroot)) {
+ storage_format_size(device->size_bytes, capacity, sizeof(capacity));
+ snprintf(app.setup.usb, sizeof(app.setup.usb), "%.96s, %s", device->model, capacity);
+ if (!confirm_erase(device, target->chkroot)) {
app.setup.usb[0] = '\0';
continue;
}
- app.busy = 1;
- start_progress(chkroot ? "Setting up Chkroot on USB" : "Setting up the USB storage");
- if (app.demo) {
- static const char *const statuses[] = {"Creating the partitions", "Formatting", "Activating swap",
- "Copying /usr"};
- demo_work(statuses, chkroot ? 3 : 4, 5000);
- snprintf(uuid, uuid_size, "demo");
- result = 1;
- } else if (chkroot)
- result = multiboot_prepare(device, config, progress, NULL, layout, error, sizeof(error));
- else
- result = storage_prepare(device, progress, NULL, uuid, uuid_size, error, sizeof(error));
- app.busy = 0;
- if (result)
+ if (prepare_device(device, target, error, sizeof(error)))
return 1;
app.setup.usb[0] = '\0';
show_error("USB setup failed", error);
@@ -385,38 +413,70 @@ static int configure_storage(const struct multiboot_config *config, int chkroot,
/* Network */
+/* The choices of the network screen: map is -2 for the existing connection, -3 to scan again, else the index of
+ * the interface. Returns how many. */
+static int network_items(const struct network_interface interfaces[], int count, const char *current_interface,
+ const char *current_address, char labels[][160], const char *items[], int map[])
+{
+ int item_count = 0;
+ if (current_interface[0]) {
+ snprintf(labels[item_count], sizeof(labels[item_count]), "Use the existing connection\t%.31s\t%.47s",
+ current_interface, current_address);
+ items[item_count] = labels[item_count];
+ map[item_count++] = -2;
+ }
+ for (int i = 0; i < count; ++i) {
+ if (item_count >= UI_MAX_ITEMS - 1)
+ break;
+ if (interfaces[i].wireless)
+ continue;
+ snprintf(labels[item_count], sizeof(labels[item_count]), "LAN with DHCP\t%.31s\t%s", interfaces[i].name,
+ interfaces[i].link ? "Cable connected" : "No link");
+ items[item_count] = labels[item_count];
+ map[item_count++] = i;
+ }
+ items[item_count] = "Scan for network interfaces again";
+ map[item_count++] = -3;
+ return item_count;
+}
+
+/* DHCP on the interface, 1 when it has an address. */
+static int connect_interface(const struct network_interface *interface, char *chosen_address, size_t address_size,
+ char *error, size_t error_size)
+{
+ int result = 1;
+ app.busy = 1;
+ start_progress("Setting up the network");
+ if (app.demo) {
+ static const char *const statuses[] = {"Requesting an address with DHCP"};
+ demo_work(statuses, 1, 2000);
+ snprintf(chosen_address, address_size, "%s", interface->address[0] ? interface->address :
+ "192.168.0.99"); /* NOSONAR a made-up address of the demo */
+ } else
+ result = network_configure_dhcp(interface->name, progress, NULL, chosen_address, address_size, error,
+ error_size);
+ app.busy = 0;
+ return result;
+}
+
static int configure_network(char *chosen_interface, size_t interface_size, char *chosen_address,
size_t address_size)
{
struct network_interface interfaces[NETWORK_MAX_INTERFACES];
char labels[UI_MAX_ITEMS][160];
const char *items[UI_MAX_ITEMS];
- char current_interface[32] = "";
- char current_address[48] = "";
char error[512];
int map[UI_MAX_ITEMS];
for (;;) {
+ char current_interface[32] = "";
+ char current_address[48] = "";
int count = network_scan(interfaces, NETWORK_MAX_INTERFACES);
- int item_count = 0;
+ int item_count;
int choice;
- int i;
- if (network_has_ipv4(current_interface, sizeof(current_interface), current_address,
- sizeof(current_address))) {
- snprintf(labels[item_count], sizeof(labels[item_count]), "Use the existing connection\t%.31s\t%.47s",
- current_interface, current_address);
- items[item_count] = labels[item_count];
- map[item_count++] = -2;
- }
- for (i = 0; i < count && item_count < UI_MAX_ITEMS - 1; ++i) {
- if (interfaces[i].wireless)
- continue;
- snprintf(labels[item_count], sizeof(labels[item_count]), "LAN with DHCP\t%.31s\t%s", interfaces[i].name,
- interfaces[i].link ? "Cable connected" : "No link");
- items[item_count] = labels[item_count];
- map[item_count++] = i;
- }
- items[item_count] = "Scan for network interfaces again";
- map[item_count++] = -3;
+ if (!network_has_ipv4(current_interface, sizeof(current_interface), current_address,
+ sizeof(current_address)))
+ current_interface[0] = '\0';
+ item_count = network_items(interfaces, count, current_interface, current_address, labels, items, map);
choice = choose("Set up the network",
"The installation needs internet access. The wizard sets up wired LAN with DHCP, Wi-Fi can be set up "
"later in Enigma2.", NULL, items, item_count, 0, "Connect");
@@ -429,23 +489,10 @@ static int configure_network(char *chosen_interface, size_t interface_size, char
snprintf(chosen_address, address_size, "%s", current_address);
return 1;
}
- i = map[choice];
- app.busy = 1;
- start_progress("Setting up the network");
- if (app.demo) {
- static const char *const statuses[] = {"Requesting an address with DHCP"};
- demo_work(statuses, 1, 2000);
- snprintf(chosen_address, address_size, "%s", interfaces[i].address[0] ? interfaces[i].address :
- "192.168.0.99");
- error[0] = '\0';
- }
- if (app.demo || network_configure_dhcp(interfaces[i].name, progress, NULL, chosen_address, address_size,
- error, sizeof(error))) {
- app.busy = 0;
- snprintf(chosen_interface, interface_size, "%s", interfaces[i].name);
+ if (connect_interface(&interfaces[map[choice]], chosen_address, address_size, error, sizeof(error))) {
+ snprintf(chosen_interface, interface_size, "%s", interfaces[map[choice]].name);
return 1;
}
- app.busy = 0;
show_error("Network setup failed", error);
}
}
@@ -453,7 +500,7 @@ static int configure_network(char *chosen_interface, size_t interface_size, char
/* Installation */
/* 1 when installed, -1 to set up the network again, 0 to stop. */
-static int install(const struct multiboot_config *config, struct multiboot_layout *layout, int chkroot)
+static int install(const struct multiboot_config *config, const struct multiboot_layout *layout, int chkroot)
{
const char *const items[] = {chkroot ? "Download and install the image" : "Install the SmallBox packages now",
"Set up the network again"};
@@ -502,6 +549,7 @@ static int write_done_marker(const char *uuid, const char *interface, const char
int fd = mkstemp(temporary);
FILE *file;
time_t now = time(NULL);
+ int saved;
if (fd < 0) {
snprintf(error, error_size, "The completion marker cannot be created: %s", strerror(errno));
return 0;
@@ -516,7 +564,10 @@ static int write_done_marker(const char *uuid, const char *interface, const char
}
fprintf(file, "version=%s\ncompleted=%lld\nuuid=%s\ninterface=%s\nip=%s\n", VERSION, (long long)now,
uuid ? uuid : "", interface ? interface : "", address ? address : "");
- if (fflush(file) != 0 || fsync(fd) != 0 || fclose(file) != 0 || rename(temporary, DONE_MARKER) != 0) {
+ saved = fflush(file) == 0 && fsync(fd) == 0;
+ if (fclose(file) != 0) /* Closed also after a failed flush. */
+ saved = 0;
+ if (!saved || rename(temporary, DONE_MARKER) != 0) {
unlink(temporary);
snprintf(error, error_size, "The completion marker could not be saved: %s", strerror(errno));
return 0;
@@ -582,153 +633,209 @@ static int previous_setup(void)
}
}
-static int run_wizard(void)
-{
+/* What the wizard sets up, filled step by step. */
+struct wizard {
struct multiboot_config config;
struct multiboot_layout layout;
- struct setup *s = &app.setup;
- char uuid[128] = "";
- char interface[32] = "";
- char address[48] = "";
+ char uuid[128];
+ char interface[32];
+ char address[48];
char error[512];
- char footer[96];
- int active = 0;
- int chkroot = 0;
+ int chkroot;
+};
+/* The configuration and the start of the summary, 0 when it cannot be read. */
+static int load_setup(struct wizard *w)
+{
+ struct setup *s = &app.setup;
if (app.demo)
- demo_config(&config);
- else if (!multiboot_config_load(&config, error, sizeof(error))) {
- show_error("Wizard configuration", error);
- return 1;
+ demo_config(&w->config);
+ else if (!multiboot_config_load(&w->config, w->error, sizeof(w->error))) {
+ show_error("Wizard configuration", w->error);
+ return 0;
}
- snprintf(s->receiver, sizeof(s->receiver), "%s", config.machine_build[0] ? config.machine_build :
- config.machine);
+ snprintf(s->receiver, sizeof(s->receiver), "%s", w->config.machine_build[0] ? w->config.machine_build :
+ w->config.machine);
ui_header(&app.ui, app.demo ? "Demo mode" : ""); /* The receiver is in the summary. */
eth0_ipv4(s->address, sizeof(s->address));
+ return 1;
+}
- step(STEP_WELCOME);
- if (!app.demo)
- active = storage_is_expander_active(uuid, sizeof(uuid));
- if (active && config.policy != MULTIBOOT_REQUIRED) {
- int result = previous_setup();
- if (result < 0)
- return 2;
- if (result == 0) {
- active = 0;
- uuid[0] = '\0';
- } else {
- snprintf(s->mode, sizeof(s->mode), "FlashExpander");
- snprintf(s->usb, sizeof(s->usb), "Prepared earlier");
- }
+/* An earlier run prepared the USB storage: 1 to keep it, 0 to set it up again, -1 to stop. */
+static int earlier_storage(struct wizard *w)
+{
+ int result;
+ if (app.demo || !storage_is_expander_active(w->uuid, sizeof(w->uuid)))
+ return 0;
+ if (w->config.policy == MULTIBOOT_REQUIRED)
+ return 0;
+ result = previous_setup();
+ if (result == 1) {
+ snprintf(app.setup.mode, sizeof(app.setup.mode), "FlashExpander");
+ snprintf(app.setup.usb, sizeof(app.setup.usb), "Prepared earlier");
+ } else
+ w->uuid[0] = '\0';
+ return result;
+}
+
+/* The mode the configuration allows: 0 for FlashExpander, 1 for Chkroot, -1 for BACK. */
+static int choose_mode(const struct multiboot_config *config)
+{
+ if (config->policy == MULTIBOOT_OPTIONAL) {
+ const char *const modes[] = {"FlashExpander\t/usr on USB, the image stays in flash",
+ "Chkroot Multiboot\tThe whole image on USB, the kernel stays in flash"};
+ int choice = choose("How should the receiver use USB?",
+ "An SSD or a hard disk is faster than a cheap USB stick. With Chkroot the USB device must "
+ "stay connected.", NULL, modes, 2, 0, "Continue");
+ return choice < 0 ? -1 : choice == 1;
}
- if (!active || config.policy == MULTIBOOT_REQUIRED) {
- for (;;) {
- step(STEP_WELCOME);
- ui_keys(footer, sizeof(footer), &(struct ui_key_names){.ok = "Start setup"});
- show("Welcome",
- "This wizard prepares the receiver for its small flash and memory. It has to be completed before "
- "Enigma2 can start.\n\nYou need a USB device, which will be erased, and a wired network with "
- "internet access.\n\nINFO shows the version and the licenses.", footer, 0);
- if (stop_requested)
- return 2;
-
- step(STEP_MODE);
- if (config.policy == MULTIBOOT_OPTIONAL) {
- const char *const modes[] = {"FlashExpander\t/usr on USB, the image stays in flash",
- "Chkroot Multiboot\tThe whole image on USB, the kernel stays in flash"};
- int choice = choose("How should the receiver use USB?",
- "An SSD or a hard disk is faster than a cheap USB stick. With Chkroot the USB device must "
- "stay connected.", NULL, modes, 2, 0, "Continue");
- if (stop_requested)
- return 2;
- if (choice < 0)
- continue;
- chkroot = choice == 1;
- } else if (config.policy == MULTIBOOT_REQUIRED) {
- const char *const required[] = {"Chkroot Multiboot\tThe whole image on USB"};
- int choice = choose("Chkroot is required",
- "The flash of this receiver holds only the bootstrap and the shared kernel. The complete "
- "Enigma2 root file system is installed on USB.", NULL, required, 1, 0, "Continue");
- if (stop_requested)
- return 2;
- if (choice < 0)
- continue;
- chkroot = 1;
- }
- snprintf(s->mode, sizeof(s->mode), chkroot ? "Chkroot Multiboot" : "FlashExpander");
-
- step(STEP_STORAGE);
- if (configure_storage(&config, chkroot, uuid, sizeof(uuid), &layout))
- break;
- if (stop_requested)
- return 2;
- s->mode[0] = '\0'; /* BACK, to the welcome again. */
- }
+ if (config->policy == MULTIBOOT_REQUIRED) {
+ const char *const required[] = {"Chkroot Multiboot\tThe whole image on USB"};
+ int choice = choose("Chkroot is required",
+ "The flash of this receiver holds only the bootstrap and the shared kernel. The complete "
+ "Enigma2 root file system is installed on USB.", NULL, required, 1, 0, "Continue");
+ return choice < 0 ? -1 : 1;
}
+ return 0;
+}
+
+/* Welcome, mode and USB storage until the storage is prepared, 0 to stop. */
+static int setup_storage(struct wizard *w)
+{
+ char footer[96];
+ ui_keys(footer, sizeof(footer), &(struct ui_key_names){.ok = "Start setup"});
+ for (;;) {
+ int mode;
+ step(STEP_WELCOME);
+ show("Welcome",
+ "This wizard prepares the receiver for its small flash and memory. It has to be completed before "
+ "Enigma2 can start.\n\nYou need a USB device, which will be erased, and a wired network with "
+ "internet access.\n\nINFO shows the version and the licenses.", footer, 0);
+ if (stop_requested)
+ return 0;
+ step(STEP_MODE);
+ mode = choose_mode(&w->config);
+ if (stop_requested)
+ return 0;
+ if (mode < 0)
+ continue;
+ w->chkroot = mode;
+ snprintf(app.setup.mode, sizeof(app.setup.mode), w->chkroot ? "Chkroot Multiboot" : "FlashExpander");
+ step(STEP_STORAGE);
+ if (configure_storage(&(struct storage_target){&w->config, w->chkroot, w->uuid, sizeof(w->uuid),
+ &w->layout}))
+ return 1;
+ if (stop_requested)
+ return 0;
+ app.setup.mode[0] = '\0'; /* BACK, to the welcome again. */
+ }
+}
+/* Sets the clock for the downloads, 1 when it is right. */
+static int set_clock(struct wizard *w)
+{
+ int result = 1;
+ app.busy = 1;
+ start_progress("Setting the clock");
+ if (app.demo) {
+ static const char *const statuses[] = {"Asking the time servers"};
+ demo_work(statuses, 1, 1500);
+ } else
+ result = network_synchronize_time(progress, NULL, w->error, sizeof(w->error));
+ app.busy = 0;
+ if (!result)
+ show_error("The clock could not be set", w->error);
+ return result;
+}
+
+/* Network, clock and installation until installed, 0 to stop. */
+static int network_and_install(struct wizard *w)
+{
for (;;) {
int result;
step(STEP_NETWORK);
- if (!configure_network(interface, sizeof(interface), address, sizeof(address)))
- return 2;
- snprintf(s->network, sizeof(s->network), "%s, %s", interface, address);
- app.busy = 1;
- start_progress("Setting the clock");
- if (app.demo) {
- static const char *const statuses[] = {"Asking the time servers"};
- demo_work(statuses, 1, 1500);
- } else if (!network_synchronize_time(progress, NULL, error, sizeof(error))) {
- app.busy = 0;
- show_error("The clock could not be set", error);
+ if (!configure_network(w->interface, sizeof(w->interface), w->address, sizeof(w->address)))
+ return 0;
+ snprintf(app.setup.network, sizeof(app.setup.network), "%s, %s", w->interface, w->address);
+ if (!set_clock(w))
continue;
- }
- app.busy = 0;
step(STEP_INSTALL);
- result = install(&config, &layout, chkroot);
- if (result == 1)
- break;
- if (result == 0)
- return 2;
- s->network[0] = '\0';
+ result = install(&w->config, &w->layout, w->chkroot);
+ if (result >= 0)
+ return result;
+ app.setup.network[0] = '\0';
}
+}
- if (!app.demo && !chkroot) {
- if (config.single_core && !network_disable_optional_services(progress, NULL, error, sizeof(error))) {
- show_error("Single-core boot profile failed", error);
- return 1;
- }
- if (!write_done_marker(uuid, interface, address, error, sizeof(error))) {
- show_error("Completion failed", error);
- return 1;
- }
- } else if (!app.demo)
+/* The markers that end the setup, 0 when they could not be written. */
+static int write_markers(struct wizard *w)
+{
+ if (app.demo)
+ return 1;
+ if (w->chkroot) {
write_reboot_marker();
- sync();
+ return 1;
+ }
+ if (w->config.single_core && !network_disable_optional_services(progress, NULL, w->error, sizeof(w->error))) {
+ show_error("Single-core boot profile failed", w->error);
+ return 0;
+ }
+ if (!write_done_marker(w->uuid, w->interface, w->address, w->error, sizeof(w->error))) {
+ show_error("Completion failed", w->error);
+ return 0;
+ }
+ return 1;
+}
+
+static void reboot_receiver(void)
+{
+ char reboot_path[256];
+ if (process_find("reboot", reboot_path, sizeof(reboot_path))) {
+ char *argv[] = {reboot_path, NULL};
+ process_run(argv, NULL, NULL, NULL);
+ }
+}
+/* The last screen, then the restart unless in test or demo mode. */
+static void finish(int chkroot)
+{
+ char footer[96];
+ const char *title = chkroot ? "Chkroot SmallBox is ready" : "SmallBox is ready";
+ const char *body = chkroot ?
+ "The complete Enigma2 root file system is installed in the verified USB slot. The internal kernel was "
+ "not flashed. The receiver restarts into Chkroot now.\n\n" KEEP_USB :
+ "FlashExpander, 512 MB swap, network and the SmallBox packages are ready. The receiver restarts now."
+ "\n\n" KEEP_USB;
step(STEP_DONE);
- ui_keys(footer, sizeof(footer), &(struct ui_key_names){.ok = "Exit"});
- {
- const char *title = chkroot ? "Chkroot SmallBox is ready" : "SmallBox is ready";
- const char *body = chkroot ?
- "The complete Enigma2 root file system is installed in the verified USB slot. The internal kernel was "
- "not flashed. The receiver restarts into Chkroot now.\n\n" KEEP_USB :
- "FlashExpander, 512 MB swap, network and the SmallBox packages are ready. The receiver restarts now."
- "\n\n" KEEP_USB;
- if (app.no_reboot || app.demo) {
- show(title, body, footer, 0);
- return 0;
- }
- summary(1);
- ui_screen(&app.ui, title, body, "Restarting...");
+ if (app.no_reboot || app.demo) {
+ ui_keys(footer, sizeof(footer), &(struct ui_key_names){.ok = "Exit"});
+ show(title, body, footer, 0);
+ return;
}
+ summary(1);
+ ui_screen(&app.ui, title, body, "Restarting...");
sleep(2);
- {
- char reboot_path[256];
- if (process_find("reboot", reboot_path, sizeof(reboot_path))) {
- char *argv[] = {reboot_path, NULL};
- process_run(argv, NULL, NULL, NULL);
- }
- }
+ reboot_receiver();
+}
+
+static int run_wizard(void)
+{
+ static struct wizard w;
+ int earlier;
+ if (!load_setup(&w))
+ return 1;
+ step(STEP_WELCOME);
+ earlier = earlier_storage(&w);
+ if (earlier < 0)
+ return 2;
+ if (!earlier && !setup_storage(&w))
+ return 2;
+ if (!network_and_install(&w))
+ return 2;
+ if (!write_markers(&w))
+ return 1;
+ sync();
+ finish(w.chkroot);
return 0;
}
diff --git a/src/multiboot.c b/src/multiboot.c
index bc9bfab..c0fd07f 100644
--- a/src/multiboot.c
+++ b/src/multiboot.c
@@ -30,13 +30,14 @@
#define FSTAB_BEGIN "# SMALLBOX-CHKROOT BEGIN"
#define FSTAB_END "# SMALLBOX-CHKROOT END"
-static void set_error(char *error, size_t error_size, const char *format, ...)
+__attribute__((format(printf, 3, 4)))
+static void set_error(char *error, size_t error_size, const char *format, ...) /* NOSONAR printf-style error text */
{
va_list arguments;
if (!error || error_size == 0)
return;
va_start(arguments, format);
- vsnprintf(error, error_size, format, arguments);
+ vsnprintf(error, error_size, format, arguments); /* NOSONAR format is checked by the printf attribute */
va_end(arguments);
}
@@ -49,11 +50,11 @@ static void progress(multiboot_progress_cb callback, void *opaque, int percent,
static void trim(char *text)
{
- char *start = text;
+ const char *start = text;
size_t length;
if (!text)
return;
- while (*start && isspace((unsigned char)*start))
+ while (*start && isspace((unsigned char)*start)) /* NOSONAR isspace on a char of a NUL-terminated string */
start++;
if (start != text)
memmove(text, start, strlen(start) + 1);
@@ -65,7 +66,7 @@ static void trim(char *text)
static int ensure_directory(const char *path, mode_t mode)
{
struct stat status;
- if (mkdir(path, mode) == 0)
+ if (mkdir(path, mode) == 0) /* NOSONAR a work or mount directory of the wizard */
return 1;
return errno == EEXIST && stat(path, &status) == 0 &&
S_ISDIR(status.st_mode);
@@ -73,7 +74,7 @@ static int ensure_directory(const char *path, mode_t mode)
static int fsync_directory(const char *path)
{
- int fd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ int fd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC); /* NOSONAR the directory of a file the wizard wrote */
int result;
if (fd < 0)
return 0;
@@ -88,22 +89,25 @@ static int write_atomic(const char *path, const char *text, mode_t mode)
char directory[PATH_MAX];
char *slash;
int fd;
+ int written;
FILE *file;
if (snprintf(temporary, sizeof(temporary), "%s.tmp.%ld", path,
(long)getpid()) >= (int)sizeof(temporary))
return 0;
- fd = open(temporary, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode);
+ fd = open(temporary, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode); /* NOSONAR next to a file of the wizard */
if (fd < 0)
return 0;
file = fdopen(fd, "w");
if (!file) {
close(fd);
- unlink(temporary);
+ unlink(temporary); /* NOSONAR the temporary file created just above */
return 0;
}
- if (fputs(text, file) == EOF || fflush(file) != 0 || fsync(fd) != 0 ||
- fclose(file) != 0 || rename(temporary, path) != 0) {
- unlink(temporary);
+ written = fputs(text, file) != EOF && fflush(file) == 0 && fsync(fd) == 0;
+ if (fclose(file) != 0)
+ written = 0;
+ if (!written || rename(temporary, path) != 0) { /* NOSONAR a file of the wizard */
+ unlink(temporary); /* NOSONAR the temporary file created above */
return 0;
}
snprintf(directory, sizeof(directory), "%s", path);
@@ -125,11 +129,11 @@ static int read_text(const char *path, char *text, size_t text_size)
if (!text || text_size == 0)
return 0;
text[0] = '\0';
- file = fopen(path, "r");
+ file = fopen(path, "r"); /* NOSONAR /proc or a STARTUP file of the wizard */
if (!file)
return 0;
used = fread(text, 1, text_size - 1, file);
- text[used] = '\0';
+ text[used] = '\0'; /* NOSONAR used is limited to the buffer just above */
fclose(file);
trim(text);
return used != 0;
@@ -142,6 +146,43 @@ static void config_value(char *destination, size_t destination_size,
snprintf(destination, destination_size, "%s", value ? value : "");
}
+static enum multiboot_policy parse_policy(const char *value)
+{
+ if (strcmp(value, "required") == 0)
+ return MULTIBOOT_REQUIRED;
+ if (strcmp(value, "optional") == 0)
+ return MULTIBOOT_OPTIONAL;
+ return MULTIBOOT_DISABLED;
+}
+
+static void apply_config_entry(struct multiboot_config *config, const char *key,
+ const char *value)
+{
+ if (strcmp(key, "chkroot") == 0) {
+ config->policy = parse_policy(value);
+ } else if (strcmp(key, "machine") == 0) {
+ config_value(config->machine, sizeof(config->machine), value);
+ } else if (strcmp(key, "machine_build") == 0) {
+ config_value(config->machine_build,
+ sizeof(config->machine_build), value);
+ } else if (strcmp(key, "mtd_kernel") == 0) {
+ config_value(config->mtd_kernel, sizeof(config->mtd_kernel), value);
+ } else if (strcmp(key, "image_feed") == 0) {
+ config_value(config->image_feed, sizeof(config->image_feed), value);
+ } else if (strcmp(key, "image_url") == 0) {
+ config_value(config->image_url, sizeof(config->image_url), value);
+ } else if (strcmp(key, "image_match") == 0) {
+ config_value(config->image_match, sizeof(config->image_match), value);
+ } else if (strcmp(key, "maximum_slots") == 0) {
+ int slots = atoi(value);
+ if (slots >= 1 && slots <= 16)
+ config->maximum_slots = slots;
+ } else if (strcmp(key, "single_core") == 0) {
+ config->single_core = strcmp(value, "1") == 0 ||
+ strcmp(value, "yes") == 0 || strcmp(value, "true") == 0;
+ }
+}
+
int multiboot_config_load(struct multiboot_config *config, char *error,
size_t error_size)
{
@@ -177,34 +218,7 @@ int multiboot_config_load(struct multiboot_config *config, char *error,
value = separator + 1;
trim(key);
trim(value);
- if (strcmp(key, "chkroot") == 0) {
- if (strcmp(value, "required") == 0)
- config->policy = MULTIBOOT_REQUIRED;
- else if (strcmp(value, "optional") == 0)
- config->policy = MULTIBOOT_OPTIONAL;
- else
- config->policy = MULTIBOOT_DISABLED;
- } else if (strcmp(key, "machine") == 0) {
- config_value(config->machine, sizeof(config->machine), value);
- } else if (strcmp(key, "machine_build") == 0) {
- config_value(config->machine_build,
- sizeof(config->machine_build), value);
- } else if (strcmp(key, "mtd_kernel") == 0) {
- config_value(config->mtd_kernel, sizeof(config->mtd_kernel), value);
- } else if (strcmp(key, "image_feed") == 0) {
- config_value(config->image_feed, sizeof(config->image_feed), value);
- } else if (strcmp(key, "image_url") == 0) {
- config_value(config->image_url, sizeof(config->image_url), value);
- } else if (strcmp(key, "image_match") == 0) {
- config_value(config->image_match, sizeof(config->image_match), value);
- } else if (strcmp(key, "maximum_slots") == 0) {
- int slots = atoi(value);
- if (slots >= 1 && slots <= 16)
- config->maximum_slots = slots;
- } else if (strcmp(key, "single_core") == 0) {
- config->single_core = strcmp(value, "1") == 0 ||
- strcmp(value, "yes") == 0 || strcmp(value, "true") == 0;
- }
+ apply_config_entry(config, key, value);
}
fclose(file);
if (config->policy != MULTIBOOT_DISABLED &&
@@ -237,10 +251,9 @@ static int selected_device_is_valid(const struct storage_device *selected)
struct storage_device devices[STORAGE_MAX_DEVICES];
char error[128];
int count;
- int index;
count = storage_scan_usb(devices, STORAGE_MAX_DEVICES, error,
sizeof(error));
- for (index = 0; index < count; ++index) {
+ for (int index = 0; index < count; ++index) {
if (strcmp(devices[index].path, selected->path) == 0 &&
devices[index].size_bytes == selected->size_bytes)
return 1;
@@ -253,7 +266,7 @@ static int source_is_partition(const char *source, const char *device)
size_t length = strlen(device);
if (strncmp(source, device, length) != 0)
return 0;
- return source[length] == '\0' || source[length] == 'p' ||
+ return source[length] == '\0' || source[length] == 'p' || /* NOSONAR prefix matched, index in bounds */
isdigit((unsigned char)source[length]);
}
@@ -262,28 +275,34 @@ static void run_quiet(char *const argv[])
process_run(argv, NULL, NULL, NULL);
}
-static int deactivate_device(const struct storage_device *device, char *error,
- size_t error_size)
+static void swapoff_device(const struct storage_device *device)
{
FILE *file;
char line[1024];
- char mounts[32][PATH_MAX];
- int mount_count = 0;
- int index;
file = fopen("/proc/swaps", "r");
- if (file) {
- if (fgets(line, sizeof(line), file)) {
- while (fgets(line, sizeof(line), file)) {
- char source[PATH_MAX];
- if (sscanf(line, "%4095s", source) == 1 &&
- source_is_partition(source, device->path)) {
- char *argv[] = {"swapoff", source, NULL};
- run_quiet(argv);
- }
+ if (!file)
+ return;
+ if (fgets(line, sizeof(line), file)) {
+ while (fgets(line, sizeof(line), file)) {
+ char source[PATH_MAX];
+ if (sscanf(line, "%4095s", source) == 1 &&
+ source_is_partition(source, device->path)) {
+ char *argv[] = {"swapoff", source, NULL};
+ run_quiet(argv);
}
}
- fclose(file);
}
+ fclose(file);
+}
+
+static int deactivate_device(const struct storage_device *device, char *error,
+ size_t error_size)
+{
+ FILE *file;
+ char line[1024];
+ char mounts[32][PATH_MAX];
+ int mount_count = 0;
+ swapoff_device(device);
file = fopen("/proc/mounts", "r");
if (!file)
return 1;
@@ -304,7 +323,7 @@ static int deactivate_device(const struct storage_device *device, char *error,
snprintf(mounts[mount_count++], sizeof(mounts[0]), "%s", mountpoint);
}
fclose(file);
- for (index = mount_count - 1; index >= 0; --index) {
+ for (int index = mount_count - 1; index >= 0; --index) {
char *argv[] = {"umount", mounts[index], NULL};
if (process_run(argv, NULL, NULL, NULL) != 0) {
set_error(error, error_size, "Cannot unmount %s.", mounts[index]);
@@ -321,7 +340,7 @@ static int create_nomount(const char *name)
if (snprintf(path, sizeof(path), "/dev/nomount.%s", name) >=
(int)sizeof(path))
return 0;
- fd = open(path, O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
+ fd = open(path, O_WRONLY | O_CREAT | O_CLOEXEC, 0600); /* NOSONAR guard for a device the wizard found */
if (fd < 0)
return 0;
close(fd);
@@ -331,26 +350,28 @@ static int create_nomount(const char *name)
static int wait_for_partitions(const char *part1, const char *part2,
const char *part3)
{
- int attempt;
struct stat status1;
struct stat status2;
struct stat status3;
- for (attempt = 0; attempt < 120; ++attempt) {
+ const struct timespec delay = {0, 100000000L};
+ for (int attempt = 0; attempt < 120; ++attempt) {
if (stat(part1, &status1) == 0 && S_ISBLK(status1.st_mode) &&
stat(part2, &status2) == 0 && S_ISBLK(status2.st_mode) &&
stat(part3, &status3) == 0 && S_ISBLK(status3.st_mode))
return 1;
- usleep(100000);
+ nanosleep(&delay, NULL);
}
return 0;
}
-static int blkid_value(const char *partition, const char *field, char *value,
+static int blkid_value(char *partition, const char *field, char *value,
size_t value_size)
{
char blkid[PATH_MAX];
- char *argv[] = {blkid, "-s", (char *)field, "-o", "value",
- (char *)partition, NULL};
+ char field_argument[32];
+ char *argv[] = {blkid, "-s", field_argument, "-o", "value", partition,
+ NULL};
+ snprintf(field_argument, sizeof(field_argument), "%s", field);
if (!process_find("blkid", blkid, sizeof(blkid)) ||
process_capture(argv, value, value_size) != 0)
return 0;
@@ -372,6 +393,15 @@ static int append_word(char *output, size_t output_size, const char *word)
return 1;
}
+static int flash_boot_token(const char *token)
+{
+ return strncmp(token, "ubi.mtd=", 8) == 0 ||
+ strncmp(token, "rootfstype=", 11) == 0 ||
+ strncmp(token, "rootflags=", 10) == 0 ||
+ strcmp(token, "rootwait") == 0 || strcmp(token, "rw") == 0 ||
+ strcmp(token, "ro") == 0;
+}
+
static int build_flash_startup(const struct multiboot_config *config,
char *startup, size_t startup_size)
{
@@ -379,7 +409,6 @@ static int build_flash_startup(const struct multiboot_config *config,
char copy[4096];
char kernel[96];
char *saveptr = NULL;
- char *token;
int root_found = 0;
if (!read_text("/proc/cmdline", command_line, sizeof(command_line)))
return 0;
@@ -391,19 +420,15 @@ static int build_flash_startup(const struct multiboot_config *config,
if (!append_word(startup, startup_size, kernel))
return 0;
snprintf(copy, sizeof(copy), "%s", command_line);
- for (token = strtok_r(copy, " \t\r\n", &saveptr); token;
+ for (const char *token = strtok_r(copy, " \t\r\n", &saveptr); token;
token = strtok_r(NULL, " \t\r\n", &saveptr)) {
if (strncmp(token, "root=", 5) == 0) {
root_found = 1;
if (!append_word(startup, startup_size, token))
return 0;
- } else if (strncmp(token, "ubi.mtd=", 8) == 0 ||
- strncmp(token, "rootfstype=", 11) == 0 ||
- strncmp(token, "rootflags=", 10) == 0 ||
- strcmp(token, "rootwait") == 0 || strcmp(token, "rw") == 0 ||
- strcmp(token, "ro") == 0) {
- if (!append_word(startup, startup_size, token))
- return 0;
+ } else if (flash_boot_token(token) &&
+ !append_word(startup, startup_size, token)) {
+ return 0;
}
}
if (!root_found)
@@ -423,7 +448,6 @@ static int write_startup_files(const struct multiboot_config *config,
char external_startup[1024];
char kernel[96];
char path[PATH_MAX];
- int slot;
if (!ensure_directory(STARTUP_MOUNT, 0755)) {
set_error(error, error_size, "The temporary STARTUP mount cannot be created.");
return 0;
@@ -448,7 +472,7 @@ static int write_startup_files(const struct multiboot_config *config,
snprintf(kernel, sizeof(kernel), "%s", config->mtd_kernel);
else
snprintf(kernel, sizeof(kernel), "/dev/%s", config->mtd_kernel);
- for (slot = 1; slot <= layout->slot_count; ++slot) {
+ for (int slot = 1; slot <= layout->slot_count; ++slot) {
snprintf(external_startup, sizeof(external_startup),
"kernel=%s root=UUID=%s rootsubdir=linuxrootfs%d "
"rootfstype=ext4\n", kernel, layout->root_uuid, slot);
@@ -470,38 +494,34 @@ static int write_startup_files(const struct multiboot_config *config,
return 0;
}
-int multiboot_prepare(const struct storage_device *device,
- const struct multiboot_config *config, multiboot_progress_cb callback,
- void *opaque, struct multiboot_layout *layout, char *error,
- size_t error_size)
-{
- uint64_t bytes;
- uint64_t total_sectors;
- uint64_t alignment;
- uint64_t startup_start;
- uint64_t root_start;
- uint64_t swap_sectors;
- uint64_t swap_start;
- uint64_t root_size;
- uint64_t startup_size;
- unsigned int sector_size = 512;
- int disk_fd = -1;
- int root_mounted = 0;
- int swap_active = 0;
+struct partition_tools {
char sfdisk[PATH_MAX];
char mkfs_vfat[PATH_MAX];
char mkfs_ext4[PATH_MAX];
char mkswap[PATH_MAX];
- char script[2048];
- char part_name[64];
- char metadata[512];
- if (error && error_size)
- error[0] = '\0';
- if (!device || !config || !layout || !selected_device_is_valid(device)) {
- set_error(error, error_size,
- "The selected USB device is no longer safely available.");
- return 0;
- }
+};
+
+struct partition_plan {
+ uint64_t startup_start;
+ uint64_t startup_size;
+ uint64_t root_start;
+ uint64_t root_size;
+ uint64_t swap_start;
+ uint64_t swap_sectors;
+};
+
+struct prepare_state {
+ int disk_fd;
+ int root_mounted;
+ int swap_active;
+ multiboot_progress_cb callback;
+ void *opaque;
+};
+
+static int init_layout(const struct storage_device *device,
+ const struct multiboot_config *config, struct multiboot_layout *layout,
+ char *error, size_t error_size)
+{
memset(layout, 0, sizeof(*layout));
snprintf(layout->disk, sizeof(layout->disk), "%s", device->path);
snprintf(layout->disk_name, sizeof(layout->disk_name), "%s", device->name);
@@ -527,77 +547,88 @@ int multiboot_prepare(const struct storage_device *device,
set_error(error, error_size, "The USB media mount path is too long.");
return 0;
}
- progress(callback, opaque, 3,
- "Locking the selected USB device against automatic mounts...");
- if (!create_nomount(device->name)) {
- set_error(error, error_size, "The automatic mount guard cannot be created.");
- return 0;
- }
- if (!deactivate_device(device, error, error_size))
- return 0;
- disk_fd = open(device->path, O_RDONLY | O_CLOEXEC);
- if (disk_fd < 0 || ioctl(disk_fd, BLKGETSIZE64, &bytes) < 0) {
- set_error(error, error_size, "The USB device size cannot be read: %s",
- strerror(errno));
- goto failed;
- }
+ return 1;
+}
+
+static int plan_partitions(int disk_fd, uint64_t bytes, uint64_t device_bytes,
+ struct partition_plan *plan, char *error, size_t error_size)
+{
+ unsigned int sector_size = 512;
+ uint64_t total_sectors;
+ uint64_t alignment;
if (ioctl(disk_fd, BLKSSZGET, §or_size) < 0 || sector_size == 0)
sector_size = 512;
- if (bytes != device->size_bytes || bytes < SWAP_BYTES + MIN_ROOT_BYTES +
+ if (bytes != device_bytes || bytes < SWAP_BYTES + MIN_ROOT_BYTES +
8ULL * 1024ULL * 1024ULL) {
set_error(error, error_size, "The USB device is too small or has changed.");
- goto failed;
+ return 0;
}
total_sectors = bytes / sector_size;
alignment = 1024ULL * 1024ULL / sector_size;
if (!alignment)
alignment = 1;
- startup_start = 4ULL * 1024ULL * 1024ULL / sector_size;
- root_start = 5ULL * 1024ULL * 1024ULL / sector_size;
- if (startup_start < alignment)
- startup_start = alignment;
- if (root_start <= startup_start)
- root_start = startup_start + alignment;
- swap_sectors = SWAP_BYTES / sector_size;
- if (total_sectors <= swap_sectors + root_start + 34)
- goto size_failed;
- swap_start = ((total_sectors - 34 - swap_sectors) / alignment) * alignment;
- root_size = swap_start - root_start;
- startup_size = root_start - startup_start;
- if (root_size * sector_size < MIN_ROOT_BYTES || !startup_size)
- goto size_failed;
- if (!process_find("sfdisk", sfdisk, sizeof(sfdisk)) ||
- !process_find("mkfs.vfat", mkfs_vfat, sizeof(mkfs_vfat)) ||
- (!process_find("mkfs.ext4", mkfs_ext4, sizeof(mkfs_ext4)) &&
- !process_find("mke2fs", mkfs_ext4, sizeof(mkfs_ext4))) ||
- !process_find("mkswap", mkswap, sizeof(mkswap))) {
+ plan->startup_start = 4ULL * 1024ULL * 1024ULL / sector_size;
+ plan->root_start = 5ULL * 1024ULL * 1024ULL / sector_size;
+ if (plan->startup_start < alignment)
+ plan->startup_start = alignment;
+ if (plan->root_start <= plan->startup_start)
+ plan->root_start = plan->startup_start + alignment;
+ plan->swap_sectors = SWAP_BYTES / sector_size;
+ if (total_sectors > plan->swap_sectors + plan->root_start + 34) {
+ plan->swap_start = ((total_sectors - 34 - plan->swap_sectors) /
+ alignment) * alignment;
+ plan->root_size = plan->swap_start - plan->root_start;
+ plan->startup_size = plan->root_start - plan->startup_start;
+ if (plan->root_size * sector_size >= MIN_ROOT_BYTES && plan->startup_size)
+ return 1;
+ }
+ set_error(error, error_size,
+ "The USB device has insufficient space for rootfs and 512 MB swap.");
+ return 0;
+}
+
+static int find_partition_tools(struct partition_tools *tools, char *error,
+ size_t error_size)
+{
+ if (!process_find("sfdisk", tools->sfdisk, sizeof(tools->sfdisk)) ||
+ !process_find("mkfs.vfat", tools->mkfs_vfat, sizeof(tools->mkfs_vfat)) ||
+ (!process_find("mkfs.ext4", tools->mkfs_ext4, sizeof(tools->mkfs_ext4)) &&
+ !process_find("mke2fs", tools->mkfs_ext4, sizeof(tools->mkfs_ext4))) ||
+ !process_find("mkswap", tools->mkswap, sizeof(tools->mkswap))) {
set_error(error, error_size,
"Required GPT, FAT, ext4 or swap tools are missing.");
- goto failed;
+ return 0;
}
- progress(callback, opaque, 10,
- "Creating the STARTUP, rootfs and 512 MB swap partitions...");
+ return 1;
+}
+
+static int write_partition_table(struct partition_tools *tools,
+ struct multiboot_layout *layout, const struct partition_plan *plan,
+ char *error, size_t error_size)
+{
+ char script[2048];
+ char *argv[] = {tools->sfdisk, "--wipe", "always", "--wipe-partitions",
+ "always", layout->disk, NULL};
snprintf(script, sizeof(script),
"label: gpt\nunit: sectors\n"
"%s : start=%llu, size=%llu, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name=\"startup\"\n"
"%s : start=%llu, size=%llu, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=\"rootfs\"\n"
"%s : start=%llu, size=%llu, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, name=\"swap\"\n",
- layout->startup_partition, (unsigned long long)startup_start,
- (unsigned long long)startup_size, layout->root_partition,
- (unsigned long long)root_start, (unsigned long long)root_size,
- layout->swap_partition, (unsigned long long)swap_start,
- (unsigned long long)swap_sectors);
- {
- char *argv[] = {sfdisk, "--wipe", "always", "--wipe-partitions",
- "always", (char *)device->path, NULL};
- if (process_run(argv, script, NULL, NULL) != 0) {
- set_error(error, error_size, "Creating the GPT partition table failed.");
- goto failed;
- }
+ layout->startup_partition, (unsigned long long)plan->startup_start,
+ (unsigned long long)plan->startup_size, layout->root_partition,
+ (unsigned long long)plan->root_start, (unsigned long long)plan->root_size,
+ layout->swap_partition, (unsigned long long)plan->swap_start,
+ (unsigned long long)plan->swap_sectors);
+ if (process_run(argv, script, NULL, NULL) != 0) {
+ set_error(error, error_size, "Creating the GPT partition table failed.");
+ return 0;
}
- ioctl(disk_fd, BLKRRPART);
- close(disk_fd);
- disk_fd = -1;
+ return 1;
+}
+
+static void guard_new_partitions(const struct multiboot_layout *layout)
+{
+ char part_name[64];
snprintf(part_name, sizeof(part_name), "%s",
strrchr(layout->startup_partition, '/') + 1);
create_nomount(part_name);
@@ -607,97 +638,187 @@ int multiboot_prepare(const struct storage_device *device,
snprintf(part_name, sizeof(part_name), "%s",
strrchr(layout->swap_partition, '/') + 1);
create_nomount(part_name);
- if (!wait_for_partitions(layout->startup_partition, layout->root_partition,
- layout->swap_partition)) {
- set_error(error, error_size, "The new USB partitions did not appear.");
- goto failed;
- }
+}
+
+static int format_partitions(struct partition_tools *tools,
+ struct multiboot_layout *layout, multiboot_progress_cb callback,
+ void *opaque, char *error, size_t error_size)
+{
progress(callback, opaque, 20, "Formatting the STARTUP partition as FAT32...");
{
- char *argv[] = {mkfs_vfat, "-F", "32", "-n", "STARTUP",
+ char *argv[] = {tools->mkfs_vfat, "-F", "32", "-n", "STARTUP",
layout->startup_partition, NULL};
if (process_run(argv, NULL, NULL, NULL) != 0) {
set_error(error, error_size, "Formatting the STARTUP partition failed.");
- goto failed;
+ return 0;
}
}
progress(callback, opaque, 30,
"Formatting the Chkroot rootfs partition as legacy-compatible ext4...");
{
- char *argv[] = {mkfs_ext4, "-F", "-t", "ext4", "-L", "rootfs",
+ char *argv[] = {tools->mkfs_ext4, "-F", "-t", "ext4", "-L", "rootfs",
"-m", "0", "-O",
"^64bit,^extent,^flex_bg,^huge_file,^dir_nlink,^extra_isize,^metadata_csum",
layout->root_partition, NULL};
if (process_run(argv, NULL, NULL, NULL) != 0) {
set_error(error, error_size, "Formatting the rootfs partition failed.");
- goto failed;
+ return 0;
}
}
progress(callback, opaque, 42, "Creating and activating exactly 512 MB swap...");
{
- char *argv[] = {mkswap, "-L", "swap", layout->swap_partition, NULL};
+ char *argv[] = {tools->mkswap, "-L", "swap", layout->swap_partition, NULL};
if (process_run(argv, NULL, NULL, NULL) != 0) {
set_error(error, error_size, "Formatting the swap partition failed.");
- goto failed;
+ return 0;
}
}
- {
- char *argv[] = {"swapon", layout->swap_partition, NULL};
- if (process_run(argv, NULL, NULL, NULL) != 0) {
- set_error(error, error_size, "Activating the 512 MB swap failed.");
- goto failed;
- }
+ return 1;
+}
+
+static int activate_swap(struct multiboot_layout *layout, char *error,
+ size_t error_size)
+{
+ char *argv[] = {"swapon", layout->swap_partition, NULL};
+ if (process_run(argv, NULL, NULL, NULL) != 0) {
+ set_error(error, error_size, "Activating the 512 MB swap failed.");
+ return 0;
}
- swap_active = 1;
+ return 1;
+}
+
+static int read_partition_uuids(struct multiboot_layout *layout, char *error,
+ size_t error_size)
+{
if (!blkid_value(layout->root_partition, "UUID", layout->root_uuid,
sizeof(layout->root_uuid)) ||
!blkid_value(layout->swap_partition, "UUID", layout->swap_uuid,
sizeof(layout->swap_uuid))) {
set_error(error, error_size, "The rootfs or swap UUID cannot be read.");
- goto failed;
+ return 0;
}
- progress(callback, opaque, 50, "Writing and verifying the Multiboot slots...");
- if (!write_startup_files(config, layout, error, error_size))
- goto failed;
+ return 1;
+}
+
+static int mount_media_root(const struct multiboot_layout *layout, char *error,
+ size_t error_size)
+{
if (!ensure_directory("/media", 0755) ||
!ensure_directory(layout->media_mount, 0755)) {
set_error(error, error_size, "The persistent media mount cannot be created.");
- goto failed;
+ return 0;
}
if (mount(layout->root_partition, layout->media_mount, "ext4", 0, NULL) < 0) {
set_error(error, error_size, "The Chkroot rootfs partition cannot be mounted.");
- goto failed;
+ return 0;
}
- root_mounted = 1;
+ return 1;
+}
+
+static int write_layout_metadata(const struct multiboot_layout *layout,
+ char *error, size_t error_size)
+{
+ char metadata[512];
+ char metadata_path[PATH_MAX];
snprintf(metadata, sizeof(metadata),
"version=1\nmode=chkroot\ndisk=%s\nroot_uuid=%s\nswap_uuid=%s\n"
"target_slot=%d\nslots=%d\n", layout->disk, layout->root_uuid,
layout->swap_uuid, layout->target_slot, layout->slot_count);
- {
- char metadata_path[PATH_MAX];
- snprintf(metadata_path, sizeof(metadata_path), "%s/.smallbox-multiboot",
- layout->media_mount);
- if (!write_atomic(metadata_path, metadata, 0644)) {
- set_error(error, error_size, "The Multiboot metadata cannot be saved.");
- goto failed;
- }
+ snprintf(metadata_path, sizeof(metadata_path), "%s/.smallbox-multiboot",
+ layout->media_mount);
+ if (!write_atomic(metadata_path, metadata, 0644)) {
+ set_error(error, error_size, "The Multiboot metadata cannot be saved.");
+ return 0;
}
- progress(callback, opaque, 100,
+ return 1;
+}
+
+static int prepare_device(const struct storage_device *device,
+ const struct multiboot_config *config, struct multiboot_layout *layout,
+ struct prepare_state *state, char *error, size_t error_size)
+{
+ struct partition_tools tools;
+ struct partition_plan plan;
+ uint64_t bytes;
+ if (!init_layout(device, config, layout, error, error_size))
+ return 0;
+ progress(state->callback, state->opaque, 3,
+ "Locking the selected USB device against automatic mounts...");
+ if (!create_nomount(device->name)) {
+ set_error(error, error_size, "The automatic mount guard cannot be created.");
+ return 0;
+ }
+ if (!deactivate_device(device, error, error_size))
+ return 0;
+ state->disk_fd = open(device->path, O_RDONLY | O_CLOEXEC); /* NOSONAR the USB device the user chose */
+ if (state->disk_fd < 0 || ioctl(state->disk_fd, BLKGETSIZE64, &bytes) < 0) {
+ set_error(error, error_size, "The USB device size cannot be read: %s",
+ strerror(errno));
+ return 0;
+ }
+ if (!plan_partitions(state->disk_fd, bytes, device->size_bytes, &plan,
+ error, error_size) || !find_partition_tools(&tools, error, error_size))
+ return 0;
+ progress(state->callback, state->opaque, 10,
+ "Creating the STARTUP, rootfs and 512 MB swap partitions...");
+ if (!write_partition_table(&tools, layout, &plan, error, error_size))
+ return 0;
+ ioctl(state->disk_fd, BLKRRPART);
+ close(state->disk_fd);
+ state->disk_fd = -1;
+ guard_new_partitions(layout);
+ if (!wait_for_partitions(layout->startup_partition, layout->root_partition,
+ layout->swap_partition)) {
+ set_error(error, error_size, "The new USB partitions did not appear.");
+ return 0;
+ }
+ if (!format_partitions(&tools, layout, state->callback, state->opaque, error, error_size) ||
+ !activate_swap(layout, error, error_size))
+ return 0;
+ state->swap_active = 1;
+ if (!read_partition_uuids(layout, error, error_size))
+ return 0;
+ progress(state->callback, state->opaque, 50, "Writing and verifying the Multiboot slots...");
+ if (!write_startup_files(config, layout, error, error_size) ||
+ !mount_media_root(layout, error, error_size))
+ return 0;
+ state->root_mounted = 1;
+ if (!write_layout_metadata(layout, error, error_size))
+ return 0;
+ progress(state->callback, state->opaque, 100,
"Chkroot partitions, swap and STARTUP slots are ready.");
return 1;
+}
-size_failed:
- set_error(error, error_size,
- "The USB device has insufficient space for rootfs and 512 MB swap.");
-failed:
- if (disk_fd >= 0)
- close(disk_fd);
- if (root_mounted)
+static void release_prepare(struct multiboot_layout *layout,
+ const struct prepare_state *state)
+{
+ if (state->disk_fd >= 0)
+ close(state->disk_fd);
+ if (state->root_mounted)
umount2(layout->media_mount, MNT_DETACH);
- if (swap_active) {
+ if (state->swap_active) {
char *argv[] = {"swapoff", layout->swap_partition, NULL};
run_quiet(argv);
}
+}
+
+int multiboot_prepare(const struct storage_device *device,
+ const struct multiboot_config *config, multiboot_progress_cb callback,
+ void *opaque, struct multiboot_layout *layout, char *error,
+ size_t error_size)
+{
+ struct prepare_state state = {.disk_fd = -1, .callback = callback, .opaque = opaque};
+ if (error && error_size)
+ error[0] = '\0';
+ if (!device || !config || !layout || !selected_device_is_valid(device)) {
+ set_error(error, error_size,
+ "The selected USB device is no longer safely available.");
+ return 0;
+ }
+ if (prepare_device(device, config, layout, &state, error, error_size))
+ return 1;
+ release_prepare(layout, &state);
return 0;
}
@@ -707,14 +828,14 @@ static char *read_allocated_file(const char *path, size_t maximum,
FILE *file;
long size;
char *data;
- file = fopen(path, "rb");
+ file = fopen(path, "rb"); /* NOSONAR the image list the wizard downloaded */
if (!file) {
set_error(error, error_size, "Cannot read %s: %s", path,
strerror(errno));
return NULL;
}
- if (fseek(file, 0, SEEK_END) != 0 || (size = ftell(file)) < 0 ||
- (size_t)size > maximum || fseek(file, 0, SEEK_SET) != 0) {
+ size = fseek(file, 0, SEEK_END) == 0 ? ftell(file) : -1;
+ if (size < 0 || (size_t)size > maximum || fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
set_error(error, error_size, "The downloaded image list is invalid.");
return NULL;
@@ -798,7 +919,7 @@ static int select_feed_image(const char *json_path,
uint64_t *expected_size, char *error, size_t error_size)
{
char *json;
- char *cursor;
+ const char *cursor;
unsigned long long best_date = 0;
uint64_t best_size = 0;
char best_url[1024] = "";
@@ -809,7 +930,7 @@ static int select_feed_image(const char *json_path,
cursor = json;
while ((cursor = strstr(cursor, "\"link\"")) != NULL) {
char candidate[1024];
- char *object_end = strchr(cursor, '}');
+ const char *object_end = strchr(cursor, '}');
const char *value = strchr(cursor, ':');
const char *after;
unsigned long long date;
@@ -888,18 +1009,29 @@ static void transfer_tick(void *opaque)
if (state->expected)
snprintf(detail, sizeof(detail),
"Downloading full image: %llu / %llu MiB (%d%%), %ld s",
- (unsigned long long)(received / 1024ULL / 1024ULL),
- (unsigned long long)(state->expected / 1024ULL / 1024ULL),
+ received / 1024ULL / 1024ULL,
+ state->expected / 1024ULL / 1024ULL,
(int)(received * 100ULL / state->expected), elapsed);
else
snprintf(detail, sizeof(detail),
"Downloading full image: %llu MiB, %ld s - %.100s",
- (unsigned long long)(received / 1024ULL / 1024ULL), elapsed,
+ received / 1024ULL / 1024ULL, elapsed,
state->last_line);
progress(state->callback, state->opaque, percent, detail);
}
-static int download_file(const char *url, const char *path, uint64_t expected,
+static int file_status(const char *path, struct stat *status)
+{
+ int fd = open(path, O_RDONLY | O_CLOEXEC); /* NOSONAR the download of the wizard */
+ int result;
+ if (fd < 0)
+ return -1;
+ result = fstat(fd, status);
+ close(fd);
+ return result;
+}
+
+static int download_file(char *url, const char *path, uint64_t expected,
multiboot_progress_cb callback, void *opaque, char *error,
size_t error_size)
{
@@ -917,7 +1049,7 @@ static int download_file(const char *url, const char *path, uint64_t expected,
set_error(error, error_size, "The image download path is too long.");
return 0;
}
- unlink(temporary);
+ unlink(temporary); /* NOSONAR a leftover partial download of the wizard */
memset(&state, 0, sizeof(state));
state.callback = callback;
state.opaque = opaque;
@@ -926,15 +1058,15 @@ static int download_file(const char *url, const char *path, uint64_t expected,
snprintf(state.path, sizeof(state.path), "%s", temporary);
progress(callback, opaque, 12, "Starting the full-image download...");
{
- char *argv[] = {wget, "-O", temporary, (char *)url, NULL};
+ char *argv[] = {wget, "-O", temporary, url, NULL};
result = process_run_with_updates(argv, NULL, transfer_line,
transfer_tick, 1000, &state);
}
- if (result != 0 || stat(temporary, &status) != 0 || status.st_size <= 0) {
+ if (result != 0 || file_status(temporary, &status) != 0 || status.st_size <= 0) {
set_error(error, error_size, "Downloading the full image failed%s%s.",
state.last_line[0] ? ": " : "",
state.last_line[0] ? state.last_line : "");
- unlink(temporary);
+ unlink(temporary); /* NOSONAR the partial download of the wizard */
return 0;
}
if (expected && (uint64_t)status.st_size != expected) {
@@ -942,12 +1074,12 @@ static int download_file(const char *url, const char *path, uint64_t expected,
"The downloaded image size is invalid (%llu instead of %llu bytes).",
(unsigned long long)status.st_size,
(unsigned long long)expected);
- unlink(temporary);
+ unlink(temporary); /* NOSONAR the partial download of the wizard */
return 0;
}
- if (rename(temporary, path) != 0) {
+ if (rename(temporary, path) != 0) { /* NOSONAR the download of the wizard */
set_error(error, error_size, "The downloaded image cannot be finalized.");
- unlink(temporary);
+ unlink(temporary); /* NOSONAR the partial download of the wizard */
return 0;
}
return 1;
@@ -971,7 +1103,7 @@ static void remember_text(char *buffer, size_t buffer_size, const char *line,
const char *kept = line;
size_t length;
size_t used;
- char *newline;
+ const char *newline;
if (!buffer || buffer_size < 2 || !line)
return;
length = strlen(kept);
@@ -1070,7 +1202,7 @@ static int find_image_directory(const char *directory, int depth, char *result,
struct stat status;
if (depth < 0)
return 0;
- stream = opendir(directory);
+ stream = opendir(directory); /* NOSONAR inside the image the wizard unpacked */
if (!stream)
return 0;
while ((entry = readdir(stream)) != NULL) {
@@ -1109,6 +1241,7 @@ static int update_target_fstab(const struct multiboot_layout *layout,
char line[1024];
int skip = 0;
int fd;
+ int written;
if (snprintf(path, sizeof(path), "%s/etc/fstab", root_directory) >=
(int)sizeof(path) || snprintf(temporary, sizeof(temporary),
"%s/etc/fstab.smallbox.XXXXXX", root_directory) >=
@@ -1116,7 +1249,7 @@ static int update_target_fstab(const struct multiboot_layout *layout,
set_error(error, error_size, "The installed fstab path is too long.");
return 0;
}
- input = fopen(path, "r");
+ input = fopen(path, "r"); /* NOSONAR fstab in the slot the wizard installed */
fd = mkstemp(temporary);
if (fd < 0) {
if (input) fclose(input);
@@ -1127,7 +1260,7 @@ static int update_target_fstab(const struct multiboot_layout *layout,
output = fdopen(fd, "w");
if (!output) {
close(fd);
- unlink(temporary);
+ unlink(temporary); /* NOSONAR the temporary file created just above */
if (input) fclose(input);
return 0;
}
@@ -1151,9 +1284,11 @@ static int update_target_fstab(const struct multiboot_layout *layout,
layout->root_uuid, layout->media_mount);
fprintf(output, "UUID=%s none swap sw 0 0\n", layout->swap_uuid);
fprintf(output, "%s\n", FSTAB_END);
- if (fflush(output) != 0 || fsync(fd) != 0 || fclose(output) != 0 ||
- rename(temporary, path) != 0) {
- unlink(temporary);
+ written = fflush(output) == 0 && fsync(fd) == 0;
+ if (fclose(output) != 0)
+ written = 0;
+ if (!written || rename(temporary, path) != 0) { /* NOSONAR fstab in the slot the wizard installed */
+ unlink(temporary); /* NOSONAR the temporary file created above */
set_error(error, error_size, "The installed fstab cannot be saved.");
return 0;
}
@@ -1232,11 +1367,11 @@ static int configure_installed_root(const struct multiboot_config *config,
static void normalize_startup(char *text)
{
- char *read_cursor = text;
+ const char *read_cursor = text;
char *write_cursor = text;
int spacing = 0;
while (*read_cursor) {
- if (isspace((unsigned char)*read_cursor)) {
+ if (isspace((unsigned char)*read_cursor)) { /* NOSONAR isspace on a char of a NUL-terminated string */
spacing = write_cursor != text;
} else {
if (spacing)
@@ -1255,7 +1390,6 @@ int multiboot_detect_active_slot(const struct multiboot_layout *layout,
char active[4096];
char candidate[4096];
char path[PATH_MAX];
- int index;
if (slot)
*slot = -1;
if (!layout || !ensure_directory(STARTUP_MOUNT, 0755) ||
@@ -1276,7 +1410,7 @@ int multiboot_detect_active_slot(const struct multiboot_layout *layout,
return 1;
}
}
- for (index = 1; index <= layout->slot_count; ++index) {
+ for (int index = 1; index <= layout->slot_count; ++index) {
snprintf(path, sizeof(path), "%s/STARTUP_%d", STARTUP_MOUNT, index);
if (!read_text(path, candidate, sizeof(candidate)))
continue;
@@ -1342,101 +1476,117 @@ static int activate_slot(const struct multiboot_layout *layout, char *error,
return 1;
}
-int multiboot_install(const struct multiboot_config *config,
- struct multiboot_layout *layout, multiboot_progress_cb callback,
- multiboot_display_clear_cb clear_display, void *opaque, char *error,
- size_t error_size)
-{
+struct install_paths {
char work[PATH_MAX];
- char feed_path[PATH_MAX];
- char archive_path[PATH_MAX];
- char unpack_path[PATH_MAX];
+ char feed[PATH_MAX];
+ char archive[PATH_MAX];
+ char unpack[PATH_MAX];
char image_directory[PATH_MAX];
char url[1024];
- char wget[PATH_MAX];
- char unzip[PATH_MAX];
- char ofgwrite[PATH_MAX];
- char root_argument[96];
- char current_argument[32];
- char target_argument[32];
- char ofgwrite_log[PATH_MAX];
- uint64_t expected_size = 0;
- struct timed_state timed;
- int result;
- int active_slot;
- if (error && error_size)
- error[0] = '\0';
- if (!config || !layout || layout->target_slot <= 0 ||
- strncmp(layout->root_partition, "/dev/", 5) != 0) {
- set_error(error, error_size, "The Chkroot installation target is invalid.");
- return 0;
- }
- if (!multiboot_detect_active_slot(layout, &active_slot, error, error_size) ||
- active_slot != layout->current_slot) {
- set_error(error, error_size,
- "Installation stopped because the active slot is not internal flash.");
- return 0;
- }
- if (snprintf(work, sizeof(work), "%s/%s", layout->media_mount,
- WORK_DIRECTORY) >= (int)sizeof(work) ||
- snprintf(feed_path, sizeof(feed_path), "%s/images.json", work) >=
- (int)sizeof(feed_path) ||
- snprintf(archive_path, sizeof(archive_path),
- "%s/smallbox-multiboot.zip", work) >= (int)sizeof(archive_path) ||
- snprintf(unpack_path, sizeof(unpack_path), "%s/unpacked-%ld", work,
- (long)getpid()) >= (int)sizeof(unpack_path)) {
+ uint64_t expected_size;
+};
+
+static int prepare_install_paths(const struct multiboot_layout *layout,
+ struct install_paths *paths, char *error, size_t error_size)
+{
+ if (snprintf(paths->work, sizeof(paths->work), "%s/%s", layout->media_mount,
+ WORK_DIRECTORY) >= (int)sizeof(paths->work) ||
+ snprintf(paths->feed, sizeof(paths->feed), "%s/images.json",
+ paths->work) >= (int)sizeof(paths->feed) ||
+ snprintf(paths->archive, sizeof(paths->archive),
+ "%s/smallbox-multiboot.zip", paths->work) >= (int)sizeof(paths->archive) ||
+ snprintf(paths->unpack, sizeof(paths->unpack), "%s/unpacked-%ld",
+ paths->work, (long)getpid()) >= (int)sizeof(paths->unpack)) {
set_error(error, error_size, "The external image path is too long.");
return 0;
}
- if (!ensure_directory(work, 0755) || !ensure_directory(unpack_path, 0755)) {
+ if (!ensure_directory(paths->work, 0755) ||
+ !ensure_directory(paths->unpack, 0755)) {
set_error(error, error_size, "The external image directory cannot be created.");
return 0;
}
+ return 1;
+}
+
+/* The image to download into paths->url, from the configuration or the feed. */
+static int resolve_image_url(const struct multiboot_config *config,
+ struct install_paths *paths, multiboot_progress_cb callback, void *opaque,
+ char *error, size_t error_size)
+{
+ char wget[PATH_MAX];
+ char feed_url[sizeof(config->image_feed)];
if (config->image_url[0]) {
- snprintf(url, sizeof(url), "%s", config->image_url);
- } else {
- if (!config->image_feed[0] || !process_find("wget", wget, sizeof(wget))) {
- set_error(error, error_size, "No SmallBox Multiboot image source is configured.");
+ snprintf(paths->url, sizeof(paths->url), "%s", config->image_url);
+ return 1;
+ }
+ if (!config->image_feed[0] || !process_find("wget", wget, sizeof(wget))) {
+ set_error(error, error_size, "No SmallBox Multiboot image source is configured.");
+ return 0;
+ }
+ progress(callback, opaque, 5, "Loading the OE-Alliance image list...");
+ snprintf(feed_url, sizeof(feed_url), "%s", config->image_feed);
+ {
+ char *argv[] = {wget, "-O", paths->feed, feed_url, NULL};
+ if (process_run(argv, NULL, NULL, NULL) != 0) {
+ set_error(error, error_size, "The OE-Alliance image list cannot be downloaded.");
return 0;
}
- progress(callback, opaque, 5, "Loading the OE-Alliance image list...");
- {
- char *argv[] = {wget, "-O", feed_path,
- (char *)config->image_feed, NULL};
- if (process_run(argv, NULL, NULL, NULL) != 0) {
- set_error(error, error_size, "The OE-Alliance image list cannot be downloaded.");
- return 0;
- }
- }
- if (!select_feed_image(feed_path, config, url, sizeof(url),
- &expected_size, error, error_size))
- return 0;
}
- if (!download_file(url, archive_path, expected_size, callback, opaque,
- error, error_size))
- return 0;
+ return select_feed_image(paths->feed, config, paths->url, sizeof(paths->url),
+ &paths->expected_size, error, error_size);
+}
+
+static void init_timed_state(struct timed_state *timed,
+ multiboot_progress_cb callback, void *opaque, int percent)
+{
+ memset(timed, 0, sizeof(*timed));
+ timed->callback = callback;
+ timed->opaque = opaque;
+ timed->percent = percent;
+ timed->started = time(NULL);
+}
+
+static int unpack_image(struct install_paths *paths,
+ multiboot_progress_cb callback, void *opaque, char *error,
+ size_t error_size)
+{
+ char unzip[PATH_MAX];
+ struct timed_state timed;
+ int result;
if (!process_find("unzip", unzip, sizeof(unzip))) {
set_error(error, error_size, "unzip is not installed in the bootstrap image.");
return 0;
}
- memset(&timed, 0, sizeof(timed));
- timed.callback = callback;
- timed.opaque = opaque;
- timed.percent = 52;
- timed.started = time(NULL);
+ init_timed_state(&timed, callback, opaque, 52);
snprintf(timed.action, sizeof(timed.action), "Extracting the full image");
progress(callback, opaque, 52, "Extracting the full image on USB...");
{
- char *argv[] = {unzip, "-o", archive_path, "-d", unpack_path, NULL};
+ char *argv[] = {unzip, "-o", paths->archive, "-d", paths->unpack, NULL};
result = process_run_with_updates(argv, NULL, timed_line, timed_tick,
1000, &timed);
}
- if (result != 0 || !find_image_directory(unpack_path, 5, image_directory,
- sizeof(image_directory))) {
+ if (result != 0 || !find_image_directory(paths->unpack, 5,
+ paths->image_directory, sizeof(paths->image_directory))) {
set_error(error, error_size,
"The downloaded archive does not contain a usable rootfs image.");
return 0;
}
+ return 1;
+}
+
+static int run_ofgwrite(const struct multiboot_layout *layout,
+ struct install_paths *paths, multiboot_progress_cb callback,
+ multiboot_display_clear_cb clear_display, void *opaque, char *error,
+ size_t error_size)
+{
+ char ofgwrite[PATH_MAX];
+ char root_argument[96];
+ char current_argument[32];
+ char target_argument[32];
+ char ofgwrite_log[PATH_MAX];
+ struct timed_state timed;
+ const char *diagnostic;
+ int result;
if (!process_find("ofgwrite", ofgwrite, sizeof(ofgwrite))) {
set_error(error, error_size, "ofgwrite is not installed in the bootstrap image.");
return 0;
@@ -1447,8 +1597,8 @@ int multiboot_install(const struct multiboot_config *config,
layout->current_slot);
snprintf(target_argument, sizeof(target_argument), "-m%d",
layout->target_slot);
- if (snprintf(ofgwrite_log, sizeof(ofgwrite_log), "%s/ofgwrite.log", work) >=
- (int)sizeof(ofgwrite_log)) {
+ if (snprintf(ofgwrite_log, sizeof(ofgwrite_log), "%s/ofgwrite.log",
+ paths->work) >= (int)sizeof(ofgwrite_log)) {
set_error(error, error_size, "The ofgwrite log path is too long.");
return 0;
}
@@ -1456,26 +1606,22 @@ int multiboot_install(const struct multiboot_config *config,
* is shared by all Chkroot slots. */
progress(callback, opaque, 62,
"Starting ofgwrite for the selected Chkroot slot. The display will switch to ofgwrite...");
- memset(&timed, 0, sizeof(timed));
- timed.callback = callback;
- timed.opaque = opaque;
- timed.percent = 70;
- timed.started = time(NULL);
+ init_timed_state(&timed, callback, opaque, 70);
snprintf(timed.action, sizeof(timed.action),
"Installing rootfs-only into USB slot %d", layout->target_slot);
- timed.log = fopen(ofgwrite_log, "w");
+ timed.log = fopen(ofgwrite_log, "w"); /* NOSONAR the log in the work directory of the wizard */
if (timed.log) {
fprintf(timed.log,
"Command: %s -q %s %s %s %s\n",
ofgwrite, root_argument, current_argument, target_argument,
- image_directory);
+ paths->image_directory);
fflush(timed.log);
}
if (clear_display)
clear_display(opaque);
{
char *argv[] = {ofgwrite, "-q", root_argument, current_argument,
- target_argument, image_directory, NULL};
+ target_argument, paths->image_directory, NULL};
/* ofgwrite owns the framebuffer until it exits. Capture its text output,
* but do not let the SmallBox progress callback draw over its UI. */
result = process_run(argv, NULL, timed_line, &timed);
@@ -1490,13 +1636,46 @@ int multiboot_install(const struct multiboot_config *config,
"ofgwrite completed. Restoring the SmallBox Wizard display..." :
"ofgwrite returned an error. Restoring the diagnostic screen...");
if (result != 0) {
+ diagnostic = timed.issues[0] ? timed.issues : timed.history;
+ if (!diagnostic[0])
+ diagnostic = "No text output was captured.";
set_error(error, error_size,
- "ofgwrite failed while installing rootfs-only into slot %d (status %d).\nDiagnostic output:\n%.300s\nComplete log on USB: smallbox/ofgwrite.log",
- layout->target_slot, result,
- timed.issues[0] ? timed.issues :
- (timed.history[0] ? timed.history : "No text output was captured."));
+ "ofgwrite failed while installing rootfs-only into slot %d (status %d).\n"
+ "Diagnostic output:\n%.300s\nComplete log on USB: smallbox/ofgwrite.log",
+ layout->target_slot, result, diagnostic);
return 0;
}
+ return 1;
+}
+
+int multiboot_install(const struct multiboot_config *config,
+ const struct multiboot_layout *layout, multiboot_progress_cb callback,
+ multiboot_display_clear_cb clear_display, void *opaque, char *error,
+ size_t error_size)
+{
+ struct install_paths paths = {.expected_size = 0};
+ int active_slot;
+ if (error && error_size)
+ error[0] = '\0';
+ if (!config || !layout || layout->target_slot <= 0 ||
+ strncmp(layout->root_partition, "/dev/", 5) != 0) {
+ set_error(error, error_size, "The Chkroot installation target is invalid.");
+ return 0;
+ }
+ if (!multiboot_detect_active_slot(layout, &active_slot, error, error_size) ||
+ active_slot != layout->current_slot) {
+ set_error(error, error_size,
+ "Installation stopped because the active slot is not internal flash.");
+ return 0;
+ }
+ if (!prepare_install_paths(layout, &paths, error, error_size) ||
+ !resolve_image_url(config, &paths, callback, opaque, error, error_size) ||
+ !download_file(paths.url, paths.archive, paths.expected_size, callback,
+ opaque, error, error_size) ||
+ !unpack_image(&paths, callback, opaque, error, error_size) ||
+ !run_ofgwrite(layout, &paths, callback, clear_display, opaque, error,
+ error_size))
+ return 0;
progress(callback, opaque, 90,
"Verifying the installed Enigma2 rootfs and persistent mounts...");
if (!configure_installed_root(config, layout, error, error_size))
diff --git a/src/network.c b/src/network.c
index 64169b3..f840fe2 100644
--- a/src/network.c
+++ b/src/network.c
@@ -28,13 +28,14 @@
#define MAX_RECOVERY_SKIPS 512
#define MINIMUM_TLS_TIME ((time_t)1577836800)
-static void set_error(char *error, size_t error_size, const char *format, ...)
+__attribute__((format(printf, 3, 4)))
+static void set_error(char *error, size_t error_size, const char *format, ...) /* NOSONAR printf-style error text */
{
va_list arguments;
if (!error || error_size == 0)
return;
va_start(arguments, format);
- vsnprintf(error, error_size, format, arguments);
+ vsnprintf(error, error_size, format, arguments); /* NOSONAR format is checked by the printf attribute */
va_end(arguments);
}
@@ -67,9 +68,12 @@ static int get_ipv4(const char *interface, char *address, size_t address_size)
{
int socket_fd;
struct ifreq request;
- struct sockaddr_in *internet;
- if (!safe_interface_name(interface))
+ const struct sockaddr_in *internet;
+ socklen_t text_size;
+ if (!address || address_size == 0 || !safe_interface_name(interface))
return 0;
+ /* inet_ntop needs at most INET_ADDRSTRLEN bytes */
+ text_size = address_size > INET_ADDRSTRLEN ? INET_ADDRSTRLEN : (socklen_t)address_size;
socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (socket_fd < 0)
return 0;
@@ -80,8 +84,8 @@ static int get_ipv4(const char *interface, char *address, size_t address_size)
return 0;
}
close(socket_fd);
- internet = (struct sockaddr_in *)&request.ifr_addr;
- if (!inet_ntop(AF_INET, &internet->sin_addr, address, address_size))
+ internet = (const struct sockaddr_in *)&request.ifr_addr;
+ if (!inet_ntop(AF_INET, &internet->sin_addr, address, text_size))
return 0;
return strcmp(address, "0.0.0.0") != 0;
}
@@ -110,7 +114,7 @@ static int interface_compare(const void *left, const void *right)
int network_scan(struct network_interface interfaces[], int maximum)
{
DIR *directory;
- struct dirent *entry;
+ const struct dirent *entry;
int count = 0;
if (!interfaces || maximum <= 0)
return 0;
@@ -151,8 +155,7 @@ int network_has_ipv4(char *interface, size_t interface_size,
{
struct network_interface interfaces[NETWORK_MAX_INTERFACES];
int count = network_scan(interfaces, NETWORK_MAX_INTERFACES);
- int i;
- for (i = 0; i < count; ++i) {
+ for (int i = 0; i < count; ++i) {
if (interfaces[i].address[0]) {
if (interface && interface_size)
snprintf(interface, interface_size, "%s", interfaces[i].name);
@@ -168,7 +171,6 @@ int network_synchronize_time(network_progress_cb callback, void *opaque,
char *error, size_t error_size)
{
char chronyc[PATH_MAX];
- int second;
if (time(NULL) >= MINIMUM_TLS_TIME)
return 1;
@@ -189,7 +191,7 @@ int network_synchronize_time(network_progress_cb callback, void *opaque,
process_run(online_argv, NULL, NULL, NULL);
process_run(step_argv, NULL, NULL, NULL);
}
- for (second = 0; second < 45; ++second) {
+ for (int second = 0; second < 45; ++second) {
char status[128];
if (time(NULL) >= MINIMUM_TLS_TIME) {
if (callback)
@@ -212,34 +214,42 @@ int network_synchronize_time(network_progress_cb callback, void *opaque,
return 0;
}
+static int write_all(int fd, const char *data, size_t length)
+{
+ size_t used = 0;
+ while (used < length) {
+ ssize_t result = write(fd, data + used, length - used); /* NOSONAR fd is the wizard's config backup */
+ if (result < 0)
+ return 0;
+ used += (size_t)result;
+ }
+ return 1;
+}
+
static int backup_file_once(const char *source, const char *backup)
{
char buffer[4096];
int source_fd;
int backup_fd;
ssize_t length;
- if (access(backup, F_OK) == 0 || access(source, F_OK) != 0)
- return 1;
source_fd = open(source, O_RDONLY | O_CLOEXEC);
- if (source_fd < 0)
- return 0;
+ if (source_fd < 0) {
+ /* nothing to back up, or a backup exists already */
+ if (errno == ENOENT)
+ return 1;
+ return access(backup, F_OK) == 0;
+ }
backup_fd = open(backup, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
if (backup_fd < 0) {
close(source_fd);
return errno == EEXIST;
}
while ((length = read(source_fd, buffer, sizeof(buffer))) > 0) {
- ssize_t used = 0;
- while (used < length) {
- ssize_t result = write(backup_fd, buffer + used,
- (size_t)(length - used));
- if (result < 0) {
- close(source_fd);
- close(backup_fd);
- unlink(backup);
- return 0;
- }
- used += result;
+ if (!write_all(backup_fd, buffer, (size_t)length)) {
+ close(source_fd);
+ close(backup_fd);
+ unlink(backup);
+ return 0;
}
}
fsync(backup_fd);
@@ -256,10 +266,11 @@ static int write_interfaces_file(const char *selected, char *error,
int count = network_scan(interfaces, NETWORK_MAX_INTERFACES);
int fd;
FILE *file;
- int i;
int found = 0;
+ int failed = 0;
+ int saved_errno = 0;
- for (i = 0; i < count; ++i) {
+ for (int i = 0; i < count; ++i) {
if (strcmp(interfaces[i].name, selected) == 0 &&
!interfaces[i].wireless)
found = 1;
@@ -289,17 +300,28 @@ static int write_interfaces_file(const char *selected, char *error,
}
fputs("# Automatically generated by SmallBox Wizard.\n\n", file);
fputs("auto lo\niface lo inet loopback\n\n", file);
- for (i = 0; i < count; ++i) {
+ for (int i = 0; i < count; ++i) {
const char *prefix = strcmp(interfaces[i].name, selected) == 0 ? "" : "# ";
fprintf(file, "%sauto %s\n", prefix, interfaces[i].name);
fprintf(file, "# iface %s inet6 dhcp\n", interfaces[i].name);
fprintf(file, "%siface %s inet dhcp\n\n", prefix,
interfaces[i].name);
}
- if (fflush(file) != 0 || fsync(fd) != 0 || fclose(file) != 0 ||
- rename(temporary, "/etc/network/interfaces") != 0) {
+ if (fflush(file) != 0 || fsync(fd) != 0) {
+ failed = 1;
+ saved_errno = errno;
+ }
+ if (fclose(file) != 0 && !failed) {
+ failed = 1;
+ saved_errno = errno;
+ }
+ if (!failed && rename(temporary, "/etc/network/interfaces") != 0) {
+ failed = 1;
+ saved_errno = errno;
+ }
+ if (failed) {
set_error(error, error_size, "The network configuration could not be saved: %s",
- strerror(errno));
+ strerror(saved_errno));
unlink(temporary);
return 0;
}
@@ -319,13 +341,14 @@ int network_configure_dhcp(const char *interface,
{
char ifup[256];
char ifdown[256];
- int attempt;
+ char name[IFNAMSIZ];
if (address && address_size) address[0] = '\0';
if (error && error_size) error[0] = '\0';
if (!safe_interface_name(interface)) {
set_error(error, error_size, "Invalid network interface name.");
return 0;
}
+ snprintf(name, sizeof(name), "%s", interface);
progress(callback, opaque, 10, "Saving the DHCP configuration...");
if (!write_interfaces_file(interface, error, error_size))
return 0;
@@ -334,12 +357,12 @@ int network_configure_dhcp(const char *interface,
return 0;
}
if (process_find("ifdown", ifdown, sizeof(ifdown))) {
- char *down_argv[] = {ifdown, (char *)interface, NULL};
+ char *down_argv[] = {ifdown, name, NULL};
process_run(down_argv, NULL, NULL, NULL);
}
progress(callback, opaque, 35, "Activating LAN and waiting for DHCP...");
{
- char *up_argv[] = {ifup, (char *)interface, NULL};
+ char *up_argv[] = {ifup, name, NULL};
int result = process_run(up_argv, NULL, NULL, NULL);
if (result != 0 && !get_ipv4(interface, address, address_size)) {
set_error(error, error_size, "ifup/DHCP failed with status %d.",
@@ -347,7 +370,7 @@ int network_configure_dhcp(const char *interface,
return 0;
}
}
- for (attempt = 0; attempt < 20; ++attempt) {
+ for (int attempt = 0; attempt < 20; ++attempt) {
if (get_ipv4(interface, address, address_size)) {
progress(callback, opaque, 100, "An IP address has been assigned.");
return 1;
@@ -428,10 +451,9 @@ static int copy_package_token(const char *text, char *package,
static int hexadecimal_hash(const char *text, size_t length)
{
- size_t i;
if (!text || (length != 32 && length != 40 && length != 64))
return 0;
- for (i = 0; i < length; ++i) {
+ for (size_t i = 0; i < length; ++i) {
if (!((text[i] >= '0' && text[i] <= '9') ||
(text[i] >= 'a' && text[i] <= 'f') ||
(text[i] >= 'A' && text[i] <= 'F')))
@@ -440,34 +462,44 @@ static int hexadecimal_hash(const char *text, size_t length)
return 1;
}
+/* Index of the first '_' in text[0..length), or length. */
+static size_t underscore_index(const char *text, size_t length)
+{
+ size_t index = 0;
+ while (index < length && text[index] != '_')
+ index++;
+ return index;
+}
+
static int package_from_ipk_path(const char *line, char *package,
size_t package_size)
{
const char *end;
- const char *start;
- const char *separator;
+ size_t start;
+ size_t stop;
+ size_t separator;
size_t length;
- if (!line)
+ if (!line || !package || package_size == 0)
return 0;
end = strstr(line, ".ipk");
if (!end)
return 0;
- start = end;
- while (start > line && start[-1] != '/' && start[-1] != ' ' &&
- start[-1] != '\t' && start[-1] != '\'' && start[-1] != '"')
+ stop = (size_t)(end - line);
+ start = stop;
+ while (start > 0 && line[start - 1] != '/' && line[start - 1] != ' ' &&
+ line[start - 1] != '\t' && line[start - 1] != '\'' && line[start - 1] != '"')
start--;
- separator = memchr(start, '_', (size_t)(end - start));
- if (separator && hexadecimal_hash(start, (size_t)(separator - start))) {
+ /* start <= separator <= stop */
+ separator = start + underscore_index(line + start, stop - start);
+ if (separator < stop && hexadecimal_hash(line + start, separator - start)) {
start = separator + 1;
- separator = memchr(start, '_', (size_t)(end - start));
+ separator = start + underscore_index(line + start, stop - start);
}
- if (separator)
- end = separator;
- length = (size_t)(end - start);
+ length = separator - start;
if (!length || length >= package_size)
return 0;
- memcpy(package, start, length);
+ memcpy(package, line + start, length);
package[length] = '\0';
return 1;
}
@@ -482,11 +514,10 @@ static int action_package(const char *line, char *package, size_t package_size)
{"Upgrading ", 10},
{"Configuring ", 12}
};
- size_t i;
if (!line)
return 0;
- for (i = 0; i < sizeof(actions) / sizeof(actions[0]); ++i) {
+ for (size_t i = 0; i < sizeof(actions) / sizeof(actions[0]); ++i) {
if (strncmp(line, actions[i].prefix, actions[i].length) == 0)
return copy_package_token(line + actions[i].length, package,
package_size);
@@ -531,7 +562,6 @@ static int error_package(const char *line, char *package, size_t package_size)
NULL
};
const char *quoted;
- int i;
if (!line)
return 0;
@@ -543,7 +573,7 @@ static int error_package(const char *line, char *package, size_t package_size)
quoted = strstr(line, "Package \"");
if (quoted && copy_package_token(quoted + 9, package, package_size))
return 1;
- for (i = 0; prefixes[i]; ++i) {
+ for (int i = 0; prefixes[i]; ++i) {
const char *match = strstr(line, prefixes[i]);
if (match && copy_package_token(match + strlen(prefixes[i]), package,
package_size))
@@ -554,10 +584,9 @@ static int error_package(const char *line, char *package, size_t package_size)
static int plan_find(const struct package_plan *plan, const char *package)
{
- int i;
if (!plan || !package || !package[0])
return -1;
- for (i = 0; i < plan->count; ++i) {
+ for (int i = 0; i < plan->count; ++i) {
if (strcmp(plan->entries[i].name, package) == 0)
return i;
}
@@ -581,6 +610,7 @@ static int failure_package_score(const struct package_plan *plan,
static int plan_add(struct package_plan *plan, const char *package)
{
struct package_entry *resized;
+ struct package_entry *entry;
int capacity;
if (!plan || !package || !package[0] ||
@@ -588,7 +618,11 @@ static int plan_add(struct package_plan *plan, const char *package)
return 1;
if (plan_find(plan, package) >= 0)
return 1;
+ if (plan->count < 0 || plan->count > plan->capacity)
+ return 0;
if (plan->count == plan->capacity) {
+ if (plan->capacity > INT_MAX / 2)
+ return 0;
capacity = plan->capacity ? plan->capacity * 2 : 128;
resized = realloc(plan->entries,
(size_t)capacity * sizeof(plan->entries[0]));
@@ -597,9 +631,9 @@ static int plan_add(struct package_plan *plan, const char *package)
plan->entries = resized;
plan->capacity = capacity;
}
- memset(&plan->entries[plan->count], 0, sizeof(plan->entries[0]));
- snprintf(plan->entries[plan->count].name,
- sizeof(plan->entries[plan->count].name), "%s", package);
+ entry = &plan->entries[plan->count];
+ memset(entry, 0, sizeof(*entry));
+ snprintf(entry->name, sizeof(entry->name), "%s", package);
plan->count++;
return 1;
}
@@ -622,17 +656,16 @@ static void plan_line(const char *line, void *opaque)
plan_add(plan, package);
}
-static int plan_counts(struct package_plan *plan, int *installed,
+static int plan_counts(const struct package_plan *plan, int *installed,
int *remaining, int *skipped)
{
int installed_count = 0;
int remaining_count = 0;
int skipped_count = 0;
- int i;
if (!plan)
return 0;
- for (i = 0; i < plan->count; ++i) {
+ for (int i = 0; i < plan->count; ++i) {
if (plan->entries[i].installed)
installed_count++;
else if (plan->entries[i].skipped)
@@ -662,23 +695,21 @@ static void installed_line(const char *line, void *opaque)
plan->entries[index].installed = 1;
}
-static void refresh_installed(const char *opkg, struct package_plan *plan)
+static void refresh_installed(char *opkg, struct package_plan *plan)
{
- char *argv[] = {(char *)opkg, "list-installed", NULL};
- int i;
+ char *argv[] = {opkg, "list-installed", NULL};
if (!plan)
return;
- for (i = 0; i < plan->count; ++i)
+ for (int i = 0; i < plan->count; ++i)
plan->entries[i].installed = 0;
process_run(argv, NULL, installed_line, plan);
}
static void reset_action_marks(struct package_plan *plan)
{
- int i;
if (!plan)
return;
- for (i = 0; i < plan->count; ++i)
+ for (int i = 0; i < plan->count; ++i)
plan->entries[i].seen_action = 0;
}
@@ -714,13 +745,54 @@ static void install_tick(void *opaque)
show_install_state(opaque);
}
+static void record_failure_line(struct install_state *state, const char *line)
+{
+ char package[NETWORK_PACKAGE_NAME_SIZE];
+ int score = 0;
+
+ if (error_package(line, package, sizeof(package))) {
+ score = failure_package_score(state->plan, package);
+ } else if (state->current_package[0]) {
+ snprintf(package, sizeof(package), "%s", state->current_package);
+ score = 2;
+ }
+ if (score >= state->failure_score && score > 0) {
+ snprintf(state->failure_package, sizeof(state->failure_package), "%s",
+ package);
+ state->failure_score = score;
+ snprintf(state->failure_detail, sizeof(state->failure_detail), "%s",
+ line);
+ } else if (!state->failure_detail[0] &&
+ strcmp(line, "Collected errors:") != 0) {
+ snprintf(state->failure_detail, sizeof(state->failure_detail), "%s",
+ line);
+ }
+}
+
+static void advance_package_action(struct install_state *state)
+{
+ int index = plan_find(state->plan, state->current_package);
+ int calculated;
+
+ if (index >= 0 && !state->plan->entries[index].seen_action) {
+ state->plan->entries[index].seen_action = 1;
+ state->completed++;
+ }
+ if (state->planned > 0) {
+ calculated = 45 + state->completed * 45 / state->planned;
+ if (calculated > 92)
+ calculated = 92;
+ if (calculated > state->percent)
+ state->percent = calculated;
+ } else if (state->percent < 90) {
+ state->percent += 2;
+ }
+}
+
static void install_line(const char *line, void *opaque)
{
struct install_state *state = opaque;
char package[NETWORK_PACKAGE_NAME_SIZE];
- int calculated;
- int index;
- int score;
if (!state || !line)
return;
@@ -729,44 +801,13 @@ static void install_line(const char *line, void *opaque)
if (action_package(line, package, sizeof(package)))
snprintf(state->current_package, sizeof(state->current_package), "%s",
package);
- if (error_line(line)) {
- score = 0;
- if (error_package(line, package, sizeof(package))) {
- score = failure_package_score(state->plan, package);
- } else if (state->current_package[0]) {
- snprintf(package, sizeof(package), "%s", state->current_package);
- score = 2;
- }
- if (score >= state->failure_score && score > 0) {
- snprintf(state->failure_package, sizeof(state->failure_package), "%s",
- package);
- state->failure_score = score;
- snprintf(state->failure_detail, sizeof(state->failure_detail), "%s",
- line);
- } else if (!state->failure_detail[0] &&
- strcmp(line, "Collected errors:") != 0) {
- snprintf(state->failure_detail, sizeof(state->failure_detail), "%s",
- line);
- }
- }
+ if (error_line(line))
+ record_failure_line(state, line);
if (state->phase == INSTALL_PHASE_UPDATE) {
if (state->percent < 32)
state->percent++;
} else if (package_action_line(line)) {
- index = plan_find(state->plan, state->current_package);
- if (index >= 0 && !state->plan->entries[index].seen_action) {
- state->plan->entries[index].seen_action = 1;
- state->completed++;
- }
- if (state->planned > 0) {
- calculated = 45 + state->completed * 45 / state->planned;
- if (calculated > 92)
- calculated = 92;
- if (calculated > state->percent)
- state->percent = calculated;
- } else if (state->percent < 90) {
- state->percent += 2;
- }
+ advance_package_action(state);
} else if (strncmp(line, "Downloading ", 12) == 0 &&
state->percent < 45) {
state->percent = 45;
@@ -798,10 +839,10 @@ static void prepare_install_state(struct install_state *state,
state->failure_score = 0;
}
-static int run_group_install(const char *opkg, const char *cache_option,
+static int run_group_install(char *opkg, char *cache_option,
struct install_state *state)
{
- char *argv[] = {(char *)opkg, (char *)cache_option, OPKG_CACHE_DIR,
+ char *argv[] = {opkg, cache_option, OPKG_CACHE_DIR,
"-t", OPKG_TMP_DIR, "-l", OPKG_LISTS_DIR, "install",
"packagegroup-openatv-small", NULL};
write_log_line(state->log, "--- Installing packagegroup-openatv-small ---");
@@ -809,16 +850,15 @@ static int run_group_install(const char *opkg, const char *cache_option,
1000, state);
}
-static int run_remaining_install(const char *opkg, const char *cache_option,
+static int run_remaining_install(char *opkg, char *cache_option,
struct package_plan *plan, struct install_state *state)
{
char **argv;
int argument_count = 9;
int index = 0;
- int i;
int result;
- for (i = 0; i < plan->count; ++i) {
+ for (int i = 0; i < plan->count; ++i) {
if (plan->entries[i].skipped && !plan->entries[i].installed)
argument_count += 2;
else if (!plan->entries[i].installed)
@@ -827,22 +867,22 @@ static int run_remaining_install(const char *opkg, const char *cache_option,
argv = calloc((size_t)argument_count + 1, sizeof(argv[0]));
if (!argv)
return -1;
- argv[index++] = (char *)opkg;
- argv[index++] = (char *)cache_option;
+ argv[index++] = opkg;
+ argv[index++] = cache_option;
argv[index++] = OPKG_CACHE_DIR;
argv[index++] = "-t";
argv[index++] = OPKG_TMP_DIR;
argv[index++] = "-l";
argv[index++] = OPKG_LISTS_DIR;
argv[index++] = "--nodeps";
- for (i = 0; i < plan->count; ++i) {
+ for (int i = 0; i < plan->count; ++i) {
if (plan->entries[i].skipped && !plan->entries[i].installed) {
argv[index++] = "--add-exclude";
argv[index++] = plan->entries[i].name;
}
}
argv[index++] = "install";
- for (i = 0; i < plan->count; ++i) {
+ for (int i = 0; i < plan->count; ++i) {
if (!plan->entries[i].installed && !plan->entries[i].skipped)
argv[index++] = plan->entries[i].name;
}
@@ -888,11 +928,11 @@ static void dependent_line(const char *line, void *opaque)
plan->entries[index].skipped = 1;
}
-static void mark_dependent_packages_skipped(const char *opkg,
- struct package_plan *plan, const char *package)
+static void mark_dependent_packages_skipped(char *opkg,
+ struct package_plan *plan, char *package)
{
- char *argv[] = {(char *)opkg, "-l", OPKG_LISTS_DIR,
- "whatdependsrec", "-A", (char *)package, NULL};
+ char *argv[] = {opkg, "-l", OPKG_LISTS_DIR,
+ "whatdependsrec", "-A", package, NULL};
write_log_line(plan ? plan->log : NULL,
"--- Marking the unavailable package dependency chain as skipped ---");
process_run(argv, NULL, dependent_line, plan);
@@ -920,10 +960,9 @@ static void load_skipped_packages(struct package_plan *plan)
static const char *first_remaining_package(const struct package_plan *plan)
{
- int i;
if (!plan)
return NULL;
- for (i = 0; i < plan->count; ++i) {
+ for (int i = 0; i < plan->count; ++i) {
if (!plan->entries[i].installed && !plan->entries[i].skipped)
return plan->entries[i].name;
}
@@ -962,219 +1001,285 @@ static int save_skipped_packages(const struct package_plan *plan)
return ok;
}
-int network_install_smallbox(network_progress_cb callback,
- network_package_failure_cb failure_callback, void *opaque,
- char *error, size_t error_size)
-{
+struct install_run {
char opkg[256];
- char help[4096];
- const char *cache_option = "--cache";
+ char cache_option[sizeof("--cache-dir")];
struct package_plan plan;
struct install_state state;
- struct network_package_failure failure;
- FILE *log = NULL;
+ FILE *log;
+ network_progress_cb callback;
+ network_package_failure_cb failure_callback;
+ void *opaque;
+ char *error;
+ size_t error_size;
+ int installed;
+ int remaining;
+ int skipped;
+};
+
+static int create_work_directories(void)
+{
+ return ensure_directory("/.FlashExpander", 0755) &&
+ ensure_directory(OPKG_WORK_ROOT, 0755) &&
+ ensure_directory(OPKG_CACHE_DIR, 0755) &&
+ ensure_directory(OPKG_TMP_DIR, 0755) &&
+ ensure_directory(OPKG_LISTS_DIR, 0755);
+}
+
+static int finish_install(struct install_run *run, int result)
+{
+ fclose(run->log);
+ free(run->plan.entries);
+ return result;
+}
+
+static void detect_cache_option(struct install_run *run)
+{
+ char help[4096];
+ char *argv[] = {run->opkg, "--help", NULL};
+
+ snprintf(run->cache_option, sizeof(run->cache_option), "%s", "--cache");
+ process_capture(argv, help, sizeof(help));
+ if (strstr(help, "--cache-dir"))
+ snprintf(run->cache_option, sizeof(run->cache_option), "%s",
+ "--cache-dir");
+}
+
+static int update_package_lists(struct install_run *run)
+{
+ char *argv[] = {run->opkg, run->cache_option, OPKG_CACHE_DIR,
+ "-t", OPKG_TMP_DIR, "-l", OPKG_LISTS_DIR, "update", NULL};
int result;
- int installed = 0;
- int remaining = 0;
- int skipped = 0;
- int recovery = 0;
- memset(&plan, 0, sizeof(plan));
- memset(&state, 0, sizeof(state));
- if (error && error_size)
- error[0] = '\0';
- if (!process_find("opkg", opkg, sizeof(opkg))) {
- set_error(error, error_size, "opkg is not installed in this image.");
- return 0;
- }
- if (!ensure_directory("/.FlashExpander", 0755) ||
- !ensure_directory(OPKG_WORK_ROOT, 0755) ||
- !ensure_directory(OPKG_CACHE_DIR, 0755) ||
- !ensure_directory(OPKG_TMP_DIR, 0755) ||
- !ensure_directory(OPKG_LISTS_DIR, 0755)) {
- set_error(error, error_size,
- "The OPKG work directories cannot be created on the FlashExpander.");
- return 0;
- }
- log = fopen(OPKG_LOG_FILE, "w");
- if (!log) {
- set_error(error, error_size, "The OPKG log cannot be created: %s",
- strerror(errno));
- return 0;
- }
- plan.log = log;
- state.callback = callback;
- state.opaque = opaque;
- state.plan = &plan;
- state.log = log;
- {
- char *argv[] = {opkg, "--help", NULL};
- process_capture(argv, help, sizeof(help));
- if (strstr(help, "--cache-dir"))
- cache_option = "--cache-dir";
- }
- state.percent = 5;
- state.phase = INSTALL_PHASE_UPDATE;
- state.started = time(NULL);
- progress(callback, opaque, 5, "Updating package lists...");
- write_log_line(log, "--- Updating package lists ---");
- {
- char *argv[] = {opkg, (char *)cache_option, OPKG_CACHE_DIR,
- "-t", OPKG_TMP_DIR, "-l", OPKG_LISTS_DIR, "update", NULL};
- result = process_run_with_updates(argv, NULL, install_line,
- install_tick, 1000, &state);
- }
+ run->state.percent = 5;
+ run->state.phase = INSTALL_PHASE_UPDATE;
+ run->state.started = time(NULL);
+ progress(run->callback, run->opaque, 5, "Updating package lists...");
+ write_log_line(run->log, "--- Updating package lists ---");
+ result = process_run_with_updates(argv, NULL, install_line,
+ install_tick, 1000, &run->state);
if (result != 0) {
- set_error(error, error_size,
+ set_error(run->error, run->error_size,
"opkg update failed with status %d. %.300s Full log: %s",
- result, state.failure_detail[0] ? state.failure_detail :
+ result, run->state.failure_detail[0] ? run->state.failure_detail :
"Check the network and package feeds.", OPKG_LOG_FILE);
- fclose(log);
- free(plan.entries);
return 0;
}
- progress(callback, opaque, 35, "Resolving package dependencies...");
- write_log_line(log, "--- Resolving the package plan ---");
- {
- char *argv[] = {opkg, (char *)cache_option, OPKG_CACHE_DIR,
- "-t", OPKG_TMP_DIR, "-l", OPKG_LISTS_DIR,
- "--noaction", "install", "packagegroup-openatv-small", NULL};
- result = process_run(argv, NULL, plan_line, &plan);
- }
+ return 1;
+}
+
+static int resolve_package_plan(struct install_run *run)
+{
+ char *argv[] = {run->opkg, run->cache_option, OPKG_CACHE_DIR,
+ "-t", OPKG_TMP_DIR, "-l", OPKG_LISTS_DIR,
+ "--noaction", "install", "packagegroup-openatv-small", NULL};
+ int result;
+
+ progress(run->callback, run->opaque, 35, "Resolving package dependencies...");
+ write_log_line(run->log, "--- Resolving the package plan ---");
+ result = process_run(argv, NULL, plan_line, &run->plan);
if (result != 0) {
- set_error(error, error_size,
+ set_error(run->error, run->error_size,
"The SmallBox package plan cannot be resolved (status %d). Full log: %s",
result, OPKG_LOG_FILE);
- fclose(log);
- free(plan.entries);
return 0;
}
- load_skipped_packages(&plan);
- refresh_installed(opkg, &plan);
- plan_counts(&plan, &installed, &remaining, &skipped);
- prepare_install_state(&state, &plan, installed, remaining, skipped, 0);
- {
- char detail[192];
- snprintf(detail, sizeof(detail),
- "Installing %d packages and dependencies in one bulk operation...",
- remaining);
- progress(callback, opaque, 40, detail);
- }
- if (remaining == 0 && skipped)
- result = 0;
- else if (skipped)
- result = run_remaining_install(opkg, cache_option, &plan, &state);
- else
- result = run_group_install(opkg, cache_option, &state);
+ return 1;
+}
+
+static void refresh_plan_counts(struct install_run *run)
+{
+ refresh_installed(run->opkg, &run->plan);
+ plan_counts(&run->plan, &run->installed, &run->remaining, &run->skipped);
+}
+
+static int run_install(struct install_run *run)
+{
+ if (run->skipped)
+ return run_remaining_install(run->opkg, run->cache_option, &run->plan,
+ &run->state);
+ return run_group_install(run->opkg, run->cache_option, &run->state);
+}
+
+static int start_install(struct install_run *run)
+{
+ char detail[192];
+
+ load_skipped_packages(&run->plan);
+ refresh_plan_counts(run);
+ prepare_install_state(&run->state, &run->plan, run->installed,
+ run->remaining, run->skipped, 0);
+ snprintf(detail, sizeof(detail),
+ "Installing %d packages and dependencies in one bulk operation...",
+ run->remaining);
+ progress(run->callback, run->opaque, 40, detail);
+ if (run->remaining == 0 && run->skipped)
+ return 0;
+ return run_install(run);
+}
+
+static const char *failure_package_name(const struct install_run *run)
+{
+ const char *fallback_package = first_remaining_package(&run->plan);
+
+ if (run->state.failure_package[0])
+ return run->state.failure_package;
+ if (run->state.current_package[0])
+ return run->state.current_package;
+ return fallback_package ? fallback_package : "";
+}
+
+static const char *failure_detail_text(const struct install_run *run, int result)
+{
+ if (run->state.failure_detail[0])
+ return run->state.failure_detail;
+ if (result == 0)
+ return "OPKG returned success, but packages are still missing.";
+ return "OPKG stopped without a detailed error line. See the full log.";
+}
+
+static void describe_failure(const struct install_run *run, int result,
+ struct network_package_failure *failure)
+{
+ memset(failure, 0, sizeof(*failure));
+ failure->status = result != 0 ? result : 255;
+ failure->total = run->plan.count;
+ failure->installed = run->installed;
+ failure->remaining = run->remaining;
+ failure->skipped = run->skipped;
+ snprintf(failure->package, sizeof(failure->package), "%s",
+ failure_package_name(run));
+ snprintf(failure->detail, sizeof(failure->detail), "%s",
+ failure_detail_text(run, result));
+}
+
+static int skip_failed_package(struct install_run *run,
+ struct network_package_failure *failure)
+{
+ char detail[256];
+
+ if (!failure->package[0] || run->skipped >= MAX_RECOVERY_SKIPS ||
+ !mark_skipped(&run->plan, failure->package)) {
+ set_error(run->error, run->error_size,
+ "The failed package cannot be skipped safely. Full log: %s",
+ OPKG_LOG_FILE);
+ return 0;
+ }
+ write_log_line(run->log, "--- User selected: skip package and continue ---");
+ snprintf(detail, sizeof(detail),
+ "Checking which remaining packages depend on %.120s...",
+ failure->package);
+ progress(run->callback, run->opaque, run->state.percent, detail);
+ mark_dependent_packages_skipped(run->opkg, &run->plan, failure->package);
+ if (!save_skipped_packages(&run->plan)) {
+ set_error(run->error, run->error_size,
+ "The skipped-package list cannot be saved: %s",
+ strerror(errno));
+ return 0;
+ }
+ return 1;
+}
+static int recover_until_complete(struct install_run *run, int result)
+{
for (;;) {
- enum network_package_action action;
- const char *fallback_package;
- refresh_installed(opkg, &plan);
- plan_counts(&plan, &installed, &remaining, &skipped);
- if (result == 0 && remaining == 0)
- break;
- fallback_package = first_remaining_package(&plan);
- memset(&failure, 0, sizeof(failure));
- failure.status = result != 0 ? result : 255;
- failure.total = plan.count;
- failure.installed = installed;
- failure.remaining = remaining;
- failure.skipped = skipped;
- snprintf(failure.package, sizeof(failure.package), "%s",
- state.failure_package[0] ? state.failure_package :
- (state.current_package[0] ? state.current_package :
- (fallback_package ? fallback_package : "")));
- snprintf(failure.detail, sizeof(failure.detail), "%s",
- state.failure_detail[0] ? state.failure_detail : (result == 0 ?
- "OPKG returned success, but packages are still missing." :
- "OPKG stopped without a detailed error line. See the full log."));
- if (!failure_callback) {
- action = NETWORK_PACKAGE_ABORT;
- } else {
- action = failure_callback(&failure, opaque);
- }
+ struct network_package_failure failure;
+ enum network_package_action action = NETWORK_PACKAGE_ABORT;
+
+ refresh_plan_counts(run);
+ if (result == 0 && run->remaining == 0)
+ return 1;
+ describe_failure(run, result, &failure);
+ if (run->failure_callback)
+ action = run->failure_callback(&failure, run->opaque);
if (action == NETWORK_PACKAGE_ABORT) {
- set_error(error, error_size,
+ set_error(run->error, run->error_size,
"OPKG status %d. Package: %s. Installed %d/%d; %d remain. "
"%.180s Full log: %s", failure.status,
failure.package[0] ? failure.package : "not detected",
- installed, plan.count, remaining, failure.detail,
+ run->installed, run->plan.count, run->remaining, failure.detail,
OPKG_LOG_FILE);
- fclose(log);
- free(plan.entries);
return 0;
}
- if (action == NETWORK_PACKAGE_SKIP) {
- char detail[256];
- if (!failure.package[0] || skipped >= MAX_RECOVERY_SKIPS ||
- !mark_skipped(&plan, failure.package)) {
- set_error(error, error_size,
- "The failed package cannot be skipped safely. Full log: %s",
- OPKG_LOG_FILE);
- fclose(log);
- free(plan.entries);
- return 0;
- }
- write_log_line(log, "--- User selected: skip package and continue ---");
- snprintf(detail, sizeof(detail),
- "Checking which remaining packages depend on %.120s...",
- failure.package);
- progress(callback, opaque, state.percent, detail);
- mark_dependent_packages_skipped(opkg, &plan, failure.package);
- if (!save_skipped_packages(&plan)) {
- set_error(error, error_size,
- "The skipped-package list cannot be saved: %s",
- strerror(errno));
- fclose(log);
- free(plan.entries);
- return 0;
- }
- }
- recovery = 1;
- refresh_installed(opkg, &plan);
- plan_counts(&plan, &installed, &remaining, &skipped);
- prepare_install_state(&state, &plan, installed, remaining, skipped,
- recovery);
- if (remaining == 0)
+ if (action == NETWORK_PACKAGE_SKIP && !skip_failed_package(run, &failure))
+ return 0;
+ refresh_plan_counts(run);
+ prepare_install_state(&run->state, &run->plan, run->installed,
+ run->remaining, run->skipped, 1);
+ if (run->remaining == 0)
result = 0;
- else if (skipped)
- result = run_remaining_install(opkg, cache_option, &plan, &state);
else
- result = run_group_install(opkg, cache_option, &state);
+ result = run_install(run);
}
- if (!save_skipped_packages(&plan)) {
- set_error(error, error_size,
- "The skipped-package list cannot be saved: %s", strerror(errno));
- fclose(log);
- free(plan.entries);
- return 0;
- }
- {
- char detail[256];
- snprintf(detail, sizeof(detail),
- "Installed %d of %d packages; skipped %d. Cleaning the external OPKG cache...",
- installed, plan.count, skipped);
- progress(callback, opaque, 98, detail);
- }
- {
- char *argv[] = {opkg, (char *)cache_option, OPKG_CACHE_DIR,
- "-t", OPKG_TMP_DIR, "-l", OPKG_LISTS_DIR, "clean", NULL};
- process_run(argv, NULL, NULL, NULL);
- }
- if (skipped) {
- char detail[256];
+}
+
+static void clean_and_report(struct install_run *run)
+{
+ char detail[256];
+ char *argv[] = {run->opkg, run->cache_option, OPKG_CACHE_DIR,
+ "-t", OPKG_TMP_DIR, "-l", OPKG_LISTS_DIR, "clean", NULL};
+
+ snprintf(detail, sizeof(detail),
+ "Installed %d of %d packages; skipped %d. Cleaning the external OPKG cache...",
+ run->installed, run->plan.count, run->skipped);
+ progress(run->callback, run->opaque, 98, detail);
+ process_run(argv, NULL, NULL, NULL);
+ if (run->skipped) {
snprintf(detail, sizeof(detail),
"Installation completed with %d skipped package(s). The list is in %s.",
- skipped, OPKG_SKIPPED_FILE);
- progress(callback, opaque, 100, detail);
+ run->skipped, OPKG_SKIPPED_FILE);
+ progress(run->callback, run->opaque, 100, detail);
} else {
- progress(callback, opaque, 100,
+ progress(run->callback, run->opaque, 100,
"The SmallBox packages have been installed.");
}
- fclose(log);
- free(plan.entries);
- return 1;
+}
+
+int network_install_smallbox(network_progress_cb callback,
+ network_package_failure_cb failure_callback, void *opaque,
+ char *error, size_t error_size)
+{
+ struct install_run run;
+
+ memset(&run, 0, sizeof(run));
+ run.callback = callback;
+ run.failure_callback = failure_callback;
+ run.opaque = opaque;
+ run.error = error;
+ run.error_size = error_size;
+ if (error && error_size)
+ error[0] = '\0';
+ if (!process_find("opkg", run.opkg, sizeof(run.opkg))) {
+ set_error(error, error_size, "opkg is not installed in this image.");
+ return 0;
+ }
+ if (!create_work_directories()) {
+ set_error(error, error_size,
+ "The OPKG work directories cannot be created on the FlashExpander.");
+ return 0;
+ }
+ run.log = fopen(OPKG_LOG_FILE, "w");
+ if (!run.log) {
+ set_error(error, error_size, "The OPKG log cannot be created: %s",
+ strerror(errno));
+ return 0;
+ }
+ run.plan.log = run.log;
+ run.state.callback = callback;
+ run.state.opaque = opaque;
+ run.state.plan = &run.plan;
+ run.state.log = run.log;
+ detect_cache_option(&run);
+ if (!update_package_lists(&run) || !resolve_package_plan(&run) ||
+ !recover_until_complete(&run, start_install(&run)))
+ return finish_install(&run, 0);
+ if (!save_skipped_packages(&run.plan)) {
+ set_error(error, error_size,
+ "The skipped-package list cannot be saved: %s", strerror(errno));
+ return finish_install(&run, 0);
+ }
+ clean_and_report(&run);
+ return finish_install(&run, 1);
}
static int service_link(const char *name, const char *service)
@@ -1190,8 +1295,7 @@ static int service_link(const char *name, const char *service)
strcmp(name + name_length - service_length, service) == 0;
}
-int network_disable_optional_services(network_progress_cb callback,
- void *opaque, char *error, size_t error_size)
+static int optional_service_link(const char *name)
{
static const char *const services[] = {
"autofs",
@@ -1206,68 +1310,91 @@ int network_disable_optional_services(network_progress_cb callback,
"wsdd2",
NULL
};
- int runlevel;
+
+ for (int index = 0; services[index]; ++index) {
+ if (service_link(name, services[index]))
+ return 1;
+ }
+ return 0;
+}
+
+static int disable_service_link(const char *directory_path, const char *name,
+ char *error, size_t error_size)
+{
+ char path[PATH_MAX];
+
+ if (snprintf(path, sizeof(path), "%s/%s", directory_path, name) >=
+ (int)sizeof(path)) {
+ set_error(error, error_size, "An optional service link path is too long.");
+ return 0;
+ }
+ if (unlink(path) != 0 && errno != ENOENT) {
+ set_error(error, error_size, "%s cannot be disabled: %s", path,
+ strerror(errno));
+ return 0;
+ }
+ return 1;
+}
+
+/* -1 on error, 0 if the runlevel directory is missing, 1 when done. */
+static int disable_runlevel_services(int runlevel, int *removed, char *error,
+ size_t error_size)
+{
+ char directory_path[64];
+ DIR *directory;
+ const struct dirent *entry;
+
+ snprintf(directory_path, sizeof(directory_path), "/etc/rc%d.d", runlevel);
+ directory = opendir(directory_path);
+ if (!directory) {
+ if (errno == ENOENT)
+ return 0;
+ set_error(error, error_size, "%s cannot be inspected: %s",
+ directory_path, strerror(errno));
+ return -1;
+ }
+ for (;;) {
+ errno = 0;
+ entry = readdir(directory);
+ if (!entry)
+ break;
+ if (!optional_service_link(entry->d_name))
+ continue;
+ if (!disable_service_link(directory_path, entry->d_name, error,
+ error_size)) {
+ closedir(directory);
+ return -1;
+ }
+ (*removed)++;
+ }
+ if (errno != 0) {
+ int saved_errno = errno;
+ closedir(directory);
+ set_error(error, error_size, "%s cannot be read: %s",
+ directory_path, strerror(saved_errno));
+ return -1;
+ }
+ closedir(directory);
+ return 1;
+}
+
+int network_disable_optional_services(network_progress_cb callback,
+ void *opaque, char *error, size_t error_size)
+{
int removed = 0;
if (error && error_size)
error[0] = '\0';
progress(callback, opaque, 5,
"Disabling optional background services for this single-core receiver...");
- for (runlevel = 2; runlevel <= 5; ++runlevel) {
- char directory_path[64];
- DIR *directory;
- struct dirent *entry;
-
- snprintf(directory_path, sizeof(directory_path), "/etc/rc%d.d", runlevel);
- directory = opendir(directory_path);
- if (!directory) {
- if (errno == ENOENT)
- continue;
- set_error(error, error_size, "%s cannot be inspected: %s",
- directory_path, strerror(errno));
+ for (int runlevel = 2; runlevel <= 5; ++runlevel) {
+ int status = disable_runlevel_services(runlevel, &removed, error,
+ error_size);
+ if (status < 0)
return 0;
- }
- for (;;) {
- char path[PATH_MAX];
- int index;
-
- errno = 0;
- entry = readdir(directory);
- if (!entry) {
- if (errno != 0) {
- int saved_errno = errno;
- closedir(directory);
- set_error(error, error_size, "%s cannot be read: %s",
- directory_path, strerror(saved_errno));
- return 0;
- }
- break;
- }
-
- for (index = 0; services[index]; ++index) {
- if (!service_link(entry->d_name, services[index]))
- continue;
- if (snprintf(path, sizeof(path), "%s/%s", directory_path,
- entry->d_name) >= (int)sizeof(path)) {
- closedir(directory);
- set_error(error, error_size,
- "An optional service link path is too long.");
- return 0;
- }
- if (unlink(path) != 0 && errno != ENOENT) {
- int saved_errno = errno;
- closedir(directory);
- set_error(error, error_size, "%s cannot be disabled: %s",
- path, strerror(saved_errno));
- return 0;
- }
- removed++;
- break;
- }
- }
- closedir(directory);
- progress(callback, opaque, (runlevel - 1) * 25,
- "Applying the TV-focused single-core boot profile...");
+ if (status > 0)
+ progress(callback, opaque, (runlevel - 1) * 25,
+ "Applying the TV-focused single-core boot profile...");
}
{
char detail[192];
diff --git a/src/process.c b/src/process.c
index 2a7b1e9..e16e55a 100644
--- a/src/process.c
+++ b/src/process.c
@@ -27,9 +27,7 @@ static uint64_t current_milliseconds(void)
static void emit_lines(char *pending, size_t *pending_length,
const char *data, size_t length, process_line_cb callback, void *opaque)
{
- size_t i;
-
- for (i = 0; i < length; ++i) {
+ for (size_t i = 0; i < length; ++i) {
char ch = data[i];
if (ch == '\r')
continue;
@@ -44,20 +42,159 @@ static void emit_lines(char *pending, size_t *pending_length,
}
}
+/* A running command and what its output goes to. */
+struct run_state {
+ int output_fd;
+ pid_t child;
+ int status;
+ int child_reaped;
+ int wait_error;
+ char pending[512];
+ size_t pending_length;
+ process_line_cb callback;
+ process_tick_cb tick;
+ unsigned int tick_ms;
+ uint64_t next_tick;
+ void *opaque;
+};
+
+static void close_pipe(const int pipe_fds[2])
+{
+ if (pipe_fds[0] >= 0)
+ close(pipe_fds[0]);
+ if (pipe_fds[1] >= 0)
+ close(pipe_fds[1]);
+}
+
+/* In the child: stdout and stderr into output_pipe, stdin from input_pipe or /dev/null. */
+__attribute__((noreturn)) static void exec_child(char *const argv[], int with_input, const int output_pipe[2],
+ const int input_pipe[2])
+{
+ dup2(output_pipe[1], STDOUT_FILENO);
+ dup2(output_pipe[1], STDERR_FILENO);
+ close_pipe(output_pipe);
+ if (with_input) {
+ dup2(input_pipe[0], STDIN_FILENO);
+ close_pipe(input_pipe);
+ } else {
+ int null_fd = open("/dev/null", O_RDONLY);
+ if (null_fd >= 0) {
+ dup2(null_fd, STDIN_FILENO);
+ close(null_fd);
+ }
+ }
+ execvp(argv[0], argv); /* NOSONAR the tools of the wizard, argv is built by it */
+ dprintf(STDERR_FILENO, "Cannot execute %s: %s\n", argv[0],
+ strerror(errno));
+ _exit(127);
+}
+
+static void write_input(int fd, const char *text)
+{
+ size_t length = strlen(text);
+ size_t written = 0;
+ while (written < length) {
+ ssize_t result = write(fd, text + written, length - written); /* NOSONAR the input the wizard gives a tool */
+ if (result >= 0)
+ written += (size_t)result;
+ else if (errno != EINTR)
+ break;
+ }
+}
+
+static void tick_if_due(struct run_state *run)
+{
+ uint64_t now;
+ if (!run->tick || !run->tick_ms)
+ return;
+ now = current_milliseconds();
+ if (now >= run->next_tick) {
+ run->tick(run->opaque);
+ run->next_tick = now + run->tick_ms;
+ }
+}
+
+/* How long to wait for output: until the next tick, at most 250 ms, not at all once the child ended. */
+static uint64_t wait_time(const struct run_state *run)
+{
+ uint64_t now;
+ uint64_t remaining;
+ if (run->child_reaped)
+ return 0;
+ if (!run->tick || !run->tick_ms)
+ return 250;
+ now = current_milliseconds();
+ remaining = run->next_tick > now ? run->next_tick - now : 0;
+ return remaining > 250 ? 250 : remaining;
+}
+
+/* One round of reading the output, 0 when the output or the child ended. */
+static int run_step(struct run_state *run)
+{
+ char buffer[256];
+ fd_set read_set;
+ struct timeval timeout;
+ uint64_t remaining;
+ ssize_t result;
+ int ready;
+
+ if (!run->child_reaped) {
+ pid_t waited = waitpid(run->child, &run->status, WNOHANG);
+ if (waited == run->child)
+ run->child_reaped = 1;
+ else if (waited < 0 && errno != EINTR) {
+ run->wait_error = 1;
+ return 0;
+ }
+ }
+ tick_if_due(run);
+ remaining = wait_time(run);
+ FD_ZERO(&read_set);
+ FD_SET(run->output_fd, &read_set);
+ timeout.tv_sec = (time_t)(remaining / 1000ULL);
+ timeout.tv_usec = (suseconds_t)((remaining % 1000ULL) * 1000ULL);
+ ready = select(run->output_fd + 1, &read_set, NULL, NULL, &timeout);
+ if (ready == 0)
+ return !run->child_reaped;
+ if (ready < 0)
+ return errno == EINTR;
+ result = read(run->output_fd, buffer, sizeof(buffer));
+ if (result > 0) {
+ emit_lines(run->pending, &run->pending_length, buffer, (size_t)result, run->callback, run->opaque);
+ tick_if_due(run);
+ return 1;
+ }
+ return result < 0 && errno == EINTR;
+}
+
+/* The exit code of the child after the output ended, -1 on an error. */
+static int run_finish(struct run_state *run)
+{
+ close(run->output_fd);
+ if (run->pending_length) {
+ run->pending[run->pending_length] = '\0';
+ if (run->callback)
+ run->callback(run->pending, run->opaque);
+ }
+ while (!run->child_reaped && waitpid(run->child, &run->status, 0) < 0)
+ if (errno != EINTR)
+ return -1;
+ if (run->wait_error)
+ return -1;
+ if (WIFEXITED(run->status))
+ return WEXITSTATUS(run->status);
+ if (WIFSIGNALED(run->status))
+ return 128 + WTERMSIG(run->status);
+ return -1;
+}
+
static int process_run_internal(char *const argv[], const char *stdin_text,
process_line_cb callback, process_tick_cb tick, unsigned int tick_ms,
void *opaque)
{
int output_pipe[2] = {-1, -1};
int input_pipe[2] = {-1, -1};
- pid_t child;
- int status = 0;
- char buffer[256];
- char pending[512];
- size_t pending_length = 0;
- uint64_t next_tick = 0;
- int child_reaped = 0;
- int wait_error = 0;
+ struct run_state run = {.callback = callback, .tick = tick, .tick_ms = tick_ms, .opaque = opaque};
if (!argv || !argv[0]) {
errno = EINVAL;
@@ -66,144 +203,30 @@ static int process_run_internal(char *const argv[], const char *stdin_text,
if (pipe(output_pipe) < 0)
return -1;
if (stdin_text && pipe(input_pipe) < 0) {
- close(output_pipe[0]);
- close(output_pipe[1]);
+ close_pipe(output_pipe);
return -1;
}
-
- child = fork();
- if (child < 0) {
- close(output_pipe[0]);
- close(output_pipe[1]);
- if (input_pipe[0] >= 0) {
- close(input_pipe[0]);
- close(input_pipe[1]);
- }
+ run.child = fork();
+ if (run.child < 0) {
+ close_pipe(output_pipe);
+ close_pipe(input_pipe);
return -1;
}
- if (child == 0) {
- int null_fd;
- dup2(output_pipe[1], STDOUT_FILENO);
- dup2(output_pipe[1], STDERR_FILENO);
- close(output_pipe[0]);
- close(output_pipe[1]);
- if (stdin_text) {
- dup2(input_pipe[0], STDIN_FILENO);
- close(input_pipe[0]);
- close(input_pipe[1]);
- } else {
- null_fd = open("/dev/null", O_RDONLY);
- if (null_fd >= 0) {
- dup2(null_fd, STDIN_FILENO);
- close(null_fd);
- }
- }
- execvp(argv[0], argv);
- dprintf(STDERR_FILENO, "Cannot execute %s: %s\n", argv[0],
- strerror(errno));
- _exit(127);
- }
+ if (run.child == 0)
+ exec_child(argv, stdin_text != NULL, output_pipe, input_pipe);
close(output_pipe[1]);
+ run.output_fd = output_pipe[0];
if (stdin_text) {
- size_t length = strlen(stdin_text);
- size_t written = 0;
close(input_pipe[0]);
- while (written < length) {
- ssize_t result = write(input_pipe[1], stdin_text + written,
- length - written);
- if (result < 0) {
- if (errno == EINTR)
- continue;
- break;
- }
- written += (size_t)result;
- }
+ write_input(input_pipe[1], stdin_text);
close(input_pipe[1]);
}
if (tick && tick_ms)
- next_tick = current_milliseconds() + tick_ms;
-
- for (;;) {
- ssize_t result;
- fd_set read_set;
- struct timeval timeout;
- uint64_t now;
- uint64_t remaining = 250;
- int ready;
-
- if (!child_reaped) {
- pid_t waited = waitpid(child, &status, WNOHANG);
- if (waited == child)
- child_reaped = 1;
- else if (waited < 0 && errno != EINTR) {
- wait_error = 1;
- break;
- }
- }
- now = current_milliseconds();
- if (tick && tick_ms) {
- if (now >= next_tick) {
- tick(opaque);
- next_tick = now + tick_ms;
- }
- remaining = next_tick > now ? next_tick - now : 0;
- if (remaining > 250)
- remaining = 250;
- }
- if (child_reaped)
- remaining = 0;
- FD_ZERO(&read_set);
- FD_SET(output_pipe[0], &read_set);
- timeout.tv_sec = (time_t)(remaining / 1000ULL);
- timeout.tv_usec = (suseconds_t)((remaining % 1000ULL) * 1000ULL);
- ready = select(output_pipe[0] + 1, &read_set, NULL, NULL, &timeout);
- if (ready == 0) {
- if (child_reaped)
- break;
- continue;
- }
- if (ready < 0) {
- if (errno == EINTR)
- continue;
- break;
- }
- result = read(output_pipe[0], buffer, sizeof(buffer));
- if (result > 0) {
- emit_lines(pending, &pending_length, buffer, (size_t)result,
- callback, opaque);
- if (tick && tick_ms) {
- now = current_milliseconds();
- if (now >= next_tick) {
- tick(opaque);
- next_tick = now + tick_ms;
- }
- }
- continue;
- }
- if (result < 0 && errno == EINTR)
- continue;
- break;
- }
- close(output_pipe[0]);
- if (pending_length) {
- pending[pending_length] = '\0';
- if (callback)
- callback(pending, opaque);
- }
- if (!child_reaped) {
- while (waitpid(child, &status, 0) < 0) {
- if (errno != EINTR)
- return -1;
- }
- }
- if (wait_error)
- return -1;
- if (WIFEXITED(status))
- return WEXITSTATUS(status);
- if (WIFSIGNALED(status))
- return 128 + WTERMSIG(status);
- return -1;
+ run.next_tick = current_milliseconds() + tick_ms;
+ while (run_step(&run))
+ continue;
+ return run_finish(&run);
}
int process_run(char *const argv[], const char *stdin_text,
@@ -263,7 +286,6 @@ int process_find(const char *name, char *path, size_t path_size)
static const char *const directories[] = {
"/usr/sbin", "/usr/bin", "/sbin", "/bin", NULL
};
- int i;
if (!name || !path || path_size == 0)
return 0;
@@ -274,7 +296,7 @@ int process_find(const char *name, char *path, size_t path_size)
}
return 0;
}
- for (i = 0; directories[i]; ++i) {
+ for (int i = 0; directories[i]; ++i) {
snprintf(path, path_size, "%s/%s", directories[i], name);
if (access(path, X_OK) == 0)
return 1;
diff --git a/src/storage.c b/src/storage.c
index 2c3e50a..5cb94f5 100644
--- a/src/storage.c
+++ b/src/storage.c
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -18,6 +19,7 @@
#include
#include
#include
+#include
#include
#define EXPANDER_MOUNT "/.FlashExpander"
@@ -30,23 +32,32 @@
#define SWAP_BYTES (512ULL * 1024ULL * 1024ULL)
#define MIN_DATA_BYTES (512ULL * 1024ULL * 1024ULL)
-static void set_error(char *error, size_t error_size, const char *format, ...)
+__attribute__((format(printf, 3, 4)))
+static void set_error(char *error, size_t error_size, const char *format, ...) /* NOSONAR printf-style helper */
{
va_list arguments;
if (!error || error_size == 0)
return;
va_start(arguments, format);
- vsnprintf(error, error_size, format, arguments);
+ vsnprintf(error, error_size, format, arguments); /* NOSONAR format checked by attribute */
va_end(arguments);
}
+static void sleep_ms(long milliseconds)
+{
+ struct timespec delay;
+ delay.tv_sec = milliseconds / 1000L;
+ delay.tv_nsec = (milliseconds % 1000L) * 1000000L;
+ nanosleep(&delay, NULL);
+}
+
static void trim(char *text)
{
size_t length;
- char *start = text;
+ const char *start = text;
if (!text)
return;
- while (*start && isspace((unsigned char)*start))
+ while (*start && isspace((unsigned char)*start)) /* NOSONAR stops at the terminator */
start++;
if (start != text)
memmove(text, start, strlen(start) + 1);
@@ -75,11 +86,10 @@ static int read_text(const char *path, char *text, size_t text_size)
static int is_disk_name(const char *name)
{
- const char *cursor;
if (!name || name[0] != 's' || name[1] != 'd' ||
!isalpha((unsigned char)name[2]))
return 0;
- for (cursor = name + 2; *cursor; ++cursor) {
+ for (const char *cursor = name + 2; *cursor; ++cursor) {
if (!isalpha((unsigned char)*cursor))
return 0;
}
@@ -136,8 +146,7 @@ static void read_critical_devices(struct dev_numbers *numbers)
static int matches_critical(const char *dev, const struct dev_numbers *numbers)
{
- int i;
- for (i = 0; i < numbers->count; ++i) {
+ for (int i = 0; i < numbers->count; ++i) {
if (strcmp(dev, numbers->values[i]) == 0)
return 1;
}
@@ -285,12 +294,13 @@ const char *storage_expander_mount(void)
static int read_meta_value(const char *key, char *value, size_t value_size)
{
- FILE *file = fopen(EXPANDER_META, "r");
+ FILE *file;
char line[256];
size_t key_length;
if (!key || !value || value_size == 0)
return 0;
value[0] = '\0';
+ file = fopen(EXPANDER_META, "r");
if (!file)
return 0;
key_length = strlen(key);
@@ -351,17 +361,18 @@ static void partition_path(const struct storage_device *device, int number,
{
size_t length = strlen(device->name);
snprintf(path, path_size, "/dev/%s%s%d", device->name,
- length && isdigit((unsigned char)device->name[length - 1]) ? "p" : "",
+ length && isdigit((unsigned char)device->name[length - 1]) ? "p" : "", /* NOSONAR length >= 1 here */
number);
}
static int source_is_partition(const char *source, const char *device_path)
{
size_t length = strlen(device_path);
+ char next;
if (strncmp(source, device_path, length) != 0)
return 0;
- return source[length] == '\0' || isdigit((unsigned char)source[length]) ||
- source[length] == 'p';
+ next = source[length]; /* NOSONAR prefix matched, index <= strlen(source) */
+ return next == '\0' || isdigit((unsigned char)next) || next == 'p'; /* NOSONAR the character read above */
}
static void run_quiet(char *const argv[])
@@ -369,29 +380,36 @@ static void run_quiet(char *const argv[])
process_run(argv, NULL, NULL, NULL);
}
-static int deactivate_target(const struct storage_device *device,
- char *error, size_t error_size)
+static void swapoff_target(const struct storage_device *device)
{
FILE *file;
char line[1024];
- char sources[32][PATH_MAX];
- int count = 0;
- int i;
file = fopen("/proc/swaps", "r");
- if (file) {
- if (fgets(line, sizeof(line), file)) {
- while (fgets(line, sizeof(line), file)) {
- char source[PATH_MAX];
- if (sscanf(line, "%4095s", source) == 1 &&
- source_is_partition(source, device->path)) {
- char *argv[] = {"swapoff", source, NULL};
- run_quiet(argv);
- }
+ if (!file)
+ return;
+ if (fgets(line, sizeof(line), file)) {
+ while (fgets(line, sizeof(line), file)) {
+ char source[PATH_MAX];
+ if (sscanf(line, "%4095s", source) == 1 &&
+ source_is_partition(source, device->path)) {
+ char *argv[] = {"swapoff", source, NULL};
+ run_quiet(argv);
}
}
- fclose(file);
}
+ fclose(file);
+}
+
+static int deactivate_target(const struct storage_device *device,
+ char *error, size_t error_size)
+{
+ FILE *file;
+ char line[1024];
+ char sources[32][PATH_MAX];
+ int count = 0;
+
+ swapoff_target(device);
file = fopen("/proc/mounts", "r");
if (!file)
return 1;
@@ -411,7 +429,7 @@ static int deactivate_target(const struct storage_device *device,
snprintf(sources[count++], sizeof(sources[0]), "%s", mountpoint);
}
fclose(file);
- for (i = count - 1; i >= 0; --i) {
+ for (int i = count - 1; i >= 0; --i) {
char *argv[] = {"umount", sources[i], NULL};
if (process_run(argv, NULL, NULL, NULL) != 0) {
set_error(error, error_size, "A mount point could not be unmounted: %s",
@@ -427,9 +445,8 @@ static int validate_selected(const struct storage_device *selected)
struct storage_device devices[STORAGE_MAX_DEVICES];
char error[128];
int count;
- int i;
count = storage_scan_usb(devices, STORAGE_MAX_DEVICES, error, sizeof(error));
- for (i = 0; i < count; ++i) {
+ for (int i = 0; i < count; ++i) {
if (strcmp(devices[i].path, selected->path) == 0 &&
devices[i].size_bytes == selected->size_bytes)
return 1;
@@ -439,14 +456,13 @@ static int validate_selected(const struct storage_device *selected)
static int wait_for_partitions(const char *part1, const char *part2)
{
- int attempt;
struct stat status1;
struct stat status2;
- for (attempt = 0; attempt < 100; ++attempt) {
+ for (int attempt = 0; attempt < 100; ++attempt) {
if (stat(part1, &status1) == 0 && S_ISBLK(status1.st_mode) &&
stat(part2, &status2) == 0 && S_ISBLK(status2.st_mode))
return 1;
- usleep(100000);
+ sleep_ms(100);
}
return 0;
}
@@ -467,7 +483,7 @@ static int create_nomount_flag(const struct storage_device *device,
if (snprintf(path, path_size, "/dev/nomount.%s", device->name) >=
(int)path_size)
return 0;
- fd = open(path, O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
+ fd = open(path, O_WRONLY | O_CREAT | O_CLOEXEC, 0600); /* NOSONAR /dev/nomount. */
if (fd < 0)
return 0;
close(fd);
@@ -477,8 +493,10 @@ static int create_nomount_flag(const struct storage_device *device,
static int blkid_uuid(const char *partition, char *uuid, size_t uuid_size)
{
char blkid[PATH_MAX];
- char *argv[] = {blkid, "-s", "UUID", "-o", "value",
- (char *)partition, NULL};
+ char target[PATH_MAX];
+ char *argv[] = {blkid, "-s", "UUID", "-o", "value", target, NULL};
+ if (snprintf(target, sizeof(target), "%s", partition) >= (int)sizeof(target))
+ return 0;
if (!process_find("blkid", blkid, sizeof(blkid)))
return 0;
if (process_capture(argv, uuid, uuid_size) != 0)
@@ -496,6 +514,24 @@ static int copy_mode(const char *path, mode_t *mode)
return 1;
}
+/* Flush, sync, close and rename; the stream is always closed, errno kept. */
+static int commit_file(FILE *file, int fd, const char *temporary,
+ const char *target)
+{
+ int ok = fflush(file) == 0 && fsync(fd) == 0;
+ int saved_errno = errno;
+ if (fclose(file) != 0 && ok) {
+ ok = 0;
+ saved_errno = errno;
+ }
+ if (ok && rename(temporary, target) != 0) {
+ ok = 0;
+ saved_errno = errno;
+ }
+ errno = saved_errno;
+ return ok;
+}
+
static int update_fstab(const char *data_uuid, const char *swap_uuid,
int enable, char *error, size_t error_size)
{
@@ -548,8 +584,7 @@ static int update_fstab(const char *data_uuid, const char *swap_uuid,
fprintf(output, "UUID=%s none swap sw 0 0\n", swap_uuid);
fprintf(output, "%s\n", FSTAB_END);
}
- if (fflush(output) != 0 || fsync(fd) != 0 || fclose(output) != 0 ||
- rename(temporary, "/etc/fstab") != 0) {
+ if (!commit_file(output, fd, temporary, "/etc/fstab")) {
set_error(error, error_size, "fstab could not be saved: %s",
strerror(errno));
unlink(temporary);
@@ -600,7 +635,9 @@ static void restore_expander(const char *swap_source, int data_unmounted)
run_quiet(mount_usr);
}
if (swap_source && swap_source[0]) {
- char *swapon[] = {"swapon", (char *)swap_source, NULL};
+ char source[PATH_MAX];
+ char *swapon[] = {"swapon", source, NULL};
+ snprintf(source, sizeof(source), "%s", swap_source);
run_quiet(swapon);
}
}
@@ -680,8 +717,7 @@ static int write_metadata(const char *data_uuid, const char *swap_uuid)
}
fprintf(file, "version=1\nuuid=%s\nswap_uuid=%s\n", data_uuid,
swap_uuid);
- if (fflush(file) != 0 || fsync(fd) != 0 || fclose(file) != 0 ||
- rename(temporary, EXPANDER_META_REL) != 0) {
+ if (!commit_file(file, fd, temporary, EXPANDER_META_REL)) {
unlink(temporary);
return 0;
}
@@ -711,10 +747,12 @@ static uint64_t directory_allocated_bytes(const char *path)
{
char du[PATH_MAX];
char output[128];
+ char target[PATH_MAX];
char *end;
unsigned long long kibibytes;
- char *argv[] = {du, "-sk", (char *)path, NULL};
- if (!process_find("du", du, sizeof(du)) ||
+ char *argv[] = {du, "-sk", target, NULL};
+ if (snprintf(target, sizeof(target), "%s", path) >= (int)sizeof(target) ||
+ !process_find("du", du, sizeof(du)) ||
process_capture(argv, output, sizeof(output)) != 0)
return 0;
errno = 0;
@@ -756,9 +794,9 @@ static void copy_tick(void *opaque)
overall_percent = 69;
if (state->total_bytes)
snprintf(status, sizeof(status),
- "Copying /usr to USB: %u%% (%llu / %llu MiB, %u s)",
- copy_percent, (unsigned long long)(copied / 1024ULL / 1024ULL),
- (unsigned long long)(state->total_bytes / 1024ULL / 1024ULL),
+ "Copying /usr to USB: %u%% (%" PRIu64 " / %" PRIu64 " MiB, %u s)",
+ copy_percent, copied / (1024U * 1024U),
+ state->total_bytes / (1024U * 1024U),
state->elapsed_seconds);
else
snprintf(status, sizeof(status), "Copying /usr to USB... %u s elapsed",
@@ -774,154 +812,181 @@ static void progress(storage_progress_cb callback, void *opaque, int percent,
callback(percent, status, opaque);
}
-int storage_prepare(const struct storage_device *device,
- storage_progress_cb callback, void *opaque, char *uuid,
- size_t uuid_size, char *error, size_t error_size)
-{
- int disk_fd = -1;
- uint64_t bytes = 0;
- unsigned int sector_size = 512;
- uint64_t total_sectors;
- uint64_t alignment;
- uint64_t first_start;
- uint64_t swap_sectors;
- uint64_t swap_start;
- uint64_t first_size;
+struct prepare_state {
+ const struct storage_device *device;
+ storage_progress_cb callback;
+ void *opaque;
+ char *error;
+ size_t error_size;
+ int disk_fd;
char part1[64];
char part2[64];
- char script[512];
- char sfdisk[PATH_MAX];
- char mkfs[PATH_MAX];
- char mkswap[PATH_MAX];
- char nomount_flag[PATH_MAX];
char data_uuid[128];
char swap_uuid[128];
- int mounted = 0;
- int mount_locked = 0;
- int bound = 0;
- int swap_active = 0;
- int fstab_written = 0;
+ int mounted;
+ int mount_locked;
+ int bound;
+ int swap_active;
+ int fstab_written;
+};
- if (uuid && uuid_size) uuid[0] = '\0';
- if (error && error_size) error[0] = '\0';
- if (!device || !validate_selected(device)) {
- set_error(error, error_size,
- "The selected USB device is no longer safely available.");
- return 0;
- }
- /* This is the flag consumed by OpenATV's udev mount.sh. */
- if (!create_nomount_flag(device, nomount_flag, sizeof(nomount_flag))) {
- set_error(error, error_size,
- "The automatic mount guard cannot be created for %s.",
- device->path);
+struct partition_layout {
+ uint64_t first_start;
+ uint64_t first_size;
+ uint64_t swap_start;
+ uint64_t swap_sectors;
+};
+
+static int open_target_disk(struct prepare_state *state, uint64_t *bytes,
+ unsigned int *sector_size)
+{
+ const struct storage_device *device = state->device;
+ state->disk_fd = open(device->path, O_RDONLY | O_CLOEXEC); /* NOSONAR the USB device the user chose */
+ if (state->disk_fd < 0 || ioctl(state->disk_fd, BLKGETSIZE64, bytes) < 0) {
+ set_error(state->error, state->error_size,
+ "The size of %s cannot be read: %s", device->path, strerror(errno));
return 0;
}
- progress(callback, opaque, 3, "Preparing the USB device for exclusive use...");
- if (!deactivate_target(device, error, error_size))
+ if (ioctl(state->disk_fd, BLKSSZGET, sector_size) < 0)
+ *sector_size = 512;
+ if (*bytes != device->size_bytes || *sector_size == 0 ||
+ *bytes < SWAP_BYTES + MIN_DATA_BYTES) {
+ set_error(state->error, state->error_size,
+ "The USB device size has changed.");
return 0;
- disk_fd = open(device->path, O_RDONLY | O_CLOEXEC);
- if (disk_fd < 0 || ioctl(disk_fd, BLKGETSIZE64, &bytes) < 0) {
- set_error(error, error_size, "The size of %s cannot be read: %s",
- device->path, strerror(errno));
- goto failed;
- }
- if (ioctl(disk_fd, BLKSSZGET, §or_size) < 0)
- sector_size = 512;
- if (bytes != device->size_bytes || sector_size == 0 ||
- bytes < SWAP_BYTES + MIN_DATA_BYTES) {
- set_error(error, error_size, "The USB device size has changed.");
- goto failed;
}
- total_sectors = bytes / sector_size;
+ return 1;
+}
+
+static int compute_layout(struct prepare_state *state, uint64_t bytes,
+ unsigned int sector_size, struct partition_layout *layout)
+{
+ uint64_t total_sectors = bytes / sector_size;
+ uint64_t alignment;
if (total_sectors > 0xffffffffULL) {
- set_error(error, error_size,
+ set_error(state->error, state->error_size,
"USB devices larger than 2 TB are not supported by this version.");
- goto failed;
+ return 0;
}
alignment = 1024ULL * 1024ULL / sector_size;
if (alignment == 0) alignment = 1;
- first_start = alignment;
- swap_sectors = SWAP_BYTES / sector_size;
- swap_start = ((total_sectors - swap_sectors) / alignment) * alignment;
- first_size = swap_start - first_start;
- if (first_size * sector_size < MIN_DATA_BYTES) {
- set_error(error, error_size, "The USB device is too small.");
- goto failed;
+ layout->first_start = alignment;
+ layout->swap_sectors = SWAP_BYTES / sector_size;
+ layout->swap_start = ((total_sectors - layout->swap_sectors) / alignment) *
+ alignment;
+ layout->first_size = layout->swap_start - layout->first_start;
+ if (layout->first_size * sector_size < MIN_DATA_BYTES) {
+ set_error(state->error, state->error_size, "The USB device is too small.");
+ return 0;
}
- partition_path(device, 1, part1, sizeof(part1));
- partition_path(device, 2, part2, sizeof(part2));
+ return 1;
+}
+
+static int partition_disk(struct prepare_state *state)
+{
+ const struct storage_device *device = state->device;
+ uint64_t bytes = 0;
+ unsigned int sector_size = 512;
+ struct partition_layout layout;
+ char script[512];
+ char sfdisk[PATH_MAX];
+ char disk_path[sizeof(device->path)];
+ char *argv[] = {sfdisk, "--wipe", "always", "--wipe-partitions",
+ "always", disk_path, NULL};
+
+ if (!open_target_disk(state, &bytes, §or_size) ||
+ !compute_layout(state, bytes, sector_size, &layout))
+ return 0;
+ partition_path(device, 1, state->part1, sizeof(state->part1));
+ partition_path(device, 2, state->part2, sizeof(state->part2));
snprintf(script, sizeof(script),
"label: dos\nunit: sectors\n%s : start=%llu, size=%llu, type=83\n"
"%s : start=%llu, size=%llu, type=82\n",
- part1, (unsigned long long)first_start,
- (unsigned long long)first_size, part2,
- (unsigned long long)swap_start,
- (unsigned long long)swap_sectors);
+ state->part1, (unsigned long long)layout.first_start,
+ (unsigned long long)layout.first_size, state->part2,
+ (unsigned long long)layout.swap_start,
+ (unsigned long long)layout.swap_sectors);
if (!process_find("sfdisk", sfdisk, sizeof(sfdisk))) {
- set_error(error, error_size, "sfdisk is not installed in this image.");
- goto failed;
+ set_error(state->error, state->error_size,
+ "sfdisk is not installed in this image.");
+ return 0;
}
- progress(callback, opaque, 10, "Creating a new partition table...");
- {
- char *argv[] = {sfdisk, "--wipe", "always", "--wipe-partitions",
- "always", (char *)device->path, NULL};
- if (process_run(argv, script, NULL, NULL) != 0) {
- set_error(error, error_size, "Partitioning %s failed.",
- device->path);
- goto failed;
- }
+ progress(state->callback, state->opaque, 10,
+ "Creating a new partition table...");
+ snprintf(disk_path, sizeof(disk_path), "%s", device->path);
+ if (process_run(argv, script, NULL, NULL) != 0) {
+ set_error(state->error, state->error_size, "Partitioning %s failed.",
+ device->path);
+ return 0;
}
- ioctl(disk_fd, BLKRRPART);
- close(disk_fd);
- disk_fd = -1;
- if (!wait_for_partitions(part1, part2)) {
- set_error(error, error_size,
+ ioctl(state->disk_fd, BLKRRPART);
+ close(state->disk_fd);
+ state->disk_fd = -1;
+ if (!wait_for_partitions(state->part1, state->part2)) {
+ set_error(state->error, state->error_size,
"The new partitions did not appear in the system.");
- goto failed;
+ return 0;
}
+ return 1;
+}
+
+static int format_partitions(struct prepare_state *state)
+{
+ char mkfs[PATH_MAX];
+ char mkswap[PATH_MAX];
+ char *mkfs_argv[] = {mkfs, "-F", "-t", "ext4", "-L",
+ "FLASH_EXPANDER", "-m", "0", state->part1, NULL};
+ char *mkswap_argv[] = {mkswap, "-L", "SMALLBOX_SWAP", state->part2, NULL};
+
if (!process_find("mkfs.ext4", mkfs, sizeof(mkfs)) &&
!process_find("mke2fs", mkfs, sizeof(mkfs))) {
- set_error(error, error_size, "mkfs.ext4 is not installed in this image.");
- goto failed;
+ set_error(state->error, state->error_size,
+ "mkfs.ext4 is not installed in this image.");
+ return 0;
}
- progress(callback, opaque, 22, "Formatting the FlashExpander as ext4...");
- {
- char *argv[] = {mkfs, "-F", "-t", "ext4", "-L",
- "FLASH_EXPANDER", "-m", "0", part1, NULL};
- if (process_run(argv, NULL, NULL, NULL) != 0) {
- set_error(error, error_size, "Formatting the ext4 partition failed.");
- goto failed;
- }
+ progress(state->callback, state->opaque, 22,
+ "Formatting the FlashExpander as ext4...");
+ if (process_run(mkfs_argv, NULL, NULL, NULL) != 0) {
+ set_error(state->error, state->error_size,
+ "Formatting the ext4 partition failed.");
+ return 0;
}
if (!process_find("mkswap", mkswap, sizeof(mkswap))) {
- set_error(error, error_size, "mkswap is not installed in this image.");
- goto failed;
+ set_error(state->error, state->error_size,
+ "mkswap is not installed in this image.");
+ return 0;
}
- progress(callback, opaque, 32, "Creating the 512 MB swap partition...");
- {
- char *argv[] = {mkswap, "-L", "SMALLBOX_SWAP", part2, NULL};
- if (process_run(argv, NULL, NULL, NULL) != 0) {
- set_error(error, error_size, "Formatting the swap partition failed.");
- goto failed;
- }
+ progress(state->callback, state->opaque, 32,
+ "Creating the 512 MB swap partition...");
+ if (process_run(mkswap_argv, NULL, NULL, NULL) != 0) {
+ set_error(state->error, state->error_size,
+ "Formatting the swap partition failed.");
+ return 0;
}
+ return 1;
+}
+
+static int mount_expander(struct prepare_state *state)
+{
+ char *mount_argv[] = {"mount", "-t", "ext4", "-o", "noatime",
+ state->part1, EXPANDER_MOUNT, NULL};
+ char *swapon_argv[] = {"swapon", state->part2, NULL};
+
/* Drain any event which entered mount.sh just before the nomount flag. */
- usleep(500000);
- if (!deactivate_target(device, error, error_size))
- goto failed;
+ sleep_ms(500);
+ if (!deactivate_target(state->device, state->error, state->error_size))
+ return 0;
if (!make_directory(EXPANDER_MOUNT, 0755)) {
- set_error(error, error_size, "The mount directory cannot be created.");
- goto failed;
+ set_error(state->error, state->error_size,
+ "The mount directory cannot be created.");
+ return 0;
}
- {
- char *argv[] = {"mount", "-t", "ext4", "-o", "noatime",
- part1, EXPANDER_MOUNT, NULL};
- if (process_run(argv, NULL, NULL, NULL) != 0) {
- set_error(error, error_size, "The FlashExpander cannot be mounted.");
- goto failed;
- }
+ if (process_run(mount_argv, NULL, NULL, NULL) != 0) {
+ set_error(state->error, state->error_size,
+ "The FlashExpander cannot be mounted.");
+ return 0;
}
- mounted = 1;
+ state->mounted = 1;
/*
* Keep our current directory inside the filesystem while copying. A
* traditional automounter can no longer unmount it, and relative paths
@@ -929,119 +994,176 @@ int storage_prepare(const struct storage_device *device,
* us. This also prevents a copy from ever falling through into flash.
*/
if (chdir(EXPANDER_MOUNT) != 0) {
- set_error(error, error_size,
+ set_error(state->error, state->error_size,
"The FlashExpander mount cannot be locked: %s", strerror(errno));
- goto failed;
+ return 0;
}
- mount_locked = 1;
- progress(callback, opaque, 38,
+ state->mount_locked = 1;
+ progress(state->callback, state->opaque, 38,
"Activating 512 MB swap before copying /usr...");
- {
- char *argv[] = {"swapon", part2, NULL};
- if (process_run(argv, NULL, NULL, NULL) != 0) {
- set_error(error, error_size, "Swap could not be activated.");
- goto failed;
- }
+ if (process_run(swapon_argv, NULL, NULL, NULL) != 0) {
+ set_error(state->error, state->error_size,
+ "Swap could not be activated.");
+ return 0;
}
- swap_active = 1;
+ state->swap_active = 1;
+ return 1;
+}
+
+static int copy_usr(struct prepare_state *state)
+{
+ struct copy_state copy;
+ char *argv[] = {"cp", "-a", "/usr/.", EXPANDER_USR_REL, NULL};
+
if (!make_directory(EXPANDER_USR_REL, 0755)) {
- set_error(error, error_size, "The destination for /usr cannot be created.");
- goto failed;
+ set_error(state->error, state->error_size,
+ "The destination for /usr cannot be created.");
+ return 0;
}
- progress(callback, opaque, 42,
+ progress(state->callback, state->opaque, 42,
"Copying the complete /usr tree. Do not remove the USB device!");
- {
- struct copy_state state;
- char *argv[] = {"cp", "-a", "/usr/.", EXPANDER_USR_REL, NULL};
- memset(&state, 0, sizeof(state));
- state.callback = callback;
- state.opaque = opaque;
- state.total_bytes = directory_allocated_bytes("/usr");
- state.initial_free_bytes = filesystem_free_bytes(".");
- if (process_run_with_updates(argv, NULL, copy_line, copy_tick, 1000,
- &state) != 0) {
- if (state.error_line[0])
- set_error(error, error_size, "Copying /usr failed: %s",
- state.error_line);
- else
- set_error(error, error_size, "Copying /usr failed.");
- goto failed;
- }
+ memset(©, 0, sizeof(copy));
+ copy.callback = state->callback;
+ copy.opaque = state->opaque;
+ copy.total_bytes = directory_allocated_bytes("/usr");
+ copy.initial_free_bytes = filesystem_free_bytes(".");
+ if (process_run_with_updates(argv, NULL, copy_line, copy_tick, 1000,
+ ©) != 0) {
+ if (copy.error_line[0])
+ set_error(state->error, state->error_size,
+ "Copying /usr failed: %s", copy.error_line);
+ else
+ set_error(state->error, state->error_size, "Copying /usr failed.");
+ return 0;
}
sync();
- progress(callback, opaque, 70, "Copying /usr to USB: 100% complete.");
+ progress(state->callback, state->opaque, 70,
+ "Copying /usr to USB: 100% complete.");
if (access(EXPANDER_USR_REL "/bin", F_OK) != 0) {
- set_error(error, error_size, "The copied /usr tree is incomplete.");
- goto failed;
+ set_error(state->error, state->error_size,
+ "The copied /usr tree is incomplete.");
+ return 0;
}
- progress(callback, opaque, 72, "Configuring UUIDs and persistent mounts...");
- if (!blkid_uuid(part1, data_uuid, sizeof(data_uuid)) ||
- !blkid_uuid(part2, swap_uuid, sizeof(swap_uuid))) {
- set_error(error, error_size, "The partition UUIDs cannot be read.");
- goto failed;
+ return 1;
+}
+
+static int switch_usr(struct prepare_state *state)
+{
+ char *bind_argv[] = {"mount", "--bind", EXPANDER_USR, "/usr", NULL};
+ struct stat source_status;
+ struct stat target_status;
+
+ progress(state->callback, state->opaque, 72,
+ "Configuring UUIDs and persistent mounts...");
+ if (!blkid_uuid(state->part1, state->data_uuid, sizeof(state->data_uuid)) ||
+ !blkid_uuid(state->part2, state->swap_uuid, sizeof(state->swap_uuid))) {
+ set_error(state->error, state->error_size,
+ "The partition UUIDs cannot be read.");
+ return 0;
}
- if (!write_metadata(data_uuid, swap_uuid)) {
- set_error(error, error_size, "The FlashExpander metadata could not be saved.");
- goto failed;
+ if (!write_metadata(state->data_uuid, state->swap_uuid)) {
+ set_error(state->error, state->error_size,
+ "The FlashExpander metadata could not be saved.");
+ return 0;
}
- if (!update_fstab(data_uuid, swap_uuid, 1, error, error_size))
- goto failed;
- fstab_written = 1;
- progress(callback, opaque, 82, "Switching /usr live to the USB device...");
- {
- char *argv[] = {"mount", "--bind", EXPANDER_USR, "/usr", NULL};
- if (process_run(argv, NULL, NULL, NULL) != 0) {
- set_error(error, error_size, "The bind mount for /usr failed.");
- goto failed;
- }
+ if (!update_fstab(state->data_uuid, state->swap_uuid, 1, state->error,
+ state->error_size))
+ return 0;
+ state->fstab_written = 1;
+ progress(state->callback, state->opaque, 82,
+ "Switching /usr live to the USB device...");
+ if (process_run(bind_argv, NULL, NULL, NULL) != 0) {
+ set_error(state->error, state->error_size,
+ "The bind mount for /usr failed.");
+ return 0;
}
- bound = 1;
- {
- struct stat source_status;
- struct stat target_status;
- if (stat(EXPANDER_USR, &source_status) < 0 ||
- stat("/usr", &target_status) < 0 ||
- source_status.st_dev != target_status.st_dev) {
- set_error(error, error_size, "The /usr switch could not be verified.");
- goto failed;
- }
+ state->bound = 1;
+ if (stat(EXPANDER_USR, &source_status) < 0 ||
+ stat("/usr", &target_status) < 0 ||
+ source_status.st_dev != target_status.st_dev) {
+ set_error(state->error, state->error_size,
+ "The /usr switch could not be verified.");
+ return 0;
}
- progress(callback, opaque, 92, "Verifying the active 512 MB swap...");
+ return 1;
+}
+
+static int release_working_directory(struct prepare_state *state)
+{
+ progress(state->callback, state->opaque, 92,
+ "Verifying the active 512 MB swap...");
sync();
- if (mount_locked) {
+ if (state->mount_locked) {
if (chdir("/") < 0) {
- set_error(error, error_size,
+ set_error(state->error, state->error_size,
"The USB working directory cannot be released: %s",
strerror(errno));
- goto failed;
+ return 0;
}
- mount_locked = 0;
+ state->mount_locked = 0;
}
- progress(callback, opaque, 100, "FlashExpander and swap are active.");
- if (uuid && uuid_size)
- snprintf(uuid, uuid_size, "%s", data_uuid);
return 1;
+}
-failed:
- if (disk_fd >= 0)
- close(disk_fd);
- if (fstab_written)
+static void prepare_cleanup(struct prepare_state *state)
+{
+ char *swapoff_argv[] = {"swapoff", state->part2, NULL};
+ char *umount_usr_argv[] = {"umount", "/usr", NULL};
+ char *umount_data_argv[] = {"umount", EXPANDER_MOUNT, NULL};
+
+ if (state->disk_fd >= 0)
+ close(state->disk_fd);
+ if (state->fstab_written)
update_fstab(NULL, NULL, 0, NULL, 0);
- if (swap_active) {
- char *argv[] = {"swapoff", part2, NULL};
- run_quiet(argv);
- }
- if (bound) {
- char *argv[] = {"umount", "/usr", NULL};
- run_quiet(argv);
+ if (state->swap_active)
+ run_quiet(swapoff_argv);
+ if (state->bound)
+ run_quiet(umount_usr_argv);
+ if (state->mount_locked && chdir("/") == 0)
+ state->mount_locked = 0;
+ if (state->mounted)
+ run_quiet(umount_data_argv);
+}
+
+int storage_prepare(const struct storage_device *device,
+ storage_progress_cb callback, void *opaque, char *uuid,
+ size_t uuid_size, char *error, size_t error_size)
+{
+ struct prepare_state state;
+ char nomount_flag[PATH_MAX];
+
+ if (uuid && uuid_size) uuid[0] = '\0';
+ if (error && error_size) error[0] = '\0';
+ if (!device || !validate_selected(device)) {
+ set_error(error, error_size,
+ "The selected USB device is no longer safely available.");
+ return 0;
}
- if (mount_locked) {
- if (chdir("/") == 0)
- mount_locked = 0;
+ /* This is the flag consumed by OpenATV's udev mount.sh. */
+ if (!create_nomount_flag(device, nomount_flag, sizeof(nomount_flag))) {
+ set_error(error, error_size,
+ "The automatic mount guard cannot be created for %s.",
+ device->path);
+ return 0;
}
- if (mounted) {
- char *argv[] = {"umount", EXPANDER_MOUNT, NULL};
- run_quiet(argv);
+ progress(callback, opaque, 3, "Preparing the USB device for exclusive use...");
+ if (!deactivate_target(device, error, error_size))
+ return 0;
+ memset(&state, 0, sizeof(state));
+ state.device = device;
+ state.callback = callback;
+ state.opaque = opaque;
+ state.error = error;
+ state.error_size = error_size;
+ state.disk_fd = -1;
+ if (!partition_disk(&state) || !format_partitions(&state) ||
+ !mount_expander(&state) || !copy_usr(&state) ||
+ !switch_usr(&state) || !release_working_directory(&state)) {
+ prepare_cleanup(&state);
+ return 0;
}
- return 0;
+ progress(callback, opaque, 100, "FlashExpander and swap are active.");
+ if (uuid && uuid_size)
+ snprintf(uuid, uuid_size, "%s", state.data_uuid);
+ return 1;
}
diff --git a/src/ui.c b/src/ui.c
index 6e23e61..01cff59 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -192,7 +192,7 @@ static void put_pixels(const struct ui_context *ui, uint8_t *to, const uint8_t *
}
}
-static void flush(lv_display_t *disp, const lv_area_t *area, uint8_t *pixels)
+static void flush(lv_display_t *disp, const lv_area_t *area, uint8_t *pixels) /* NOSONAR the flush callback type of LVGL */
{
const struct ui_context *ui = active;
if (ui && ui->screen) {
@@ -725,18 +725,20 @@ static void summary(lv_obj_t *parent)
lv_obj_set_style_pad_row(obj, px(12), 0);
snprintf(copy, sizeof(copy), "%s", summary_text);
while (line && *line) {
- char *next = strchr(line, '\n');
- char *value;
+ size_t length = strcspn(line, "\n");
+ size_t name_length = strcspn(line, "\t\n");
+ int last = !line[length];
+ const char *value = "";
lv_obj_t *cell = column(obj, px(4));
- if (next)
- *next++ = '\0';
- value = strchr(line, '\t');
- if (value)
- *value++ = '\0';
+ line[length] = '\0';
+ if (name_length < length) {
+ line[name_length] = '\0';
+ value = line + name_length + 1;
+ }
lv_obj_set_width(cell, LV_SIZE_CONTENT);
label(cell, line, FONT_SMALL, COLOR_MUTED);
- label(cell, value ? value : "", FONT_TEXT, value && *value ? COLOR_TEXT : COLOR_DIM);
- line = next;
+ label(cell, value, FONT_TEXT, *value ? COLOR_TEXT : COLOR_DIM);
+ line = last ? NULL : line + length + 1;
}
}
diff --git a/tools/mklicenses.py b/tools/mklicenses.py
index cdbd226..025eef8 100644
--- a/tools/mklicenses.py
+++ b/tools/mklicenses.py
@@ -8,7 +8,7 @@
ROOT = join(dirname(__file__), "..")
# Name, what it is in the wizard, license, file with the full text.
LICENSES = (
- ("OE-Alliance SmallBox Wizard", "this application", "MIT", "LICENSE"),
+ ("OE-Alliance SmallBox Wizard", "this application", "GPL-3.0", "LICENSE"),
("LVGL 9.6.0", "the graphics library", "MIT", "lib/lvgl/LICENCE.txt"),
("DejaVu Sans 2.37", "the font", "Bitstream Vera, Arev", "tools/fonts/DejaVuSans-LICENSE.txt"),
("Font Awesome 7.3.1 Free", "the icons", "SIL OFL 1.1", "tools/fonts/fontawesome/LICENSE.txt"),