rust-apt-0.8.0/.cargo_vcs_info.json0000644000000001360000000000100126200ustar { "git": { "sha1": "ab9661787ff234ebab46ba20d34b7b38de62ca77" }, "path_in_vcs": "" }rust-apt-0.8.0/.clang-format000064400000000000000000000005411046102023000137630ustar 00000000000000BasedOnStyle: Chromium IndentWidth: 4 TabWidth: 4 UseTab: Always BreakBeforeBraces: Attach BreakStringLiterals: false AllowShortIfStatementsOnASingleLine: true AllowShortBlocksOnASingleLine: true AllowShortFunctionsOnASingleLine: true AlignAfterOpenBracket: BlockIndent PenaltyReturnTypeOnItsOwnLine: 1000 Language: Cpp Standard: Cpp11 ColumnLimit: 100 rust-apt-0.8.0/.editorconfig000064400000000000000000000002771046102023000140730ustar 00000000000000root = true [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 indent_style = tab indent_size = 4 [*.yml] indent_style = space indent_size = 2 rust-apt-0.8.0/.gitignore000064400000000000000000000003731046102023000134030ustar 00000000000000/target/ **/*.rs.bk Cargo.lock .idea *.iml apt-c/lib.o apt-c/libapt-c.a .cache compile_commands.json tests/files/cache/*.deb tests/files/cache/Packages .gitlab-ci-local* # So Cargo doesn't complain about a dirty working directory in CI Publish .cargo rust-apt-0.8.0/.gitlab-ci.yml000064400000000000000000000046051046102023000140510ustar 00000000000000stages: - test - publish variables: DEBIAN_FRONTEND: noninteractive CARGO_HOME: $CI_PROJECT_DIR/.cargo PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/${CARGO_HOME}/bin" USER: "root" test: stage: test image: debian:sid environment: cache: key: build-cache paths: - ".cargo/" - target/ script: # Debian/Ubuntu docker images contain some docker-specific configs that mess with rust-apt tests, so remove them here first. # https://github.com/GoogleContainerTools/base-images-docker/blob/master/debian/reproducible/overlay/etc/apt/apt.conf.d/docker-gzip-indexes - rm -f /etc/apt/apt.conf.d/docker* # Install needed packages - apt-get update - apt-get install eatmydata -y - eatmydata apt-get install -y build-essential curl sudo apt-utils libapt-pkg-dev clang-format codespell - curl https://sh.rustup.rs -sSf | sh -- /dev/stdin -y - rustup toolchain install nightly - rustup toolchain install stable # Run tests - cargo install just - eatmydata just check # Docker has 0777 for everything. Deb packages need to be 0755 - chmod 0755 -R tests/files/cache # Create the test .debs then run the tests - eatmydata just create-test-debs - eatmydata cargo test --no-fail-fast -- --test-threads 1 publish: stage: publish only: - main except: - tags image: debian:sid environment: cache: key: build-cache paths: - ".cargo/" - target/ script: # Install rustup and other needed dependencies - apt-get update - apt-get install -y build-essential curl git jq libapt-pkg-dev - curl https://sh.rustup.rs -sSf | sh -- /dev/stdin -y - rustup default stable # Create the Git tag and publish. - | git fetch --tags current_version="$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[0].version')" if ! git tag | grep "^v${current_version}$"; then project_url="$(echo "${CI_PROJECT_URL}" | sed "s|https://|https://oauth2:${WRITE_REPOSITORY}@|")" git tag "v${current_version}" git push -o ci.skip "${project_url}" "v${current_version}" # And now publish to crates.io CARGO_REGISTRY_TOKEN="${CARGO_KEY}" cargo publish else echo "DEBUG: Git tag 'v${current_version}' already exists." fi rust-apt-0.8.0/.vscode/c_cpp_properties.json000064400000000000000000000012341046102023000172040ustar 00000000000000{ "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**" ], "defines": [], "compilerPath": "/usr/bin/c++", "cStandard": "c17", "cppStandard": "c++14", "intelliSenseMode": "${default}", "compileCommands": "${workspaceFolder}/compile_commands.json", "mergeConfigurations": false, "browse": { "path": [ "${workspaceFolder}/**" ], "limitSymbolsToIncludedHeaders": true } } ], "version": 4 } rust-apt-0.8.0/.vscode/settings.json000064400000000000000000000035351046102023000155120ustar 00000000000000{ "files.associations": { "cctype": "cpp", "clocale": "cpp", "cmath": "cpp", "cstdarg": "cpp", "cstddef": "cpp", "cstdio": "cpp", "cstdlib": "cpp", "cstring": "cpp", "ctime": "cpp", "cwchar": "cpp", "cwctype": "cpp", "array": "cpp", "atomic": "cpp", "bit": "cpp", "*.tcc": "cpp", "chrono": "cpp", "compare": "cpp", "concepts": "cpp", "condition_variable": "cpp", "cstdint": "cpp", "deque": "cpp", "forward_list": "cpp", "list": "cpp", "map": "cpp", "set": "cpp", "string": "cpp", "unordered_map": "cpp", "unordered_set": "cpp", "vector": "cpp", "exception": "cpp", "algorithm": "cpp", "functional": "cpp", "iterator": "cpp", "memory": "cpp", "memory_resource": "cpp", "numeric": "cpp", "random": "cpp", "ratio": "cpp", "string_view": "cpp", "system_error": "cpp", "tuple": "cpp", "type_traits": "cpp", "utility": "cpp", "fstream": "cpp", "initializer_list": "cpp", "iosfwd": "cpp", "iostream": "cpp", "istream": "cpp", "limits": "cpp", "mutex": "cpp", "new": "cpp", "numbers": "cpp", "ostream": "cpp", "semaphore": "cpp", "sstream": "cpp", "stdexcept": "cpp", "stop_token": "cpp", "streambuf": "cpp", "thread": "cpp", "cinttypes": "cpp", "typeinfo": "cpp", "*.rs": "rust", "future": "cpp", "variant": "cpp", "charconv": "cpp", "csignal": "cpp", "optional": "cpp", "format": "cpp", "iomanip": "cpp", "span": "cpp" } } rust-apt-0.8.0/Cargo.toml0000644000000017400000000000100106200ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "rust-apt" version = "0.8.0" authors = [ "Blake Lee ", "Hunter Wittenborn ", ] description = "Bindings for libapt-pkg" readme = "README.md" categories = [ "api-bindings", "os", ] license = "GPL-3.0-or-later" repository = "https://gitlab.com/volian/rust-apt" [dependencies.cxx] version = "1.0" [dependencies.paste] version = "1.0" [dependencies.terminal_size] version = "0.3.0" [build-dependencies.cxx-build] version = "1.0" rust-apt-0.8.0/Cargo.toml.orig000064400000000000000000000007011046102023000142750ustar 00000000000000[package] authors = [ "Blake Lee ", "Hunter Wittenborn " ] categories = [ "api-bindings", "os", ] description = "Bindings for libapt-pkg" license = "GPL-3.0-or-later" name = "rust-apt" readme = "README.md" repository = "https://gitlab.com/volian/rust-apt" version = "0.8.0" edition = "2021" [dependencies] cxx = "1.0" paste = "1.0" terminal_size = "0.3.0" [build-dependencies] cxx-build = "1.0" rust-apt-0.8.0/LICENSE000064400000000000000000001044711046102023000124240ustar 00000000000000GNU 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 . rust-apt-0.8.0/README.md000064400000000000000000000037231046102023000126740ustar 00000000000000# RUST-APT `rust-apt` provides bindings to `libapt-pkg`. Currently `rust-apt` has most functionality available such as basic querying of package information, Installing and removing packages, updating the package lists and upgrading the system. If you find something missing, please make an Issue to request the feature. ### *This API is subject to change at any time* `rust-apt` is stable for use, but breaking changes are possible. Breaking changes will be on the minor, never on the patch. `src/raw` contains the direct C++ bindings to `libapt-pkg` that are defined in `apt-pkg-c` These are generally considered safe, but may cause segfaults if you do something wrong. We offer no safety guarantees for using the `raw` bindings directly. If you find a way to segfault without using the `raw` bindings directly, please report this as a bug. # Documentation and Examples For more instructions on how to use `rust-apt` see our [docs.rs](https://docs.rs/rust-apt/latest/rust_apt) page. # License Note This crate is licensed under the GPLv3 or later. # Building `libapt-pkg-dev` must be installed. Minimum supported version is `2.0.2`. # Thread safety It is not advised to use this crate in multiple threads. You're free to try it but development will not be focused on making this crate thread safe. # Development Make sure `cargo` and `rustup` are installed before you run the following commands. You will need the stable and nightly toolchain. Nightly is only used for `rustfmt`. Install `just`, a command runner we use to simplify some tasks. ```console cargo install just ``` Now that `cargo` and `just` are installed, You can setup your dev environment. `setup-dev` will: * Install the necessary dependencies with `apt`. * Ensure the proper toolchains are installed with `rustup`. * Create `compile_commands.json` with `bear` for better c++ linting ```console just setup-dev ``` Before you commit, check formatting and basic code QA. ```console just fmt just check ``` rust-apt-0.8.0/apt-pkg-c/acquire.h000064400000000000000000000146601046102023000150040ustar 00000000000000#pragma once #include #include #include #include #include #include "rust/cxx.h" #include "rust-apt/src/progress.rs" #include "types.h" // ItemState Enum using ItemState = pkgAcquire::Item::ItemState; struct PkgAcquire { pkgAcquire* ptr; // If true, delete ptr during deconstruction bool del; UniquePtr> uris() const; UniquePtr> workers() const; PkgAcquire() : ptr(new pkgAcquire), del(true){}; PkgAcquire(pkgAcquire* base) : ptr(base), del(false){}; ~PkgAcquire() { if (del) { delete ptr; } }; }; struct Item { pkgAcquire::Item* ptr; u32 id() const { return ptr->ID; } bool complete() const { return ptr->Complete; } u64 file_size() const { return ptr->FileSize; } ItemState status() const { return ptr->Status; } String uri() const { return ptr->DescURI(); } String dest_file() const { return ptr->DestFile; } String error_text() const { return ptr->ErrorText; } String active_subprocess() const { return ptr->ActiveSubprocess; } UniquePtr owner() const { return std::make_unique(ptr->GetOwner()); } Item(pkgAcquire::Item* base) : ptr(base){}; }; struct ItemDesc { pkgAcquire::ItemDesc* ptr; String uri() const { return ptr->URI; } String description() const { return ptr->Description; } String short_desc() const { return ptr->ShortDesc; } UniquePtr owner() const { return std::make_unique(ptr->Owner); } // Cast away the constness in this case. We aren't going to change it. ItemDesc(const pkgAcquire::ItemDesc* base) : ptr(const_cast(base)){}; ItemDesc(pkgAcquire::ItemDesc* base) : ptr(base){}; }; struct AcqWorker { pkgAcquire::Worker* ptr; pkgAcquire::ItemDesc* item_desc; String status() const { return ptr->Status; } u64 current_size() const { return ptr->CurrentItem->CurrentSize; } u64 total_size() const { return ptr->CurrentItem->TotalSize; } UniquePtr item() const { if (ptr->CurrentItem == 0) { throw std::runtime_error("Null Item!"); } return std::make_unique(item_desc); } AcqWorker(pkgAcquire::Worker* base) : ptr(base), item_desc(base->CurrentItem){}; }; struct AcqTextStatus : public pkgAcquireStatus { u32 ID; /// Callback to the rust struct AcquireProgress* callback; void AssignItemID(pkgAcquire::ItemDesc& Itm) { if (Itm.Owner->ID == 0) Itm.Owner->ID = ID++; }; bool ReleaseInfoChanges( metaIndex const* const LastRelease, metaIndex const* const CurrentRelease, std::vector&& Changes ) { (void)LastRelease; (void)CurrentRelease; (void)Changes; // if (Quiet >= 2 || isatty(STDOUT_FILENO) != 1 || isatty(STDIN_FILENO) != 1 || // _config->FindB("APT::Get::Update::InteractiveReleaseInfoChanges", false) == false) // return pkgAcquireStatus::ReleaseInfoChanges(nullptr, nullptr, std::move(Changes)); // _error->PushToStack(); // auto const confirmed = pkgAcquireStatus::ReleaseInfoChanges(L, N, // std::move(Changes)); if (confirmed == true) { _error->MergeWithStack(); // return true; // } // clearLastLine(); // _error->DumpErrors(out, GlobalError::NOTICE, false); // _error->RevertToStack(); // return YnPrompt(_("Do you want to accept these changes and continue updating from this // repository?"), false, false, out, out); // Not yet implemented. Remove return true when it is. return true; }; bool MediaChange(std::string Media, std::string Drive) { (void)Drive; (void)Media; // If we do not output on a terminal and one of the options to avoid user // interaction is given, we assume that no user is present who could react // on your media change request // if (isatty(STDOUT_FILENO) != 1 && Quiet >= 2 && // (_config->FindB("APT::Get::Assume-Yes", false) == true || // _config->FindB("APT::Get::Force-Yes", false) == true || // _config->FindB("APT::Get::Trivial-Only", false) == true)) // return false; // clearLastLine(); // ioprintf(out, // "Media change: please insert the disc labeled\n" // " '%s'\n" // "in the drive '%s' and press [Enter]\n", // Media.c_str(), Drive.c_str()); // char C = 0; // bool bStatus = true; // while (C != '\n' && C != '\r') { // int len = read(STDIN_FILENO, &C, 1); // if (C == 'c' || len <= 0) { // bStatus = false; // break; // } // } // if (bStatus) Update = true; // return bStatus; // I'm not sure what to return here. // Will need to test media swaps at some point return false; }; void IMSHit(pkgAcquire::ItemDesc& Itm) { Update = true; AssignItemID(Itm); callback->hit(ItemDesc(&Itm)); }; void Fetch(pkgAcquire::ItemDesc& Itm) { Update = true; if (Itm.Owner->Complete == true) return; AssignItemID(Itm); callback->fetch(ItemDesc(&Itm)); }; void Done(pkgAcquire::ItemDesc& Itm) { Update = true; AssignItemID(Itm); callback->done(ItemDesc(&Itm)); }; void Fail(pkgAcquire::ItemDesc& Itm) { Update = true; AssignItemID(Itm); callback->fail(ItemDesc(&Itm)); }; void Start() { pkgAcquireStatus::Start(); callback->start(); ID = 1; }; void Stop() { pkgAcquireStatus::Stop(); callback->stop(); }; bool Pulse(pkgAcquire* Owner) { Update = true; pkgAcquireStatus::Pulse(Owner); callback->pulse(Owner); return true; }; void set_callback(AcquireProgress* callback) { this->callback = callback; }; u64 current_cps() const { return this->CurrentCPS; } u64 elapsed_time() const { return this->ElapsedTime; } u64 fetched_bytes() const { return this->FetchedBytes; } u64 current_bytes() const { return this->CurrentBytes; } u64 total_bytes() const { return this->TotalBytes; } f64 percent() const { return this->Percent; } AcqTextStatus() : pkgAcquireStatus(), callback(0){}; }; inline UniquePtr> PkgAcquire::uris() const { std::vector list; pkgAcquire::UriIterator I = ptr->UriBegin(); for (; I != ptr->UriEnd(); ++I) { list.push_back(ItemDesc(I.operator->())); } return std::make_unique>(list); } inline UniquePtr> PkgAcquire::workers() const { std::vector list; for (pkgAcquire::Worker* I = ptr->WorkersBegin(); I != 0; I = ptr->WorkerStep(I)) { list.push_back(I); } return std::make_unique>(list); } inline UniquePtr acquire_status() { return std::make_unique(); } inline UniquePtr create_acquire() { return std::make_unique(); } rust-apt-0.8.0/apt-pkg-c/cache.h000064400000000000000000000052511046102023000144120ustar 00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include "rust/cxx.h" // Defines the callbacks code that's generated for progress #include "rust-apt/src/acquire.rs" #include "depcache.h" #include "records.h" #include "types.h" struct PkgCacheFile : public pkgCacheFile { // Maybe we use this if we don't want pin_mut() all over the place in Rust. PkgCacheFile* unconst() const { return const_cast(this); } /// Update the package lists, handle errors and return a Result. void update(AcqTextStatus& progress) const { ListUpdate( progress, *this->unconst()->GetSourceList(), progress.callback->pulse_interval() ); handle_errors(); } // Return a package by name. UniquePtr find_pkg(str name) const { return std::make_unique( this->unconst()->GetPkgCache()->FindPkg(APT::StringView(name.begin(), name.length())) ); } UniquePtr begin() const { return std::make_unique(this->unconst()->GetPkgCache()->PkgBegin()); } /// The priority of the package as shown in `apt policy`. int32_t priority(const VerIterator& ver) const { return this->unconst()->GetPolicy()->GetPriority(ver); } UniquePtr create_depcache() const { return std::make_unique(this->unconst()->GetDepCache()); } UniquePtr create_records() const { return std::make_unique(this->unconst()); } UniquePtr find_index(const PkgFileIterator& file) const { pkgIndexFile* index; if (!this->unconst()->GetSourceList()->FindIndex(file, index)) { _system->FindIndex(file, index); } return std::make_unique(index); } bool get_indexes(const PkgAcquire& fetcher) const { return this->unconst()->GetSourceList()->GetIndexes(fetcher.ptr, true); } PkgCacheFile() : pkgCacheFile(){}; }; inline UniquePtr create_cache(rust::Slice volatile_files) { UniquePtr cache = std::make_unique(); for (auto file_str : volatile_files) { std::string file_string(file_str); // Add the file to the cache. if (!cache->GetSourceList()->AddVolatileFile(file_string)) { _error->Error("%s", ("Couldn't add '" + file_string + "' to the cache.").c_str()); } } // Building the pkg caches can cause an error that might not // Get propagated until you get a pkg which shouldn't have errors. // See https://gitlab.com/volian/rust-apt/-/issues/24 cache->GetPkgCache(); handle_errors(); return cache; } rust-apt-0.8.0/apt-pkg-c/configuration.h000064400000000000000000000066311046102023000162210ustar 00000000000000#pragma once #include #include #include #include #include #include "rust/cxx.h" #include "types.h" /// The configuration pointer is global. /// We do not need to make a new unique one. /// Initialize the apt configuration. void init_config() { pkgInitConfig(*_config); } /// Initialize the apt system. void init_system() { pkgInitSystem(*_config, _system); } struct ConfigTree { const Configuration::Item* ptr; bool end() const { return ptr == 0; } UniquePtr raw_next() const { return std::make_unique(ptr->Next); } UniquePtr unique() const { return std::make_unique(ptr); } UniquePtr parent() const { return std::make_unique(ptr->Parent); } UniquePtr child() const { return std::make_unique(ptr->Child); } String tag() const { return ptr->Tag; } String value() const { return ptr->Value; } ConfigTree(const Configuration::Item* base) : ptr(base){}; }; UniquePtr root_tree() { return std::make_unique(_config->Tree(0)); } UniquePtr tree(String key) { return std::make_unique(_config->Tree(key.c_str())); } /// Returns a String dump of configuration options separated by `\n` String dump() { std::stringstream String_stream; _config->Dump(String_stream); return String_stream.str(); } /// Find a key and return it's value as a String. String find(String key, String default_value) { return _config->Find(key.c_str(), default_value.c_str()); } /// Find a file and return it's value as a String. String find_file(String key, String default_value) { return _config->FindFile(key.c_str(), default_value.c_str()); } /// Find a directory and return it's value as a String. String find_dir(String key, String default_value) { return _config->FindDir(key.c_str(), default_value.c_str()); } /// Same as find, but for boolean values. bool find_bool(String key, bool default_value) { return _config->FindB(key.c_str(), default_value); } /// Same as find, but for i32 values. int find_int(String key, i32 default_value) { return _config->FindI(key.c_str(), default_value); } /// Return a vector for an Apt configuration list. Vec find_vector(String key) { std::vector vector = _config->FindVector(key.c_str()); Vec rust_vector; for (const std::string& str : vector) { rust_vector.push_back(str); } return rust_vector; } /// Return a vector of supported architectures on this system. /// The main architecture is the first in the list. Vec get_architectures() { Vec rust_vector; for (const std::string& str : APT::Configuration::getArchitectures()) { rust_vector.push_back(str); } return rust_vector; } /// Set the given key to the specified value. void set(String key, String value) { _config->Set(key.c_str(), value.c_str()); } /// Simply check if a key exists. bool exists(String key) { return _config->Exists(key.c_str()); } /// Clears all values from a key. /// /// If the value is a list, the entire list is cleared. /// If you need to clear 1 value from a list see `clear_value` void clear(String key) { _config->Clear(key.c_str()); } /// Clear all configurations. void clear_all() { _config->Clear(); } /// Clear a single value from a list. void clear_value(String key, String value) { _config->Clear(key.c_str(), value.c_str()); } rust-apt-0.8.0/apt-pkg-c/depcache.h000064400000000000000000000165031046102023000151050ustar 00000000000000#pragma once #include #include #include #include "package.h" #include "util.h" #include "progress.h" using ActionGroup = pkgDepCache::ActionGroup; struct PkgDepCache { pkgDepCache* ptr; // Maybe we use this if we don't want pin_mut() all over the place in Rust. PkgDepCache* unconst() const { return const_cast(this); } UniquePtr action_group() const { return std::make_unique(*ptr); } bool is_upgradable(const PkgIterator& pkg) const { return (*ptr)[pkg].Upgradable(); } bool fix_broken() const { return pkgFixBroken(*ptr); } /// Is the Package auto installed? Packages marked as auto installed are usually dependencies. bool is_auto_installed(const PkgIterator& pkg) const { pkgDepCache::StateCache state = (*ptr)[pkg]; return state.Flags & pkgCache::Flag::Auto; } /// Is the Package able to be auto removed? bool is_garbage(const PkgIterator& pkg) const { return (*ptr)[pkg].Garbage; } /// Is the Package marked for install? bool marked_install(const PkgIterator& pkg) const { return (*ptr)[pkg].NewInstall(); } /// Is the Package marked for upgrade? bool marked_upgrade(const PkgIterator& pkg) const { return (*ptr)[pkg].Upgrade(); } /// Is the Package marked to be purged? bool marked_purge(const PkgIterator& pkg) const { return (*ptr)[pkg].Purge(); } /// Is the Package marked for removal? bool marked_delete(const PkgIterator& pkg) const { return (*ptr)[pkg].Delete(); } /// Is the Package marked for keep? bool marked_keep(const PkgIterator& pkg) const { return (*ptr)[pkg].Keep(); } /// Is the Package marked for downgrade? bool marked_downgrade(const PkgIterator& pkg) const { return (*ptr)[pkg].Downgrade(); } /// Is the Package marked for reinstall? bool marked_reinstall(const PkgIterator& pkg) const { return (*ptr)[pkg].ReInstall(); } /// Mark a package as automatically installed. /// /// MarkAuto = true will mark the package as automatically installed and false will mark it as /// manual void mark_auto(const PkgIterator& pkg, bool mark_auto) const { ptr->MarkAuto(pkg, mark_auto); } /// Mark a package for keep. /// /// This means that the package will not be changed from its current version. /// This will not stop a reinstall, but will stop removal, upgrades and downgrades /// /// Soft: /// True = will mark for keep /// False = will unmark for keep /// /// We don't believe that there is any reason to unmark packages for keep. /// If someone has a reason, and would like it implemented, please put in a feature request. /// /// FromUser: /// This is only ever True in apt underneath `MarkInstall`, /// and the bool is passed from `MarkInstall` itselfconst . /// I don't believe anyone needs access to this boolconst . /// /// Depth: /// Recursion tracker and is only used for printing Debug statements. /// No one needs access to this. Additionally Depth cannot be over 3000. bool mark_keep(const PkgIterator& pkg) const { return ptr->MarkKeep(pkg, false, false); } /// Mark a package for removal. /// /// MarkPurge: /// True the package will be purged. /// False the package will not be purged. /// /// Depth: /// Recursion tracker and is only used for printing Debug statements. /// No one needs access to this. Additionally Depth cannot be over 3000. /// /// FromUser: /// True if the user requested this. /// False the User did not request this. /// /// Typically You would always use from user. /// False here appears to be more of an implementation detail. bool mark_delete(const PkgIterator& pkg, bool purge) const { return ptr->MarkDelete(pkg, purge); } /// Mark a package for installation. /// /// AutoInst: true = Auto Install dependencies of the package. /// /// FromUser: true = Mark the package as installed from the User. /// /// Depth: /// Recursion tracker and is only used for printing Debug statements. /// No one needs access to this. Additionally Depth cannot be over 3000. /// /// ForceImportantDeps = TODO: Study what this does. bool mark_install(const PkgIterator& pkg, bool auto_inst, bool from_user) const { return ptr->MarkInstall(pkg, auto_inst, 0, from_user, false); } /// Set a version to be the candidate of it's package. void set_candidate_version(const VerIterator& ver) const { ptr->SetCandidateVersion(ver); } /// Return the candidate version of the package. UniquePtr candidate_version(const PkgIterator& pkg) const { return std::make_unique(ptr->GetCandidateVersion(pkg)); } /// Returns the installed version if it exists. /// * If a version is marked for install this will return the version to be /// installed. /// * If an installed package is marked for removal, this will return [`None`]. UniquePtr install_version(const PkgIterator& pkg) const { pkgCache& cache = ptr->GetCache(); return std::make_unique((*ptr)[pkg].InstVerIter(cache)); } /// Returns the state of the dependency as u8 u8 dep_state(const DepIterator& dep) const { return (*ptr)[dep]; } /// Checks if the dependency is important. /// /// Depends, PreDepends, Conflicts, Obsoletes, Breaks /// will return [true]. /// /// Suggests, Recommends will return [true] if they are /// configured to be installed. bool is_important_dep(const DepIterator& dep) const { return ptr->IsImportantDep(dep); } /// Mark a package for reinstallation /// /// To: /// True = The package will be marked for reinstall /// False = The package will be unmarked for reinstall void mark_reinstall(const PkgIterator& pkg, bool reinstall) const { ptr->SetReInstall(pkg, reinstall); } /// Is the installed Package broken? bool is_now_broken(const PkgIterator& pkg) const { return (*ptr)[pkg].NowBroken(); } /// Is the Package to be installed broken? bool is_inst_broken(const PkgIterator& pkg) const { return (*ptr)[pkg].InstBroken(); } /// The number of packages marked for installation. u32 install_count() const { return ptr->InstCount(); } /// The number of packages marked for removal. u32 delete_count() const { return ptr->DelCount(); } /// The number of packages marked for keep. u32 keep_count() const { return ptr->KeepCount(); } /// The number of packages with broken dependencies in the cache. u32 broken_count() const { return ptr->BrokenCount(); } /// The size of all packages to be downloaded. u64 download_size() const { return ptr->DebSize(); } /// The amount of space required for installing/removing the packages," /// /// i.e. the Installed-Size of all packages marked for installation" /// minus the Installed-Size of all packages for removal." i64 disk_size() const { return ptr->UsrSize(); } /// Perform a Full Upgrade. Remove and install new packages if necessary. void upgrade(OperationProgress& callback, int upgrade_mode) const { OpProgressWrapper op_progress(callback); // It is currently unclear if we should return a bool here. I think Result should be fine. APT::Upgrade::Upgrade(*ptr, upgrade_mode, &op_progress); handle_errors(); } /// Clear any marked changes in the DepCache. void init(OperationProgress& callback) const { OpProgressWrapper op_progress(callback); ptr->Init(&op_progress); // pkgApplyStatus(*cache->GetDepCache()); handle_errors(); } PkgDepCache(pkgDepCache* DepCache) : ptr(DepCache){}; }; rust-apt-0.8.0/apt-pkg-c/error.cc000064400000000000000000000004371046102023000146370ustar 00000000000000#include "rust-apt/apt-pkg-c/error.h" #include #include "types.h" Vec get_all() noexcept { Vec list; while (!_error->empty()) { std::string msg; bool type = _error->PopMessage(msg); list.push_back(AptError{type, msg}); } return list; } rust-apt-0.8.0/apt-pkg-c/error.h000064400000000000000000000005231046102023000144750ustar 00000000000000#pragma once #include #include "rust-apt/src/error.rs" #include "rust/cxx.h" #include "types.h" /// Handle the situation where a string is null and return a result to rust inline bool pending_error() { return _error->PendingError(); } inline bool empty() { return _error->empty(); } Vec get_all() noexcept; rust-apt-0.8.0/apt-pkg-c/package.h000064400000000000000000000154401046102023000147430ustar 00000000000000#pragma once #include #include #include #include "util.h" #include "types.h" struct VerIterator; struct PkgIterator; struct DepIterator : public pkgCache::DepIterator { void raw_next() { (*this)++; } UniquePtr unique() const { return std::make_unique(*this); } u8 dep_type() const { return (*this)->Type; } str comp_type() const { return handle_str(this->CompType()); } str target_ver() const { return handle_str(this->TargetVer()); } inline bool or_dep() const { return ((*this)->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or; } UniquePtr parent_pkg() const; UniquePtr target_pkg() const; UniquePtr parent_ver() const; UniquePtr> all_targets() const; DepIterator(const pkgCache::DepIterator& base) : pkgCache::DepIterator(base){}; }; struct PrvIterator : public pkgCache::PrvIterator { void raw_next() { (*this)++; } str name() const { return this->Name(); } str version_str() const { return handle_str(this->ProvideVersion()); } UniquePtr target_pkg() const; UniquePtr target_ver() const; UniquePtr unique() const { return std::make_unique(*this); } PrvIterator(const pkgCache::PrvIterator& base) : pkgCache::PrvIterator(base){}; }; struct PkgFileIterator : public pkgCache::PkgFileIterator { void raw_next() { (*this)++; } str filename() const { return handle_str(this->FileName()); } str archive() const { return handle_str(this->Archive()); } str origin() const { return handle_str(this->Origin()); } str codename() const { return handle_str(this->Codename()); } str label() const { return handle_str(this->Label()); } str site() const { return handle_str(this->Site()); } str component() const { return handle_str(this->Component()); } str arch() const { return handle_str(this->Architecture()); } str index_type() const { return handle_str(this->IndexType()); } bool is_downloadable() const { return !this->Flagged(pkgCache::Flag::NotSource); } UniquePtr unique() const { return std::make_unique(*this); } PkgFileIterator(const pkgCache::PkgFileIterator& base) : pkgCache::PkgFileIterator(base){}; }; struct VerFileIterator : public pkgCache::VerFileIterator { void raw_next() { (*this)++; } UniquePtr unique() const { return std::make_unique(*this); } UniquePtr package_file() const { return std::make_unique(this->File()); }; VerFileIterator(const pkgCache::VerFileIterator& base) : pkgCache::VerFileIterator(base){}; }; struct DescIterator : public pkgCache::DescIterator { void raw_next() { (*this)++; } UniquePtr unique() const { return std::make_unique(*this); } DescIterator(const pkgCache::DescIterator& base) : pkgCache::DescIterator(base){}; }; struct VerIterator : public pkgCache::VerIterator { void raw_next() { (*this)++; } str version() const { return this->VerStr(); } str arch() const { return this->Arch(); } str section() const { return handle_str(this->Section()); } str priority_str() const { return handle_str(this->PriorityType()); } str source_name() const { return this->SourcePkgName(); } str source_version() const { return this->SourceVerStr(); } u64 size() const { return (*this)->Size; } u64 installed_size() const { return (*this)->InstalledSize; } // TODO: Move this into rust? bool is_installed() const { return this->ParentPkg().CurrentVer() == *this; } UniquePtr parent_pkg() const; // This is for backend records lookups. UniquePtr translated_desc() const { return std::make_unique(this->TranslatedDescription()); } // This is for backend records lookups. // You go through here to get the package files. UniquePtr version_files() const { return std::make_unique(this->FileList()); } UniquePtr depends() const { return std::make_unique(this->DependsList()); } UniquePtr provides() const { return std::make_unique(this->ProvidesList()); } UniquePtr unique() const { return std::make_unique(*this); } VerIterator(const pkgCache::VerIterator& base) : pkgCache::VerIterator(base){}; }; struct PkgIterator : public pkgCache::PkgIterator { void raw_next() { (*this)++; } str name() const { return this->Name(); } str arch() const { return this->Arch(); } String fullname(bool Pretty) const { return this->FullName(Pretty); } u8 current_state() const { return (*this)->CurrentState; } u8 inst_state() const { return (*this)->InstState; } u8 selected_state() const { return (*this)->SelectedState; } /// True if the package is essential. bool is_essential() const { return ((*this)->Flags & pkgCache::Flag::Essential) != 0; } UniquePtr current_version() const { return std::make_unique(this->CurrentVer()); } UniquePtr versions() const { return std::make_unique(this->VersionList()); } UniquePtr provides() const { return std::make_unique(this->ProvidesList()); } UniquePtr rdepends() const { return std::make_unique(this->RevDependsList()); } UniquePtr unique() const { return std::make_unique(*this); } PkgIterator(const pkgCache::PkgIterator& base) : pkgCache::PkgIterator(base){}; }; inline UniquePtr PrvIterator::target_pkg() const { return std::make_unique(this->OwnerPkg()); } inline UniquePtr PrvIterator::target_ver() const { return std::make_unique(this->OwnerVer()); } inline UniquePtr DepIterator::parent_pkg() const { return std::make_unique(this->ParentPkg()); } inline UniquePtr DepIterator::parent_ver() const { return std::make_unique(this->ParentVer()); } inline UniquePtr DepIterator::target_pkg() const { return std::make_unique(this->TargetPkg()); } inline UniquePtr> DepIterator::all_targets() const { // pkgPrioSortList for sorting by priority? // // The version list returned is not a VerIterator. // They are the lowest level Version structs. We need to iter these // Convert them into our VerIterator, and then we can handle that in rust. UniquePtr VList(this->AllTargets()); std::vector list; for (pkgCache::Version** I = VList.get(); *I != 0; ++I) { list.push_back(VerIterator(pkgCache::VerIterator(*this->Cache(), *I))); } return std::make_unique>(list); } inline UniquePtr VerIterator::parent_pkg() const { return std::make_unique(this->ParentPkg()); } rust-apt-0.8.0/apt-pkg-c/pkgmanager.h000064400000000000000000000074011046102023000154620ustar 00000000000000#pragma once #include #include #include #include #include #include #include #include #include "cache.h" #include "rust-apt/src/progress.rs" struct PackageManager { pkgPackageManager mutable* pkgmanager; void get_archives( const PkgCacheFile& cache, const PkgRecords& records, AcqTextStatus& archive_progress ) const { pkgAcquire acquire(&archive_progress); // We probably need to let the user set their own pkgSourcePkgCacheFileList, // but there hasn't been a need to expose such in the Rust interface // yet. pkgSourceList sourcelist = *cache->GetSourceList(); if (!pkgmanager->GetArchives( &acquire, cache.unconst()->GetSourceList(), &records.records )) { handle_errors(); throw std::runtime_error( "Internal Issue with rust-apt in pkgmanager_get_archives." " Please report this as an issue." ); } pkgAcquire::RunResult result = acquire.Run(archive_progress.callback->pulse_interval()); if (result != pkgAcquire::Continue) { // The other variants are either Failed or Cancelled // Failed will always have an error for us to handle // It's unsure if Cancelled would even require a bool // I believe this may be a Keyboard Interrupt situation handle_errors(); } } void do_install(InstallProgress& callback) const { PackageManagerWrapper install_progress(callback); pkgPackageManager::OrderResult res = pkgmanager->DoInstall(&install_progress); if (res == pkgPackageManager::OrderResult::Completed) { return; } else if (res == pkgPackageManager::OrderResult::Failed) { handle_errors(); throw std::runtime_error( "Internal Issue with rust-apt in pkgmanager_do_install." " DoInstall has failed but there was no error from apt." " Please report this as an issue." ); } else if (res == pkgPackageManager::OrderResult::Incomplete) { // It's not clear that there would be any apt errors here, // But we'll try anyway. This is believed to be only for media swapping handle_errors(); throw std::runtime_error( "Internal Issue with rust-apt in pkgmanager_do_install." " DoInstall returned Incomplete, media swaps are unsupported." " Please request media swapping as a feature." ); } else { // If for whatever reason we manage to make it here (We shouldn't) // Attempt to handle any apt errors // And then fallback with a message to report with the result code. handle_errors(); throw std::runtime_error( "Internal Issue with rust-apt in pkgmanager_do_install." " Please report this as an issue. OrderResult: " + res ); } } PackageManager(pkgDepCache* depcache) : pkgmanager(_system->CreatePM(depcache)){}; }; struct ProblemResolver { pkgProblemResolver mutable resolver; /// Mark a package as protected, i.e. don't let its installation/removal state change when /// modifying packages during resolution. void protect(const PkgIterator& pkg) const { resolver.Protect(pkg); } /// Try to resolve dependency problems by marking packages for installation and removal. void resolve(bool fix_broken, OperationProgress& callback) const { OpProgressWrapper op_progress(callback); resolver.Resolve(fix_broken, &op_progress); handle_errors(); } ProblemResolver(pkgDepCache* depcache) : resolver(depcache){}; }; /// Create the problem resolver. UniquePtr create_problem_resolver(const PkgDepCache& cache) { return std::make_unique(cache.ptr); } UniquePtr create_pkgmanager(const PkgDepCache& cache) { // Package Manager needs the DepCache initialized or else invalid memory reference. return std::make_unique(cache.ptr); } rust-apt-0.8.0/apt-pkg-c/progress.h000064400000000000000000000017761046102023000152230ustar 00000000000000#pragma once #include #include #include "rust/cxx.h" #include "rust-apt/src/progress.rs" struct OpProgressWrapper : public OpProgress { /// Callback to the rust struct OperationProgress& callback; void Update() { callback.update(Op, Percent); }; void Done() { callback.done(); }; OpProgressWrapper(OperationProgress& callback) : callback(callback){}; }; struct PackageManagerWrapper : public APT::Progress::PackageManagerFancy { /// Callback to the rust struct InstallProgress& callback; bool StatusChanged( std::string pkgname, unsigned int steps_done, unsigned int total_steps, std::string action ) { callback.status_changed(pkgname, steps_done, total_steps, action); return true; }; void Error( std::string pkgname, unsigned int steps_done, unsigned int total_steps, std::string error ) { callback.error(pkgname, steps_done, total_steps, error); }; PackageManagerWrapper(InstallProgress& callback) : callback(callback){}; }; rust-apt-0.8.0/apt-pkg-c/records.h000064400000000000000000000037701046102023000150140ustar 00000000000000#pragma once #include #include #include #include #include #include #include #include "rust/cxx.h" #include "package.h" #include "types.h" struct IndexFile { pkgIndexFile* ptr; String archive_uri(str filename) const { return ptr->ArchiveURI(std::string(filename)); } bool is_trusted() const { return ptr->IsTrusted(); } IndexFile(pkgIndexFile* file) : ptr(file){}; }; struct Parser { pkgRecords::Parser& ptr; String short_desc() const { return handle_string(ptr.ShortDesc()); } String long_desc() const { return handle_string(ptr.LongDesc()); } String filename() const { return ptr.FileName(); } // TODO: Maybe look into this more if there is time. I was trying to save an allocation // ptr.RecordField(field.begin()) // This will work with String.as_str() // cache.records().get_field(&"Maintainer".to_string()) // This will not work with just "Maintainer" string literal /// Return the Source package version String. String get_field(String field) const { return handle_string(ptr.RecordField(field.c_str())); } // TODO: Lets Go Ahead and Bind HashStrings while we're here ffs /// Find the hash of a Version. Returns Result if there is no hash. String hash_find(String hash_type) const { auto hashes = ptr.Hashes(); auto hash = hashes.find(hash_type.c_str()); if (hash == NULL) { throw std::runtime_error("Hash Not Found"); } return handle_string(hash->HashValue()); } Parser(pkgRecords::Parser& parser) : ptr(parser){}; }; struct PkgRecords { pkgRecords mutable records; UniquePtr ver_lookup(const VerFileIterator& file) const { return std::make_unique(records.Lookup(file)); } /// Moves the Records into the correct place. UniquePtr desc_lookup(const DescIterator& desc) const { return std::make_unique(records.Lookup(desc.FileList())); } PkgRecords(pkgCacheFile* cache) : records(*cache->GetPkgCache()){}; }; rust-apt-0.8.0/apt-pkg-c/types.h000064400000000000000000000004071046102023000145110ustar 00000000000000#pragma once #include #include #include "rust/cxx.h" using namespace rust; template using UniquePtr = std::unique_ptr; // Forward declarations for progress.rs struct ItemDesc; struct PkgAcquire; struct AcqWorker; rust-apt-0.8.0/apt-pkg-c/util.h000064400000000000000000000046401046102023000143250ustar 00000000000000#pragma once #include #include #include #include #include #include #include #include "rust/cxx.h" #include "types.h" /// Internal Helper Functions. /// Do not expose these on the Rust side - only for use on the C++ side. /// /// Handle any apt errors and return result to rust. inline void handle_errors() { // !_error->empty() will cause a Result when there are only warnings // Instead use PendingErr() // Actual handling of the errors is done in rust if (_error->PendingError()) { throw std::runtime_error("convert to AptErrors"); } } /// Handle the situation where a string is null and return a result to rust inline const char* handle_str(const char* str) { if (!str || !strcmp(str, "")) { throw std::runtime_error("&str doesn't exist"); } return str; } /// Check if a string exists and return a Result to rust inline String handle_string(std::string string) { if (string.empty()) { throw std::runtime_error("String doesn't exist"); } return string; } ////////////////////////////////// /// End Internal Helper Functions. ////////////////////////////////// /// Compare two package version strings. inline i32 cmp_versions(str ver1, str ver2) { if (!_system) { pkgInitSystem(*_config, _system); } const char* end1 = ver1.begin() + strlen(ver1.begin()); const char* end2 = ver2.begin() + strlen(ver2.begin()); return _system->VS->DoCmpVersion(ver1.begin(), end1, ver2.begin(), end2); } /// Return an APT-styled progress bar (`[#### ]`). inline String get_apt_progress_string(f32 percent, u32 output_width) { return APT::Progress::PackageManagerFancy::GetTextProgressStr(percent, output_width); } /// Lock the APT lockfile. inline void apt_lock() { _system->Lock(); handle_errors(); } /// Unlock the APT lockfile. inline void apt_unlock() { // This can only throw an error that says "Not Locked" // By setting NoErrors true, this will return false instead // This is largely irrelevant and will be a void function _system->UnLock(true); } /// Lock the Dpkg lockfile. inline void apt_lock_inner() { _system->LockInner(); handle_errors(); } /// Unlock the Dpkg lockfile. inline void apt_unlock_inner() { // UnlockInner can not throw an error and always returns true. _system->UnLockInner(); } /// Check if the lockfile is locked. inline bool apt_is_locked() { return _system->IsLocked(); } rust-apt-0.8.0/build.rs000064400000000000000000000020221046102023000130510ustar 00000000000000fn main() { let source_files = vec![ "src/cache.rs", "src/progress.rs", "src/config.rs", "src/util.rs", "src/records.rs", "src/depcache.rs", "src/pkgmanager.rs", "src/error.rs", "src/acquire.rs", "src/iterators/package.rs", "src/iterators/version.rs", "src/iterators/dependency.rs", "src/iterators/provider.rs", "src/iterators/files.rs", ]; let mut cc_files = vec!["apt-pkg-c/error.cc"]; cxx_build::bridges(&source_files) .files(&cc_files) .flag_if_supported("-std=c++14") .compile("rust-apt"); println!("cargo:rustc-link-lib=apt-pkg"); for file in source_files { println!("cargo:rerun-if-changed={file}") } cc_files.extend_from_slice(&[ "apt-pkg-c/cache.h", "apt-pkg-c/progress.h", "apt-pkg-c/configuration.h", "apt-pkg-c/util.h", "apt-pkg-c/records.h", "apt-pkg-c/depcache.h", "apt-pkg-c/package.h", "apt-pkg-c/pkgmanager.h", "apt-pkg-c/error.h", "apt-pkg-c/types.h", "apt-pkg-c/acquire.h", ]); for file in cc_files { println!("cargo:rerun-if-changed={file}") } } rust-apt-0.8.0/justfile000064400000000000000000000053331046102023000131640ustar 00000000000000#!/usr/bin/env just --justfile [private] default: @just --list # Setup the development environment. setup-dev: # Sudo is required to install packages with apt @echo Installing required packages from apt @sudo apt-get install bear valgrind libapt-pkg-dev dpkg-dev clang-format codespell -y @just setup-toolchain [private] @setup-toolchain: #!/bin/sh set -e echo Setting up toolchains rustup toolchain install nightly rustup toolchain install stable echo Installing nightly \`rustfmt\` rustup toolchain install nightly --component rustfmt echo Nightly \`rustfmt\` successfully installed! echo Cleaning and building c++ compile commands cargo clean bear -- cargo build echo Development environment installed successfully! # Run checks check: spellcheck clippy @cargo +nightly fmt --check @echo Checks were successful! # Remove generated artifacts clean: @cargo clean @echo Done! # Build the project build: @cargo build @echo Project successfully built! # Generate docs doc: @cargo doc @echo Documentation successfully generated! # Create the debs required for tests [private] @create-test-debs: #!/bin/sh set -e cd tests/files/cache rm -f *.deb Packages* for pkg in *; do dpkg-deb --build --nocheck "${pkg}"; done dpkg-scanpackages --multiversion . /dev/null > Packages # Create an empty garbage package to make sure it fails echo "\n" > pkg.deb # Run all tests except for root test +ARGS="": @just create-test-debs @cargo test --no-fail-fast -- --test-threads 1 --skip root --skip update {{ARGS}} # Run only the root tests. Sudo password required! @test-root +ARGS="": #!/bin/sh set -e just create-test-debs sudo -E /home/${USER}/.cargo/bin/cargo \ test \ --test root \ -- --test-threads 1 {{ARGS}} # Run leak tests. Requires root @leak: #!/bin/sh set -e just create-test-debs cargo test --no-run test_binaries=$( \ find target/debug/deps -executable -type f \ -printf "%T@ %p\n" | sort -nr | awk '{print $2}' \ | grep -v ".so" ) for test in $test_binaries; do # Sudo is needed to memleak the root tests sudo valgrind --leak-check=full -- "${test}" --test-threads 1 done # Lint the codebase clippy +ARGS="": @cargo clippy --all-targets --all-features --workspace -- --deny warnings {{ARGS}} @echo Lint successful! # Format the codebase @fmt +ARGS="": #!/bin/sh set -e cargo +nightly fmt --all -- {{ARGS}} cd apt-pkg-c clang-format -i * echo Codebase formatted successfully! # Spellcheck the codebase spellcheck +ARGS="": @codespell --skip target --skip .git --skip .cargo --builtin clear,rare,informal,code --ignore-words-list mut,crate {{ARGS}} @echo Spellings look good! alias b := build alias c := check alias d := doc alias l := leak alias t := test alias r := test-root rust-apt-0.8.0/netlify.toml000064400000000000000000000015511046102023000137610ustar 00000000000000[build] publish = "target/doc" # Netlify doesn't allow us to download packages via APT, which is needed since # we need libraries from `libapt-pkg-dev`. We get around this by manually # unpacking the deb and including the needed library's path in # CPLUS_INCLUDE_PATH. command = """ set -ex packages=('libapt-pkg-dev::https://mirrors.kernel.org/ubuntu/pool/main/a/apt/libapt-pkg-dev_2.4.5_amd64.deb') export CPLUS_INCLUDE_PATH="${PWD}/usr/include" for pkg in "${packages[@]}"; do pkgname="$(echo "${pkg}" | sed 's|::.*||')" url="$(echo "${pkg}" | sed 's|.*::||')" wget "${url}" ar xf "${pkgname}"_*.deb zstd -df data.tar.zst tar -xf data.tar done rustup toolchain install stable cargo doc --no-deps # Redirect the root URL to the rust_apt docs. echo '' > target/doc/index.html """ rust-apt-0.8.0/rustfmt.toml000064400000000000000000000007011046102023000140070ustar 00000000000000version = "Two" hard_tabs = true wrap_comments = true format_strings = true fn_single_line = true empty_item_single_line = true single_line_if_else_max_width = 80 normalize_doc_attributes = true normalize_comments = true reorder_impl_items = true use_field_init_shorthand = true group_imports = "StdExternalCrate" imports_granularity = "Module" condense_wildcard_suffixes = true match_block_trailing_comma = true format_code_in_doc_comments = false rust-apt-0.8.0/src/acquire.rs000064400000000000000000000136171046102023000142060ustar 00000000000000//! File Acquiration //! //! The following was taken from libapt-pkg documentation. //! //! This module contains the Acquire system. It is responsible for bringing //! files into the local pathname space. It deals with URIs for files and //! URI handlers responsible for downloading or finding the URIs. //! //! Each file to download is represented by an Acquire::Item class subclassed //! into a specialization. The Item class can add itself to several URI //! acquire queues each prioritized by the download scheduler. When the //! system is run the proper URI handlers are spawned and the acquire //! queues are fed into the handlers by the schedular until the queues are //! empty. This allows for an Item to be downloaded from an alternate source //! if the first try turns out to fail. It also allows concurrent downloading //! of multiple items from multiple sources as well as dynamic balancing //! of load between the sources. //! //! Scheduling of downloads is done on a first ask first get basis. This //! preserves the order of the download as much as possible. And means the //! fastest source will tend to process the largest number of files. //! //! Internal methods and queues for performing gzip decompression, //! md5sum hashing and file copying are provided to allow items to apply //! a number of transformations to the data files they are working with. #[cxx::bridge] pub(crate) mod raw { #[repr(u32)] enum ItemState { StatIdle, StatFetching, StatDone, StatError, StatAuthError, StatTransientNetworkError, } unsafe extern "C++" { include!("rust-apt/apt-pkg-c/acquire.h"); type AcqTextStatus; type PkgAcquire; type AcqWorker; type ItemDesc; type Item; type ItemState; type AcquireProgress<'a> = crate::progress::AcquireProgress<'a>; /// A client-supplied unique identifier. /// /// APT progress reporting will store an ID as shown in "Get:42…". pub fn id(self: &Item) -> u32; /// `true`` if entire object has been successfully fetched. pub fn complete(self: &Item) -> bool; /// The size of the object to fetch. pub fn file_size(self: &Item) -> u64; /// Get the URI of the item. pub fn uri(self: &Item) -> String; /// The Destination file path. pub fn dest_file(self: &Item) -> String; /// The current status of this item. pub fn status(self: &Item) -> ItemState; /// Contains a textual description of the error encountered /// if ItemState is StatError or StatAuthError. pub fn error_text(self: &Item) -> String; /// Contains the name of the subprocess that is operating on this item. /// /// For instance, "store", "gzip", "rred", "gpgv". pub fn active_subprocess(self: &Item) -> String; /// The acquire process with which this item is associated. pub fn owner(self: &Item) -> UniquePtr; /// URI from which to download this item. pub fn uri(self: &ItemDesc) -> String; /// Description of this item. pub fn description(self: &ItemDesc) -> String; /// Shorter description of this item. pub fn short_desc(self: &ItemDesc) -> String; /// Underlying item which is to be downloaded. pub fn owner(self: &ItemDesc) -> UniquePtr; /// # Safety /// /// Before you do anything with AcqTextStatus you must set the callback. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn acquire_status() -> UniquePtr; /// # Safety /// /// Setting the Callback requires a raw mutable pointer to /// AcquireProgress. /// /// AcquireProgress must not be moved in memory or will segfault when /// AcqTextStatus calls it. unsafe fn set_callback(self: Pin<&mut AcqTextStatus>, progress: *mut AcquireProgress); /// The number of bytes fetched as of the most recent call /// to pkgAcquireStatus::Pulse, including local items. pub fn current_cps(self: &AcqTextStatus) -> u64; /// The amount of time that has elapsed since the download started. pub fn elapsed_time(self: &AcqTextStatus) -> u64; /// The total number of bytes accounted for by items that were /// successfully fetched. pub fn fetched_bytes(self: &AcqTextStatus) -> u64; /// The number of bytes fetched as of the most recent call to /// pkgAcquireStatus::Pulse, including local items. pub fn current_bytes(self: &AcqTextStatus) -> u64; /// The total number of bytes that need to be fetched. /// /// This member is inaccurate, as new items might be enqueued while the /// download is in progress! pub fn total_bytes(self: &AcqTextStatus) -> u64; /// The estimated percentage of the download (0-100) pub fn percent(self: &AcqTextStatus) -> f64; /// The most recent status string received from the subprocess. pub fn status(self: &AcqWorker) -> String; /// The queue entry that is currently being downloaded. pub fn item(self: &AcqWorker) -> Result>; /// How many bytes of the file have been downloaded. /// /// Zero if the current progress of the file cannot be determined. pub fn current_size(self: &AcqWorker) -> u64; /// The total number of bytes to be downloaded. /// /// Zero if the total size of the final is unknown. pub fn total_size(self: &AcqWorker) -> u64; // TODO: This should probably be unsafe, but not sure at the moment how to // handle it I guess we would need to wrap PkgAcquire and AcqWorker so they can // have proper lifetimes? /// CxxVector of active workers pub fn workers(self: &PkgAcquire) -> UniquePtr>; /// Get the ItemDesc that contain the source list URIs /// /// # Safety /// /// You must not let these out of scope of PkgAcquire. SIGABRT. unsafe fn uris(self: &PkgAcquire) -> UniquePtr>; // It isn't clear that create_acquire should be unsafe. // It doesn't segfault if you drop the Cache. // But it does return a UniquePtr so I assume it is unsafe. /// Create PkgAcquire. /// /// # Safety /// /// The returned UniquePtr cannot outlive the cache. unsafe fn create_acquire() -> UniquePtr; } } rust-apt-0.8.0/src/cache.rs000064400000000000000000000504121046102023000136120ustar 00000000000000//! Contains Cache related structs. use std::cell::OnceCell; use std::fs; use std::path::Path; use cxx::{Exception, UniquePtr}; use crate::config::{init_config_system, Config}; use crate::depcache::DepCache; use crate::error::AptErrors; use crate::progress::{AcquireProgress, InstallProgress, OperationProgress}; use crate::raw::{ create_cache, create_pkgmanager, create_problem_resolver, IntoRawIter, IterPkgIterator, PackageManager, PkgCacheFile, PkgIterator, ProblemResolver, }; use crate::records::PackageRecords; use crate::util::{apt_lock, apt_unlock, apt_unlock_inner}; use crate::Package; /// Selection of Upgrade type #[repr(i32)] pub enum Upgrade { /// Upgrade will Install new and Remove packages in addition to /// upgrading them. /// /// Equivalent to `apt full-upgrade` and `apt-get dist-upgrade`. FullUpgrade = 0, /// Upgrade will Install new but not Remove packages. /// /// Equivalent to `apt upgrade`. Upgrade = 1, /// Upgrade will Not Install new or Remove packages. /// /// Equivalent to `apt-get upgrade`. SafeUpgrade = 3, } /// Selection of how to sort enum Sort { /// Disable the sort method. Disable, /// Enable the sort method. Enable, /// Reverse the sort method. Reverse, } /// Determines how to sort packages from the Cache. pub struct PackageSort { names: bool, upgradable: Sort, virtual_pkgs: Sort, installed: Sort, auto_installed: Sort, auto_removable: Sort, } impl Default for PackageSort { fn default() -> PackageSort { PackageSort { names: false, upgradable: Sort::Disable, virtual_pkgs: Sort::Disable, installed: Sort::Disable, auto_installed: Sort::Disable, auto_removable: Sort::Disable, } } } impl PackageSort { /// Packages will be sorted by their names a -> z. pub fn names(mut self) -> Self { self.names = true; self } /// Only packages that are upgradable will be included. pub fn upgradable(mut self) -> Self { self.upgradable = Sort::Enable; self } /// Only packages that are NOT upgradable will be included. pub fn not_upgradable(mut self) -> Self { self.upgradable = Sort::Reverse; self } /// Virtual packages will be included. pub fn include_virtual(mut self) -> Self { self.virtual_pkgs = Sort::Enable; self } /// Only Virtual packages will be included. pub fn only_virtual(mut self) -> Self { self.virtual_pkgs = Sort::Reverse; self } /// Only packages that are installed will be included. pub fn installed(mut self) -> Self { self.installed = Sort::Enable; self } /// Only packages that are NOT installed will be included. pub fn not_installed(mut self) -> Self { self.installed = Sort::Reverse; self } /// Only packages that are auto installed will be included. pub fn auto_installed(mut self) -> Self { self.auto_installed = Sort::Enable; self } /// Only packages that are manually installed will be included. pub fn manually_installed(mut self) -> Self { self.auto_installed = Sort::Reverse; self } /// Only packages that are auto removable will be included. pub fn auto_removable(mut self) -> Self { self.auto_removable = Sort::Enable; self } /// Only packages that are NOT auto removable will be included. pub fn not_auto_removable(mut self) -> Self { self.auto_removable = Sort::Reverse; self } } /// The main struct for accessing any and all `apt` data. pub struct Cache { pub(crate) ptr: UniquePtr, depcache: OnceCell, records: OnceCell, pkgmanager: OnceCell>, problem_resolver: OnceCell>, local_debs: Vec, } impl Cache { /// Initialize the configuration system, open and return the cache. /// This is the entry point for all operations of this crate. /// /// `local_files` allows you to temporarily add local files to the cache, as /// long as they are one of the following: /// /// - `*.deb` or `*.ddeb` files /// - `Packages` and `Sources` files from apt repositories. These files can /// be compressed. /// - `*.dsc` or `*.changes` files /// - A valid directory containing the file `./debian/control` /// /// This function returns an [`AptErrors`] if any of the files cannot /// be found or are invalid. /// /// Note that if you run [`Cache::commit`] or [`Cache::update`], /// You will be required to make a new cache to perform any further changes pub fn new>(local_files: &[T]) -> Result { let volatile_files: Vec<_> = local_files.iter().map(|d| d.as_ref()).collect(); init_config_system(); Ok(Cache { ptr: create_cache(&volatile_files)?, depcache: OnceCell::new(), records: OnceCell::new(), pkgmanager: OnceCell::new(), problem_resolver: OnceCell::new(), local_debs: volatile_files .into_iter() .filter(|f| f.ends_with(".deb")) .map(|f| f.to_string()) .collect(), }) } /// Internal Method for generating the package list. pub fn raw_pkgs(&self) -> impl Iterator> { unsafe { self.begin().raw_iter() } } /// Get the DepCache pub fn depcache(&self) -> &DepCache { self.depcache .get_or_init(|| DepCache::new(unsafe { self.create_depcache() })) } /// Get the PkgRecords pub fn records(&self) -> &PackageRecords { self.records .get_or_init(|| PackageRecords::new(unsafe { self.create_records() })) } /// Get the PkgManager pub fn pkg_manager(&self) -> &PackageManager { self.pkgmanager .get_or_init(|| unsafe { create_pkgmanager(self.depcache()) }) } /// Get the ProblemResolver pub fn resolver(&self) -> &ProblemResolver { self.problem_resolver .get_or_init(|| unsafe { create_problem_resolver(self.depcache()) }) } /// Iterate through the packages in a random order pub fn iter(&self) -> CacheIter { CacheIter { pkgs: unsafe { self.begin().raw_iter() }, cache: self, } } /// An iterator of packages in the cache. pub fn packages(&self, sort: &PackageSort) -> impl Iterator { let mut pkg_list = vec![]; for pkg in self.raw_pkgs() { match sort.virtual_pkgs { // Virtual packages are enabled, include them. // This works differently than the rest. I should probably change defaults. Sort::Enable => {}, // If disabled and pkg has no versions, exclude Sort::Disable => { if unsafe { pkg.versions().end() } { continue; } }, // If reverse and the package has versions, exclude // This section is for if you only want virtual packages Sort::Reverse => { if unsafe { !pkg.versions().end() } { continue; } }, } match sort.upgradable { // Virtual packages are enabled, include them. Sort::Disable => {}, // If disabled and pkg has no versions, exclude Sort::Enable => { // If the package isn't installed, then it can not be upgradable if unsafe { pkg.current_version().end() } || !self.depcache().is_upgradable(&pkg) { continue; } }, // If reverse and the package is installed and upgradable, exclude // This section is for if you only want packages that are not upgradable Sort::Reverse => { if unsafe { !pkg.current_version().end() } && self.depcache().is_upgradable(&pkg) { continue; } }, } match sort.installed { // Installed Package is Disabled, so we keep them Sort::Disable => {}, Sort::Enable => { if unsafe { pkg.current_version().end() } { continue; } }, // Only include installed packages. Sort::Reverse => { if unsafe { !pkg.current_version().end() } { continue; } }, } match sort.auto_installed { // Installed Package is Disabled, so we keep them Sort::Disable => {}, Sort::Enable => { if !self.depcache().is_auto_installed(&pkg) { continue; } }, // Only include installed packages. Sort::Reverse => { if self.depcache().is_auto_installed(&pkg) { continue; } }, } match sort.auto_removable { // auto_removable is Disabled, so we keep them Sort::Disable => {}, // If the package is not auto removable skip it. Sort::Enable => { // If the Package isn't auto_removable skip if !self.depcache().is_garbage(&pkg) { continue; } }, // If the package is auto removable skip it. Sort::Reverse => { if self.depcache().is_garbage(&pkg) { continue; } }, } // If this is reached we're clear to include the package. pkg_list.push(pkg); } if sort.names { pkg_list.sort_by_cached_key(|pkg| pkg.name().to_string()); } pkg_list.into_iter().map(|pkg| Package::new(self, pkg)) } /// Updates the package cache and returns a Result /// /// Here is an example of how you may parse the Error messages. /// /// ``` /// use rust_apt::new_cache; /// use rust_apt::progress::AcquireProgress; /// /// let cache = new_cache!().unwrap(); /// let mut progress = AcquireProgress::apt(); /// if let Err(e) = cache.update(&mut progress) { /// for error in e.iter() { /// if error.is_error { /// println!("Error: {}", error.msg); /// } else { /// println!("Warning: {}", error.msg); /// } /// } /// } /// ``` /// # Known Errors: /// * E:Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied) /// * E:Unable to lock directory /var/lib/apt/lists/ pub fn update(self, progress: &mut AcquireProgress) -> Result<(), AptErrors> { Ok(self.ptr.update(progress.mut_status())?) } /// Mark all packages for upgrade /// /// # Example: /// /// ``` /// use rust_apt::new_cache; /// use rust_apt::cache::Upgrade; /// /// let cache = new_cache!().unwrap(); /// /// cache.upgrade(Upgrade::FullUpgrade).unwrap(); /// ``` pub fn upgrade(&self, upgrade_type: Upgrade) -> Result<(), AptErrors> { let mut progress = OperationProgress::quiet(); Ok(self .depcache() .upgrade(progress.pin().as_mut(), upgrade_type as i32)?) } /// Resolve dependencies with the changes marked on all packages. This marks /// additional packages for installation/removal to satisfy the dependency /// chain. /// /// Note that just running a `mark_*` function on a package doesn't /// guarantee that the selected state will be kept during dependency /// resolution. If you need such, make sure to run /// [`crate::Package::protect`] after marking your requested /// modifications. /// /// If `fix_broken` is set to [`true`], the library will try to repair /// broken dependencies of installed packages. /// /// Returns [`Err`] if there was an error reaching dependency resolution. #[allow(clippy::result_unit_err)] pub fn resolve(&self, fix_broken: bool) -> Result<(), AptErrors> { Ok(self .resolver() .resolve(fix_broken, OperationProgress::quiet().pin().as_mut())?) } /// Autoinstall every broken package and run the problem resolver /// Returns false if the problem resolver fails. /// /// # Example: /// /// ``` /// use rust_apt::new_cache; /// /// let cache = new_cache!().unwrap(); /// /// cache.fix_broken(); /// /// for pkg in cache.get_changes(false) { /// println!("Pkg Name: {}", pkg.name()) /// } /// ``` pub fn fix_broken(&self) -> bool { self.depcache().fix_broken() } /// Fetch any archives needed to complete the transaction. /// /// # Returns: /// * A [`Result`] enum: the [`Ok`] variant if fetching succeeded, and /// [`Err`] if there was an issue. /// /// # Example: /// ``` /// use rust_apt::new_cache; /// use rust_apt::progress::AcquireProgress; /// /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("neovim").unwrap(); /// let mut progress = AcquireProgress::apt(); /// /// pkg.mark_install(true, true); /// pkg.protect(); /// cache.resolve(true).unwrap(); /// /// cache.get_archives(&mut progress).unwrap(); /// ``` /// # Known Errors: /// * W:Problem unlinking the file /// /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb - /// PrepareFiles (13: Permission denied) /// * W:Problem unlinking the file /// /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb - /// PrepareFiles (13: Permission denied) /// * W:Problem unlinking the file /// /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb - /// PrepareFiles (13: Permission denied) /// * W:Problem unlinking the file /// /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb - /// PrepareFiles (13: Permission denied) /// * W:Problem unlinking the file /// /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb - /// PrepareFiles (13: Permission denied) /// * W:Problem unlinking the file /var/log/apt/eipp.log.xz - FileFd::Open /// (13: Permission denied) /// * W:Could not open file /var/log/apt/eipp.log.xz - open (17: File /// exists) /// * W:Could not open file '/var/log/apt/eipp.log.xz' - EIPP::OrderInstall /// (17: File exists) /// * E:Internal Error, ordering was unable to handle the media swap" pub fn get_archives(&self, progress: &mut AcquireProgress) -> Result<(), Exception> { self.pkg_manager() .get_archives(&self.ptr, self.records(), progress.mut_status()) } /// Install, remove, and do any other actions requested by the cache. /// /// # Returns: /// * A [`Result`] enum: the [`Ok`] variant if transaction was successful, /// and [`Err`] if there was an issue. /// /// # Example: /// ``` /// use rust_apt::new_cache; /// use rust_apt::progress::{AcquireProgress, InstallProgress}; /// /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("neovim").unwrap(); /// let mut acquire_progress = AcquireProgress::apt(); /// let mut install_progress = InstallProgress::apt(); /// /// pkg.mark_install(true, true); /// pkg.protect(); /// cache.resolve(true).unwrap(); /// /// // These need root /// // cache.get_archives(&mut acquire_progress).unwrap(); /// // cache.do_install(&mut install_progress).unwrap(); /// ``` /// /// # Known Errors: /// * W:Problem unlinking the file /var/log/apt/eipp.log.xz - FileFd::Open /// (13: Permission denied) /// * W:Could not open file /var/log/apt/eipp.log.xz - open (17: File /// exists) /// * W:Could not open file '/var/log/apt/eipp.log.xz' - EIPP::OrderInstall /// (17: File exists) /// * E:Could not create temporary file for /var/lib/apt/extended_states - /// mkstemp (13: Permission denied) /// * E:Failed to write temporary StateFile /var/lib/apt/extended_states /// * W:Could not open file '/var/log/apt/term.log' - OpenLog (13: /// Permission denied) /// * E:Sub-process /usr/bin/dpkg returned an error code (2) /// * W:Problem unlinking the file /var/cache/apt/pkgcache.bin - /// pkgDPkgPM::Go (13: Permission denied) pub fn do_install(self, progress: &mut InstallProgress) -> Result<(), AptErrors> { Ok(self.pkg_manager().do_install(progress.pin().as_mut())?) } /// Handle get_archives and do_install in an easy wrapper. /// /// # Returns: /// * A [`Result`]: the [`Ok`] variant if transaction was successful, and /// [`Err`] if there was an issue. /// # Example: /// ``` /// use rust_apt::new_cache; /// use rust_apt::progress::{AcquireProgress, InstallProgress}; /// /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("neovim").unwrap(); /// let mut acquire_progress = AcquireProgress::apt(); /// let mut install_progress = InstallProgress::apt(); /// /// pkg.mark_install(true, true); /// pkg.protect(); /// cache.resolve(true).unwrap(); /// /// // This needs root /// // cache.commit(&mut acquire_progress, &mut install_progress).unwrap(); /// ``` pub fn commit( self, progress: &mut AcquireProgress, install_progress: &mut InstallProgress, ) -> Result<(), AptErrors> { // Lock the whole thing so as to prevent tamper apt_lock()?; let config = Config::new(); let archive_dir = config.dir("Dir::Cache::Archives", "/var/cache/apt/archives/"); // Copy local debs into archives dir for deb in &self.local_debs { // If it reaches this point it really will be a valid filename, allegedly if let Some(filename) = Path::new(deb).file_name() { // Append the file name onto the archive dir fs::copy(deb, archive_dir.to_string() + &filename.to_string_lossy())?; } } // The archives can be grabbed during the apt lock. self.get_archives(progress)?; // If the system is locked we will want to unlock the dpkg files. // This way when dpkg is running it can access its files. apt_unlock_inner(); // Perform the operation. self.do_install(install_progress)?; // Finally Unlock the whole thing. apt_unlock(); Ok(()) } /// Get a single package. /// /// `cache.get("apt")` Returns a Package object for the native arch. /// /// `cache.get("apt:i386")` Returns a Package object for the i386 arch pub fn get(&self, name: &str) -> Option { Some(Package::new(self, unsafe { self.find_pkg(name).make_safe()? })) } /// An iterator over the packages /// that will be altered when `cache.commit()` is called. /// /// # sort_name: /// * [`true`] = Packages will be in alphabetical order /// * [`false`] = Packages will not be sorted by name pub fn get_changes(&self, sort_name: bool) -> impl Iterator { let mut changed = Vec::new(); let depcache = self.depcache(); for pkg in self.raw_pkgs() { if depcache.marked_install(&pkg) || depcache.marked_delete(&pkg) || depcache.marked_upgrade(&pkg) || depcache.marked_downgrade(&pkg) || depcache.marked_reinstall(&pkg) { changed.push(pkg); } } if sort_name { // Sort by cached key seems to be the fastest for what we're doing. // Maybe consider impl ord or something for these. changed.sort_by_cached_key(|pkg| pkg.name().to_string()); } changed .into_iter() .map(|pkg_ptr| Package::new(self, pkg_ptr)) } } /// Iterator Implementation for the Cache. pub struct CacheIter<'a> { pkgs: IterPkgIterator, cache: &'a Cache, } impl<'a> Iterator for CacheIter<'a> { type Item = Package<'a>; fn next(&mut self) -> Option { Some(Package::new(self.cache, self.pkgs.next()?)) } } #[cxx::bridge] pub(crate) mod raw { impl UniquePtr {} unsafe extern "C++" { include!("rust-apt/apt-pkg-c/cache.h"); type PkgCacheFile; type PkgIterator = crate::raw::PkgIterator; type VerIterator = crate::raw::VerIterator; type PkgFileIterator = crate::raw::PkgFileIterator; type PkgRecords = crate::records::raw::PkgRecords; type IndexFile = crate::records::raw::IndexFile; type PkgDepCache = crate::depcache::raw::PkgDepCache; type AcqTextStatus = crate::acquire::raw::AcqTextStatus; type PkgAcquire = crate::acquire::raw::PkgAcquire; /// Create the CacheFile. pub fn create_cache(volatile_files: &[&str]) -> Result>; /// Update the package lists, handle errors and return a Result. pub fn update(self: &PkgCacheFile, progress: Pin<&mut AcqTextStatus>) -> Result<()>; /// Loads the index files into PkgAcquire. /// /// Used to get to source list uris. /// /// It's not clear if this returning a bool is useful. pub fn get_indexes(self: &PkgCacheFile, fetcher: &PkgAcquire) -> bool; /// Return a pointer to PkgDepcache. /// /// # Safety /// /// The returned UniquePtr cannot outlive the cache. unsafe fn create_depcache(self: &PkgCacheFile) -> UniquePtr; /// Return a pointer to PkgRecords. /// /// # Safety /// /// The returned UniquePtr cannot outlive the cache. unsafe fn create_records(self: &PkgCacheFile) -> UniquePtr; /// The priority of the Version as shown in `apt policy`. pub fn priority(self: &PkgCacheFile, version: &VerIterator) -> i32; /// Lookup the IndexFile of the Package file /// /// # Safety /// /// The IndexFile can not outlive PkgCacheFile. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn find_index(self: &PkgCacheFile, file: &PkgFileIterator) -> UniquePtr; /// Return a package by name and optionally architecture. /// /// # Safety /// /// If the Internal Pkg Pointer is NULL, operations can segfault. /// You should call `make_safe()` asap to convert it to an Option. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn find_pkg(self: &PkgCacheFile, name: &str) -> UniquePtr; /// Return the pointer to the start of the PkgIterator. /// /// # Safety /// /// If the Internal Pkg Pointer is NULL, operations can segfault. /// You should call `raw_iter()` asap. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn begin(self: &PkgCacheFile) -> UniquePtr; } } rust-apt-0.8.0/src/config.rs000064400000000000000000000230611046102023000140140ustar 00000000000000//! Contains config related structs and functions. use cxx::UniquePtr; /// Struct for Apt Configuration /// /// All apt configuration methods do not require this struct. /// You can call the bindings directly from raw::apt if you would like. #[derive(Debug)] pub struct Config {} // TODO: ConfigTree can (signal: 11, SIGSEGV: invalid memory reference) // if you get a ConfigTree object and then clear the config with clear_all // Make clear_all consume the Config struct and make ConfigTree have a lifetime // to it? impl Default for Config { /// Create a new config object and safely init the config system. /// /// If you initialize the struct without `new()` or `default()` /// You will need to manually initialize the config system. fn default() -> Self { Self::new() } } // TODO: I think we should not accept &str if we just call to_string() anyway impl Config { /// Create a new config object and safely init the config system. /// /// If you initialize the struct without `new()` or `default()` /// You will need to manually initialize the config system. pub fn new() -> Self { init_config_system(); Self {} } /// Clears all configuratations, re-initialize, and returns the config /// object. pub fn new_clear() -> Self { raw::clear_all(); Self::new() } /// Resets the configurations. /// /// If you'd like to clear everything and NOT reinit /// you can call `self.clear_all` or `raw::clear_all` directly pub fn reset(&self) { self.clear_all(); init_config_system(); } /// Clears all values from a key. /// /// If the value is a list, the entire list is cleared. /// If you need to clear 1 value from a list see `self.clear_value` pub fn clear(&self, key: &str) { raw::clear(key.to_string()); } /// Clear a single value from a list. /// Used for removing one item in an apt configuruation list pub fn clear_value(&self, key: &str, value: &str) { raw::clear_value(key.to_string(), value.to_string()); } /// Clears all configuratations. /// /// This will leave you with an empty configuration object /// and most things probably won't work right. pub fn clear_all(&self) { raw::clear_all(); } /// Returns a string dump of configuration options separated by `\n` pub fn dump(&self) -> String { raw::dump() } /// Find a key and return it's value as a string. /// /// default is what will be returned if nothing is found. pub fn find(&self, key: &str, default: &str) -> String { raw::find(key.to_string(), default.to_string()) } /// Exactly like find but takes no default and returns an option instead. pub fn get(&self, key: &str) -> Option { let value = raw::find(key.to_string(), "".to_string()); if value.is_empty() { return None; } Some(value) } /// Find a file and return it's value as a string. /// /// default is what will be returned if nothing is found. /// /// `key = "Dir::Cache::pkgcache"` should return /// `/var/cache/apt/pkgcache.bin` /// /// There is not much difference in `self.dir` and `self.file` /// /// `dir` will return with a trailing `/` where `file` will not. pub fn file(&self, key: &str, default: &str) -> String { raw::find_file(key.to_string(), default.to_string()) } /// Find a directory and return it's value as a string. /// /// default is what will be returned if nothing is found. /// /// `key = "Dir::Etc::sourceparts"` should return `/etc/apt/sources.list.d/` /// /// There is not much difference in `self.dir` and `self.file` /// /// `dir` will return with a trailing `/` where `file` will not. pub fn dir(&self, key: &str, default: &str) -> String { raw::find_dir(key.to_string(), default.to_string()) } /// Same as find, but for boolean values. pub fn bool(&self, key: &str, default: bool) -> bool { raw::find_bool(key.to_string(), default) } /// Same as find, but for i32 values. pub fn int(&self, key: &str, default: i32) -> i32 { raw::find_int(key.to_string(), default) } /// Return a vector for an Apt configuration list. /// /// An example of a common key that contains a list `raw::NeverAutoRemove`. pub fn find_vector(&self, key: &str) -> Vec { raw::find_vector(key.to_string()) } /// Return a vector of supported architectures on this system. /// The main architecture is the first in the list. pub fn get_architectures(&self) -> Vec { raw::get_architectures() } /// Simply check if a key exists. pub fn contains(&self, key: &str) -> bool { raw::exists(key.to_string()) } /// Set the given key to the specified value. pub fn set(&self, key: &str, value: &str) { raw::set(key.to_string(), value.to_string()) } pub fn tree(&self, key: &str) -> Option { let tree = unsafe { raw::tree(key.to_string()) }; if tree.end() { return None; } Some(ConfigTree::new(tree)) } pub fn root_tree(&self) -> Option { let tree = unsafe { raw::root_tree() }; if tree.end() { return None; } Some(ConfigTree::new(tree)) } /// Add strings from a vector into an apt configuration list. /// /// If the configuration key is not a list, /// you will receive a vector with one item. /// /// Example: /// ``` /// use rust_apt::config::Config; /// let config = Config::new(); /// /// let apt_list = vec!["This", "is", "my", "apt", "list"]; /// // Using "AptList" here will not work and will panic. /// config.set_vector("AptList", &apt_list); /// ``` pub fn set_vector(&self, key: &str, values: &Vec<&str>) { let mut vec_key = String::from(key); if !vec_key.ends_with("::") { vec_key.push_str("::"); } for value in values { raw::set(vec_key.to_string(), value.to_string()); } } } pub struct ConfigTree { pub ptr: UniquePtr, } impl ConfigTree { pub fn new(ptr: UniquePtr) -> Self { ConfigTree { ptr } } pub fn tag(&self) -> Option { let tag = self.ptr.tag(); if tag.is_empty() { return None; } Some(tag) } pub fn value(&self) -> Option { let value = self.ptr.value(); if value.is_empty() { return None; } Some(value) } pub fn child(&self) -> Option { let child = unsafe { self.ptr.child() }; if child.end() { None } else { Some(ConfigTree::new(child)) } } pub fn sibling(&self) -> Option { let child = unsafe { self.ptr.raw_next() }; if child.end() { None } else { Some(ConfigTree::new(child)) } } pub fn parent(&self) -> Option { let parent = unsafe { self.ptr.parent() }; if parent.end() { None } else { Some(ConfigTree::new(parent)) } } pub fn iter(&self) -> IterConfigTree { IterConfigTree(unsafe { ConfigTree::new(self.ptr.unique()) }) } } impl IntoIterator for ConfigTree { type IntoIter = IterConfigTree; type Item = ConfigTree; fn into_iter(self) -> Self::IntoIter { IterConfigTree(self) } } pub struct IterConfigTree(ConfigTree); impl Iterator for IterConfigTree { type Item = ConfigTree; fn next(&mut self) -> Option { if self.0.ptr.end() { None } else { let ret = unsafe { self.0.ptr.unique() }; let next = unsafe { self.0.ptr.raw_next() }; self.0.ptr = next; Some(ConfigTree::new(ret)) } } } /// Safely Init Apt Configuration and System. /// /// If the configuration has already been initialized, don't reinit. /// /// This could cause some things to get reset. pub fn init_config_system() { if !raw::exists("APT::Architecture".to_string()) { raw::init_config(); } raw::init_system(); } #[cxx::bridge] pub(crate) mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/configuration.h"); type ConfigTree; /// init the system. This must occur before creating the cache. pub fn init_system(); /// init the config. This must occur before creating the cache. pub fn init_config(); /// Returns a string dump of configuration options separated by `\n` pub fn dump() -> String; /// Find a key and return it's value as a string. pub fn find(key: String, default_value: String) -> String; /// Find a file and return it's value as a string. pub fn find_file(key: String, default_value: String) -> String; /// Find a directory and return it's value as a string. pub fn find_dir(key: String, default_value: String) -> String; /// Same as find, but for boolean values. pub fn find_bool(key: String, default_value: bool) -> bool; /// Same as find, but for i32 values. pub fn find_int(key: String, default_value: i32) -> i32; /// Return a vector for an Apt configuration list. pub fn find_vector(key: String) -> Vec; /// Return a vector of supported architectures on this system. /// The main architecture is the first in the list. pub fn get_architectures() -> Vec; /// Set the given key to the specified value. pub fn set(key: String, value: String); /// Simply check if a key exists. pub fn exists(key: String) -> bool; /// Clears all values from a key. /// /// If the value is a list, the entire list is cleared. /// If you need to clear 1 value from a list see `clear_value` pub fn clear(key: String); /// Clears all configuratations. pub fn clear_all(); /// Clear a single value from a list. /// Used for removing one item in an apt configuruation list pub fn clear_value(key: String, value: String); unsafe fn tree(key: String) -> UniquePtr; unsafe fn root_tree() -> UniquePtr; pub fn end(self: &ConfigTree) -> bool; unsafe fn raw_next(self: &ConfigTree) -> UniquePtr; unsafe fn unique(self: &ConfigTree) -> UniquePtr; unsafe fn parent(self: &ConfigTree) -> UniquePtr; unsafe fn child(self: &ConfigTree) -> UniquePtr; pub fn tag(self: &ConfigTree) -> String; pub fn value(self: &ConfigTree) -> String; } } rust-apt-0.8.0/src/depcache.rs000064400000000000000000000260561046102023000143120ustar 00000000000000//! Dependency Extension data for the cache. //! //! The following was taken from libapt-pkg documentation. //! //! This class stores the cache data and a set of extension structures for //! monitoring the current state of all the packages. It also generates and //! caches the 'install' state of many things. This refers to the state of the //! package after an install has been run. //! //! The StateCache::State field can be -1,0,1,2 which is <,=,>,no current. //! StateCache::Mode is which of the 3 fields is active. //! //! This structure is important to support the readonly status of the cache //! file. When the data is saved the cache will be refreshed from our //! internal rep and written to disk. Then the actual persistent data //! files will be put on the disk. //! //! Each dependency is compared against 3 target versions to produce to //! 3 dependency results. //! Now - Compared using the Currently install version //! Install - Compared using the install version (final state) //! CVer - (Candidate Version) Compared using the Candidate Version //! The candidate and now results are used to decide whether a package //! should be automatically installed or if it should be left alone. //! //! Remember, the Candidate Version is selected based on the distribution //! settings for the Package. The Install Version is selected based on the //! state (Delete, Keep, Install) field and can be either the Current Version //! or the Candidate version. //! //! The Candidate version is what is shown the 'Install Version' field. use cxx::UniquePtr; use crate::error::AptErrors; use crate::progress::OperationProgress; use crate::raw::PkgDepCache; use crate::util::DiskSpace; /// Dependency Extension data for the cache. pub struct DepCache { pub(crate) ptr: UniquePtr, } impl DepCache { pub fn new(ptr: UniquePtr) -> DepCache { DepCache { ptr } } /// Clear any marked changes in the DepCache. pub fn clear_marked(&self) -> Result<(), AptErrors> { Ok(self.init(OperationProgress::quiet().pin().as_mut())?) } /// The amount of space required for installing/removing the packages." /// /// i.e. the Installed-Size of all packages marked for installation" /// minus the Installed-Size of all packages for removal." pub fn disk_size(&self) -> DiskSpace { let size = self.ptr.disk_size(); if size < 0 { return DiskSpace::Free(-size as u64); } DiskSpace::Require(size as u64) } } #[cxx::bridge] pub(crate) mod raw { impl UniquePtr {} unsafe extern "C++" { include!("rust-apt/apt-pkg-c/depcache.h"); type PkgDepCache; /// An action group is a group of actions that are currently being /// performed. /// /// While an active group is active, certain routine /// clean-up actions that would normally be performed after every /// cache operation are delayed until the action group is /// completed. This is necessary primarily to avoid inefficiencies /// when modifying a large number of packages at once. /// /// This struct represents an active action group. Creating an /// instance will create an action group; destroying one will /// destroy the corresponding action group. /// /// The following operations are suppressed by this class: /// /// - Keeping the Marked and Garbage flags up to date. /// /// Here is an example of creating and releasing an ActionGroup. /// /// ``` /// use rust_apt::new_cache; /// /// let cache = new_cache!().unwrap(); /// let mut action_group = unsafe { cache.depcache().action_group() }; /// /// // The C++ deconstructor will be run when the action group leaves scope. /// // You can also call it explicitly. /// action_group.pin_mut().release(); /// ``` type ActionGroup; type PkgIterator = crate::iterators::PkgIterator; type VerIterator = crate::iterators::VerIterator; type DepIterator = crate::iterators::DepIterator; type OperationProgress<'a> = crate::progress::OperationProgress<'a>; /// Clear any marked changes in the DepCache. pub fn init(self: &PkgDepCache, callback: Pin<&mut OperationProgress>) -> Result<()>; /// Autoinstall every broken package and run the problem resolver /// Returns false if the problem resolver fails. pub fn fix_broken(self: &PkgDepCache) -> bool; /// Return a new [`ActionGroup`] of the current DepCache /// /// ActionGroup will be released once it leaves scope /// or ['ActionGroup::release'] is called /// /// # Safety /// /// The returned UniquePtr cannot outlive the cache. unsafe fn action_group(self: &PkgDepCache) -> UniquePtr; /// This will release the [`ActionGroup`] which will trigger a /// MarkAndSweep pub fn release(self: Pin<&mut ActionGroup>); /// Perform an Upgrade. /// /// ## mark_auto: /// * [0] = Remove and install new packages if necessary. /// * [1] = New packages will be installed but nothing will be /// removed. /// * [3] = Neither remove or install new packages. pub fn upgrade( self: &PkgDepCache, progress: Pin<&mut OperationProgress>, upgrade_mode: i32, ) -> Result<()>; /// Check if the package is upgradable. pub fn is_upgradable(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package auto installed? Packages marked as auto installed are /// usually dependencies. pub fn is_auto_installed(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package able to be auto removed? pub fn is_garbage(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package marked for install? pub fn marked_install(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package marked for upgrade? pub fn marked_upgrade(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package marked to be purged? pub fn marked_purge(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package marked for removal? pub fn marked_delete(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package marked for keep? pub fn marked_keep(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package marked for downgrade? pub fn marked_downgrade(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package marked for reinstall? pub fn marked_reinstall(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// # Mark a package as automatically installed. /// /// ## mark_auto: /// * [true] = Mark the package as automatically installed. /// * [false] = Mark the package as manually installed. pub fn mark_auto(self: &PkgDepCache, pkg: &PkgIterator, mark_auto: bool); /// # Mark a package for keep. /// /// ## Returns: /// * [true] if the mark was successful /// * [false] if the mark was unsuccessful /// /// This means that the package will not be changed from its current /// version. This will not stop a reinstall, but will stop removal, /// upgrades and downgrades /// /// We don't believe that there is any reason to unmark packages for /// keep. If someone has a reason, and would like it implemented, please /// put in a feature request. pub fn mark_keep(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// # Mark a package for removal. /// /// ## Returns: /// * [true] if the mark was successful /// * [false] if the mark was unsuccessful /// /// ## purge: /// * [true] = Configuration files will be removed along with the /// package. /// * [false] = Only the package will be removed. pub fn mark_delete(self: &PkgDepCache, pkg: &PkgIterator, purge: bool) -> bool; /// # Mark a package for installation. /// /// ## auto_inst: /// * [true] = Additionally mark the dependencies for this package. /// * [false] = Mark only this package. /// /// ## from_user: /// * [true] = The package will be marked manually installed. /// * [false] = The package will be unmarked automatically installed. /// /// ## Returns: /// * [true] if the mark was successful /// * [false] if the mark was unsuccessful /// /// If a package is already installed, at the latest version, /// and you mark that package for install you will get true, /// but the package will not be altered. /// `pkg.marked_install()` will be false pub fn mark_install( self: &PkgDepCache, pkg: &PkgIterator, auto_inst: bool, from_user: bool, ) -> bool; /// Set a version to be the candidate of it's package. pub fn set_candidate_version(self: &PkgDepCache, ver: &VerIterator); /// Get a pointer to the version that is set to be installed. /// /// # Safety /// /// If there is no candidate the inner pointer will be null. /// This will cause segfaults if methods are used on a Null Version. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn candidate_version( self: &PkgDepCache, pkg: &PkgIterator, ) -> UniquePtr; /// Get a pointer to the version that is installed. /// /// # Safety /// /// If there is no version the inner pointer will be null. /// This will cause segfaults if methods are used on a Null Version. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn install_version(self: &PkgDepCache, pkg: &PkgIterator) -> UniquePtr; /// Returns the state of the dependency as u8 pub fn dep_state(self: &PkgDepCache, dep: &DepIterator) -> u8; /// Checks if the dependency is important. /// /// Depends, PreDepends, Conflicts, Obsoletes, Breaks /// will return [true]. /// /// Suggests, Recommends will return [true] if they are /// configured to be installed. pub fn is_important_dep(self: &PkgDepCache, dep: &DepIterator) -> bool; /// # Mark a package for reinstallation. /// /// ## Returns: /// * [true] if the mark was successful /// * [false] if the mark was unsuccessful /// /// ## reinstall: /// * [true] = The package will be marked for reinstall. /// * [false] = The package will be unmarked for reinstall. pub fn mark_reinstall(self: &PkgDepCache, pkg: &PkgIterator, reinstall: bool); /// Is the installed Package broken? pub fn is_now_broken(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// Is the Package to be installed broken? pub fn is_inst_broken(self: &PkgDepCache, pkg: &PkgIterator) -> bool; /// The number of packages marked for installation. pub fn install_count(self: &PkgDepCache) -> u32; /// The number of packages marked for removal. pub fn delete_count(self: &PkgDepCache) -> u32; /// The number of packages marked for keep. pub fn keep_count(self: &PkgDepCache) -> u32; /// The number of packages with broken dependencies in the cache. pub fn broken_count(self: &PkgDepCache) -> u32; /// The size of all packages to be downloaded. pub fn download_size(self: &PkgDepCache) -> u64; /// The amount of space required for installing/removing the packages," /// /// i.e. the Installed-Size of all packages marked for installation" /// minus the Installed-Size of all packages for removal." pub fn disk_size(self: &PkgDepCache) -> i64; } } rust-apt-0.8.0/src/error.rs000064400000000000000000000040611046102023000136770ustar 00000000000000//! There be Errors here. use std::fmt; use cxx::Exception; #[doc(inline)] pub use raw::{empty, pending_error, AptError}; #[cxx::bridge] pub(crate) mod raw { /// Representation of a single Apt Error or Warning #[derive(Debug)] struct AptError { /// * [`true`] = Error. /// * [`false`] = Warning, Notice, etc. pub is_error: bool, /// The String version of the Error. pub msg: String, } unsafe extern "C++" { include!("rust-apt/apt-pkg-c/error.h"); /// Returns [`true`] if there are any pending Apt Errors. pub fn pending_error() -> bool; /// Returns [`true`] if there are no Errors or Warnings. pub fn empty() -> bool; /// Returns all Apt Errors or Warnings. pub fn get_all() -> Vec; } } impl fmt::Display for AptError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_error { write!(f, "E: {}", self.msg)?; } else { write!(f, "W: {}", self.msg)?; } Ok(()) } } impl std::error::Error for AptError {} /// Struct that represents multiple apt errors and warnings. /// /// This is essentially just a wrapper around [`Vec`] #[derive(Debug)] pub struct AptErrors { pub(crate) ptr: Vec, } impl AptErrors { pub fn new() -> AptErrors { AptErrors { ptr: raw::get_all(), } } } impl Default for AptErrors { fn default() -> Self { Self::new() } } impl fmt::Display for AptErrors { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for error in self.iter() { writeln!(f, "{error}")?; } Ok(()) } } impl From for AptErrors { fn from(err: String) -> Self { AptErrors { ptr: vec![AptError { is_error: true, msg: err, }], } } } impl From for AptErrors { fn from(err: Exception) -> Self { if err.what() == "convert to AptErrors" { return AptErrors::new(); } // The times where it's not an Apt error to be converted are slim AptErrors::from(err.what().to_string()) } } impl From for AptErrors { fn from(err: std::io::Error) -> Self { AptErrors::from(err.to_string()) } } impl std::error::Error for AptErrors {} rust-apt-0.8.0/src/iterators/dependency.rs000064400000000000000000000223531046102023000167040ustar 00000000000000use std::cell::OnceCell; use std::collections::HashMap; use std::fmt; use cxx::UniquePtr; use crate::raw::{DepIterator, VerIterator}; use crate::{Cache, Package, Version}; /// DepFlags defined in depcache.h #[allow(non_upper_case_globals, non_snake_case)] pub mod DepFlags { pub const DepNow: u8 = 1; pub const DepInstall: u8 = 2; pub const DepCVer: u8 = 4; pub const DepGNow: u8 = 8; pub const DepGInstall: u8 = 16; pub const DepGVer: u8 = 32; } /// The different types of Dependencies. #[derive(Debug, Eq, PartialEq, Hash)] pub enum DepType { Depends = 1, PreDepends = 2, Suggests = 3, Recommends = 4, Conflicts = 5, Replaces = 6, Obsoletes = 7, DpkgBreaks = 8, Enhances = 9, } impl From for DepType { fn from(value: u8) -> Self { match value { 1 => DepType::Depends, 2 => DepType::PreDepends, 3 => DepType::Suggests, 4 => DepType::Recommends, 5 => DepType::Conflicts, 6 => DepType::Replaces, 7 => DepType::Obsoletes, 8 => DepType::DpkgBreaks, 9 => DepType::Enhances, _ => panic!("Dependency is malformed?"), } } } impl AsRef for DepType { fn as_ref(&self) -> &str { match self { DepType::Depends => "Depends", DepType::PreDepends => "PreDepends", DepType::Suggests => "Suggests", DepType::Recommends => "Recommends", DepType::Conflicts => "Conflicts", DepType::Replaces => "Replaces", DepType::Obsoletes => "Obsoletes", DepType::DpkgBreaks => "Breaks", DepType::Enhances => "Enhances", } } } impl fmt::Display for DepType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.as_ref()) } } /// A struct representing a Base Dependency. pub struct BaseDep<'a> { pub ptr: UniquePtr, cache: &'a Cache, target: OnceCell>, parent_ver: OnceCell>, } impl<'a> BaseDep<'a> { pub fn new(ptr: UniquePtr, cache: &'a Cache) -> BaseDep { BaseDep { ptr, cache, target: OnceCell::new(), parent_ver: OnceCell::new(), } } /// This is the name of the dependency. pub fn name(&self) -> &str { self.target_package().name() } /// Return the target package. /// /// For Reverse Dependencies this will actually return the parent package pub fn target_package(&self) -> &Package<'a> { self.target.get_or_init(|| { if self.is_reverse() { Package::new(self.cache, unsafe { self.parent_pkg() }) } else { Package::new(self.cache, unsafe { self.target_pkg() }) } }) } /// The target version &str of the dependency if specified. pub fn version(&self) -> Option<&str> { if self.is_reverse() { Some( self.parent_ver .get_or_init(|| unsafe { self.parent_ver() }) .version(), ) } else { self.target_ver().ok() } } /// The Dependency Type. Depends, Recommends, etc. pub fn dep_type(&self) -> DepType { DepType::from(self.ptr.dep_type()) } /// Comparison type of the dependency version, if specified. pub fn comp_type(&self) -> Option<&str> { self.ptr.comp_type().ok() } // Iterate all Versions that are able to satisfy this dependency pub fn all_targets(&self) -> Vec { unsafe { self.ptr .all_targets() .iter() .map(|v| Version::new(v.unique(), self.cache)) .collect() } } } impl<'a> fmt::Display for BaseDep<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let (Some(comp), Some(version)) = (self.comp_type(), self.version()) { write!(f, "({} {comp} {version})", self.name()) } else { write!(f, "({})", self.name()) } } } impl<'a> fmt::Debug for BaseDep<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("BaseDep") .field("parent", unsafe { &self.parent_pkg().name() }) .field("name", &self.name()) .field("comp", &self.comp_type()) .field("version", &self.version()) .field("dep_type", &self.dep_type()) .field("is_reverse", &self.is_reverse()) .finish() } } /// A struct representing a single Dependency record. /// /// This can contain multiple Base Dependencies that can /// satisfy the same Dependency. #[derive(fmt::Debug)] pub struct Dependency<'a> { pub(crate) ptr: Vec>, } impl<'a> Dependency<'a> { /// Return the Dep Type of this group. Depends, Pre-Depends. pub fn dep_type(&self) -> DepType { self[0].dep_type() } /// Returns True if there are multiple dependencies that can satisfy this pub fn is_or(&self) -> bool { self.len() > 1 } /// Returns a reference to the first BaseDep pub fn first(&self) -> &BaseDep<'a> { &self[0] } } impl<'a> fmt::Display for Dependency<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut dep_str = String::new(); for (i, base_dep) in self.iter().enumerate() { dep_str += &base_dep.to_string(); if i + 1 != self.len() { dep_str += " | " } } write!( f, "{} {:?} {dep_str}", unsafe { self.first().parent_pkg().fullname(false) }, self.dep_type(), )?; Ok(()) } } pub fn create_depends_map( cache: &Cache, dep: Option>, ) -> HashMap> { let mut dependencies: HashMap> = HashMap::new(); if let Some(mut dep) = dep { while !dep.end() { let mut or_deps = vec![]; or_deps.push(BaseDep::new(unsafe { dep.unique() }, cache)); // This means that more than one thing can satisfy a dependency. // For reverse dependencies we cannot get the or deps. // This can cause a segfault // See: https://gitlab.com/volian/rust-apt/-/merge_requests/36 if dep.or_dep() && !dep.is_reverse() { loop { dep.pin_mut().raw_next(); or_deps.push(BaseDep::new(unsafe { dep.unique() }, cache)); // This is the last of the Or group if !dep.or_dep() { break; } } } let dep_type = DepType::from(dep.dep_type()); // If the entry already exists in the map append it. if let Some(vec) = dependencies.get_mut(&dep_type) { vec.push(Dependency { ptr: or_deps }) } else { // Doesn't exist so we create it dependencies.insert(dep_type, vec![Dependency { ptr: or_deps }]); } dep.pin_mut().raw_next(); } } dependencies } #[cxx::bridge] pub(crate) mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/package.h"); type DepIterator; type PkgIterator = crate::raw::PkgIterator; type VerIterator = crate::raw::VerIterator; /// The Parent PkgIterator for this dependency /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn parent_pkg(self: &DepIterator) -> UniquePtr; /// The Parent VerIterator for this dependency /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn parent_ver(self: &DepIterator) -> UniquePtr; // Dependency Declarations /// String representation of the dependency compare type /// "","<=",">=","<",">","=","!=" /// /// This returns Error for no compare type. pub fn comp_type(self: &DepIterator) -> Result<&str>; // Get the dependency type as a u8 // You can use `DepType::from(raw_dep.dep_type())` to convert to enum. pub fn dep_type(self: &DepIterator) -> u8; /// Returns true if the dependency type is critical. /// /// Depends, PreDepends, Conflicts, Obsoletes, Breaks /// will return [true]. /// /// Suggests, Recommends, Replaces and Enhances /// will return [false]. #[cxx_name = "IsCritical"] pub fn is_critical(self: &DepIterator) -> bool; /// Return True if the dep is reverse, false if normal #[cxx_name = "Reverse"] pub fn is_reverse(self: &DepIterator) -> bool; pub fn target_ver(self: &DepIterator) -> Result<&str>; /// Return the Target Package for the dependency. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn target_pkg(self: &DepIterator) -> UniquePtr; /// Returns a CxxVector of VerIterators. /// /// # Safety /// /// These can not be owned and will need to be Cloned with unique. /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn all_targets(self: &DepIterator) -> UniquePtr>; /// Return true if this dep is Or'd with the next. The last dep in the /// or group will return False. pub fn or_dep(self: &DepIterator) -> bool; #[cxx_name = "Index"] pub fn index(self: &DepIterator) -> u64; /// Clone the pointer. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn unique(self: &DepIterator) -> UniquePtr; pub fn raw_next(self: Pin<&mut DepIterator>); pub fn end(self: &DepIterator) -> bool; } } rust-apt-0.8.0/src/iterators/files.rs000064400000000000000000000126571046102023000156760ustar 00000000000000use std::cell::OnceCell; use cxx::UniquePtr; use crate::raw::{IndexFile, PkgFileIterator, VerFileIterator}; use crate::{Cache, PackageRecords}; /// Associates a version with a PackageFile /// /// This allows a full description of all Versions in all files pub struct VersionFile<'a> { pub(crate) ptr: UniquePtr, cache: &'a Cache, } impl<'a> VersionFile<'a> { pub fn new(ptr: UniquePtr, cache: &'a Cache) -> VersionFile<'a> { VersionFile { ptr, cache } } /// Return the PkgRecords Parser for the VersionFile pub fn lookup(&self) -> &PackageRecords { self.cache.records().ver_lookup(&self.ptr) } /// Return the PackageFile for this VersionFile pub fn package_file(&self) -> PackageFile<'a> { PackageFile::new(unsafe { self.ptr.package_file() }, self.cache) } } /// Stores information about the files used to generate the cache /// /// Package files are referenced by Version structures to be able to know /// after which Packages file includes this Version. pub struct PackageFile<'a> { pub(crate) ptr: UniquePtr, cache: &'a Cache, index: OnceCell>, } impl<'a> PackageFile<'a> { pub fn new(ptr: UniquePtr, cache: &'a Cache) -> PackageFile { PackageFile { ptr, cache, index: OnceCell::new(), } } pub fn index_file(&self) -> &IndexFile { self.index .get_or_init(|| unsafe { self.cache.find_index(self) }) } } cxx_convert_result!( PackageFile, /// The path to the PackageFile filename() -> &str, /// The Archive of the PackageFile. ex: unstable archive() -> &str, /// The Origin of the PackageFile. ex: Debian origin() -> &str, /// The Codename of the PackageFile. ex: main, non-free codename() -> &str, /// The Label of the PackageFile. ex: Debian label() -> &str, /// The Hostname of the PackageFile. ex: deb.debian.org site() -> &str, /// The Component of the PackageFile. ex: sid component() -> &str, /// The Architecture of the PackageFile. ex: amd64 arch() -> &str, /// The Index Type of the PackageFile. Known values are: /// /// Debian Package Index, Debian Translation Index /// and Debian dpkg status file, index_type() -> &str, ); #[cxx::bridge] pub(crate) mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/package.h"); type VerFileIterator; type DescIterator; type PkgFileIterator; /// The path to the PackageFile pub fn filename(self: &PkgFileIterator) -> Result<&str>; /// The Archive of the PackageFile. ex: unstable pub fn archive(self: &PkgFileIterator) -> Result<&str>; /// The Origin of the PackageFile. ex: Debian pub fn origin(self: &PkgFileIterator) -> Result<&str>; /// The Codename of the PackageFile. ex: main, non-free pub fn codename(self: &PkgFileIterator) -> Result<&str>; /// The Label of the PackageFile. ex: Debian pub fn label(self: &PkgFileIterator) -> Result<&str>; /// The Hostname of the PackageFile. ex: deb.debian.org pub fn site(self: &PkgFileIterator) -> Result<&str>; /// The Component of the PackageFile. ex: sid pub fn component(self: &PkgFileIterator) -> Result<&str>; /// The Architecture of the PackageFile. ex: amd64 pub fn arch(self: &PkgFileIterator) -> Result<&str>; /// The Index Type of the PackageFile. Known values are: /// /// Debian Package Index, Debian Translation Index, Debian dpkg status /// file, pub fn index_type(self: &PkgFileIterator) -> Result<&str>; /// `true` if the PackageFile contains packages that can be downloaded pub fn is_downloadable(self: &PkgFileIterator) -> bool; /// The Index number of the PackageFile #[cxx_name = "Index"] pub fn index(self: &PkgFileIterator) -> u64; /// Clone the pointer. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn unique(self: &PkgFileIterator) -> UniquePtr; pub fn raw_next(self: Pin<&mut PkgFileIterator>); pub fn end(self: &PkgFileIterator) -> bool; /// Return the package file associated with this version file. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn package_file(self: &VerFileIterator) -> UniquePtr; #[cxx_name = "Index"] pub fn index(self: &VerFileIterator) -> u64; /// Clone the pointer. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn unique(self: &VerFileIterator) -> UniquePtr; pub fn raw_next(self: Pin<&mut VerFileIterator>); pub fn end(self: &VerFileIterator) -> bool; #[cxx_name = "Index"] pub fn index(self: &DescIterator) -> u64; /// Clone the pointer. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn unique(self: &DescIterator) -> UniquePtr; pub fn raw_next(self: Pin<&mut DescIterator>); pub fn end(self: &DescIterator) -> bool; } } rust-apt-0.8.0/src/iterators/mod.rs000064400000000000000000000004561046102023000153450ustar 00000000000000pub mod dependency; pub mod files; pub mod package; pub mod provider; pub mod version; pub use dependency::raw::DepIterator; pub use files::raw::{DescIterator, PkgFileIterator, VerFileIterator}; pub use package::raw::PkgIterator; pub use provider::raw::PrvIterator; pub use version::raw::VerIterator; rust-apt-0.8.0/src/iterators/package.rs000064400000000000000000000336671046102023000161730ustar 00000000000000use std::cell::OnceCell; use std::collections::HashMap; use std::fmt; use cxx::UniquePtr; use crate::raw::{IntoRawIter, PkgIterator}; use crate::{create_depends_map, Cache, DepType, Dependency, Provider, Version}; /// The state that the user wishes the package to be in. #[derive(Debug, Eq, PartialEq, Hash)] pub enum PkgSelectedState { Unknown = 0, Install = 1, Hold = 2, DeInstall = 3, Purge = 4, } impl From for PkgSelectedState { fn from(value: u8) -> Self { match value { 0 => PkgSelectedState::Unknown, 1 => PkgSelectedState::Install, 2 => PkgSelectedState::Hold, 3 => PkgSelectedState::DeInstall, 4 => PkgSelectedState::Purge, _ => panic!("PkgSelectedState is malformed?"), } } } /// Installation state of the package #[derive(Debug, Eq, PartialEq, Hash)] pub enum PkgInstState { Ok = 0, ReInstReq = 1, HoldInst = 2, HoldReInstReq = 3, } impl From for PkgInstState { fn from(value: u8) -> Self { match value { 0 => PkgInstState::Ok, 1 => PkgInstState::ReInstReq, 2 => PkgInstState::HoldInst, 3 => PkgInstState::HoldReInstReq, _ => panic!("PkgInstState is malformed?"), } } } /// The current state of a Package. #[derive(Debug, Eq, PartialEq, Hash)] pub enum PkgCurrentState { NotInstalled = 0, UnPacked = 1, HalfConfigured = 2, HalfInstalled = 4, ConfigFiles = 5, Installed = 6, TriggersAwaited = 7, TriggersPending = 8, } impl From for PkgCurrentState { fn from(value: u8) -> Self { match value { 0 => PkgCurrentState::NotInstalled, 1 => PkgCurrentState::UnPacked, 2 => PkgCurrentState::HalfConfigured, 4 => PkgCurrentState::HalfInstalled, 5 => PkgCurrentState::ConfigFiles, 6 => PkgCurrentState::Installed, 7 => PkgCurrentState::TriggersAwaited, 8 => PkgCurrentState::TriggersPending, _ => panic!("PkgCurrentState is malformed?"), } } } /// A single unique libapt package. pub struct Package<'a> { pub(crate) ptr: UniquePtr, pub(crate) cache: &'a Cache, rdepends_map: OnceCell>>>, } impl<'a> Package<'a> { pub fn new(cache: &'a Cache, ptr: UniquePtr) -> Package<'a> { Package { ptr, cache, rdepends_map: OnceCell::new(), } } /// Returns a Reverse Dependency Map of the package /// /// Dependencies are in a `Vec` /// /// The Dependency struct represents an Or Group of dependencies. /// /// For example where we use the [`crate::DepType::Depends`] key: /// /// ``` /// use rust_apt::{new_cache, DepType}; /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("apt").unwrap(); /// for dep in pkg.rdepends().get(&DepType::Depends).unwrap() { /// if dep.is_or() { /// for base_dep in dep.iter() { /// println!("{}", base_dep.name()) /// } /// } else { /// // is_or is false so there is only one BaseDep /// println!("{}", dep.first().name()) /// } /// } /// ``` pub fn rdepends(&self) -> &HashMap>> { self.rdepends_map.get_or_init(|| { create_depends_map(self.cache, unsafe { self.ptr.rdepends().make_safe() }) }) } /// Return either a Version or None /// /// # Example: /// ``` /// use rust_apt::new_cache; /// /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("apt").unwrap(); /// /// pkg.get_version("2.4.7"); /// ``` pub fn get_version(&'a self, version_str: &str) -> Option> { for ver in unsafe { self.ptr.versions().raw_iter() } { if version_str == ver.version() { return Some(Version::new(ver, self.cache)); } } None } /// True if the Package is installed. pub fn is_installed(&self) -> bool { unsafe { !self.current_version().end() } } /// True if the package has versions. /// /// If a package has no versions it is considered virtual. pub fn has_versions(&self) -> bool { unsafe { !self.ptr.versions().end() } } /// True if the package provides any other packages. pub fn has_provides(&self) -> bool { unsafe { !self.ptr.provides().end() } } /// The installed state of this package. pub fn inst_state(&self) -> PkgInstState { PkgInstState::from(self.ptr.inst_state()) } /// The selected state of this package. pub fn selected_state(&self) -> PkgSelectedState { PkgSelectedState::from(self.ptr.selected_state()) } /// The current state of this package. pub fn current_state(&self) -> PkgCurrentState { PkgCurrentState::from(self.ptr.current_state()) } /// Returns the version object of the installed version. /// /// If there isn't an installed version, returns None pub fn installed(&self) -> Option> { Some(Version::new( unsafe { self.current_version().make_safe() }?, self.cache, )) } /// Returns the version object of the candidate. /// /// If there isn't a candidate, returns None pub fn candidate(&self) -> Option> { Some(Version::new( unsafe { self.cache.depcache().candidate_version(self).make_safe()? }, self.cache, )) } /// Returns the install version if it exists. /// /// # This differs from [`crate::Package::installed`] in the /// # following ways: /// /// * If a version is marked for install this will return the version to be /// installed. /// * If an installed package is marked for removal, this will return /// [`None`]. pub fn install_version(&self) -> Option> { // Cxx error here just indicates that the Version doesn't exist Some(Version::new( unsafe { self.cache.depcache().install_version(self).make_safe()? }, self.cache, )) } /// Returns a version list /// starting with the newest and ending with the oldest. pub fn versions(&self) -> impl Iterator> { unsafe { self.ptr.versions() } .raw_iter() .map(|ver| Version::new(ver, self.cache)) } /// Returns a list of providers pub fn provides(&self) -> impl Iterator> { unsafe { self.ptr.provides() } .raw_iter() .map(|p| Provider::new(p, self.cache)) } /// Check if the package is upgradable. /// /// ## skip_depcache: /// /// Skipping the DepCache is unnecessary if it's already been initialized. /// If you're unsure use `false` /// /// * [true] = Increases performance by skipping the pkgDepCache. /// * [false] = Use DepCache to check if the package is upgradable pub fn is_upgradable(&self) -> bool { self.is_installed() && self.cache.depcache().is_upgradable(self) } /// Check if the package is auto installed. (Not installed by the user) pub fn is_auto_installed(&self) -> bool { self.cache.depcache().is_auto_installed(self) } /// Check if the package is auto removable pub fn is_auto_removable(&self) -> bool { (self.is_installed() || self.marked_install()) && self.cache.depcache().is_garbage(self) } /// Check if the package is now broken pub fn is_now_broken(&self) -> bool { self.cache.depcache().is_now_broken(self) } /// Check if the package package installed is broken pub fn is_inst_broken(&self) -> bool { self.cache.depcache().is_inst_broken(self) } /// Check if the package is marked install pub fn marked_install(&self) -> bool { self.cache.depcache().marked_install(self) } /// Check if the package is marked upgrade pub fn marked_upgrade(&self) -> bool { self.cache.depcache().marked_upgrade(self) } /// Check if the package is marked purge pub fn marked_purge(&self) -> bool { self.cache.depcache().marked_purge(self) } /// Check if the package is marked delete pub fn marked_delete(&self) -> bool { self.cache.depcache().marked_delete(self) } /// Check if the package is marked keep pub fn marked_keep(&self) -> bool { self.cache.depcache().marked_keep(self) } /// Check if the package is marked downgrade pub fn marked_downgrade(&self) -> bool { self.cache.depcache().marked_downgrade(self) } /// Check if the package is marked reinstall pub fn marked_reinstall(&self) -> bool { self.cache.depcache().marked_reinstall(self) } /// # Mark a package as automatically installed. /// /// ## mark_auto: /// * [true] = Mark the package as automatically installed. /// * [false] = Mark the package as manually installed. pub fn mark_auto(&self, mark_auto: bool) -> bool { self.cache.depcache().mark_auto(self, mark_auto); // Convert to a bool to remain consistent with other mark functions. true } /// # Mark a package for keep. /// /// ## Returns: /// * [true] if the mark was successful /// * [false] if the mark was unsuccessful /// /// This means that the package will not be changed from its current /// version. This will not stop a reinstall, but will stop removal, upgrades /// and downgrades /// /// We don't believe that there is any reason to unmark packages for keep. /// If someone has a reason, and would like it implemented, please put in a /// feature request. pub fn mark_keep(&self) -> bool { self.cache.depcache().mark_keep(self) } /// # Mark a package for removal. /// /// ## Returns: /// * [true] if the mark was successful /// * [false] if the mark was unsuccessful /// /// ## purge: /// * [true] = Configuration files will be removed along with the package. /// * [false] = Only the package will be removed. pub fn mark_delete(&self, purge: bool) -> bool { self.cache.depcache().mark_delete(self, purge) } /// # Mark a package for installation. /// /// ## auto_inst: /// * [true] = Additionally mark the dependencies for this package. /// * [false] = Mark only this package. /// /// ## from_user: /// * [true] = The package will be marked manually installed. /// * [false] = The package will be unmarked automatically installed. /// /// ## Returns: /// * [true] if the mark was successful /// * [false] if the mark was unsuccessful /// /// If a package is already installed, at the latest version, /// and you mark that package for install you will get true, /// but the package will not be altered. /// `pkg.marked_install()` will be false pub fn mark_install(&self, auto_inst: bool, from_user: bool) -> bool { self.cache .depcache() .mark_install(self, auto_inst, from_user) } /// # Mark a package for reinstallation. /// /// ## Returns: /// * [true] if the mark was successful /// * [false] if the mark was unsuccessful /// /// ## reinstall: /// * [true] = The package will be marked for reinstall. /// * [false] = The package will be unmarked for reinstall. pub fn mark_reinstall(&self, reinstall: bool) -> bool { self.cache.depcache().mark_reinstall(self, reinstall); // Convert to a bool to remain consistent with other mark functions/ true } /// Protect a package's state /// for when [`crate::cache::Cache::resolve`] is called. pub fn protect(&self) { self.cache.resolver().protect(self) } } impl<'a> fmt::Display for Package<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name())?; Ok(()) } } impl<'a> fmt::Debug for Package<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let versions: Vec = self.versions().collect(); f.debug_struct("Package") .field("name", &self.name()) .field("arch", &self.arch()) .field("virtual", &versions.is_empty()) .field("versions", &versions) .finish_non_exhaustive() } } #[cxx::bridge] pub(crate) mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/package.h"); type PkgIterator; type VerIterator = crate::iterators::VerIterator; type PrvIterator = crate::iterators::PrvIterator; type DepIterator = crate::iterators::DepIterator; /// Get the name of the package without the architecture. pub fn name(self: &PkgIterator) -> &str; /// Get the architecture of a package. pub fn arch(self: &PkgIterator) -> &str; /// Get the fullname of the package. /// /// Pretty is a bool that will omit the native arch. pub fn fullname(self: &PkgIterator, pretty: bool) -> String; /// Get the current state of a package. pub fn current_state(self: &PkgIterator) -> u8; /// Get the installed state of a package. pub fn inst_state(self: &PkgIterator) -> u8; /// Get the selected state of a package. pub fn selected_state(self: &PkgIterator) -> u8; /// True if the package is essential. pub fn is_essential(self: &PkgIterator) -> bool; /// Get a pointer the the currently installed version. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn current_version(self: &PkgIterator) -> UniquePtr; /// Get a pointer to the beginning of the VerIterator. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn versions(self: &PkgIterator) -> UniquePtr; /// Get the providers of this package. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn provides(self: &PkgIterator) -> UniquePtr; /// Get the reverse dependencies of this package /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn rdepends(self: &PkgIterator) -> UniquePtr; #[cxx_name = "Index"] pub fn index(self: &PkgIterator) -> u64; /// Clone the pointer. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn unique(self: &PkgIterator) -> UniquePtr; pub fn raw_next(self: Pin<&mut PkgIterator>); pub fn end(self: &PkgIterator) -> bool; } } rust-apt-0.8.0/src/iterators/provider.rs000064400000000000000000000056031046102023000164170ustar 00000000000000use std::fmt; use cxx::UniquePtr; use crate::raw::PrvIterator; use crate::{Cache, Package, Version}; /// A Provider provides a Version and/or Package. /// /// Typically if you had a virtual package you would get its providers /// to find which Package/Version you should really install. pub struct Provider<'a> { pub(crate) ptr: UniquePtr, cache: &'a Cache, } impl<'a> Provider<'a> { pub fn new(ptr: UniquePtr, cache: &'a Cache) -> Provider<'a> { Provider { ptr, cache } } /// Return the Target Package of the provider. pub fn package(&self) -> Package<'a> { Package::new(self.cache, unsafe { self.target_pkg() }) } /// Return the Target Version of the provider. pub fn version(&'a self) -> Version<'a> { Version::new(unsafe { self.target_ver() }, self.cache) } } impl<'a> fmt::Display for Provider<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let version = self.version(); write!( f, "{} provides {} {}", self.name(), version.parent().fullname(false), version.version(), )?; Ok(()) } } impl<'a> fmt::Debug for Provider<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Provider") .field("name", &self.name()) .field("version", &self.version()) .finish() } } #[cxx::bridge] pub(crate) mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/package.h"); type PrvIterator; type PkgIterator = crate::raw::PkgIterator; type VerIterator = crate::raw::VerIterator; /// The name of what this provider provides pub fn name(self: &PrvIterator) -> &str; /// The version string that this provides pub fn version_str(self: &PrvIterator) -> Result<&str>; /// The Target Package that can satisfy this provides /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn target_pkg(self: &PrvIterator) -> UniquePtr; /// The Target Version that can satisfy this provides /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn target_ver(self: &PrvIterator) -> UniquePtr; #[cxx_name = "Index"] pub fn index(self: &PrvIterator) -> u64; /// Clone the pointer. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn unique(self: &PrvIterator) -> UniquePtr; pub fn raw_next(self: Pin<&mut PrvIterator>); pub fn end(self: &PrvIterator) -> bool; } } rust-apt-0.8.0/src/iterators/version.rs000064400000000000000000000242741046102023000162570ustar 00000000000000use std::cell::OnceCell; use std::cmp::Ordering; use std::collections::HashMap; use std::fmt; use cxx::UniquePtr; use crate::raw::{IntoRawIter, VerIterator}; use crate::util::cmp_versions; use crate::{ create_depends_map, Cache, DepType, Dependency, Package, PackageFile, PackageRecords, Provider, VersionFile, }; /// Represents a single Version of a package. pub struct Version<'a> { pub(crate) ptr: UniquePtr, cache: &'a Cache, depends_map: OnceCell>>>, } impl<'a> Version<'a> { pub fn new(ptr: UniquePtr, cache: &'a Cache) -> Version<'a> { Version { ptr, cache, depends_map: OnceCell::new(), } } /// Returns a list of providers pub fn provides(&self) -> impl Iterator> { unsafe { self.ptr.provides() } .raw_iter() .map(|p| Provider::new(p, self.cache)) } pub fn version_files(&self) -> impl Iterator> { unsafe { self.ptr.version_files() } .raw_iter() .map(|v| VersionFile::new(v, self.cache)) } /// Returns an iterator of PackageFiles (Origins) for the version pub fn package_files(&self) -> impl Iterator> { self.version_files().map(|v| v.package_file()) } /// Return the version's parent package. pub fn parent(&self) -> Package<'a> { Package::new(self.cache, unsafe { self.parent_pkg() }) } /// Returns a reference to the Dependency Map owned by the Version /// /// Dependencies are in a `Vec` /// /// The Dependency struct represents an Or Group of dependencies. /// The base deps are located in `Dependency.base_deps` /// /// For example where we use the `DepType::Depends` key: /// /// ``` /// use rust_apt::{new_cache, DepType}; /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("apt").unwrap(); /// let version = pkg.candidate().unwrap(); /// for dep in version.depends_map().get(&DepType::Depends).unwrap() { /// if dep.is_or() { /// for base_dep in dep.iter() { /// println!("{}", base_dep.name()) /// } /// } else { /// // is_or is false so there is only one BaseDep /// println!("{}", dep.first().name()) /// } /// } /// ``` pub fn depends_map(&self) -> &HashMap>> { self.depends_map .get_or_init(|| create_depends_map(self.cache, unsafe { self.depends().make_safe() })) } /// Returns a reference Vector, if it exists, for the given key. /// /// See the doc for `depends_map()` for more information. pub fn get_depends(&self, key: &DepType) -> Option<&Vec>> { self.depends_map().get(key) } /// Returns a Reference Vector, if it exists, for "Enhances". pub fn enhances(&self) -> Option<&Vec>> { self.get_depends(&DepType::Enhances) } /// Returns a Reference Vector, if it exists, /// for "Depends" and "PreDepends". pub fn dependencies(&self) -> Option>> { let mut ret_vec: Vec<&Dependency> = Vec::new(); for dep_type in [DepType::Depends, DepType::PreDepends] { if let Some(dep_list) = self.get_depends(&dep_type) { for dep in dep_list { ret_vec.push(dep) } } } if ret_vec.is_empty() { return None; } Some(ret_vec) } /// Returns a Reference Vector, if it exists, for "Recommends". pub fn recommends(&self) -> Option<&Vec>> { self.get_depends(&DepType::Recommends) } /// Returns a Reference Vector, if it exists, for "suggests". pub fn suggests(&self) -> Option<&Vec>> { self.get_depends(&DepType::Suggests) } /// Move the PkgRecords into the correct place for the Description fn desc_lookup(&self) -> Option<&PackageRecords> { let desc = unsafe { self.translated_desc().make_safe()? }; Some(self.cache.records().desc_lookup(&desc)) } /// Get the translated long description pub fn description(&self) -> Option { self.desc_lookup()?.long_desc() } /// Get the translated short description pub fn summary(&self) -> Option { self.desc_lookup()?.short_desc() } /// Get data from the specified record field /// /// # Returns: /// * Some String or None if the field doesn't exist. /// /// # Example: /// ``` /// use rust_apt::new_cache; /// use rust_apt::records::RecordField; /// /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("apt").unwrap(); /// let cand = pkg.candidate().unwrap(); /// /// println!("{}", cand.get_record(RecordField::Maintainer).unwrap()); /// // Or alternatively you can just pass any string /// println!("{}", cand.get_record("Description-md5").unwrap()); /// ``` pub fn get_record(&self, field: &T) -> Option { self.version_files() .next()? .lookup() .get_field(field.to_string()) } /// Get the hash specified. If there isn't one returns None /// `version.hash("md5sum")` pub fn hash(&self, hash_type: &T) -> Option { self.version_files() .next()? .lookup() .hash_find(hash_type.to_string()) } /// Get the sha256 hash. If there isn't one returns None /// This is equivalent to `version.hash("sha256")` pub fn sha256(&self) -> Option { self.hash("sha256") } /// Get the sha512 hash. If there isn't one returns None /// This is equivalent to `version.hash("sha512")` pub fn sha512(&self) -> Option { self.hash("sha512") } /// Returns an Iterator of URIs for the Version. pub fn uris(&self) -> impl Iterator + 'a { self.version_files().filter_map(|v| { let pkg_file = v.package_file(); if !pkg_file.is_downloadable() { return None; } Some(pkg_file.index_file().archive_uri(&v.lookup().filename())) }) } /// Set this version as the candidate. pub fn set_candidate(&self) { self.cache.depcache().set_candidate_version(self); } /// The priority of the Version as shown in `apt policy`. pub fn priority(&self) -> i32 { self.cache.priority(self) } } // Implementations for comparing versions. impl<'a> PartialEq for Version<'a> { fn eq(&self, other: &Self) -> bool { matches!( cmp_versions(self.version(), other.version()), Ordering::Equal ) } } impl<'a> PartialOrd for Version<'a> { fn partial_cmp(&self, other: &Self) -> Option { Some(cmp_versions(self.version(), other.version())) } } impl<'a> fmt::Display for Version<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.version())?; Ok(()) } } impl<'a> fmt::Debug for Version<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let parent = self.parent(); f.debug_struct("Version") .field("pkg", &parent.name()) .field("arch", &self.arch()) .field("version", &self.version()) .field( "is_candidate", &parent.candidate().is_some_and(|cand| self == &cand), ) .field("is_installed", &self.is_installed()) .finish_non_exhaustive() } } #[cxx::bridge] pub(crate) mod raw { impl CxxVector {} unsafe extern "C++" { include!("rust-apt/apt-pkg-c/package.h"); type VerIterator; type PkgIterator = crate::iterators::PkgIterator; type PrvIterator = crate::iterators::PrvIterator; type DepIterator = crate::iterators::DepIterator; type DescIterator = crate::iterators::DescIterator; type VerFileIterator = crate::iterators::VerFileIterator; /// The version string of the version. "1.4.10". pub fn version(self: &VerIterator) -> &str; /// The Arch of the version. "amd64". pub fn arch(self: &VerIterator) -> &str; /// Return the version's parent PkgIterator. /// /// # Safety /// /// The returned UniquePtr cannot outlive the cache. unsafe fn parent_pkg(self: &VerIterator) -> UniquePtr; /// The section of the version as shown in `apt show`. pub fn section(self: &VerIterator) -> Result<&str>; /// The priority string as shown in `apt show`. pub fn priority_str(self: &VerIterator) -> Result<&str>; /// The size of the .deb file. pub fn size(self: &VerIterator) -> u64; /// The uncompressed size of the .deb file. pub fn installed_size(self: &VerIterator) -> u64; /// True if the version is able to be downloaded. #[cxx_name = "Downloadable"] pub fn is_downloadable(self: &VerIterator) -> bool; /// True if the version is currently installed pub fn is_installed(self: &VerIterator) -> bool; /// Always contains the name, even if it is the same as the binary name pub fn source_name(self: &VerIterator) -> &str; // Always contains the version string, // even if it is the same as the binary version. pub fn source_version(self: &VerIterator) -> &str; /// Return Providers Iterator /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn provides(self: &VerIterator) -> UniquePtr; /// Return Dependency Iterator /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn depends(self: &VerIterator) -> UniquePtr; /// Return the version files. /// You go through here to get the package files. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn version_files(self: &VerIterator) -> UniquePtr; /// This is for backend records lookups. /// /// # Safety /// /// If the inner pointer is null segfaults can occur. /// /// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option /// is recommended. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn translated_desc(self: &VerIterator) -> UniquePtr; #[cxx_name = "Index"] pub fn index(self: &VerIterator) -> u64; /// Clone the pointer. /// /// # Safety /// /// The returned UniquePtr cannot outlive the cache. unsafe fn unique(self: &VerIterator) -> UniquePtr; pub fn raw_next(self: Pin<&mut VerIterator>); pub fn end(self: &VerIterator) -> bool; } } rust-apt-0.8.0/src/lib.rs000064400000000000000000000063131046102023000133160ustar 00000000000000//! rust-apt provides bindings to `libapt-pkg`. //! The goal is to eventually have all of the functionality of `python-apt` //! //! The source repository is //! For more information please see the readme in the source code. //! //! Each module has a `raw` submodule containing c++ bindings to `libapt-pkg` //! //! These are safe to use in terms of memory, //! but may cause segfaults if you do something wrong. //! //! If you find a way to segfault without using the `libapt-pkg` bindings //! directly, please report this as a bug. // Clippy is really mad at my safety docs and idk why #![allow(clippy::missing_safety_doc)] #[macro_use] mod macros; mod acquire; pub mod cache; pub mod config; mod depcache; pub mod error; mod iterators; mod pkgmanager; pub mod progress; pub mod records; pub mod tagfile; pub mod util; #[doc(inline)] pub use cache::{Cache, PackageSort}; pub use iterators::dependency::{create_depends_map, BaseDep, DepFlags, DepType, Dependency}; pub use iterators::files::{PackageFile, VersionFile}; pub use iterators::package::{Package, PkgCurrentState, PkgInstState, PkgSelectedState}; pub use iterators::provider::Provider; pub use iterators::version::Version; /// C++ bindings for libapt-pkg pub mod raw { pub use crate::acquire::raw::{ acquire_status, create_acquire, AcqTextStatus, AcqWorker, Item, ItemDesc, ItemState, PkgAcquire, }; pub use crate::cache::raw::{create_cache, PkgCacheFile}; pub use crate::depcache::raw::{ActionGroup, PkgDepCache}; pub use crate::iterators::{ DepIterator, DescIterator, PkgFileIterator, PkgIterator, PrvIterator, VerFileIterator, VerIterator, }; pub use crate::pkgmanager::raw::{ create_pkgmanager, create_problem_resolver, PackageManager, ProblemResolver, }; pub use crate::records::raw::{IndexFile, Parser, PkgRecords}; pub use crate::util::raw::*; // Hmm, maybe this is reason enough to make a wrapper in C++ // So that the raw functions are methods on a "Config" struct? // But it may need to not outlive the cache if we do that. pub mod config { pub use crate::config::raw::*; } /// Iterator trait for libapt raw bindings pub trait IntoRawIter { type Item; fn raw_iter(self) -> Self::Item; fn make_safe(self) -> Option where Self: Sized; fn to_vec(self) -> Vec where Self: Sized; } use cxx::UniquePtr; use paste::paste; raw_iter!( PkgIterator, VerIterator, DepIterator, PrvIterator, VerFileIterator, DescIterator, PkgFileIterator ); } use depcache::DepCache; use error::AptErrors; use records::PackageRecords; impl_deref!( Cache -> raw::PkgCacheFile, DepCache -> raw::PkgDepCache, PackageRecords -> raw::PkgRecords, AptErrors -> Vec, Package<'a> -> raw::PkgIterator, Version<'a> -> raw::VerIterator, Dependency<'a> -> Vec>, BaseDep<'a> -> raw::DepIterator, Provider<'a> -> raw::PrvIterator, VersionFile<'a> -> raw::VerFileIterator, PackageFile<'a> -> raw::PkgFileIterator, ); // Version is omitted because it has special needs impl_partial_eq!( Package<'a>, BaseDep<'a>, Provider<'a>, VersionFile<'a>, PackageFile<'a>, ); impl_hash_eq!( Package<'a>, Version<'a>, BaseDep<'a>, Provider<'a>, VersionFile<'a>, PackageFile<'a>, ); rust-apt-0.8.0/src/macros.rs000064400000000000000000000061661046102023000140420ustar 00000000000000#[macro_export] /// Macro to create the cache, optionally including local valid files. /// /// This includes the following: /// - `*.deb` or `*.ddeb` files /// - `Packages` and `Sources` files from apt repositories. These files can be /// compressed. /// - `*.dsc` or `*.changes` files /// - A valid directory containing the file `./debian/control` /// /// Here is an example of the two ways you can use this. /// /// ``` /// use rust_apt::new_cache; /// /// let cache = new_cache!().unwrap(); /// /// println!("{}", cache.get("apt").unwrap().name()); /// /// // Any file that can be added to the cache /// let local_files = vec![ /// "tests/files/cache/apt.deb", /// "tests/files/cache/Packages", /// ]; /// /// let cache = new_cache!(&local_files).unwrap(); /// println!("{}", cache.get("apt").unwrap().get_version("5000:1.0.0").unwrap().version()); /// ``` /// /// Returns [`Result`] macro_rules! new_cache { () => {{ let files: Vec = Vec::new(); $crate::cache::Cache::new(&files) }}; ($slice:expr) => {{ $crate::cache::Cache::new($slice) }}; } /// Implements RawIter trait for raw apt iterators macro_rules! raw_iter { ($($ty:ty),*) => {$( paste!( #[doc = "Iterator Struct for [`" $ty "`]."] pub struct [](UniquePtr<$ty>); impl Iterator for [] { type Item = UniquePtr<$ty>; fn next(&mut self) -> Option { if self.0.end() { None } else { let ptr = unsafe { self.0.unique() }; self.0.pin_mut().raw_next(); Some(ptr) } } } impl IntoRawIter for UniquePtr<$ty> { type Item = []; fn raw_iter(self) -> Self::Item { [](self) } fn make_safe(self) -> Option { if self.end() { None } else { Some(self) } } fn to_vec(self) -> Vec { self.raw_iter().collect() } } ); )*}; } /// Generates the boiler plate for wrapper structs /// where we need to change a Result to an option. macro_rules! cxx_convert_result { ($wrapper:ident, $($(#[$meta:meta])* $method:ident ( $( $arg:ident : $arg_ty:ty ),* ) -> $ret:ty ),* $(,)? ) => { impl<'a> $wrapper<'a> { $( $(#[$meta])* pub fn $method(&self, $( $arg : $arg_ty ),* ) -> Option<$ret> { self.ptr.$method($( $arg ),*).ok() } )* } }; } macro_rules! impl_partial_eq { ($($wrapper:ident $(<$lt:lifetime>)?),* $(,)?) => { $( impl $(<$lt>)? PartialEq for $wrapper $(<$lt>)? { fn eq(&self, other: &Self) -> bool { self.index() == other.index() } } )* }; } macro_rules! impl_hash_eq { ($($wrapper:ident $(<$lt:lifetime>)?),* $(,)?) => { $( impl $(<$lt>)? std::hash::Hash for $wrapper $(<$lt>)? { fn hash(&self, state: &mut H) { self.index().hash(state); } } impl $(<$lt>)? Eq for $wrapper $(<$lt>)? {} )* }; } /// Implements deref for apt smart pointer structs. macro_rules! impl_deref { ($($wrapper:ident $(<$lt:lifetime>)? -> $target:ty),* $(,)?) => { $( impl $(<$lt>)? std::ops::Deref for $wrapper $(<$lt>)? { type Target = $target; #[inline] fn deref(&self) -> &Self::Target { &self.ptr } } )* }; } rust-apt-0.8.0/src/pkgmanager.rs000064400000000000000000000026361046102023000146700ustar 00000000000000//! Contains types and bindings for fetching and installing packages from the //! cache. #[cxx::bridge] pub(crate) mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/pkgmanager.h"); type PackageManager; type ProblemResolver; type PkgCacheFile = crate::cache::raw::PkgCacheFile; type PkgIterator = crate::cache::raw::PkgIterator; type PkgRecords = crate::records::raw::PkgRecords; type PkgDepCache = crate::depcache::raw::PkgDepCache; type AcqTextStatus = crate::acquire::raw::AcqTextStatus; type InstallProgress<'a> = crate::progress::InstallProgress<'a>; type OperationProgress<'a> = crate::progress::OperationProgress<'a>; /// # Safety /// /// The returned UniquePtr cannot outlive the cache. unsafe fn create_pkgmanager(depcache: &PkgDepCache) -> UniquePtr; pub fn get_archives( self: &PackageManager, cache: &PkgCacheFile, records: &PkgRecords, progress: Pin<&mut AcqTextStatus>, ) -> Result<()>; pub fn do_install(self: &PackageManager, progress: Pin<&mut InstallProgress>) -> Result<()>; /// # Safety /// /// The returned UniquePtr cannot outlive the cache. unsafe fn create_problem_resolver(depcache: &PkgDepCache) -> UniquePtr; pub fn protect(self: &ProblemResolver, pkg: &PkgIterator); fn resolve( self: &ProblemResolver, fix_broken: bool, op_progress: Pin<&mut OperationProgress>, ) -> Result<()>; } } rust-apt-0.8.0/src/progress.rs000064400000000000000000000453731046102023000144250ustar 00000000000000//! Contains Progress struct for updating the package list. use std::fmt::Write as _; use std::io::{stdout, Write}; use std::pin::Pin; use cxx::{ExternType, UniquePtr}; use crate::config::Config; use crate::error::raw::pending_error; use crate::raw::{acquire_status, AcqTextStatus, ItemDesc, ItemState, PkgAcquire}; use crate::util::{ get_apt_progress_string, terminal_height, terminal_width, time_str, unit_str, NumSys, }; /// Customize the output shown during file downloads. pub trait DynAcquireProgress { /// Called on c++ to set the pulse interval. fn pulse_interval(&self) -> usize; /// Called when an item is confirmed to be up-to-date. fn hit(&mut self, item: &ItemDesc); /// Called when an Item has started to download fn fetch(&mut self, item: &ItemDesc); /// Called when an Item fails to download fn fail(&mut self, item: &ItemDesc); /// Called periodically to provide the overall progress information fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire); /// Called when an item is successfully and completely fetched. fn done(&mut self, item: &ItemDesc); /// Called when progress has started fn start(&mut self); /// Called when progress has finished fn stop(&mut self, status: &AcqTextStatus); } /// Customize the output of operation progress on things like opening the cache. pub trait DynOperationProgress { fn update(&mut self, operation: String, percent: f32); fn done(&mut self); } /// Customize the output of installation progress. pub trait DynInstallProgress { fn status_changed( &mut self, pkgname: String, steps_done: u64, total_steps: u64, action: String, ); fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String); } /// A struct aligning with `apt`'s AcquireStatus. /// /// This struct takes a struct with impl AcquireProgress /// It sets itself as the callback from C++ AcqTextStatus /// which will then call the functions on this struct. /// This struct will then forward those calls to your struct via /// trait methods. pub struct AcquireProgress<'a> { status: UniquePtr, inner: Box, } impl<'a> AcquireProgress<'a> { /// Create a new AcquireProgress Struct from a struct that implements /// AcquireProgress trait. pub fn new(inner: impl DynAcquireProgress + 'a) -> Self { Self { status: unsafe { acquire_status() }, inner: Box::new(inner), } } /// Create a new AcquireProgress Struct with the default `apt` /// implementation. pub fn apt() -> Self { Self::new(AptAcquireProgress::new()) } /// Create a new AcquireProgress Struct that outputs nothing. pub fn quiet() -> Self { Self::new(AptAcquireProgress::disable()) } /// Sets AcquireProgress as the AcqTextStatus callback and /// returns a Pinned mutable reference to AcqTextStatus. pub fn mut_status(&mut self) -> Pin<&mut AcqTextStatus> { unsafe { // Create raw mutable pointer to ourself let raw_ptr = &mut *(self as *mut AcquireProgress); // Pin AcqTextStatus in place so it is not moved in memory // Segfault can occur if it is moved let mut status = self.status.pin_mut(); // Set our raw pointer we created as the callback for C++ AcqTextStatus. // AcqTextStatus will then be fed into libapt who will call its methods // providing information. AcqTextStatus then uses this pointer to send that // information back to rust on this struct. This struct will then send it // through the trait methods on the `inner` object. status.as_mut().set_callback(raw_ptr); status } } /// Called on c++ to set the pulse interval. pub(crate) fn pulse_interval(&mut self) -> usize { self.inner.pulse_interval() } /// Called when an item is confirmed to be up-to-date. pub(crate) fn hit(&mut self, item: &ItemDesc) { self.inner.hit(item) } /// Called when an Item has started to download pub(crate) fn fetch(&mut self, item: &ItemDesc) { self.inner.fetch(item) } /// Called when an Item fails to download pub(crate) fn fail(&mut self, item: &ItemDesc) { self.inner.fail(item) } /// Called periodically to provide the overall progress information pub(crate) fn pulse(&mut self, owner: &PkgAcquire) { self.inner.pulse(&self.status, owner) } /// Called when progress has started pub(crate) fn start(&mut self) { self.inner.start() } /// Called when an item is successfully and completely fetched. pub(crate) fn done(&mut self, item: &ItemDesc) { self.inner.done(item) } /// Called when progress has finished pub(crate) fn stop(&mut self) { self.inner.stop(&self.status) } } impl<'a> Default for AcquireProgress<'a> { fn default() -> Self { Self::apt() } } /// Impl for sending AcquireProgress across the barrier. unsafe impl<'a> ExternType for AcquireProgress<'a> { type Id = cxx::type_id!("AcquireProgress"); type Kind = cxx::kind::Trivial; } /// Allows lengthy operations to communicate their progress. /// /// The [`Default`] and only implementation of this is /// [`self::OperationProgress::quiet`]. pub struct OperationProgress<'a> { inner: Box, } impl<'a> OperationProgress<'a> { /// Create a new OpProgress Struct from a struct that implements /// AcquireProgress trait. pub fn new(inner: impl DynOperationProgress + 'static) -> Self { Self { inner: Box::new(inner), } } /// Returns a OperationProgress that outputs no data /// /// Generally I have not found much use for displaying OpProgress pub fn quiet() -> Self { Self::new(NoOpProgress {}) } /// Called when an operation has been updated. fn update(&mut self, operation: String, percent: f32) { self.inner.update(operation, percent) } /// Called when an operation has finished. fn done(&mut self) { self.inner.done() } pub fn pin(&mut self) -> Pin<&mut OperationProgress<'a>> { Pin::new(self) } } impl<'a> Default for OperationProgress<'a> { fn default() -> Self { Self::quiet() } } /// Impl for sending AcquireProgress across the barrier. unsafe impl<'a> ExternType for OperationProgress<'a> { type Id = cxx::type_id!("OperationProgress"); type Kind = cxx::kind::Trivial; } /// Struct for displaying Progress of Package Installation. /// /// The [`Default`] implementation mirrors apt's. pub struct InstallProgress<'a> { inner: Box, } impl<'a> InstallProgress<'a> { /// Create a new OpProgress Struct from a struct that implements /// AcquireProgress trait. pub fn new(inner: impl DynInstallProgress + 'static) -> Self { Self { inner: Box::new(inner), } } /// Returns a OperationProgress that outputs no data /// /// Generally I have not found much use for displaying OpProgress pub fn apt() -> Self { Self::new(AptInstallProgress::new()) } fn status_changed( &mut self, pkgname: String, steps_done: u64, total_steps: u64, action: String, ) { self.inner .status_changed(pkgname, steps_done, total_steps, action) } fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String) { self.inner.error(pkgname, steps_done, total_steps, error) } pub fn pin(&mut self) -> Pin<&mut InstallProgress<'a>> { Pin::new(self) } } impl<'a> Default for InstallProgress<'a> { fn default() -> Self { Self::apt() } } /// Impl for sending DynInstallProgress across the barrier. unsafe impl<'a> ExternType for InstallProgress<'a> { type Id = cxx::type_id!("InstallProgress"); type Kind = cxx::kind::Trivial; } /// Internal struct to pass into [`crate::Cache::resolve`]. The C++ library for /// this wants a progress parameter for this, but it doesn't appear to be doing /// anything. Furthermore, [the Python-APT implementation doesn't accept a /// parameter for their dependency resolution functionality](https://apt-team.pages.debian.net/python-apt/library/apt_pkg.html#apt_pkg.ProblemResolver.resolve), /// so we should be safe to remove it here. struct NoOpProgress {} impl DynOperationProgress for NoOpProgress { fn update(&mut self, _operation: String, _percent: f32) {} fn done(&mut self) {} } /// AptAcquireProgress is the default struct for the update method on the cache. /// /// This struct mimics the output of `apt update`. #[derive(Default, Debug)] pub struct AptAcquireProgress { lastline: usize, pulse_interval: usize, disable: bool, config: Config, } impl AptAcquireProgress { /// Returns a new default progress instance. pub fn new() -> Self { Self::default() } /// Returns a disabled progress instance. No output will be shown. pub fn disable() -> Self { AptAcquireProgress { disable: true, ..Default::default() } } /// Helper function to clear the last line. fn clear_last_line(&mut self, term_width: usize) { if self.disable { return; } if self.lastline == 0 { return; } if self.lastline > term_width { self.lastline = term_width } print!("\r{}", " ".repeat(self.lastline)); print!("\r"); stdout().flush().unwrap(); } } impl DynAcquireProgress for AptAcquireProgress { /// Used to send the pulse interval to the apt progress class. /// /// Pulse Interval is in microseconds. /// /// Example: 1 second = 1000000 microseconds. /// /// Apt default is 500000 microseconds or 0.5 seconds. /// /// The higher the number, the less frequent pulse updates will be. /// /// Pulse Interval set to 0 assumes the apt defaults. fn pulse_interval(&self) -> usize { self.pulse_interval } /// Called when an item is confirmed to be up-to-date. /// /// Prints out the short description and the expected size. fn hit(&mut self, item: &ItemDesc) { if self.disable { return; } self.clear_last_line(terminal_width() - 1); println!("\rHit:{} {}", item.owner().id(), item.description()); } /// Called when an Item has started to download /// /// Prints out the short description and the expected size. fn fetch(&mut self, item: &ItemDesc) { if self.disable { return; } self.clear_last_line(terminal_width() - 1); let mut string = format!("\rGet:{} {}", item.owner().id(), item.description()); let file_size = item.owner().file_size(); if file_size != 0 { string.push_str(&format!(" [{}]", unit_str(file_size, NumSys::Decimal))); } println!("{string}"); } /// Called when an item is successfully and completely fetched. /// /// We don't print anything here to remain consistent with apt. fn done(&mut self, _item: &ItemDesc) { // self.clear_last_line(terminal_width() - 1); // println!("This is done!"); } /// Called when progress has started. /// /// Start does not pass information into the method. /// /// We do not print anything here to remain consistent with apt. /// lastline length is set to 0 to ensure consistency when progress begins. fn start(&mut self) { self.lastline = 0; } /// Called when progress has finished. /// /// Stop does not pass information into the method. /// /// prints out the bytes downloaded and the overall average line speed. fn stop(&mut self, owner: &AcqTextStatus) { if self.disable { return; } self.clear_last_line(terminal_width() - 1); if pending_error() { return; } if owner.fetched_bytes() != 0 { println!( "Fetched {} in {} ({}/s)", unit_str(owner.fetched_bytes(), NumSys::Decimal), time_str(owner.elapsed_time()), unit_str(owner.current_cps(), NumSys::Decimal) ); } else { println!("Nothing to fetch."); } } /// Called when an Item fails to download. /// /// Print out the ErrorText for the Item. fn fail(&mut self, item: &ItemDesc) { if self.disable { return; } self.clear_last_line(terminal_width() - 1); let mut show_error = true; let error_text = item.owner().error_text(); let desc = format!("{} {}", item.owner().id(), item.description()); match item.owner().status() { ItemState::StatIdle | ItemState::StatDone => { println!("\rIgn: {desc}"); let key = "Acquire::Progress::Ignore::ShowErrorText"; if error_text.is_empty() || self.config.bool(key, false) { show_error = false; } }, _ => { println!("\rErr: {desc}"); }, } if show_error { println!("\r{error_text}"); } } /// Called periodically to provide the overall progress information /// /// Draws the current progress. /// Each line has an overall percent meter and a per active item status /// meter along with an overall bandwidth and ETA indicator. fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire) { if self.disable { return; } // Minus 1 for the cursor let term_width = terminal_width() - 1; let mut string = String::new(); let mut percent_str = format!("\r{:.0}%", status.percent()); let mut eta_str = String::new(); // Set the ETA string if there is a rate of download let current_cps = status.current_cps(); if current_cps != 0 { let _ = write!( eta_str, " {} {}", // Current rate of download unit_str(current_cps, NumSys::Decimal), // ETA String time_str((status.total_bytes() - status.current_bytes()) / current_cps) ); } for worker in owner.workers().iter() { let mut work_string = String::new(); work_string.push_str(" ["); let Ok(item) = worker.item() else { if !worker.status().is_empty() { work_string.push_str(&worker.status()); work_string.push(']'); } continue; }; let id = item.owner().id(); if id != 0 { let _ = write!(work_string, " {id} "); } work_string.push_str(&item.short_desc()); let sub = item.owner().active_subprocess(); if !sub.is_empty() { work_string.push(' '); work_string.push_str(&sub); } work_string.push(' '); work_string.push_str(&unit_str(worker.current_size(), NumSys::Decimal)); if worker.total_size() > 0 && !item.owner().complete() { let _ = write!( work_string, "/{} {}%", unit_str(worker.total_size(), NumSys::Decimal), (worker.current_size() * 100) / worker.total_size() ); } work_string.push(']'); if (string.len() + work_string.len() + percent_str.len() + eta_str.len()) > term_width { break; } string.push_str(&work_string); } // Display at least something if there is no worker strings if string.is_empty() { string = " [Working]".to_string() } // Push the worker strings on the percent string percent_str.push_str(&string); // Fill the remaining space in the terminal if eta exists if !eta_str.is_empty() { let fill_size = percent_str.len() + eta_str.len(); if fill_size < term_width { percent_str.push_str(&" ".repeat(term_width - fill_size)) } } // Push the final eta to the end of the filled string percent_str.push_str(&eta_str); // Print and flush stdout print!("{percent_str}"); stdout().flush().unwrap(); if self.lastline > percent_str.len() { self.clear_last_line(term_width); } self.lastline = percent_str.len(); } } /// Default struct to handle the output of a transaction. pub struct AptInstallProgress { config: Config, } impl AptInstallProgress { pub fn new() -> Self { Self { config: Config::new(), } } } impl Default for AptInstallProgress { fn default() -> Self { Self::new() } } impl DynInstallProgress for AptInstallProgress { fn status_changed( &mut self, _pkgname: String, steps_done: u64, total_steps: u64, _action: String, ) { // Get the terminal's width and height. let term_height = terminal_height(); let term_width = terminal_width(); // Save the current cursor position. print!("\x1b7"); // Go to the progress reporting line. print!("\x1b[{term_height};0f"); std::io::stdout().flush().unwrap(); // Convert the float to a percentage string. let percent = steps_done as f32 / total_steps as f32; let mut percent_str = (percent * 100.0).round().to_string(); let percent_padding = match percent_str.len() { 1 => " ", 2 => " ", 3 => "", _ => unreachable!(), }; percent_str = percent_padding.to_owned() + &percent_str; // Get colors for progress reporting. // NOTE: The APT implementation confusingly has 'Progress-fg' for 'bg_color', // and the same the other way around. let bg_color = self .config .find("Dpkg::Progress-Fancy::Progress-fg", "\x1b[42m"); let fg_color = self .config .find("Dpkg::Progress-Fancy::Progress-bg", "\x1b[30m"); const BG_COLOR_RESET: &str = "\x1b[49m"; const FG_COLOR_RESET: &str = "\x1b[39m"; print!("{bg_color}{fg_color}Progress: [{percent_str}%]{BG_COLOR_RESET}{FG_COLOR_RESET} "); // The length of "Progress: [100%] ". const PROGRESS_STR_LEN: usize = 17; // Print the progress bar. // We should safely be able to convert the `usize`.try_into() into the `u32` // needed by `get_apt_progress_string`, as usize ints only take up 8 bytes on a // 64-bit processor. print!( "{}", get_apt_progress_string(percent, (term_width - PROGRESS_STR_LEN).try_into().unwrap()) ); std::io::stdout().flush().unwrap(); // If this is the last change, remove the progress reporting bar. // if steps_done == total_steps { // print!("{}", " ".repeat(term_width)); // print!("\x1b[0;{}r", term_height); // } // Finally, go back to the previous cursor position. print!("\x1b8"); std::io::stdout().flush().unwrap(); } // TODO: Need to figure out when to use this. fn error(&mut self, _pkgname: String, _steps_done: u64, _total_steps: u64, _error: String) {} } #[cxx::bridge] pub(crate) mod raw { extern "Rust" { type AcquireProgress<'a>; type OperationProgress<'a>; type InstallProgress<'a>; /// Called when an operation has been updated. fn update(self: &mut OperationProgress, operation: String, percent: f32); /// Called when an operation has finished. fn done(self: &mut OperationProgress); /// Called when the install status has changed. fn status_changed( self: &mut InstallProgress, pkgname: String, steps_done: u64, total_steps: u64, action: String, ); // TODO: What kind of errors can be returned here? // Research and update higher level structs as well // TODO: Create custom errors when we have better information fn error( self: &mut InstallProgress, pkgname: String, steps_done: u64, total_steps: u64, error: String, ); /// Called on c++ to set the pulse interval. fn pulse_interval(self: &mut AcquireProgress) -> usize; /// Called when an item is confirmed to be up-to-date. fn hit(self: &mut AcquireProgress, item: &ItemDesc); /// Called when an Item has started to download fn fetch(self: &mut AcquireProgress, item: &ItemDesc); /// Called when an Item fails to download fn fail(self: &mut AcquireProgress, item: &ItemDesc); /// Called periodically to provide the overall progress information fn pulse(self: &mut AcquireProgress, owner: &PkgAcquire); /// Called when an item is successfully and completely fetched. fn done(self: &mut AcquireProgress, item: &ItemDesc); /// Called when progress has started fn start(self: &mut AcquireProgress); /// Called when progress has finished fn stop(self: &mut AcquireProgress); } extern "C++" { type ItemDesc = crate::acquire::raw::ItemDesc; type PkgAcquire = crate::acquire::raw::PkgAcquire; include!("rust-apt/apt-pkg-c/types.h"); } } rust-apt-0.8.0/src/records.rs000064400000000000000000000167501046102023000142170ustar 00000000000000//! Allows access to complete package description records directly from the //! file. use std::cell::{Ref, RefCell}; use cxx::UniquePtr; // TODO: Probably just make this a real enum // we an add a variant RecordField::String("Package".to_string()) // or something like that. /// A module containing [`&str`] constants for known record fields /// /// Pass through to the [`crate::Version::get_record`] method /// or you can use a custom [`&str`] like the ones listed below. /// /// Other Known Record Keys: /// /// `Conffiles` `Status` `Python-Version` `Auto-Built-Package` /// `Enhances` `Cnf-Extra-Commands` `Gstreamer-Elements` /// `Gstreamer-Encoders` `Lua-Versions` `Original-Maintainer` `Protected` /// `Gstreamer-Uri-Sources` `Vendor` `Build-Ids` `Efi-Vendor` `SHA512` /// `Build-Essential` `Important` `X-Cargo-Built-Using` /// `Cnf-Visible-Pkgname` `Gstreamer-Decoders` `SHA1` `Gstreamer-Uri-Sinks` /// `Gstreamer-Version` `Ghc-Package` `Static-Built-Using` /// `Postgresql-Catversion` `Python-Egg-Name` `Built-Using` `License` /// `Cnf-Ignore-Commands` `Go-Import-Path` `Ruby-Versions` #[allow(non_upper_case_globals, non_snake_case)] pub mod RecordField { /// Name of the package `apt` pub const Package: &str = "Package"; /// The name of the source package and the version if it exists /// `zsh (5.9-1)` // TODO: We need to write a parser to be able to handle this properly // The apt source that does this is in debrecords.cc pub const Source: &str = "Source"; /// Version of the package `2.5.2` pub const Version: &str = "Version"; /// The unpacked size in KiB? `4352` pub const InstalledSize: &str = "Installed-Size"; /// The homepage of the software /// `https://gitlab.com/volian/rust-apt` pub const Homepage: &str = "Homepage"; /// If the package is essential `yes` pub const Essential: &str = "Essential"; /// The Maintainer of the package /// `APT Development Team ` pub const Maintainer: &str = "Maintainer"; /// The Original Maintainer of the package. /// Most common to see on Ubuntu packages repackaged from Debian /// `APT Development Team ` pub const OriginalMaintainer: &str = "Original-Maintainer"; /// The Architecture of the package `amd64` pub const Architecture: &str = "Architecture"; /// Packages that this one replaces /// `apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~)` pub const Replaces: &str = "Replaces"; /// Packages that this one provides /// `apt-transport-https (= 2.5.2)` pub const Provides: &str = "Provides"; /// Packages that must be installed and configured before this one /// `libc6 (>= 2.34), libtinfo6 (>= 6)` pub const PreDepends: &str = "Pre-Depends"; /// Packages this one depends on /// `adduser, gpgv | gpgv2 | gpgv1, libapt-pkg6.0 (>= 2.5.2)` pub const Depends: &str = "Depends"; /// Packages that are recommended to be installed with this one /// `ca-certificates` pub const Recommends: &str = "Recommends"; /// Packages that are suggested to be installed with this one /// `apt-doc, aptitude | synaptic | wajig, dpkg-dev (>= 1.17.2)` pub const Suggests: &str = "Suggests"; /// Packages that are broken by installing this. /// `apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~)` pub const Breaks: &str = "Breaks"; /// Packages that conflict with this one /// `bash-completion (<< 20060301-0)` pub const Conflicts: &str = "Conflicts"; /// The raw description of the package /// `commandline package manager` pub const Description: &str = "Description"; /// The MD5 sum of the description /// `9fb97a88cb7383934ef963352b53b4a7` pub const DescriptionMD5: &str = "Description-md5"; /// Any tags associated with this package /// `admin::package-management, devel::lang:ruby, hardware::storage` pub const Tag: &str = "Tag"; /// The type of multi arch for the package. /// Either `allowed`, `foreign`, or `same` pub const MultiArch: &str = "Multi-Arch"; /// The section of the package `admin` pub const Section: &str = "Section"; /// The Priority of the package `required` pub const Priority: &str = "Priority"; /// The raw filename of the package /// `pool/main/a/apt/apt_2.5.2_amd64.deb` pub const Filename: &str = "Filename"; /// The compressed size of the .deb in bytes `1500520` pub const Size: &str = "Size"; /// The MD5 sum of the package `8797c5716952fba7779bd072e53acee5` pub const MD5sum: &str = "MD5sum"; /// The SHA256 sum of the package /// `a6dd99a52ec937faa20e1617da36b8b27a2ed8bc9300bf7eb8404041ede52200` pub const SHA256: &str = "SHA256"; } pub struct PackageRecords { pub(crate) ptr: UniquePtr, parser: RefCell>, index: RefCell, } impl PackageRecords { pub fn new(ptr: UniquePtr) -> PackageRecords { PackageRecords { ptr, parser: RefCell::new(UniquePtr::null()), index: RefCell::new(0), } } fn replace_index(&self, index: u64) -> bool { if self.index.borrow().eq(&index) { return false; } self.index.replace(index); true } fn parser(&self) -> Ref> { if self.parser.borrow().is_null() { panic!("You must call ver_lookup or desc_lookup first!") } self.parser.borrow() } pub fn ver_lookup(&self, file: &raw::VerFileIterator) -> &PackageRecords { if self.replace_index(file.index()) { unsafe { self.parser.replace(self.ptr.ver_lookup(file)) }; } self } pub fn desc_lookup(&self, file: &raw::DescIterator) -> &PackageRecords { if self.replace_index(file.index()) { unsafe { self.parser.replace(self.ptr.desc_lookup(file)) }; } self } pub fn short_desc(&self) -> Option { self.parser().short_desc().ok() } pub fn long_desc(&self) -> Option { self.parser().long_desc().ok() } pub fn filename(&self) -> String { self.parser().filename() } pub fn get_field(&self, field: String) -> Option { self.parser().get_field(field).ok() } pub fn hash_find(&self, hash_type: String) -> Option { self.parser().hash_find(hash_type).ok() } } #[cxx::bridge] pub(crate) mod raw { impl UniquePtr {} unsafe extern "C++" { include!("rust-apt/apt-pkg-c/records.h"); type PkgRecords; type Parser; type IndexFile; type VerFileIterator = crate::iterators::VerFileIterator; type DescIterator = crate::iterators::DescIterator; /// Move the records into the correct spot for the Version File. /// /// # Safety /// /// The returned Parser can not out live the records struct. /// If you hold a Parser and lookup another file, the data that parser /// returns will change. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn ver_lookup(self: &PkgRecords, ver_file: &VerFileIterator) -> UniquePtr; /// Move the records into the correct spot for the Description File. /// /// # Safety /// /// The returned Parser can not out live the records struct. /// If you hold a Parser and lookup another file, the data that parser /// returns will change. /// /// The returned UniquePtr cannot outlive the cache. unsafe fn desc_lookup(self: &PkgRecords, desc_file: &DescIterator) -> UniquePtr; pub fn filename(self: &Parser) -> String; pub fn long_desc(self: &Parser) -> Result; pub fn short_desc(self: &Parser) -> Result; pub fn get_field(self: &Parser, field: String) -> Result; pub fn hash_find(self: &Parser, hash_type: String) -> Result; pub fn archive_uri(self: &IndexFile, filename: &str) -> String; /// Return true if the IndexFile is trusted. pub fn is_trusted(self: &IndexFile) -> bool; } } rust-apt-0.8.0/src/tagfile.rs000064400000000000000000000150321046102023000141610ustar 00000000000000//! Contains structs and functions to parse Debian-styled RFC 822 files. use core::iter::Iterator; use std::collections::HashMap; use std::fmt; #[derive(Debug)] /// The result of a parsing error. pub struct ParserError { pub msg: String, pub line: Option, } impl fmt::Display for ParserError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(num) = self.line { write!(f, "{} at line '{num}'", self.msg)? } else { write!(f, "{}", self.msg)? } Ok(()) } } impl std::error::Error for ParserError {} /// A section in a TagFile. A TagFile is made up of double-newline (`\n\n`) /// separated paragraphs, each of which make up one of these sections. #[derive(Debug)] pub struct TagSection { data: HashMap, } impl TagSection { fn error(msg: &str, line: Option) -> Result { Err(ParserError { msg: "E:".to_owned() + msg, line, }) } fn line_is_key(line: &str) -> bool { !line.starts_with(' ') && !line.starts_with('\t') } fn next_line_extends_value(lines: &[&str], current_line: usize) -> bool { if let Some(next_line) = lines.get(current_line + 1) { !Self::line_is_key(next_line) } else { false } } /// Create a new [`TagSection`] instance. /// # Returns /// * A [`Result`]: The [`Ok`] variant if there was no issue parsing the /// section, and the [`Err`] variant if there was. pub fn new(section: &str) -> Result { // Make sure the string doesn't contain multiple sections. if section.contains("\n\n") { return Self::error("More than one section was found", None); } // Make sure the user didn't pass an empty string. if section.is_empty() { return Self::error("An empty string was passed", None); } // Start building up the HashMap. let mut data = HashMap::new(); let lines = section.lines().collect::>(); // Variables used while parsing. let mut current_key: Option = None; let mut current_value = String::new(); for (index, line) in lines.iter().enumerate() { // Indexes start at 0, so increase by 1 to get the line number. let line_number = index + 1; // If this line starts with a comment ignore it. if line.starts_with('#') { continue; } // If this line is a new key, split the line into the key and its value. if Self::line_is_key(line) { let (key, value) = match line.split_once(':') { Some((key, value)) => { (key.to_string(), value.strip_prefix(' ').unwrap_or(value)) }, None => { return Self::error( "Line doesn't contain a ':' separator", Some(line_number), ); }, }; // Set the current key and value. // If the value is empty, then this is a multiline field, and it's going to be // one of these things: // 1. A multiline field, in which case we want to add a // newline to reflect such. // 2. A key with an empty value, in which case it will // be removed post-processing. current_key = Some(key); if value.is_empty() { current_value = "\n".to_string(); } else { current_value = value.to_string(); // If the next extends the value, add the newline before it. if Self::next_line_extends_value(&lines, index) { current_value += "\n"; } } } // If this line is indented with spaces or tabs, add it to the current value. // This should never end up running in conjunction with the above `if` block. if line.starts_with(' ') || line.starts_with('\t') { current_value += line; // If the next line extends the value, add the newline. `line_number` // conveniently is the next index, so use that to our advantage. if Self::next_line_extends_value(&lines, index) { current_value += "\n"; } } // If the next line is a new key or this is the last line, add the current key // and value to the HashMap. `line_number` conveniently is the next index, so // use that to our advantage. if !Self::next_line_extends_value(&lines, index) { // If no key exists, we've defined a paragraph (at the beginning of the control // file) with no key. This would be parsed at the very beginning, but the file // may have an unknown amount of comment lines, so we just do this here as a // normal step of the parsing stage. if current_key.is_none() { return Self::error( "No key defined for the currently indented line", Some(line_number), ); } // Add the key and reset the `current_key` and `current_value` counters. data.insert(current_key.unwrap(), current_value); current_key = None; current_value = String::new(); } } Ok(Self { data }) } /// Get the underlying [`HashMap`] used in the generated [`TagSection`]. pub fn hashmap(&self) -> &HashMap { &self.data } /// Get the value of the specified key. pub fn get(&self, key: &str) -> Option<&String> { self.data.get(key) } /// Get the value of the specified key, /// /// Returns specified default on failure. pub fn get_default<'a, 'b: 'a>(&'a self, key: &str, default: &'b str) -> &str { if let Some(value) = self.data.get(key) { return value; } default } } /// Parses a TagFile: these are files such as Debian `control` and `Packages` /// files. /// /// # Returns /// * A [`Result`]: The [`Ok`] variant containing the vector of [`TagSection`] /// objects if there was no issue parsing the file, and the [`Err`] variant if /// there was. pub fn parse_tagfile(content: &str) -> Result, ParserError> { let mut sections = vec![]; let section_strings = content.split("\n\n"); for (iter, section) in section_strings.clone().enumerate() { // If this section is empty (i.e. more than one empty line was placed between // each section), then ignore this section. if section.is_empty() || section.chars().all(|c| c == '\n') { break; } match TagSection::new(section) { Ok(section) => sections.push(section), Err(mut err) => { // If an error line was provided, add the number of lines in the sections before // this one. Otherwise no line was specified, and we'll just specify the number // of lines in the section before this one so we know which section the line is // in. let mut line_count = 0; for _ in 0..iter { // Add one for the line separation between each section. line_count += 1; // Add the line count in this section. line_count += section_strings.clone().count(); } if let Some(line) = err.line { err.line = Some(line_count + line); } else { err.line = Some(line_count); } }, } } Ok(sections) } rust-apt-0.8.0/src/util.rs000064400000000000000000000203211046102023000135200ustar 00000000000000//! Contains miscellaneous helper utilities. use std::cmp::Ordering; use terminal_size::{terminal_size, Height, Width}; use crate::error::AptErrors; use crate::{config, Cache, DepFlags, Package}; /// Get the terminal's height, i.e. the number of rows it has. /// /// # Returns: /// * The terminal height, or `24` if it cannot be determined. pub fn terminal_height() -> usize { if let Some((_, Height(rows))) = terminal_size() { usize::from(rows) } else { 24 } } /// Get the terminal's width, i.e. the number of columns it has. /// /// # Returns: /// * The terminal width, or `80` if it cannot be determined. pub fn terminal_width() -> usize { if let Some((Width(cols), _)) = terminal_size() { usize::from(cols) } else { 80 } } /// Compares two package versions, `ver1` and `ver2`. The returned enum variant /// applies to the first version passed in. /// /// # Examples /// ``` /// use rust_apt::util::cmp_versions; /// use std::cmp::Ordering; /// /// let ver1 = "5.0"; /// let ver2 = "6.0"; /// let result = cmp_versions(ver1, ver2); /// /// assert_eq!(Ordering::Less, result); /// ``` pub fn cmp_versions(ver1: &str, ver2: &str) -> Ordering { let result = raw::cmp_versions(ver1, ver2); match result { _ if result < 0 => Ordering::Less, _ if result == 0 => Ordering::Equal, _ => Ordering::Greater, } } /// Disk Space that `apt` will use for a transaction. pub enum DiskSpace { /// Additional Disk Space required. Require(u64), /// Disk Space that will be freed Free(u64), } /// Numeral System for unit conversion. pub enum NumSys { /// Base 2 | 1024 | KibiByte (KiB) Binary, /// Base 10 | 1000 | KiloByte (KB) Decimal, } /// Converts bytes into human readable output. /// /// ``` /// use rust_apt::new_cache; /// use rust_apt::util::{unit_str, NumSys}; /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("apt").unwrap(); /// let version = pkg.candidate().unwrap(); /// /// println!("{}", unit_str(version.size(), NumSys::Decimal)); /// ``` pub fn unit_str(val: u64, base: NumSys) -> String { let val = val as f64; let (num, tera, giga, mega, kilo) = match base { NumSys::Binary => (1024.0_f64, "TiB", "GiB", "MiB", "KiB"), NumSys::Decimal => (1000.0_f64, "TB", "GB", "MB", "KB"), }; let powers = [ (num.powi(4), tera), (num.powi(3), giga), (num.powi(2), mega), (num, kilo), ]; for (divisor, unit) in powers { if val > divisor { return format!("{:.2} {unit}", val / divisor); } } format!("{val} B") } /// Converts seconds into a human readable time string. pub fn time_str(seconds: u64) -> String { if seconds > 60 * 60 * 24 { return format!( "{}d {}h {}min {}s", seconds / 60 / 60 / 24, (seconds / 60 / 60) % 24, (seconds / 60) % 60, seconds % 60, ); } if seconds > 60 * 60 { return format!( "{}h {}min {}s", (seconds / 60 / 60) % 24, (seconds / 60) % 60, seconds % 60, ); } if seconds > 60 { return format!("{}min {}s", (seconds / 60) % 60, seconds % 60,); } format!("{seconds}s") } /// Get an APT-styled progress bar. /// /// # Returns: /// * [`String`] representing the progress bar. /// /// # Example: /// ``` /// use rust_apt::util::get_apt_progress_string; /// let progress = get_apt_progress_string(0.5, 10); /// assert_eq!(progress, "[####....]"); /// ``` pub fn get_apt_progress_string(percent: f32, output_width: u32) -> String { raw::get_apt_progress_string(percent, output_width) } /// Lock the APT lockfile. /// This should be done before modifying any APT files /// such as with [`crate::cache::Cache::update`] /// and then [`apt_unlock`] should be called after. /// /// This Function Requires root /// /// If [`apt_lock`] is called `n` times, [`apt_unlock`] must also be called `n` /// times to release all acquired locks. /// /// # Known Error Messages: /// * `E:Could not open lock file /var/lib/dpkg/lock-frontend - open (13: /// Permission denied)` /// * `E:Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), /// are you root?` pub fn apt_lock() -> Result<(), AptErrors> { config::init_config_system(); Ok(raw::apt_lock()?) } /// Unlock the APT lockfile. pub fn apt_unlock() { config::init_config_system(); raw::apt_unlock() } /// Unlock the Dpkg lockfile. /// This should be done before manually running /// [`crate::cache::Cache::do_install`] /// and then [`apt_unlock_inner`] should be called after. /// /// This Function Requires root pub fn apt_lock_inner() -> Result<(), AptErrors> { config::init_config_system(); Ok(raw::apt_lock_inner()?) } /// Unlock the Dpkg lockfile. pub fn apt_unlock_inner() { config::init_config_system(); raw::apt_unlock_inner() } /// Checks if any locks are currently active for the lockfile. Note that this /// will only return [`true`] if the current process has an active lock, while /// calls to [`apt_lock`] will return an [`AptErrors`] if another process has an /// active lock. pub fn apt_is_locked() -> bool { config::init_config_system(); raw::apt_is_locked() } /// Reference implementation to print broken packages just like apt does. /// /// ## Returns [`None`] if the package is not considered broken /// /// ## now: /// * [true] = When checking broken packages before modifying the cache. /// * [false] = When checking broken packages after modifying the cache. pub fn show_broken_pkg(cache: &Cache, pkg: &Package, now: bool) -> Option { // If the package isn't broken for the state Return None if (now && !pkg.is_now_broken()) || (!now && !pkg.is_inst_broken()) { return None; }; let mut broken_string = String::new(); broken_string += &format!(" {pkg} :"); // Pick the proper version based on now status. // else Return with just the package name like Apt does. let Some(ver) = (match now { true => pkg.installed(), false => pkg.install_version(), }) else { broken_string += "\n"; return Some(broken_string); }; let indent = pkg.name().len() + 3; let mut first = true; // ShowBrokenDeps for dep in ver.depends_map().values().flatten() { for (i, base_dep) in dep.iter().enumerate() { if !cache.depcache().is_important_dep(base_dep) { continue; } let dep_flag = if now { DepFlags::DepGNow } else { DepFlags::DepInstall }; if cache.depcache().dep_state(base_dep) & dep_flag == dep_flag { continue; } if !first { broken_string += &" ".repeat(indent); } first = false; // If it's the first or Dep if i > 0 { broken_string += &" ".repeat(base_dep.dep_type().as_ref().len() + 3); } else { broken_string += &format!(" {}: ", base_dep.dep_type()) } broken_string += base_dep.target_package().name(); if let (Ok(ver_str), Some(comp)) = (base_dep.target_ver(), base_dep.comp_type()) { broken_string += &format!(" ({comp} {ver_str})"); } let target = base_dep.target_package(); if !target.has_provides() { if let Some(target_ver) = target.install_version() { broken_string += &format!(" but {target_ver} is to be installed") } else if target.candidate().is_some() { broken_string += " but it is not going to be installed"; } else if target.has_provides() { broken_string += " but it is a virtual package"; } else { broken_string += " but it is not installable"; } } if i + 1 != dep.len() { broken_string += " or" } broken_string += "\n"; } } Some(broken_string) } #[cxx::bridge] pub(crate) mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/util.h"); /// Compares two package versions, `ver1` and `ver2`. The returned /// integer's value is mapped to one of the following integers: /// - Less than 0: `ver1` is less than `ver2`. /// - Equal to 0: `ver1` is equal to `ver2`. /// - Greater than 0: `ver1` is greater than `ver2`. /// /// Unless you have a specific need for otherwise, you should probably /// use [`crate::util::cmp_versions`] instead. pub fn cmp_versions(ver1: &str, ver2: &str) -> i32; /// Return an APT-styled progress bar (`[####..]`). pub fn get_apt_progress_string(percent: f32, output_width: u32) -> String; /// Lock the lockfile. pub fn apt_lock() -> Result<()>; /// Unock the lockfile. pub fn apt_unlock(); /// Lock the Dpkg lockfile. pub fn apt_lock_inner() -> Result<()>; /// Unlock the Dpkg lockfile. pub fn apt_unlock_inner(); /// Check if the lockfile is locked. pub fn apt_is_locked() -> bool; } } rust-apt-0.8.0/tests/cache.rs000064400000000000000000000446671046102023000142040ustar 00000000000000mod cache { use std::collections::HashMap; use std::fmt::Write as _; use cxx::{CxxVector, UniquePtr}; use rust_apt::cache::*; use rust_apt::raw::{create_acquire, IntoRawIter, ItemDesc}; use rust_apt::util::*; use rust_apt::{new_cache, DepType}; // This is a manual test. I don't know a good way to dynamically test this // Maybe by installing a test-deb with certain depends and checking the // packages? #[test] fn auto_removeable() { let cache = new_cache!().unwrap(); let sort = PackageSort::default().auto_removable().names(); for pkg in cache.packages(&sort) { println!("{}", pkg.name()) } } #[test] fn time_cache_iter() { let cache = new_cache!().unwrap(); let sort = PackageSort::default().include_virtual(); use std::time::Instant; let now = Instant::now(); dbg!(cache.packages(&sort).count()); let elapsed = now.elapsed(); println!("Elapsed: {:.2?}", elapsed); let now = Instant::now(); dbg!(cache.iter().count()); let elapsed = now.elapsed(); println!("Elapsed: {:.2?}", elapsed); let now = Instant::now(); dbg!(unsafe { cache.begin().raw_iter().count() }); let elapsed = now.elapsed(); println!("Elapsed: {:.2?}", elapsed); } #[test] fn with_debs() { let cache = new_cache!(&[ "tests/files/cache/apt.deb", "tests/files/cache/dep-pkg1_0.0.1.deb", ]) .unwrap(); cache.get("apt").unwrap().get_version("5000:1.0.0").unwrap(); cache.get("dep-pkg1").unwrap(); assert!(new_cache!(&["tests/files/this-file-doesnt-exist.deb"]).is_err()); // Check if it errors on a garbage empty file as well // signal: 11, SIGSEGV: invalid memory reference assert!(new_cache!(&["tests/files/cache/pkg.deb"]).is_err()); } #[test] fn with_packages() { let cache = new_cache!(&["tests/files/cache/Packages",]).unwrap(); cache.get("apt").unwrap().get_version("5000:1.0.0").unwrap(); cache.get("broken-or-dep").unwrap(); cache.get("dep-pkg1").unwrap().get_version("0.0.1").unwrap(); cache.get("dep-pkg1").unwrap().get_version("0.0.2").unwrap(); cache.get("dep-pkg2").unwrap(); cache.get("no-description").unwrap(); assert!(new_cache!(&["tests/files/cache/Packages.gz"]).is_err()); } #[test] fn empty_deps() { // This would fail before https://gitlab.com/volian/rust-apt/-/merge_requests/29 let cache = new_cache!().unwrap(); let sort = PackageSort::default(); // Iterate through all of the package and versions for pkg in cache.packages(&sort) { for version in pkg.versions() { // Call depends_map to check for panic on null dependencies. version.depends_map(); } } } #[test] fn parent_pkg() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); let version = pkg.versions().next().unwrap(); let parent = version.parent(); assert_eq!(pkg.index(), parent.index()) } #[test] fn get_version() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); // The candidate for apt surely exists. let cand = pkg.candidate().unwrap(); assert!(pkg.get_version(cand.version()).is_some()); // I sure hope this doesn't exist. assert!(pkg.get_version("9.0.0.1").is_none()); } #[test] fn all_packages() { let cache = new_cache!().unwrap(); let sort = PackageSort::default(); // All real packages should not be empty. assert!(cache.packages(&sort).next().is_some()); for pkg in cache.packages(&sort) { // impl display?? // println!("{pkg}") println!("{}:{}", pkg.name(), pkg.arch()) } } #[test] fn descriptions() { let cache = new_cache!().unwrap(); // Apt should exist let pkg = cache.get("apt").unwrap(); // Apt should have a candidate let cand = pkg.candidate().unwrap(); // Apt should be installed let inst = pkg.installed().unwrap(); // Assign installed descriptions let inst_sum = inst.summary(); let inst_desc = inst.description(); // Assign candidate descriptions let cand_sum = cand.summary(); let cand_desc = cand.description(); // If the lookup fails for whatever reason // The summary and description are the same assert_ne!(inst_sum, inst_desc); assert_ne!(cand_sum, cand_desc); dbg!(inst_desc); dbg!(cand_desc); } // This should not segfault, but has in the past. // See https://gitlab.com/volian/rust-apt/-/issues/28 #[test] fn no_description() { let cache = new_cache!(&["tests/files/cache/no-description_0.0.1.deb"]).unwrap(); let pkg = cache.get("no-description").unwrap(); let cand = pkg.candidate().unwrap(); if let Some(desc) = cand.description() { println!("{desc}"); } } #[test] fn version_uris() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); // Only test the candidate. // It's possible for the installed version to have no uris let cand = pkg.candidate().unwrap(); assert!(cand.uris().next().is_some()); dbg!(cand.uris().collect::>()); } #[test] fn depcache_marked() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); assert!(!pkg.marked_install()); assert!(!pkg.marked_upgrade()); assert!(!pkg.marked_delete()); assert!(pkg.marked_keep()); assert!(!pkg.marked_downgrade()); assert!(!pkg.marked_reinstall()); assert!(!pkg.is_now_broken()); assert!(!pkg.is_inst_broken()); } #[test] fn hashes() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); // Apt could be installed, and the package no longer exists // In the cache. For this case we grab the candidate so it won't fail. let version = pkg.candidate().unwrap(); assert!(version.sha256().is_some()); assert!(version.hash("sha256").is_some()); assert!(version.sha512().is_none()); assert!(version.hash("md5sum").is_some()); assert!(version.hash("sha1").is_none()) } #[test] fn shortname() { let cache = new_cache!().unwrap(); let sort = PackageSort::default(); for pkg in cache.packages(&sort) { assert!(!pkg.name().contains(':')) } } #[test] fn depends() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); let cand = pkg.candidate().unwrap(); // Apt candidate should have dependencies for deps in cand.dependencies().unwrap() { for dep in deps.iter() { // Apt Dependencies should have targets assert!(dep.all_targets().first().is_some()); } } assert!(cand.recommends().is_some()); assert!(cand.suggests().is_some()); // TODO: Add these as methods assert!(cand.get_depends(&DepType::Replaces).is_some()); // This test seems to work on Debian Sid desktop systems, but not in a Debian // Sid Docker container (and potentially other distros too). Leaving this // commented out until a solution is found. // assert!(cand.get_depends("Conflicts").is_some()); assert!(cand.get_depends(&DepType::DpkgBreaks).is_some()); // This part is basically just formatting an apt depends String // Like you would see in `apt show` let mut dep_str = String::new(); dep_str.push_str("Depends: "); dbg!(cand.depends_map().get(&DepType::Depends).unwrap()); for dep in cand.depends_map().get(&DepType::Depends).unwrap() { if dep.is_or() { let mut or_str = String::new(); let total = dep.len() - 1; for (num, base_dep) in dep.iter().enumerate() { or_str.push_str(base_dep.name()); if let Some(comp) = base_dep.comp_type() { let _ = write!(or_str, "({} {})", comp, base_dep.version().unwrap()); } if num != total { or_str.push_str(" | "); } else { or_str.push_str(", "); } } dep_str.push_str(&or_str) } else { let lone_dep = dep.first(); dep_str.push_str(lone_dep.name()); if let Some(comp) = lone_dep.comp_type() { let _ = write!(dep_str, " ({} {})", comp, lone_dep.version().unwrap()); } dep_str.push_str(", "); } } println!("{dep_str}"); } #[test] fn test_hashmap() { let cache = new_cache!().unwrap(); // clippy thinks that the package is mutable // But it only hashes the ID and you can't really mutate a version #[allow(clippy::mutable_key_type)] let mut pkg_map = HashMap::new(); // clippy thinks that the version is mutable // But it only hashes the ID and you can't really mutate a version #[allow(clippy::mutable_key_type)] let mut ver_map = HashMap::new(); let sort = PackageSort::default(); // Iterate the package cache and add them to a hashmap for pkg in cache.packages(&sort) { let value = pkg.arch().to_string(); pkg_map.insert(pkg, value); } // Iterate the package map and add all the candidates into a hashmap for (pkg, _arch) in pkg_map.iter() { if let Some(cand) = pkg.candidate() { let value = cand.arch().to_string(); ver_map.insert(cand, value); } } // Doesn't need an assert. It won't compile // if the structs can't go into a hashmap } #[test] fn debug_interfaces() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); println!("{pkg:?}"); println!("{pkg:#?}"); let cand = pkg.candidate().unwrap(); println!("{cand:?}"); println!("{cand:#?}"); for dep_vec in cand.depends_map().values() { for dep in dep_vec { println!("{dep:#?}"); } } let pkg = cache.get("python3:any").unwrap(); for provider in pkg.provides() { println!("{provider:#?}") } } #[test] fn display_interfaces() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); println!("{pkg}"); let cand = pkg.candidate().unwrap(); println!("{cand}"); for dep_vec in cand.depends_map().values() { for dep in dep_vec { println!("{dep}"); } } let pkg = cache.get("python3:any").unwrap(); for provider in pkg.provides() { println!("{provider}") } } #[test] fn parent_dep() { let cache = new_cache!().unwrap(); let sort = PackageSort::default(); for pkg in cache.packages(&sort) { // Iterate over the reverse depends // Iterating rdepends could segfault. // See: https://gitlab.com/volian/rust-apt/-/merge_requests/36 for deps in pkg.rdepends().values() { for dep in deps { let base_dep = dep.first(); // Reverse Dependencies always have a version base_dep.version().unwrap(); } } // There should be a candidate to iterate its regular deps if let Some(cand) = pkg.candidate() { if let Some(deps) = cand.dependencies() { for dep in &deps { let base_dep = dep.first(); // Regular deps do not always have a version base_dep.version(); } } } } } #[test] fn provides_list() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); let cand = pkg.candidate().unwrap(); let provides_list: Vec<_> = cand.provides().collect(); assert!(provides_list.len() == 1); // 'apt' seems to always provide for 'apt-transport-https' at APT's version. // If it ever doesn't, this test will break. let provide = provides_list.first().unwrap(); assert!(provide.name() == "apt-transport-https"); assert!(provide.version_str().unwrap() == cand.version()); } // This Test is for https://gitlab.com/volian/rust-apt/-/issues/24 // TODO: refactor and enable this test so it can run in the CI to make sure we // don't regress. We need to get the lists dir from the apt config, and then // maybe pick a random InRelease file Back that up, do the editing and then // restore it at the end of the test. cache.packages should be an error and not // segfault. // // #[test] // fn test_segfault() { // use std::io::Write; // let mut f = std::fs::OpenOptions::new() // .write(true) // .append(true) // .open("/var/lib/apt/lists/deb.debian.org_debian_dists_sid_InRelease") // .unwrap(); // f.write_all(b"\ndsadasdasdas\n").unwrap(); // f.flush().unwrap(); // drop(f); // let cache = new_cache!().unwrap(); // let sort = PackageSort::default(); // assert!(cache.packages(&sort).is_err()) /// This test is tied pretty closely to the currently available versions in /// the Ubuntu/Debian repos. Feel free to adjust if you can verify its /// needed. #[test] fn rev_provides_list() { // Test concrete packages with provides. let cache = new_cache!().unwrap(); let apt = cache.get("apt").unwrap(); let ver = apt.candidate().unwrap(); let pkg = cache.get("apt-transport-https").unwrap(); { let mut rev_provides_list = pkg.provides(); let provides_pkg = rev_provides_list.next().unwrap(); assert!(rev_provides_list.next().is_none()); let parent = unsafe { provides_pkg.target_pkg() }; assert!(parent.name().contains("apt")); assert_eq!(provides_pkg.version_str().unwrap(), ver.version()); } { let mut rev_provides_list = pkg.provides().filter(|p| match p.version_str() { Err(_) => false, Ok(version) => version == ver.version(), }); let provides_pkg = rev_provides_list.next().unwrap(); assert!(rev_provides_list.next().is_none()); let parent = unsafe { provides_pkg.target_pkg() }; assert_eq!(parent.name(), "apt"); assert_eq!(provides_pkg.version_str().unwrap(), ver.version()); } { let mut rev_provides_list = pkg.provides().filter(|p| match p.version_str() { Err(_) => false, Ok(version) => version == "50000000000.0.0", }); assert!(rev_provides_list.next().is_none()); } // Test a virtual package with provides. { let pkg = cache.get("www-browser").unwrap(); assert!(pkg.provides().next().is_some()); } } #[test] fn sources() { let cache = new_cache!().unwrap(); let uris: UniquePtr>; let acquire = unsafe { create_acquire() }; cache.get_indexes(&acquire); // If the source lists don't exists there is problems. uris = unsafe { acquire.uris() }; assert!(!uris.is_empty()); // This is an example of why the uri function is unsafe. // uncommenting drop will cause it to SIBABRT // drop(acquire); for item in uris.iter() { println!("{} = {}", item.uri(), item.owner().dest_file()) } } #[test] fn cache_count() { let cache = new_cache!().unwrap(); match cache.depcache().disk_size() { DiskSpace::Require(num) => { assert_eq!(num, 0); }, DiskSpace::Free(num) => { panic!("Whoops it should be 0, not {num}."); }, } } #[test] fn test_unit_str() { let testcase = [ (1649267441664_u64, "1.50 TiB", "1.65 TB"), (1610612736_u64, "1.50 GiB", "1.61 GB"), (1572864_u64, "1.50 MiB", "1.57 MB"), (1536_u64, "1.50 KiB", "1.54 KB"), (1024_u64, "1024 B", "1.02 KB"), (1_u64, "1 B", "1 B"), ]; for (num, binary, decimal) in testcase { assert_eq!(binary, unit_str(num, NumSys::Binary)); assert_eq!(decimal, unit_str(num, NumSys::Decimal)); } } #[test] // This test relies on the version of 'apt' being higher than 'dpkg'. fn version_comparisons() { let cache = new_cache!().unwrap(); let apt = cache.get("apt").unwrap(); let dpkg = cache.get("dpkg").unwrap(); let apt_ver = apt.candidate().unwrap(); let dpkg_ver = dpkg.candidate().unwrap(); assert!(apt_ver > dpkg_ver); assert!(dpkg_ver < apt_ver); assert!(apt_ver != dpkg_ver); } #[test] // This test relies on 'neofetch' and 'gsasl-common' not being installed. fn good_resolution() { let cache = new_cache!().unwrap(); let pkg = cache.get("neofetch").unwrap(); pkg.mark_install(true, true); pkg.protect(); cache.resolve(false).unwrap(); let pkg2 = cache.get("gsasl-common").unwrap(); pkg2.mark_install(true, true); assert!(pkg2.marked_install()) } // For now `zeek` has broken dependencies so the resolver errors. // If this test fails, potentially find a reason. #[test] fn bad_resolution() { let cache = new_cache!().unwrap(); let pkg = cache.get("zeek").unwrap(); pkg.mark_install(false, true); pkg.protect(); assert!(cache.resolve(false).is_err()); } #[test] fn depcache_clear() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); pkg.mark_delete(true); assert!(pkg.marked_delete()); cache.depcache().clear_marked().unwrap(); assert!(!pkg.marked_delete()); } #[test] fn origins() { let cache = new_cache!().unwrap(); let apt = cache.get("apt").unwrap(); let apt_ver = apt.candidate().unwrap(); let pkg_files = apt_ver.package_files().collect::>(); // Package files should not be empty if we got a candidate from `apt`. assert!(!pkg_files.is_empty()); for pkg_file in pkg_files { // Apt should have all of these blocks in the package file. assert!(pkg_file.filename().is_some()); assert!(pkg_file.archive().is_some()); println!("{}", pkg_file.filename().unwrap()); if pkg_file.is_downloadable() { assert!(pkg_file.origin().is_some()); assert!(pkg_file.codename().is_some()); assert!(pkg_file.label().is_some()); assert!(pkg_file.site().is_some()); assert!(pkg_file.arch().is_some()); } // These should be okay regardless. assert!(pkg_file.component().is_some()); assert!(pkg_file.index_type().is_some()); // Index should not be 0. assert_ne!(pkg_file.index(), 0); // Apt should likely be from a trusted repository. assert!(pkg_file.index_file().is_trusted()); // Print it in case I want to see. // println!("{pkg_file}"); } } #[test] fn depcache_install_ver() { let cache = new_cache!(&[ "tests/files/cache/dep-pkg1_0.0.1.deb", "tests/files/cache/dep-pkg1_0.0.2.deb", ]) .unwrap(); let pkg = cache.get("dep-pkg1").unwrap(); pkg.mark_install(false, false); // This package is not installed, only marked assert!(pkg.installed().is_none()); // This package is not installed // but this will return the version to be installed let install_ver = pkg.install_version().unwrap(); // The version should match the latest because it's the default candidate. assert!(install_ver.version() == "0.0.2"); let old_ver = pkg.get_version("0.0.1").unwrap(); old_ver.set_candidate(); pkg.mark_install(false, false); let install_ver = pkg.install_version().unwrap(); // Now it should match the old version we just marked. assert!(install_ver.version() == "0.0.1"); } #[test] fn broken_pkgs() { let cache = new_cache!(&["tests/files/cache/broken-or-dep_0.0.1.deb"]).unwrap(); let pkg = cache.get("broken-or-dep").unwrap(); // let config = Config::new(); // config.set("Debug::pkgProblemResolver", "1"); pkg.protect(); pkg.mark_install(false, true); let expected = concat!( " broken-or-dep : Depends: not-exist (>= 3.6.1) but it is not installable or\n", " really-not-exist but it is not installable\n", " Depends: python3-not-exist but it is not installable\n", ); let err = cache.resolve(false).unwrap_err(); for pkg in cache.iter() { if let Some(broken) = show_broken_pkg(&cache, &pkg, false) { assert_eq!(broken, expected); println!("{broken}"); } } println!("{err}"); } } rust-apt-0.8.0/tests/config.rs000064400000000000000000000070551046102023000143740ustar 00000000000000mod config { use std::collections::VecDeque; use std::process::Command; use rust_apt::config::Config; #[test] fn clear() { // Test to make sure that the config populates properly. // Config will be empty if it hasn't been initialized. let config = Config::new_clear(); config.clear_all(); let empty_config = config.find("APT::Architecture", ""); assert!(!config.contains("APT::Architecture")); assert!(empty_config.is_empty()); // Reset the configuration which will clear and reinit. config.reset(); // Now it should NOT be empty. let config_dump = config.find("APT::Architecture", ""); assert!(config.contains("APT::Architecture")); assert!(!config_dump.is_empty()); println!("{}", config.dump()); } #[test] fn find_and_set() { let config = Config::new_clear(); let key = "rust_apt::NotExist"; // Find our key. It should not exist. assert_eq!(config.find(key, "None"), "None"); // Set the key to something. config.set(key, "Exists!"); // Find again and it should be there. assert_eq!(config.find(key, "None"), "Exists!"); // Test other find functions on known defaults. assert!(!config.bool("APT::Install-Suggests", true)); assert_eq!(config.int("APT::Install-Suggests", 20), 0); // Directory is different in CI. Just check for the name assert!( config .file("Dir::Cache::pkgcache", "") .split('/') .any(|x| x == "pkgcache.bin") ); assert_eq!( config.dir("Dir::Etc::sourceparts", ""), "/etc/apt/sources.list.d/" ); // Check if we can set a configuration list and retrieve it. // Make sure that the target vector is empty. assert!(config.find_vector("rust_apt::aptlist").is_empty()); // Now fill our configuration vector and set it. let apt_list = vec!["this", "is", "my", "apt", "list"]; config.set_vector("rust_apt::aptlist", &apt_list); // Retrieve a new vector from the configuration. let apt_vector = config.find_vector("rust_apt::aptlist"); // If everything went smooth, our original vector should match the new one assert_eq!(apt_list, apt_vector); // Now test if we can remove a single value from the list. config.clear_value("rust_apt::aptlist", "my"); // This will let us know if it worked! assert_eq!( config.find_vector("rust_apt::aptlist"), vec!["this", "is", "apt", "list"] ); // Finally test and see if we can clear the entire list. config.clear("rust_apt::aptlist"); assert!(config.find_vector("rust_apt::aptlist").is_empty()); } #[test] fn get_architectures() { let config = Config::new(); let output = dbg!( String::from_utf8( Command::new("dpkg") .arg("--print-architecture") .output() .unwrap() .stdout, ) .unwrap() ); let arches = dbg!(config.get_architectures()); assert!(arches.contains(&output.strip_suffix('\n').unwrap().to_string())); } #[test] fn config_tree() { // An example of how you might walk the entire config tree. let config = Config::new(); let Some(tree) = config.root_tree() else { return; }; let mut stack = VecDeque::new(); stack.push_back((tree, 0)); while let Some((node, indent)) = stack.pop_back() { let indent_str = " ".repeat(indent); if let Some(item) = node.sibling() { stack.push_back((item, indent)); } if let Some(item) = node.child() { stack.push_back((item, indent + 2)); } if let Some(tag) = node.tag() { if !tag.is_empty() { println!("{}Tag: {}", indent_str, tag); } } if let Some(value) = node.value() { if !value.is_empty() { println!("{}Value: {}", indent_str, value); } } } } } rust-apt-0.8.0/tests/depcache.rs000064400000000000000000000042731046102023000146620ustar 00000000000000mod depcache { use rust_apt::cache::Upgrade; use rust_apt::new_cache; #[test] fn mark_reinstall() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); dbg!(pkg.marked_reinstall()); dbg!(pkg.mark_reinstall(true)); assert!(pkg.marked_reinstall()); } #[test] fn action_groups() { let cache = new_cache!().unwrap(); let mut action_group = unsafe { cache.depcache().action_group() }; // This is unsafe due to SIGABRT if you try to release after dropping the cache. // Probably should get wrapped and have a lifetime related to the Cache. // drop(cache); // The C++ deconstructor will be run when the action group leaves scope. action_group.pin_mut().release(); } // Make a test for getting the candidate after you set a candidate. // Make sure it's the expected version. // We had to change to getting the candidate from the depcache. // https://gitlab.com/volian/rust-apt/-/issues/14 // #[test] // fn changes_test() { // let cache = new_cache!().unwrap(); // let pkg = cache.get("nala").unwrap(); // let ver = pkg.get_version("0.12.1").unwrap(); // ver.set_candidate(); // let cand = pkg.candidate().unwrap(); // println!("Version is {}", cand.version()) // } #[test] fn upgrade() { // There isn't a great way to test if upgrade is working properly // as this is dynamic depending on the system. // This test will always pass, but print the status of the changes. // Occasionally manually compare the output to apt full-upgrade. let cache = new_cache!().unwrap(); cache.upgrade(Upgrade::FullUpgrade).unwrap(); for pkg in cache.get_changes(true) { if pkg.marked_install() { println!("{} is marked install", pkg.name()); // If the package is marked install then it will also // show up as marked upgrade, downgrade etc. // Check this first and continue. continue; } if pkg.marked_upgrade() { println!("{} is marked upgrade", pkg.name()) } if pkg.marked_delete() { println!("{} is marked remove", pkg.name()) } if pkg.marked_reinstall() { println!("{} is marked reinstall", pkg.name()) } if pkg.marked_downgrade() { println!("{} is marked downgrade", pkg.name()) } } } } rust-apt-0.8.0/tests/files/cache/apt/DEBIAN/control000064400000000000000000000002431046102023000200270ustar 00000000000000Package: apt Version: 5000:1.0.0 Description: This package would never exist in a normal APT repository Maintainer: Foo Bar Architecture: all rust-apt-0.8.0/tests/files/cache/broken-or-dep_0.0.1/DEBIAN/control000064400000000000000000000004611046102023000224250ustar 00000000000000 Package: broken-or-dep Version: 0.0.1 Section: base Priority: optional Architecture: all Depends: not-exist (>= 3.6.1) | really-not-exist, htop, python3-not-exist, Maintainer: Your Name Description: Rust FTW This is only used for testing. Why would you install this? rust-apt-0.8.0/tests/files/cache/dep-pkg1_0.0.1/DEBIAN/control000064400000000000000000000003511046102023000213670ustar 00000000000000Package: dep-pkg1 Version: 0.0.1 Section: base Priority: optional Architecture: all Depends: htop, python3-rich Maintainer: Your Name Description: Rust FTW This is only used for testing. Why would you install this? rust-apt-0.8.0/tests/files/cache/dep-pkg1_0.0.2/DEBIAN/control000064400000000000000000000003511046102023000213700ustar 00000000000000Package: dep-pkg1 Version: 0.0.2 Section: base Priority: optional Architecture: all Depends: htop, python3-rich Maintainer: Your Name Description: Rust FTW This is only used for testing. Why would you install this? rust-apt-0.8.0/tests/files/cache/dep-pkg2_0.0.1/DEBIAN/control000064400000000000000000000003461046102023000213740ustar 00000000000000Package: dep-pkg2 Version: 0.0.1 Section: base Priority: optional Architecture: all Depends: neofetch, xonsh Maintainer: Your Name Description: Rust FTW This is only used for testing. Why would you install this? rust-apt-0.8.0/tests/files/cache/no-description_0.0.1/DEBIAN/control000064400000000000000000000002341046102023000227140ustar 00000000000000Package: no-description Version: 0.0.1 Section: base Priority: optional Architecture: all Depends: htop, python3-rich Maintainer: Your Name rust-apt-0.8.0/tests/files/tagfile/correct.control000064400000000000000000000006011046102023000203270ustar 00000000000000Package: pkg1 Version: 1.0.0 Description: pkgdesc1 Multi-Line: Wow This is Multiple lines! Back-To: Normal Package: pkg2 Version: 2.0.0 Description: pkgdesc2 Value-Starts-On-Newline: Well that's interesting! It's nice that this isn't failing the test, isn't it?? Normal-Line: Once again Tabbed-Indentation: All my homies know that tabs be superior. Why not just use both?rust-apt-0.8.0/tests/records.rs000064400000000000000000000015631046102023000145660ustar 00000000000000mod records { use rust_apt::new_cache; use rust_apt::records::RecordField; #[test] fn fields() { let cache = new_cache!().unwrap(); let cand = cache.get("apt").unwrap().candidate().unwrap(); assert_eq!( cand.get_record(RecordField::Maintainer).unwrap(), "APT Development Team " ); // Apt should not have a homepage assert!(cand.get_record(RecordField::Homepage).is_none()); // This should also equal the same as the cand version assert_eq!( cand.get_record(RecordField::Version).unwrap(), cand.version() ); // We can just print these for good luck. println!("Depends {:?}", cand.get_record(RecordField::Depends)); println!("PreDepends {:?}", cand.get_record(RecordField::PreDepends)); // This should be the same as what the Hash accessors will give. assert_eq!(cand.get_record("SHA256"), cand.sha256()); } } rust-apt-0.8.0/tests/root.rs000064400000000000000000000077061046102023000141150ustar 00000000000000mod root { use rust_apt::config::Config; use rust_apt::new_cache; use rust_apt::progress::{AcquireProgress, DynAcquireProgress, InstallProgress}; use rust_apt::raw::{AcqTextStatus, ItemDesc, ItemState, PkgAcquire}; use rust_apt::util::*; #[test] fn lock() { apt_lock().unwrap(); apt_lock().unwrap(); assert!(apt_is_locked()); apt_unlock(); assert!(apt_is_locked()); apt_unlock(); assert!(!apt_is_locked()); } #[test] fn update() { struct Progress {} impl DynAcquireProgress for Progress { fn pulse_interval(&self) -> usize { 0 } fn hit(&mut self, item: &ItemDesc) { println!("\rHit:{} {}", item.owner().id(), item.description()); } fn fetch(&mut self, item: &ItemDesc) { let mut string = format!("\rGet:{} {}", item.owner().id(), item.description()); let file_size = item.owner().file_size(); if file_size != 0 { string.push_str(&format!(" [{}]", unit_str(file_size, NumSys::Decimal))); } println!("{string}"); } fn done(&mut self, _item: &ItemDesc) {} fn start(&mut self) {} fn stop(&mut self, owner: &AcqTextStatus) { if owner.fetched_bytes() != 0 { println!( "Fetched {} in {} ({}/s)", unit_str(owner.fetched_bytes(), NumSys::Decimal), time_str(owner.elapsed_time()), unit_str(owner.current_cps(), NumSys::Decimal) ); } else { println!("Nothing to fetch."); } } fn fail(&mut self, item: &ItemDesc) { let mut show_error = true; let error_text = item.owner().error_text(); let desc = format!("{} {}", item.owner().id(), item.description()); match item.owner().status() { ItemState::StatIdle | ItemState::StatDone => { println!("\rIgn: {desc}"); if error_text.is_empty() || Config::new().bool("Acquire::Progress::Ignore::ShowErrorText", false) { show_error = false; } }, _ => { println!("\rErr: {desc}"); }, } if show_error { println!("\r{error_text}"); } } fn pulse(&mut self, _status: &AcqTextStatus, _owner: &PkgAcquire) {} } let cache = new_cache!().unwrap(); // Test the default implementation for it let mut progress = AcquireProgress::apt(); cache.update(&mut progress).unwrap(); let cache = new_cache!().unwrap(); // Test a new impl for AcquireProgress let mut progress = AcquireProgress::new(Progress {}); cache.update(&mut progress).unwrap(); } #[test] fn install_and_remove() { let cache = new_cache!().unwrap(); let pkg = cache.get("neofetch").unwrap(); pkg.protect(); pkg.mark_install(true, true); cache.resolve(false).unwrap(); dbg!(pkg.marked_install()); let mut progress = AcquireProgress::apt(); let mut inst_progress = InstallProgress::apt(); cache.commit(&mut progress, &mut inst_progress).unwrap(); // After commit a new cache must be created for more operations let cache = new_cache!().unwrap(); // I need to pick a better package. This removes my neofetch every time! let pkg = cache.get("neofetch").unwrap(); pkg.mark_delete(true); cache.commit(&mut progress, &mut inst_progress).unwrap(); } #[test] fn install_with_debs() { let debs = [ "tests/files/cache/dep-pkg1_0.0.1.deb", "tests/files/cache/dep-pkg2_0.0.1.deb", ]; let cache = new_cache!(&debs).unwrap(); let pkg1 = cache.get("dep-pkg1").unwrap(); let pkg2 = cache.get("dep-pkg2").unwrap(); pkg1.mark_install(true, true); pkg2.mark_install(true, true); cache.resolve(false).unwrap(); let mut progress = AcquireProgress::apt(); let mut inst_progress = InstallProgress::apt(); cache.commit(&mut progress, &mut inst_progress).unwrap(); // You have to get a new cache after using commit. let cache = new_cache!(&debs).unwrap(); // New packages will be required as well. let pkg1 = cache.get("dep-pkg1").unwrap(); let pkg2 = cache.get("dep-pkg2").unwrap(); // Leave no trace pkg1.mark_delete(true); pkg2.mark_delete(true); cache.commit(&mut progress, &mut inst_progress).unwrap(); } } rust-apt-0.8.0/tests/sort.rs000064400000000000000000000060071046102023000141120ustar 00000000000000mod sort { use rust_apt::cache::*; use rust_apt::new_cache; #[test] fn defaults() { let cache = new_cache!().unwrap(); let mut installed = false; let mut auto_installed = false; // Test defaults and ensure there are no virtual packages. // And that we have any packages at all. let mut real_pkgs = Vec::new(); let mut virtual_pkgs = Vec::new(); let sort = PackageSort::default(); for pkg in cache.packages(&sort) { if pkg.is_auto_installed() { auto_installed = true; } if pkg.is_installed() { installed = true; } if pkg.has_versions() { real_pkgs.push(pkg); continue; } virtual_pkgs.push(pkg); } assert!(!real_pkgs.is_empty()); assert!(virtual_pkgs.is_empty()); assert!(auto_installed); assert!(installed) } #[test] fn include_virtual() { let cache = new_cache!().unwrap(); // Check that we have virtual and real packages after sorting. let mut real_pkgs = Vec::new(); let mut virtual_pkgs = Vec::new(); let sort = PackageSort::default().include_virtual().names(); for pkg in cache.packages(&sort) { if pkg.has_versions() { real_pkgs.push(pkg); continue; } virtual_pkgs.push(pkg); } assert!(!real_pkgs.is_empty()); assert!(!virtual_pkgs.is_empty()); } #[test] fn only_virtual() { let cache = new_cache!().unwrap(); // Check that we have only virtual packages. let mut real_pkgs = Vec::new(); let mut virtual_pkgs = Vec::new(); let sort = PackageSort::default().only_virtual(); for pkg in cache.packages(&sort) { if pkg.has_versions() { real_pkgs.push(pkg); continue; } virtual_pkgs.push(pkg); } assert!(real_pkgs.is_empty()); assert!(!virtual_pkgs.is_empty()); } #[test] fn upgradable() { let cache = new_cache!().unwrap(); let sort = PackageSort::default().upgradable(); for pkg in cache.packages(&sort) { assert!(pkg.is_upgradable()) } let sort = PackageSort::default().not_upgradable(); for pkg in cache.packages(&sort) { assert!(!pkg.is_upgradable()) } } #[test] fn installed() { let cache = new_cache!().unwrap(); let sort = PackageSort::default().installed(); for pkg in cache.packages(&sort) { assert!(pkg.is_installed()) } let sort = PackageSort::default().not_installed(); for pkg in cache.packages(&sort) { assert!(!pkg.is_installed()) } } #[test] fn auto_installed() { let cache = new_cache!().unwrap(); let sort = PackageSort::default().auto_installed(); for pkg in cache.packages(&sort) { println!("{}", pkg.name()); assert!(pkg.is_auto_installed()) } let sort = PackageSort::default().manually_installed(); for pkg in cache.packages(&sort) { assert!(!pkg.is_auto_installed()); } } #[test] fn auto_removable() { let cache = new_cache!().unwrap(); let sort = PackageSort::default().auto_removable(); for pkg in cache.packages(&sort) { assert!(pkg.is_auto_removable()) } let sort = PackageSort::default().not_auto_removable(); for pkg in cache.packages(&sort) { assert!(!pkg.is_auto_removable()) } } } rust-apt-0.8.0/tests/tagfile.rs000064400000000000000000000035161046102023000145400ustar 00000000000000mod tagfile { use rust_apt::tagfile::{self, TagSection}; #[test] fn correct() { let control_file = include_str!("files/tagfile/correct.control"); let dpkg_status = include_str!("/var/lib/dpkg/status"); let control_sections: Vec<&str> = control_file.split("\n\n").collect(); let control_section_one = TagSection::new(control_sections.first().unwrap()).unwrap(); let control_section_two = TagSection::new(control_sections.get(1).unwrap()).unwrap(); assert!(tagfile::parse_tagfile(dpkg_status).is_ok()); assert!(tagfile::parse_tagfile(control_file).is_ok()); assert!(TagSection::new(control_file).is_err()); assert!( TagSection::new("This-Is-Not-A-Valid-Control-File-Because-Its-Not-Colon-Separated") .is_err() ); assert_eq!(control_section_one.get("Package").unwrap(), "pkg1"); assert_eq!(control_section_one.get("Version").unwrap(), "1.0.0"); assert_eq!(control_section_one.get("Description").unwrap(), "pkgdesc1"); assert_eq!( control_section_one.get("Multi-Line").unwrap(), "Wow\n This is\n Multiple lines!" ); assert_eq!(control_section_one.get("Back-To").unwrap(), "Normal"); assert!( control_section_one .get("Not-A-Key-In-The-Control-File") .is_none() ); assert_eq!(control_section_two.get("Package").unwrap(), "pkg2"); assert_eq!(control_section_two.get("Version").unwrap(), "2.0.0"); assert_eq!(control_section_two.get("Description").unwrap(), "pkgdesc2"); assert_eq!( control_section_two.get("Value-Starts-On-Newline").unwrap(), "\n Well that's interesting!\n It's nice that this isn't failing the test, isn't \ it??" ); assert_eq!( control_section_two.get("Normal-Line").unwrap(), "Once again" ); assert_eq!( control_section_two.get("Tabbed-Indentation").unwrap(), "\n\tAll my homies know that tabs be superior.\n\t Why not just use both?" ); } } rust-apt-0.8.0/tests/util.rs000064400000000000000000000005051046102023000140750ustar 00000000000000mod util { use std::cmp::Ordering; use rust_apt::util; #[test] fn cmp_versions() { let ver1 = "5.0"; let ver2 = "6.0"; assert_eq!(Ordering::Less, util::cmp_versions(ver1, ver2)); assert_eq!(Ordering::Equal, util::cmp_versions(ver1, ver1)); assert_eq!(Ordering::Greater, util::cmp_versions(ver2, ver1)); } }