rust-apt-0.7.0/.cargo_vcs_info.json0000644000000001360000000000100126170ustar { "git": { "sha1": "8682720460fce9b9e7c92943c193f71cdfc88f66" }, "path_in_vcs": "" }rust-apt-0.7.0/.clang-format000064400000000000000000000005411046102023000137620ustar 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.7.0/.editorconfig000064400000000000000000000002771046102023000140720ustar 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.7.0/.gitignore000064400000000000000000000003401046102023000133740ustar 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 .gitlab-ci-local* # So Cargo doesn't complain about a dirty working directory in CI Publish .cargo rust-apt-0.7.0/.gitlab-ci.yml000064400000000000000000000046051046102023000140500ustar 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.7.0/.vscode/c_cpp_properties.json000064400000000000000000000012341046102023000172030ustar 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.7.0/.vscode/settings.json000064400000000000000000000035351046102023000155110ustar 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.7.0/Cargo.toml0000644000000016720000000000100106230ustar # 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.7.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.terminal_size] version = "0.3.0" [build-dependencies.cxx-build] version = "1.0" rust-apt-0.7.0/Cargo.toml.orig000064400000000000000000000006631046102023000143030ustar 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.7.0" edition = "2021" [dependencies] cxx = "1.0" terminal_size = "0.3.0" [build-dependencies] cxx-build = "1.0" rust-apt-0.7.0/LICENSE000064400000000000000000001044711046102023000124230ustar 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.7.0/README.md000064400000000000000000000037161046102023000126750ustar 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 [crates.io](https://crates.io/crates/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.7.0/apt-pkg-c/cache.h000064400000000000000000000057351046102023000144200ustar 00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include "rust/cxx.h" #include "rust-apt/src/raw/cache.rs" #include "rust-apt/src/raw/progress.rs" /// Update the package lists, handle errors and return a Result. inline void Cache::update(DynAcquireProgress& callback) const { AcqTextStatus progress(callback); ListUpdate(progress, *ptr->GetSourceList(), pulse_interval(callback)); handle_errors(); } // Return a package by name. inline Package Cache::unsafe_find_pkg(rust::string name) const noexcept { return Package{ std::make_unique(safe_get_pkg_cache(ptr.get())->FindPkg(name.c_str()))}; } inline Package Cache::begin() const { return Package{std::make_unique(safe_get_pkg_cache(ptr.get())->PkgBegin())}; } /// The priority of the package as shown in `apt policy`. inline int32_t Cache::priority(const Version& ver) const noexcept { return ptr->GetPolicy()->GetPriority(*ver.ptr); } inline DepCache Cache::create_depcache() const noexcept { return DepCache{std::make_unique(ptr->GetDepCache())}; } inline std::unique_ptr Cache::create_records() const noexcept { return Records::Unique(ptr); } inline void Cache::find_index(PackageFile& pkg_file) const noexcept { if (!pkg_file.index_file) { pkgIndexFile* index; if (!ptr->GetSourceList()->FindIndex(*pkg_file.ptr, index)) { _system->FindIndex(*pkg_file.ptr, index); } pkg_file.index_file = std::make_unique(index); } } /// These should probably go under a index file binding; /// Return true if the PackageFile is trusted. inline bool Cache::is_trusted(PackageFile& pkg_file) const noexcept { this->find_index(pkg_file); return (*pkg_file.index_file)->IsTrusted(); } /// Get the package list uris. This is the files that are updated with `apt update`. inline rust::Vec Cache::source_uris() const noexcept { pkgAcquire fetcher; rust::Vec list; ptr->GetSourceList()->GetIndexes(&fetcher, true); pkgAcquire::UriIterator I = fetcher.UriBegin(); for (; I != fetcher.UriEnd(); ++I) { list.push_back(SourceURI{I->URI, flNotDir(I->Owner->DestFile)}); } return list; } inline Cache create_cache(rust::Slice deb_files) { std::unique_ptr cache = std::make_unique(); for (auto deb_str : deb_files) { std::string deb_string(deb_str.c_str()); // Make sure this is a valid archive. // signal: 11, SIGSEGV: invalid memory reference FileFd fd(deb_string, FileFd::ReadOnly); debDebFile debfile(fd); handle_errors(); // Add the deb to the cache. if (!cache->GetSourceList()->AddVolatileFile(deb_string)) { _error->Error("%s", ("Couldn't add '" + deb_string + "' to the cache.").c_str()); handle_errors(); } handle_errors(); } return Cache{std::move(cache)}; } rust-apt-0.7.0/apt-pkg-c/configuration.h000064400000000000000000000055551046102023000162240ustar 00000000000000#pragma once #include #include #include #include #include #include "rust/cxx.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); } /// Returns a string dump of configuration options separated by `\n` rust::string config_dump() { std::stringstream string_stream; _config->Dump(string_stream); return string_stream.str(); } /// Find a key and return it's value as a string. rust::string config_find(rust::string key, rust::string default_value) { return _config->Find(key.c_str(), default_value.c_str()); } /// Find a file and return it's value as a string. rust::string config_find_file(rust::string key, rust::string default_value) { return _config->FindFile(key.c_str(), default_value.c_str()); } /// Find a directory and return it's value as a string. rust::string config_find_dir(rust::string key, rust::string default_value) { return _config->FindDir(key.c_str(), default_value.c_str()); } /// Same as find, but for boolean values. bool config_find_bool(rust::string key, bool default_value) { return _config->FindB(key.c_str(), default_value); } /// Same as find, but for i32 values. int config_find_int(rust::string key, int default_value) { return _config->FindI(key.c_str(), default_value); } /// Return a vector for an Apt configuration list. rust::vec config_find_vector(rust::string key) { std::vector config_vector = _config->FindVector(key.c_str()); rust::vec rust_vector; for (const std::string& str : config_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. rust::vec config_get_architectures() { rust::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 config_set(rust::string key, rust::string value) { _config->Set(key.c_str(), value.c_str()); } /// Simply check if a key exists. bool config_exists(rust::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 `config_clear_value` void config_clear(rust::string key) { _config->Clear(key.c_str()); } /// Clear all configurations. void config_clear_all() { _config->Clear(); } /// Clear a single value from a list. void config_clear_value(rust::string key, rust::string value) { _config->Clear(key.c_str(), value.c_str()); } rust-apt-0.7.0/apt-pkg-c/depcache.h000064400000000000000000000221511046102023000151000ustar 00000000000000#pragma once #include #include #include #include "rust/cxx.h" #include "rust-apt/src/raw/depcache.rs" /// Clear any marked changes in the DepCache. inline void DepCache::init(DynOperationProgress& callback) const { OpProgressWrapper op_progress(callback); (*ptr)->Init(&op_progress); // pkgApplyStatus(*cache->GetDepCache()); handle_errors(); } /// Autoinstall every broken package and run the problem resolver /// Returns false if the problem resolver fails. inline bool DepCache::fix_broken() const noexcept { return pkgFixBroken(**ptr); } inline ActionGroup DepCache::action_group() const noexcept { return ActionGroup{std::make_unique(**ptr)}; } inline void ActionGroup::release() const noexcept { ptr->release(); } /// Is the Package upgradable? /// /// `skip_depcache = true` increases performance by skipping the pkgDepCache /// Skipping the depcache is very unnecessary if it's already been /// initialized If you're not sure, set `skip_depcache = false` inline bool DepCache::is_upgradable(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].Upgradable(); } /// Is the Package auto installed? Packages marked as auto installed are usually dependencies. inline bool DepCache::is_auto_installed(const Package& pkg) const noexcept { pkgDepCache::StateCache state = (**ptr)[*pkg.ptr]; return state.Flags & pkgCache::Flag::Auto; } /// Is the Package able to be auto removed? inline bool DepCache::is_garbage(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].Garbage; } /// Is the Package marked for install? inline bool DepCache::marked_install(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].NewInstall(); } /// Is the Package marked for upgrade? inline bool DepCache::marked_upgrade(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].Upgrade(); } /// Is the Package marked to be purged? inline bool DepCache::marked_purge(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].Purge(); } /// Is the Package marked for removal? inline bool DepCache::marked_delete(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].Delete(); } /// Is the Package marked for keep? inline bool DepCache::marked_keep(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].Keep(); } /// Is the Package marked for downgrade? inline bool DepCache::marked_downgrade(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].Downgrade(); } /// Is the Package marked for reinstall? inline bool DepCache::marked_reinstall(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].ReInstall(); } /// Mark a package as automatically installed. /// /// MarkAuto = true will mark the package as automatically installed and false will mark it as /// manual inline void DepCache::mark_auto(const Package& pkg, bool mark_auto) const noexcept { (*ptr)->MarkAuto(*pkg.ptr, 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. inline bool DepCache::mark_keep(const Package& pkg) const noexcept { return (*ptr)->MarkKeep(*pkg.ptr, 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. inline bool DepCache::mark_delete(const Package& pkg, bool purge) const noexcept { return (*ptr)->MarkDelete(*pkg.ptr, 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. inline bool DepCache::mark_install(const Package& pkg, bool auto_inst, bool from_user) const noexcept { return (*ptr)->MarkInstall(*pkg.ptr, auto_inst, 0, from_user, false); } /// Set a version to be the candidate of it's package. inline void DepCache::set_candidate_version(const Version& ver) const noexcept { (*ptr)->SetCandidateVersion(*ver.ptr); } /// Return the candidate version of the package. /// Ptr will be NULL if there isn't a candidate. inline Version DepCache::unsafe_candidate_version(const Package& pkg) const noexcept { return Version{std::make_unique((*ptr)->GetCandidateVersion(*pkg.ptr))}; } /// 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`]. inline Version DepCache::unsafe_install_version(const Package& pkg) const noexcept { pkgCache& cache = (*ptr)->GetCache(); return Version{std::make_unique((**ptr)[*pkg.ptr].InstVerIter(cache))}; } /// Returns the state of the dependency as u8 inline uint8_t DepCache::dep_state(const Dependency& dep) const noexcept { return (**ptr)[*dep.ptr]; } /// 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. inline bool DepCache::is_important_dep(const Dependency& dep) const noexcept { return (*ptr)->IsImportantDep(*dep.ptr); } /// Mark a package for reinstallation /// /// To: /// True = The package will be marked for reinstall /// False = The package will be unmarked for reinstall inline void DepCache::mark_reinstall(const Package& pkg, bool reinstall) const noexcept { (*ptr)->SetReInstall(*pkg.ptr, reinstall); } /// Is the installed Package broken? inline bool DepCache::is_now_broken(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].NowBroken(); } /// Is the Package to be installed broken? inline bool DepCache::is_inst_broken(const Package& pkg) const noexcept { return (**ptr)[*pkg.ptr].InstBroken(); } /// The number of packages marked for installation. inline u_int32_t DepCache::install_count() const noexcept { return (*ptr)->InstCount(); } /// The number of packages marked for removal. inline u_int32_t DepCache::delete_count() const noexcept { return (*ptr)->DelCount(); } /// The number of packages marked for keep. inline u_int32_t DepCache::keep_count() const noexcept { return (*ptr)->KeepCount(); } /// The number of packages with broken dependencies in the cache. inline u_int32_t DepCache::broken_count() const noexcept { return (*ptr)->BrokenCount(); } /// The size of all packages to be downloaded. inline u_int64_t DepCache::download_size() const noexcept { 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." inline int64_t DepCache::disk_size() const noexcept { return (*ptr)->UsrSize(); } /// Perform a Full Upgrade. Remove and install new packages if necessary. inline void DepCache::full_upgrade(DynOperationProgress& callback) const { OpProgressWrapper op_progress(callback); // This is equivalent to `apt full-upgrade` and `apt-get dist-upgrade` // It is currently unclear if we should return a bool here. I think Result should be fine. APT::Upgrade::Upgrade(**ptr, APT::Upgrade::ALLOW_EVERYTHING, &op_progress); handle_errors(); } /// Perform a Safe Upgrade. Neither remove or install new packages. inline void DepCache::safe_upgrade(DynOperationProgress& callback) const { OpProgressWrapper op_progress(callback); // This is equivalent to `apt-get upgrade` APT::Upgrade::Upgrade( **ptr, APT::Upgrade::FORBID_REMOVE_PACKAGES | APT::Upgrade::FORBID_INSTALL_NEW_PACKAGES, &op_progress ); handle_errors(); } /// Perform an Install Upgrade. New packages will be installed but nothing will be removed. inline void DepCache::install_upgrade(DynOperationProgress& callback) const { OpProgressWrapper op_progress(callback); // This is equivalent to `apt upgrade` APT::Upgrade::Upgrade(**ptr, APT::Upgrade::FORBID_REMOVE_PACKAGES, &op_progress); handle_errors(); } rust-apt-0.7.0/apt-pkg-c/package.h000064400000000000000000000232331046102023000147410ustar 00000000000000#pragma once #include #include #include #include "rust/cxx.h" #include "rust-apt/src/raw/package.rs" #include "util.h" inline rust::Str Provider::name() const noexcept { return ptr->Name(); } inline rust::Str Provider::version_str() const { return handle_str(ptr->ProvideVersion()); } inline void Provider::raw_next() const noexcept { ++(*ptr); } inline bool Provider::end() const noexcept { return ptr->end(); } inline Package Provider::target_pkg() const noexcept { return Package{std::make_unique(ptr->OwnerPkg())}; } inline Package Dependency::parent_pkg() const noexcept { return Package{std::make_unique(ptr->ParentPkg())}; } inline Version Provider::target_ver() const noexcept { return Version{std::make_unique(ptr->OwnerVer())}; } inline Provider Provider::unique() const noexcept { return Provider{std::make_unique(*ptr)}; } /// The path to the PackageFile inline rust::Str PackageFile::filename() const { return handle_str(ptr->FileName()); } /// The Archive of the PackageFile. ex: unstable inline rust::Str PackageFile::archive() const { return handle_str(ptr->Archive()); } /// The Origin of the PackageFile. ex: Debian inline rust::Str PackageFile::origin() const { return handle_str(ptr->Origin()); } /// The Codename of the PackageFile. ex: main, non-free inline rust::Str PackageFile::codename() const { return handle_str(ptr->Codename()); } /// The Label of the PackageFile. ex: Debian inline rust::Str PackageFile::label() const { return handle_str(ptr->Label()); } /// The Hostname of the PackageFile. ex: deb.debian.org inline rust::Str PackageFile::site() const { return handle_str(ptr->Site()); } /// The Component of the PackageFile. ex: sid inline rust::Str PackageFile::component() const { return handle_str(ptr->Component()); } /// The Architecture of the PackageFile. ex: amd64 inline rust::Str PackageFile::arch() const { return handle_str(ptr->Architecture()); } /// The Index Type of the PackageFile. Known values are: /// /// Debian Package Index, Debian Translation Index, Debian dpkg status file, inline rust::Str PackageFile::index_type() const { return handle_str(ptr->IndexType()); } /// The Index number of the PackageFile inline uint64_t PackageFile::index() const noexcept { return ptr->Index(); } // Return the package file object. inline PackageFile DescriptionFile::pkg_file() const noexcept { return PackageFile{std::make_unique(ptr->File()), NULL}; } // Return the Index of the Package File. inline uint64_t DescriptionFile::index() const noexcept { return ptr->Index(); } // Increment the iterator one inline void DescriptionFile::raw_next() const noexcept { ++(*ptr); } // Checks if the pointer is null meaning there is no more. inline bool DescriptionFile::end() const noexcept { return ptr->end(); } inline DescriptionFile DescriptionFile::unique() const noexcept { return DescriptionFile{std::make_unique(*ptr)}; } // Return the VersionFile object. inline PackageFile VersionFile::pkg_file() const noexcept { return PackageFile{std::make_unique(ptr->File()), NULL}; } // Return the Index of the VersionFile. inline uint64_t VersionFile::index() const noexcept { return ptr->Index(); } // Increment the iterator one inline void VersionFile::raw_next() const noexcept { ++(*ptr); } // Checks if the pointer is null meaning there is no more. inline bool VersionFile::end() const noexcept { return ptr->end(); } inline VersionFile VersionFile::unique() const noexcept { return VersionFile{std::make_unique(*ptr)}; } /// String representation of the dependency compare type /// "","<=",">=","<",">","=","!=" inline rust::Str Dependency::comp_type() const { return handle_str(ptr->CompType()); } inline uint32_t Dependency::index() const noexcept { return ptr->Index(); } /// u8 representation of the DepType. Will be converted to Enum in rust inline uint8_t Dependency::u8_dep_type() const noexcept { return (*ptr)->Type; } // Return true if this dep is Or'd with the next. The last dep in the or group will return False. inline bool Dependency::compare_op() const noexcept { return ((*ptr)->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or; } inline rust::Str Dependency::target_ver() const { return handle_str(ptr->TargetVer()); } inline bool Dependency::is_reverse() const noexcept { return ptr->Reverse(); } inline Version Dependency::parent_ver() const noexcept { return Version{std::make_unique(ptr->ParentVer())}; } inline Package Dependency::target_pkg() const noexcept { return Package{std::make_unique(ptr->TargetPkg())}; } // This should be tested. I'm not entirely sure this is even going to work. inline Version Dependency::all_targets() const noexcept { return Version{std::make_unique(*ptr->Cache(), *ptr->AllTargets())}; } /// Increment the Dep Iterator once inline void Dependency::raw_next() const noexcept { ++(*ptr); } /// Is the pointer null, basically inline bool Dependency::end() const noexcept { return ptr->end(); } inline Dependency Dependency::unique() const noexcept { return Dependency{std::make_unique(*ptr)}; } /// The ID of the version. inline Package Version::parent_pkg() const noexcept { return Package{std::make_unique(ptr->ParentPkg())}; } /// The ID of the version. inline uint32_t Version::id() const noexcept { return (*ptr)->ID; } /// The version string of the version. "1.4.10" inline rust::Str Version::version() const noexcept { return ptr->VerStr(); } /// The architecture of a version. inline rust::Str Version::arch() const noexcept { return ptr->Arch(); } /// The section of the version as shown in `apt show`. inline rust::Str Version::section() const { // Some packages, such as msft teams, doesn't have a section. return handle_str(ptr->Section()); } /// The priority string as shown in `apt show`. inline rust::Str Version::priority_str() const { return handle_str(ptr->PriorityType()); } /// The size of the .deb file. inline uint64_t Version::size() const noexcept { return (*ptr)->Size; } /// The uncompressed size of the .deb file. inline uint64_t Version::installed_size() const noexcept { return (*ptr)->InstalledSize; } /// True if the version is able to be downloaded. inline bool Version::is_downloadable() const noexcept { return ptr->Downloadable(); } /// True if the version is currently installed. inline bool Version::is_installed() const noexcept { return ptr->ParentPkg().CurrentVer() == *ptr; } // This is for backend records lookups. You can also get package files from here. inline DescriptionFile Version::unsafe_description_file() const { auto desc_file = ptr->TranslatedDescription(); // Must check if DescFileIterator is null first. // See https://gitlab.com/volian/rust-apt/-/issues/28 if (desc_file.end()) { throw std::runtime_error("DescFile doesn't exist"); } return DescriptionFile{std::make_unique(desc_file.FileList())}; } // You go through here to get the package files. inline VersionFile Version::unsafe_version_file() const noexcept { return VersionFile{std::make_unique(ptr->FileList())}; } // /// Return the parent package. TODO: This probably isn't going to work rn // inline pkgCache::PkgIterator parent() const noexcept { return ptr->ParentPkg(); } /// Always contains the name, even if it is the same as the binary name inline rust::Str Version::source_name() const noexcept { return ptr->SourcePkgName(); } // Always contains the version string, even if it is the same as the binary version inline rust::Str Version::source_version() const noexcept { return ptr->SourceVerStr(); } inline Dependency Version::unsafe_depends() const noexcept { return Dependency{std::make_unique(ptr->DependsList())}; } inline Dependency Package::unsafe_rev_depends() const noexcept { return Dependency{std::make_unique(ptr->RevDependsList())}; } inline Provider Version::unsafe_provides() const noexcept { return Provider{std::make_unique(ptr->ProvidesList())}; } inline void Version::raw_next() const noexcept { ++(*ptr); } inline bool Version::end() const noexcept { return ptr->end(); } inline Version Version::unique() const noexcept { return Version{std::make_unique(*ptr)}; } inline rust::Str Package::name() const noexcept { return ptr->Name(); } inline rust::Str Package::arch() const noexcept { return ptr->Arch(); } inline rust::String Package::fullname(bool Pretty) const noexcept { return ptr->FullName(Pretty); } inline u_int32_t Package::id() const noexcept { return (*ptr)->ID; } inline u_int8_t Package::current_state() const noexcept { return (*ptr)->CurrentState; } inline u_int8_t Package::inst_state() const noexcept { return (*ptr)->InstState; } inline u_int8_t Package::selected_state() const noexcept { return (*ptr)->SelectedState; } /// Return the installed version of the package. /// Ptr will be NULL if it's not installed. inline Version Package::unsafe_current_version() const noexcept { return Version{std::make_unique(ptr->CurrentVer())}; } /// True if the package is essential. inline bool Package::is_essential() const noexcept { return ((*ptr)->Flags & pkgCache::Flag::Essential) != 0; } inline Version Package::unsafe_version_list() const noexcept { return Version{std::make_unique(ptr->VersionList())}; } inline Provider Package::unsafe_provides() const noexcept { return Provider{std::make_unique(ptr->ProvidesList())}; } inline void Package::raw_next() const noexcept { ++(*ptr); } inline bool Package::end() const noexcept { return this->ptr->end(); } inline Package Package::unique() const noexcept { return Package{std::make_unique(*ptr)}; } rust-apt-0.7.0/apt-pkg-c/pkgmanager.h000064400000000000000000000073721046102023000154700ustar 00000000000000#pragma once #include #include #include #include #include #include #include #include struct PackageManager { pkgPackageManager mutable* pkgmanager; inline void get_archives( const Cache& cache, const Records& records, DynAcquireProgress& callback ) const { AcqTextStatus archive_progress(callback); pkgAcquire acquire(&archive_progress); // We probably need to let the user set their own pkgSourceList, // but there hasn't been a need to expose such in the Rust interface // yet. pkgSourceList sourcelist = *cache->GetSourceList(); if (!pkgmanager->GetArchives(&acquire, cache.ptr->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(pulse_interval(callback)); 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(); } } inline void do_install(DynInstallProgress& 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. inline void protect(const Package& pkg) const { resolver.Protect(*pkg.ptr); } /// Try to resolve dependency problems by marking packages for installation and removal. inline void resolve(bool fix_broken, DynOperationProgress& callback) const { OpProgressWrapper op_progress(callback); resolver.Resolve(fix_broken, &op_progress); handle_errors(); } ProblemResolver(pkgDepCache* depcache) : resolver(depcache){}; }; /// Create the problem resolver. std::unique_ptr create_problem_resolver(const Cache& cache) { return std::make_unique(cache.ptr->GetDepCache()); } std::unique_ptr create_pkgmanager(const Cache& cache) { // Package Manager needs the DepCache initialized or else invalid memory reference. return std::make_unique(cache.ptr->GetDepCache()); } rust-apt-0.7.0/apt-pkg-c/progress.cc000064400000000000000000000143651046102023000153560ustar 00000000000000#include "rust-apt/src/raw/progress.rs" #include #include #include "progress.h" /// AcqTextStatus modeled from in apt-private/acqprogress.cc /// /// AcqTextStatus::AcqTextStatus - Constructor AcqTextStatus::AcqTextStatus(DynAcquireProgress& callback) : pkgAcquireStatus(), callback(callback) {} /// Called when progress has started. /// /// We do not print anything here to remain consistent with apt. /// lastline length is set to 0 to ensure consistency when progress begins. void AcqTextStatus::Start() { pkgAcquireStatus::Start(); start(callback); ID = 1; } /// Internal function to assign the correct ID to an Item. /// /// We can likely move this into the rust side with a refactor of the items. /// Not sure it that should be done, we'll see in the future. void AcqTextStatus::AssignItemID(pkgAcquire::ItemDesc& Itm) { if (Itm.Owner->ID == 0) Itm.Owner->ID = ID++; } /// Called when an item is confirmed to be up-to-date. /// /// Prints out the short description and the expected size. void AcqTextStatus::IMSHit(pkgAcquire::ItemDesc& Itm) { AssignItemID(Itm); hit(callback, Itm.Owner->ID, Itm.Description); Update = true; } /// Called when an Item has started to download /// /// Prints out the short description and the expected size. void AcqTextStatus::Fetch(pkgAcquire::ItemDesc& Itm) { Update = true; if (Itm.Owner->Complete == true) return; AssignItemID(Itm); fetch(callback, Itm.Owner->ID, Itm.Description, Itm.Owner->FileSize); } /// Called when an item is successfully and completely fetched. /// /// We don't print anything here to remain consistent with apt. void AcqTextStatus::Done(pkgAcquire::ItemDesc& Itm) { Update = true; AssignItemID(Itm); done(callback); } /// Called when an Item fails to download. /// /// Print out the ErrorText for the Item. void AcqTextStatus::Fail(pkgAcquire::ItemDesc& Itm) { AssignItemID(Itm); fail(callback, Itm.Owner->ID, Itm.Description, Itm.Owner->Status, Itm.Owner->ErrorText); Update = true; } /// Called when progress has finished. /// /// prints out the bytes downloaded and the overall average line speed. void AcqTextStatus::Stop() { pkgAcquireStatus::Stop(); stop(callback, FetchedBytes, ElapsedTime, CurrentCPS, _error->PendingError()); } /// 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. bool AcqTextStatus::Pulse(pkgAcquire* Owner) { pkgAcquireStatus::Pulse(Owner); rust::vec list; for (pkgAcquire::Worker* I = Owner->WorkersBegin(); I != 0; I = Owner->WorkerStep(I)) { // There is no item running if (I->CurrentItem == 0) { list.push_back(Worker{ false, I->Status, 0, "", "", 0, 0, false, }); continue; } list.push_back(Worker{ true, I->Status, I->CurrentItem->Owner->ID, I->CurrentItem->ShortDesc, I->CurrentItem->Owner->ActiveSubprocess, I->CurrentItem->CurrentSize, I->CurrentItem->TotalSize, I->CurrentItem->Owner->Complete, }); } pulse(callback, list, Percent, TotalBytes, CurrentBytes, CurrentCPS); Update = true; return true; } /// Not Yet Implemented /// /// Invoked when the user should be prompted to change the inserted removable media. bool AcqTextStatus::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; } /// Not Yet Implemented /// /// Ask the user for confirmation of changes to infos about a repository /// Must return true if the user accepts or false if not bool AcqTextStatus::ReleaseInfoChanges( metaIndex const* const L, metaIndex const* const N, std::vector&& Changes ) { (void)L; (void)N; (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; } /// Calls for OpProgress usage. OpProgressWrapper::OpProgressWrapper(DynOperationProgress& callback) : callback(callback) {} void OpProgressWrapper::Update() { op_update(callback, Op, Percent); } void OpProgressWrapper::Done() { op_done(callback); } /// Calls for InstallProgress usage. PackageManagerWrapper::PackageManagerWrapper(DynInstallProgress& callback) : callback(callback) {} bool PackageManagerWrapper::StatusChanged( std::string pkgname, unsigned int steps_done, unsigned int total_steps, std::string action ) { inst_status_changed(callback, pkgname, steps_done, total_steps, action); return true; } void PackageManagerWrapper::Error( std::string pkgname, unsigned int steps_done, unsigned int total_steps, std::string error ) { inst_error(callback, pkgname, steps_done, total_steps, error); } rust-apt-0.7.0/apt-pkg-c/progress.h000064400000000000000000000061321046102023000152110ustar 00000000000000#pragma once #include #include #include #include "rust/cxx.h" struct Worker; /// Classes for pkgAcquireStatus usage. class DynAcquireProgress { public: DynAcquireProgress(DynAcquireProgress&&) noexcept; ~DynAcquireProgress() noexcept; using IsRelocatable = std::true_type; int pulse_interval() const noexcept; void hit(u_int32_t id, std::string description) const noexcept; void fetch(u_int32_t id, std::string description, u_int64_t file_size) const noexcept; void fail(u_int32_t id, std::string description, u_int32_t status, std::string error_text) const noexcept; void pulse( rust::vec workers, double percent, u_int64_t total_bytes, u_int64_t current_bytes, u_int64_t current_cps ) const noexcept; void done() const noexcept; void start() const noexcept; void stop( u_int64_t fetched_bytes, u_int64_t elapsed_time, u_int64_t current_cps, bool pending_errors ) const noexcept; }; class AcqTextStatus : public pkgAcquireStatus { unsigned long ID; /// Callback to the rust struct DynAcquireProgress& callback; void clearLastLine(); void AssignItemID(pkgAcquire::ItemDesc& Itm); public: virtual bool ReleaseInfoChanges( metaIndex const* const LastRelease, metaIndex const* const CurrentRelease, std::vector&& Changes ); virtual bool MediaChange(std::string Media, std::string Drive); virtual void IMSHit(pkgAcquire::ItemDesc& Itm); virtual void Fetch(pkgAcquire::ItemDesc& Itm); virtual void Done(pkgAcquire::ItemDesc& Itm); virtual void Fail(pkgAcquire::ItemDesc& Itm); virtual void Start(); virtual void Stop(); bool Pulse(pkgAcquire* Owner); AcqTextStatus(DynAcquireProgress& callback); }; /// Classes for OpProgress usage. class DynOperationProgress { public: void op_update(std::string operation, float percent); void op_done(); }; class OpProgressWrapper : public OpProgress { /// Callback to the rust struct DynOperationProgress& callback; public: void Update(); void Done(); OpProgressWrapper(DynOperationProgress& callback); }; /// Classes for InstallProgress usage. class DynInstallProgress { public: /// This is supposed to return a bool, but I have zero clue when that'd be needed in practice. /// TODO: StatusChanged returns a bool sometimes in the C++ lib, though I'm not sure if it ever /// happens in practice. void inst_status_changed( std::string pkgname, u_int64_t steps_done, u_int64_t total_steps, std::string action ); void inst_error( std::string pkgname, u_int64_t steps_done, u_int64_t total_steps, std::string error ); }; class PackageManagerWrapper : public APT::Progress::PackageManagerFancy { // class PackageManagerWrapper { /// Callback to the rust struct DynInstallProgress& callback; public: virtual bool StatusChanged( std::string pkgname, unsigned int steps_done, unsigned int total_steps, std::string action ); virtual void Error( std::string pkgname, unsigned int steps_done, unsigned int total_steps, std::string error ); PackageManagerWrapper(DynInstallProgress& callback); }; rust-apt-0.7.0/apt-pkg-c/records.h000064400000000000000000000047511046102023000150130ustar 00000000000000#pragma once #include #include #include #include #include #include #include #include "rust/cxx.h" /// Package Record Management: struct Records { pkgRecords mutable records; pkgRecords::Parser mutable* parser; u_int64_t mutable last; inline bool already_has(u_int64_t index) const { if (last == index) { return true; } last = index; return false; } /// Moves the Records into the correct place. inline void ver_file_lookup(const VersionFile& ver_file) const { if (this->already_has(ver_file.index())) { return; } this->parser = &records.Lookup(*ver_file.ptr); } /// Moves the Records into the correct place. inline void desc_file_lookup(const DescriptionFile& desc_file) const { if (this->already_has(desc_file.index())) { return; } this->parser = &records.Lookup(*desc_file.ptr); } /// Return the URI for a version as determined by it's package file. /// A version could have multiple package files and multiple URIs. inline rust::string ver_uri(const PackageFile& pkg_file) const { if (!pkg_file.index_file) { throw std::runtime_error("You have to run 'cache.find_index()' first!"); } if (!parser) { throw std::runtime_error( "You have to run 'cache.ver_lookup()' or 'desc_lookup()' first!" ); } return (*pkg_file.index_file)->ArchiveURI(parser->FileName()); } /// Return the translated long description of a Package. inline rust::string long_desc() const { return handle_string(parser->LongDesc()); } /// Return the translated short description of a Package. inline rust::string short_desc() const { return handle_string(parser->ShortDesc()); } /// Return the Source package version string. inline rust::string get_field(rust::string field) const { return handle_string(parser->RecordField(field.c_str())); } /// Find the hash of a Version. Returns Result if there is no hash. inline rust::string hash_find(rust::string hash_type) const { auto hashes = parser->Hashes(); auto hash = hashes.find(hash_type.c_str()); if (hash == NULL) { throw std::runtime_error("Hash Not Found"); } return handle_string(hash->HashValue()); } Records(const std::unique_ptr& cache) : records(*safe_get_pkg_cache(cache.get())), parser(0), last(0){}; /// UniquePtr Constructor static std::unique_ptr Unique(const std::unique_ptr& cache) { return std::make_unique(cache); }; }; rust-apt-0.7.0/apt-pkg-c/types.h000064400000000000000000000011471046102023000145120ustar 00000000000000#pragma once #include using PkgCacheFile = pkgCacheFile; // DepCache is owned by the PkgCacheFile. // Needs to be * to prevent CXX from deleting it. using PkgDepCache = pkgDepCache*; using IndexFile = pkgIndexFile*; using PkgActionGroup = pkgDepCache::ActionGroup; using PkgIterator = pkgCache::PkgIterator; using VerIterator = pkgCache::VerIterator; using PrvIterator = pkgCache::PrvIterator; using DepIterator = pkgCache::DepIterator; using VerFileIterator = pkgCache::VerFileIterator; using DescFileIterator = pkgCache::DescFileIterator; using PkgFileIterator = pkgCache::PkgFileIterator; rust-apt-0.7.0/apt-pkg-c/util.h000064400000000000000000000056751046102023000143350ustar 00000000000000#pragma once #include #include #include #include #include #include #include "rust/cxx.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() { std::string err_str; while (!_error->empty()) { std::string msg; bool Type = _error->PopMessage(msg); err_str.append(Type == true ? "E:" : "W:"); err_str.append(msg); err_str.append(";"); } // Throwing runtime_error returns result to rust. // Remove the last ";" in the string before sending it. if (err_str.length()) { err_str.pop_back(); throw std::runtime_error(err_str); } } /// 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; } /// Getting the PkgCache can segfault if apt errors are not handled /// This function makes it safer as it will return a result in the event /// A package list or something is corrupt. /// See https://gitlab.com/volian/rust-apt/-/issues/24 inline pkgCache* safe_get_pkg_cache(pkgCacheFile* cache) { pkgCache* pkg_cache = cache->GetPkgCache(); handle_errors(); return pkg_cache; } /// Check if a string exists and return a Result to rust inline rust::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 int32_t cmp_versions(rust::String ver1_rust, rust::String ver2_rust) { const char* ver1 = ver1_rust.c_str(); const char* ver2 = ver2_rust.c_str(); if (!_system) { pkgInitSystem(*_config, _system); } return _system->VS->DoCmpVersion(ver1, ver1 + strlen(ver1), ver2, ver2 + strlen(ver2)); } /// Return an APT-styled progress bar (`[#### ]`). inline rust::String get_apt_progress_string(float percent, uint32_t 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.7.0/build.rs000064400000000000000000000025401046102023000130550ustar 00000000000000fn main() { let source_files = vec![ "src/raw/package.rs", "src/raw/cache.rs", "src/raw/progress.rs", "src/raw/config.rs", "src/raw/util.rs", "src/raw/records.rs", "src/raw/depcache.rs", "src/raw/pkgmanager.rs", ]; cxx_build::bridges(source_files) .file("apt-pkg-c/progress.cc") .flag_if_supported("-std=c++14") .compile("rust-apt"); println!("cargo:rustc-link-lib=apt-pkg"); println!("cargo:rerun-if-changed=src/raw/cache.rs"); println!("cargo:rerun-if-changed=src/raw/progress.rs"); println!("cargo:rerun-if-changed=src/raw/config.rs"); println!("cargo:rerun-if-changed=src/raw/util.rs"); println!("cargo:rerun-if-changed=src/raw/records.rs"); println!("cargo:rerun-if-changed=src/raw/depcache.rs"); println!("cargo:rerun-if-changed=src/raw/package.rs"); println!("cargo:rerun-if-changed=src/raw/pkgmanager.rs"); println!("cargo:rerun-if-changed=apt-pkg-c/progress.cc"); println!("cargo:rerun-if-changed=apt-pkg-c/cache.h"); println!("cargo:rerun-if-changed=apt-pkg-c/progress.h"); println!("cargo:rerun-if-changed=apt-pkg-c/configuration.h"); println!("cargo:rerun-if-changed=apt-pkg-c/util.h"); println!("cargo:rerun-if-changed=apt-pkg-c/records.h"); println!("cargo:rerun-if-changed=apt-pkg-c/depcache.h"); println!("cargo:rerun-if-changed=apt-pkg-c/package.h"); println!("cargo:rerun-if-changed=apt-pkg-c/pkgmanager.h"); } rust-apt-0.7.0/justfile000064400000000000000000000046061046102023000131650ustar 00000000000000#!/usr/bin/env just --justfile default: @just --list # Setup the development environment. @setup-dev: #!/bin/sh set -e echo Installing required packages from apt sudo apt-get install bear valgrind libapt-pkg-dev clang-format codespell -y 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! # Sudo is required to install packages with apt # 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! # Create the debs required for tests create-test-debs: #!/bin/sh set -e cd tests/files/cache rm -f *.deb for pkg in *; do dpkg-deb --build --nocheck "${pkg}"; done # 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="--skip ./target --skip ./.git": @codespell --builtin clear,rare,informal,code --ignore-words-list mut,crate {{ARGS}} @echo Spellings look good! rust-apt-0.7.0/netlify.toml000064400000000000000000000015511046102023000137600ustar 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.7.0/rustfmt.toml000064400000000000000000000007011046102023000140060ustar 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.7.0/src/cache.rs000064400000000000000000000460031046102023000136120ustar 00000000000000//! Contains Cache related structs. use std::cell::OnceCell; use std::error::Error; use std::fs; use std::ops::Deref; use std::path::Path; use cxx::{Exception, UniquePtr}; use crate::config::{init_config_system, Config}; use crate::depcache::DepCache; use crate::package::Package; use crate::raw::cache::raw; use crate::raw::package::RawPackage; use crate::raw::pkgmanager::raw::{ create_pkgmanager, create_problem_resolver, PackageManager, ProblemResolver, }; use crate::raw::progress::{AcquireProgress, InstallProgress, OperationProgress}; use crate::raw::records::raw::Records; use crate::util::{apt_lock, apt_unlock, apt_unlock_inner}; type RawRecords = UniquePtr; type RawPkgManager = UniquePtr; type RawProblemResolver = UniquePtr; /// Internal struct to pass into [`self::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 NoOpProgress { /// Return the AptAcquireProgress in a box /// To easily pass through for progress pub fn new_box() -> Box { Box::new(NoOpProgress {}) } } impl OperationProgress for NoOpProgress { fn update(&mut self, _: String, _: f32) {} fn done(&mut self) {} } /// Selection of Upgrade type 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, /// Upgrade will Not Install new or Remove packages. /// /// Equivalent to `apt-get upgrade`. SafeUpgrade, /// Upgrade will Install new but not Remove packages. /// /// Equivalent to `apt upgrade`. Upgrade, } pub enum Sort { /// Disable the sort method. Disable, /// Enable the sort method. Enable, /// Reverse the sort method. Reverse, } pub struct PackageSort { pub names: bool, pub upgradable: Sort, pub virtual_pkgs: Sort, pub installed: Sort, pub auto_installed: Sort, pub 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 { cache: raw::Cache, 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. /// /// deb_files allows you to add local `.deb` files to the cache. /// /// This function returns an [`Exception`] if any of the `.deb` files cannot /// be found. /// /// 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(deb_files: &[T]) -> Result { let deb_pkgs: Vec<_> = deb_files.iter().map(|d| d.to_string()).collect(); init_config_system(); Ok(Cache { cache: raw::create_cache(&deb_pkgs)?, depcache: OnceCell::new(), records: OnceCell::new(), pkgmanager: OnceCell::new(), problem_resolver: OnceCell::new(), local_debs: deb_files.iter().map(|d| d.to_string()).collect(), }) } /// Internal Method for generating the package list. pub fn raw_pkgs(&self) -> Result, Exception> { self.begin() } /// Get the DepCache pub fn depcache(&self) -> &DepCache { self.depcache .get_or_init(|| DepCache::new(self.create_depcache())) } /// Get the PkgRecords pub fn records(&self) -> &RawRecords { self.records.get_or_init(|| self.create_records()) } /// Get the PkgManager pub fn pkg_manager(&self) -> &RawPkgManager { self.pkgmanager .get_or_init(|| create_pkgmanager(&self.cache)) } /// Get the ProblemResolver pub fn resolver(&self) -> &RawProblemResolver { self.problem_resolver .get_or_init(|| create_problem_resolver(&self.cache)) } /// Iterate through the packages in a random order pub fn iter(&self) -> CacheIter { CacheIter { pkgs: self.begin().unwrap(), cache: self, } } /// An iterator of packages in the cache. pub fn packages(&self, sort: &PackageSort) -> Result, Exception> { 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 !pkg.has_versions() { continue; } }, // If reverse and the package has versions, exclude // This section is for if you only want virtual packages Sort::Reverse => { if pkg.has_versions() { continue; } }, } match sort.upgradable { // Virtual packages are enabled, include them. Sort::Disable => {}, // If disabled and pkg has no versions, exclude Sort::Enable => { // TODO: These are probably wrong. // If the package isn't installed, then it can not be upgradable if !pkg.is_installed() || !self.depcache().is_upgradable(&pkg) { continue; } }, // If reverse and the package has versions, exclude // This section is for if you only want virtual packages Sort::Reverse => { if pkg.is_installed() && self.depcache().is_upgradable(&pkg) { continue; } }, } match sort.installed { // Installed Package is Disabled, so we keep them Sort::Disable => {}, Sort::Enable => { if !pkg.is_installed() { continue; } }, // Only include installed packages. Sort::Reverse => { if pkg.is_installed() { 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 is installed or marked install then it cannot be Garbage. if (!pkg.is_installed() || !self.depcache().marked_install(&pkg)) || self.depcache().is_garbage(&pkg) { continue; } }, // Only include installed packages. // If the package is auto removable skip it. Sort::Reverse => { if (pkg.is_installed() || self.depcache().marked_install(&pkg)) && 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()); } Ok(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::raw::progress::{AcquireProgress, AptAcquireProgress}; /// /// let cache = new_cache!().unwrap(); /// let mut progress: Box = Box::new(AptAcquireProgress::new()); /// if let Err(error) = cache.update(&mut progress) { /// for msg in error.what().split(';') { /// if msg.starts_with("E:") { /// println!("Error: {}", &msg[2..]); /// } /// if msg.starts_with("W:") { /// println!("Warning: {}", &msg[2..]); /// } /// } /// } /// ``` /// # 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 Box) -> Result<(), Exception> { self.cache.update(progress)?; Ok(()) } /// 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<(), Exception> { let mut progress = NoOpProgress::new_box(); match upgrade_type { Upgrade::FullUpgrade => self.depcache().full_upgrade(&mut progress), Upgrade::SafeUpgrade => self.depcache().safe_upgrade(&mut progress), Upgrade::Upgrade => self.depcache().install_upgrade(&mut progress), } } /// 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::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<(), Exception> { // Use our dummy OperationProgress struct. See // [`crate::cache::OperationProgress`] for why we need this. self.resolver() .resolve(fix_broken, &mut NoOpProgress::new_box()) } /// 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).unwrap() { /// 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::raw::progress::{AptAcquireProgress}; /// /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("neovim").unwrap(); /// let mut progress = AptAcquireProgress::new_box(); /// /// 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 Box) -> Result<(), Exception> { self.pkg_manager() .get_archives(&self.cache, self.records(), progress) } /// 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::raw::progress::{AptAcquireProgress, AptInstallProgress}; /// /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("neovim").unwrap(); /// let mut acquire_progress = AptAcquireProgress::new_box(); /// let mut install_progress = AptInstallProgress::new_box(); /// /// 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 Box) -> Result<(), Exception> { self.pkg_manager().do_install(progress) } /// 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::raw::progress::{AptAcquireProgress, AptInstallProgress}; /// /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("neovim").unwrap(); /// let mut acquire_progress = AptAcquireProgress::new_box(); /// let mut install_progress = AptInstallProgress::new_box(); /// /// 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 Box, install_progress: &mut Box, ) -> Result<(), Box> { // 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, self.find_pkg(name)?)) } /// 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) -> Result, Exception> { 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()); } Ok(changed .into_iter() .map(|pkg_ptr| Package::new(self, pkg_ptr))) } } /// Iterator Implementation for the Cache. pub struct CacheIter<'a> { pkgs: RawPackage, 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()?)) } } impl<'a> IntoIterator for &'a Cache { type IntoIter = CacheIter<'a>; type Item = Package<'a>; fn into_iter(self) -> Self::IntoIter { self.iter() } } /// Implementation to be able to call the raw cache methods from the high level /// struct impl Deref for Cache { type Target = raw::Cache; #[inline] fn deref(&self) -> &raw::Cache { &self.cache } } rust-apt-0.7.0/src/config.rs000064400000000000000000000125021046102023000140110ustar 00000000000000//! Contains config related structs and functions. use crate::raw::config::raw; /// 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 {} 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() } } 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::config_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::config_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::config_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::config_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::config_clear_all(); } /// Returns a string dump of configuration options separated by `\n` pub fn dump(&self) -> String { raw::config_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::config_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::config_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::config_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::config_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::config_find_bool(key.to_string(), default) } /// Same as find, but for i32 values. pub fn int(&self, key: &str, default: i32) -> i32 { raw::config_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::config_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::config_get_architectures() } /// Simply check if a key exists. pub fn contains(&self, key: &str) -> bool { raw::config_exists(key.to_string()) } /// Set the given key to the specified value. pub fn set(&self, key: &str, value: &str) { raw::config_set(key.to_string(), value.to_string()) } /// 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::config_set(vec_key.to_string(), value.to_string()); } } } /// 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::config_find("APT".to_string(), "".to_string()).is_empty() { raw::init_config(); } raw::init_system(); } rust-apt-0.7.0/src/depcache.rs000064400000000000000000000030561046102023000143040ustar 00000000000000use std::ops::Deref; use cxx::Exception; use crate::package::{Package, Version}; use crate::raw::depcache::raw; use crate::raw::progress::NoOpProgress; use crate::util::DiskSpace; type RawDepCache = raw::DepCache; pub struct DepCache { ptr: RawDepCache, } impl DepCache { pub fn new(ptr: RawDepCache) -> DepCache { DepCache { ptr } } /// Clear any marked changes in the DepCache. pub fn clear_marked(&self) -> Result<(), Exception> { // Use our dummy OperationProgress struct. self.init(&mut NoOpProgress::new_box()) } /// 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) } /// Returns the installed version if it exists. /// /// # This differs from [`crate::package::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<'a>(&self, pkg: &'a Package) -> Option> { // Cxx error here just indicates that the Version doesn't exist Some(Version::new(self.ptr.install_version(pkg)?, pkg.cache)) } } impl Deref for DepCache { type Target = RawDepCache; #[inline] fn deref(&self) -> &RawDepCache { &self.ptr } } rust-apt-0.7.0/src/lib.rs000064400000000000000000000013261046102023000133140ustar 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. #[macro_use] pub mod raw; pub mod cache; pub mod config; pub mod depcache; pub mod macros; pub mod package; pub mod records; pub mod tagfile; pub mod util; rust-apt-0.7.0/src/macros.rs000064400000000000000000000014301046102023000140260ustar 00000000000000#[macro_export] /// Macro to create the cache, optionally including debs /// /// 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()); /// /// let local_debs = vec![ /// "tests/files/cache/apt.deb", /// "tests/files/cache/dep-pkg1_0.0.1.deb", /// ]; /// /// let cache = new_cache!(&local_debs).unwrap(); /// println!("{}", cache.get("apt").unwrap().get_version("5000:1.0.0").unwrap().version()); /// ``` /// /// Returns `Result` macro_rules! new_cache { () => {{ let debs: Vec = Vec::new(); $crate::cache::Cache::new(&debs) }}; ($slice:expr) => {{ $crate::cache::Cache::new($slice) }}; } rust-apt-0.7.0/src/package.rs000064400000000000000000000526201046102023000141440ustar 00000000000000//! Contains Package, Version and Dependency Structs. use std::cell::OnceCell; use std::cmp::Ordering; use std::collections::HashMap; use std::fmt; use std::hash::{Hash, Hasher}; use std::ops::Deref; use crate::cache::Cache; pub use crate::raw::package::DepType; use crate::raw::package::{RawDependency, RawPackage, RawPackageFile, RawProvider, RawVersion}; use crate::util::cmp_versions; pub struct Package<'a> { ptr: RawPackage, pub(crate) cache: &'a Cache, rdepends_map: OnceCell>>>, } impl<'a> Package<'a> { pub fn new(cache: &'a Cache, ptr: RawPackage) -> Package<'a> { Package { ptr, cache, rdepends_map: OnceCell::new(), } } /// Internal Method for generating the version list. fn raw_versions(&self) -> impl Iterator { self.version_list().into_iter().flatten() } /// Returns a Reverse Dependency Map of the package /// /// 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; /// use rust_apt::package::DepType; /// let cache = new_cache!().unwrap(); /// let pkg = cache.get("apt").unwrap(); /// for dep in pkg.rdepends_map().get(&DepType::Depends).unwrap() { /// if dep.is_or() { /// for base_dep in &dep.base_deps { /// println!("{}", base_dep.name()) /// } /// } else { /// // is_or is false so there is only one BaseDep /// println!("{}", dep.first().name()) /// } /// } /// ``` pub fn rdepends_map(&self) -> &HashMap>> { self.rdepends_map .get_or_init(|| create_depends_map(self.cache, self.rev_depends_list())) } /// 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 self.raw_versions() { if version_str == ver.version() { return Some(Version::new(ver, self.cache)); } } None } /// Returns the version object of the installed version. /// /// If there isn't an installed version, returns None pub fn installed(&self) -> Option { // Cxx error here just indicates that the Version doesn't exist Some(Version::new(self.current_version()?, self.cache)) } /// Returns the version object of the candidate. /// /// If there isn't a candidate, returns None pub fn candidate(&self) -> Option { // Cxx error here just indicates that the Version doesn't exist Some(Version::new( self.cache.depcache().candidate_version(self)?, self.cache, )) } /// Returns a version list /// starting with the newest and ending with the oldest. pub fn versions(&self) -> impl Iterator { self.raw_versions().map(|ver| Version::new(ver, self.cache)) } /// Returns a list of providers pub fn provides(&self) -> impl Iterator> { self.provides_list() .into_iter() .flatten() .map(|provider| Provider::new(provider, 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> Deref for Package<'a> { type Target = RawPackage; #[inline] fn deref(&self) -> &RawPackage { &self.ptr } } // Implementations for comparing packages. impl<'a> PartialEq for Package<'a> { fn eq(&self, other: &Self) -> bool { self.id() == other.id() } } 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() } } pub struct Version<'a> { ptr: RawVersion, cache: &'a Cache, depends_map: OnceCell>>>, } impl<'a> Version<'a> { pub fn new(ptr: RawVersion, cache: &'a Cache) -> Version<'a> { Version { ptr, cache, depends_map: OnceCell::new(), } } /// Returns a list of providers pub fn provides(&self) -> impl Iterator> { self.provides_list() .into_iter() .flatten() .map(|provider| Provider::new(provider, self.cache)) } /// Returns an iterator of PackageFiles (Origins) for the version pub fn package_files(&self) -> impl Iterator + '_ { // TODO: We should probably not expect here. self.version_files() .expect("No Version Files") .map(|pkg_file| pkg_file.pkg_file()) } /// Return the version's parent package. pub fn parent(&self) -> Package<'a> { Package::new(self.cache, 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; /// use rust_apt::package::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.base_deps { /// 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, self.depends())) } /// 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(); if let Some(dep_list) = self.get_depends(&DepType::Depends) { for dep in dep_list { ret_vec.push(dep) } } if let Some(dep_list) = self.get_depends(&DepType::PreDepends) { 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) } /// Get the translated long description pub fn description(&self) -> Option { if let Some(desc_file) = self.description_files()?.next() { self.cache.records().desc_file_lookup(&desc_file); return self.cache.records().long_desc().ok(); } None } /// Get the translated short description pub fn summary(&self) -> Option { if let Some(desc_file) = self.description_files()?.next() { self.cache.records().desc_file_lookup(&desc_file); return self.cache.records().short_desc().ok(); } None } /// 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 { if let Some(ver_file) = self.version_files()?.next() { self.cache.records().ver_file_lookup(&ver_file); return self.cache.records().get_field(field.to_string()).ok(); } None } /// Get the hash specified. If there isn't one returns None /// `version.hash("md5sum")` pub fn hash(&self, hash_type: &T) -> Option { if let Some(ver_file) = self.version_files()?.next() { self.cache.records().ver_file_lookup(&ver_file); return self.cache.records().hash_find(hash_type.to_string()).ok(); } None } /// 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(&'a self) -> impl Iterator + '_ { // TODO: Maybe remove Package_files method and make a map of ver_file pkg_file? self.package_files().filter_map(|mut pkg_file| { self.cache.find_index(&mut pkg_file); let ver_file = self.version_files()?.next()?; self.cache.records().ver_file_lookup(&ver_file); if let Ok(uri) = self.cache.records().ver_uri(&pkg_file) { // Should match this from the configurations. Hardcoding is okay for now. if !uri.ends_with("/var/lib/dpkg/status") { return Some(uri); } } None }) } /// 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> Deref for Version<'a> { type Target = RawVersion; #[inline] fn deref(&self) -> &RawVersion { &self.ptr } } 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(); // Lifetimes make us have to do some weird things. let is_candidate = match self.cache.depcache().candidate_version(&parent) { Some(cand) => { let temp_ver = Version::new(cand, self.cache); self == &temp_ver }, None => false, }; f.debug_struct("Version") .field("pkg", &parent.name()) .field("arch", &self.arch()) .field("version", &self.version()) .field("is_candidate", &is_candidate) .field("is_installed", &self.is_installed()) .finish_non_exhaustive() } } pub fn create_depends_map( cache: &Cache, dep: Option, ) -> HashMap> { let mut dependencies: HashMap> = HashMap::new(); if let Some(dep) = dep { while !dep.end() { let mut or_deps = vec![]; or_deps.push(BaseDep::new(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.compare_op() && !dep.is_reverse() { loop { dep.raw_next(); or_deps.push(BaseDep::new(dep.unique(), cache)); // This is the last of the Or group if !dep.compare_op() { break; } } } let dep_type = 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 { base_deps: or_deps }) } else { // Doesn't exist so we create it dependencies.insert(dep_type, vec![Dependency { base_deps: or_deps }]); } dep.raw_next(); } } dependencies } /// A struct representing a Base Dependency. pub struct BaseDep<'a> { ptr: RawDependency, cache: &'a Cache, target: OnceCell>, parent_ver: OnceCell, } impl<'a> BaseDep<'a> { pub fn new(ptr: RawDependency, 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, self.parent_pkg()) } else { Package::new(self.cache, 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(|| self.parent_ver()).version()) } else { self.target_ver().ok() } } /// Comparison type of the dependency version, if specified. pub fn comp(&self) -> Option<&str> { self.comp_type().ok() } // Iterate all Versions that are able to satisfy this dependency pub fn all_targets(&self) -> impl Iterator { self.ptr.all_targets() } } impl<'a> Deref for BaseDep<'a> { type Target = RawDependency; #[inline] fn deref(&self) -> &RawDependency { &self.ptr } } impl<'a> fmt::Display for BaseDep<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let (Some(comp), Some(version)) = (self.comp(), 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", &self.parent_pkg().name()) .field("name", &self.name()) .field("comp", &self.comp()) .field("version", &self.version()) .field("dep_type", &self.dep_type()) .field("is_reverse", &self.is_reverse()) .finish() } } /// A struct representing an Or_Group of Dependencies. #[derive(fmt::Debug)] pub struct Dependency<'a> { /// Vector of BaseDeps that can satisfy this dependency. pub base_deps: Vec>, } impl<'a> Dependency<'a> { /// Return the Dep Type of this group. Depends, Pre-Depends. pub fn dep_type(&self) -> DepType { self.base_deps[0].dep_type() } /// Returns True if there are multiple dependencies that can satisfy this pub fn is_or(&self) -> bool { self.base_deps.len() > 1 } /// Returns a reference to the first BaseDep pub fn first(&self) -> &BaseDep<'a> { &self.base_deps[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.base_deps.iter().enumerate() { dep_str += &base_dep.to_string(); if i + 1 != self.base_deps.len() { dep_str += " | " } } write!( f, "{} {:?} {dep_str}", self.first().parent_pkg().fullname(false), self.dep_type(), )?; Ok(()) } } pub struct Provider<'a> { ptr: RawProvider, cache: &'a Cache, } impl<'a> Provider<'a> { pub fn new(ptr: RawProvider, 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, self.target_pkg()) } /// Return the Target Version of the provider. pub fn version(&'a self) -> Version<'a> { Version::new(self.target_ver(), self.cache) } } impl<'a> Deref for Provider<'a> { type Target = RawProvider; #[inline] fn deref(&self) -> &RawProvider { &self.ptr } } 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() } } // Implementation allowing structs to be put into a hashmap impl<'a> Hash for Package<'a> { fn hash(&self, state: &mut H) { self.id().hash(state); } } impl<'a> Hash for Version<'a> { fn hash(&self, state: &mut H) { self.id().hash(state); } } impl<'a> Eq for Package<'a> {} impl<'a> Eq for Version<'a> {} rust-apt-0.7.0/src/raw/cache.rs000064400000000000000000000046721046102023000144110ustar 00000000000000//! Contains Cache related structs. use super::package::RawPackage; /// This module contains the bindings and structs shared with c++ #[cxx::bridge] pub mod raw { pub struct Cache { ptr: UniquePtr, } impl UniquePtr {} unsafe extern "C++" { include!("rust-apt/apt-pkg-c/types.h"); include!("rust-apt/apt-pkg-c/package.h"); include!("rust-apt/apt-pkg-c/util.h"); include!("rust-apt/apt-pkg-c/depcache.h"); include!("rust-apt/apt-pkg-c/records.h"); include!("rust-apt/apt-pkg-c/progress.h"); include!("rust-apt/apt-pkg-c/cache.h"); type PkgCacheFile; type Package = crate::raw::package::raw::Package; type Version = crate::raw::package::raw::Version; type PackageFile = crate::raw::package::raw::PackageFile; type SourceURI = crate::raw::package::raw::SourceURI; type Records = crate::raw::records::raw::Records; type DepCache = crate::raw::depcache::raw::DepCache; type DynAcquireProgress = crate::raw::progress::raw::DynAcquireProgress; /// Create the CacheFile. /// /// It is advised to init the config and system before creating the /// cache. These bindings can be found in config::raw. pub fn create_cache(deb_files: &[String]) -> Result; // TODO: What kind of errors can be returned here? // TODO: Implement custom errors to match with apt errors /// Update the package lists, handle errors and return a Result. pub fn update(self: &Cache, progress: &mut DynAcquireProgress) -> Result<()>; /// Returns an iterator of SourceURIs. /// /// These are the files that `apt update` will fetch. pub fn source_uris(self: &Cache) -> Vec; pub fn create_depcache(self: &Cache) -> DepCache; pub fn create_records(self: &Cache) -> UniquePtr; /// The priority of the Version as shown in `apt policy`. pub fn priority(self: &Cache, version: &Version) -> i32; /// Lookup the IndexFile of the Package file pub fn find_index(self: &Cache, pkg_file: &mut PackageFile); /// Return true if the PackageFile is trusted. pub fn is_trusted(self: &Cache, pkg_file: &mut PackageFile) -> bool; /// Return a package by name and optionally architecture. pub fn unsafe_find_pkg(self: &Cache, name: String) -> Package; /// Return the pointer to the start of the PkgIterator. pub fn begin(self: &Cache) -> Result; } } impl raw::Cache { pub fn find_pkg(&self, name: &str) -> Option { self.unsafe_find_pkg(name.to_string()).make_safe() } } rust-apt-0.7.0/src/raw/config.rs000064400000000000000000000036471046102023000146140ustar 00000000000000/// This module contains the bindings and structs shared with c++ #[cxx::bridge] pub mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/configuration.h"); /// 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 config_dump() -> String; /// Find a key and return it's value as a string. pub fn config_find(key: String, default_value: String) -> String; /// Find a file and return it's value as a string. pub fn config_find_file(key: String, default_value: String) -> String; /// Find a directory and return it's value as a string. pub fn config_find_dir(key: String, default_value: String) -> String; /// Same as find, but for boolean values. pub fn config_find_bool(key: String, default_value: bool) -> bool; /// Same as find, but for i32 values. pub fn config_find_int(key: String, default_value: i32) -> i32; /// Return a vector for an Apt configuration list. pub fn config_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 config_get_architectures() -> Vec; /// Set the given key to the specified value. pub fn config_set(key: String, value: String); /// Simply check if a key exists. pub fn config_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 `config_clear_value` pub fn config_clear(key: String); /// Clears all configuratations. pub fn config_clear_all(); /// Clear a single value from a list. /// Used for removing one item in an apt configuruation list pub fn config_clear_value(key: String, value: String); } } rust-apt-0.7.0/src/raw/depcache.rs000064400000000000000000000223371046102023000151000ustar 00000000000000use super::package::{RawPackage, RawVersion}; /// This module contains the bindings and structs shared with c++ #[cxx::bridge] pub mod raw { pub struct DepCache { ptr: UniquePtr, } /// 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; /// use rust_apt::raw::progress::{AcquireProgress, AptAcquireProgress}; /// /// let cache = new_cache!().unwrap(); /// let action_group = cache.depcache().action_group(); /// /// // The C++ deconstructor will be run when the action group leaves scope. /// // You can also call it explicitly. /// action_group.release(); /// ``` pub struct ActionGroup { ptr: UniquePtr, } unsafe extern "C++" { include!("rust-apt/apt-pkg-c/types.h"); include!("rust-apt/apt-pkg-c/package.h"); include!("rust-apt/apt-pkg-c/util.h"); include!("rust-apt/apt-pkg-c/progress.h"); include!("rust-apt/apt-pkg-c/depcache.h"); type PkgDepCache; type PkgActionGroup; type Package = crate::raw::package::raw::Package; type Version = crate::raw::package::raw::Version; type Dependency = crate::raw::package::raw::Dependency; type DynOperationProgress = crate::raw::progress::raw::DynOperationProgress; pub fn init(self: &DepCache, callback: &mut DynOperationProgress) -> Result<()>; /// Autoinstall every broken package and run the problem resolver /// Returns false if the problem resolver fails. pub fn fix_broken(self: &DepCache) -> bool; /// Return a new [`ActionGroup`] of the current DepCache /// /// ActionGroup will be released once it leaves scope /// or ['ActionGroup::release'] is called pub fn action_group(self: &DepCache) -> ActionGroup; /// This will release the [`ActionGroup`] which will trigger a /// MarkAndSweep pub fn release(self: &ActionGroup); /// Perform a Full Upgrade. /// Remove and install new packages if necessary. pub fn full_upgrade(self: &DepCache, progress: &mut DynOperationProgress) -> Result<()>; /// Perform a Safe Upgrade. Neither remove or install new packages. pub fn safe_upgrade(self: &DepCache, progress: &mut DynOperationProgress) -> Result<()>; /// Perform an Install Upgrade. /// New packages will be installed but nothing will be removed. pub fn install_upgrade(self: &DepCache, progress: &mut DynOperationProgress) -> Result<()>; /// 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: &DepCache, pkg: &Package) -> bool; /// Is the Package auto installed? Packages marked as auto installed are /// usually dependencies. pub fn is_auto_installed(self: &DepCache, pkg: &Package) -> bool; /// Is the Package able to be auto removed? pub fn is_garbage(self: &DepCache, pkg: &Package) -> bool; /// Is the Package marked for install? pub fn marked_install(self: &DepCache, pkg: &Package) -> bool; /// Is the Package marked for upgrade? pub fn marked_upgrade(self: &DepCache, pkg: &Package) -> bool; /// Is the Package marked to be purged? pub fn marked_purge(self: &DepCache, pkg: &Package) -> bool; /// Is the Package marked for removal? pub fn marked_delete(self: &DepCache, pkg: &Package) -> bool; /// Is the Package marked for keep? pub fn marked_keep(self: &DepCache, pkg: &Package) -> bool; /// Is the Package marked for downgrade? pub fn marked_downgrade(self: &DepCache, pkg: &Package) -> bool; /// Is the Package marked for reinstall? pub fn marked_reinstall(self: &DepCache, pkg: &Package) -> 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: &DepCache, pkg: &Package, 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: &DepCache, pkg: &Package) -> 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: &DepCache, pkg: &Package, 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: &DepCache, pkg: &Package, auto_inst: bool, from_user: bool, ) -> bool; /// Set a version to be the candidate of it's package. pub fn set_candidate_version(self: &DepCache, ver: &Version); /// 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. pub fn unsafe_candidate_version(self: &DepCache, pkg: &Package) -> Version; /// Get a pointer to the version that is installed. /// /// * 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 segfault. /// /// Safety: If there is no candidate the inner pointer will be null. /// This will cause segfaults if methods are used on a Null Version. pub fn unsafe_install_version(self: &DepCache, pkg: &Package) -> Version; /// Returns the state of the dependency as u8 pub fn dep_state(self: &DepCache, dep: &Dependency) -> 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: &DepCache, dep: &Dependency) -> 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: &DepCache, pkg: &Package, reinstall: bool); /// Is the installed Package broken? pub fn is_now_broken(self: &DepCache, pkg: &Package) -> bool; /// Is the Package to be installed broken? pub fn is_inst_broken(self: &DepCache, pkg: &Package) -> bool; /// The number of packages marked for installation. pub fn install_count(self: &DepCache) -> u32; /// The number of packages marked for removal. pub fn delete_count(self: &DepCache) -> u32; /// The number of packages marked for keep. pub fn keep_count(self: &DepCache) -> u32; /// The number of packages with broken dependencies in the cache. pub fn broken_count(self: &DepCache) -> u32; /// The size of all packages to be downloaded. pub fn download_size(self: &DepCache) -> 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: &DepCache) -> i64; } } impl raw::DepCache { pub fn candidate_version(&self, pkg: &RawPackage) -> Option { self.unsafe_candidate_version(pkg).make_safe() } pub fn install_version(&self, pkg: &RawPackage) -> Option { self.unsafe_install_version(pkg).make_safe() } } rust-apt-0.7.0/src/raw/mod.rs000064400000000000000000000002071046102023000141130ustar 00000000000000pub mod cache; pub mod config; pub mod depcache; pub mod package; pub mod pkgmanager; pub mod progress; pub mod records; pub mod util; rust-apt-0.7.0/src/raw/package.rs000064400000000000000000000464131046102023000147400ustar 00000000000000pub type RawPackage = raw::Package; pub type RawVersion = raw::Version; pub type RawProvider = raw::Provider; pub type RawDependency = raw::Dependency; pub type RawVersionFile = raw::VersionFile; pub type RawDescriptionFile = raw::DescriptionFile; pub type RawPackageFile = raw::PackageFile; use std::fmt; use std::hash::{Hash, Hasher}; /// This module contains the bindings and structs shared with c++ #[cxx::bridge] pub mod raw { // Some weirdness exists in import order. // SourceURI is defined here, but used in the cache. // We need to impl vec so it can be put in one. impl Vec {} #[derive(Debug)] pub struct SourceURI { pub uri: String, pub path: String, } pub struct Package { ptr: UniquePtr, } pub struct Version { ptr: UniquePtr, } pub struct Provider { ptr: UniquePtr, } pub struct Dependency { ptr: UniquePtr, } pub struct VersionFile { ptr: UniquePtr, } pub struct DescriptionFile { ptr: UniquePtr, } pub struct PackageFile { ptr: UniquePtr, index_file: UniquePtr, } unsafe extern "C++" { include!("rust-apt/apt-pkg-c/types.h"); include!("rust-apt/apt-pkg-c/package.h"); type PkgIterator; type VerIterator; type PrvIterator; type DepIterator; type VerFileIterator; type DescFileIterator; type PkgFileIterator; type IndexFile; // Package Declarations /// Get the name of the package without the architecture. pub fn name(self: &Package) -> &str; /// Get the architecture of a package. pub fn arch(self: &Package) -> &str; /// Get the fullname of the package. /// /// Pretty is a bool that will omit the native arch. pub fn fullname(self: &Package, pretty: bool) -> String; /// Get the ID of a package. pub fn id(self: &Package) -> u32; /// Get the current state of a package. pub fn current_state(self: &Package) -> u8; /// Get the installed state of a package. pub fn inst_state(self: &Package) -> u8; /// Get the selected state of a package. pub fn selected_state(self: &Package) -> u8; /// Get a pointer the the currently installed version /// /// Safety: If Version.end() is true, /// calling methods on the Version can segfault. pub fn unsafe_current_version(self: &Package) -> Version; /// Get a pointer to the beginning of the VerIterator /// /// Safety: If Version.end() is true, /// calling methods on the Version can segfault. pub fn unsafe_version_list(self: &Package) -> Version; /// Get the providers of this package pub fn unsafe_provides(self: &Package) -> Provider; pub fn unsafe_rev_depends(self: &Package) -> Dependency; /// True if the package is essential. pub fn is_essential(self: &Package) -> bool; pub fn raw_next(self: &Package); /// This will tell you if the inner PkgIterator is null /// /// The cxx is_null function will still show non null because of /// wrappers in c++ pub fn end(self: &Package) -> bool; // A simple way to clone the pointer pub fn unique(self: &Package) -> Package; // Version Declarations /// The version string of the version. "1.4.10". pub fn version(self: &Version) -> &str; /// The Arch of the version. "amd64". pub fn arch(self: &Version) -> &str; /// Return the version's parent RawPackage. pub fn parent_pkg(self: &Version) -> Package; /// The ID of the version. pub fn id(self: &Version) -> u32; /// The section of the version as shown in `apt show`. pub fn section(self: &Version) -> Result<&str>; /// The priority string as shown in `apt show`. pub fn priority_str(self: &Version) -> Result<&str>; /// The size of the .deb file. pub fn size(self: &Version) -> u64; /// The uncompressed size of the .deb file. pub fn installed_size(self: &Version) -> u64; /// True if the version is able to be downloaded. pub fn is_downloadable(self: &Version) -> bool; /// True if the version is currently installed pub fn is_installed(self: &Version) -> bool; /// Always contains the name, even if it is the same as the binary name pub fn source_name(self: &Version) -> &str; // Always contains the version string, // even if it is the same as the binary version. pub fn source_version(self: &Version) -> &str; pub fn unsafe_provides(self: &Version) -> Provider; pub fn unsafe_depends(self: &Version) -> Dependency; // This is for backend records lookups. // You can also get package files from here. pub fn unsafe_description_file(self: &Version) -> Result; // You go through here to get the package files. pub fn unsafe_version_file(self: &Version) -> VersionFile; /// Return the parent package. TODO: This probably isn't going to work /// rn pub fn parent(self: &Package) -> bool; pub fn raw_next(self: &Version); /// This will tell you if the inner PkgIterator is null /// /// The cxx is_null function will still show non null because of /// wrappers in c++ pub fn end(self: &Version) -> bool; // A simple way to clone the pointer pub fn unique(self: &Version) -> Version; // Provider Declarations /// The name of what this provider provides pub fn name(self: &Provider) -> &str; pub fn version_str(self: &Provider) -> Result<&str>; /// The Target Package that can satisfy this provides pub fn target_pkg(self: &Provider) -> Package; pub fn parent_pkg(self: &Dependency) -> Package; pub fn parent_ver(self: &Dependency) -> Version; /// The Target Version that can satisfy this provides pub fn target_ver(self: &Provider) -> Version; pub fn raw_next(self: &Provider); // A simple way to clone the pointer pub fn unique(self: &Provider) -> Provider; /// This will tell you if the inner PkgIterator is null /// /// The cxx is_null function will still show non null because of /// wrappers in c++ pub fn end(self: &Provider) -> bool; // Dependency Declarations /// String representation of the dependency compare type /// "","<=",">=","<",">","=","!=" pub fn comp_type(self: &Dependency) -> Result<&str>; pub fn index(self: &Dependency) -> u32; // Get the dependency type as a u8 pub fn u8_dep_type(self: &Dependency) -> u8; pub fn target_ver(self: &Dependency) -> Result<&str>; pub fn target_pkg(self: &Dependency) -> Package; pub fn all_targets(self: &Dependency) -> Version; /// Return true if this dep is Or'd with the next. The last dep in the /// or group will return False. pub fn compare_op(self: &Dependency) -> bool; /// Increment the Dep Iterator once pub fn raw_next(self: &Dependency); /// Return True if the dep is reverse, false if normal pub fn is_reverse(self: &Dependency) -> bool; /// Is the pointer null, basically pub fn end(self: &Dependency) -> bool; // A simple way to clone the pointer pub fn unique(self: &Dependency) -> Dependency; // PackageFile Declarations /// The path to the PackageFile pub fn filename(self: &PackageFile) -> Result<&str>; /// The Archive of the PackageFile. ex: unstable pub fn archive(self: &PackageFile) -> Result<&str>; /// The Origin of the PackageFile. ex: Debian pub fn origin(self: &PackageFile) -> Result<&str>; /// The Codename of the PackageFile. ex: main, non-free pub fn codename(self: &PackageFile) -> Result<&str>; /// The Label of the PackageFile. ex: Debian pub fn label(self: &PackageFile) -> Result<&str>; /// The Hostname of the PackageFile. ex: deb.debian.org pub fn site(self: &PackageFile) -> Result<&str>; /// The Component of the PackageFile. ex: sid pub fn component(self: &PackageFile) -> Result<&str>; /// The Architecture of the PackageFile. ex: amd64 pub fn arch(self: &PackageFile) -> 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: &PackageFile) -> Result<&str>; /// The Index number of the PackageFile pub fn index(self: &PackageFile) -> u64; // /// VersionFile Declarations /// Return the package file associated with this version file. pub fn pkg_file(self: &VersionFile) -> PackageFile; /// Increment the iterator pub fn raw_next(self: &VersionFile); pub fn index(self: &VersionFile) -> u64; pub fn end(self: &VersionFile) -> bool; // A simple way to clone the pointer pub fn unique(self: &VersionFile) -> VersionFile; /// Return the package file associated with this version file. pub fn pkg_file(self: &DescriptionFile) -> PackageFile; /// Increment the iterator pub fn raw_next(self: &DescriptionFile); pub fn index(self: &DescriptionFile) -> u64; pub fn end(self: &DescriptionFile) -> bool; // A simple way to clone the pointer pub fn unique(self: &DescriptionFile) -> DescriptionFile; } } impl raw::Package { pub fn current_version(&self) -> Option { self.unsafe_current_version().make_safe() } pub fn version_list(&self) -> Option { self.unsafe_version_list().make_safe() } pub fn provides_list(&self) -> Option { self.unsafe_provides().make_safe() } pub fn rev_depends_list(&self) -> Option { self.unsafe_rev_depends().make_safe() } /// True if the Package is installed. pub fn is_installed(&self) -> bool { self.current_version().is_some() } /// True if the package has versions. /// /// If a package has no versions it is considered virtual. pub fn has_versions(&self) -> bool { self.version_list().is_some() } /// True if the package provides any other packages. pub fn has_provides(&self) -> bool { self.provides_list().is_some() } } impl raw::Version { pub fn provides_list(&self) -> Option { self.unsafe_provides().make_safe() } pub fn depends(&self) -> Option { self.unsafe_depends().make_safe() } pub fn version_files(&self) -> Option { self.unsafe_version_file().make_safe() } pub fn description_files(&self) -> Option { self.unsafe_description_file().ok()?.make_safe() } } impl raw::Dependency { /// The Dependency Type. Depends, Recommends, etc. pub fn dep_type(&self) -> DepType { DepType::from(self.u8_dep_type()) } /// Returns true if the dependency type is critical. /// /// Depends, PreDepends, Conflicts, Obsoletes, Breaks /// will return [true]. /// /// Suggests, Recommends, Replaces and Enhances /// will return [false]. pub fn is_critical(&self) -> bool { match self.dep_type() { DepType::Depends => true, DepType::PreDepends => true, DepType::Suggests => false, DepType::Recommends => false, DepType::Conflicts => true, DepType::Replaces => false, DepType::Obsoletes => true, DepType::Breaks => true, DepType::Enhances => false, } } } /// 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 DeoGVer: u8 = 32; } #[derive(fmt::Debug, Eq, PartialEq, Hash)] pub enum DepType { Depends, PreDepends, Suggests, Recommends, Conflicts, Replaces, Obsoletes, Breaks, Enhances, } 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::Breaks, 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::Breaks => "Breaks", DepType::Enhances => "Enhances", } } } impl fmt::Display for DepType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.as_ref()) } } macro_rules! raw_iter { ($structname: ident) => { impl Iterator for $structname { type Item = $structname; fn next(&mut self) -> Option { if self.end() { None } else { let ptr = self.unique(); self.raw_next(); Some(ptr) } } } impl $structname { pub fn make_safe(self) -> Option<$structname> { if self.end() { None } else { Some(self) } } } }; } raw_iter!(RawPackage); raw_iter!(RawVersion); raw_iter!(RawProvider); raw_iter!(RawDependency); raw_iter!(RawVersionFile); raw_iter!(RawDescriptionFile); // TODO: Maybe make some internal macros and export them so // this can be used in the higher level package.rs macro_rules! raw_hash { ($structname: ident) => { impl Hash for $structname { fn hash(&self, state: &mut H) { self.id().hash(state); } } impl PartialEq for $structname { fn eq(&self, other: &$structname) -> bool { self.id() == other.id() } } impl Eq for $structname {} }; } raw_hash!(RawPackage); raw_hash!(RawVersion); #[cfg(test)] mod raw_tests { use crate::raw::cache::raw::create_cache; use crate::raw::package::RawVersionFile; #[test] fn test() { crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); let pkg = cache.find_pkg("apt").unwrap(); dbg!(pkg.name()); let installed = pkg.current_version().unwrap(); dbg!(installed.version()); for pkg in cache.begin().unwrap() { println!("ID: {}", pkg.id()); println!("Name: {}", pkg.name()); println!("Arch: {}", pkg.arch()); println!("FullName: {}", pkg.fullname(false)); println!("current_state: {}", pkg.current_state()); println!("inst_state: {}", pkg.inst_state()); println!("selected_state: {}\n", pkg.selected_state()); match pkg.version_list() { Some(versions) => { for ver in versions { println!("Version of '{}'", pkg.name()); println!(" Version: {}", &ver.version()); println!(" Arch: {}", &ver.arch()); println!(" Section: {}", &ver.section().unwrap_or_default()); println!(" Source Pkg: {}", &ver.source_name()); println!(" Source Version: {}\n", &ver.source_version()); println!("End: {}\n\n", pkg.end()); } }, None => { println!("'{}' is a Virtual Package\n", pkg.name()); }, } } } #[test] fn raw_provides() { crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); // Check Native Arch let pkg = cache.find_pkg("www-browser").unwrap(); println!("{}:{}", pkg.name(), pkg.arch()); for provider in pkg.provides_list().unwrap() { println!("Provider: {}", provider.name()); println!( " Pkg: {}, Version: {}", provider.target_pkg().name(), provider.target_ver().version() ); } let pkg = cache.find_pkg("apt").unwrap(); let cand = pkg.current_version().unwrap(); for provider in cand.provides_list().unwrap() { println!("Provider: {}", provider.name()); println!( " Pkg: {}, Version: {}", provider.target_pkg().name(), provider.target_ver().version() ); } } #[test] fn raw_depends() { crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); let pkg = cache.find_pkg("apt").unwrap(); let cand = pkg.current_version().unwrap(); for dep in cand.depends().unwrap() { println!( "Dep: {}, Comp Op: {}", dep.target_pkg().name(), dep.compare_op() ); for dep_ver in dep.all_targets() { println!("Version: {}", dep_ver.version()) } } } #[test] fn raw_files() { crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); let pkg = cache.find_pkg("apt").unwrap(); let cand = pkg.current_version().unwrap(); let depcache = cache.create_depcache(); // Apt should have a candidate as well as current version assert!(!depcache.unsafe_candidate_version(&pkg).ptr.is_null()); let ver_files: Vec = cand.version_files().unwrap().collect(); println!("Ver Files: {}", ver_files.len()); println!("Desc Files: {}", cand.description_files().unwrap().count()); for file in ver_files { let pkg_file = file.pkg_file(); #[rustfmt::skip] // Skip Formatting the string. println!( "PackageFile: {{\n \ FileName: {},\n \ Archive: {},\n \ Origin: {}\n \ Label: {}\n \ Site: {}\n \ Arch: {}\n \ Component: {}\n \ Index Type: {}\n \ Index: {}\n\ }}", pkg_file.filename().unwrap_or("Unknown"), pkg_file.archive().unwrap_or("Unknown"), pkg_file.origin().unwrap_or("Unknown"), pkg_file.label().unwrap_or("Unknown"), pkg_file.site().unwrap_or("Unknown"), pkg_file.arch().unwrap_or("Unknown"), pkg_file.component().unwrap_or("Unknown"), pkg_file.index_type().unwrap_or("Unknown"), pkg_file.index(), ); } } #[test] fn raw_depcache() { crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); let pkg = cache.find_pkg("apt").unwrap(); let depcache = cache.create_depcache(); dbg!(depcache.is_upgradable(&pkg)); depcache.mark_delete(&pkg, false); dbg!(depcache.marked_delete(&pkg)); dbg!(depcache.delete_count()); let mut progress = crate::raw::progress::NoOpProgress::new_box(); depcache.full_upgrade(&mut progress).unwrap(); for pkg in cache.begin().unwrap() { if depcache.marked_upgrade(&pkg) { println!("Upgrade => {}", pkg.fullname(false)); } } } #[test] fn source_uris() { crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); dbg!(cache.source_uris()); } #[test] fn priority() { crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); let pkg = cache.find_pkg("apt").unwrap(); let depcache = cache.create_depcache(); let cand = depcache.unsafe_candidate_version(&pkg); dbg!(cache.priority(&cand)); } #[test] fn records() { crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); let pkg = cache.find_pkg("apt").unwrap(); let depcache = cache.create_depcache(); let cand = depcache.unsafe_candidate_version(&pkg); let records = cache.create_records(); records.ver_file_lookup(&cand.version_files().unwrap().next().unwrap()); dbg!(records.short_desc().unwrap()); records.desc_file_lookup(&cand.description_files().unwrap().next().unwrap()); dbg!(records.long_desc().unwrap()); let mut pkg_file = cand.version_files().unwrap().next().unwrap().pkg_file(); cache.find_index(&mut pkg_file); dbg!(cache.is_trusted(&mut pkg_file)); dbg!(records.ver_uri(&pkg_file).unwrap()); } #[test] fn update() { // This test Requires root crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); let mut progress = crate::raw::progress::AptAcquireProgress::new_box(); cache.update(&mut progress).unwrap(); } #[test] fn pacman() { crate::config::init_config_system(); let debs: Vec = vec![]; let cache = create_cache(&debs).unwrap(); let _pacman = crate::raw::pkgmanager::raw::create_pkgmanager(&cache); let _resolve = crate::raw::pkgmanager::raw::create_problem_resolver(&cache); } } rust-apt-0.7.0/src/raw/pkgmanager.rs000064400000000000000000000031121046102023000154460ustar 00000000000000//! Contains types and bindings for fetching and installing packages from the //! cache. /// This module contains the bindings and structs shared with c++ #[cxx::bridge] pub mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/progress.h"); include!("rust-apt/apt-pkg-c/cache.h"); include!("rust-apt/apt-pkg-c/records.h"); include!("rust-apt/apt-pkg-c/util.h"); include!("rust-apt/apt-pkg-c/pkgmanager.h"); type PackageManager; type ProblemResolver; type Cache = crate::raw::cache::raw::Cache; type Package = crate::raw::cache::raw::Package; type Records = crate::raw::records::raw::Records; type DynAcquireProgress = crate::raw::progress::raw::DynAcquireProgress; type DynInstallProgress = crate::raw::progress::raw::DynInstallProgress; type DynOperationProgress = crate::raw::progress::raw::DynOperationProgress; pub fn create_pkgmanager(cache: &Cache) -> UniquePtr; pub fn get_archives( self: &PackageManager, cache: &Cache, records: &Records, progress: &mut DynAcquireProgress, ) -> Result<()>; pub fn do_install(self: &PackageManager, progress: &mut DynInstallProgress) -> Result<()>; pub fn create_problem_resolver(cache: &Cache) -> UniquePtr; pub fn protect(self: &ProblemResolver, pkg: &Package); // 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 pub fn resolve( self: &ProblemResolver, fix_broken: bool, op_progress: &mut DynOperationProgress, ) -> Result<()>; } } rust-apt-0.7.0/src/raw/progress.rs000064400000000000000000000441731046102023000152120ustar 00000000000000//! Contains Progress struct for updating the package list. use std::fmt::Write as _; use std::io::{stdout, Write}; use cxx::ExternType; use crate::config::Config; // use crate::config::Config; use crate::util::{ get_apt_progress_string, terminal_height, terminal_width, time_str, unit_str, NumSys, }; pub type Worker = raw::Worker; /// Trait you can impl on any struct to customize the output shown during file /// downloads. pub trait AcquireProgress { /// 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, id: u32, description: String); /// Called when an Item has started to download fn fetch(&mut self, id: u32, description: String, file_size: u64); /// Called when an Item fails to download fn fail(&mut self, id: u32, description: String, status: u32, error_text: String); /// Called periodically to provide the overall progress information fn pulse( &mut self, workers: Vec, percent: f32, total_bytes: u64, current_bytes: u64, current_cps: u64, ); /// Called when an item is successfully and completely fetched. fn done(&mut self); /// Called when progress has started fn start(&mut self); /// Called when progress has finished fn stop( &mut self, fetched_bytes: u64, elapsed_time: u64, current_cps: u64, pending_errors: bool, ); } /// Trait you can impl on any struct to customize the output of operation /// progress on things like opening the cache. pub trait OperationProgress { fn update(&mut self, operation: String, percent: f32); fn done(&mut self); } /// Internal struct to pass into [`self::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. pub(crate) struct NoOpProgress {} impl NoOpProgress { /// Return the AptAcquireProgress in a box /// To easily pass through for progress pub fn new_box() -> Box { Box::new(NoOpProgress {}) } } impl OperationProgress for NoOpProgress { fn update(&mut self, _: String, _: f32) {} fn done(&mut self) {} } /// Trait you can impl on any struct to customize the output of installation /// progress. pub trait InstallProgress { 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); } // TODO: Make better structs for pkgAcquire items, workers, owners. /// 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, } impl AptAcquireProgress { /// Returns a new default progress instance. pub fn new() -> Self { Self::default() } /// Return the AptAcquireProgress in a box /// To easily pass through for progress pub fn new_box() -> Box { Box::new(Self::new()) } /// 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 AcquireProgress 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, id: u32, description: String) { if self.disable { return; } self.clear_last_line(terminal_width() - 1); println!("\rHit:{id} {description}"); } /// Called when an Item has started to download /// /// Prints out the short description and the expected size. fn fetch(&mut self, id: u32, description: String, file_size: u64) { if self.disable { return; } self.clear_last_line(terminal_width() - 1); if file_size != 0 { println!( "\rGet:{id} {description} [{}]", unit_str(file_size, NumSys::Decimal) ); } else { println!("\rGet:{id} {description}"); } } /// Called when an item is successfully and completely fetched. /// /// We don't print anything here to remain consistent with apt. /// /// TODO: Pass through information here. /// Likely when we make a general struct fork the items. fn done(&mut self) { // 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, fetched_bytes: u64, elapsed_time: u64, current_cps: u64, pending_errors: bool, ) { if self.disable { return; } self.clear_last_line(terminal_width() - 1); if pending_errors { return; } if fetched_bytes != 0 { println!( "Fetched {} in {} ({}/s)", unit_str(fetched_bytes, NumSys::Decimal), time_str(elapsed_time), unit_str(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, id: u32, description: String, status: u32, error_text: String) { if self.disable { return; } self.clear_last_line(terminal_width() - 1); let mut show_error = true; if status == 0 || status == 2 { println!("\rIgn: {id} {description}"); // TODO: Add in support for apt configurations later // error_text is empty || // _config->FindB("Acquire::Progress::Ignore::ShowErrorText", false) == false) // Do not show the error if it was simply ignored // show_error can be removed once configuration is added in show_error = false; if error_text.is_empty() { show_error = false; } } else { println!("\rErr: {id} {description}"); } 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, workers: Vec, percent: f32, total_bytes: u64, current_bytes: u64, current_cps: u64, ) { 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{percent:.0}%"); let mut eta_str = String::new(); // Set the ETA string if there is a rate of download if current_cps != 0 { let _ = write!( eta_str, " {} {}", // Current rate of download unit_str(current_cps, NumSys::Decimal), // ETA String time_str((total_bytes - current_bytes) / current_cps) ); } for worker in workers { let mut work_string = String::new(); work_string.push_str(" ["); if !worker.is_current { if !worker.status.is_empty() { work_string.push_str(&worker.status); work_string.push(']'); } continue; } if worker.id != 0 { let _ = write!(work_string, " {} ", worker.id); } work_string.push_str(&worker.short_desc); if !worker.active_subprocess.is_empty() { work_string.push(' '); work_string.push_str(&worker.active_subprocess); } work_string.push(' '); work_string.push_str(&unit_str(worker.current_size, NumSys::Decimal)); if worker.total_size > 0 && !worker.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 { #[allow(dead_code)] pub fn new() -> Self { Self { config: Config::new(), } } /// Return the AptInstallProgress in a box /// To easily pass through to do_install pub fn new_box() -> Box { Box::new(Self::new()) } } impl Default for AptInstallProgress { fn default() -> Self { Self::new() } } impl InstallProgress 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) {} } /// This module contains the bindings and structs shared with c++ #[cxx::bridge] pub mod raw { /// A simple representation of an Acquire worker. /// /// TODO: Make this better. struct Worker { is_current: bool, status: String, id: u64, short_desc: String, active_subprocess: String, current_size: u64, total_size: u64, complete: bool, } extern "Rust" { /// Called on c++ to set the pulse interval. fn pulse_interval(progress: &mut DynAcquireProgress) -> usize; /// Called when an item is confirmed to be up-to-date. fn hit(progress: &mut DynAcquireProgress, id: u32, description: String); /// Called when an Item has started to download fn fetch(progress: &mut DynAcquireProgress, id: u32, description: String, file_size: u64); /// Called when an Item fails to download fn fail( progress: &mut DynAcquireProgress, id: u32, description: String, status: u32, error_text: String, ); /// Called periodically to provide the overall progress information fn pulse( progress: &mut DynAcquireProgress, workers: Vec, percent: f32, total_bytes: u64, current_bytes: u64, current_cps: u64, ); /// Called when an item is successfully and completely fetched. fn done(progress: &mut DynAcquireProgress); /// Called when progress has started fn start(progress: &mut DynAcquireProgress); /// Called when progress has finished fn stop( progress: &mut DynAcquireProgress, fetched_bytes: u64, elapsed_time: u64, current_cps: u64, pending_errors: bool, ); /// Called when an operation has been updated. fn op_update(progress: &mut DynOperationProgress, operation: String, percent: f32); /// Called when an operation has finished. fn op_done(progress: &mut DynOperationProgress); /// fn inst_status_changed( progress: &mut DynInstallProgress, 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 inst_error( progress: &mut DynInstallProgress, pkgname: String, steps_done: u64, total_steps: u64, error: String, ); } unsafe extern "C++" { type DynAcquireProgress = Box; type DynOperationProgress = Box; type DynInstallProgress = Box; include!("rust-apt/apt-pkg-c/progress.h"); } } /// Impl for sending AcquireProgress across the barrier. unsafe impl ExternType for Box { type Id = cxx::type_id!("DynAcquireProgress"); type Kind = cxx::kind::Trivial; } /// Impl for sending OperationProgress across the barrier. /// TODO: Needs to be reviewed in GitLab MR, because I've got just about zero /// clue what I'm doing. unsafe impl ExternType for Box { type Id = cxx::type_id!("DynOperationProgress"); type Kind = cxx::kind::Trivial; } /// Impl for sending InstallProgress across the barrier. /// TODO: Needs to be reviewed in GitLab MR, because I've got just about zero /// clue what I'm doing. unsafe impl ExternType for Box { type Id = cxx::type_id!("DynInstallProgress"); type Kind = cxx::kind::Trivial; } // Begin AcquireProgress trait functions // These must be defined outside the cxx bridge but in the same file /// Called on c++ to set the pulse interval. fn pulse_interval(progress: &mut Box) -> usize { (**progress).pulse_interval() } /// Called when an item is confirmed to be up-to-date. fn hit(progress: &mut Box, id: u32, description: String) { (**progress).hit(id, description) } /// Called when an Item has started to download fn fetch(progress: &mut Box, id: u32, description: String, file_size: u64) { (**progress).fetch(id, description, file_size) } /// Called when an Item fails to download fn fail( progress: &mut Box, id: u32, description: String, status: u32, error_text: String, ) { (**progress).fail(id, description, status, error_text) } /// Called periodically to provide the overall progress information fn pulse( progress: &mut Box, workers: Vec, percent: f32, total_bytes: u64, current_bytes: u64, current_cps: u64, ) { (**progress).pulse(workers, percent, total_bytes, current_bytes, current_cps) } /// Called when an item is successfully and completely fetched. fn done(progress: &mut Box) { (**progress).done() } /// Called when progress has started fn start(progress: &mut Box) { (**progress).start() } /// Called when progress has finished fn stop( progress: &mut Box, fetched_bytes: u64, elapsed_time: u64, current_cps: u64, pending_errors: bool, ) { (**progress).stop(fetched_bytes, elapsed_time, current_cps, pending_errors) } // End AcquireProgress trait functions // Begin OperationProgress trait functions // These must be defined outside the cxx bridge but in the same file /// Called when an operation has been updated. fn op_update(progress: &mut Box, operation: String, percent: f32) { (**progress).update(operation, percent) } /// Called when an operation has finished. fn op_done(progress: &mut Box) { (**progress).done() } // End OperationProgress trait functions // Begin InstallProgress trait functions fn inst_status_changed( progress: &mut Box, pkgname: String, steps_done: u64, total_steps: u64, action: String, ) { (**progress).status_changed(pkgname, steps_done, total_steps, action) } fn inst_error( progress: &mut Box, pkgname: String, steps_done: u64, total_steps: u64, error: String, ) { (**progress).error(pkgname, steps_done, total_steps, error) } // End InstallProgress trait functions rust-apt-0.7.0/src/raw/records.rs000064400000000000000000000015641046102023000150040ustar 00000000000000/// This module contains the bindings and structs shared with c++ #[cxx::bridge] pub mod raw { unsafe extern "C++" { include!("rust-apt/apt-pkg-c/package.h"); include!("rust-apt/apt-pkg-c/records.h"); type Records; type VersionFile = crate::raw::package::raw::VersionFile; type DescriptionFile = crate::raw::package::raw::DescriptionFile; type PackageFile = crate::raw::package::raw::PackageFile; pub fn ver_file_lookup(self: &Records, ver_file: &VersionFile); pub fn desc_file_lookup(self: &Records, desc_file: &DescriptionFile); pub fn long_desc(self: &Records) -> Result; pub fn short_desc(self: &Records) -> Result; pub fn get_field(self: &Records, field: String) -> Result; pub fn hash_find(self: &Records, hash_type: String) -> Result; pub fn ver_uri(self: &Records, pkg_file: &PackageFile) -> Result; } } rust-apt-0.7.0/src/raw/util.rs000064400000000000000000000026121046102023000143130ustar 00000000000000/// This module contains the bindings and structs shared with c++ #[cxx::bridge] pub 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: String, ver2: String) -> i32; /// Return an APT-styled progress bar (`[####..]`). pub fn get_apt_progress_string(percent: f32, output_width: u32) -> String; /// Lock the lockfile. // TODO: There's `unlock_inner` functions in the Python APT library, but I have no clue how // we'd implement them in regard to our structs and such in this library. They seem to only // be used to have a lock on dpkg between calls, which shouldn't be an issue in most cases, // though it should probably be looked into. 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.7.0/src/records.rs000064400000000000000000000104461046102023000142120ustar 00000000000000/// This module contains the bindings and structs shared with c++ /// A module containing [`&str`] constants for known record fields /// /// Pass through to the [`crate::package::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"; } rust-apt-0.7.0/src/tagfile.rs000064400000000000000000000137751046102023000141740ustar 00000000000000//! Contains structs and functions to parse Debian-styled RFC 822 files. use core::iter::Iterator; use std::collections::HashMap; #[derive(Debug)] /// The result of a parsing error. pub struct ParserError { pub msg: String, pub line: Option, } /// 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. 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.to_string()) } } /// 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.7.0/src/util.rs000064400000000000000000000165271046102023000135340ustar 00000000000000//! Contains miscellaneous helper utilities. use std::cmp::Ordering; pub use cxx::Exception; use terminal_size::{terminal_size, Height, Width}; use crate::cache::Cache; use crate::config; use crate::package::Package; use crate::raw::package::DepFlags; use crate::raw::util::raw; /// 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.to_owned(), ver2.to_owned()); 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<(), Exception> { config::init_config_system(); 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<(), Exception> { config::init_config_system(); 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, calls /// to [`apt_lock`] will return an [`Exception`] 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 => cache.depcache().install_version(pkg), }) 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.base_deps.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()) { broken_string += &format!(" ({comp} {ver_str})"); } let target = base_dep.target_package(); if !target.has_provides() { if let Some(target_ver) = cache.depcache().install_version(target) { 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.base_deps.len() { broken_string += " or" } broken_string += "\n"; } } Some(broken_string) } rust-apt-0.7.0/tests/cache.rs000064400000000000000000000432571046102023000141750ustar 00000000000000mod cache { use std::collections::HashMap; use std::fmt::Write as _; use rust_apt::cache::*; use rust_apt::new_cache; use rust_apt::package::DepType; use rust_apt::util::*; #[test] fn test_raw_pkg() { let cache = new_cache!().unwrap(); for pkg in cache.raw_pkgs().unwrap() { println!("ID: {}", pkg.id()); println!("Name: {}", pkg.name()); println!("Arch: {}", pkg.arch()); println!("FullName: {}", pkg.fullname(false)); println!("current_state: {}", pkg.current_state()); println!("inst_state: {}", pkg.inst_state()); println!("selected_state: {}\n", pkg.selected_state()); match pkg.version_list() { Some(ver) => { println!("Version of '{}'", pkg.name()); println!(" Version: {}", ver.version()); println!(" Arch: {}", ver.arch()); println!(" Section: {}", ver.section().unwrap_or_default()); println!(" Source Pkg: {}", ver.source_name()); println!(" Source Version: {}\n", ver.source_version()); println!("End: {}\n\n", pkg.end()); }, None => { println!("'{}' is a Virtual Package\n", pkg.name()); }, } } } #[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 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).unwrap() { 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.id(), parent.id()) } #[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).unwrap().next().is_some()); for pkg in cache.packages(&sort).unwrap() { // 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).unwrap() { 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.base_deps { // Apt Dependencies should have targets assert!(dep.all_targets().next().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::Breaks).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: "); for dep in cand.depends_map().get(&DepType::Depends).unwrap() { if dep.is_or() { let mut or_str = String::new(); let total = dep.base_deps.len() - 1; for (num, base_dep) in dep.base_deps.iter().enumerate() { or_str.push_str(base_dep.name()); if let Some(comp) = base_dep.comp() { 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() { 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).unwrap() { 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).unwrap() { // Iterate over the reverse depends // Iterating rdepends could segfault. // See: https://gitlab.com/volian/rust-apt/-/merge_requests/36 for deps in pkg.rdepends_map().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 = cand.provides_list().unwrap().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_list().unwrap(); let provides_pkg = rev_provides_list.next().unwrap(); assert!(rev_provides_list.next().is_none()); let parent = 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_list() .unwrap() .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 = 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_list() .unwrap() .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_list().unwrap().next().is_some()); } } #[test] fn sources() { let cache = new_cache!().unwrap(); // If the source lists don't exists there is problems. assert!(!cache.source_uris().is_empty()); } #[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 mut pkg_file in pkg_files { // Apt should have all of these blocks in the package file. assert!(pkg_file.filename().is_ok()); assert!(pkg_file.archive().is_ok()); // If the archive is `/var/lib/dpkg/status` These will be None. if pkg_file.archive().unwrap() != "now" { assert!(pkg_file.origin().is_ok()); assert!(pkg_file.codename().is_ok()); assert!(pkg_file.label().is_ok()); assert!(pkg_file.site().is_ok()); assert!(pkg_file.arch().is_ok()); } // These should be okay regardless. assert!(pkg_file.component().is_ok()); assert!(pkg_file.index_type().is_ok()); // Index should not be 0. assert_ne!(pkg_file.index(), 0); // Apt should likely be from a trusted repository. assert!(cache.is_trusted(&mut pkg_file)); // 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 = cache.depcache().install_version(&pkg).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 = cache.depcache().install_version(&pkg).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 { if let Some(broken) = show_broken_pkg(&cache, &pkg, false) { assert_eq!(broken, expected); println!("{broken}"); } } println!("{}", err.what()); } } rust-apt-0.7.0/tests/config.rs000064400000000000000000000054201046102023000143650ustar 00000000000000mod config { 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())); } } rust-apt-0.7.0/tests/depcache.rs000064400000000000000000000040341046102023000146540ustar 00000000000000mod depcache { use rust_apt::cache::Upgrade; use rust_apt::new_cache; // use rust_apt::package::Mark; #[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 action_group = cache.depcache().action_group(); // The C++ deconstructor will be run when the action group leaves scope. action_group.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).unwrap() { 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.7.0/tests/files/cache/apt/DEBIAN/control000064400000000000000000000002431046102023000200260ustar 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.7.0/tests/files/cache/broken-or-dep_0.0.1/DEBIAN/control000064400000000000000000000004611046102023000224240ustar 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.7.0/tests/files/cache/dep-pkg1_0.0.1/DEBIAN/control000064400000000000000000000003511046102023000213660ustar 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.7.0/tests/files/cache/dep-pkg1_0.0.2/DEBIAN/control000064400000000000000000000003511046102023000213670ustar 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.7.0/tests/files/cache/dep-pkg2_0.0.1/DEBIAN/control000064400000000000000000000003461046102023000213730ustar 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.7.0/tests/files/cache/no-description_0.0.1/DEBIAN/control000064400000000000000000000002341046102023000227130ustar 00000000000000Package: no-description Version: 0.0.1 Section: base Priority: optional Architecture: all Depends: htop, python3-rich Maintainer: Your Name rust-apt-0.7.0/tests/files/tagfile/correct.control000064400000000000000000000006011046102023000203260ustar 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.7.0/tests/records.rs000064400000000000000000000020401046102023000145540ustar 00000000000000mod records { use rust_apt::new_cache; use rust_apt::records::RecordField; #[test] fn fields() { let cache = new_cache!().unwrap(); let pkg = cache.get("apt").unwrap(); // let pkg = cache.get("nala").unwrap(); let cand = pkg.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()); // The apt source field be none as it is just "apt" assert!(cand.get_record(RecordField::Source).is_some()); // 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.7.0/tests/root.rs000064400000000000000000000074141046102023000141100ustar 00000000000000mod root { use rust_apt::new_cache; use rust_apt::raw::progress::{raw, AcquireProgress, AptAcquireProgress, AptInstallProgress}; 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 AcquireProgress for Progress { fn pulse_interval(&self) -> usize { 0 } fn hit(&mut self, id: u32, description: String) { println!("\rHit:{id} {description}"); } fn fetch(&mut self, id: u32, description: String, file_size: u64) { if file_size != 0 { println!( "\rGet:{id} {description} [{}]", unit_str(file_size, NumSys::Decimal) ); } else { println!("\rGet:{id} {description}"); } } fn done(&mut self) {} fn start(&mut self) {} fn stop( &mut self, fetched_bytes: u64, elapsed_time: u64, current_cps: u64, _pending_errors: bool, ) { if fetched_bytes != 0 { println!( "Fetched {} in {} ({}/s)", unit_str(fetched_bytes, NumSys::Decimal), time_str(elapsed_time), unit_str(current_cps, NumSys::Decimal) ); } else { println!("Nothing to fetch."); } } fn fail(&mut self, id: u32, description: String, status: u32, error_text: String) { let mut show_error = true; if status == 0 || status == 2 { println!("\rIgn: {id} {description}"); if error_text.is_empty() { show_error = false; } } else { println!("\rErr: {id} {description}"); } if show_error { println!("\r{error_text}"); } } fn pulse( &mut self, _workers: Vec, _percent: f32, _total_bytes: u64, _current_bytes: u64, _current_cps: u64, ) { } } let cache = new_cache!().unwrap(); // Test a new impl for AcquireProgress let mut progress: Box = Box::new(Progress {}); cache.update(&mut progress).unwrap(); let cache = new_cache!().unwrap(); // Test the default implementation for it let mut progress = AptAcquireProgress::new_box(); 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 = AptAcquireProgress::new_box(); let mut inst_progress = AptInstallProgress::new_box(); 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 = AptAcquireProgress::new_box(); let mut inst_progress = AptInstallProgress::new_box(); 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.7.0/tests/sort.rs000064400000000000000000000061521046102023000141120ustar 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).unwrap() { 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).unwrap() { 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).unwrap() { 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).unwrap() { assert!(pkg.is_upgradable()) } let sort = PackageSort::default().not_upgradable(); for pkg in cache.packages(&sort).unwrap() { assert!(!pkg.is_upgradable()) } } #[test] fn installed() { let cache = new_cache!().unwrap(); let sort = PackageSort::default().installed(); for pkg in cache.packages(&sort).unwrap() { assert!(pkg.is_installed()) } let sort = PackageSort::default().not_installed(); for pkg in cache.packages(&sort).unwrap() { 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).unwrap() { println!("{}", pkg.name()); assert!(pkg.is_auto_installed()) } let sort = PackageSort::default().manually_installed(); for pkg in cache.packages(&sort).unwrap() { 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).unwrap() { assert!(pkg.is_auto_removable()) } let sort = PackageSort::default().not_auto_removable(); for pkg in cache.packages(&sort).unwrap() { assert!(!pkg.is_auto_removable()) } } } rust-apt-0.7.0/tests/tagfile.rs000064400000000000000000000035161046102023000145370ustar 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.7.0/tests/util.rs000064400000000000000000000005051046102023000140740ustar 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)); } }