devicemapper-0.34.4/.cargo_vcs_info.json0000644000000001360000000000100135700ustar { "git": { "sha1": "099aea8db6787f304dfe2fbf1b968b86f149667a" }, "path_in_vcs": "" }devicemapper-0.34.4/CHANGES.txt000064400000000000000000000707421046102023000142030ustar 00000000000000devicemapper 0.34.4 =================== Recommended Rust toolchain version: 1.80.1 Recommended development release: Fedora 40 - Expose the message ioctl: https://github.com/stratis-storage/devicemapper-rs/pull/933 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/934 https://github.com/stratis-storage/devicemapper-rs/pull/932 https://github.com/stratis-storage/devicemapper-rs/pull/931 https://github.com/stratis-storage/devicemapper-rs/pull/930 https://github.com/stratis-storage/devicemapper-rs/pull/929 https://github.com/stratis-storage/devicemapper-rs/pull/928 devicemapper 0.34.3 =================== Recommended Rust toolchain version: 1.79.0 Recommended development release: Fedora 40 - Use once_cell instead of lazy_static for lazy statics: https://github.com/stratis-storage/devicemapper-rs/pull/917 - Increase nix dependency lower bound to 0.29.0: https://github.com/stratis-storage/devicemapper-rs/pull/922 - Increase tempfile dependency lower bound to 3.4.0: https://github.com/stratis-storage/devicemapper-rs/pull/926 https://github.com/stratis-storage/devicemapper-rs/pull/925 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/927 https://github.com/stratis-storage/devicemapper-rs/pull/923 https://github.com/stratis-storage/devicemapper-rs/pull/921 https://github.com/stratis-storage/devicemapper-rs/pull/920 https://github.com/stratis-storage/devicemapper-rs/pull/919 https://github.com/stratis-storage/devicemapper-rs/pull/918 https://github.com/stratis-storage/devicemapper-rs/pull/916 https://github.com/stratis-storage/devicemapper-rs/pull/915 https://github.com/stratis-storage/devicemapper-rs/pull/914 devicemapper 0.34.2 =================== Recommended Rust toolchain version: 1.76.0 Recommended development release: Fedora 39 - Increase retry dependency lower bound to 2.0.0: https://github.com/stratis-storage/devicemapper-rs/pull/902 - Increase env_logger dependency lower bound to 0.11.0: https://github.com/stratis-storage/devicemapper-rs/pull/907 - Increase nix dependency lower bound to 0.28.0: https://github.com/stratis-storage/devicemapper-rs/pull/908 - devicemapper-rs-sys: Release version 0.3.0 https://github.com/stratis-storage/devicemapper-rs/pull/906 - devicemapper-rs-sys: Increase bindgen dependency lower bound: https://github.com/stratis-storage/devicemapper-rs/pull/904 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/911 https://github.com/stratis-storage/devicemapper-rs/pull/910 https://github.com/stratis-storage/devicemapper-rs/pull/909 https://github.com/stratis-storage/devicemapper-rs/pull/905 https://github.com/stratis-storage/devicemapper-rs/pull/903 https://github.com/stratis-storage/devicemapper-rs/pull/900 devicemapper 0.34.1 =================== Recommended Rust toolchain version: 1.75.0 Recommended development release: Fedora 39 - Downgrade some log entries to trace level: https://github.com/stratis-storage/devicemapper-rs/pull/890 - Use loopdev-3 instead of loopdev package: https://github.com/stratis-storage/devicemapper-rs/pull/898 - Increase nix dependency lower bound to 0.27.1: https://github.com/stratis-storage/devicemapper-rs/pull/886 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/897 https://github.com/stratis-storage/devicemapper-rs/pull/896 https://github.com/stratis-storage/devicemapper-rs/pull/895 https://github.com/stratis-storage/devicemapper-rs/pull/894 https://github.com/stratis-storage/devicemapper-rs/pull/893 https://github.com/stratis-storage/devicemapper-rs/pull/892 https://github.com/stratis-storage/devicemapper-rs/pull/889 devicemapper 0.34.0 =================== Recommended Rust toolchain version: 1.73.0 Recommended development release: Fedora 38 - devicemapper-sys: Release version 0.2.0 https://github.com/stratis-storage/devicemapper-rs/pull/883 - devicemapper-sys: Increase bindgen dependency lower bound to 0.68.1: https://github.com/stratis-storage/devicemapper-rs/pull/880 - Increase bitflags dependency lower bound to 2.3.3: https://github.com/stratis-storage/devicemapper-rs/pull/888 https://github.com/stratis-storage/devicemapper-rs/pull/855 - Do not use default features for retry crate: https://github.com/stratis-storage/devicemapper-rs/pull/869 - devicemapper-sys: Remove unused nix dependency specification: https://github.com/stratis-storage/devicemapper-rs/pull/872 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/885 https://github.com/stratis-storage/devicemapper-rs/pull/884 https://github.com/stratis-storage/devicemapper-rs/pull/882 https://github.com/stratis-storage/devicemapper-rs/pull/881 https://github.com/stratis-storage/devicemapper-rs/pull/879 https://github.com/stratis-storage/devicemapper-rs/pull/878 https://github.com/stratis-storage/devicemapper-rs/pull/877 https://github.com/stratis-storage/devicemapper-rs/pull/876 https://github.com/stratis-storage/devicemapper-rs/pull/875 https://github.com/stratis-storage/devicemapper-rs/pull/874 https://github.com/stratis-storage/devicemapper-rs/pull/873 https://github.com/stratis-storage/devicemapper-rs/pull/871 https://github.com/stratis-storage/devicemapper-rs/pull/870 https://github.com/stratis-storage/devicemapper-rs/pull/868 https://github.com/stratis-storage/devicemapper-rs/pull/867 https://github.com/stratis-storage/devicemapper-rs/pull/866 https://github.com/stratis-storage/devicemapper-rs/pull/864 devicemapper 0.33.5 =================== Recommended Rust toolchain version: 1.70.0 Lowest supported Rust toolchain version: 1.69.0 Recommended development release: Fedora 38 - Test for presence of udev daemon in UdevSync implementation: https://github.com/stratis-storage/devicemapper-rs/pull/859 - Patch Cargo.toml to avoid loopdev FTBFS: https://github.com/stratis-storage/devicemapper-rs/pull/852 - Increase bitflags dependency lower bound to 1.3.2: https://github.com/stratis-storage/devicemapper-rs/pull/855 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/862 https://github.com/stratis-storage/devicemapper-rs/pull/861 https://github.com/stratis-storage/devicemapper-rs/pull/860 https://github.com/stratis-storage/devicemapper-rs/pull/857 https://github.com/stratis-storage/devicemapper-rs/pull/854 https://github.com/stratis-storage/devicemapper-rs/pull/853 devicemapper 0.33.4 =================== Recommended Rust toolchain version: 1.68.0 Lowest supported Rust toolchain version: 1.66.1 Recommended development release: Fedora 37 - Make CacheDev, LinearDev, and ThinPoolDev private on resume(): https://github.com/stratis-storage/devicemapper-rs/pull/850 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/849 devicemapper 0.33.3 =================== Recommended Rust toolchain version: 1.68.0 Lowest supported Rust toolchain version: 1.66.1 Recommended development release: Fedora 37 - Send udev options to make cache, linear, and thinpool devs private: https://github.com/stratis-storage/devicemapper-rs/pull/845 - Increase env_logger dependency lower bound to 0.10.0: https://github.com/stratis-storage/devicemapper-rs/pull/844 devicemapper 0.33.2 =================== Recommended Rust toolchain version: 1.68.0 Lowest supported Rust toolchain version: 1.66.1 Recommended development release: Fedora 37 - Expose target table related structs: https://github.com/stratis-storage/devicemapper-rs/pull/839 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/842 https://github.com/stratis-storage/devicemapper-rs/pull/841 https://github.com/stratis-storage/devicemapper-rs/pull/838 devicemapper 0.33.1 =================== Recommended Rust toolchain version: 1.67.1 Lowest supported Rust toolchain version: 1.66.1 Recommended development release: Fedora 37 - Require devicemapper-sys v0.1.5: https://github.com/stratis-storage/devicemapper-rs/pull/837 - Release devicemapper-sys v0.1.5: https://github.com/stratis-storage/devicemapper-rs/pull/835 devicemapper 0.33.0 =================== Recommended Rust toolchain version: 1.67.1 Lowest supported Rust toolchain version: 1.66.1 Recommended development release: Fedora 37 - Add udev synchronization and logging support: https://github.com/stratis-storage/devicemapper-rs/issues/768 https://github.com/stratis-storage/devicemapper-rs/issues/730 https://github.com/stratis-storage/devicemapper-rs/pull/774 - Increase nix dependency lower bound to 0.26.0: https://github.com/stratis-storage/devicemapper-rs/pull/820 - Release devicemapper-sys v0.1.4: https://github.com/stratis-storage/devicemapper-rs/pull/826 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/828 https://github.com/stratis-storage/devicemapper-rs/pull/827 https://github.com/stratis-storage/devicemapper-rs/pull/825 https://github.com/stratis-storage/devicemapper-rs/pull/824 https://github.com/stratis-storage/devicemapper-rs/pull/823 https://github.com/stratis-storage/devicemapper-rs/pull/822 https://github.com/stratis-storage/devicemapper-rs/pull/821 https://github.com/stratis-storage/devicemapper-rs/pull/819 https://github.com/stratis-storage/devicemapper-rs/pull/818 https://github.com/stratis-storage/devicemapper-rs/pull/817 https://github.com/stratis-storage/devicemapper-rs/pull/816 https://github.com/stratis-storage/devicemapper-rs/pull/814 https://github.com/stratis-storage/devicemapper-rs/pull/803 devicemapper 0.32.3 =================== Recommended Rust toolchain version: 1.65.0 Lowest supported Rust toolchain version: 1.62.1 Recommended development release: Fedora 37 - Expose DeviceInfo struct: https://github.com/stratis-storage/devicemapper-rs/pull/787 - Fix a code defect that could result in an ill-formed ioctl message header: https://github.com/stratis-storage/devicemapper-rs/pull/806 - Increase bindgen dependency version specification to 0.63: https://github.com/stratis-storage/devicemapper-rs/pull/804 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/812 https://github.com/stratis-storage/devicemapper-rs/pull/810 https://github.com/stratis-storage/devicemapper-rs/pull/807 https://github.com/stratis-storage/devicemapper-rs/pull/802 https://github.com/stratis-storage/devicemapper-rs/pull/800 https://github.com/stratis-storage/devicemapper-rs/pull/799 https://github.com/stratis-storage/devicemapper-rs/pull/798 https://github.com/stratis-storage/devicemapper-rs/pull/797 https://github.com/stratis-storage/devicemapper-rs/pull/796 https://github.com/stratis-storage/devicemapper-rs/pull/795 https://github.com/stratis-storage/devicemapper-rs/pull/794 https://github.com/stratis-storage/devicemapper-rs/pull/793 https://github.com/stratis-storage/devicemapper-rs/pull/791 https://github.com/stratis-storage/devicemapper-rs/pull/788 https://github.com/stratis-storage/devicemapper-rs/pull/786 https://github.com/stratis-storage/devicemapper-rs/pull/785 https://github.com/stratis-storage/devicemapper-rs/pull/778 devicemapper 0.32.2 =================== Recommended Rust toolchain version: 1.63.0 Lowest supported Rust toolchain version: 1.62.1 Recommended development release: Fedora 36 - Update to edition 2021: https://github.com/stratis-storage/devicemapper-rs/pull/766 - Do not use feature_args for thinpool equivalence: https://github.com/stratis-storage/devicemapper-rs/pull/783 - Increase loopdev dependency lower bound to 0.4.0: https://github.com/stratis-storage/devicemapper-rs/pull/781 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/780 https://github.com/stratis-storage/devicemapper-rs/pull/779 https://github.com/stratis-storage/devicemapper-rs/pull/777 https://github.com/stratis-storage/devicemapper-rs/pull/776 https://github.com/stratis-storage/devicemapper-rs/pull/775 devicemapper 0.32.1 =================== Recommended Rust toolchain version: 1.62.0 Lowest supported Rust toolchain version: 1.58.1 Recommended development release: Fedora 36 - Expose TargetTable and TargetParams: https://github.com/stratis-storage/devicemapper-rs/issues/747 https://github.com/stratis-storage/devicemapper-rs/pull/750 - Add a proper list of categories to the Cargo.toml file: https://github.com/stratis-storage/devicemapper-rs/pull/751 - Release devicemapper-sys version 0.1.3: https://github.com/stratis-storage/devicemapper-rs/pull/771 - Set a per-command ioctl version in device-mapper header: https://github.com/stratis-storage/devicemapper-rs/issues/769 https://github.com/stratis-storage/devicemapper-rs/pull/772 https://github.com/stratis-storage/devicemapper-rs/pull/770 - Increase nix dependency version to 0.24.0: https://github.com/stratis-storage/devicemapper-rs/pull/758 - Increase uuid dependency version to 1.0.0: https://github.com/stratis-storage/devicemapper-rs/pull/760 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/767 https://github.com/stratis-storage/devicemapper-rs/pull/765 https://github.com/stratis-storage/devicemapper-rs/pull/764 https://github.com/stratis-storage/devicemapper-rs/pull/763 https://github.com/stratis-storage/devicemapper-rs/pull/761 https://github.com/stratis-storage/devicemapper-rs/pull/759 https://github.com/stratis-storage/devicemapper-rs/pull/757 https://github.com/stratis-storage/devicemapper-rs/pull/756 https://github.com/stratis-storage/devicemapper-rs/pull/755 https://github.com/stratis-storage/devicemapper-rs/pull/754 https://github.com/stratis-storage/devicemapper-rs/pull/753 https://github.com/stratis-storage/devicemapper-rs/pull/752 https://github.com/stratis-storage/devicemapper-rs/pull/748 devicemapper 0.32.0 =================== Recommended Rust toolchain version: 1.58.1 Lowest supported Rust toolchain version: 1.54.0 Recommended development release: Fedora 35 - New version: 0.32.0: https://github.com/stratis-storage/devicemapper-rs/pull/744 - Allow passing feature args when creating a thinpool device: https://github.com/stratis-storage/devicemapper-rs/pull/745 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/743 https://github.com/stratis-storage/devicemapper-rs/pull/742 devicemapper 0.31.0 =================== Recommended Rust toolchain version: 1.58.0 Lowest supported Rust toolchain version: 1.54.0 Recommended development release: Fedora 34 - Allow specifying features args when setting up a thinpool device: https://github.com/stratis-storage/devicemapper-rs/pull/731 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/739 https://github.com/stratis-storage/devicemapper-rs/pull/738 https://github.com/stratis-storage/devicemapper-rs/pull/737 https://github.com/stratis-storage/devicemapper-rs/pull/736 https://github.com/stratis-storage/devicemapper-rs/pull/734 devicemapper 0.30.1 =================== Recommended Rust toolchain version: 1.57.0 Lowest supported Rust toolchain version: 1.54.0 Recommended development release: Fedora 34 - New release: 0.30.1 https://github.com/stratis-storage/devicemapper-rs/pull/714 - Make internal errors module public: https://github.com/stratis-storage/devicemapper-rs/issues/724 https://github.com/stratis-storage/devicemapper-rs/pull/726 - Release devicemapper-sys version 0.1.2: https://github.com/stratis-storage/devicemapper-rs/pull/720 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/733 https://github.com/stratis-storage/devicemapper-rs/pull/732 https://github.com/stratis-storage/devicemapper-rs/pull/729 https://github.com/stratis-storage/devicemapper-rs/pull/728 https://github.com/stratis-storage/devicemapper-rs/pull/725 https://github.com/stratis-storage/devicemapper-rs/pull/718 https://github.com/stratis-storage/devicemapper-rs/pull/717 https://github.com/stratis-storage/devicemapper-rs/pull/715 https://github.com/stratis-storage/devicemapper-rs/pull/713 https://github.com/stratis-storage/devicemapper-rs/pull/712 devicemapper 0.30.0 =================== Recommended Rust toolchain version: 1.55.0 Lowest supported Rust toolchain version: 1.54.0 Recommended development release: Fedora 34 - Introduce devicemapper-version based conditional compilation: https://github.com/stratis-storage/devicemapper-rs/issues/686 https://github.com/stratis-storage/devicemapper-rs/pull/709 https://github.com/stratis-storage/devicemapper-rs/pull/705 https://github.com/stratis-storage/devicemapper-rs/pull/703 https://github.com/stratis-storage/devicemapper-rs/pull/702 https://github.com/stratis-storage/devicemapper-rs/pull/700 https://github.com/stratis-storage/devicemapper-rs/pull/699 https://github.com/stratis-storage/devicemapper-rs/pull/698 - Pass DmOptions parameter to DmDevice::suspend and status() methods: https://github.com/stratis-storage/devicemapper-rs/issues/663 https://github.com/stratis-storage/devicemapper-rs/pull/707 - Make DmOptions implement Copy: https://github.com/stratis-storage/devicemapper-rs/pull/710 - Pass DmOptions parameter to Dm::table_load: https://github.com/stratis-storage/devicemapper-rs/pull/627 - Allow DM_SECURE_DATA flag in Dm::table_load: https://github.com/stratis-storage/devicemapper-rs/pull/688 - Remove implementation of description() method from DmError: https://github.com/stratis-storage/devicemapper-rs/pull/657 - Make name field optional in DeviceInfo struct: https://github.com/stratis-storage/devicemapper-rs/pull/668 - Use definitions of constants defined in dm-ioctl.h: https://github.com/stratis-storage/devicemapper-rs/pull/693 https://github.com/stratis-storage/devicemapper-rs/pull/691 - Use bindgen with only runtime feature enabled: https://github.com/stratis-storage/devicemapper-rs/pull/665 - Remove dependency on error-chain: https://github.com/stratis-storage/devicemapper-rs/pull/671 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/706 https://github.com/stratis-storage/devicemapper-rs/pull/704 https://github.com/stratis-storage/devicemapper-rs/pull/701 https://github.com/stratis-storage/devicemapper-rs/pull/697 https://github.com/stratis-storage/devicemapper-rs/pull/696 https://github.com/stratis-storage/devicemapper-rs/pull/695 https://github.com/stratis-storage/devicemapper-rs/pull/689 https://github.com/stratis-storage/devicemapper-rs/pull/687 https://github.com/stratis-storage/devicemapper-rs/pull/685 https://github.com/stratis-storage/devicemapper-rs/pull/684 https://github.com/stratis-storage/devicemapper-rs/pull/683 https://github.com/stratis-storage/devicemapper-rs/pull/680 https://github.com/stratis-storage/devicemapper-rs/pull/679 https://github.com/stratis-storage/devicemapper-rs/pull/678 https://github.com/stratis-storage/devicemapper-rs/pull/677 https://github.com/stratis-storage/devicemapper-rs/pull/676 https://github.com/stratis-storage/devicemapper-rs/pull/674 https://github.com/stratis-storage/devicemapper-rs/pull/673 https://github.com/stratis-storage/devicemapper-rs/pull/672 https://github.com/stratis-storage/devicemapper-rs/pull/669 https://github.com/stratis-storage/devicemapper-rs/pull/666 https://github.com/stratis-storage/devicemapper-rs/pull/662 https://github.com/stratis-storage/devicemapper-rs/pull/661 devicemapper 0.29.2 =================== Recommended Rust toolchain version: 1.53.0 Lowest supported Rust toolchain version: 1.49 - General cleanup of core implementation: * use bindgen to dynamically generate ioctl bindings * handle potential unmarshalling errors https://github.com/stratis-storage/devicemapper-rs/issues/623 https://github.com/stratis-storage/devicemapper-rs/issues/433 https://github.com/stratis-storage/devicemapper-rs/issues/525 https://github.com/stratis-storage/devicemapper-rs/pull/633 - Enable support for Android targets: https://github.com/stratis-storage/devicemapper-rs/pull/626 - Make all types generated by range macro hashable: https://github.com/stratis-storage/devicemapper-rs/pull/622 - Add CI support for Android build target: https://github.com/stratis-storage/devicemapper-rs/issues/640 https://github.com/stratis-storage/devicemapper-rs/pull/652 - Add CI for musl: https://github.com/stratis-storage/devicemapper-rs/issues/648 https://github.com/stratis-storage/devicemapper-rs/pull/651 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/656 https://github.com/stratis-storage/devicemapper-rs/pull/655 https://github.com/stratis-storage/devicemapper-rs/pull/653 https://github.com/stratis-storage/devicemapper-rs/pull/650 https://github.com/stratis-storage/devicemapper-rs/pull/646 https://github.com/stratis-storage/devicemapper-rs/pull/645 https://github.com/stratis-storage/devicemapper-rs/pull/642 https://github.com/stratis-storage/devicemapper-rs/pull/639 https://github.com/stratis-storage/devicemapper-rs/pull/636 https://github.com/stratis-storage/devicemapper-rs/pull/635 https://github.com/stratis-storage/devicemapper-rs/pull/634 https://github.com/stratis-storage/devicemapper-rs/pull/631 https://github.com/stratis-storage/devicemapper-rs/pull/629 https://github.com/stratis-storage/devicemapper-rs/pull/628 https://github.com/stratis-storage/devicemapper-rs/pull/625 https://github.com/stratis-storage/devicemapper-rs/pull/621 https://github.com/stratis-storage/devicemapper-rs/pull/619 https://github.com/stratis-storage/devicemapper-rs/pull/617 devicemapper 0.29.1 =================== Recommended Rust toolchain version: 1.51.0 Lowest supported Rust toolchain version: 1.49 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/612 https://github.com/stratis-storage/devicemapper-rs/pull/611 https://github.com/stratis-storage/devicemapper-rs/pull/608 https://github.com/stratis-storage/devicemapper-rs/pull/607 https://github.com/stratis-storage/devicemapper-rs/pull/605 https://github.com/stratis-storage/devicemapper-rs/pull/603 https://github.com/stratis-storage/devicemapper-rs/pull/602 https://github.com/stratis-storage/devicemapper-rs/pull/601 devicemapper 0.29.0 =================== Recommended Rust toolchain version: 1.48.0 Lowest supported Rust toolchain version: 1.47 YAML linter: yamllint (1.25.0) Python: 3.8.5 New minimum Rust crate requirements: - nix: 0.19 - Use u128 for underlying Bytes representation: https://github.com/stratis-storage/devicemapper-rs/issues/426 https://github.com/stratis-storage/devicemapper-rs/pull/598 - Implement AsRawFd for DM: https://github.com/stratis-storage/devicemapper-rs/pull/592 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/600 https://github.com/stratis-storage/devicemapper-rs/pull/599 https://github.com/stratis-storage/devicemapper-rs/pull/597 https://github.com/stratis-storage/devicemapper-rs/pull/595 https://github.com/stratis-storage/devicemapper-rs/pull/594 https://github.com/stratis-storage/devicemapper-rs/pull/593 https://github.com/stratis-storage/devicemapper-rs/pull/591 https://github.com/stratis-storage/devicemapper-rs/pull/590 https://github.com/stratis-storage/devicemapper-rs/pull/588 devicemapper 0.28.1 =================== Recommended Rust toolchain version: 1.47.0 Lowest supported Rust toolchain version: 1.45 New minimum Rust crate requirements: - error_chain: 0.12.4 - nix: 0.18 - uuid: 0.8 YAML linter: yamllint (1.23.0) - Fix incorrect parsing of flakey target parameters: https://github.com/stratis-storage/devicemapper-rs/issues/261 https://github.com/stratis-storage/devicemapper-rs/pull/492 - Fix some bugs in parsing target parameters when the number of feature arguments is 0: https://github.com/stratis-storage/devicemapper-rs/issues/484 https://github.com/stratis-storage/devicemapper-rs/pull/489 https://github.com/stratis-storage/devicemapper-rs/pull/495 - Fix an incompatibility with musl: https://github.com/stratis-storage/devicemapper-rs/issues/560 https://github.com/stratis-storage/devicemapper-rs/pull/562 - Use fully qualified names in macros: https://github.com/stratis-storage/devicemapper-rs/pull/524 - Tidies and Maintenance: https://github.com/stratis-storage/devicemapper-rs/pull/585 https://github.com/stratis-storage/devicemapper-rs/pull/584 https://github.com/stratis-storage/devicemapper-rs/pull/583 https://github.com/stratis-storage/devicemapper-rs/pull/582 https://github.com/stratis-storage/devicemapper-rs/pull/581 https://github.com/stratis-storage/devicemapper-rs/pull/580 https://github.com/stratis-storage/devicemapper-rs/pull/579 https://github.com/stratis-storage/devicemapper-rs/pull/578 https://github.com/stratis-storage/devicemapper-rs/pull/577 https://github.com/stratis-storage/devicemapper-rs/pull/576 https://github.com/stratis-storage/devicemapper-rs/pull/575 https://github.com/stratis-storage/devicemapper-rs/pull/574 https://github.com/stratis-storage/devicemapper-rs/pull/573 https://github.com/stratis-storage/devicemapper-rs/pull/572 https://github.com/stratis-storage/devicemapper-rs/pull/571 https://github.com/stratis-storage/devicemapper-rs/pull/569 https://github.com/stratis-storage/devicemapper-rs/pull/568 https://github.com/stratis-storage/devicemapper-rs/pull/567 https://github.com/stratis-storage/devicemapper-rs/pull/566 https://github.com/stratis-storage/devicemapper-rs/pull/565 https://github.com/stratis-storage/devicemapper-rs/pull/564 https://github.com/stratis-storage/devicemapper-rs/pull/561 https://github.com/stratis-storage/devicemapper-rs/pull/558 https://github.com/stratis-storage/devicemapper-rs/pull/555 https://github.com/stratis-storage/devicemapper-rs/pull/554 https://github.com/stratis-storage/devicemapper-rs/pull/553 https://github.com/stratis-storage/devicemapper-rs/pull/552 https://github.com/stratis-storage/devicemapper-rs/pull/551 https://github.com/stratis-storage/devicemapper-rs/pull/550 https://github.com/stratis-storage/devicemapper-rs/pull/548 https://github.com/stratis-storage/devicemapper-rs/pull/547 https://github.com/stratis-storage/devicemapper-rs/pull/546 https://github.com/stratis-storage/devicemapper-rs/pull/545 https://github.com/stratis-storage/devicemapper-rs/pull/544 https://github.com/stratis-storage/devicemapper-rs/pull/543 https://github.com/stratis-storage/devicemapper-rs/pull/542 https://github.com/stratis-storage/devicemapper-rs/pull/541 https://github.com/stratis-storage/devicemapper-rs/pull/540 https://github.com/stratis-storage/devicemapper-rs/pull/539 https://github.com/stratis-storage/devicemapper-rs/pull/538 https://github.com/stratis-storage/devicemapper-rs/pull/536 https://github.com/stratis-storage/devicemapper-rs/pull/534 https://github.com/stratis-storage/devicemapper-rs/pull/533 https://github.com/stratis-storage/devicemapper-rs/pull/532 https://github.com/stratis-storage/devicemapper-rs/pull/531 https://github.com/stratis-storage/devicemapper-rs/pull/530 https://github.com/stratis-storage/devicemapper-rs/pull/529 https://github.com/stratis-storage/devicemapper-rs/pull/528 https://github.com/stratis-storage/devicemapper-rs/pull/527 https://github.com/stratis-storage/devicemapper-rs/pull/521 https://github.com/stratis-storage/devicemapper-rs/pull/520 https://github.com/stratis-storage/devicemapper-rs/pull/519 https://github.com/stratis-storage/devicemapper-rs/pull/518 https://github.com/stratis-storage/devicemapper-rs/pull/517 https://github.com/stratis-storage/devicemapper-rs/pull/514 https://github.com/stratis-storage/devicemapper-rs/pull/512 https://github.com/stratis-storage/devicemapper-rs/pull/511 https://github.com/stratis-storage/devicemapper-rs/pull/509 https://github.com/stratis-storage/devicemapper-rs/pull/508 https://github.com/stratis-storage/devicemapper-rs/pull/507 https://github.com/stratis-storage/devicemapper-rs/pull/504 https://github.com/stratis-storage/devicemapper-rs/pull/502 https://github.com/stratis-storage/devicemapper-rs/pull/501 https://github.com/stratis-storage/devicemapper-rs/pull/500 https://github.com/stratis-storage/devicemapper-rs/pull/499 https://github.com/stratis-storage/devicemapper-rs/pull/497 https://github.com/stratis-storage/devicemapper-rs/pull/496 https://github.com/stratis-storage/devicemapper-rs/pull/494 https://github.com/stratis-storage/devicemapper-rs/pull/487 https://github.com/stratis-storage/devicemapper-rs/pull/486 https://github.com/stratis-storage/devicemapper-rs/pull/480 https://github.com/stratis-storage/devicemapper-rs/pull/478 https://github.com/stratis-storage/devicemapper-rs/pull/477 devicemapper-0.34.4/Cargo.toml0000644000000053560000000000100115770ustar # 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" rust-version = "1.71.1" name = "devicemapper" version = "0.34.4" authors = ["Stratis Developers "] build = "build.rs" exclude = [ ".clippy.toml", ".githooks/*", ".gitignore", ".github/*", "Makefile", ] autobins = false autoexamples = false autotests = false autobenches = false description = "A library for using Linux device mapper" documentation = "https://docs.rs/devicemapper/" readme = "README.md" keywords = [ "Linux", "device", "mapper", "libdm", "storage", ] categories = [ "os::linux-apis", "api-bindings", ] license = "MPL-2.0" repository = "https://github.com/stratis-storage/devicemapper-rs" [lib] name = "devicemapper" path = "src/lib.rs" [dependencies.bitflags] version = "2.3.3" [dependencies.devicemapper-sys] version = "0.3.0" [dependencies.env_logger] version = "0.11.0" [dependencies.log] version = "0.4.14" [dependencies.nix] version = "0.29.0" features = [ "fs", "ioctl", "mount", ] [dependencies.once_cell] version = "1.19.0" [dependencies.rand] version = "0.8.0" [dependencies.retry] version = "2.0.0" default-features = false [dependencies.semver] version = "1.0.0" [dependencies.serde] version = "1.0.60" [dev-dependencies.assert_matches] version = "1.5.0" [dev-dependencies.libmount] version = "0.1.11" [dev-dependencies.loopdev-3] version = "0.5.0" [dev-dependencies.tempfile] version = "3.4.0" [dev-dependencies.uuid] version = "1.0.0" features = ["v4"] [build-dependencies.devicemapper-sys] version = "0.3.0" [build-dependencies.semver] version = "1.0.0" [lints.clippy.all] level = "deny" priority = 0 [lints.clippy.cargo] level = "deny" priority = 1 [lints.clippy.multiple-crate-versions] level = "allow" priority = 2 [lints.rust.future_incompatible] level = "deny" priority = 1 [lints.rust.nonstandard_style] level = "deny" priority = 4 [lints.rust.rust_2018_idioms] level = "deny" priority = 3 [lints.rust.unexpected_cfgs] level = "deny" priority = 0 check-cfg = [ "cfg(devicemapper41supported)", "cfg(devicemapper42supported)", "cfg(devicemapper437supported)", "cfg(devicemapper441supported)", "cfg(devicemapper46supported)", ] [lints.rust.unused] level = "deny" priority = 2 [lints.rust.warnings] level = "deny" priority = 0 devicemapper-0.34.4/Cargo.toml.orig000064400000000000000000000034601046102023000152520ustar 00000000000000[package] name = "devicemapper" version = "0.34.4" authors = ["Stratis Developers "] description = "A library for using Linux device mapper" documentation = "https://docs.rs/devicemapper/" repository = "https://github.com/stratis-storage/devicemapper-rs" readme = "README.md" categories = ["os::linux-apis", "api-bindings"] keywords = ["Linux", "device", "mapper", "libdm", "storage"] license = "MPL-2.0" edition = "2021" rust-version = "1.71.1" # LOWEST SUPPORTED RUST TOOLCHAIN exclude = [".clippy.toml", ".githooks/*", ".gitignore", ".github/*", "Makefile"] [dependencies] bitflags = "2.3.3" nix = {version = "0.29.0", features=["fs", "ioctl", "mount"]} env_logger="0.11.0" semver = "1.0.0" serde = "1.0.60" rand = "0.8.0" retry = {version = "2.0.0", default-features=false} log = "0.4.14" once_cell = "1.19.0" [dev-dependencies] assert_matches = "1.5.0" libmount = "0.1.11" loopdev-3 = "0.5.0" tempfile = "3.4.0" [dev-dependencies.uuid] version = "1.0.0" features = ["v4"] [dependencies.devicemapper-sys] version = "0.3.0" path = "./devicemapper-rs-sys" [build-dependencies.devicemapper-sys] version = "0.3.0" path = "./devicemapper-rs-sys" [build-dependencies] semver = "1.0.0" [lints.rust] warnings = { level = "deny" } future_incompatible = { level = "deny", priority = 1 } unused = { level = "deny", priority = 2} rust_2018_idioms = { level = "deny", priority = 3 } nonstandard_style = { level = "deny", priority = 4 } unexpected_cfgs = { level = "deny", check-cfg = [ 'cfg(devicemapper41supported)', 'cfg(devicemapper42supported)', 'cfg(devicemapper437supported)', 'cfg(devicemapper441supported)', 'cfg(devicemapper46supported)' ] } [lints.clippy] all = { level = "deny" } cargo = { level = "deny" , priority = 1} multiple-crate-versions = { level = "allow", priority = 2 } devicemapper-0.34.4/LICENSE.md000064400000000000000000000362771046102023000140030ustar 00000000000000Mozilla Public License Version 2.0 ================================== ### 1. Definitions **1.1. “Contributor”** means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. **1.2. “Contributor Version”** means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. **1.3. “Contribution”** means Covered Software of a particular Contributor. **1.4. “Covered Software”** means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. **1.5. “Incompatible With Secondary Licenses”** means * **(a)** that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or * **(b)** that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. **1.6. “Executable Form”** means any form of the work other than Source Code Form. **1.7. “Larger Work”** means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. **1.8. “License”** means this document. **1.9. “Licensable”** means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. **1.10. “Modifications”** means any of the following: * **(a)** any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or * **(b)** any new file in Source Code Form that contains any Covered Software. **1.11. “Patent Claims” of a Contributor** means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. **1.12. “Secondary License”** means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. **1.13. “Source Code Form”** means the form of the work preferred for making modifications. **1.14. “You” (or “Your”)** means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means **(a)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(b)** ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. ### 2. License Grants and Conditions #### 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: * **(a)** under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and * **(b)** under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. #### 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. #### 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: * **(a)** for any code that a Contributor has removed from Covered Software; or * **(b)** for infringements caused by: **(i)** Your and any other third party's modifications of Covered Software, or **(ii)** the combination of its Contributions with other software (except as part of its Contributor Version); or * **(c)** under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). #### 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). #### 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. #### 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. #### 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. ### 3. Responsibilities #### 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. #### 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: * **(a)** such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and * **(b)** You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. #### 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). #### 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. #### 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. ### 4. Inability to Comply Due to Statute or Regulation If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: **(a)** comply with the terms of this License to the maximum extent possible; and **(b)** describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. ### 5. Termination **5.1.** The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated **(a)** provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and **(b)** on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. **5.2.** If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. **5.3.** In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ### 6. Disclaimer of Warranty > Covered Software is provided under this License on an “as is” > basis, without warranty of any kind, either expressed, implied, or > statutory, including, without limitation, warranties that the > Covered Software is free of defects, merchantable, fit for a > particular purpose or non-infringing. The entire risk as to the > quality and performance of the Covered Software is with You. > Should any Covered Software prove defective in any respect, You > (not any Contributor) assume the cost of any necessary servicing, > repair, or correction. This disclaimer of warranty constitutes an > essential part of this License. No use of any Covered Software is > authorized under this License except under this disclaimer. ### 7. Limitation of Liability > Under no circumstances and under no legal theory, whether tort > (including negligence), contract, or otherwise, shall any > Contributor, or anyone who distributes Covered Software as > permitted above, be liable to You for any direct, indirect, > special, incidental, or consequential damages of any character > including, without limitation, damages for lost profits, loss of > goodwill, work stoppage, computer failure or malfunction, or any > and all other commercial damages or losses, even if such party > shall have been informed of the possibility of such damages. This > limitation of liability shall not apply to liability for death or > personal injury resulting from such party's negligence to the > extent applicable law prohibits such limitation. Some > jurisdictions do not allow the exclusion or limitation of > incidental or consequential damages, so this exclusion and > limitation may not apply to You. ### 8. Litigation Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. ### 9. Miscellaneous This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. ### 10. Versions of the License #### 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. #### 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. #### 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). #### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. ## Exhibit A - Source Code Form License Notice This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. ## Exhibit B - “Incompatible With Secondary Licenses” Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. devicemapper-0.34.4/README.md000064400000000000000000000007461046102023000136460ustar 00000000000000## devicemapper-rs #### A library wrapping Linux's devicemapper ioctls (does not use libdm). ### Development status #### BETA, feature complete but needs testing. ### Documentation [API Documentation](https://docs.rs/devicemapper). [Devicemapper Documentation](https://www.kernel.org/doc/Documentation/device-mapper/) ### How to contribute GitHub is used for pull requests and issue tracking. ### License [Mozilla Public License 2.0](https://www.mozilla.org/MPL/2.0/FAQ.html) devicemapper-0.34.4/_typos.toml000064400000000000000000000001131046102023000145650ustar 00000000000000[default.extend-words] # Misidentified as misspelling of "of" opf = "opf" devicemapper-0.34.4/build.rs000064400000000000000000000017471046102023000140360ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use semver::Version; use devicemapper_sys::{DM_VERSION_MAJOR, DM_VERSION_MINOR, DM_VERSION_PATCHLEVEL}; // List of DM ioctl interface versions that introduce new ioctl commands static DM_VERSIONS: &[&str] = &["4.1.0", "4.2.0", "4.6.0", "4.37.0", "4.41.0"]; fn main() { let version = Version::parse(&format!( "{DM_VERSION_MAJOR}.{DM_VERSION_MINOR}.{DM_VERSION_PATCHLEVEL}" )) .expect("simple version string is not parseable"); for ver in DM_VERSIONS.iter().take_while(|ver_string| { let iter_version = Version::parse(ver_string).expect("Could not parse version"); version >= iter_version }) { println!( "cargo:rustc-cfg=devicemapper{}supported", ver.split('.').take(2).collect::>().join("") ); } } devicemapper-0.34.4/src/cachedev.rs000064400000000000000000001071661046102023000152720ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{ collections::{HashMap, HashSet}, fmt, path::PathBuf, str::FromStr, }; use crate::{ consts::IEC, core::{DevId, Device, DeviceInfo, DmFlags, DmName, DmOptions, DmUuid, DM}, lineardev::{LinearDev, LinearDevTargetParams}, result::{DmError, DmResult, ErrorEnum}, shared::{ device_create, device_exists, device_match, get_status, get_status_line_fields, make_unexpected_value_error, parse_device, parse_value, DmDevice, TargetLine, TargetParams, TargetTable, TargetTypeBuf, }, units::{DataBlocks, MetaBlocks, Sectors}, }; // Specified in kernel docs /// The minimum size recommended in the docs for a cache block. pub const MIN_CACHE_BLOCK_SIZE: Sectors = Sectors(64); // 32 KiB /// The maximum size recommended in the docs for a cache block. pub const MAX_CACHE_BLOCK_SIZE: Sectors = Sectors(2 * IEC::Mi); // 1 GiB const CACHE_TARGET_NAME: &str = "cache"; /// Struct representing params for a cache target #[derive(Clone, Debug, Eq, PartialEq)] pub struct CacheTargetParams { /// Cache metadata device pub meta: Device, /// Cache device pub cache: Device, /// Origin device with data to be cached pub origin: Device, /// Cache block size pub cache_block_size: Sectors, /// Feature arguments pub feature_args: HashSet, /// IO policy pub policy: String, /// IO policy arguments pub policy_args: HashMap, } impl CacheTargetParams { /// Create a new CacheTargetParams struct pub fn new( meta: Device, cache: Device, origin: Device, cache_block_size: Sectors, feature_args: Vec, policy: String, policy_args: Vec<(String, String)>, ) -> CacheTargetParams { CacheTargetParams { meta, cache, origin, cache_block_size, feature_args: feature_args.into_iter().collect::>(), policy, policy_args: policy_args.into_iter().collect::>(), } } } impl fmt::Display for CacheTargetParams { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {}", CACHE_TARGET_NAME, self.param_str()) } } impl FromStr for CacheTargetParams { type Err = DmError; fn from_str(s: &str) -> DmResult { let vals = s.split(' ').collect::>(); if vals.len() < 8 { let err_msg = format!( "expected at least 8 values in params string \"{}\", found {}", s, vals.len() ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } if vals[0] != CACHE_TARGET_NAME { let err_msg = format!( "Expected a cache target entry but found target type {}", vals[0] ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let metadata_dev = parse_device(vals[1], "metadata sub-device for cache target")?; let cache_dev = parse_device(vals[2], "cache sub-device for cache target")?; let origin_dev = parse_device(vals[3], "origin sub-device for cache target")?; let block_size = Sectors(parse_value(vals[4], "data block size")?); let num_feature_args: usize = parse_value(vals[5], "number of feature args")?; let end_feature_args_index = 6 + num_feature_args; let feature_args: Vec = vals[6..end_feature_args_index] .iter() .map(|x| (*x).to_string()) .collect(); let policy = vals[end_feature_args_index].to_owned(); let num_policy_args: usize = parse_value(vals[end_feature_args_index + 1], "number of policy args")?; let start_policy_args_index = end_feature_args_index + 2; let end_policy_args_index = start_policy_args_index + num_policy_args; let policy_args: Vec<(String, String)> = vals [start_policy_args_index..end_policy_args_index] .chunks(2) .map(|x| (x[0].to_string(), x[1].to_string())) .collect(); Ok(CacheTargetParams::new( metadata_dev, cache_dev, origin_dev, block_size, feature_args, policy, policy_args, )) } } impl TargetParams for CacheTargetParams { fn param_str(&self) -> String { let feature_args = if self.feature_args.is_empty() { "0".to_owned() } else { format!( "{} {}", self.feature_args.len(), self.feature_args .iter() .cloned() .collect::>() .join(" ") ) }; let policy_args = if self.policy_args.is_empty() { "0".to_owned() } else { format!( "{} {}", self.policy_args.len(), self.policy_args .iter() .map(|(k, v)| format!("{k} {v}")) .collect::>() .join(" ") ) }; format!( "{} {} {} {} {} {} {}", self.meta, self.cache, self.origin, *self.cache_block_size, feature_args, self.policy, policy_args ) } fn target_type(&self) -> TargetTypeBuf { TargetTypeBuf::new(CACHE_TARGET_NAME.into()).expect("CACHE_TARGET_NAME is valid") } } /// A target table for a cache device. #[derive(Clone, Debug, Eq, PartialEq)] pub struct CacheDevTargetTable { /// The device's table pub table: TargetLine, } impl CacheDevTargetTable { /// Make a new CacheDevTargetTable from the required input pub fn new(start: Sectors, length: Sectors, params: CacheTargetParams) -> CacheDevTargetTable { CacheDevTargetTable { table: TargetLine::new(start, length, params), } } } impl fmt::Display for CacheDevTargetTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let table = &self.table; writeln!(f, "{} {} {}", *table.start, *table.length, table.params) } } impl TargetTable for CacheDevTargetTable { fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult { if table.len() != 1 { let err_msg = format!( "CacheDev table should have exactly one line, has {} lines", table.len() ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let line = table.first().expect("table.len() == 1"); Ok(CacheDevTargetTable::new( Sectors(line.0), Sectors(line.1), format!("{} {}", line.2, line.3).parse::()?, )) } fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> { to_raw_table_unique!(self) } } /// Cache usage #[derive(Debug)] pub struct CacheDevUsage { /// The metadata block size, should always be equal to META_BLOCK_SIZE. /// At time of writing, all metadata blocks have the same size. pub meta_block_size: Sectors, /// The number of metadata blocks in use pub used_meta: MetaBlocks, /// The number of metadata blocks available pub total_meta: MetaBlocks, /// The cache block size pub cache_block_size: Sectors, /// Used cache blocks pub used_cache: DataBlocks, /// Total cache blocks pub total_cache: DataBlocks, } impl CacheDevUsage { /// Make a new CacheDevUsage struct pub fn new( meta_block_size: Sectors, used_meta: MetaBlocks, total_meta: MetaBlocks, cache_block_size: Sectors, used_cache: DataBlocks, total_cache: DataBlocks, ) -> CacheDevUsage { // This is defined at the kernel level and should not change. assert_eq!(meta_block_size, Sectors(8)); CacheDevUsage { meta_block_size, used_meta, total_meta, cache_block_size, used_cache, total_cache, } } } /// Cache dev performance data #[derive(Debug)] pub struct CacheDevPerformance { /// Number of read hits pub read_hits: u64, /// Number of read misses pub read_misses: u64, /// Number of write hits pub write_hits: u64, /// Number of write misses pub write_misses: u64, /// Number of demotions pub demotions: u64, /// Number of promotions pub promotions: u64, /// Number of dirty blocks pub dirty: u64, } impl CacheDevPerformance { /// Construct a new CacheDevPerformance struct pub fn new( read_hits: u64, read_misses: u64, write_hits: u64, write_misses: u64, demotions: u64, promotions: u64, dirty: u64, ) -> CacheDevPerformance { CacheDevPerformance { read_hits, read_misses, write_hits, write_misses, demotions, promotions, dirty, } } } /// The cache metadata mode #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CacheDevMetadataMode { /// The cache is working normally. Good, /// The cache has been forced to transition to read-only mode. ReadOnly, } /// Status values of a cache device when it is working #[derive(Debug)] pub struct CacheDevWorkingStatus { /// A struct recording block usage for all devices pub usage: CacheDevUsage, /// A struct recording cache dev performance pub performance: CacheDevPerformance, /// The feature args pub feature_args: Vec, /// The core args pub core_args: Vec<(String, String)>, /// The name of the replacement policy to use /// User-defined policies are permitted. pub policy: String, /// Arguments for the designated policy pub policy_args: Vec<(String, String)>, /// cache metadata mode pub metadata_mode: CacheDevMetadataMode, /// needs_check flag has been set in metadata superblock pub needs_check: bool, } impl CacheDevWorkingStatus { /// Make a new CacheDevWorkingStatus struct #[allow(clippy::too_many_arguments)] pub fn new( usage: CacheDevUsage, performance: CacheDevPerformance, feature_args: Vec, core_args: Vec<(String, String)>, policy: String, policy_args: Vec<(String, String)>, metadata_mode: CacheDevMetadataMode, needs_check: bool, ) -> CacheDevWorkingStatus { CacheDevWorkingStatus { usage, performance, feature_args, core_args, policy, policy_args, metadata_mode, needs_check, } } } /// Return type of CacheDev::status() #[derive(Debug)] pub enum CacheDevStatus { /// The cache has not failed utterly Working(Box), /// Devicemapper has reported that it could not obtain the status Error, /// The cache is in a failed condition Fail, } impl FromStr for CacheDevStatus { type Err = DmError; // Note: This method is not entirely complete. In particular, *_args values // may require more or better checking or processing. fn from_str(status_line: &str) -> DmResult { if status_line.starts_with("Error") { return Ok(CacheDevStatus::Error); } if status_line.starts_with("Fail") { return Ok(CacheDevStatus::Fail); } let status_vals = get_status_line_fields(status_line, 17)?; let usage = { let meta_block_size = status_vals[0]; let meta_usage = status_vals[1].split('/').collect::>(); let cache_block_size = status_vals[2]; let cache_usage = status_vals[3].split('/').collect::>(); CacheDevUsage::new( Sectors(parse_value(meta_block_size, "meta block size")?), MetaBlocks(parse_value(meta_usage[0], "used meta")?), MetaBlocks(parse_value(meta_usage[1], "total meta")?), Sectors(parse_value(cache_block_size, "cache block size")?), DataBlocks(parse_value(cache_usage[0], "used cache")?), DataBlocks(parse_value(cache_usage[1], "total cache")?), ) }; let performance = CacheDevPerformance::new( parse_value(status_vals[4], "read hits")?, parse_value(status_vals[5], "read misses")?, parse_value(status_vals[6], "write hits")?, parse_value(status_vals[7], "write misses")?, parse_value(status_vals[8], "demotions")?, parse_value(status_vals[9], "promotions")?, parse_value(status_vals[10], "dirty")?, ); let num_feature_args: usize = parse_value(status_vals[11], "number of feature args")?; let core_args_start_index = 12usize + num_feature_args; let feature_args: Vec = status_vals[12..core_args_start_index] .iter() .map(|x| (*x).to_string()) .collect(); let (policy_start_index, core_args) = CacheDev::parse_pairs(core_args_start_index, &status_vals)?; let policy = status_vals[policy_start_index].to_string(); let (rest_start_index, policy_args) = CacheDev::parse_pairs(policy_start_index + 1, &status_vals)?; let cache_metadata_mode = match status_vals[rest_start_index] { "rw" => CacheDevMetadataMode::Good, "ro" => CacheDevMetadataMode::ReadOnly, val => { return Err(make_unexpected_value_error( rest_start_index + 1, val, "cache metadata mode", )); } }; let needs_check = match status_vals[rest_start_index + 1] { "-" => false, "needs_check" => true, val => { return Err(make_unexpected_value_error( rest_start_index + 1, val, "needs check", )); } }; Ok(CacheDevStatus::Working(Box::new( CacheDevWorkingStatus::new( usage, performance, feature_args, core_args, policy, policy_args, cache_metadata_mode, needs_check, ), ))) } } /// DM Cache device #[derive(Debug)] pub struct CacheDev { dev_info: Box, meta_dev: LinearDev, cache_dev: LinearDev, origin_dev: LinearDev, table: CacheDevTargetTable, } impl DmDevice for CacheDev { fn device(&self) -> Device { device!(self) } fn devnode(&self) -> PathBuf { devnode!(self) } // Omit replacement policy field from equality test when checking that // two devices are the same. Equality of replacement policies is not a // necessary requirement for equality of devices as the replacement // policy can be changed dynamically by a reload of of the device's table. // It is convenient that this is the case, because checking equality of // replacement policies is somewhat hard. "default", which is a valid // policy string, is not a particular policy, but an alias for the default // policy for this version of devicemapper. Therefore, using string // equality to check equivalence can result in false negatives, as // "default" != "smq", the current default policy in the recent kernel. // Note: There is the possibility of implementing the following somewhat // complicated check. Without loss of generality, let // left[0].params.policy = "default" and // right[0].params.policy = X, where X != "default". Then, if X is the // default policy, return true, otherwise return false. Unfortunately, // there is no straightforward programmatic way of determining the default // policy for a given kernel, and we are assured that the default policy // can vary between kernels, and may of course, change in future. fn equivalent_tables( left: &CacheDevTargetTable, right: &CacheDevTargetTable, ) -> DmResult { let left = &left.table; let right = &right.table; Ok(left.start == right.start && left.length == right.length && left.params.meta == right.params.meta && left.params.origin == right.params.origin && left.params.cache_block_size == right.params.cache_block_size && left.params.feature_args == right.params.feature_args && left.params.policy_args == right.params.policy_args) } fn name(&self) -> &DmName { name!(self) } fn size(&self) -> Sectors { self.origin_dev.size() } fn table(&self) -> &CacheDevTargetTable { table!(self) } fn teardown(&mut self, dm: &DM) -> DmResult<()> { dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?; self.cache_dev.teardown(dm)?; self.origin_dev.teardown(dm)?; self.meta_dev.teardown(dm)?; Ok(()) } fn uuid(&self) -> Option<&DmUuid> { uuid!(self) } } /// Cache device implementation. impl CacheDev { /// Construct a new CacheDev with the given data and meta devs. /// Returns an error if the device is already known to the kernel. pub fn new( dm: &DM, name: &DmName, uuid: Option<&DmUuid>, meta: LinearDev, cache: LinearDev, origin: LinearDev, cache_block_size: Sectors, ) -> DmResult { if device_exists(dm, name)? { let err_msg = format!("cachedev {name} already exists"); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let table = CacheDev::gen_default_table(&meta, &cache, &origin, cache_block_size); let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?; Ok(CacheDev { dev_info: Box::new(dev_info), meta_dev: meta, cache_dev: cache, origin_dev: origin, table, }) } /// Set up a cache device from the given metadata and data devices. pub fn setup( dm: &DM, name: &DmName, uuid: Option<&DmUuid>, meta: LinearDev, cache: LinearDev, origin: LinearDev, cache_block_size: Sectors, ) -> DmResult { let table = CacheDev::gen_default_table(&meta, &cache, &origin, cache_block_size); let dev = if device_exists(dm, name)? { let dev_info = dm.device_info(&DevId::Name(name))?; let dev = CacheDev { dev_info: Box::new(dev_info), meta_dev: meta, cache_dev: cache, origin_dev: origin, table, }; device_match(dm, &dev, uuid)?; dev } else { let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?; CacheDev { dev_info: Box::new(dev_info), meta_dev: meta, cache_dev: cache, origin_dev: origin, table, } }; Ok(dev) } /// Set the table for the existing origin device. /// This action puts the device in a state where it is ready to be resumed. /// Warning: It is the client's responsibility to make sure the designated /// table is compatible with the device's existing table. /// If not, this function will still succeed, but some kind of /// data corruption will be the inevitable result. pub fn set_origin_table( &mut self, dm: &DM, table: Vec>, ) -> DmResult<()> { self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.origin_dev.set_table(dm, table)?; self.origin_dev.resume(dm)?; let mut table = self.table.clone(); table.table.length = self.origin_dev.size(); self.table_load(dm, &table, DmOptions::default())?; self.table = table; Ok(()) } /// Set the table for the existing cache sub-device. /// This action puts the device in a state where it is ready to be resumed. /// Warning: It is the client's responsibility to make sure the designated /// table is compatible with the device's existing table. /// If not, this function will still succeed, but some kind of /// data corruption will be the inevitable result. pub fn set_cache_table( &mut self, dm: &DM, table: Vec>, ) -> DmResult<()> { self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.cache_dev.set_table(dm, table)?; self.cache_dev.resume(dm)?; // Reload the table, even though it is unchanged. Otherwise, we // suffer from whacky smq bug documented in the following PR: // https://github.com/stratis-storage/devicemapper-rs/pull/279. self.table_load(dm, self.table(), DmOptions::default())?; Ok(()) } /// Set the table for the existing meta sub-device. /// This action puts the device in a state where it is ready to be resumed. /// Warning: It is the client's responsibility to make sure the designated /// table is compatible with the device's existing table. /// If not, this function will still succeed, but some kind of /// data corruption will be the inevitable result. pub fn set_meta_table( &mut self, dm: &DM, table: Vec>, ) -> DmResult<()> { self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.meta_dev.set_table(dm, table)?; self.meta_dev.resume(dm)?; // Reload the table, even though it is unchanged. Otherwise, we // suffer from whacky smq bug documented in the following PR: // https://github.com/stratis-storage/devicemapper-rs/pull/279. self.table_load(dm, self.table(), DmOptions::default())?; Ok(()) } /// Generate a table to be passed to DM. The format of the table /// entries is: /// "cache" /// where the cache-specific string has the format: /// /// <#num feature args (1)> writethrough /// <#num policy args (0)> /// There is exactly one entry in the table. /// Various defaults are hard coded in the method. fn gen_default_table( meta: &LinearDev, cache: &LinearDev, origin: &LinearDev, cache_block_size: Sectors, ) -> CacheDevTargetTable { CacheDevTargetTable::new( Sectors::default(), origin.size(), CacheTargetParams::new( meta.device(), cache.device(), origin.device(), cache_block_size, vec!["writethrough".into()], "default".to_owned(), vec![], ), ) } /// Parse pairs of arguments from a slice fn parse_pairs(start_index: usize, vals: &[&str]) -> DmResult<(usize, Vec<(String, String)>)> { let num_pairs: usize = parse_value(vals[start_index], "number of pairs")?; if num_pairs % 2 != 0 { let err_msg = format!("Number of args \"{num_pairs}\" is not even"); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let next_start_index = start_index + num_pairs + 1; Ok(( next_start_index, vals[start_index + 1..next_start_index] .chunks(2) .map(|p| (p[0].to_string(), p[1].to_string())) .collect(), )) } /// Get the current status of the cache device. pub fn status(&self, dm: &DM, options: DmOptions) -> DmResult { status!(self, dm, options) } } #[cfg(test)] use std::fs::OpenOptions; #[cfg(test)] use std::path::Path; #[cfg(test)] use crate::core::devnode_to_devno; #[cfg(test)] use crate::lineardev::LinearTargetParams; #[cfg(test)] use crate::testing::{blkdev_size, test_name}; #[cfg(test)] // Make a minimal cachedev. Put the meta and cache on one device, and put // the origin on a separate device. paths.len() must be at least 2 or the // method will fail. pub fn minimal_cachedev(dm: &DM, paths: &[&Path]) -> CacheDev { assert!(paths.len() >= 2); let dev1 = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let meta_name = test_name("cache-meta").expect("valid format"); // Minimum recommended metadata size for thinpool let meta_length = Sectors(4 * IEC::Ki); let meta_params = LinearTargetParams::new(dev1, Sectors(0)); let meta_table = vec![TargetLine::new( Sectors(0), meta_length, LinearDevTargetParams::Linear(meta_params), )]; let meta = LinearDev::setup(dm, &meta_name, None, meta_table).unwrap(); let cache_name = test_name("cache-cache").expect("valid format"); let cache_offset = meta_length; let cache_length = MIN_CACHE_BLOCK_SIZE; let cache_params = LinearTargetParams::new(dev1, cache_offset); let cache_table = vec![TargetLine::new( Sectors(0), cache_length, LinearDevTargetParams::Linear(cache_params), )]; let cache = LinearDev::setup(dm, &cache_name, None, cache_table).unwrap(); let dev2_size = blkdev_size(&OpenOptions::new().read(true).open(paths[1]).unwrap()).sectors(); let dev2 = Device::from(devnode_to_devno(paths[1]).unwrap().unwrap()); let origin_name = test_name("cache-origin").expect("valid format"); let origin_params = LinearTargetParams::new(dev2, Sectors(0)); let origin_table = vec![TargetLine::new( Sectors(0), dev2_size, LinearDevTargetParams::Linear(origin_params), )]; let origin = LinearDev::setup(dm, &origin_name, None, origin_table).unwrap(); CacheDev::new( dm, &test_name("cache").expect("valid format"), None, meta, cache, origin, MIN_CACHE_BLOCK_SIZE, ) .unwrap() } #[cfg(test)] mod tests { use std::path::Path; use crate::testing::test_with_spec; use super::*; // Test creating a minimal cache dev. // Verify that status method executes and gives reasonable values. fn test_minimal_cache_dev(paths: &[&Path]) { assert!(paths.len() >= 2); let dm = DM::new().unwrap(); let mut cache = minimal_cachedev(&dm, paths); match cache.status(&dm, DmOptions::default()).unwrap() { CacheDevStatus::Working(ref status) => { let usage = &status.usage; assert_eq!(usage.meta_block_size, Sectors(8)); // Even an empty cache dev takes up some metadata space. assert!(usage.used_meta > MetaBlocks(0)); assert_eq!(usage.cache_block_size, MIN_CACHE_BLOCK_SIZE); assert_eq!( usage.cache_block_size, cache.table.table.params.cache_block_size ); let performance = &status.performance; // No write activity should mean all write performance data is 0 assert_eq!(performance.write_hits, 0); assert_eq!(performance.write_misses, 0); assert_eq!(performance.demotions, 0); assert_eq!(performance.dirty, 0); // The current defaults for configuration values assert_eq!(status.feature_args, vec!["writethrough"]); assert_eq!( status.core_args, vec![("migration_threshold".to_string(), "2048".to_string())] ); assert_eq!(status.policy, "smq"); assert_eq!(status.policy_args, vec![] as Vec<(String, String)>); assert_eq!(status.metadata_mode, CacheDevMetadataMode::Good); assert!(!status.needs_check); } status => panic!("unexpected thinpool status: {status:?}"), } let table = CacheDev::read_kernel_table(&dm, &DevId::Name(cache.name())) .unwrap() .table; let params = &table.params; assert_eq!(params.cache_block_size, MIN_CACHE_BLOCK_SIZE); assert_eq!( params.feature_args, vec!["writethrough".into()] .into_iter() .collect::>() ); assert_eq!(params.policy, "default"); cache.teardown(&dm).unwrap(); } #[test] fn loop_test_minimal_cache_dev() { test_with_spec(2, test_minimal_cache_dev); } /// Basic test of meta size change. /// This executes the code paths, but is not enough to ensure correctness. /// * Construct a minimal cache /// * Expand the meta device by one block fn test_meta_size_change(paths: &[&Path]) { assert!(paths.len() >= 3); let dm = DM::new().unwrap(); let mut cache = minimal_cachedev(&dm, paths); let mut table = cache.meta_dev.table().table.clone(); let dev3 = Device::from(devnode_to_devno(paths[2]).unwrap().unwrap()); let extra_length = MIN_CACHE_BLOCK_SIZE; let cache_params = LinearTargetParams::new(dev3, Sectors(0)); let current_length = cache.meta_dev.size(); match cache.status(&dm, DmOptions::default()).unwrap() { CacheDevStatus::Working(ref status) => { let usage = &status.usage; assert_eq!(*usage.total_meta * usage.meta_block_size, current_length); } CacheDevStatus::Error => panic!("devicemapper could not obtain cache status"), CacheDevStatus::Fail => panic!("cache should not have failed"), } table.push(TargetLine::new( current_length, extra_length, LinearDevTargetParams::Linear(cache_params), )); assert_matches!(cache.set_meta_table(&dm, table), Ok(_)); cache.resume(&dm).unwrap(); match cache.status(&dm, DmOptions::default()).unwrap() { CacheDevStatus::Working(ref status) => { let usage = &status.usage; let assigned_length = current_length + extra_length; assert!(*usage.total_meta * usage.meta_block_size <= assigned_length); assert_eq!(assigned_length, cache.meta_dev.size()); } CacheDevStatus::Error => panic!("devicemapper could not obtain cache status"), CacheDevStatus::Fail => panic!("cache should not have failed"), } cache.teardown(&dm).unwrap(); } #[test] fn loop_test_meta_size_change() { test_with_spec(3, test_meta_size_change); } /// Basic test of cache size change /// This executes the code paths, but is not enough to ensure correctness. /// * Construct a minimal cache /// * Expand the cache by one more block /// * Decrease the cache to its original size fn test_cache_size_change(paths: &[&Path]) { assert!(paths.len() >= 3); let dm = DM::new().unwrap(); let mut cache = minimal_cachedev(&dm, paths); let mut cache_table = cache.cache_dev.table().table.clone(); let dev3 = Device::from(devnode_to_devno(paths[2]).unwrap().unwrap()); let extra_length = MIN_CACHE_BLOCK_SIZE; let cache_params = LinearTargetParams::new(dev3, Sectors(0)); let current_length = cache.cache_dev.size(); match cache.status(&dm, DmOptions::default()).unwrap() { CacheDevStatus::Working(ref status) => { let usage = &status.usage; assert_eq!(*usage.total_cache * usage.cache_block_size, current_length); } CacheDevStatus::Error => panic!("devicemapper could not obtain cache status"), CacheDevStatus::Fail => panic!("cache should not have failed"), } cache_table.push(TargetLine::new( current_length, extra_length, LinearDevTargetParams::Linear(cache_params), )); assert_matches!(cache.set_cache_table(&dm, cache_table.clone()), Ok(_)); cache.resume(&dm).unwrap(); match cache.status(&dm, DmOptions::default()).unwrap() { CacheDevStatus::Working(ref status) => { let usage = &status.usage; assert_eq!( *usage.total_cache * usage.cache_block_size, current_length + extra_length ); } CacheDevStatus::Error => panic!("devicemapper could not obtain cache status"), CacheDevStatus::Fail => panic!("cache should not have failed"), } cache_table.pop(); assert_matches!(cache.set_cache_table(&dm, cache_table), Ok(_)); cache.resume(&dm).unwrap(); match cache.status(&dm, DmOptions::default()).unwrap() { CacheDevStatus::Working(ref status) => { let usage = &status.usage; assert_eq!(*usage.total_cache * usage.cache_block_size, current_length); } CacheDevStatus::Error => panic!("devicemapper could not obtain cache status"), CacheDevStatus::Fail => panic!("cache should not have failed"), } cache.teardown(&dm).unwrap(); } #[test] fn loop_test_cache_size_change() { test_with_spec(3, test_cache_size_change); } /// Test changing the size of the origin device. /// Verify that once changed, the new size is reflected in origin device /// and cache device. fn test_origin_size_change(paths: &[&Path]) { assert!(paths.len() >= 3); let dm = DM::new().unwrap(); let mut cache = minimal_cachedev(&dm, paths); let mut origin_table = cache.origin_dev.table().table.clone(); let origin_size = cache.origin_dev.size(); let dev3_size = blkdev_size(&OpenOptions::new().read(true).open(paths[2]).unwrap()).sectors(); let dev3 = Device::from(devnode_to_devno(paths[2]).unwrap().unwrap()); let origin_params = LinearTargetParams::new(dev3, Sectors(0)); origin_table.push(TargetLine::new( origin_size, dev3_size, LinearDevTargetParams::Linear(origin_params), )); cache.set_origin_table(&dm, origin_table).unwrap(); cache.resume(&dm).unwrap(); let origin_size = origin_size + dev3_size; assert_eq!(cache.origin_dev.size(), origin_size); assert_eq!(cache.size(), origin_size); cache.teardown(&dm).unwrap(); } #[test] fn loop_test_origin_size_change() { test_with_spec(3, test_origin_size_change); } /// Verify that suspending and resuming the cache doesn't fail. fn test_suspend(paths: &[&Path]) { assert!(paths.len() >= 2); let dm = DM::new().unwrap(); let mut cache = minimal_cachedev(&dm, paths); cache .suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH)) .unwrap(); cache .suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH)) .unwrap(); cache.resume(&dm).unwrap(); cache.resume(&dm).unwrap(); cache.teardown(&dm).unwrap(); } #[test] fn loop_test_suspend() { test_with_spec(2, test_suspend); } } devicemapper-0.34.4/src/consts.rs000064400000000000000000000012441046102023000150270ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #[allow(non_upper_case_globals)] #[allow(non_snake_case)] /// International Electrotechnical Commission Units Standards pub mod IEC { /// kibi pub const Ki: u64 = 1024; /// mebi pub const Mi: u64 = 1024 * Ki; /// gibi pub const Gi: u64 = 1024 * Mi; /// tebi pub const Ti: u64 = 1024 * Gi; /// pebi pub const Pi: u64 = 1024 * Ti; /// exbi pub const Ei: u64 = 1024 * Pi; // Ei is the maximum IEC unit expressible in u64. } devicemapper-0.34.4/src/core/device.rs000064400000000000000000000124561046102023000157140ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{fmt, path::Path, str::FromStr}; use nix::libc::{dev_t, major, makedev, minor}; use nix::sys::stat::{self, SFlag}; use crate::{ core::errors, result::{DmError, DmResult}, }; /// A struct containing the device's major and minor numbers /// /// Also allows conversion to/from a single 64bit dev_t value. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct Device { /// Device major number pub major: u32, /// Device minor number pub minor: u32, } /// Display format is the device number in `:` format impl fmt::Display for Device { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.major, self.minor) } } impl FromStr for Device { type Err = DmError; fn from_str(s: &str) -> Result { let vals = s.split(':').collect::>(); if vals.len() != 2 { let err_msg = format!("value \"{s}\" split into wrong number of fields"); return Err(DmError::Core(errors::Error::InvalidArgument(err_msg))); } let major = vals[0].parse::().map_err(|_| { DmError::Core(errors::Error::InvalidArgument(format!( "could not parse \"{}\" to obtain major number", vals[0] ))) })?; let minor = vals[1].parse::().map_err(|_| { DmError::Core(errors::Error::InvalidArgument(format!( "could not parse \"{}\" to obtain minor number", vals[1] ))) })?; Ok(Device { major, minor }) } } impl From for Device { fn from(val: dev_t) -> Device { let major = unsafe { major(val) }; #[cfg(target_os = "android")] let major = major as u32; let minor = unsafe { minor(val) }; #[cfg(target_os = "android")] let minor = minor as u32; Device { major, minor } } } impl From for dev_t { fn from(dev: Device) -> dev_t { #[cfg(target_os = "android")] #[allow(unused_unsafe)] // No longer unsafe in libc 0.2.133. #[allow(clippy::useless_conversion)] // Param types u32 in libc 0.2.133 unsafe { makedev( dev.major .try_into() .expect("value is smaller than max positive i32"), dev.minor .try_into() .expect("value is smaller than max positive i32"), ) } #[cfg(not(target_os = "android"))] #[allow(unused_unsafe)] // No longer unsafe in libc 0.2.133. unsafe { makedev(dev.major, dev.minor) } } } /// The Linux kernel's kdev_t encodes major/minor values as mmmM MMmm. impl Device { /// Make a Device from a kdev_t. pub fn from_kdev_t(val: u32) -> Device { Device { major: (val & 0xf_ff00) >> 8, minor: (val & 0xff) | ((val >> 12) & 0xf_ff00), } } /// Convert to a kdev_t. Return None if values are not expressible as a /// kdev_t. pub fn to_kdev_t(self) -> Option { if self.major > 0xfff || self.minor > 0xf_ffff { return None; } Some((self.minor & 0xff) | (self.major << 8) | ((self.minor & !0xff) << 12)) } } /// Get a device number from a device node. /// Return None if the device is not a block device; devicemapper is not /// interested in other sorts of devices. Return None if the device appears /// not to exist. pub fn devnode_to_devno(path: &Path) -> DmResult> { match stat::stat(path) { Ok(metadata) => Ok( if metadata.st_mode & SFlag::S_IFMT.bits() == SFlag::S_IFBLK.bits() { Some(metadata.st_rdev) } else { None }, ), Err(nix::Error::ENOENT) => Ok(None), Err(err) => Err(DmError::Core(errors::Error::MetadataIo( path.to_owned(), err.to_string(), ))), } } #[cfg(test)] mod tests { use super::*; #[test] /// Verify conversion is correct both ways fn test_dev_t_conversion() { let test_devt_1: dev_t = 0xabcd_ef12_3456_7890; let dev1 = Device::from(test_devt_1); // Default glibc dev_t encoding is MMMM Mmmm mmmM MMmm. I guess if // we're on a platform where non-default is used, we'll fail. assert_eq!(dev1.major, 0xabcd_e678); assert_eq!(dev1.minor, 0xf123_4590); let test_devt_2: dev_t = dev_t::from(dev1); assert_eq!(test_devt_1, test_devt_2); } #[test] /// Verify conversion is correct both ways fn test_kdev_t_conversion() { let test_devt_1: u32 = 0x1234_5678; let dev1 = Device::from_kdev_t(test_devt_1); // Default kernel kdev_t "huge" encoding is mmmM MMmm. assert_eq!(dev1.major, 0x456); assert_eq!(dev1.minor, 0x1_2378); let test_devt_2: u32 = dev1.to_kdev_t().unwrap(); assert_eq!(test_devt_1, test_devt_2); // a Device inexpressible as a kdev_t let dev2 = Device::from(0xabcd_ef12_3456_7890); assert_eq!(dev2.to_kdev_t(), None); } } devicemapper-0.34.4/src/core/deviceinfo.rs000064400000000000000000000066131046102023000165660ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use nix::libc::c_char; use semver::Version; use crate::{ core::{ device::Device, dm_flags::DmFlags, dm_ioctl as dmi, errors, types::{DmName, DmNameBuf, DmUuid, DmUuidBuf}, util::str_from_c_str, }, result::{DmError, DmResult}, }; /// Contains information about the device. #[derive(Clone, Debug)] pub struct DeviceInfo { version: Version, #[allow(dead_code)] data_size: u32, #[allow(dead_code)] data_start: u32, pub(super) target_count: u32, open_count: i32, flags: DmFlags, event_nr: u32, dev: Device, name: Option, uuid: Option, } impl TryFrom for DeviceInfo { type Error = DmError; fn try_from(ioctl: dmi::Struct_dm_ioctl) -> DmResult { let uuid = str_from_c_str(&ioctl.uuid as &[c_char]).ok_or_else(|| { errors::Error::InvalidArgument("Devicemapper UUID is not null terminated".to_string()) })?; let uuid = if uuid.is_empty() { None } else { Some(DmUuidBuf::new(uuid.to_string())?) }; let name = str_from_c_str(&ioctl.name as &[c_char]).ok_or_else(|| { errors::Error::InvalidArgument("Devicemapper name is not null terminated".to_string()) })?; let name = if name.is_empty() { None } else { Some(DmNameBuf::new(name.to_string())?) }; Ok(DeviceInfo { version: Version::new( u64::from(ioctl.version[0]), u64::from(ioctl.version[1]), u64::from(ioctl.version[2]), ), data_size: ioctl.data_size, data_start: ioctl.data_start, target_count: ioctl.target_count, open_count: ioctl.open_count, flags: DmFlags::from_bits_truncate(ioctl.flags), event_nr: ioctl.event_nr, // dm_ioctl struct reserves 64 bits for device but kernel "huge" // encoding is only 32 bits. dev: Device::from_kdev_t(ioctl.dev as u32), uuid, name, }) } } impl DeviceInfo { /// Parses a DM ioctl structure. /// /// Equivalent to `DeviceInfo::try_from(hdr)`. pub fn new(hdr: dmi::Struct_dm_ioctl) -> DmResult { DeviceInfo::try_from(hdr) } /// The major, minor, and patchlevel versions of devicemapper. pub fn version(&self) -> &Version { &self.version } /// The number of times the device is currently open. pub fn open_count(&self) -> i32 { self.open_count } /// The last event number for the device. pub fn event_nr(&self) -> u32 { self.event_nr } /// The device's major and minor device numbers, as a Device. pub fn device(&self) -> Device { self.dev } /// The device's name. pub fn name(&self) -> Option<&DmName> { self.name.as_ref().map(|name| name.as_ref()) } /// The device's devicemapper uuid. pub fn uuid(&self) -> Option<&DmUuid> { self.uuid.as_ref().map(|uuid| uuid.as_ref()) } /// The flags returned from the device. pub fn flags(&self) -> DmFlags { self.flags } } devicemapper-0.34.4/src/core/dm.rs000064400000000000000000001236131046102023000150530ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{ cmp, fs::File, io::{Cursor, Read, Write}, mem::size_of, os::unix::io::{AsRawFd, RawFd}, slice, str, }; use nix::{errno, libc::ioctl as nix_ioctl}; use retry::{delay::Fixed, retry_with_index, Error as RetryError, OperationResult}; use semver::Version; use crate::{ core::{ device::Device, deviceinfo::DeviceInfo, dm_flags::DmFlags, dm_ioctl as dmi, dm_options::DmOptions, dm_udev_sync::{UdevSync, UdevSyncAction}, errors, types::{DevId, DmName, DmNameBuf, DmUuid}, util::{ align_to, c_struct_from_slice, mut_slice_from_c_str, slice_from_c_struct, str_from_byte_slice, str_from_c_str, }, }, result::{DmError, DmResult, ErrorEnum}, }; #[cfg(target_os = "linux")] /// Control path for user space to pass IOCTL to kernel DM const DM_CTL_PATH: &str = "/dev/mapper/control"; #[cfg(target_os = "android")] /// Control path for user space to pass IOCTL to kernel DM const DM_CTL_PATH: &str = "/dev/device-mapper"; /// Start with a large buffer to make BUFFER_FULL rare. Libdm does this too. const MIN_BUF_SIZE: usize = 16 * 1024; /// Number of device remove retry attempts const DM_REMOVE_RETRIES: usize = 5; /// Delay between remove attempts const DM_REMOVE_MSLEEP_DELAY: u64 = 200; /// Context needed for communicating with devicemapper. pub struct DM { file: File, } impl DmOptions { /// Generate a header to be used for IOCTL. fn to_ioctl_hdr( self, id: Option<&DevId<'_>>, allowable_flags: DmFlags, ) -> DmResult { let clean_flags = allowable_flags & self.flags(); let event_nr = self.udev_flags().bits() << dmi::DM_UDEV_FLAGS_SHIFT; let mut hdr: dmi::Struct_dm_ioctl = devicemapper_sys::dm_ioctl { flags: clean_flags.bits(), event_nr, data_start: size_of::() as u32, ..Default::default() }; if let Some(id) = id { match id { DevId::Name(name) => DM::hdr_set_name(&mut hdr, name)?, DevId::Uuid(uuid) => DM::hdr_set_uuid(&mut hdr, uuid)?, }; }; Ok(hdr) } } impl DM { /// Create a new context for communicating with DM. pub fn new() -> DmResult { Ok(DM { file: File::open(DM_CTL_PATH) .map_err(|err| DmError::Core(errors::Error::ContextInit(err.to_string())))?, }) } fn hdr_set_name(hdr: &mut dmi::Struct_dm_ioctl, name: &DmName) -> DmResult<()> { let _ = name .as_bytes() .read(mut_slice_from_c_str(&mut hdr.name)) .map_err(|err| errors::Error::GeneralIo(err.to_string()))?; Ok(()) } fn hdr_set_uuid(hdr: &mut dmi::Struct_dm_ioctl, uuid: &DmUuid) -> DmResult<()> { let _ = uuid .as_bytes() .read(mut_slice_from_c_str(&mut hdr.uuid)) .map_err(|err| errors::Error::GeneralIo(err.to_string()))?; Ok(()) } /// Get the file within the DM context, likely for polling purposes. pub fn file(&self) -> &File { &self.file } // Make the ioctl call specified by the given ioctl number. // Set the required DM version to the lowest that supports the given ioctl. fn do_ioctl( &self, ioctl: u8, hdr: &mut dmi::Struct_dm_ioctl, in_data: Option<&[u8]>, ) -> DmResult<(DeviceInfo, Vec)> { let op = request_code_readwrite!(dmi::DM_IOCTL, ioctl, size_of::()); #[cfg(target_os = "android")] let op = op as i32; let ioctl_version = dmi::ioctl_to_version(ioctl); hdr.version[0] = ioctl_version.0; hdr.version[1] = ioctl_version.1; hdr.version[2] = ioctl_version.2; // Begin udev sync transaction and set DM_UDEV_PRIMARY_SOURCE_FLAG // if ioctl command generates uevents. let sync = UdevSync::begin(hdr, ioctl)?; let data_size = cmp::max( MIN_BUF_SIZE, size_of::() + in_data.map_or(0, |x| x.len()), ); let mut buffer: Vec = Vec::with_capacity(data_size); let mut buffer_hdr; loop { hdr.data_size = buffer.capacity() as u32; let hdr_slc = unsafe { let len = hdr.data_start as usize; let ptr = hdr as *mut dmi::Struct_dm_ioctl as *mut u8; slice::from_raw_parts_mut(ptr, len) }; buffer.clear(); buffer.extend_from_slice(hdr_slc); if let Some(in_data) = in_data { buffer.extend(in_data.iter().cloned()); } buffer.resize(buffer.capacity(), 0); buffer_hdr = unsafe { &mut *(buffer.as_mut_ptr() as *mut dmi::Struct_dm_ioctl) }; if let Err(err) = unsafe { convert_ioctl_res!(nix_ioctl(self.file.as_raw_fd(), op, buffer.as_mut_ptr())) } { // Cancel udev sync and clean up semaphore sync.cancel(); return Err(DmError::Core(errors::Error::Ioctl( op as u8, DeviceInfo::new(*hdr).ok().map(Box::new), DeviceInfo::new(*buffer_hdr).ok().map(Box::new), Box::new(err), ))); } if (buffer_hdr.flags & DmFlags::DM_BUFFER_FULL.bits()) == 0 { break; } // If DM_BUFFER_FULL is set, DM requires more space for the // response. Double the capacity of the buffer and re-try the // ioctl. If the size of the buffer is already as large as can be // possibly expressed in data_size field, return an error. // Never allow the size to exceed u32::MAX. let len = buffer.capacity(); if len == u32::MAX as usize { return Err(DmError::Core(errors::Error::IoctlResultTooLarge)); } buffer.resize((len as u32).saturating_mul(2) as usize, 0); } let data_end = cmp::max(buffer_hdr.data_size, buffer_hdr.data_start); // Synchronize with udev event processing sync.end(buffer_hdr.flags)?; Ok(( DeviceInfo::try_from(*buffer_hdr)?, buffer[buffer_hdr.data_start as usize..data_end as usize].to_vec(), )) } /// Devicemapper version information: Major, Minor, and patchlevel versions. pub fn version(&self) -> DmResult<(u32, u32, u32)> { let mut hdr = DmOptions::default().to_ioctl_hdr(None, DmFlags::empty())?; let (hdr_out, _) = self.do_ioctl(dmi::DM_VERSION_CMD as u8, &mut hdr, None)?; Ok(( hdr_out .version() .major .try_into() .expect("dm_ioctl struct field is u32"), hdr_out .version() .minor .try_into() .expect("dm_ioctl struct field is u32"), hdr_out .version() .patch .try_into() .expect("dm_ioctl struct field is u32"), )) } /// Remove all DM devices and tables. Use discouraged other than /// for debugging. /// /// If `DM_DEFERRED_REMOVE` is set, the request will succeed for /// in-use devices, and they will be removed when released. /// /// Valid flags: `DM_DEFERRED_REMOVE` pub fn remove_all(&self, options: DmOptions) -> DmResult<()> { let mut hdr = options.to_ioctl_hdr(None, DmFlags::DM_DEFERRED_REMOVE)?; self.do_ioctl(dmi::DM_REMOVE_ALL_CMD as u8, &mut hdr, None)?; Ok(()) } /// Returns a list of tuples containing DM device names, a Device, which /// holds their major and minor device numbers, and on kernels that /// support it, each device's last event_nr. pub fn list_devices(&self) -> DmResult)>> { let mut hdr = DmOptions::default().to_ioctl_hdr(None, DmFlags::empty())?; let (hdr_out, data_out) = self.do_ioctl(dmi::DM_LIST_DEVICES_CMD as u8, &mut hdr, None)?; let event_nr_set = hdr_out.version() >= &Version::new(4, 37, 0); let mut devs = Vec::new(); if !data_out.is_empty() { let mut result = &data_out[..]; loop { let device = c_struct_from_slice::(result).ok_or_else(|| { DmError::Dm( ErrorEnum::Invalid, "Received null pointer from kernel".to_string(), ) })?; let name_offset = unsafe { (device.name.as_ptr() as *const u8).offset_from(device as *const _ as *const u8) } as usize; let dm_name = str_from_byte_slice(&result[name_offset..]) .map(|s| s.to_owned()) .ok_or_else(|| { DmError::Dm( ErrorEnum::Invalid, "Devicemapper name is not valid UTF8".to_string(), ) })?; // Get each device's event number after its name, if the kernel // DM version supports it. // Should match offset calc in kernel's // drivers/md/dm-ioctl.c:list_devices let event_nr = if event_nr_set { // offsetof "name" in Struct_dm_name_list. let offset = align_to(name_offset + dm_name.len() + 1, size_of::()); let nr = u32::from_ne_bytes( result[offset..offset + size_of::()] .try_into() .map_err(|_| { DmError::Dm( ErrorEnum::Invalid, "Incorrectly sized slice for u32".to_string(), ) })?, ); Some(nr) } else { None }; devs.push((DmNameBuf::new(dm_name)?, device.dev.into(), event_nr)); if device.next == 0 { break; } result = &result[device.next as usize..]; } } Ok(devs) } /// Create a DM device. It starts out in a "suspended" state. /// /// Valid flags: `DM_READONLY`, `DM_PERSISTENT_DEV` /// /// # Example /// /// ```no_run /// use devicemapper::{DM, DmOptions, DmName}; /// /// let dm = DM::new().unwrap(); /// /// // Setting a uuid is optional /// let name = DmName::new("example-dev").expect("is valid DM name"); /// let dev = dm.device_create(name, None, DmOptions::default()).unwrap(); /// ``` pub fn device_create( &self, name: &DmName, uuid: Option<&DmUuid>, options: DmOptions, ) -> DmResult { let mut hdr = options.to_ioctl_hdr(None, DmFlags::DM_READONLY | DmFlags::DM_PERSISTENT_DEV)?; Self::hdr_set_name(&mut hdr, name)?; if let Some(uuid) = uuid { Self::hdr_set_uuid(&mut hdr, uuid)?; } debug!("Creating device {} (uuid={:?})", name, uuid); self.do_ioctl(dmi::DM_DEV_CREATE_CMD as u8, &mut hdr, None) .map(|(hdr, _)| hdr) } fn try_device_remove( &self, id: &DevId<'_>, options: DmOptions, ) -> OperationResult { let mut hdr = match options.to_ioctl_hdr(Some(id), DmFlags::DM_DEFERRED_REMOVE) { Ok(hdr) => hdr, Err(err) => { return OperationResult::Err(err); } }; match self.do_ioctl(dmi::DM_DEV_REMOVE_CMD as u8, &mut hdr, None) { Err(err) => { if let DmError::Core(errors::Error::Ioctl(op, hdr_in, hdr_out, errno)) = err { if *errno == errno::Errno::EBUSY { OperationResult::Retry(DmError::Core(errors::Error::Ioctl( op, hdr_in, hdr_out, errno, ))) } else { OperationResult::Err(DmError::Core(errors::Error::Ioctl( op, hdr_in, hdr_out, errno, ))) } } else { OperationResult::Err(err) } } Ok((deviceinfo, _)) => OperationResult::Ok(deviceinfo), } } /// Remove a DM device and its mapping tables. /// /// If `DM_DEFERRED_REMOVE` is set, the request for an in-use /// devices will succeed, and it will be removed when no longer /// used. /// /// Valid flags: `DM_DEFERRED_REMOVE` pub fn device_remove(&self, id: &DevId<'_>, options: DmOptions) -> DmResult { debug!("Removing device {}", id); #[allow(clippy::blocks_in_conditions)] match retry_with_index( Fixed::from_millis(DM_REMOVE_MSLEEP_DELAY).take(DM_REMOVE_RETRIES - 1), |i| { trace!("Device remove attempt {} of {}", i, DM_REMOVE_RETRIES); self.try_device_remove(id, options) }, ) { Ok(deviceinfo) => Ok(deviceinfo), Err(RetryError { error, .. }) => Err(error), } } /// Change a DM device's name OR set the device's uuid for the first time. /// /// Prerequisite: if `new == DevId::Name(new_name)`, `old_name != new_name` /// Prerequisite: if `new == DevId::Uuid(uuid)`, device's current uuid /// must be `""`. /// Note: Possibly surprisingly, returned `DeviceInfo`'s uuid or name field /// contains the previous value, not the newly set value. pub fn device_rename(&self, old_name: &DmName, new: &DevId<'_>) -> DmResult { let (options, id_in) = match *new { DevId::Name(name) => (DmOptions::default(), name.as_bytes()), DevId::Uuid(uuid) => ( DmOptions::default().set_flags(DmFlags::DM_UUID), uuid.as_bytes(), ), }; let data_in = [id_in, &[b'\0']].concat(); let mut hdr = options.to_ioctl_hdr(None, DmFlags::DM_UUID)?; Self::hdr_set_name(&mut hdr, old_name)?; debug!("Renaming device {} to {}", old_name, new); self.do_ioctl(dmi::DM_DEV_RENAME_CMD as u8, &mut hdr, Some(&data_in)) .map(|(hdr, _)| hdr) } /// Suspend or resume a DM device, depending on if `DM_SUSPEND` flag /// is set or not. /// /// Resuming a DM device moves a table loaded into the "inactive" /// slot by [`Self::table_load`] into the "active" slot. /// /// Will block until pending I/O is completed unless DM_NOFLUSH /// flag is given. Will freeze filesystem unless DM_SKIP_LOCKFS /// flags is given. Additional I/O to a suspended device will be /// held until it is resumed. /// /// Valid flags: `DM_SUSPEND`, `DM_NOFLUSH`, `DM_SKIP_LOCKFS` /// /// # Example /// /// ```no_run /// use devicemapper::{DM, DevId, DmFlags, DmOptions, DmName}; /// let dm = DM::new().unwrap(); /// /// let name = DmName::new("example-dev").expect("is valid DM name"); /// let id = DevId::Name(name); /// dm.device_suspend(&id, DmOptions::default().set_flags(DmFlags::DM_SUSPEND)).unwrap(); /// ``` pub fn device_suspend(&self, id: &DevId<'_>, options: DmOptions) -> DmResult { let mut hdr = options.to_ioctl_hdr( Some(id), DmFlags::DM_SUSPEND | DmFlags::DM_NOFLUSH | DmFlags::DM_SKIP_LOCKFS, )?; let action = if options.flags().contains(DmFlags::DM_SUSPEND) { "Suspending" } else { "Resuming" }; debug!("{} device {}", action, id); self.do_ioctl(dmi::DM_DEV_SUSPEND_CMD as u8, &mut hdr, None) .map(|(hdr, _)| hdr) } /// Get DeviceInfo for a device. This is also returned by other /// methods, but if just the DeviceInfo is desired then this just /// gets it. pub fn device_info(&self, id: &DevId<'_>) -> DmResult { let mut hdr = DmOptions::default().to_ioctl_hdr(Some(id), DmFlags::empty())?; trace!("Retrieving info for {}", id); self.do_ioctl(dmi::DM_DEV_STATUS_CMD as u8, &mut hdr, None) .map(|(hdr, _)| hdr) } /// Wait for a device to report an event. /// /// Once an event occurs, this function behaves just like /// [`Self::table_status`], see that function for more details. /// /// This interface is not very friendly to monitoring multiple devices. /// Events are also exported via uevents, that method may be preferable. #[allow(clippy::type_complexity)] pub fn device_wait( &self, id: &DevId<'_>, options: DmOptions, ) -> DmResult<(DeviceInfo, Vec<(u64, u64, String, String)>)> { let mut hdr = options.to_ioctl_hdr(Some(id), DmFlags::DM_QUERY_INACTIVE_TABLE)?; trace!("Waiting on event for {}", id); let (hdr_out, data_out) = self.do_ioctl(dmi::DM_DEV_WAIT_CMD as u8, &mut hdr, None)?; let status = DM::parse_table_status(hdr.target_count, &data_out)?; Ok((hdr_out, status)) } /// Load targets for a device into its inactive table slot. /// /// `targets` is an array of `(sector_start, sector_length, type, params)`. /// /// `options` Valid flags: `DM_READ_ONLY`, `DM_SECURE_DATA` /// /// # Example /// /// ```no_run /// use devicemapper::{DM, DevId, DmName, DmOptions}; /// let dm = DM::new().unwrap(); /// /// // Create a 16MiB device (32768 512-byte sectors) that maps to /dev/sdb1 /// // starting 1MiB into sdb1 /// let table = vec![( /// 0, /// 32768, /// "linear".into(), /// "/dev/sdb1 2048".into() /// )]; /// /// let name = DmName::new("example-dev").expect("is valid DM name"); /// let id = DevId::Name(name); /// dm.table_load(&id, &table, DmOptions::default()).unwrap(); /// ``` pub fn table_load( &self, id: &DevId<'_>, targets: &[(u64, u64, String, String)], options: DmOptions, ) -> DmResult { let mut cursor = Cursor::new(Vec::new()); // Construct targets first, since we need to know how many & size // before initializing the header. for (sector_start, length, target_type, params) in targets { let mut targ = dmi::Struct_dm_target_spec { sector_start: *sector_start, length: *length, status: 0, ..Default::default() }; let dst = mut_slice_from_c_str(&mut targ.target_type); assert!( target_type.len() <= dst.len(), "TargetType max length = targ.target_type.len()" ); let _ = target_type .as_bytes() .read(dst) .map_err(|err| errors::Error::GeneralIo(err.to_string()))?; // Size of the largest single member of dm_target_spec let align_to_size = size_of::(); let aligned_len = align_to(params.len() + 1usize, align_to_size); targ.next = (size_of::() + aligned_len) as u32; cursor .write_all(slice_from_c_struct(&targ)) .map_err(|err| errors::Error::GeneralIo(err.to_string()))?; cursor .write_all(params.as_bytes()) .map_err(|err| errors::Error::GeneralIo(err.to_string()))?; let padding = aligned_len - params.len(); cursor .write_all(vec![0; padding].as_slice()) .map_err(|err| errors::Error::GeneralIo(err.to_string()))?; } let mut hdr = options.to_ioctl_hdr(Some(id), DmFlags::DM_READONLY | DmFlags::DM_SECURE_DATA)?; // io_ioctl() will set hdr.data_size but we must set target_count hdr.target_count = targets.len() as u32; // Flatten targets into a buf let data_in = cursor.into_inner(); trace!("Loading table \"{:?}\" for {}", targets, id); self.do_ioctl(dmi::DM_TABLE_LOAD_CMD as u8, &mut hdr, Some(&data_in)) .map(|(hdr, _)| hdr) } /// Clear the "inactive" table for a device. pub fn table_clear(&self, id: &DevId<'_>) -> DmResult { let mut hdr = DmOptions::default().to_ioctl_hdr(Some(id), DmFlags::empty())?; trace!("Clearing inactive table for {}", id); self.do_ioctl(dmi::DM_TABLE_CLEAR_CMD as u8, &mut hdr, None) .map(|(hdr, _)| hdr) } /// Query DM for which devices are referenced by the "active" /// table for this device. /// /// If DM_QUERY_INACTIVE_TABLE is set, instead return for the /// inactive table. /// /// Valid flags: DM_QUERY_INACTIVE_TABLE pub fn table_deps(&self, id: &DevId<'_>, options: DmOptions) -> DmResult> { let mut hdr = options.to_ioctl_hdr(Some(id), DmFlags::DM_QUERY_INACTIVE_TABLE)?; trace!("Querying dependencies for {}", id); let (_, data_out) = self.do_ioctl(dmi::DM_TABLE_DEPS_CMD as u8, &mut hdr, None)?; if data_out.is_empty() { Ok(vec![]) } else { let result = &data_out[..]; let target_deps = unsafe { &*(result.as_ptr() as *const dmi::Struct_dm_target_deps) }; let dev_slc = unsafe { slice::from_raw_parts( result[size_of::()..].as_ptr() as *const u64, target_deps.count as usize, ) }; // Note: The DM target_deps struct reserves 64 bits for each entry // but only 32 bits is used by kernel "huge" dev_t encoding. Ok(dev_slc .iter() .map(|d| Device::from_kdev_t(*d as u32)) .collect()) } } /// Parse a device's table. The table value is in buf, count indicates the /// expected number of lines. /// Trims trailing white space off final entry on each line. This /// canonicalization makes checking identity of tables easier. /// Postcondition: The length of the next to last entry in any tuple is /// no more than 16 characters. fn parse_table_status(count: u32, buf: &[u8]) -> DmResult> { let mut targets = Vec::new(); if !buf.is_empty() { let mut next_off = 0; for _ in 0..count { let result = &buf[next_off..]; let targ = unsafe { &*(result.as_ptr() as *const dmi::Struct_dm_target_spec) }; let target_type = str_from_c_str(&targ.target_type) .ok_or_else(|| { DmError::Dm( ErrorEnum::Invalid, "Could not convert target type to a String".to_string(), ) })? .to_string(); let params = str_from_byte_slice(&result[size_of::()..]) .ok_or_else(|| { DmError::Dm( ErrorEnum::Invalid, "Invalid DM target parameters returned from kernel".to_string(), ) })? .to_string(); targets.push((targ.sector_start, targ.length, target_type, params)); next_off = targ.next as usize; } } Ok(targets) } /// Return the status of all targets for a device's "active" /// table. /// /// Returns DeviceInfo and a Vec of (sector_start, sector_length, type, params). /// /// If DM_STATUS_TABLE flag is set, returns the current table value. Otherwise /// returns target-specific status information. /// /// If DM_NOFLUSH is set, retrieving the target-specific status information for /// targets with metadata will not cause a metadata write. /// /// If DM_QUERY_INACTIVE_TABLE is set, instead return the status of the /// inactive table. /// /// Valid flags: DM_NOFLUSH, DM_STATUS_TABLE, DM_QUERY_INACTIVE_TABLE /// /// # Example /// /// ```no_run /// use devicemapper::{DM, DevId, DmFlags, DmOptions, DmName}; /// let dm = DM::new().unwrap(); /// /// let name = DmName::new("example-dev").expect("is valid DM name"); /// let id = DevId::Name(name); /// let res = dm.table_status(&id, /// DmOptions::default().set_flags(DmFlags::DM_STATUS_TABLE)).unwrap(); /// println!("{:?} {:?}", res.0.name(), res.1); /// ``` #[allow(clippy::type_complexity)] pub fn table_status( &self, id: &DevId<'_>, options: DmOptions, ) -> DmResult<(DeviceInfo, Vec<(u64, u64, String, String)>)> { let mut hdr = options.to_ioctl_hdr( Some(id), DmFlags::DM_NOFLUSH | DmFlags::DM_STATUS_TABLE | DmFlags::DM_QUERY_INACTIVE_TABLE, )?; trace!("Retrieving table status for {}", id); let (hdr_out, data_out) = self.do_ioctl(dmi::DM_TABLE_STATUS_CMD as u8, &mut hdr, None)?; let status = DM::parse_table_status(hdr_out.target_count, &data_out)?; Ok((hdr_out, status)) } /// Returns a list of each loaded target type with its name, and /// version broken into major, minor, and patchlevel. #[cfg(devicemapper41supported)] pub fn list_versions(&self) -> DmResult> { let mut hdr = DmOptions::default().to_ioctl_hdr(None, DmFlags::empty())?; trace!("Listing loaded target versions"); let (_, data_out) = self.do_ioctl(dmi::DM_LIST_VERSIONS_CMD as u8, &mut hdr, None)?; let mut targets = Vec::new(); if !data_out.is_empty() { let mut result = &data_out[..]; loop { let tver = unsafe { &*(result.as_ptr() as *const dmi::Struct_dm_target_versions) }; let name = str_from_byte_slice(&result[size_of::()..]) .ok_or_else(|| { DmError::Dm( ErrorEnum::Invalid, "Invalid DM target name returned from kernel".to_string(), ) })? .to_string(); targets.push((name, tver.version[0], tver.version[1], tver.version[2])); if tver.next == 0 { break; } result = &result[tver.next as usize..]; } } Ok(targets) } /// Send a message to the device specified by id and the sector /// specified by sector. If sending to the whole device, set sector to /// None. #[cfg(devicemapper42supported)] pub fn target_msg( &self, id: &DevId<'_>, sector: Option, msg: &str, ) -> DmResult<(DeviceInfo, Option)> { let mut hdr = DmOptions::default().to_ioctl_hdr(Some(id), DmFlags::empty())?; let msg_struct = dmi::Struct_dm_target_msg { sector: sector.unwrap_or_default(), ..Default::default() }; let mut data_in = unsafe { let ptr = &msg_struct as *const dmi::Struct_dm_target_msg as *mut u8; slice::from_raw_parts(ptr, size_of::()).to_vec() }; data_in.extend(msg.as_bytes()); data_in.push(b'\0'); debug!("Sending target message \"{}\" to {}", msg, id); let (hdr_out, data_out) = self.do_ioctl(dmi::DM_TARGET_MSG_CMD as u8, &mut hdr, Some(&data_in))?; let output = if (hdr_out.flags().bits() & DmFlags::DM_DATA_OUT.bits()) > 0 { Some( str::from_utf8(&data_out[..data_out.len() - 1]) .map(|res| res.to_string()) .map_err(|_| { DmError::Dm( ErrorEnum::Invalid, "Could not convert output to a String".to_string(), ) })?, ) } else { None }; Ok((hdr_out, output)) } /// If DM is being used to poll for events, once it indicates readiness it /// will continue to do so until we rearm it, which is what this method /// does. #[cfg(devicemapper437supported)] pub fn arm_poll(&self) -> DmResult { let mut hdr = DmOptions::default().to_ioctl_hdr(None, DmFlags::empty())?; trace!("Issuing device-mapper arm poll command"); self.do_ioctl(dmi::DM_DEV_ARM_POLL_CMD as u8, &mut hdr, None) .map(|(hdr, _)| hdr) } } impl AsRawFd for DM { fn as_raw_fd(&self) -> RawFd { self.file.as_raw_fd() } } #[cfg(test)] mod tests { use crate::{ core::errors::Error, result::DmError, testing::{test_name, test_uuid}, }; use super::*; #[test] /// Test that some version can be obtained. fn sudo_test_version() { assert_matches!(DM::new().unwrap().version(), Ok(_)); } #[test] /// Test that versions for some targets can be obtained. fn sudo_test_versions() { assert!(!DM::new().unwrap().list_versions().unwrap().is_empty()); } #[test] /// Verify that if no devices have been created the list of test devices /// is empty. fn sudo_test_list_devices_empty() { assert!(DM::new().unwrap().list_test_devices().unwrap().is_empty()); } #[test] /// Verify that if one test device has been created, it will be the only /// test device listed. fn sudo_test_list_devices() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); dm.device_create(&name, None, DmOptions::default()).unwrap(); let devices = dm.list_test_devices().unwrap(); assert_eq!(devices.len(), 1); if dm.version().unwrap().1 >= 37 { assert_matches!(devices.first().expect("len is 1"), (nm, _, Some(0)) if nm == &name); } else { assert_matches!(devices.first().expect("len is 1"), (nm, _, None) if nm == &name); } dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } #[test] /// Test that device creation gives a device with the expected name. fn sudo_test_create() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); let result = dm.device_create(&name, None, DmOptions::default()).unwrap(); assert_eq!(result.name(), Some(&*name)); assert_eq!(result.uuid(), None); dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } #[test] /// Verify that creation with a UUID results in correct name and UUID. fn sudo_test_create_uuid() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); let uuid = test_uuid("example-363333333333333").expect("is valid DM uuid"); let result = dm .device_create(&name, Some(&uuid), DmOptions::default()) .unwrap(); assert_eq!(result.name(), Some(&*name)); assert_eq!(result.uuid(), Some(&*uuid)); dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } #[test] /// Verify that resetting uuid fails. fn sudo_test_rename_uuid() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); let uuid = test_uuid("example-363333333333333").expect("is valid DM uuid"); dm.device_create(&name, Some(&uuid), DmOptions::default()) .unwrap(); let new_uuid = test_uuid("example-9999999999").expect("is valid DM uuid"); assert_matches!( dm.device_rename(&name, &DevId::Uuid(&new_uuid)), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::EINVAL && op == dmi::DM_DEV_RENAME_CMD as u8 ); dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } #[test] /// Verify that resetting uuid to same uuid fails. /// Since a device with that UUID already exists, the UUID can not be used. fn sudo_test_rename_uuid_id() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); let uuid = test_uuid("example-363333333333333").expect("is valid DM uuid"); dm.device_create(&name, Some(&uuid), DmOptions::default()) .unwrap(); assert_matches!( dm.device_rename(&name, &DevId::Uuid(&uuid)), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::EBUSY && op == dmi::DM_DEV_RENAME_CMD as u8 ); dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } #[test] /// Verify that setting a new uuid succeeds. /// Note that the uuid is not set in the returned dev_info. fn sudo_test_set_uuid() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); dm.device_create(&name, None, DmOptions::default()).unwrap(); let uuid = test_uuid("example-363333333333333").expect("is valid DM uuid"); let result = dm.device_rename(&name, &DevId::Uuid(&uuid)).unwrap(); assert_eq!(result.uuid(), None); assert_eq!( dm.device_info(&DevId::Name(&name)).unwrap().uuid().unwrap(), &*uuid ); assert_matches!(dm.device_info(&DevId::Uuid(&uuid)), Ok(_)); dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } #[test] /// Test that device rename to same name fails. /// Since a device with that name already exists, the name can not be used. fn sudo_test_rename_id() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); dm.device_create(&name, None, DmOptions::default()).unwrap(); assert_matches!( dm.device_rename(&name, &DevId::Name(&name)), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::EBUSY && op == dmi::DM_DEV_RENAME_CMD as u8 ); dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } #[test] /// Test that device rename to different name works. /// Verify that the only test device in the list of devices is a device /// with the new name. fn sudo_test_rename() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); dm.device_create(&name, None, DmOptions::default()).unwrap(); let new_name = test_name("example-dev-2").expect("is valid DM name"); dm.device_rename(&name, &DevId::Name(&new_name)).unwrap(); assert_matches!( dm.device_info(&DevId::Name(&name)), Err(DmError::Core(Error::Ioctl(_, _, _, err))) if *err == nix::errno::Errno::ENXIO ); assert_matches!(dm.device_info(&DevId::Name(&new_name)), Ok(_)); let devices = dm.list_test_devices().unwrap(); assert_eq!(devices.len(), 1); if dm.version().unwrap().1 >= 37 { assert_matches!(devices.first().expect("len is 1"), (nm, _, Some(0)) if nm == &new_name); } else { assert_matches!(devices.first().expect("len is 1"), (nm, _, None) if nm == &new_name); } let third_name = test_name("example-dev-3").expect("is valid DM name"); dm.device_create(&third_name, None, DmOptions::default()) .unwrap(); assert_matches!( dm.device_rename(&new_name, &DevId::Name(&third_name)), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::EBUSY && op == dmi::DM_DEV_RENAME_CMD as u8 ); dm.device_remove(&DevId::Name(&third_name), DmOptions::default()) .unwrap(); dm.device_remove(&DevId::Name(&new_name), DmOptions::default()) .unwrap(); } #[test] /// Renaming a device that does not exist yields an error. fn sudo_test_rename_non_existent() { let new_name = test_name("new_name").expect("is valid DM name"); assert_matches!( DM::new().unwrap().device_rename( &test_name("old_name").expect("is valid DM name"), &DevId::Name(&new_name) ), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::ENXIO && op == dmi::DM_DEV_RENAME_CMD as u8 ); } #[test] /// Removing a device that does not exist yields an error. fn sudo_test_remove_non_existent() { assert_matches!( DM::new().unwrap().device_remove( &DevId::Name(&test_name("junk").expect("is valid DM name")), DmOptions::default() ), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::ENXIO && op == dmi::DM_DEV_REMOVE_CMD as u8 ); } #[test] /// A newly created device has no deps. fn sudo_test_empty_deps() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); dm.device_create(&name, None, DmOptions::default()).unwrap(); let deps = dm .table_deps(&DevId::Name(&name), DmOptions::default()) .unwrap(); assert!(deps.is_empty()); dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } #[test] /// Table status on a non-existent name should return an error. fn sudo_test_table_status_non_existent() { assert_matches!( DM::new().unwrap().table_status( &DevId::Name(&test_name("junk").expect("is valid DM name")), DmOptions::default() ), Err(DmError::Core(Error::Ioctl(_, _, _, err))) if *err == nix::errno::Errno::ENXIO ); } #[test] /// Table status on a non-existent name with TABLE_STATUS flag errors. fn sudo_test_table_status_non_existent_table() { let name = test_name("junk").expect("is valid DM name"); assert_matches!( DM::new().unwrap().table_status( &DevId::Name(&name), DmOptions::default().set_flags(DmFlags::DM_STATUS_TABLE) ), Err(DmError::Core(Error::Ioctl(_, _, _, err))) if *err == nix::errno::Errno::ENXIO ); } #[test] /// The table should have an entry for a newly created device. /// The device has no segments, so the second part of the info should /// be empty. /// The UUID of the returned info should be the device's UUID. fn sudo_test_table_status() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); let uuid = test_uuid("uuid").expect("is valid DM UUID"); dm.device_create(&name, Some(&uuid), DmOptions::default()) .unwrap(); let (hdr_out, status) = dm .table_status(&DevId::Name(&name), DmOptions::default()) .unwrap(); assert!(status.is_empty()); assert_eq!(hdr_out.uuid(), Some(&*uuid)); dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } #[test] /// Verify that getting the status of a non-existent device specified /// by name returns an error. fn sudo_status_no_name() { let name = test_name("example_dev").expect("is valid DM name"); assert_matches!( DM::new().unwrap().device_info(&DevId::Name(&name)), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::ENXIO && op == dmi::DM_DEV_STATUS_CMD as u8 ); } #[test] /// Verify that creating a device with the same name twice fails. /// Verify that creating a device with the same uuid twice fails. fn sudo_test_double_creation() { let dm = DM::new().unwrap(); let name = test_name("example-dev").expect("is valid DM name"); let uuid = test_uuid("uuid").expect("is valid DM UUID"); let name_alt = test_name("name-alt").expect("is valid DM name"); let uuid_alt = test_uuid("uuid-alt").expect("is valid DM UUID"); dm.device_create(&name, Some(&uuid), DmOptions::default()) .unwrap(); assert_matches!( dm.device_create(&name, Some(&uuid), DmOptions::default()), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::EBUSY && op == dmi::DM_DEV_CREATE_CMD as u8 ); assert_matches!( dm.device_create(&name, None, DmOptions::default()), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::EBUSY && op == dmi::DM_DEV_CREATE_CMD as u8 ); assert_matches!( dm.device_create(&name, Some(&uuid_alt), DmOptions::default()), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::EBUSY && op == dmi::DM_DEV_CREATE_CMD as u8 ); assert_matches!( dm.device_create(&name_alt, Some(&uuid), DmOptions::default()), Err(DmError::Core(Error::Ioctl(op, _, _, err))) if *err == nix::errno::Errno::EBUSY && op == dmi::DM_DEV_CREATE_CMD as u8 ); dm.device_remove(&DevId::Name(&name), DmOptions::default()) .unwrap(); } } devicemapper-0.34.4/src/core/dm_flags.rs000064400000000000000000000077001046102023000162250ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::core::dm_ioctl as dmi; bitflags! { /// Flags used by devicemapper. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct DmFlags: dmi::__u32 { /// In: Device should be read-only. /// Out: Device is read-only. const DM_READONLY = dmi::DM_READONLY_FLAG; /// In: Device should be suspended. /// Out: Device is suspended. const DM_SUSPEND = dmi::DM_SUSPEND_FLAG; /// In: Use passed-in minor number. const DM_PERSISTENT_DEV = dmi::DM_PERSISTENT_DEV_FLAG; /// In: STATUS command returns table info instead of status. const DM_STATUS_TABLE = dmi::DM_STATUS_TABLE_FLAG; /// Out: Active table is present. const DM_ACTIVE_PRESENT = dmi::DM_ACTIVE_PRESENT_FLAG; /// Out: Inactive table is present. const DM_INACTIVE_PRESENT = dmi::DM_INACTIVE_PRESENT_FLAG; /// Out: Passed-in buffer was too small. const DM_BUFFER_FULL = dmi::DM_BUFFER_FULL_FLAG; /// Obsolete. const DM_SKIP_BDGET = dmi::DM_SKIP_BDGET_FLAG; /// In: Avoid freezing filesystem when suspending. const DM_SKIP_LOCKFS = dmi::DM_SKIP_LOCKFS_FLAG; /// In: Suspend without flushing queued I/Os. const DM_NOFLUSH = dmi::DM_NOFLUSH_FLAG; /// In: Query inactive table instead of active. const DM_QUERY_INACTIVE_TABLE = dmi::DM_QUERY_INACTIVE_TABLE_FLAG; /// Out: A uevent was generated, the caller may need to wait for it. const DM_UEVENT_GENERATED = dmi::DM_UEVENT_GENERATED_FLAG; /// In: Rename affects UUID field, not name field. const DM_UUID = dmi::DM_UUID_FLAG; /// In: All buffers are wiped after use. Use when handling crypto keys. const DM_SECURE_DATA = dmi::DM_SECURE_DATA_FLAG; /// Out: A message generated output data. const DM_DATA_OUT = dmi::DM_DATA_OUT_FLAG; /// In: Do not remove in-use devices. /// Out: Device scheduled to be removed when closed. const DM_DEFERRED_REMOVE = dmi::DM_DEFERRED_REMOVE; /// Out: Device is suspended internally. const DM_INTERNAL_SUSPEND = dmi::DM_INTERNAL_SUSPEND_FLAG; } } bitflags! { /// Flags used by devicemapper, see: /// https://sourceware.org/git/?p=lvm2.git;a=blob;f=libdm/libdevmapper.h#l3627 /// for complete information about the meaning of the flags. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct DmUdevFlags: u32 { /// Disables basic device-mapper udev rules that create symlinks in /dev/ /// directory. const DM_UDEV_DISABLE_DM_RULES_FLAG = dmi::DM_UDEV_DISABLE_DM_RULES_FLAG; /// Disable subsystem udev rules, but allow general DM udev rules to run. const DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG = dmi::DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG; /// Disable dm udev rules which create symlinks in /dev/disk/* directory. const DM_UDEV_DISABLE_DISK_RULES_FLAG = dmi::DM_UDEV_DISABLE_DISK_RULES_FLAG; /// Disable all rules that are not general dm nor subsystem related. const DM_UDEV_DISABLE_OTHER_RULES_FLAG = dmi::DM_UDEV_DISABLE_OTHER_RULES_FLAG; /// Instruct udev rules to give lower priority to the device. const DM_UDEV_LOW_PRIORITY_FLAG = dmi::DM_UDEV_LOW_PRIORITY_FLAG; /// Disable libdevmapper's node management. const DM_UDEV_DISABLE_LIBRARY_FALLBACK = dmi::DM_UDEV_DISABLE_LIBRARY_FALLBACK; /// Automatically appended to all IOCTL calls issues by libdevmapper for generating /// udev uevents. const DM_UDEV_PRIMARY_SOURCE_FLAG = dmi::DM_UDEV_PRIMARY_SOURCE_FLAG; } } devicemapper-0.34.4/src/core/dm_ioctl.rs000064400000000000000000000044671046102023000162520ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::collections::HashMap; use once_cell::sync::Lazy; pub use devicemapper_sys::{ dm_ioctl as Struct_dm_ioctl, dm_name_list as Struct_dm_name_list, dm_target_deps as Struct_dm_target_deps, dm_target_msg as Struct_dm_target_msg, dm_target_spec as Struct_dm_target_spec, dm_target_versions as Struct_dm_target_versions, *, }; // Map device-mapper ioctl commands to the minimum ioctl interface version // required. The mapping is based on the _cmd_data_v4 table defined in // libdm/ioctl/libdm-iface.c in the lvm2/libdevmapper sources. static IOCTL_VERSIONS: Lazy> = Lazy::new(|| { HashMap::from([ (DM_VERSION_CMD, (4, 0, 0)), (DM_REMOVE_ALL_CMD, (4, 0, 0)), (DM_LIST_DEVICES_CMD, (4, 0, 0)), (DM_DEV_CREATE_CMD, (4, 0, 0)), (DM_DEV_REMOVE_CMD, (4, 0, 0)), (DM_DEV_RENAME_CMD, (4, 0, 0)), (DM_DEV_SUSPEND_CMD, (4, 0, 0)), (DM_DEV_STATUS_CMD, (4, 0, 0)), (DM_DEV_WAIT_CMD, (4, 0, 0)), (DM_TABLE_LOAD_CMD, (4, 0, 0)), (DM_TABLE_CLEAR_CMD, (4, 0, 0)), (DM_TABLE_DEPS_CMD, (4, 0, 0)), (DM_TABLE_STATUS_CMD, (4, 0, 0)), #[cfg(devicemapper41supported)] (DM_LIST_VERSIONS_CMD, (4, 1, 0)), #[cfg(devicemapper42supported)] (DM_TARGET_MSG_CMD, (4, 2, 0)), #[cfg(devicemapper46supported)] (DM_DEV_SET_GEOMETRY_CMD, (4, 6, 0)), // libdevmapper sets DM_DEV_ARM_POLL to (4, 36, 0) however the command was // added after 4.36.0: depend on 4.37 to reliably access ARM_POLL. #[cfg(devicemapper437supported)] (DM_DEV_ARM_POLL_CMD, (4, 37, 0)), #[cfg(devicemapper441supported)] (DM_GET_TARGET_VERSION_CMD, (4, 41, 0)), ]) }); // Map device-mapper ioctl commands to (major, minor, patchlevel) // tuple specifying the required kernel ioctl interface version. pub(crate) fn ioctl_to_version(ioctl: u8) -> (u32, u32, u32) { let ioctl = &(ioctl as u32); if IOCTL_VERSIONS.contains_key(ioctl) { IOCTL_VERSIONS[ioctl] } else { unreachable!("Unknown device-mapper ioctl command: {}", ioctl); } } devicemapper-0.34.4/src/core/dm_options.rs000064400000000000000000000027141046102023000166240ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::core::dm_flags::{DmFlags, DmUdevFlags}; /// Encapsulates options for device mapper calls #[derive(Clone, Copy, Debug, Default)] pub struct DmOptions { flags: DmFlags, udev_flags: DmUdevFlags, } impl DmOptions { /// Set the DmFlags value for self. Replace the previous value. /// Consumes self. pub fn set_flags(mut self, flags: DmFlags) -> DmOptions { self.flags = flags; self } /// Set the DmUdevFlags value for self. Replace the previous value. /// Consumes self. pub fn set_udev_flags(mut self, udev_flags: DmUdevFlags) -> DmOptions { self.udev_flags = udev_flags; self } /// Retrieve the flags value pub fn flags(&self) -> DmFlags { self.flags } /// Retrieve the cookie flags (used for input in upper 16 bits of event_nr header field). pub fn udev_flags(&self) -> DmUdevFlags { self.udev_flags } /// Set default udev flags for a private (internal) device. pub fn private() -> DmOptions { DmOptions::default().set_udev_flags( DmUdevFlags::DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG | DmUdevFlags::DM_UDEV_DISABLE_DISK_RULES_FLAG | DmUdevFlags::DM_UDEV_DISABLE_OTHER_RULES_FLAG, ) } } devicemapper-0.34.4/src/core/dm_udev_sync.rs000064400000000000000000000460231046102023000171310ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{core::dm_ioctl as dmi, result::DmResult}; pub trait UdevSyncAction { fn begin(hdr: &mut dmi::Struct_dm_ioctl, ioctl: u8) -> DmResult; fn end(self, flags: u32) -> DmResult<()>; fn cancel(self); fn is_active(&self) -> bool; } #[cfg(not(target_os = "android"))] pub mod sync_semaphore { use nix::libc::{ c_int, key_t, sembuf, semctl as libc_semctl, semget as libc_semget, semop as libc_semop, EEXIST, ENOMEM, ENOSPC, // These don't exist in the Linux libc crate // GETVAL, SETVAL, SEM_INFO, IPC_CREAT, IPC_EXCL, IPC_NOWAIT, IPC_RMID, }; use nix::unistd::{access, AccessFlags}; use once_cell::sync::Lazy; use rand::Rng; use retry::{delay::NoDelay, retry, OperationResult}; use std::{io, path::Path}; use crate::core::sysvsem::seminfo; use crate::{ core::dm_flags::{DmFlags, DmUdevFlags}, core::sysvsem::{semun, GETVAL, SEM_INFO, SETVAL}, core::{dm_ioctl as dmi, errors}, result::{DmError, DmResult}, }; use super::UdevSyncAction; // Mode for cookie semaphore creation const COOKIE_MODE: i32 = 0o600; impl DmError { fn udev_sync_error_from_os() -> DmError { DmError::Core(errors::Error::UdevSync( io::Error::last_os_error().to_string(), )) } } static SYSV_SEM_SUPPORTED: Lazy = Lazy::new(sysv_sem_supported); /// Test whether the system is configured for SysV semaphore support. fn sysv_sem_supported() -> bool { let mut info: seminfo = Default::default(); let arg = semun { __buf: &mut info }; match semctl(0, 0, SEM_INFO, Some(arg)) { Ok(maxid) if maxid < 0 => { warn!(concat!( "Kernel not configured for System V IPC semaphores.", "Disabling udev notifications." )); false } Err(err) => { error!( concat!( "Error retrieving System V semaphore limits: {}.", "Disabling udev notifications." ), err ); false } Ok(_) => { if info.semmsl > 0 && info.semmni > 0 && info.semmns > 0 { if info.semmsl < 1000 || info.semmni < 1000 || info.semmns < 1000 { warn!(concat!( "Low System V IPC semaphore limits detected: consider ", "increasing values in /proc/sys/kernel/sem to avoid exhaustion." )); } true } else { false } } } } const UDEV_SOCKET_PATH: &str = "/run/udev/control"; fn udev_running() -> bool { matches!( access(Path::new(UDEV_SOCKET_PATH), AccessFlags::F_OK), Ok(()) ) } /// Allocate or retrieve a SysV semaphore set identifier fn semget(key: i32, nsems: i32, semflg: i32) -> Result { let semid = unsafe { libc_semget(key as key_t, nsems as c_int, semflg as c_int) }; match semid { i if i < 0 => Err(io::Error::last_os_error()), _ => Ok(semid), } } fn semctl_cmd_allowed(cmd: i32) -> Result<(), std::io::Error> { match cmd { IPC_RMID | GETVAL | SETVAL | SEM_INFO => Ok(()), _ => Err(io::Error::from(io::ErrorKind::Unsupported)), } } /// SysV semaphore set control operations fn semctl( semid: i32, semnum: i32, cmd: i32, semun: Option, ) -> Result { semctl_cmd_allowed(cmd)?; let semun = semun.unwrap_or_default(); let r = unsafe { libc_semctl(semid as c_int, semnum as c_int, cmd as c_int, semun) }; match r { i if i < 0 => Err(io::Error::last_os_error()), _ => Ok(r), } } /// Attempt to generate a unique, non-zero SysV IPC key and allocate a semaphore /// set for notifications. fn generate_semaphore_cookie() -> OperationResult<(u32, i32), std::io::Error> { let mut base_cookie = 0u16; while base_cookie == 0 { base_cookie = rand::thread_rng().gen::(); } let cookie = dmi::DM_COOKIE_MAGIC << dmi::DM_UDEV_FLAGS_SHIFT | base_cookie as u32; match semget(cookie as i32, 1, COOKIE_MODE | IPC_CREAT | IPC_EXCL) { Ok(semid) => OperationResult::Ok((cookie, semid)), Err(err) => match err.raw_os_error() { Some(ENOMEM) => OperationResult::Err(err), Some(ENOSPC) => OperationResult::Err(err), Some(EEXIST) => OperationResult::Retry(err), _ => OperationResult::Err(err), }, } } /// Create a new, unique udev notification semaphore and return the cookie /// value and semid. /// /// This function will attempt to allocate a new notification semaphore and sets /// the initial count to the 1. This count will be decremented by udev once rule /// processing for the transaction is complete. fn notify_sem_create() -> DmResult<(u32, i32)> { let (cookie, semid) = match retry(NoDelay.take(4), generate_semaphore_cookie) { Ok((cookie, semid)) => (cookie, semid), Err(err) => { error!("Failed to generate udev notification semaphore: {}", err); return Err(DmError::Core(errors::Error::UdevSync(err.to_string()))); } }; let sem_arg: semun = semun { val: 1 }; if let Err(err) = semctl(semid, 0, SETVAL, Some(sem_arg)) { error!("Failed to initialize udev notification semaphore: {}", err); if let Err(err2) = notify_sem_destroy(cookie, semid) { error!("Failed to clean up udev notification semaphore: {}", err2); } return Err(DmError::Core(errors::Error::UdevSync(err.to_string()))); } match semctl(semid, 0, GETVAL, None) { Ok(1) => Ok((cookie, semid)), _ => { error!( "Initialization of udev notification semaphore returned inconsistent value." ); Err(DmError::udev_sync_error_from_os()) } } } /// Destroy the notification semaphore identified by semid. /// /// Remove the SysV semaphore set identified by the SysV IPC ID semid and /// IPC key cookie. This removes the SysV IPC ID identified by the cookie /// value and should be called following completion or cancelation of a /// notification semaphore. fn notify_sem_destroy(cookie: u32, semid: i32) -> DmResult<()> { if let Err(err) = semctl(semid, 0, IPC_RMID, None) { error!( "Failed to remove udev synchronization semaphore {} for cookie {}", semid, cookie ); return Err(DmError::Core(errors::Error::UdevSync(err.to_string()))); }; Ok(()) } /// Increment the semaphore identified by SysV IPC ID semid. fn notify_sem_inc(cookie: u32, semid: i32) -> DmResult<()> { // DM protocol always uses the 0th semaphore in the set identified by semid let mut sb = sembuf { sem_num: 0, sem_op: 1, sem_flg: 0, }; let r = unsafe { libc_semop(semid, &mut sb, 1) }; match r { i if i < 0 => { error!( "Failed to increment udev synchronization semaphore {} for cookie {}", semid, cookie ); Err(DmError::udev_sync_error_from_os()) } _ => Ok(()), } } /// Decrement the semaphore identified by SysV IPC ID semid. fn notify_sem_dec(cookie: u32, semid: i32) -> DmResult<()> { // DM protocol always uses the 0th semaphore in the set identified by semid let mut sb = sembuf { sem_num: 0, sem_op: -1, sem_flg: IPC_NOWAIT as i16, }; let r = unsafe { libc_semop(semid, &mut sb, 1) }; match r { i if i < 0 => { error!( "Failed to decrement udev synchronization semaphore {} for cookie {}", semid, cookie ); Err(DmError::udev_sync_error_from_os()) } _ => Ok(()), } } /// Wait for completion of notification semaphore identified by SysV IPC ID semid. /// /// This function blocks until the value of the first semaphore in the set /// identified by semid reaches zero (normally as a result of the dmsetup /// udev_complete invoked at the end of udev rule processing). fn notify_sem_wait(cookie: u32, semid: i32) -> DmResult<()> { if let Err(err) = notify_sem_dec(cookie, semid) { error!( concat!( "Failed to set initial state for notification ", "semaphore identified by cookie value {}: {}" ), cookie, err ); if let Err(err2) = notify_sem_destroy(cookie, semid) { error!("Failed to clean up udev notification semaphore: {}", err2); } } let mut sb = sembuf { sem_num: 0, sem_op: 0, sem_flg: 0, }; let r = unsafe { libc_semop(semid, &mut sb, 1) }; match r { i if i < 0 => { error!( "Failed to wait on notification semaphore {} for cookie {}", semid, cookie ); Err(DmError::udev_sync_error_from_os()) } _ => Ok(()), } } #[derive(Debug)] pub struct UdevSync { cookie: u32, semid: Option, } impl UdevSyncAction for UdevSync { /// Begin UdevSync notification transaction. /// /// Allocate a SysV semaphore according to the device-mapper udev cookie /// protocol and set the initial state of the semaphore counter. fn begin(hdr: &mut dmi::Struct_dm_ioctl, ioctl: u8) -> DmResult { if !udev_running() { return Err(DmError::Core(errors::Error::UdevSync( "Udev daemon is not running: unable to create devices.".to_string(), ))); } match ioctl as u32 { dmi::DM_DEV_REMOVE_CMD | dmi::DM_DEV_RENAME_CMD | dmi::DM_DEV_SUSPEND_CMD if *SYSV_SEM_SUPPORTED && (hdr.flags & DmFlags::DM_SUSPEND.bits()) == 0 => {} _ => { return Ok(UdevSync { cookie: 0, semid: None, }); } }; let (base_cookie, semid) = notify_sem_create()?; // Encode the primary source flag and the random base cookie value into // the header event_nr input field. hdr.event_nr |= (DmUdevFlags::DM_UDEV_PRIMARY_SOURCE_FLAG.bits() << dmi::DM_UDEV_FLAGS_SHIFT) | (base_cookie & !dmi::DM_UDEV_FLAGS_MASK); debug!( "Created UdevSync {{ cookie: {}, semid: {} }}", hdr.event_nr, semid ); if let Err(err) = notify_sem_inc(hdr.event_nr, semid) { error!( "Failed to set udev notification semaphore initial state: {}", err ); if let Err(err2) = notify_sem_destroy(hdr.event_nr, semid) { error!("Failed to clean up udev notification semaphore: {}", err2); } return Err(err); } Ok(UdevSync { cookie: hdr.event_nr, semid: Some(semid), }) } /// End UdevSync notification transaction. /// /// Wait for notification from the udev daemon on the semaphore owned by /// this UdevSync instance and destroy the semaphore on success. fn end(self, flags: u32) -> DmResult<()> { if self.is_active() { let semid = self.semid.expect("active UdevSync must have valid semid"); if (flags & DmFlags::DM_UEVENT_GENERATED.bits()) == 0 { if let Err(err) = notify_sem_dec(self.cookie, semid) { error!("Failed to clear notification semaphore state: {}", err); if let Err(err2) = notify_sem_destroy(self.cookie, semid) { error!("Failed to clean up notification semaphore: {}", err2); } return Err(err); } } trace!("Waiting on {:?}", self); notify_sem_wait(self.cookie, semid)?; trace!("Destroying {:?}", self); if let Err(err) = notify_sem_destroy(self.cookie, semid) { error!("Failed to clean up notification semaphore: {}", err); } } Ok(()) } /// Cancel an in-progress UdevSync notification transaction. /// /// Destroy the notification semaphore owned by this UdevSync instance /// without waiting for completion. fn cancel(self) { if self.is_active() { let semid = self.semid.expect("active UdevSync must have valid semid"); trace!("Canceling {:?}", self); if let Err(err) = notify_sem_destroy(self.cookie, semid) { error!("Failed to clean up notification semaphore: {}", err); } } } /// Test whether this UdevSync instance has an active notification semaphore. fn is_active(&self) -> bool { self.cookie != 0 && self.semid.is_some() } } #[cfg(test)] mod tests { use super::*; use crate::core::dm_flags::DmUdevFlags; // SysV IPC key value for testing ("DMRS" in ASCII characters) const IPC_TEST_KEY: i32 = 0x444d5253; #[test] fn test_semget_invalid_nsems() { assert!(semget(0, -1, 0).is_err()); } #[test] fn test_semget_create_destroy() { assert_matches!(semget(IPC_TEST_KEY, 1, IPC_CREAT | IPC_EXCL), Ok(semid) => assert!(semctl(semid, 0, IPC_RMID, None).is_ok())); } #[test] fn test_notify_sem_create_destroy() { assert_matches!(notify_sem_create(), Ok((cookie, semid)) => assert!(notify_sem_destroy(cookie, semid).is_ok())); } #[test] fn test_udevsync_non_primary_source() { let mut hdr: dmi::Struct_dm_ioctl = devicemapper_sys::dm_ioctl { ..Default::default() }; let sync = UdevSync::begin(&mut hdr, dmi::DM_TABLE_STATUS_CMD as u8).unwrap(); assert_eq!(sync.cookie, 0); assert_eq!(sync.semid, None); assert_eq!(hdr.event_nr, 0); assert!(sync.end(DmFlags::empty().bits()).is_ok()); } #[test] fn test_udevsync_non_primary_source_cancel() { let mut hdr: dmi::Struct_dm_ioctl = devicemapper_sys::dm_ioctl { ..Default::default() }; let sync = UdevSync::begin(&mut hdr, dmi::DM_TABLE_STATUS_CMD as u8).unwrap(); assert_eq!(sync.cookie, 0); assert_eq!(sync.semid, None); assert_eq!(hdr.event_nr, 0); sync.cancel(); } #[test] fn test_udevsync_primary_source_end() { let mut hdr: dmi::Struct_dm_ioctl = devicemapper_sys::dm_ioctl { ..Default::default() }; let sync = UdevSync::begin(&mut hdr, dmi::DM_DEV_REMOVE_CMD as u8).unwrap(); assert_ne!((sync.cookie & !dmi::DM_UDEV_FLAGS_MASK), 0); assert!(sync.semid.unwrap() >= 0); assert!(notify_sem_dec(sync.cookie, sync.semid.unwrap()).is_ok()); assert_eq!( (hdr.event_nr >> dmi::DM_UDEV_FLAGS_SHIFT) & DmUdevFlags::DM_UDEV_PRIMARY_SOURCE_FLAG.bits(), DmUdevFlags::DM_UDEV_PRIMARY_SOURCE_FLAG.bits() ); assert!(sync.end(DmFlags::DM_UEVENT_GENERATED.bits()).is_ok()); } #[test] fn test_udevsync_primary_source_cancel() { let mut hdr: dmi::Struct_dm_ioctl = devicemapper_sys::dm_ioctl { ..Default::default() }; let sync = UdevSync::begin(&mut hdr, dmi::DM_DEV_REMOVE_CMD as u8).unwrap(); assert_ne!((sync.cookie & !dmi::DM_UDEV_FLAGS_MASK), 0); assert!(sync.semid.unwrap() >= 0); assert_eq!( (hdr.event_nr >> dmi::DM_UDEV_FLAGS_SHIFT) & DmUdevFlags::DM_UDEV_PRIMARY_SOURCE_FLAG.bits(), DmUdevFlags::DM_UDEV_PRIMARY_SOURCE_FLAG.bits() ); sync.cancel(); } #[test] fn test_udevsync_primary_source_end_no_uevent() { let mut hdr: dmi::Struct_dm_ioctl = devicemapper_sys::dm_ioctl { ..Default::default() }; let sync = UdevSync::begin(&mut hdr, dmi::DM_DEV_REMOVE_CMD as u8).unwrap(); assert_ne!((sync.cookie & !dmi::DM_UDEV_FLAGS_MASK), 0); assert!(sync.semid.unwrap() >= 0); assert_eq!( (hdr.event_nr >> dmi::DM_UDEV_FLAGS_SHIFT) & DmUdevFlags::DM_UDEV_PRIMARY_SOURCE_FLAG.bits(), DmUdevFlags::DM_UDEV_PRIMARY_SOURCE_FLAG.bits() ); assert!(sync.end(DmFlags::empty().bits()).is_ok()); } } } #[cfg(target_os = "android")] pub mod sync_noop { use super::UdevSyncAction; use crate::{core::dm_ioctl as dmi, result::DmResult}; #[derive(Debug)] pub struct UdevSync { cookie: u32, semid: Option, } impl UdevSyncAction for UdevSync { fn begin(hdr: &mut dmi::Struct_dm_ioctl, ioctl: u8) -> DmResult { debug!("Created noop UdevSync {{ cookie: {}, semid: {} }}", 0, -1); Ok(UdevSync { cookie: 0, semid: None, }) } fn end(self, _flags: u32) -> DmResult<()> { trace!("Destroying noop {:?}", self); Ok(()) } fn cancel(self) { trace!("Canceling noop {:?}", self); } fn is_active(&self) -> bool { false } } } #[cfg(target_os = "android")] pub use self::sync_noop::UdevSync; #[cfg(not(target_os = "android"))] pub use self::sync_semaphore::UdevSync; devicemapper-0.34.4/src/core/errors.rs000064400000000000000000000056341046102023000157710ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. /*! Definition for low level error class for core methods !*/ use std::{self, path::PathBuf}; use crate::core::deviceinfo::DeviceInfo; #[derive(Clone, Debug)] /// Internal error for low-level devicemapper operations pub enum Error { /// An error returned on failure to create a devicemapper context ContextInit(String), /// This is a generic error that can be returned when a method /// receives an invalid argument. Ideally, the argument should be /// invalid in itself, i.e., it should not be made invalid by some /// part of the program state or the environment. InvalidArgument(String), /// An error returned exclusively by DM methods. /// This error is initiated in DM::do_ioctl and returned by /// numerous wrapper methods. Ioctl( u8, Option>, Option>, Box, ), /// An error returned when the response exceeds the maximum possible /// size of the ioctl buffer. IoctlResultTooLarge, /// An error returned on failure to get metadata for a device MetadataIo(PathBuf, String), /// An error returned on general IO failure GeneralIo(String), /// An error synchronizing with udev UdevSync(String), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::ContextInit(err) => { write!(f, "DM context not initialized due to IO error: {err}") } Error::InvalidArgument(err) => write!(f, "invalid argument: {err}"), Error::Ioctl(op, hdr_in, hdr_out, err) => write!( f, "low-level ioctl error due to nix error; ioctl number: {op}, input header: {hdr_in:?}, header result: {hdr_out:?}, error: {err}" ), Error::IoctlResultTooLarge => write!( f, "ioctl result too large for maximum buffer size: {} bytes", u32::MAX ), Error::MetadataIo(device_path, err) => write!( f, "failed to stat metadata for device at {} due to IO error: {}", device_path.display(), err ), Error::GeneralIo(err) => { write!(f, "failed to perform operation due to IO error: {err}") } Error::UdevSync(err) => { write!(f, "failed to perform udev sync operation: {}", err) } } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::Ioctl(_, _, _, err) => Some(err), _ => None, } } } devicemapper-0.34.4/src/core/mod.rs000064400000000000000000000012101046102023000152160ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Modules that support handling of devicemapper ioctls at a low-level. mod device; mod deviceinfo; mod dm; mod dm_flags; mod dm_ioctl; mod dm_options; mod dm_udev_sync; pub mod errors; mod sysvsem; mod types; mod util; pub use self::{ device::{devnode_to_devno, Device}, deviceinfo::DeviceInfo, dm::DM, dm_flags::{DmFlags, DmUdevFlags}, dm_options::DmOptions, types::{DevId, DmName, DmNameBuf, DmUuid, DmUuidBuf}, }; devicemapper-0.34.4/src/core/sysvsem.rs000064400000000000000000000004211046102023000161530ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. pub use devicemapper_sys::{seminfo, semun, GETVAL, SEM_INFO, SETVAL}; devicemapper-0.34.4/src/core/types.rs000064400000000000000000000032541046102023000156150ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{fmt, ops::Deref}; use crate::{ core::{ dm_ioctl::{DM_NAME_LEN, DM_UUID_LEN}, errors, }, result::DmError, }; // Casts yield correct results since values generated by bindgen from // dm-ioctl.h are certainly small enough to fit in usize. const DM_NAME_LEN_USIZE: usize = DM_NAME_LEN as usize; const DM_UUID_LEN_USIZE: usize = DM_UUID_LEN as usize; /// An error function to construct an error when creating a new string id. fn err_func(err_msg: &str) -> DmError { DmError::Core(errors::Error::InvalidArgument(err_msg.into())) } // A devicemapper name. Really just a string, but also the argument type of // DevId::Name. Used in function arguments to indicate that the function // takes only a name, not a devicemapper uuid. str_id!(DmName, DmNameBuf, DM_NAME_LEN_USIZE, err_func); // A devicemapper uuid. A devicemapper uuid has a devicemapper-specific // format. str_id!(DmUuid, DmUuidBuf, DM_UUID_LEN_USIZE, err_func); /// Used as a parameter for functions that take either a Device name /// or a Device UUID. #[derive(Debug, PartialEq, Eq)] pub enum DevId<'a> { /// The parameter is the device's name Name(&'a DmName), /// The parameter is the device's devicemapper uuid Uuid(&'a DmUuid), } impl<'a> fmt::Display for DevId<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { DevId::Name(name) => write!(f, "{name}"), DevId::Uuid(uuid) => write!(f, "{uuid}"), } } } devicemapper-0.34.4/src/core/util.rs000064400000000000000000000033721046102023000154270ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{mem::size_of, slice, str}; use nix::libc::c_char; /// The smallest number divisible by `align_to` and at least `num`. /// Precondition: `align_to` is a power of 2. /// Precondition: `num` + `align_to` < usize::MAX + 1. #[inline] pub fn align_to(num: usize, align_to: usize) -> usize { let agn = align_to - 1; (num + agn) & !agn } /// Convert from a &[c_char] to a &[u8]. pub fn byte_slice_from_c_str(c_str: &[c_char]) -> &[u8] { unsafe { slice::from_raw_parts(c_str as *const _ as *const u8, c_str.len()) } } /// Return a String parsed from the C string up to the first \0, or None pub fn str_from_c_str(slc: &[c_char]) -> Option<&str> { let slc = byte_slice_from_c_str(slc); str_from_byte_slice(slc) } /// Return a String parsed from the byte slice up to the first \0, or None pub fn str_from_byte_slice(slc: &[u8]) -> Option<&str> { slc.iter() .position(|c| *c == b'\0') .and_then(|i| str::from_utf8(&slc[..i]).ok()) } /// Return a mutable slice from the mutable C string provided as input pub fn mut_slice_from_c_str(c_str: &mut [c_char]) -> &mut [u8] { unsafe { slice::from_raw_parts_mut(c_str as *mut _ as *mut u8, c_str.len()) } } /// Convert the C struct into a properly-sized byte slice pub fn slice_from_c_struct(strct: &T) -> &[u8] { unsafe { slice::from_raw_parts(strct as *const _ as *const u8, size_of::()) } } /// Convert the byte slice into a properly sized C string reference pub fn c_struct_from_slice(slice: &[u8]) -> Option<&T> { unsafe { (slice as *const _ as *const T).as_ref() } } devicemapper-0.34.4/src/id_macros.rs000064400000000000000000000122611046102023000154570ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // A module to contain functionality used for generating DM ids which // are restricted in length and format by devicemapper. // Evaluates to an error string if the value does not match the requirements. macro_rules! str_check { ($value:expr, $max_allowed_chars:expr) => {{ let value = $value; let max_allowed_chars = $max_allowed_chars; if !value.is_ascii() { Some(format!("value {} has some non-ascii characters", value)) } else { let num_chars = value.len(); if num_chars == 0 { Some("value has zero characters".into()) } else if num_chars > max_allowed_chars { Some(format!( "value {} has {} chars which is greater than maximum allowed {}", value, num_chars, max_allowed_chars )) } else { None } } }}; } /// Define borrowed and owned versions of string types that guarantee /// conformance to DM restrictions, such as maximum length. // This implementation follows the example of Path/PathBuf as closely as // possible. macro_rules! str_id { ($B:ident, $O:ident, $MAX:ident, $err_func:ident) => { /// The borrowed version of the DM identifier. #[derive(Debug, PartialEq, Eq, Hash)] pub struct $B { inner: str, } /// The owned version of the DM identifier. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct $O { inner: String, } impl $B { /// Create a new borrowed identifier from a `&str`. pub fn new(value: &str) -> $crate::result::DmResult<&$B> { if let Some(err_msg) = str_check!(value, $MAX - 1) { return Err($err_func(&err_msg)); } Ok(unsafe { &*(value as *const str as *const $B) }) } /// Get the inner value as bytes pub fn as_bytes(&self) -> &[u8] { self.inner.as_bytes() } } impl ToOwned for $B { type Owned = $O; fn to_owned(&self) -> $O { $O { inner: self.inner.to_owned(), } } } impl std::fmt::Display for $B { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", &self.inner) } } impl $O { /// Construct a new owned identifier. pub fn new(value: String) -> $crate::result::DmResult<$O> { if let Some(err_msg) = str_check!(&value, $MAX - 1) { return Err($err_func(&err_msg)); } Ok($O { inner: value }) } } impl AsRef<$B> for $O { fn as_ref(&self) -> &$B { self } } impl std::borrow::Borrow<$B> for $O { fn borrow(&self) -> &$B { self.deref() } } impl std::ops::Deref for $O { type Target = $B; fn deref(&self) -> &$B { $B::new(&self.inner).expect("inner satisfies all correctness criteria for $B::new") } } }; } #[cfg(test)] mod tests { use std::ops::Deref; use crate::{core::errors::Error, result::DmError}; fn err_func(err_msg: &str) -> DmError { DmError::Core(Error::InvalidArgument(err_msg.into())) } const TYPE_LEN: usize = 12; str_id!(Id, IdBuf, TYPE_LEN, err_func); #[test] /// Test for errors on an empty name. fn test_empty_name() { assert_matches!(Id::new(""), Err(DmError::Core(Error::InvalidArgument(_)))); assert_matches!( IdBuf::new("".into()), Err(DmError::Core(Error::InvalidArgument(_))) ); } #[test] /// Test for errors on an overlong name. fn test_too_long_name() { let name = "a".repeat(TYPE_LEN + 1); assert_matches!( Id::new(&name), Err(DmError::Core(Error::InvalidArgument(_))) ); assert_matches!( IdBuf::new(name), Err(DmError::Core(Error::InvalidArgument(_))) ); } #[test] /// Test the concrete methods and traits of the interface. fn test_interface() { let id = Id::new("id").expect("is valid id"); let id_buf = IdBuf::new("id".into()).expect("is valid id"); // Test as_bytes. assert_eq!(id.as_bytes(), &[105u8, 100u8]); assert_eq!(id_buf.as_bytes(), &[105u8, 100u8]); // Test ToOwned implementation. // $B.to_owned() == $O assert_eq!(id.to_owned(), id_buf); // Test Display implementation // X.to_string() = (*X).to_string() assert_eq!(id.to_string(), (*id).to_string()); assert_eq!(id_buf.to_string(), (*id_buf).to_string()); // Test Deref assert_eq!(id_buf.deref(), id); assert_eq!(*id_buf, *id); } } devicemapper-0.34.4/src/lib.rs000064400000000000000000000117601046102023000142700ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Low-level devicemapper configuration of the running kernel. //! //! # Overview //! //! Linux's devicemapper allows the creation of block devices whose //! storage is mapped to other block devices in useful ways, either by //! changing the location of its data blocks, or performing some //! operation on the data itself. This is a low-level facility that is //! used by higher-level volume managers such as LVM2. Uses may //! include: //! //! * Dividing a large block device into smaller logical volumes (dm-linear) //! * Combining several separate block devices into a single block //! device with better performance and/or redundancy (dm-raid) //! * Encrypting a block device (dm-crypt) //! * Performing Copy-on-Write (COW) allocation of a volume's blocks //! enabling fast volume cloning and snapshots (dm-thin) //! * Configuring a smaller, faster block device to act as a cache for a //! larger, slower one (dm-cache) //! * Verifying the contents of a read-only volume (dm-verity) //! //! # Usage //! //! Before they can be used, DM devices must be created using //! `DM::device_create()`, have a mapping table loaded using //! `DM::table_load()`, and then activated with //! `DM::device_suspend()`. (This function is used for both suspending //! and activating a device.) Once activated, they can be used as a //! regular block device, including having other DM devices map to //! them. //! //! Devices have "active" and "inactive" mapping tables. See function //! descriptions for which table they affect. //! //! # Polling for Events //! //! Since DM minor version 37, first available in Linux kernel 4.14, the file //! descriptor associated with a `DM` context may be polled for events generated by //! DM devices. //! //! The fd will indicate POLLIN if any events have occurred on any DM devices //! since the fd was opened, or since `DM::arm_poll()` was called. Therefore, //! in order to determine which DM devices have generated an event, the //! following usage is required: //! //! 1. Create a `DM`. //! 2. Call `DM::list_devices()` and track the `event_nr`s for any DM devices //! of interest. //! 3. `poll()` on the `DM`'s file descriptor, obtained by calling //! `DM::file().as_raw_fd()`. //! 4. If the fd indicates activity, first clear the event by calling //! `DM::arm_poll()`. This must be done before event processing to ensure //! events are not missed. //! 5. Process events. Call `DM::list_devices()` again, and compare `event_nr` //! returned by the more recent call with `event_nr` values from the earlier //! call. If `event_nr` differs, an event has occurred on that specific //! device. Handle the event(s). Update the list of last-seen `event_nr`s. //! 6. Optionally loop and re-invoke `poll()` on the fd to wait for more //! events. #![allow(clippy::doc_markdown)] #![warn(missing_docs)] #[macro_use] extern crate bitflags; #[macro_use] extern crate nix; #[macro_use] extern crate log; /// Range macros #[macro_use] mod range_macros; /// ID macros #[macro_use] mod id_macros; /// shared constants mod consts; /// core functionality mod core; /// Macros shared by device mapper devices. #[macro_use] mod shared_macros; /// cachedev mod cachedev; /// functions to create continuous linear space given device segments mod lineardev; /// return results container mod result; /// functionality shared between devices mod shared; /// allocate a device from a pool mod thindev; /// the id the pool uses to track its devices mod thindevid; /// thinpooldev is shared space for other thin provisioned devices to use mod thinpooldev; /// representation of units used by the outer layers mod units; #[cfg(test)] mod testing; /// More useful test output for match cases #[cfg(test)] #[macro_use] extern crate assert_matches; pub use crate::{ cachedev::{ CacheDev, CacheDevPerformance, CacheDevStatus, CacheDevTargetTable, CacheDevUsage, CacheDevWorkingStatus, CacheTargetParams, MAX_CACHE_BLOCK_SIZE, MIN_CACHE_BLOCK_SIZE, }, consts::IEC, core::{ devnode_to_devno, errors, DevId, Device, DeviceInfo, DmFlags, DmName, DmNameBuf, DmOptions, DmUdevFlags, DmUuid, DmUuidBuf, DM, }, lineardev::{ FlakeyTargetParams, LinearDev, LinearDevTargetParams, LinearDevTargetTable, LinearTargetParams, }, result::{DmError, DmResult, ErrorEnum}, shared::{ device_exists, message, DmDevice, TargetLine, TargetParams, TargetTable, TargetType, TargetTypeBuf, }, thindev::{ThinDev, ThinDevTargetTable, ThinDevWorkingStatus, ThinStatus, ThinTargetParams}, thindevid::ThinDevId, thinpooldev::{ ThinPoolDev, ThinPoolDevTargetTable, ThinPoolNoSpacePolicy, ThinPoolStatus, ThinPoolStatusSummary, ThinPoolTargetParams, ThinPoolUsage, ThinPoolWorkingStatus, }, units::{Bytes, DataBlocks, MetaBlocks, Sectors, SECTOR_SIZE}, }; devicemapper-0.34.4/src/lineardev.rs000064400000000000000000000771541046102023000155040ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{collections::HashSet, fmt, path::PathBuf, str::FromStr}; use crate::{ core::{DevId, Device, DeviceInfo, DmFlags, DmName, DmOptions, DmUuid, DM}, result::{DmError, DmResult, ErrorEnum}, shared::{ device_create, device_exists, device_match, parse_device, parse_value, DmDevice, TargetLine, TargetParams, TargetTable, TargetTypeBuf, }, units::Sectors, }; const FLAKEY_TARGET_NAME: &str = "flakey"; const LINEAR_TARGET_NAME: &str = "linear"; /// Struct representing params for a linear target #[derive(Clone, Debug, Eq, PartialEq)] pub struct LinearTargetParams { /// Device on which this segment resides. pub device: Device, /// Start offset in device on which this segment resides. pub start_offset: Sectors, } impl LinearTargetParams { /// Create a new LinearTargetParams struct pub fn new(device: Device, start_offset: Sectors) -> LinearTargetParams { LinearTargetParams { device, start_offset, } } } impl fmt::Display for LinearTargetParams { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {}", LINEAR_TARGET_NAME, self.param_str()) } } impl FromStr for LinearTargetParams { type Err = DmError; fn from_str(s: &str) -> DmResult { let vals = s.split(' ').collect::>(); if vals.len() != 3 { let err_msg = format!( "expected 3 values in params string \"{}\", found {}", s, vals.len() ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } if vals[0] != LINEAR_TARGET_NAME { let err_msg = format!( "Expected a linear target entry but found target type {}", vals[0] ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let device = parse_device(vals[1], "block device for linear target")?; let start = Sectors(parse_value(vals[2], "physical start offset")?); Ok(LinearTargetParams::new(device, start)) } } impl TargetParams for LinearTargetParams { fn param_str(&self) -> String { format!("{} {}", self.device, *self.start_offset) } fn target_type(&self) -> TargetTypeBuf { TargetTypeBuf::new(LINEAR_TARGET_NAME.into()).expect("LINEAR_TARGET_NAME is valid") } } #[derive(Debug, Hash, Clone, Eq, PartialEq)] pub enum Direction { Reads, Writes, } impl fmt::Display for Direction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Direction::Reads => write!(f, "r"), Direction::Writes => write!(f, "w"), } } } impl FromStr for Direction { type Err = DmError; fn from_str(s: &str) -> DmResult { if s == "r" { Ok(Direction::Reads) } else if s == "w" { Ok(Direction::Writes) } else { let err_msg = format!("Expected r or w, found {s}"); Err(DmError::Dm(ErrorEnum::Invalid, err_msg)) } } } /// Flakey target optional feature parameters: /// If no feature parameters are present, during the periods of /// unreliability, all I/O returns errors. #[derive(Debug, Hash, Clone, Eq, PartialEq)] pub enum FeatureArg { /// drop_writes: /// /// All write I/O is silently ignored. /// Read I/O is handled correctly. DropWrites, /// error_writes: /// /// All write I/O is failed with an error signalled. /// Read I/O is handled correctly. ErrorWrites, /// corrupt_bio_byte : /// /// During , replace of the data of /// each matching bio with . /// /// : The offset of the byte to replace. /// Counting starts at 1, to replace the first byte. /// : Either 'r' to corrupt reads or 'w' to corrupt writes. /// 'w' is incompatible with drop_writes. /// : The value (from 0-255) to write. /// : Perform the replacement only if bio->bi_opf has all the /// selected flags set. CorruptBioByte(u64, Direction, u8, u64), } impl fmt::Display for FeatureArg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FeatureArg::DropWrites => write!(f, "drop_writes"), FeatureArg::ErrorWrites => write!(f, "error_writes"), FeatureArg::CorruptBioByte(offset, direction, value, flags) => { write!(f, "corrupt_bio_byte {offset} {direction} {value} {flags}") } } } } /// Target params for flakey target #[derive(Clone, Debug, Eq, PartialEq)] pub struct FlakeyTargetParams { /// The device on which this segment resides pub device: Device, /// The starting offset of this segments in the device. pub start_offset: Sectors, /// Interval during which flakey target is up, in seconds /// DM source type is unsigned, so restrict to u32. pub up_interval: u32, /// Interval during which flakey target is down, in seconds /// DM source type is unsigned, so restrict to u32. pub down_interval: u32, /// Optional feature arguments pub feature_args: HashSet, } impl FlakeyTargetParams { /// Create a new flakey target param struct. pub fn new( device: Device, start_offset: Sectors, up_interval: u32, down_interval: u32, feature_args: Vec, ) -> FlakeyTargetParams { FlakeyTargetParams { device, start_offset, up_interval, down_interval, feature_args: feature_args.into_iter().collect::>(), } } } impl fmt::Display for FlakeyTargetParams { /// Generate params to be passed to DM. The format of the params is: /// /// ```plain /// [ []] /// ``` /// /// Mandatory parameters: /// /// * ``: Full pathname to the underlying block-device, or a /// "major:minor" device-number. /// * ``: Starting sector within the device. /// * ``: Number of seconds device is available. /// * ``: Number of seconds device returns errors. /// /// Optional feature parameters: /// If no feature parameters are present, during the periods of /// unreliability, all I/O returns errors. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {}", FLAKEY_TARGET_NAME, self.param_str()) } } impl FromStr for FlakeyTargetParams { type Err = DmError; fn from_str(s: &str) -> DmResult { fn parse_feature_args(vals: &[&str]) -> DmResult> { let mut vals_iter = vals.iter(); let mut result: Vec = Vec::new(); while let Some(x) = vals_iter.next() { match x { &"drop_writes" => result.push(FeatureArg::DropWrites), &"error_writes" => result.push(FeatureArg::ErrorWrites), &"corrupt_bio_byte" => { let offset = vals_iter .next() .ok_or({ let err_msg = "corrupt_bio_byte takes 4 parameters"; DmError::Dm(ErrorEnum::Invalid, err_msg.to_string()) }) .and_then(|s| parse_value::(s, "offset"))?; let direction = vals_iter .next() .ok_or({ let err_msg = "corrupt_bio_byte takes 4 parameters"; DmError::Dm(ErrorEnum::Invalid, err_msg.to_string()) }) .and_then(|s| parse_value::(s, "direction"))?; let value = vals_iter .next() .ok_or({ let err_msg = "corrupt_bio_byte takes 4 parameters"; DmError::Dm(ErrorEnum::Invalid, err_msg.to_string()) }) .and_then(|s| parse_value::(s, "value"))?; let flags = vals_iter .next() .ok_or({ let err_msg = "corrupt_bio_byte takes 4 parameters"; DmError::Dm(ErrorEnum::Invalid, err_msg.to_string()) }) .and_then(|s| parse_value::(s, "flags"))?; result.push(FeatureArg::CorruptBioByte(offset, direction, value, flags)); } x => { let err_msg = format!("{x} is an unrecognized feature parameter"); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } } } Ok(result) } let vals = s.split(' ').collect::>(); if vals.len() < 5 { let err_msg = format!( "expected at least five values in params string \"{}\", found {}", s, vals.len() ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } if vals[0] != FLAKEY_TARGET_NAME { let err_msg = format!( "Expected a flakey target entry but found target type {}", vals[0] ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let device = parse_device(vals[1], "block device for flakey target")?; let start_offset = Sectors(parse_value(vals[2], "physical start offset")?); let up_interval = parse_value(vals[3], "up interval")?; let down_interval = parse_value(vals[4], "down interval")?; let feature_args = if vals.len() == 5 { vec![] } else { parse_feature_args( &vals[6..6 + parse_value::(vals[5], "number of feature args")?], )? }; Ok(FlakeyTargetParams::new( device, start_offset, up_interval, down_interval, feature_args, )) } } impl TargetParams for FlakeyTargetParams { fn param_str(&self) -> String { let feature_args = if self.feature_args.is_empty() { "0".to_owned() } else { format!( "{} {}", self.feature_args.len(), self.feature_args .iter() .map(|x| x.to_string()) .collect::>() .join(" ") ) }; format!( "{} {} {} {} {}", self.device, *self.start_offset, self.up_interval, self.down_interval, feature_args ) } fn target_type(&self) -> TargetTypeBuf { TargetTypeBuf::new(FLAKEY_TARGET_NAME.into()).expect("FLAKEY_TARGET_NAME is valid") } } /// Target params for linear dev. These are either flakey or linear. #[derive(Clone, Debug, Eq, PartialEq)] pub enum LinearDevTargetParams { /// A flakey target Flakey(FlakeyTargetParams), /// A linear target Linear(LinearTargetParams), } impl fmt::Display for LinearDevTargetParams { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { LinearDevTargetParams::Flakey(ref flakey) => flakey.fmt(f), LinearDevTargetParams::Linear(ref linear) => linear.fmt(f), } } } impl FromStr for LinearDevTargetParams { type Err = DmError; fn from_str(s: &str) -> DmResult { let target_type = Some(s.split_once(' ').map_or(s, |x| x.0)).ok_or_else(|| { DmError::Dm( ErrorEnum::Invalid, format!("target line string \"{s}\" did not contain any values"), ) })?; if target_type == FLAKEY_TARGET_NAME { Ok(LinearDevTargetParams::Flakey( s.parse::()?, )) } else if target_type == LINEAR_TARGET_NAME { Ok(LinearDevTargetParams::Linear( s.parse::()?, )) } else { Err(DmError::Dm( ErrorEnum::Invalid, format!("unexpected target type \"{target_type}\""), )) } } } impl TargetParams for LinearDevTargetParams { fn param_str(&self) -> String { match *self { LinearDevTargetParams::Flakey(ref flakey) => flakey.param_str(), LinearDevTargetParams::Linear(ref linear) => linear.param_str(), } } fn target_type(&self) -> TargetTypeBuf { match *self { LinearDevTargetParams::Flakey(ref flakey) => flakey.target_type(), LinearDevTargetParams::Linear(ref linear) => linear.target_type(), } } } /// A target table for a linear device. Such a table allows flakey targets /// as well as linear targets. #[derive(Clone, Debug, Eq, PartialEq)] pub struct LinearDevTargetTable { /// The device's table pub table: Vec>, } impl LinearDevTargetTable { /// Make a new LinearDevTargetTable from a suitable vec pub fn new(table: Vec>) -> LinearDevTargetTable { LinearDevTargetTable { table } } } impl fmt::Display for LinearDevTargetTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for line in &self.table { writeln!(f, "{} {} {}", *line.start, *line.length, line.params)?; } Ok(()) } } impl TargetTable for LinearDevTargetTable { fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult { Ok(LinearDevTargetTable { table: table .iter() .map(|x| -> DmResult> { Ok(TargetLine::new( Sectors(x.0), Sectors(x.1), format!("{} {}", x.2, x.3).parse::()?, )) }) .collect::>>()?, }) } fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> { self.table .iter() .map(|x| { ( *x.start, *x.length, x.params.target_type().to_string(), x.params.param_str(), ) }) .collect::>() } } /// A DM construct of combined Segments #[derive(Debug)] pub struct LinearDev { /// Data about the device dev_info: Box, table: LinearDevTargetTable, } impl DmDevice for LinearDev { fn device(&self) -> Device { device!(self) } fn devnode(&self) -> PathBuf { devnode!(self) } // Since linear devices have no default or configuration parameters, // and the ordering of segments matters, two linear devices represent // the same linear device only if their tables match exactly. fn equivalent_tables( left: &LinearDevTargetTable, right: &LinearDevTargetTable, ) -> DmResult { Ok(left == right) } fn name(&self) -> &DmName { name!(self) } fn size(&self) -> Sectors { self.table.table.iter().map(|l| l.length).sum() } fn table(&self) -> &LinearDevTargetTable { table!(self) } fn teardown(&mut self, dm: &DM) -> DmResult<()> { dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?; Ok(()) } fn uuid(&self) -> Option<&DmUuid> { uuid!(self) } } /// Use DM to concatenate a list of segments together into a /// linear block device of continuous sectors. impl LinearDev { /// Construct a new block device by concatenating the given segments /// into linear space. /// If the device is already known to the kernel, just verifies that the /// segments argument passed exactly matches the kernel data. /// /// Warning: If the segments overlap, this method will succeed. However, /// the behavior of the linear device in that case should be treated as /// undefined. /// /// Note: A linear device is just a mapping in the kernel from locations /// in that device to locations on other devices which are specified by /// their device number. There is usually a device node so that data can /// be read from and written to the device. Other than that, it really /// has no existence. Consequently, there is no conflict in overloading /// this method to mean both "make a wholly new device" and "establish /// the existence of the requested device". Of course, a linear device /// is usually expected to hold data, so it is important to get the /// mapping just right. pub fn setup( dm: &DM, name: &DmName, uuid: Option<&DmUuid>, table: Vec>, ) -> DmResult { let table = LinearDevTargetTable::new(table); let dev = if device_exists(dm, name)? { let dev_info = dm.device_info(&DevId::Name(name))?; let dev = LinearDev { dev_info: Box::new(dev_info), table, }; device_match(dm, &dev, uuid)?; dev } else { let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?; LinearDev { dev_info: Box::new(dev_info), table, } }; Ok(dev) } /// Set the segments for this linear device. /// This action puts the device in a state where it is ready to be resumed. /// Warning: It is the client's responsibility to make sure the designated /// segments are compatible with the device's existing segments. /// If they are not, this function will still succeed, but some kind of /// data corruption will be the inevitable result. pub fn set_table( &mut self, dm: &DM, table: Vec>, ) -> DmResult<()> { let table = LinearDevTargetTable::new(table); self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.table_load(dm, &table, DmOptions::default())?; self.table = table; Ok(()) } /// Set the name for this LinearDev. pub fn set_name(&mut self, dm: &DM, name: &DmName) -> DmResult<()> { if self.name() == name { return Ok(()); } dm.device_rename(self.name(), &DevId::Name(name))?; self.dev_info = Box::new(dm.device_info(&DevId::Name(name))?); Ok(()) } } #[cfg(test)] mod tests { use std::{clone::Clone, fs::OpenOptions, path::Path}; use crate::{ core::{devnode_to_devno, Device}, testing::{blkdev_size, test_name, test_with_spec}, }; use super::*; /// Verify that a new linear dev with 0 segments fails. fn test_empty(_paths: &[&Path]) { assert_matches!( LinearDev::setup( &DM::new().unwrap(), &test_name("new").expect("valid format"), None, vec![], ), Err(_) ); } /// Verify that setting an empty table on an existing DM device fails. fn test_empty_table_set(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let name = test_name("name").expect("valid format"); let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let params = LinearTargetParams::new(dev, Sectors(0)); let table = vec![TargetLine::new( Sectors(0), Sectors(1), LinearDevTargetParams::Linear(params), )]; let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap(); assert_matches!(ld.set_table(&dm, vec![]), Err(_)); ld.resume(&dm).unwrap(); ld.teardown(&dm).unwrap(); } /// Verify that id rename succeeds. fn test_rename_id(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let name = test_name("name").expect("valid format"); let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let params = LinearTargetParams::new(dev, Sectors(0)); let table = vec![TargetLine::new( Sectors(0), Sectors(1), LinearDevTargetParams::Linear(params), )]; let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap(); ld.set_name(&dm, &name).unwrap(); assert_eq!(ld.name(), &*name); ld.teardown(&dm).unwrap(); } /// Verify that after a rename, the device has the new name. fn test_rename(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let name = test_name("name").expect("valid format"); let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let params = LinearTargetParams::new(dev, Sectors(0)); let table = vec![TargetLine::new( Sectors(0), Sectors(1), LinearDevTargetParams::Linear(params), )]; let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap(); let new_name = test_name("new_name").expect("valid format"); ld.set_name(&dm, &new_name).unwrap(); assert_eq!(ld.name(), &*new_name); ld.teardown(&dm).unwrap(); } /// Verify that passing the same segments two times gets two segments. /// Verify that the size of the devnode is the size of the sum of the /// ranges of the segments. Verify that the table contains entries for both /// segments. fn test_duplicate_segments(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let name = test_name("name").expect("valid format"); let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let params = LinearTargetParams::new(dev, Sectors(0)); let table = vec![ TargetLine::new( Sectors(0), Sectors(1), LinearDevTargetParams::Linear(params.clone()), ), TargetLine::new( Sectors(1), Sectors(1), LinearDevTargetParams::Linear(params), ), ]; let range: Sectors = table.iter().map(|s| s.length).sum(); let count = table.len(); let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap(); let table = LinearDev::read_kernel_table(&dm, &DevId::Name(ld.name())) .unwrap() .table; assert_eq!(table.len(), count); assert_matches!(table[0].params, LinearDevTargetParams::Linear(ref device) if device.device == dev); assert_matches!(table[1].params, LinearDevTargetParams::Linear(ref device) if device.device == dev); assert_eq!( blkdev_size(&OpenOptions::new().read(true).open(ld.devnode()).unwrap()).sectors(), range ); ld.teardown(&dm).unwrap(); } /// Use five segments, each distinct. If parsing works correctly, /// default table should match extracted table. fn test_several_segments(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let name = test_name("name").expect("valid format"); let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let table = (0..5) .map(|n| { TargetLine::new( Sectors(n), Sectors(1), LinearDevTargetParams::Linear(LinearTargetParams::new(dev, Sectors(n))), ) }) .collect::>(); let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap(); let loaded_table = LinearDev::read_kernel_table(&dm, &DevId::Name(ld.name())).unwrap(); assert!( LinearDev::equivalent_tables(&LinearDevTargetTable::new(table), &loaded_table).unwrap() ); ld.teardown(&dm).unwrap(); } /// Verify that constructing a second dev with the same name succeeds /// only if it has the same list of segments. fn test_same_name(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let name = test_name("name").expect("valid format"); let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let params = LinearTargetParams::new(dev, Sectors(0)); let table = vec![TargetLine::new( Sectors(0), Sectors(1), LinearDevTargetParams::Linear(params), )]; let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap(); let params2 = LinearTargetParams::new(dev, Sectors(1)); let table2 = vec![TargetLine::new( Sectors(0), Sectors(1), LinearDevTargetParams::Linear(params2), )]; assert_matches!(LinearDev::setup(&dm, &name, None, table2), Err(_)); assert_matches!(LinearDev::setup(&dm, &name, None, table), Ok(_)); ld.teardown(&dm).unwrap(); } /// Verify constructing a second linear dev with the same segment succeeds. fn test_same_segment(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let name = test_name("name").expect("valid format"); let ersatz = test_name("ersatz").expect("valid format"); let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let params = LinearTargetParams::new(dev, Sectors(0)); let table = vec![TargetLine::new( Sectors(0), Sectors(1), LinearDevTargetParams::Linear(params), )]; let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap(); let ld2 = LinearDev::setup(&dm, &ersatz, None, table); assert_matches!(ld2, Ok(_)); ld2.unwrap().teardown(&dm).unwrap(); ld.teardown(&dm).unwrap(); } /// Verify that suspending and immediately resuming doesn't fail. fn test_suspend(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let name = test_name("name").expect("valid format"); let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let params = LinearTargetParams::new(dev, Sectors(0)); let table = vec![TargetLine::new( Sectors(0), Sectors(1), LinearDevTargetParams::Linear(params), )]; let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap(); ld.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH)) .unwrap(); ld.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH)) .unwrap(); ld.resume(&dm).unwrap(); ld.resume(&dm).unwrap(); ld.teardown(&dm).unwrap(); } #[test] fn test_flakey_target_params_zero() { let result = "flakey 8:32 0 16 2 0" .parse::() .unwrap(); assert_eq!(result.feature_args, HashSet::new()); } #[test] fn test_flakey_target_params_none() { let result = "flakey 8:32 0 16 2".parse::().unwrap(); assert_eq!(result.feature_args, HashSet::new()); } #[test] fn test_flakey_target_params_drop_writes() { let result = "flakey 8:32 0 16 2 1 drop_writes" .parse::() .unwrap(); let expected = [FeatureArg::DropWrites] .iter() .cloned() .collect::>(); assert_eq!(result.feature_args, expected); } #[test] fn test_flakey_target_params_error_writes() { let result = "flakey 8:32 0 16 2 1 error_writes" .parse::() .unwrap(); let expected = [FeatureArg::ErrorWrites] .iter() .cloned() .collect::>(); assert_eq!(result.feature_args, expected); } #[test] fn test_flakey_target_params_corrupt_bio_byte_reads() { let result = "flakey 8:32 0 16 2 5 corrupt_bio_byte 32 r 1 0" .parse::() .unwrap(); let expected = [FeatureArg::CorruptBioByte(32, Direction::Reads, 1, 0)] .iter() .cloned() .collect::>(); assert_eq!(result.feature_args, expected); } #[test] fn test_flakey_target_params_corrupt_bio_byte_writes() { let result = "flakey 8:32 0 16 2 5 corrupt_bio_byte 224 w 0 32" .parse::() .unwrap(); let expected = [FeatureArg::CorruptBioByte(224, Direction::Writes, 0, 32)] .iter() .cloned() .collect::>(); assert_eq!(result.feature_args, expected); } #[test] fn test_flakey_target_params_corrupt_bio_byte_and_drop_writes() { let result = "flakey 8:32 0 16 2 6 corrupt_bio_byte 32 r 1 0 drop_writes" .parse::() .unwrap(); let expected = [ FeatureArg::CorruptBioByte(32, Direction::Reads, 1, 0), FeatureArg::DropWrites, ] .iter() .cloned() .collect::>(); assert_eq!(result.feature_args, expected); } #[test] fn test_flakey_target_params_drop_writes_and_corrupt_bio_byte() { let result = "flakey 8:32 0 16 2 6 corrupt_bio_byte 32 r 1 0 drop_writes" .parse::() .unwrap(); let expected = [ FeatureArg::DropWrites, FeatureArg::CorruptBioByte(32, Direction::Reads, 1, 0), ] .iter() .cloned() .collect::>(); assert_eq!(result.feature_args, expected); } #[test] fn test_flakey_target_params_error_writes_and_drop_writes() { let result = "flakey 8:32 0 16 2 2 error_writes drop_writes" .parse::() .unwrap(); let expected = [FeatureArg::ErrorWrites, FeatureArg::DropWrites] .iter() .cloned() .collect::>(); assert_eq!(result.feature_args, expected); } #[test] fn loop_test_duplicate_segments() { test_with_spec(1, test_duplicate_segments); } #[test] fn loop_test_empty() { test_with_spec(0, test_empty); } #[test] fn loop_test_empty_table_set() { test_with_spec(1, test_empty_table_set); } #[test] fn loop_test_rename() { test_with_spec(1, test_rename); } #[test] fn loop_test_rename_id() { test_with_spec(1, test_rename_id); } #[test] fn loop_test_same_name() { test_with_spec(1, test_same_name); } #[test] fn loop_test_segment() { test_with_spec(1, test_same_segment); } #[test] fn loop_test_several_segments() { test_with_spec(1, test_several_segments); } #[test] fn loop_test_suspend() { test_with_spec(1, test_suspend); } } devicemapper-0.34.4/src/range_macros.rs000064400000000000000000000244071046102023000161640ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // An omnibus macro that includes all simple macros. macro_rules! range_u64 { ( $(#[$comment:meta])? $T: ident, $display_name: expr) => { range!($(#[$comment])? $T, $display_name, u64, serialize_u64); from_u64!($T); mul!($T, u64, u32, u16, u8); div!($T, u64, u32, u16, u8); } } macro_rules! range_u128 { ( $(#[$comment:meta])? $T: ident, $display_name: expr) => { range!($(#[$comment])? $T, $display_name, u128, serialize_u128); from_u128!($T); mul!($T, u128, u64, u32, u16, u8); div!($T, u128, u64, u32, u16, u8); } } macro_rules! range { ( $(#[$comment:meta])? $T: ident, $display_name: expr, $inner:ty, $serde_method:ident) => { $( #[$comment] )? #[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct $T(pub $inner); checked_add!($T); debug_macro!($T); display!($T, $display_name); serde_macro!($T, $serde_method); sum!($T); add!($T); add_assign!($T); sub!($T); sub_assign!($T); rem!($T); deref!($T, $inner); }; } macro_rules! add { ($T:ident) => { impl std::ops::Add<$T> for $T { type Output = $T; fn add(self, rhs: $T) -> $T { $T(self.0 + *rhs) } } }; } macro_rules! sub { ($T:ident) => { impl std::ops::Sub<$T> for $T { type Output = $T; fn sub(self, rhs: $T) -> $T { $T(self.0 - *rhs) } } }; } macro_rules! add_assign { ($T:ident) => { impl std::ops::AddAssign<$T> for $T { fn add_assign(&mut self, rhs: $T) { *self = $T(self.0 + *rhs) } } }; } macro_rules! sub_assign { ($T:ident) => { impl std::ops::SubAssign<$T> for $T { fn sub_assign(&mut self, rhs: $T) { *self = $T(self.0 - *rhs) } } }; } macro_rules! deref { ($T:ident, $inner:ty) => { impl std::ops::Deref for $T { type Target = $inner; fn deref(&self) -> &$inner { &self.0 } } }; } macro_rules! from { ($T:ident, $inner:ty, $($t:ty),+) => { $( impl From<$t> for $T { fn from(t: $t) -> $T { $T(<$inner>::from(t)) } } )+ impl From for $T { fn from(t: usize) -> $T { $T(t as $inner) } } impl From<$inner> for $T { fn from(t: $inner) -> $T { $T(t) } } }; } macro_rules! from_u64 { ($T:ident) => { from!($T, u64, u32, u16, u8); }; } macro_rules! from_u128 { ($T:ident) => { from!($T, u128, u64, u32, u16, u8); }; } macro_rules! debug_macro { ($T:ident) => { impl std::fmt::Debug for $T { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, stringify!($T))?; write!(f, "({})", **self) } } }; } macro_rules! display { ($T:ident, $display_name:expr) => { impl std::fmt::Display for $T { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} {}", self.0, $display_name) } } }; } macro_rules! serde_macro { ($T:ident, $serde_method:ident) => { impl serde::Serialize for $T { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.$serde_method(**self) } } impl<'de> serde::Deserialize<'de> for $T { fn deserialize(deserializer: D) -> Result<$T, D::Error> where D: serde::de::Deserializer<'de>, { Ok($T(serde::Deserialize::deserialize(deserializer)?)) } } }; } macro_rules! sum { ($T:ident) => { impl std::iter::Sum for $T { fn sum>(iter: I) -> $T { iter.fold($T::default(), std::ops::Add::add) } } }; } // Define a complete set of division operations. macro_rules! div { ($T:ident, $inner:ty, $($t:ty),+) => { $( impl std::ops::Div<$t> for $T { type Output = $T; fn div(self, rhs: $t) -> $T { $T(self.0 / <$inner>::from(rhs)) } } )+ impl std::ops::Div for $T { type Output = $T; fn div(self, rhs: usize) -> $T { $T(self.0 / rhs as $inner) } } impl std::ops::Div<$inner> for $T { type Output = $T; fn div(self, rhs: $inner) -> $T { $T(self.0 / rhs) } } impl std::ops::Div for $T { type Output = $inner; fn div(self, rhs: $T) -> $inner { self.0 / *rhs } } }; } // Define a complete set of multiplication operations. macro_rules! mul { ($T:ident, $inner:ty, $($t:ty),+) => { $( impl std::ops::Mul<$t> for $T { type Output = $T; fn mul(self, rhs: $t) -> $T { $T(self.0 * <$inner>::from(rhs)) } } impl std::ops::Mul<$T> for $t { type Output = $T; fn mul(self, rhs: $T) -> $T { $T(rhs.0 * <$inner>::from(self)) } } )+ impl std::ops::Mul<$inner> for $T { type Output = $T; fn mul(self, rhs: $inner) -> $T { $T(self.0 * rhs) } } impl std::ops::Mul<$T> for $inner { type Output = $T; fn mul(self, rhs: $T) -> $T { $T(rhs.0 * self) } } impl std::ops::Mul for $T { type Output = $T; fn mul(self, rhs: usize) -> $T { $T(self.0 * rhs as $inner) } } impl std::ops::Mul<$T> for usize { type Output = $T; fn mul(self, rhs: $T) -> $T { $T(rhs.0 * self as $inner) } } }; } // Define a complete set of remainder operations. macro_rules! rem { ($T: ident) => { impl std::ops::Rem for $T where $T: From, { type Output = $T; fn rem(self, rhs: T) -> $T { $T(self.0 % <$T>::from(rhs).0) } } }; } macro_rules! checked_add { ($T: ident) => { impl $T { /// Add two items of the same type, return None if overflow. pub fn checked_add(&self, other: $T) -> Option<$T> { self.0.checked_add(other.0).map($T) } } }; } #[cfg(test)] mod tests { range_u64!(Units, "units"); #[test] /// Test implicit derivations for Units fn test_implicit_derivations() { // Test Clone. Note cloning within an assertion disables the clippy // allow, so no assertion here. #[allow(clippy::clone_on_copy)] let _ = Units(1).clone(); // Test Default assert_eq!(Units::default(), Units(0)); // Test Ord, PartialOrd assert!(Units::default() < Units(1)); assert!(Units(1) >= Units::default()); // Test Copy let z = Units(3); let mut d = z; d += Units(1); assert_eq!(z, Units(3)); assert_eq!(d, Units(4)); } #[test] /// Test non-arithmetic derivations that are derived explicitly fn test_explicit_derivations() { // Test From assert_eq!(Units::from(3u8) + Units(3), Units(6)); // Test Deref assert_eq!(*Units(3) + 3, 6); // Test Debug assert_eq!(format!("{:?}", Units(3)), "Units(3)"); // Test Display assert_eq!(format!("{:}", Units(3)), "3 units"); } #[test] /// Test operations that work on an iterator of values, like sum and /// product. fn test_summary_operations() { // Test Sum assert_eq!( [Units(2), Units(3)].iter().cloned().sum::(), Units(5) ); } #[test] /// Test addition. fn test_addition() { assert_eq!(Units(1) + Units(3), Units(4)); let mut z = Units(1); z += Units(3); assert_eq!(z, Units(4)); assert_eq!(Units(u64::MAX).checked_add(Units(1)), None); } #[test] /// Test subtraction fn test_subtraction() { assert_eq!(Units(3) - Units(1), Units(2)); let mut z = Units(3); z -= Units(1); assert_eq!(z, Units(2)); } #[test] /// Test multiplication fn test_multiplication() { assert_eq!(Units(3) * 2u8, Units(6)); assert_eq!(2u8 * Units(3), Units(6)); assert_eq!(Units(3) * 2u16, Units(6)); assert_eq!(2u16 * Units(3), Units(6)); assert_eq!(Units(3) * 2u32, Units(6)); assert_eq!(2u32 * Units(3), Units(6)); assert_eq!(Units(3) * 2u64, Units(6)); assert_eq!(2u64 * Units(3), Units(6)); assert_eq!(Units(3) * 2usize, Units(6)); assert_eq!(2usize * Units(3), Units(6)); } #[test] /// Test division and remainder together fn test_division_and_remainder() { assert_eq!(Units(3) / Units(2), 1); assert_eq!(Units(3) % Units(2), Units(1)); assert_eq!(Units(5) / 2u8, Units(2)); assert_eq!(Units(3) % 2u8, Units(1)); assert_eq!(Units(5) / 2u16, Units(2)); assert_eq!(Units(3) % 2u16, Units(1)); assert_eq!(Units(5) / 2u32, Units(2)); assert_eq!(Units(3) % 2u32, Units(1)); assert_eq!(Units(5) / 2u64, Units(2)); assert_eq!(Units(3) % 2u64, Units(1)); assert_eq!(Units(5) / 2usize, Units(2)); assert_eq!(Units(3) % 2usize, Units(1)); } } devicemapper-0.34.4/src/result.rs000064400000000000000000000026001046102023000150310ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{error::Error, fmt}; use crate::core::errors; /// A very simple breakdown of outer layer errors. #[derive(Clone, Debug)] pub enum ErrorEnum { /// generic error code Error, /// invalid value passed as argument Invalid, /// something not found NotFound, } impl fmt::Display for ErrorEnum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } /// Super error type, with constructors distinguishing outer errors from /// core errors. #[derive(Clone, Debug)] pub enum DmError { /// DM errors Dm(ErrorEnum, String), /// Errors in the core devicemapper functionality Core(errors::Error), } /// return result for DM functions pub type DmResult = Result; impl From for DmError { fn from(err: errors::Error) -> DmError { DmError::Core(err) } } impl fmt::Display for DmError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { DmError::Core(ref err) => write!(f, "DM Core error: {err}"), DmError::Dm(ref err, ref msg) => write!(f, "DM error: {err}: {msg}"), } } } impl Error for DmError {} devicemapper-0.34.4/src/shared.rs000064400000000000000000000205261046102023000147700ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // A module to contain functionality shared among the various types of // devices. use std::{ fmt, ops::Deref, path::{Path, PathBuf}, str::FromStr, }; use crate::{ core::{devnode_to_devno, DevId, Device, DeviceInfo, DmFlags, DmName, DmOptions, DmUuid, DM}, result::{DmError, DmResult, ErrorEnum}, units::Sectors, }; fn err_func(err_msg: &str) -> DmError { DmError::Dm(ErrorEnum::Invalid, err_msg.into()) } /// Number of bytes in Struct_dm_target_spec::target_type field. const DM_TARGET_TYPE_LEN: usize = 16; str_id!(TargetType, TargetTypeBuf, DM_TARGET_TYPE_LEN, err_func); /// The trait for properties of the params string of TargetType pub trait TargetParams: Clone + fmt::Debug + fmt::Display + Eq + FromStr + PartialEq { /// Return the param string only fn param_str(&self) -> String; /// Return the target type fn target_type(&self) -> TargetTypeBuf; } /// One line of a device mapper table. #[derive(Clone, Debug, Eq, PartialEq)] pub struct TargetLine { /// The start of the segment pub start: Sectors, /// The length of the segment pub length: Sectors, /// The target specific parameters pub params: T, } impl TargetLine { /// Make a new TargetLine struct pub fn new(start: Sectors, length: Sectors, params: T) -> TargetLine { TargetLine { start, length, params, } } } /// Manages a target's table pub trait TargetTable: Clone + fmt::Debug + fmt::Display + Eq + PartialEq + Sized { /// Constructs a table from a raw table returned by DM::table_status() fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult; /// Generates a table that can be loaded by DM::table_load() fn to_raw_table(&self) -> Vec<(u64, u64, String, String)>; } /// A trait capturing some shared properties of DM devices. pub trait DmDevice { /// The device. fn device(&self) -> Device; /// The device's device node. fn devnode(&self) -> PathBuf; /// Check if tables indicate an equivalent device. fn equivalent_tables(left: &T, right: &T) -> DmResult; /// Read the devicemapper table fn read_kernel_table(dm: &DM, id: &DevId<'_>) -> DmResult { let (_, table) = dm.table_status(id, DmOptions::default().set_flags(DmFlags::DM_STATUS_TABLE))?; T::from_raw_table(&table) } /// The device's name. fn name(&self) -> &DmName; /// Resume I/O on the device. fn resume(&mut self, dm: &DM) -> DmResult<()> { dm.device_suspend(&DevId::Name(self.name()), DmOptions::private())?; Ok(()) } /// The number of sectors available for user data. fn size(&self) -> Sectors; /// Suspend I/O on the device. fn suspend(&mut self, dm: &DM, options: DmOptions) -> DmResult<()> { dm.device_suspend( &DevId::Name(self.name()), DmOptions::default() .set_flags(DmFlags::DM_SUSPEND | options.flags()) .set_udev_flags(options.udev_flags()), )?; Ok(()) } /// What the device thinks its table is. fn table(&self) -> &T; /// Load a table fn table_load(&self, dm: &DM, table: &T, options: DmOptions) -> DmResult<()> { dm.table_load(&DevId::Name(self.name()), &table.to_raw_table(), options)?; Ok(()) } /// Erase the kernel's memory of this device. fn teardown(&mut self, dm: &DM) -> DmResult<()>; /// The device's UUID, if available. /// Note that the UUID is not any standard UUID format. fn uuid(&self) -> Option<&DmUuid>; } /// Send a message that expects no reply to target device. pub fn message>(dm: &DM, target: &D, msg: &str) -> DmResult<()> { dm.target_msg(&DevId::Name(target.name()), None, msg)?; Ok(()) } /// Create a device, load a table, and resume it allowing the caller to specify the DmOptions for /// resuming. pub fn device_create( dm: &DM, name: &DmName, uuid: Option<&DmUuid>, table: &T, suspend_options: DmOptions, ) -> DmResult { dm.device_create(name, uuid, DmOptions::default())?; let id = DevId::Name(name); let dev_info = match dm.table_load(&id, &table.to_raw_table(), DmOptions::default()) { Err(e) => { dm.device_remove(&id, DmOptions::default())?; return Err(e); } Ok(dev_info) => dev_info, }; dm.device_suspend(&id, suspend_options)?; Ok(dev_info) } /// Verify that kernel data matches arguments passed. pub fn device_match>( dm: &DM, dev: &D, uuid: Option<&DmUuid>, ) -> DmResult<()> { let kernel_table = D::read_kernel_table(dm, &DevId::Name(dev.name()))?; let device_table = dev.table(); if !D::equivalent_tables(&kernel_table, device_table)? { let err_msg = format!( "Specified new table \"{device_table:?}\" does not match kernel table \"{kernel_table:?}\"" ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } if dev.uuid() != uuid { let err_msg = format!( "Specified uuid \"{:?}\" does not match kernel uuuid \"{:?}\"", uuid, dev.uuid() ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } Ok(()) } /// Check if a device of the given name exists. pub fn device_exists(dm: &DM, name: &DmName) -> DmResult { dm.list_devices() .map(|l| l.iter().any(|(n, _, _)| &**n == name)) } /// Parse a device from either of a path or a maj:min pair pub fn parse_device(val: &str, desc: &str) -> DmResult { let device = if val.starts_with('/') { devnode_to_devno(Path::new(val))? .ok_or_else(|| { DmError::Dm( ErrorEnum::Invalid, format!("Failed to parse \"{desc}\" from input \"{val}\""), ) })? .into() } else { val.parse::()? }; Ok(device) } /// Parse a value or return an error. pub fn parse_value(val: &str, desc: &str) -> DmResult where T: FromStr, { val.parse::().map_err(|_| { DmError::Dm( ErrorEnum::Invalid, format!("Failed to parse value for \"{desc}\" from input \"{val}\""), ) }) } /// Get fields for a single status line. /// Return an error if an insufficient number of fields are obtained. pub fn get_status_line_fields(status_line: &str, number_required: usize) -> DmResult> { let status_vals = status_line.split(' ').collect::>(); let length = status_vals.len(); if length < number_required { return Err(DmError::Dm( ErrorEnum::Invalid, format!( "Insufficient number of fields for status; requires at least {number_required}, found only {length} in status line \"{status_line}\"" ), )); } Ok(status_vals) } /// Get unique status element from a status result. /// Return an error if an incorrect number of lines is obtained. pub fn get_status(status_lines: &[(u64, u64, String, String)]) -> DmResult { let length = status_lines.len(); if length != 1 { return Err(DmError::Dm( ErrorEnum::Invalid, format!( "Incorrect number of lines for status; expected 1, found {} in status result \"{}\"", length, status_lines.iter().map(|(s, l, t, v)| format!("{s} {l} {t} {v}")).collect::>().join(", ") ), )); } Ok(status_lines .first() .expect("if length != 1, already returned") .3 .to_owned()) } /// Construct an error when parsing yields an unexpected value. /// Indicate the location of the unexpected value, 1-indexed, its actual /// value, and the name of the expected thing. pub fn make_unexpected_value_error(value_index: usize, value: &str, item_name: &str) -> DmError { DmError::Dm( ErrorEnum::Invalid, format!( "Kernel returned unexpected {value_index}th value \"{value}\" for item representing {item_name} in status result" ), ) } devicemapper-0.34.4/src/shared_macros.rs000064400000000000000000000025211046102023000163270ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // A module to contain functionality shared among the various types of // devices and implemented by means of macros. macro_rules! device { ($s:ident) => { $s.dev_info.device() }; } macro_rules! name { ($s:ident) => { match $s.dev_info.name() { Some(n) => n, None => panic!("Name is required for device"), } }; } macro_rules! uuid { ($s:ident) => { $s.dev_info.uuid() }; } macro_rules! devnode { ($s:ident) => { ["/dev", &format!("dm-{}", $s.dev_info.device().minor)] .iter() .collect() }; } macro_rules! to_raw_table_unique { ($s:ident) => { vec![( *$s.table.start, *$s.table.length, $s.table.params.target_type().to_string(), $s.table.params.param_str(), )] }; } macro_rules! table { ($s:ident) => { &$s.table }; } macro_rules! status { ($s:ident, $dm:ident, $options:ident) => { get_status( &$dm.table_status(&$crate::core::DevId::Name($s.name()), $options)? .1, )? .parse() }; } devicemapper-0.34.4/src/testing/logger.rs000064400000000000000000000006421046102023000164530ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::sync::Once; static LOGGER_INIT: Once = Once::new(); /// Initialize the logger once. More than one init() attempt returns /// errors. pub fn init_logger() { LOGGER_INIT.call_once(env_logger::init); } devicemapper-0.34.4/src/testing/loopbacked.rs000064400000000000000000000067621046102023000173100ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{ fs::OpenOptions, io::{self, Seek, SeekFrom, Write}, panic, path::{Path, PathBuf}, }; use loopdev::{LoopControl, LoopDevice}; use tempfile::{self, TempDir}; use crate::{ consts::IEC, testing::{logger::init_logger, test_lib::clean_up}, units::{Bytes, Sectors, SECTOR_SIZE}, }; /// Write buf at offset length times. fn write_sectors>( path: P, offset: Sectors, length: Sectors, buf: &[u8; SECTOR_SIZE], ) -> io::Result<()> { let mut f = OpenOptions::new().write(true).open(path)?; f.seek(SeekFrom::Start((*offset.bytes()).try_into().unwrap()))?; for _ in 0..*length { f.write_all(buf)?; } f.sync_all()?; Ok(()) } /// Zero sectors at the given offset for length sectors. fn wipe_sectors>(path: P, offset: Sectors, length: Sectors) -> io::Result<()> { write_sectors(path, offset, length, &[0u8; SECTOR_SIZE]) } pub struct LoopTestDev { ld: LoopDevice, } impl LoopTestDev { fn new(lc: &LoopControl, path: &Path) -> LoopTestDev { OpenOptions::new() .read(true) .write(true) .open(path) .unwrap(); let ld = lc.next_free().unwrap(); ld.attach_file(path).unwrap(); // Wipe one MiB at the start of the device. Devicemapper data may be // left on the device even after a teardown. wipe_sectors( ld.path().unwrap(), Sectors(0), Bytes(u128::from(IEC::Mi)).sectors(), ) .unwrap(); LoopTestDev { ld } } fn path(&self) -> PathBuf { self.ld.path().unwrap() } fn detach(&self) { self.ld.detach().unwrap() } } impl Drop for LoopTestDev { fn drop(&mut self) { self.detach() } } /// Setup count loop backed devices in dir. /// Make sure each loop device is backed by a sparse 1 GiB file. The entire file will read back /// as initialized with zero. fn get_devices(count: u8, dir: &TempDir) -> Vec { let lc = LoopControl::open().unwrap(); let mut loop_devices = Vec::new(); for index in 0..count { let path = dir.path().join(format!("store{}", &index)); let f = OpenOptions::new() .read(true) .write(true) .create(true) .truncate(true) .open(&path) .unwrap(); nix::unistd::ftruncate(&f, IEC::Gi as nix::libc::off_t).unwrap(); f.sync_all().unwrap(); let ltd = LoopTestDev::new(&lc, &path); loop_devices.push(ltd); } loop_devices } /// Set up count loopbacked devices. /// Then, run the designated test. /// Then, take down the loop devices. pub fn test_with_spec(count: u8, test: F) where F: Fn(&[&Path]) + panic::RefUnwindSafe, { init_logger(); clean_up().unwrap(); let tmpdir = tempfile::Builder::new() .prefix("devicemapper") .tempdir() .unwrap(); let loop_devices: Vec = get_devices(count, &tmpdir); let device_paths: Vec = loop_devices.iter().map(|x| x.path()).collect(); let device_paths: Vec<&Path> = device_paths.iter().map(|x| x.as_path()).collect(); let result = panic::catch_unwind(|| test(&device_paths)); let tear_down = clean_up(); result.unwrap(); tear_down.unwrap(); } devicemapper-0.34.4/src/testing/mod.rs000064400000000000000000000007051046102023000157530ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Modules that support testing. mod logger; mod loopbacked; mod test_lib; pub use self::{ loopbacked::test_with_spec, test_lib::{ blkdev_size, test_name, test_string, test_uuid, udev_settle, xfs_create_fs, xfs_set_uuid, }, }; devicemapper-0.34.4/src/testing/test_lib.rs000064400000000000000000000200611046102023000167760ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{ fs::File, io::Read, os::unix::io::AsRawFd, panic::catch_unwind, path::{Path, PathBuf}, process::Command, sync::Once, }; use nix::mount::{umount2, MntFlags}; use uuid::Uuid; use crate::{ core::{DevId, Device, DmNameBuf, DmOptions, DmUuidBuf, DM}, result::{DmError, DmResult, ErrorEnum}, units::Bytes, }; static INIT: Once = Once::new(); static mut DM_CONTEXT: Option = None; impl DM { /// Returns a subset of the devices returned by list_devices(), namely /// the devices whose names end with DM_TEST_ID, our test device suffix. /// This function is useful for listing devices in tests that should not /// take non-test devices into account. pub fn list_test_devices(&self) -> Result)>> { let mut test_devs = self.list_devices()?; test_devs.retain(|x| x.0.as_bytes().ends_with(DM_TEST_ID.as_bytes())); Ok(test_devs) } } // send IOCTL via blkgetsize64 ioctl_read!( /// # Safety /// /// This function is a wrapper for `libc::ioctl` and therefore is unsafe for the same reasons /// as other libc bindings. It accepts a file descriptor and mutable pointer so the semantics /// of the invoked `ioctl` command should be examined to determine the effect it will have /// on the resources passed to the command. blkgetsize64, 0x12, 114, u64 ); /// get the size of a given block device file pub fn blkdev_size(file: &File) -> Bytes { let mut val: u64 = 0; unsafe { blkgetsize64(file.as_raw_fd(), &mut val) }.unwrap(); Bytes(u128::from(val)) } fn get_dm() -> &'static DM { unsafe { INIT.call_once(|| DM_CONTEXT = Some(DM::new().unwrap())); match DM_CONTEXT { Some(ref context) => context, _ => panic!("DM_CONTEXT.is_some()"), } } } /// String that is to be concatenated with test supplied name to identify /// devices and filesystems generated by tests. static DM_TEST_ID: &str = "_dm-rs_test_delme"; /// Generate a string with an identifying test suffix pub fn test_string(name: &str) -> String { let mut namestr = String::from(name); namestr.push_str(DM_TEST_ID); namestr } /// Execute command while collecting stdout & stderr. fn execute_cmd(cmd: &mut Command) -> DmResult<()> { match cmd.output() { Err(err) => Err(DmError::Dm( ErrorEnum::Error, format!("cmd: {cmd:?}, error '{err}'"), )), Ok(result) => { if result.status.success() { Ok(()) } else { let std_out_txt = String::from_utf8_lossy(&result.stdout); let std_err_txt = String::from_utf8_lossy(&result.stderr); let err_msg = format!("cmd: {cmd:?} stdout: {std_out_txt} stderr: {std_err_txt}"); Err(DmError::Dm(ErrorEnum::Error, err_msg)) } } } } /// Generate an XFS FS pub fn xfs_create_fs(devnode: &Path, uuid: Option) -> DmResult<()> { let mut command = Command::new("mkfs.xfs"); command.arg("-f"); command.arg("-q"); command.arg(devnode); if let Some(uuid) = uuid { command.arg("-m"); command.arg(format!("uuid={uuid}")); } execute_cmd(&mut command) } /// Set a UUID for a XFS volume. pub fn xfs_set_uuid(devnode: &Path, uuid: &Uuid) -> DmResult<()> { execute_cmd( Command::new("xfs_admin") .arg("-U") .arg(format!("{uuid}")) .arg(devnode), ) } /// Wait for udev activity to be done. pub fn udev_settle() -> DmResult<()> { execute_cmd(Command::new("udevadm").arg("settle")) } /// Generate the test name given the test supplied name. pub fn test_name(name: &str) -> DmResult { DmNameBuf::new(test_string(name)) } /// Generate the test uuid given the test supplied name. pub fn test_uuid(name: &str) -> DmResult { DmUuidBuf::new(test_string(name)) } mod cleanup_errors { use super::DmError; #[derive(Debug)] pub enum Error { Ioe(std::io::Error), Mnt(libmount::mountinfo::ParseError), Nix(nix::Error), Msg(String), Chained(String, Box), Dm(DmError), } pub type Result = std::result::Result; impl From for Error { fn from(err: nix::Error) -> Error { Error::Nix(err) } } impl From for Error { fn from(err: libmount::mountinfo::ParseError) -> Error { Error::Mnt(err) } } impl From for Error { fn from(err: std::io::Error) -> Error { Error::Ioe(err) } } impl From for Error { fn from(err: String) -> Error { Error::Msg(err) } } impl From for Error { fn from(err: DmError) -> Error { Error::Dm(err) } } } use self::cleanup_errors::{Error, Result}; /// Attempt to remove all device mapper devices which match the test naming convention. /// FIXME: Current implementation complicated by https://bugzilla.redhat.com/show_bug.cgi?id=1506287 fn dm_test_devices_remove() -> Result<()> { /// One iteration of removing devicemapper devices fn one_iteration() -> Result<(bool, Vec)> { let mut progress_made = false; let mut remain = Vec::new(); for n in get_dm() .list_test_devices() .map_err(|e| { Error::Chained( "failed while listing DM devices, giving up".into(), Box::new(e), ) })? .iter() .map(|d| &d.0) { match get_dm().device_remove(&DevId::Name(n), DmOptions::default()) { Ok(_) => progress_made = true, Err(_) => remain.push(n.to_string()), } } Ok((progress_made, remain)) } /// Do one iteration of removals until progress stops. Return remaining /// dm devices. fn do_while_progress() -> Result> { let mut result = one_iteration()?; while result.0 { result = one_iteration()?; } Ok(result.1) } || -> Result<()> { if catch_unwind(get_dm).is_err() { return Err("Unable to initialize DM".to_string().into()); } do_while_progress().and_then(|remain| { if !remain.is_empty() { let err_msg = format!("Some test-generated DM devices remaining: {remain:?}"); Err(err_msg.into()) } else { Ok(()) } }) }() .map_err(|e| { Error::Chained( "Failed to ensure removal of all test-generated DM devices".into(), Box::new(e), ) }) } /// Unmount any filesystems that contain DM_TEST_ID in the mount point. /// Return immediately on the first unmount failure. fn dm_test_fs_unmount() -> Result<()> { || -> Result<()> { let mut mount_data = String::new(); File::open("/proc/self/mountinfo")?.read_to_string(&mut mount_data)?; let parser = libmount::mountinfo::Parser::new(mount_data.as_bytes()); for mount_point in parser .filter_map(|x| x.ok()) .filter_map(|m| m.mount_point.into_owned().into_string().ok()) .filter(|mp| mp.contains(DM_TEST_ID)) { umount2(&PathBuf::from(mount_point), MntFlags::MNT_DETACH)?; } Ok(()) }() .map_err(|e| { Error::Chained( "Failed to ensure all test-generated filesystems were unmounted".into(), Box::new(e), ) }) } /// Unmount any filesystems or devicemapper devices which contain DM_TEST_ID /// in the path or name. Immediately return on first error. pub fn clean_up() -> Result<()> { dm_test_fs_unmount().and_then(|_| dm_test_devices_remove()) } devicemapper-0.34.4/src/thindev.rs000064400000000000000000000764121046102023000151700ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{fmt, path::PathBuf, str::FromStr}; use crate::{ core::{DevId, Device, DeviceInfo, DmFlags, DmName, DmOptions, DmUuid, DM}, result::{DmError, DmResult, ErrorEnum}, shared::{ device_create, device_exists, device_match, get_status, get_status_line_fields, message, parse_device, parse_value, DmDevice, TargetLine, TargetParams, TargetTable, TargetTypeBuf, }, thindevid::ThinDevId, thinpooldev::ThinPoolDev, units::Sectors, }; const THIN_TARGET_NAME: &str = "thin"; /// Struct representing params for a thin target #[derive(Clone, Debug, Eq, PartialEq)] pub struct ThinTargetParams { /// Thin pool for the given thin device pub pool: Device, /// Thin ID pub thin_id: ThinDevId, /// Optional block device outside of pool to be treated as a read-only snapshot /// origin pub external_origin_dev: Option, } impl ThinTargetParams { /// Create a new ThinTargetParams struct pub fn new( pool: Device, thin_id: ThinDevId, external_origin_dev: Option, ) -> ThinTargetParams { ThinTargetParams { pool, thin_id, external_origin_dev, } } } impl fmt::Display for ThinTargetParams { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {}", THIN_TARGET_NAME, self.param_str()) } } impl FromStr for ThinTargetParams { type Err = DmError; fn from_str(s: &str) -> DmResult { let vals = s.split(' ').collect::>(); let len = vals.len(); if !(3..=4).contains(&len) { let err_msg = format!("expected 3 or 4 values in params string \"{s}\", found {len}"); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } if vals[0] != THIN_TARGET_NAME { let err_msg = format!( "Expected a thin target entry but found target type {}", vals[0] ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } Ok(ThinTargetParams::new( parse_device(vals[1], "thinpool device for thin target")?, vals[2].parse::()?, if len == 3 { None } else { Some(parse_device( vals[3], "external origin device for thin snapshot", )?) }, )) } } impl TargetParams for ThinTargetParams { fn param_str(&self) -> String { match self.external_origin_dev { None => format!("{} {}", self.pool, self.thin_id), Some(dev) => format!("{} {} {}", self.pool, self.thin_id, dev), } } fn target_type(&self) -> TargetTypeBuf { TargetTypeBuf::new(THIN_TARGET_NAME.into()).expect("THIN_TARGET_NAME is valid") } } /// A target table for a thin device. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ThinDevTargetTable { /// The device's table pub table: TargetLine, } impl ThinDevTargetTable { /// Make a new ThinDevTargetTable from required input pub fn new(start: Sectors, length: Sectors, params: ThinTargetParams) -> ThinDevTargetTable { ThinDevTargetTable { table: TargetLine::new(start, length, params), } } } impl fmt::Display for ThinDevTargetTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let table = &self.table; writeln!(f, "{} {} {}", *table.start, *table.length, table.params) } } impl TargetTable for ThinDevTargetTable { fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult { if table.len() != 1 { let err_msg = format!( "ThinDev table should have exactly one line, has {} lines", table.len() ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let line = table.first().expect("table.len() == 1"); Ok(ThinDevTargetTable::new( Sectors(line.0), Sectors(line.1), format!("{} {}", line.2, line.3).parse::()?, )) } fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> { to_raw_table_unique!(self) } } /// DM construct for a thin block device #[derive(Debug)] pub struct ThinDev { dev_info: Box, table: ThinDevTargetTable, } impl DmDevice for ThinDev { fn device(&self) -> Device { device!(self) } fn devnode(&self) -> PathBuf { devnode!(self) } // This method is incomplete. It is expected that it will be refined so // that it will return true in more cases, i.e., to be less stringent. fn equivalent_tables(left: &ThinDevTargetTable, right: &ThinDevTargetTable) -> DmResult { Ok(left == right) } fn name(&self) -> &DmName { name!(self) } fn resume(&mut self, dm: &DM) -> DmResult<()> { dm.device_suspend(&DevId::Name(self.name()), DmOptions::default())?; Ok(()) } fn size(&self) -> Sectors { self.table.table.length } fn table(&self) -> &ThinDevTargetTable { table!(self) } fn teardown(&mut self, dm: &DM) -> DmResult<()> { dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?; Ok(()) } fn uuid(&self) -> Option<&DmUuid> { uuid!(self) } } /// Status values for a thin device that is working #[derive(Clone, Debug)] pub struct ThinDevWorkingStatus { /// The number of mapped sectors pub nr_mapped_sectors: Sectors, /// The highest mapped sector if any. pub highest_mapped_sector: Option, } impl ThinDevWorkingStatus { /// Make a new ThinDevWorkingStatus struct pub fn new( nr_mapped_sectors: Sectors, highest_mapped_sector: Option, ) -> ThinDevWorkingStatus { ThinDevWorkingStatus { nr_mapped_sectors, highest_mapped_sector, } } } #[derive(Clone, Debug)] /// Thin device status. pub enum ThinStatus { /// Thin device is good. Includes number of mapped sectors, and /// highest mapped sector. Working(Box), /// Devicemapper has reported that it could not obtain the status Error, /// Thin device is failed. Fail, } impl FromStr for ThinStatus { type Err = DmError; fn from_str(status_line: &str) -> DmResult { if status_line.starts_with("Error") { return Ok(ThinStatus::Error); } if status_line.starts_with("Fail") { return Ok(ThinStatus::Fail); } let status_vals = get_status_line_fields(status_line, 2)?; let count = Sectors(parse_value(status_vals[0], "sector count")?); let highest = if count == Sectors(0) { None } else { Some(Sectors(parse_value(status_vals[1], "highest used sector")?)) }; Ok(ThinStatus::Working(Box::new(ThinDevWorkingStatus::new( count, highest, )))) } } /// support use of DM for thin provisioned devices over pools impl ThinDev { /// Create a ThinDev using thin_pool as the backing store. /// If the specified thin_id is already in use by the thin pool an error /// is returned. If the device is already among the list of devices that /// dm is aware of, return an error. pub fn new( dm: &DM, name: &DmName, uuid: Option<&DmUuid>, length: Sectors, thin_pool: &ThinPoolDev, thin_id: ThinDevId, ) -> DmResult { message(dm, thin_pool, &format!("create_thin {thin_id}"))?; if device_exists(dm, name)? { let err_msg = "Uncreated device should not be known to kernel"; return Err(DmError::Dm(ErrorEnum::Invalid, err_msg.into())); } let thin_pool_device = thin_pool.device(); let table = ThinDev::gen_default_table(length, thin_pool_device, thin_id); let dev_info = device_create(dm, name, uuid, &table, DmOptions::default())?; Ok(ThinDev { dev_info: Box::new(dev_info), table, }) } /// Set up a thin device which already belongs to the given thin_pool. /// The thin device is identified by the thin_id, which is already /// known to the pool. /// /// If the device is already known to kernel, just verify that specified /// data matches and return an error if it does not. /// /// If the device has no thin id already registered with the thin pool /// an error is returned. pub fn setup( dm: &DM, name: &DmName, uuid: Option<&DmUuid>, length: Sectors, thin_pool: &ThinPoolDev, thin_id: ThinDevId, ) -> DmResult { let thin_pool_device = thin_pool.device(); let table = ThinDev::gen_default_table(length, thin_pool_device, thin_id); let dev = if device_exists(dm, name)? { let dev_info = dm.device_info(&DevId::Name(name))?; let dev = ThinDev { dev_info: Box::new(dev_info), table, }; device_match(dm, &dev, uuid)?; dev } else { let dev_info = device_create(dm, name, uuid, &table, DmOptions::default())?; ThinDev { dev_info: Box::new(dev_info), table, } }; Ok(dev) } /// Create a snapshot of a ThinDev. Once created a snapshot /// is the same as any other thin provisioned device. There is /// no need to track any connection between the source and the /// snapshot. pub fn snapshot( &self, dm: &DM, snapshot_name: &DmName, snapshot_uuid: Option<&DmUuid>, thin_pool: &ThinPoolDev, snapshot_thin_id: ThinDevId, ) -> DmResult { let source_id = DevId::Name(self.name()); dm.device_suspend( &source_id, DmOptions::default().set_flags(DmFlags::DM_SUSPEND), )?; message( dm, thin_pool, &format!( "create_snap {} {}", snapshot_thin_id, self.table.table.params.thin_id ), )?; dm.device_suspend(&source_id, DmOptions::default())?; let table = ThinDev::gen_default_table(self.size(), thin_pool.device(), snapshot_thin_id); let dev_info = Box::new(device_create( dm, snapshot_name, snapshot_uuid, &table, DmOptions::default(), )?); Ok(ThinDev { dev_info, table }) } /// Generate a table to be passed to DM. The format of the table /// entries is: /// "thin" /// where the thin device specific string has the format: /// /// There is exactly one entry in the table. /// Various defaults are hard coded in the method. fn gen_default_table( length: Sectors, thin_pool: Device, thin_id: ThinDevId, ) -> ThinDevTargetTable { ThinDevTargetTable::new( Sectors::default(), length, ThinTargetParams::new(thin_pool, thin_id, None), ) } /// return the thin id of the linear device pub fn id(&self) -> ThinDevId { self.table.table.params.thin_id } /// Get the current status of the thin device. pub fn status(&self, dm: &DM, options: DmOptions) -> DmResult { status!(self, dm, options) } /// Set the table for the thin device's target pub fn set_table(&mut self, dm: &DM, table: TargetLine) -> DmResult<()> { let table = ThinDevTargetTable::new(table.start, table.length, table.params); self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.table_load(dm, &table, DmOptions::default())?; self.resume(dm)?; self.table = table; Ok(()) } /// Tear down the DM device, and also delete resources associated /// with its thin id from the thinpool. pub fn destroy(&mut self, dm: &DM, thin_pool: &ThinPoolDev) -> DmResult<()> { let thin_id = self.table.table.params.thin_id; self.teardown(dm)?; message(dm, thin_pool, &format!("delete {thin_id}"))?; Ok(()) } } #[cfg(test)] mod tests { use std::{ fs::{canonicalize, OpenOptions}, io::Write, path::Path, }; use nix::mount::{mount, umount2, MntFlags, MsFlags}; use uuid::Uuid; use crate::{ consts::IEC, core::errors::Error, shared::DmDevice, testing::{ blkdev_size, test_name, test_string, test_uuid, test_with_spec, udev_settle, xfs_create_fs, xfs_set_uuid, }, thinpooldev::{minimal_thinpool, ThinPoolStatus}, units::DataBlocks, }; use super::*; const MIN_THIN_DEV_SIZE: Sectors = Sectors(1); /// Verify that specifying a size of 0 Sectors will cause a failure. fn test_zero_size(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); assert_matches!( ThinDev::new( &dm, &test_name("name").expect("is valid DM name"), None, Sectors(0), &tp, ThinDevId::new_u64(0).expect("is below limit") ), Err(_) ); udev_settle().unwrap(); tp.teardown(&dm).unwrap(); } /// Verify that setting up a thin device without first calling new() /// causes an error. The underlying reason is that the thin pool hasn't /// been informed about the thin device by messaging the value of the /// thin id. fn test_setup_without_new(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); let td_size = MIN_THIN_DEV_SIZE; assert_matches!( ThinDev::setup( &dm, &test_name("name").expect("is valid DM name"), None, td_size, &tp, ThinDevId::new_u64(0).expect("is below limit") ), Err(DmError::Core(Error::Ioctl(_, _, _, _))) ); tp.teardown(&dm).unwrap(); } /// Verify success when constructing a new ThinDev. Check that the /// status of the device is as expected. Verify that it is now possible /// to call setup() on the thin dev specifying the same name and id. /// Verify that calling new() for the second time fails. Verify that /// setup() is idempotent, calling setup() twice in succession succeeds. /// Verify that setup() succeeds on an existing device, whether or not /// it has been torn down. fn test_basic(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); let thin_id = ThinDevId::new_u64(0).expect("is below limit"); let id = test_name("name").expect("is valid DM name"); let td_size = MIN_THIN_DEV_SIZE; let mut td = ThinDev::new(&dm, &id, None, td_size, &tp, thin_id).unwrap(); udev_settle().unwrap(); let table = ThinDev::read_kernel_table(&dm, &DevId::Name(td.name())) .unwrap() .table; assert_eq!(table.params.pool, tp.device()); assert_eq!(table.params.thin_id, thin_id); assert_matches!( td.status(&dm, DmOptions::default()).unwrap(), ThinStatus::Error | ThinStatus::Working(_) ); assert_eq!( blkdev_size(&OpenOptions::new().read(true).open(td.devnode()).unwrap()), td_size.bytes() ); // New thindev w/ same id fails. assert_matches!( ThinDev::new(&dm, &id, None, td_size, &tp, thin_id), Err(DmError::Core(Error::Ioctl(_, _, _, _))) ); // Verify that the device of that name does exist. assert!(device_exists(&dm, &id).unwrap()); // Setting up the just created thin dev succeeds. assert_matches!(ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id), Ok(_)); udev_settle().unwrap(); // Setting up the just created thin dev once more succeeds. assert_matches!(ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id), Ok(_)); // Teardown the thindev, then set it back up. td.teardown(&dm).unwrap(); let mut td = ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id).unwrap(); udev_settle().unwrap(); td.destroy(&dm, &tp).unwrap(); tp.teardown(&dm).unwrap(); } /// Test thin device create, load, and snapshot and make sure that all is well with udev /// db and symlink generation. fn test_udev_userspace(paths: &[&Path]) { // Confirm that the correct symlink has been constructed. fn validate(path_uuid: &Uuid, devnode: &Path) { udev_settle().unwrap(); // Make sure the uuid symlink was created let symlink = PathBuf::from(format!("/dev/disk/by-uuid/{path_uuid}")); assert!(symlink.exists()); assert_eq!(*devnode, canonicalize(symlink).unwrap()); } // Set the FS with devnode to a new auto generated UUID fn set_new_fs_uuid(devnode: &Path) -> Uuid { // Tmp mount & umount to complete the XFS transactions so that we can change the UUID let tmp_dir = tempfile::Builder::new() .prefix(&test_string("test_udev_userspace_mp")) .tempdir() .unwrap(); mount( Some(devnode), tmp_dir.path(), Some("xfs"), MsFlags::empty(), None as Option<&str>, ) .unwrap(); umount2(tmp_dir.path(), MntFlags::MNT_DETACH).unwrap(); // Set the fs UUID to something new let new_uuid = Uuid::new_v4(); xfs_set_uuid(devnode, &new_uuid).unwrap(); new_uuid } let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); let thin_id = ThinDevId::new_u64(0).expect("is below limit"); let id = test_name("udev_test_thin_dev").expect("is valid DM name"); let mut td = ThinDev::new(&dm, &id, None, tp.size(), &tp, thin_id).unwrap(); udev_settle().unwrap(); let uuid = Uuid::new_v4(); // Create the XFS FS on top of the thin device xfs_create_fs(&td.devnode(), Some(uuid)).unwrap(); // Synchronize with udev processing triggered by xfs_create_fs() udev_settle().unwrap(); validate(&uuid, &td.devnode()); // Teardown the thindev, then set it back up and make sure all is well with udev td.teardown(&dm).unwrap(); td = ThinDev::setup(&dm, &id, None, tp.size(), &tp, thin_id).unwrap(); validate(&uuid, &td.devnode()); // Create a snapshot and make sure we get correct actions in user space WRT udev let ss_id = ThinDevId::new_u64(1).expect("is below limit"); let ss_name = test_name("snap_name").expect("is valid DM name"); let mut ss = td.snapshot(&dm, &ss_name, None, &tp, ss_id).unwrap(); udev_settle().unwrap(); let ss_new_uuid = set_new_fs_uuid(&ss.devnode()); // Validate that the symlink for original and snapshot are correct validate(&ss_new_uuid, &ss.devnode()); validate(&uuid, &td.devnode()); // Tear everything down ss.destroy(&dm, &tp).unwrap(); td.destroy(&dm, &tp).unwrap(); tp.teardown(&dm).unwrap(); } /// Verify success when taking a snapshot of a ThinDev. Check that /// the size of the snapshot is the same as the source. /// Verify that empty thindev has no data usage. fn test_snapshot(paths: &[&Path]) { assert!(!paths.is_empty()); let td_size = MIN_THIN_DEV_SIZE; let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); let orig_data_usage = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert_eq!(orig_data_usage, DataBlocks(0)); // Create new ThinDev as source for snapshot let thin_id = ThinDevId::new_u64(0).expect("is below limit"); let thin_name = test_name("name").expect("is valid DM name"); let mut td = ThinDev::new(&dm, &thin_name, None, td_size, &tp, thin_id).unwrap(); udev_settle().unwrap(); let data_usage_1 = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert_eq!(data_usage_1, DataBlocks(0)); // Create a snapshot of the source let ss_id = ThinDevId::new_u64(1).expect("is below limit"); let ss_name = test_name("snap_name").expect("is valid DM name"); let mut ss = td.snapshot(&dm, &ss_name, None, &tp, ss_id).unwrap(); udev_settle().unwrap(); let data_usage_2 = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert_eq!(data_usage_2, DataBlocks(0)); // Verify the source and the snapshot are the same size. assert_eq!(td.size(), ss.size()); ss.destroy(&dm, &tp).unwrap(); td.destroy(&dm, &tp).unwrap(); tp.teardown(&dm).unwrap(); } /// Verify no failures when creating a thindev from a pool, mounting a /// filesystem on the thin device, and writing to that filesystem. /// Verify reasonable usage behavior. fn test_filesystem(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); let thin_id = ThinDevId::new_u64(0).expect("is below limit"); let thin_name = test_name("name").expect("is valid DM name"); let mut td = ThinDev::new(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap(); udev_settle().unwrap(); let orig_data_usage = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert_eq!(orig_data_usage, DataBlocks(0)); xfs_create_fs(&td.devnode(), None).unwrap(); let data_usage_1 = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert!(data_usage_1 > DataBlocks(0)); let tmp_dir = tempfile::Builder::new() .prefix(&test_string("devicemapper_testing")) .tempdir() .unwrap(); mount( Some(&td.devnode()), tmp_dir.path(), Some("xfs"), MsFlags::empty(), None as Option<&str>, ) .unwrap(); for i in 0..100 { let file_path = tmp_dir.path().join(format!("devicemapper_test{i}.txt")); writeln!( &OpenOptions::new() .create(true) .truncate(true) .write(true) .open(file_path) .unwrap(), "data" ) .unwrap(); } umount2(tmp_dir.path(), MntFlags::MNT_DETACH).unwrap(); let data_usage_2 = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert!(data_usage_2 > data_usage_1); td.destroy(&dm, &tp).unwrap(); tp.teardown(&dm).unwrap(); } /// Verify reasonable usage behavior when taking a snapshot of a thindev /// with an existing filesystem. In particular, just taking a snapshot /// should not increase the pool usage at all. /// If ThindevA is one GiB and ThindevB is 1 TiB, the making a filesystem /// on ThindevB consumes at least 32 times the space as making a filesystem /// on ThindevA. Verify that setting the UUID of a snapshot causes the /// snapshot to consume approximately the same amount of space as its /// source. fn test_snapshot_usage(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); let thin_id = ThinDevId::new_u64(0).expect("is below limit"); let thin_name = test_name("name").expect("is valid DM name"); let mut td = ThinDev::new(&dm, &thin_name, None, Sectors(2 * IEC::Mi), &tp, thin_id).unwrap(); udev_settle().unwrap(); let orig_data_usage = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert_eq!(orig_data_usage, DataBlocks(0)); xfs_create_fs(&td.devnode(), None).unwrap(); let data_usage_1 = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert!(data_usage_1 > DataBlocks(0)); // Create a snapshot of the source let ss_id = ThinDevId::new_u64(1).expect("is below limit"); let ss_name = test_name("snap_name").expect("is valid DM name"); let ss_uuid = test_uuid("snap_uuid").expect("is valid DM uuid"); let mut ss = td .snapshot(&dm, &ss_name, Some(&ss_uuid), &tp, ss_id) .unwrap(); udev_settle().unwrap(); let data_usage_2 = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert_eq!(data_usage_2, data_usage_1); xfs_set_uuid(&ss.devnode(), &Uuid::new_v4()).unwrap(); // Setting the uuid of the snapshot filesystem bumps the usage, // but does not increase the usage quite as much as establishing // the origin. let data_usage_3 = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert!(data_usage_3 - data_usage_2 > DataBlocks(0)); assert!(data_usage_3 - data_usage_2 < data_usage_1); assert!(data_usage_3 - data_usage_2 > data_usage_1 / 2usize); let thin_id = ThinDevId::new_u64(2).expect("is below limit"); let thin_name = test_name("name1").expect("is valid DM name"); let mut td1 = ThinDev::new(&dm, &thin_name, None, Sectors(2 * IEC::Gi), &tp, thin_id).unwrap(); udev_settle().unwrap(); let data_usage_4 = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert_eq!(data_usage_4, data_usage_3); xfs_create_fs(&td1.devnode(), None).unwrap(); let data_usage_5 = match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => status.usage.used_data, ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("failed to get thinpool status"), }; assert!(data_usage_5 - data_usage_4 > 32usize * data_usage_1); ss.destroy(&dm, &tp).unwrap(); td1.destroy(&dm, &tp).unwrap(); td.destroy(&dm, &tp).unwrap(); tp.teardown(&dm).unwrap(); } /// Verify that destroy() actually deallocates the space from the /// thinpool, by attempting to reinstantiate it using the same thin id and /// verifying that it fails. fn test_thindev_destroy(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); let thin_id = ThinDevId::new_u64(0).expect("is below limit"); let thin_name = test_name("name").expect("is valid DM name"); let mut td = ThinDev::new(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap(); td.teardown(&dm).unwrap(); // This should work let mut td = ThinDev::setup(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap(); td.destroy(&dm, &tp).unwrap(); // This should fail assert_matches!( ThinDev::setup(&dm, &thin_name, None, tp.size(), &tp, thin_id), Err(DmError::Core(Error::Ioctl(_, _, _, _))) ); tp.teardown(&dm).unwrap(); } #[test] fn loop_test_basic() { test_with_spec(1, test_basic); } #[test] fn loop_test_basic_udev() { test_with_spec(1, test_udev_userspace); } #[test] fn loop_test_zero_size() { test_with_spec(1, test_zero_size); } #[test] fn loop_test_setup_without_new() { test_with_spec(1, test_setup_without_new); } #[test] fn loop_test_snapshot() { test_with_spec(1, test_snapshot); } #[test] fn loop_test_snapshot_usage() { test_with_spec(1, test_snapshot_usage); } #[test] fn loop_test_filesystem() { test_with_spec(1, test_filesystem); } #[test] fn loop_test_thindev_destroy() { test_with_spec(1, test_thindev_destroy); } } devicemapper-0.34.4/src/thindevid.rs000064400000000000000000000042651046102023000155020ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{fmt, str::FromStr}; use crate::{ result::{DmError, DmResult, ErrorEnum}, shared::parse_value, }; const THIN_DEV_ID_LIMIT: u64 = 0x0100_0000; // 2 ^ 24 #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] /// A thindev id is a 24 bit number, i.e., its bit width is not a power of 2. pub struct ThinDevId { value: u32, } impl ThinDevId { /// Make a new ThinDevId. /// Return an error if value is too large to represent in 24 bits. pub fn new_u64(value: u64) -> DmResult { if value < THIN_DEV_ID_LIMIT { Ok(ThinDevId { value: value as u32, }) } else { Err(DmError::Dm( ErrorEnum::Invalid, format!("argument {value} unrepresentable in 24 bits"), )) } } } impl From for u32 { fn from(id: ThinDevId) -> u32 { id.value } } impl fmt::Display for ThinDevId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.value, f) } } impl FromStr for ThinDevId { type Err = DmError; fn from_str(s: &str) -> Result { ThinDevId::new_u64(parse_value(s, "thindev id")?) } } impl serde::Serialize for ThinDevId { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_u32(self.value) } } impl<'de> serde::Deserialize<'de> for ThinDevId { fn deserialize(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { Ok(ThinDevId { value: serde::Deserialize::deserialize(deserializer)?, }) } } #[cfg(test)] mod tests { use super::*; #[test] /// Verify that new_checked_u64 discriminates. fn test_new_checked_u64() { assert_matches!(ThinDevId::new_u64(2u64.pow(32)), Err(_)); assert_matches!(ThinDevId::new_u64(THIN_DEV_ID_LIMIT - 1), Ok(_)); } } devicemapper-0.34.4/src/thinpooldev.rs000064400000000000000000001036661046102023000160640ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{collections::hash_set::HashSet, fmt, path::PathBuf, str::FromStr}; use crate::{ core::{DevId, Device, DeviceInfo, DmFlags, DmName, DmOptions, DmUuid, DM}, lineardev::{LinearDev, LinearDevTargetParams}, result::{DmError, DmResult, ErrorEnum}, shared::{ device_create, device_exists, device_match, get_status, get_status_line_fields, make_unexpected_value_error, parse_device, parse_value, DmDevice, TargetLine, TargetParams, TargetTable, TargetTypeBuf, }, units::{DataBlocks, MetaBlocks, Sectors}, }; #[cfg(test)] use std::path::Path; #[cfg(test)] use crate::core::devnode_to_devno; const THINPOOL_TARGET_NAME: &str = "thin-pool"; /// Struct representing params for a thin pool target #[derive(Clone, Debug, Eq, PartialEq)] pub struct ThinPoolTargetParams { /// Thin pool metadata device pub metadata_dev: Device, /// Thin pool data device pub data_dev: Device, /// Block size for allocations within the thin pool pub data_block_size: Sectors, /// Amount of free space left at which to trigger the low water mark pub low_water_mark: DataBlocks, /// Feature arguments pub feature_args: HashSet, } impl ThinPoolTargetParams { /// Create a new ThinPoolTargetParams struct pub fn new( metadata_dev: Device, data_dev: Device, data_block_size: Sectors, low_water_mark: DataBlocks, feature_args: Vec, ) -> ThinPoolTargetParams { ThinPoolTargetParams { metadata_dev, data_dev, data_block_size, low_water_mark, feature_args: feature_args.into_iter().collect::>(), } } } impl fmt::Display for ThinPoolTargetParams { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {}", THINPOOL_TARGET_NAME, self.param_str()) } } impl FromStr for ThinPoolTargetParams { type Err = DmError; fn from_str(s: &str) -> DmResult { let vals = s.split(' ').collect::>(); if vals.len() < 5 { let err_msg = format!( "expected at least 5 values in params string \"{}\", found {}", s, vals.len() ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } if vals[0] != THINPOOL_TARGET_NAME { let err_msg = format!( "Expected a thin-pool target entry but found target type {}", vals[0] ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let metadata_dev = parse_device(vals[1], "metadata device for thinpool target")?; let data_dev = parse_device(vals[2], "data device for thinpool target")?; let data_block_size = Sectors(parse_value(vals[3], "data block size")?); let low_water_mark = DataBlocks(parse_value(vals[4], "low water mark")?); let feature_args = if vals.len() == 5 { vec![] } else { vals[6..6 + parse_value::(vals[5], "number of feature args")?] .iter() .map(|x| (*x).to_string()) .collect() }; Ok(ThinPoolTargetParams::new( metadata_dev, data_dev, data_block_size, low_water_mark, feature_args, )) } } impl TargetParams for ThinPoolTargetParams { fn param_str(&self) -> String { let feature_args = if self.feature_args.is_empty() { "0".to_owned() } else { format!( "{} {}", self.feature_args.len(), self.feature_args .iter() .cloned() .collect::>() .join(" ") ) }; format!( "{} {} {} {} {}", self.metadata_dev, self.data_dev, *self.data_block_size, *self.low_water_mark, feature_args ) } fn target_type(&self) -> TargetTypeBuf { TargetTypeBuf::new(THINPOOL_TARGET_NAME.into()).expect("THINPOOL_TARGET_NAME is valid") } } /// A target table for a thin pool device. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ThinPoolDevTargetTable { /// The device's table pub table: TargetLine, } impl ThinPoolDevTargetTable { /// Make a new ThinPoolDevTargetTable from a suitable vec pub fn new( start: Sectors, length: Sectors, params: ThinPoolTargetParams, ) -> ThinPoolDevTargetTable { ThinPoolDevTargetTable { table: TargetLine::new(start, length, params), } } } impl fmt::Display for ThinPoolDevTargetTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let table = &self.table; writeln!(f, "{} {} {}", *table.start, *table.length, table.params) } } impl TargetTable for ThinPoolDevTargetTable { fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult { if table.len() != 1 { let err_msg = format!( "ThinPoolDev table should have exactly one line, has {} lines", table.len() ); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let line = table.first().expect("table.len() == 1"); Ok(ThinPoolDevTargetTable::new( Sectors(line.0), Sectors(line.1), format!("{} {}", line.2, line.3).parse::()?, )) } fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> { to_raw_table_unique!(self) } } /// DM construct to contain thin provisioned devices #[derive(Debug)] pub struct ThinPoolDev { dev_info: Box, meta_dev: LinearDev, data_dev: LinearDev, table: ThinPoolDevTargetTable, } impl DmDevice for ThinPoolDev { fn device(&self) -> Device { device!(self) } fn devnode(&self) -> PathBuf { devnode!(self) } // This method is incomplete. It is expected that it will be refined so // that it will return true in more cases, i.e., to be less stringent. // In particular, two devices are equivalent even if their low water // marks are different. fn equivalent_tables( left: &ThinPoolDevTargetTable, right: &ThinPoolDevTargetTable, ) -> DmResult { let left = &left.table; let right = &right.table; Ok(left.start == right.start && left.length == right.length && left.params.metadata_dev == right.params.metadata_dev && left.params.data_dev == right.params.data_dev && left.params.data_block_size == right.params.data_block_size) } fn name(&self) -> &DmName { name!(self) } fn size(&self) -> Sectors { self.data_dev.size() } fn table(&self) -> &ThinPoolDevTargetTable { table!(self) } fn teardown(&mut self, dm: &DM) -> DmResult<()> { dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?; self.data_dev.teardown(dm)?; self.meta_dev.teardown(dm)?; Ok(()) } fn uuid(&self) -> Option<&DmUuid> { uuid!(self) } } #[derive(Debug, Clone)] /// Contains values indicating the thinpool's used vs total /// allocations for metadata and data blocks. pub struct ThinPoolUsage { /// The number of metadata blocks that are in use. pub used_meta: MetaBlocks, /// The total number of metadata blocks available to the thinpool. pub total_meta: MetaBlocks, /// The number of data blocks that are in use. pub used_data: DataBlocks, /// The total number of data blocks available to the thinpool. pub total_data: DataBlocks, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] /// Indicates if a working thinpool is working optimally, or is /// experiencing a non-fatal error condition. pub enum ThinPoolStatusSummary { /// The pool is working normally. Good, /// The pool has been forced to transition to read-only mode. ReadOnly, /// The pool is out of space. OutOfSpace, } /// Policy if no space on device #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ThinPoolNoSpacePolicy { /// error the IO if no space on device Error, /// queue the IO if no space on device Queue, } /// Status of a working thin pool, i.e, one that does not have status Fail #[derive(Debug, Clone)] pub struct ThinPoolWorkingStatus { /// The transaction id. pub transaction_id: u64, /// A struct recording block usage for meta and data devices. pub usage: ThinPoolUsage, /// A single block value indicating the held metadata root pub held_metadata_root: Option, /// discard_passdown/no_discard_passdown pub discard_passdown: bool, /// no space policy pub no_space_policy: ThinPoolNoSpacePolicy, /// A summary of some other status information. pub summary: ThinPoolStatusSummary, /// needs_check flag has been set in metadata superblock pub needs_check: bool, /// The lowater value for the metadata device in metablocks. This value /// is set by the kernel. Available in kernel version 4.19 and later. pub meta_low_water: Option, } impl ThinPoolWorkingStatus { /// Make a new ThinPoolWorkingStatus struct #[allow(clippy::too_many_arguments)] pub fn new( transaction_id: u64, usage: ThinPoolUsage, held_metadata_root: Option, discard_passdown: bool, no_space_policy: ThinPoolNoSpacePolicy, summary: ThinPoolStatusSummary, needs_check: bool, meta_low_water: Option, ) -> ThinPoolWorkingStatus { ThinPoolWorkingStatus { transaction_id, usage, held_metadata_root, discard_passdown, no_space_policy, summary, needs_check, meta_low_water, } } } #[derive(Debug, Clone)] /// Top-level thinpool status that indicates if it is working or failed. pub enum ThinPoolStatus { /// The thinpool has not failed utterly. Working(Box), /// Devicemapper has reported that it could not obtain the status Error, /// The thinpool is in a failed condition. Fail, } impl FromStr for ThinPoolStatus { type Err = DmError; fn from_str(status_line: &str) -> DmResult { if status_line.starts_with("Error") { return Ok(ThinPoolStatus::Error); } if status_line.starts_with("Fail") { return Ok(ThinPoolStatus::Fail); } let status_vals = get_status_line_fields(status_line, 8)?; let transaction_id = parse_value(status_vals[0], "transaction id")?; let usage = { let meta_vals = status_vals[1].split('/').collect::>(); let data_vals = status_vals[2].split('/').collect::>(); ThinPoolUsage { used_meta: MetaBlocks(parse_value(meta_vals[0], "used meta")?), total_meta: MetaBlocks(parse_value(meta_vals[1], "total meta")?), used_data: DataBlocks(parse_value(data_vals[0], "used data")?), total_data: DataBlocks(parse_value(data_vals[1], "total data")?), } }; let held_metadata_root = match status_vals[3] { "-" => None, val => Some(MetaBlocks(parse_value(val, "held metadata root")?)), }; let summary = match status_vals[4] { "rw" => ThinPoolStatusSummary::Good, "ro" => ThinPoolStatusSummary::ReadOnly, "out_of_data_space" => ThinPoolStatusSummary::OutOfSpace, val => { return Err(make_unexpected_value_error(5, val, "summary")); } }; let discard_passdown = match status_vals[5] { "discard_passdown" => true, "no_discard_passdown" => false, val => { return Err(make_unexpected_value_error(6, val, "discard passdown")); } }; let no_space_policy = match status_vals[6] { "error_if_no_space" => ThinPoolNoSpacePolicy::Error, "queue_if_no_space" => ThinPoolNoSpacePolicy::Queue, val => { return Err(make_unexpected_value_error(7, val, "no space policy")); } }; let needs_check = match status_vals[7] { "-" => false, "needs_check" => true, val => { return Err(make_unexpected_value_error(8, val, "needs check")); } }; let meta_low_water = status_vals .get(8) .and_then(|v| parse_value(v, "meta low water").ok()); Ok(ThinPoolStatus::Working(Box::new( ThinPoolWorkingStatus::new( transaction_id, usage, held_metadata_root, discard_passdown, no_space_policy, summary, needs_check, meta_low_water, ), ))) } } /// Use DM to create a "thin-pool". A "thin-pool" is shared space for /// other thin provisioned devices to use. /// /// See section ["Setting up a fresh pool device"](https://docs.kernel.org/admin-guide/device-mapper/thin-provisioning.html#setting-up-a-fresh-pool-device) impl ThinPoolDev { /// Construct a new `ThinPoolDev` with the given data and meta devs. /// Returns an error if the device is already known to the kernel. /// Returns an error if `data_block_size` is not within required range. /// Precondition: the metadata device does not contain any pool metadata. #[allow(clippy::too_many_arguments)] pub fn new( dm: &DM, name: &DmName, uuid: Option<&DmUuid>, meta: LinearDev, data: LinearDev, data_block_size: Sectors, low_water_mark: DataBlocks, feature_args: Vec, ) -> DmResult { if device_exists(dm, name)? { let err_msg = format!("thinpooldev {name} already exists"); return Err(DmError::Dm(ErrorEnum::Invalid, err_msg)); } let table = ThinPoolDev::gen_table(&meta, &data, data_block_size, low_water_mark, feature_args); let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?; Ok(ThinPoolDev { dev_info: Box::new(dev_info), meta_dev: meta, data_dev: data, table, }) } /// Obtain the meta device that backs this thin pool device. pub fn meta_dev(&self) -> &LinearDev { &self.meta_dev } /// Obtain the data device that backs this thin pool device. pub fn data_dev(&self) -> &LinearDev { &self.data_dev } /// Obtain the data block size for this thin pool device. pub fn data_block_size(&self) -> Sectors { self.table.table.params.data_block_size } /// Set up a thin pool from the given metadata and data device. /// Returns an error if data_block_size is not within required range. /// Precondition: There is existing metadata for this thinpool device /// on the metadata device. If the metadata is corrupted, subsequent /// errors will result, so it is expected that the metadata is /// well-formed and consistent with the data on the data device. #[allow(clippy::too_many_arguments)] pub fn setup( dm: &DM, name: &DmName, uuid: Option<&DmUuid>, meta: LinearDev, data: LinearDev, data_block_size: Sectors, low_water_mark: DataBlocks, feature_args: Vec, ) -> DmResult { let table = ThinPoolDev::gen_table(&meta, &data, data_block_size, low_water_mark, feature_args); let dev = if device_exists(dm, name)? { let dev_info = dm.device_info(&DevId::Name(name))?; let dev = ThinPoolDev { dev_info: Box::new(dev_info), meta_dev: meta, data_dev: data, table, }; device_match(dm, &dev, uuid)?; dev } else { let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?; ThinPoolDev { dev_info: Box::new(dev_info), meta_dev: meta, data_dev: data, table, } }; Ok(dev) } /// Generate a table to be passed to DM. The format of the table /// entries is: /// "thin-pool" /// where the thin-pool-specific string has the format: /// /// There is exactly one entry in the table. fn gen_table( meta: &LinearDev, data: &LinearDev, data_block_size: Sectors, low_water_mark: DataBlocks, feature_args: Vec, ) -> ThinPoolDevTargetTable { ThinPoolDevTargetTable::new( Sectors::default(), data.size(), ThinPoolTargetParams::new( meta.device(), data.device(), data_block_size, low_water_mark, feature_args, ), ) } /// Set the low water mark. /// This action puts the device in a state where it is ready to be resumed. pub fn set_low_water_mark(&mut self, dm: &DM, low_water_mark: DataBlocks) -> DmResult<()> { let mut new_table = self.table.clone(); new_table.table.params.low_water_mark = low_water_mark; self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.table_load(dm, &new_table, DmOptions::default())?; self.table = new_table; Ok(()) } /// Get the current status of the thinpool. /// Returns an error if there was an error getting the status value. pub fn status(&self, dm: &DM, options: DmOptions) -> DmResult { status!(self, dm, options) } /// Set the table for the existing metadata device. /// This action puts the device in a state where it is ready to be resumed. /// Warning: It is the client's responsibility to make sure the designated /// table is compatible with the device's existing table. /// If are not, this function will still succeed, but some kind of /// data corruption will be the inevitable result. pub fn set_meta_table( &mut self, dm: &DM, table: Vec>, ) -> DmResult<()> { self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.meta_dev.set_table(dm, table)?; self.meta_dev.resume(dm)?; // Reload the table even though it is unchanged. // See comment on CacheDev::set_cache_table for reason. self.table_load(dm, self.table(), DmOptions::default())?; Ok(()) } /// Set the data device's existing table. /// This action puts the device in a state where it is ready to be resumed. /// Warning: It is the client's responsibility to make sure the designated /// table is compatible with the device's existing table. /// If not, this function will still succeed, but some kind of /// data corruption will be the inevitable result. pub fn set_data_table( &mut self, dm: &DM, table: Vec>, ) -> DmResult<()> { self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.data_dev.set_table(dm, table)?; self.data_dev.resume(dm)?; let mut table = self.table.clone(); table.table.length = self.data_dev.size(); self.table_load(dm, &table, DmOptions::default())?; self.table = table; Ok(()) } fn set_feature_arg(&mut self, feature_arg: &str, dm: &DM) -> DmResult<()> { let mut table = self.table().clone(); if !table.table.params.feature_args.contains(feature_arg) { table .table .params .feature_args .insert(feature_arg.to_string()); self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.table_load(dm, &table, DmOptions::default())?; self.table = table; self.resume(dm)?; } Ok(()) } fn unset_feature_arg(&mut self, feature_arg: &str, dm: &DM) -> DmResult<()> { let mut table = self.table().clone(); if table.table.params.feature_args.contains(feature_arg) { table.table.params.feature_args.remove(feature_arg); self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?; self.table_load(dm, &table, DmOptions::default())?; self.table = table; self.resume(dm)?; } Ok(()) } /// Default behavior for devicemapper thin pools is to queue requests if /// the thin pool is out of space to allow time for the thin pool to extend. /// This behavior can be changed by adding the feature argument /// `error_if_no_space` to the devicemapper table. /// /// This method will add `error_if_no_space` from the devicemapper table /// if it is not present. pub fn error_if_no_space(&mut self, dm: &DM) -> DmResult<()> { self.set_feature_arg("error_if_no_space", dm) } /// Default behavior for devicemapper thin pools is to queue requests if /// the thin pool is out of space to allow time for the thin pool to extend. /// This behavior can be changed by adding the feature argument /// `error_if_no_space` to the devicemapper table. /// /// This method will remove `error_if_no_space` from the devicemapper table /// if it is present. pub fn queue_if_no_space(&mut self, dm: &DM) -> DmResult<()> { self.unset_feature_arg("error_if_no_space", dm) } /// Default behavior for devicemapper thin pools is to zero newly allocated /// data blocks. This behavior can be changed by adding the feature argument /// `skip_block_zeroing` to the devicemapper table. /// /// This method will add `skip_block_zeroing` from the devicemapper table /// if it is not present. pub fn skip_block_zeroing(&mut self, dm: &DM) -> DmResult<()> { self.set_feature_arg("skip_block_zeroing", dm) } /// Default behavior for devicemapper thin pools is to zero newly allocated /// data blocks. This behavior can be changed by adding the feature argument /// `skip_block_zeroing` to the devicemapper table. /// /// This method will remove `skip_block_zeroing` from the devicemapper table /// if it is present. pub fn require_block_zeroing(&mut self, dm: &DM) -> DmResult<()> { self.unset_feature_arg("skip_block_zeroing", dm) } /// Default behavior for devicemapper thin pools is to pass down discards. /// This behavior can be changed by adding the feature argument /// `no_discard_passdown` to the devicemapper table. /// /// This method will add `no_discard_passdown` to the devicemapper table /// if it is not present. pub fn no_discard_passdown(&mut self, dm: &DM) -> DmResult<()> { self.set_feature_arg("no_discard_passdown", dm) } /// Default behavior for devicemapper thin pools is to pass down discards. /// This behavior can be changed by adding the feature argument /// `no_discard_passdown` to the devicemapper table. /// /// This method will remove `no_discard_passdown` from the devicemapper /// table if it is present. pub fn discard_passdown(&mut self, dm: &DM) -> DmResult<()> { self.unset_feature_arg("no_discard_passdown", dm) } } #[cfg(test)] use std::fs::OpenOptions; #[cfg(test)] use crate::{ consts::IEC, lineardev::LinearTargetParams, testing::{blkdev_size, test_name}, }; /// Values are explicitly stated in the device-mapper kernel documentation. #[cfg(test)] const MIN_DATA_BLOCK_SIZE: Sectors = Sectors(128); // 64 KiB #[cfg(test)] #[allow(dead_code)] const MAX_DATA_BLOCK_SIZE: Sectors = Sectors(2 * IEC::Mi); // 1 GiB #[cfg(test)] const MIN_RECOMMENDED_METADATA_SIZE: Sectors = Sectors(4 * IEC::Ki); // 2 MiB #[cfg(test)] #[allow(dead_code)] // Note that this value is stated in the kernel docs to be 16 GiB, but the // devicemapper source gives a different value for THIN_METADATA_MAX_SECTORS, // which is the actual maximum size. const MAX_METADATA_SIZE: MetaBlocks = MetaBlocks(255 * ((1 << 14) - 64)); #[cfg(test)] /// Generate a minimal thinpool dev. Use all the space available not consumed /// by the metadata device for the data device. pub fn minimal_thinpool(dm: &DM, path: &Path) -> ThinPoolDev { let dev_size = blkdev_size(&OpenOptions::new().read(true).open(path).unwrap()).sectors(); let dev = Device::from(devnode_to_devno(path).unwrap().unwrap()); let meta_params = LinearTargetParams::new(dev, Sectors(0)); let meta_table = vec![TargetLine::new( Sectors(0), MIN_RECOMMENDED_METADATA_SIZE, LinearDevTargetParams::Linear(meta_params), )]; let meta = LinearDev::setup( dm, &test_name("meta").expect("valid format"), None, meta_table, ) .unwrap(); let data_params = LinearTargetParams::new(dev, MIN_RECOMMENDED_METADATA_SIZE); let data_table = vec![TargetLine::new( Sectors(0), dev_size - MIN_RECOMMENDED_METADATA_SIZE, LinearDevTargetParams::Linear(data_params), )]; let data = LinearDev::setup( dm, &test_name("data").expect("valid format"), None, data_table, ) .unwrap(); ThinPoolDev::new( dm, &test_name("pool").expect("valid format"), None, meta, data, MIN_DATA_BLOCK_SIZE, DataBlocks(1), vec![ "no_discard_passdown".to_owned(), "skip_block_zeroing".to_owned(), ], ) .unwrap() } #[cfg(test)] mod tests { use std::path::Path; use crate::{ core::{errors::Error, DmFlags}, testing::{test_name, test_with_spec}, }; use super::*; /// Verify success when constructing a new ThinPoolDev with minimum values /// for data block size and metadata device. Check that the status of the /// device is as expected. fn test_minimum_values(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) if status.summary == ThinPoolStatusSummary::Good => { assert!(!status.discard_passdown); assert_eq!(status.held_metadata_root, None); let usage = &status.usage; // Even an empty thinpool requires some metadata. assert!(usage.used_meta > MetaBlocks(0)); assert_eq!(usage.total_meta, tp.meta_dev().size().metablocks()); assert_eq!(usage.used_data, DataBlocks(0)); assert_eq!( usage.total_data, DataBlocks(tp.data_dev().size() / tp.data_block_size()) ); } status => panic!("unexpected thinpool status: {status:?}"), } let table = ThinPoolDev::read_kernel_table(&dm, &DevId::Name(tp.name())) .unwrap() .table; let params = &table.params; assert_eq!(params.metadata_dev, tp.meta_dev().device()); assert_eq!(params.data_dev, tp.data_dev().device()); tp.teardown(&dm).unwrap(); } #[test] fn loop_test_basic() { test_with_spec(1, test_minimum_values); } /// Verify that data block size less than minimum results in a failure. fn test_low_data_block_size(paths: &[&Path]) { assert!(!paths.is_empty()); let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap()); let dm = DM::new().unwrap(); let meta_name = test_name("meta").expect("valid format"); let meta_params = LinearTargetParams::new(dev, Sectors(0)); let meta_table = vec![TargetLine::new( Sectors(0), MIN_RECOMMENDED_METADATA_SIZE, LinearDevTargetParams::Linear(meta_params), )]; let meta = LinearDev::setup(&dm, &meta_name, None, meta_table).unwrap(); let data_name = test_name("data").expect("valid format"); let data_params = LinearTargetParams::new(dev, MIN_RECOMMENDED_METADATA_SIZE); let data_table = vec![TargetLine::new( Sectors(0), 512u64 * MIN_DATA_BLOCK_SIZE, LinearDevTargetParams::Linear(data_params), )]; let data = LinearDev::setup(&dm, &data_name, None, data_table).unwrap(); assert_matches!( ThinPoolDev::new( &dm, &test_name("pool").expect("valid format"), None, meta, data, MIN_DATA_BLOCK_SIZE / 2u64, DataBlocks(1), vec![ "no_discard_passdown".to_owned(), "skip_block_zeroing".to_owned() ], ), Err(DmError::Core(Error::Ioctl(_, _, _, _))) ); dm.device_remove(&DevId::Name(&meta_name), DmOptions::default()) .unwrap(); dm.device_remove(&DevId::Name(&data_name), DmOptions::default()) .unwrap(); } #[test] fn loop_test_low_data_block_size() { test_with_spec(1, test_low_data_block_size); } /// Verify that setting the data table does not fail and results in /// the correct size data device. fn test_set_data(paths: &[&Path]) { assert!(paths.len() > 1); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); let mut data_table = tp.data_dev.table().table.clone(); let data_size = tp.data_dev.size(); let dev2 = Device::from(devnode_to_devno(paths[1]).unwrap().unwrap()); let data_params = LinearTargetParams::new(dev2, Sectors(0)); data_table.push(TargetLine::new( data_size, data_size, LinearDevTargetParams::Linear(data_params), )); tp.set_data_table(&dm, data_table).unwrap(); tp.resume(&dm).unwrap(); match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => { let usage = &status.usage; assert_eq!( *usage.total_data * tp.table().table.params.data_block_size, 2u8 * data_size ); } ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("thin pool should not have failed"), } tp.teardown(&dm).unwrap(); } #[test] fn loop_test_set_data() { test_with_spec(2, test_set_data); } /// Verify that setting the meta table does not fail and results in /// the correct size meta device. fn test_set_meta(paths: &[&Path]) { assert!(paths.len() > 1); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); let mut meta_table = tp.meta_dev.table().table.clone(); let meta_size = tp.meta_dev.size(); let dev2 = Device::from(devnode_to_devno(paths[1]).unwrap().unwrap()); let meta_params = LinearTargetParams::new(dev2, Sectors(0)); meta_table.push(TargetLine::new( meta_size, meta_size, LinearDevTargetParams::Linear(meta_params), )); tp.set_meta_table(&dm, meta_table).unwrap(); tp.resume(&dm).unwrap(); match tp.status(&dm, DmOptions::default()).unwrap() { ThinPoolStatus::Working(ref status) => { let usage = &status.usage; assert_eq!(usage.total_meta.sectors(), 2u8 * meta_size); } ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"), ThinPoolStatus::Fail => panic!("thin pool should not have failed"), } tp.teardown(&dm).unwrap(); } #[test] fn loop_test_set_meta() { test_with_spec(2, test_set_meta); } /// Just test that suspending and resuming a thinpool has no errors. fn test_suspend(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let mut tp = minimal_thinpool(&dm, paths[0]); tp.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH)) .unwrap(); tp.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH)) .unwrap(); tp.resume(&dm).unwrap(); tp.resume(&dm).unwrap(); tp.teardown(&dm).unwrap(); } #[test] fn loop_test_suspend() { test_with_spec(1, test_suspend); } fn test_status_noflush(paths: &[&Path]) { assert!(!paths.is_empty()); let dm = DM::new().unwrap(); let tp = minimal_thinpool(&dm, paths[0]); tp.status(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH)) .unwrap(); } #[test] fn loop_test_status_noflush() { test_with_spec(1, test_status_noflush); } #[test] fn test_thinpool_target_params_zero() { let result = "thin-pool 42:42 42:43 16 2 0" .parse::() .unwrap(); assert_eq!(result.feature_args, HashSet::new()); } #[test] fn test_thinpool_target_params_none() { let result = "thin-pool 42:42 42:43 16 2" .parse::() .unwrap(); assert_eq!(result.feature_args, HashSet::new()); } } devicemapper-0.34.4/src/units.rs000064400000000000000000000045521046102023000146650ustar 00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. /// disk sector size in bytes pub const SECTOR_SIZE: usize = 512; /// a kernel defined block size constant for any DM meta device /// a DM meta device may store cache device or thinpool device metadata /// defined in drivers/md/persistent-data/dm-space-map-metadata.h as /// DM_SM_METADATA_BLOCK_SIZE. const META_BLOCK_SIZE: Sectors = Sectors(8); /// The maximum size of a metadata device. /// defined in drivers/md/persistent-data/dm-space-map-metadata.h as /// DM_SM_METADATA_MAX_BLOCKS. /// As far as I can tell, this is not a limit on the size of a designated /// metadata device, but instead on the possible usage of that device. #[allow(dead_code)] const MAX_META_DEV_SIZE: MetaBlocks = MetaBlocks(255 * ((1 << 14) - 64)); range_u64!( /// A type for data blocks DataBlocks, "data blocks" ); range_u64!( /// A type for meta blocks MetaBlocks, "meta blocks" ); impl MetaBlocks { /// Return the number of Sectors in the MetaBlocks. pub fn sectors(self) -> Sectors { self.0 * META_BLOCK_SIZE } } range_u128!( /// A type for bytes Bytes, "bytes" ); impl Bytes { /// Return the number of Sectors fully contained in these bytes. pub fn sectors(self) -> Sectors { Sectors((self.0 / SECTOR_SIZE as u128) as u64) } } range_u64!( /// A type for sectors Sectors, "sectors" ); impl Sectors { /// The number of bytes in these sectors. pub fn bytes(self) -> Bytes { // Keep both as u128 before multiplication or overflow could occur Bytes(u128::from(self.0) * SECTOR_SIZE as u128) } /// The number of whole metablocks contained in these sectors. pub fn metablocks(self) -> MetaBlocks { MetaBlocks(self / META_BLOCK_SIZE) } } #[cfg(test)] mod test { use super::*; #[test] fn test_large() { let max_sectors = Sectors(u64::MAX).bytes(); let size_sectors = max_sectors.sectors(); assert_eq!(size_sectors.bytes(), max_sectors); } #[test] fn test_too_large() { let max_bytes = Sectors(u64::MAX).bytes() + Sectors(1).bytes(); let sectors = max_bytes.sectors(); assert_eq!(sectors, Sectors(0)); } }