├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── check.yml │ ├── codecov.yml │ ├── coveralls.yml │ ├── mac.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── bors.toml ├── nvpair-sys ├── Cargo.toml ├── build.rs ├── generate-bindings ├── src │ ├── bindings.rs │ └── lib.rs └── wrapper.h ├── nvpair ├── Cargo.toml ├── src │ └── lib.rs └── tests │ └── nvpair.rs ├── systest ├── Cargo.toml ├── build.rs └── src │ └── main.rs ├── zfs-core-sys ├── Cargo.toml ├── build-disable.rs ├── build.rs ├── generate-bindings ├── src │ ├── bindings.rs │ └── lib.rs └── wrapper.h ├── zfs-core ├── Cargo.toml ├── examples │ ├── send-small-snap.rs │ └── sync-hang.rs ├── src │ └── lib.rs ├── test-prepare └── tests │ └── basic.rs └── zfs-drr ├── Cargo.toml └── src └── lib.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.yml] 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | rebase-strategy: disabled 10 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches-ignore: 4 | - '**.tmp' 5 | 6 | name: check 7 | 8 | jobs: 9 | check: 10 | env: 11 | ZFS_TEMPFS: tpool 12 | runs-on: ubuntu-20.04 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | profile: minimal 19 | toolchain: beta 20 | override: true 21 | components: rustfmt, clippy 22 | - uses: Swatinem/rust-cache@v1 23 | 24 | # note: this works around the gha images sometimes not being up-to-date 25 | # with ubuntu mirrors, which can result in 404s when fetching packages 26 | - name: apt-get update 27 | run: sudo apt-get -o Acquire::Retries=3 update 28 | 29 | - name: Install dependencies 30 | run: sudo apt install libzfslinux-dev zfsutils-linux 31 | 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: fmt 35 | args: --all -- --check 36 | 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: clippy 40 | args: -- -D warnings 41 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches-ignore: 4 | - '**.tmp' 5 | 6 | name: codecov 7 | 8 | jobs: 9 | codecov: 10 | runs-on: ubuntu-20.04 11 | env: 12 | ZFS_TEMPFS: tpool 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Setup Rust Toolchain 16 | uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: nightly 19 | override: true 20 | components: llvm-tools-preview 21 | 22 | - uses: Swatinem/rust-cache@v1 23 | 24 | # note: this works around the gha images sometimes not being up-to-date 25 | # with ubuntu mirrors, which can result in 404s when fetching packages 26 | - name: apt-get update 27 | run: sudo apt-get -o Acquire::Retries=3 update 28 | 29 | - name: Install dependencies 30 | run: sudo apt install libzfslinux-dev zfsutils-linux 31 | 32 | - name: Setup test pool 33 | run: sudo ./zfs-core/test-prepare "${ZFS_TEMPFS}" 34 | 35 | - name: Run tests & generate coverage info 36 | uses: actions-rs/cargo@v1 37 | with: 38 | command: test 39 | args: --all-features --no-fail-fast 40 | env: 41 | CARGO_INCREMENTAL: '0' 42 | RUSTFLAGS: '-Zinstrument-coverage' 43 | LLVM_PROFILE_FILE: 'rust-libzfs-%p-%m.profraw' 44 | 45 | - run: curl -L https://github.com/mozilla/grcov/releases/latest/download/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - 46 | - run: ./grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" -o lcov.info 47 | - run: bash <(curl -s https://codecov.io/bash) -f lcov.info 48 | - name: Run codacy-coverage-reporter 49 | uses: codacy/codacy-coverage-reporter-action@v1 50 | with: 51 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 52 | coverage-reports: lcov.info 53 | -------------------------------------------------------------------------------- /.github/workflows/coveralls.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches-ignore: 4 | - '**.tmp' 5 | 6 | name: coveralls 7 | 8 | jobs: 9 | coveralls: 10 | runs-on: ubuntu-20.04 11 | env: 12 | ZFS_TEMPFS: tpool 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Setup Rust Toolchain 16 | uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: nightly 19 | override: true 20 | 21 | - uses: Swatinem/rust-cache@v1 22 | 23 | # note: this works around the gha images sometimes not being up-to-date 24 | # with ubuntu mirrors, which can result in 404s when fetching packages 25 | - name: apt-get update 26 | run: sudo apt-get -o Acquire::Retries=3 update 27 | 28 | - name: Install dependencies 29 | run: sudo apt install libzfslinux-dev zfsutils-linux 30 | 31 | - name: Setup test pool 32 | run: sudo ./zfs-core/test-prepare "${ZFS_TEMPFS}" 33 | 34 | - name: Run tests & generate coverage info 35 | uses: actions-rs/cargo@v1 36 | with: 37 | command: test 38 | args: --all-features --no-fail-fast 39 | env: 40 | CARGO_INCREMENTAL: '0' 41 | RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' 42 | RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' 43 | - run: curl -L https://github.com/mozilla/grcov/releases/latest/download/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - 44 | - run: zip -0 ccov.zip `find . \( -name "*.gc*" \) -print` 45 | - run: ./grcov ccov.zip -s . -t lcov --llvm --branch --ignore-not-existing --ignore "/*" -o lcov.info 46 | - name: Archive code coverage results 47 | uses: actions/upload-artifact@v1 48 | with: 49 | name: code-coverage-report 50 | path: lcov.info 51 | - name: Upload to coveralls 52 | uses: coverallsapp/github-action@v1.1.2 53 | with: 54 | github-token: ${{ secrets.GITHUB_TOKEN }} 55 | path-to-lcov: lcov.info 56 | -------------------------------------------------------------------------------- /.github/workflows/mac.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches-ignore: 4 | - '**.tmp' 5 | 6 | name: mac 7 | jobs: 8 | mac: 9 | runs-on: macos-11 10 | strategy: 11 | matrix: 12 | rust: 13 | - beta 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: ${{ matrix.rust }} 21 | override: true 22 | 23 | - name: Cache Rust dependencies 24 | uses: Swatinem/rust-cache@v1 25 | 26 | # MD5 (OpenZFSonOsX-2.1.0-Big.Sur-11.pkg) = 80bee1c01362372ea3a803fdac56bfaa 27 | - name: Download OpenZFSonOsX-2.1.0-Big.Sur-11.pkg 28 | run: curl -o zfs.pkg https://openzfsonosx.org/forum/download/file.php?id=343&sid=4b2c0ab13fa308c9b91ea188ba907d02 29 | 30 | - name: Install openzfs-on-osx 31 | run: sudo installer -verbose -pkg zfs.pkg -target / 32 | 33 | - name: Build 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: build 37 | args: --all-targets --all 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches-ignore: 4 | - '**.tmp' 5 | 6 | name: test 7 | 8 | jobs: 9 | test: 10 | env: 11 | ZFS_TEMPFS: tpool 12 | 13 | runs-on: ubuntu-20.04 14 | strategy: 15 | matrix: 16 | rust: 17 | - stable 18 | - beta 19 | - nightly 20 | - 1.48.0 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: ${{ matrix.rust }} 29 | override: true 30 | 31 | - name: Cache Rust dependencies 32 | uses: Swatinem/rust-cache@v1 33 | 34 | # note: this works around the gha images sometimes not being up-to-date 35 | # with ubuntu mirrors, which can result in 404s when fetching packages 36 | - name: apt-get update 37 | run: sudo apt-get -o Acquire::Retries=3 update 38 | 39 | - name: Install dependencies 40 | run: sudo apt install libzfslinux-dev zfsutils-linux 41 | 42 | - name: Setup test pool 43 | run: sudo ./zfs-core/test-prepare "${ZFS_TEMPFS}" 44 | 45 | - name: Run all tests 46 | uses: actions-rs/cargo@v1 47 | with: 48 | command: test 49 | args: --all 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | *.img 6 | *.drr 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 10 | 11 | [[package]] 12 | name = "build-env" 13 | version = "0.3.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "1522ac6ee801a11bf9ef3f80403f4ede6eb41291fac3dde3de09989679305f25" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "cstr-argument" 25 | version = "0.1.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40" 28 | dependencies = [ 29 | "cfg-if", 30 | "memchr", 31 | ] 32 | 33 | [[package]] 34 | name = "doc-comment" 35 | version = "0.3.3" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 38 | 39 | [[package]] 40 | name = "foreign-types" 41 | version = "0.5.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 44 | dependencies = [ 45 | "foreign-types-macros", 46 | "foreign-types-shared", 47 | ] 48 | 49 | [[package]] 50 | name = "foreign-types-macros" 51 | version = "0.2.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "63f713f8b2aa9e24fec85b0e290c56caee12e3b6ae0aeeda238a75b28251afd6" 54 | dependencies = [ 55 | "proc-macro2", 56 | "quote", 57 | "syn", 58 | ] 59 | 60 | [[package]] 61 | name = "foreign-types-shared" 62 | version = "0.3.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855" 65 | 66 | [[package]] 67 | name = "getrandom" 68 | version = "0.2.3" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 71 | dependencies = [ 72 | "cfg-if", 73 | "libc", 74 | "wasi", 75 | ] 76 | 77 | [[package]] 78 | name = "libc" 79 | version = "0.2.105" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013" 82 | 83 | [[package]] 84 | name = "memchr" 85 | version = "2.4.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 88 | 89 | [[package]] 90 | name = "nvpair" 91 | version = "0.5.0" 92 | dependencies = [ 93 | "cstr-argument", 94 | "foreign-types", 95 | "nvpair-sys", 96 | ] 97 | 98 | [[package]] 99 | name = "nvpair-sys" 100 | version = "0.4.0" 101 | 102 | [[package]] 103 | name = "os_pipe" 104 | version = "0.9.2" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" 107 | dependencies = [ 108 | "libc", 109 | "winapi", 110 | ] 111 | 112 | [[package]] 113 | name = "pkg-config" 114 | version = "0.3.22" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" 117 | 118 | [[package]] 119 | name = "ppv-lite86" 120 | version = "0.2.14" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" 123 | 124 | [[package]] 125 | name = "proc-macro2" 126 | version = "1.0.30" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" 129 | dependencies = [ 130 | "unicode-xid", 131 | ] 132 | 133 | [[package]] 134 | name = "quote" 135 | version = "1.0.10" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 138 | dependencies = [ 139 | "proc-macro2", 140 | ] 141 | 142 | [[package]] 143 | name = "rand" 144 | version = "0.8.4" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 147 | dependencies = [ 148 | "libc", 149 | "rand_chacha", 150 | "rand_core", 151 | "rand_hc", 152 | ] 153 | 154 | [[package]] 155 | name = "rand_chacha" 156 | version = "0.3.1" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 159 | dependencies = [ 160 | "ppv-lite86", 161 | "rand_core", 162 | ] 163 | 164 | [[package]] 165 | name = "rand_core" 166 | version = "0.6.3" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 169 | dependencies = [ 170 | "getrandom", 171 | ] 172 | 173 | [[package]] 174 | name = "rand_hc" 175 | version = "0.3.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 178 | dependencies = [ 179 | "rand_core", 180 | ] 181 | 182 | [[package]] 183 | name = "redox_syscall" 184 | version = "0.2.10" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 187 | dependencies = [ 188 | "bitflags", 189 | ] 190 | 191 | [[package]] 192 | name = "remove_dir_all" 193 | version = "0.5.3" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 196 | dependencies = [ 197 | "winapi", 198 | ] 199 | 200 | [[package]] 201 | name = "snafu" 202 | version = "0.6.10" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" 205 | dependencies = [ 206 | "doc-comment", 207 | "snafu-derive", 208 | ] 209 | 210 | [[package]] 211 | name = "snafu-derive" 212 | version = "0.6.10" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" 215 | dependencies = [ 216 | "proc-macro2", 217 | "quote", 218 | "syn", 219 | ] 220 | 221 | [[package]] 222 | name = "syn" 223 | version = "1.0.80" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" 226 | dependencies = [ 227 | "proc-macro2", 228 | "quote", 229 | "unicode-xid", 230 | ] 231 | 232 | [[package]] 233 | name = "tempfile" 234 | version = "3.2.0" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 237 | dependencies = [ 238 | "cfg-if", 239 | "libc", 240 | "rand", 241 | "redox_syscall", 242 | "remove_dir_all", 243 | "winapi", 244 | ] 245 | 246 | [[package]] 247 | name = "unicode-xid" 248 | version = "0.2.2" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 251 | 252 | [[package]] 253 | name = "wasi" 254 | version = "0.10.2+wasi-snapshot-preview1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 257 | 258 | [[package]] 259 | name = "winapi" 260 | version = "0.3.9" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 263 | dependencies = [ 264 | "winapi-i686-pc-windows-gnu", 265 | "winapi-x86_64-pc-windows-gnu", 266 | ] 267 | 268 | [[package]] 269 | name = "winapi-i686-pc-windows-gnu" 270 | version = "0.4.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 273 | 274 | [[package]] 275 | name = "winapi-x86_64-pc-windows-gnu" 276 | version = "0.4.0" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 279 | 280 | [[package]] 281 | name = "zfs-core" 282 | version = "0.5.0" 283 | dependencies = [ 284 | "cstr-argument", 285 | "foreign-types", 286 | "libc", 287 | "nvpair", 288 | "os_pipe", 289 | "rand", 290 | "snafu", 291 | "tempfile", 292 | "zfs-core-sys", 293 | ] 294 | 295 | [[package]] 296 | name = "zfs-core-sys" 297 | version = "0.5.0" 298 | dependencies = [ 299 | "build-env", 300 | "libc", 301 | "nvpair-sys", 302 | "pkg-config", 303 | ] 304 | 305 | [[package]] 306 | name = "zfs-drr" 307 | version = "0.1.0" 308 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [workspace] 3 | members = ['zfs-core-sys', 'zfs-core', 'nvpair-sys', 'nvpair', 'zfs-drr'] 4 | exclude = ['systest'] 5 | 6 | 7 | #[patch.crates-io] 8 | #cstr-argument = { path = '../cstr-argument' } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-libzfs 2 | 3 | Rust APIs for zfs related interfaces. Currently: `libzfs_core` and `nvpair`. 4 | 5 | [![zfs-core crates.io](https://img.shields.io/badge/dynamic/json.svg?label=crates.io&url=https%3A%2F%2Fcrates.io%2Fapi%2Fv1%2Fcrates%2Fzfs-core%2Fversions&query=%24.versions.0.num&colorB=orange&prefix=zfs-core%20%3D%20%22&suffix=%22&style=flat)](https://crates.io/crates/zfs-core) 6 | 7 | [![zfs-core docs.rs](https://img.shields.io/badge/docs-zfs--core-brightgreen.svg?style=flat)](https://docs.rs/zfs-core/) 8 | 9 | [![zfs-core-sys crates.io](https://img.shields.io/badge/dynamic/json.svg?label=crates.io&url=https%3A%2F%2Fcrates.io%2Fapi%2Fv1%2Fcrates%2Fzfs-core-sys%2Fversions&query=%24.versions.0.num&colorB=orange&prefix=zfs-core-sys%20%3D%20%22&suffix=%22&style=flat)](https://crates.io/crates/zfs-core-sys) 10 | 11 | [![zfs-core-sys docs.rs](https://img.shields.io/badge/docs-zfs--core--sys-brightgreen.svg?style=flat)](https://docs.rs/zfs-core-sys/) 12 | 13 | [![nvpair crates.io](https://img.shields.io/badge/dynamic/json.svg?label=crates.io&url=https%3A%2F%2Fcrates.io%2Fapi%2Fv1%2Fcrates%2Fnvpair%2Fversions&query=%24.versions.0.num&colorB=orange&prefix=nvpair%20%3D%20%22&suffix=%22&style=flat)](https://crates.io/crates/nvpair) 14 | 15 | [![nvpair docs.rs](https://img.shields.io/badge/docs-nvpair-brightgreen.svg?style=flat)](https://docs.rs/nvpair/) 16 | 17 | [![nvpair-sys crates.io](https://img.shields.io/badge/dynamic/json.svg?label=crates.io&url=https%3A%2F%2Fcrates.io%2Fapi%2Fv1%2Fcrates%2Fnvpair-sys%2Fversions&query=%24.versions.0.num&colorB=orange&prefix=nvpair-sys%20%3D%20%22&suffix=%22&style=flat)](https://crates.io/crates/nvpair-sys) 18 | 19 | [![nvpair-sys docs.rs](https://img.shields.io/badge/docs-nvpair--sys-brightgreen.svg?style=flat)](https://docs.rs/nvpair-sys/) 20 | 21 | ## License 22 | 23 | Licensed under either of 24 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 25 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 26 | at your option. 27 | 28 | ### Contribution 29 | 30 | Unless you explicitly state otherwise, any contribution intentionally submitted 31 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 32 | additional terms or conditions. 33 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "test (stable)", 3 | "test (beta)", 4 | "test (1.48.0)", 5 | "check", 6 | "mac (beta)", 7 | ] 8 | cut_body_after = "---" 9 | delete_merged_branches = true 10 | -------------------------------------------------------------------------------- /nvpair-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nvpair-sys" 3 | version = "0.4.0" 4 | edition = "2018" 5 | authors = ["Cody P Schafer "] 6 | include = ["**/*.rs", "Cargo.toml"] 7 | description = "Bindings to libnvpair.so (nvpair & nvlist)" 8 | documentation = "https://docs.rs/nvpair-sys" 9 | repository = "https://github.com/jmesmon/rust-libzfs" 10 | license = "Apache-2.0 OR MIT" 11 | 12 | links = "nvpair" 13 | 14 | [dependencies] 15 | 16 | [badges] 17 | travis-ci = { repository = "jmesmon/rust-libzfs" } 18 | -------------------------------------------------------------------------------- /nvpair-sys/build.rs: -------------------------------------------------------------------------------- 1 | fn var(s: &str) -> Result { 2 | println!("cargo:rerun-if-env-changed={}", s); 3 | std::env::var(s) 4 | } 5 | 6 | fn main() { 7 | let target_os = var("CARGO_CFG_TARGET_OS").expect("Could not get env var CARGO_CFG_TARGET_OS"); 8 | 9 | // when using "openzfs on macos", zfs libs are installed outside the default link path. Add it 10 | // in 11 | // TODO: Provide a way to disable 12 | if target_os == "macos" { 13 | println!("cargo:rustc-link-search=native=/usr/local/zfs/lib"); 14 | } 15 | 16 | println!("cargo:rustc-link-lib=nvpair"); 17 | // FIXME: a bug exists in some versions of libnvpair causing it to depend on a symbol called 18 | // `aok`, which is in `libzfs`. 19 | println!("cargo:rustc-link-lib=zfs"); 20 | // nvpair uses functions from libspl on FreeBSD 21 | if target_os == "freebsd" { 22 | println!("cargo:rustc-link-lib=spl"); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /nvpair-sys/generate-bindings: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | os=$(uname -s) 4 | case "$os" in 5 | Darwin) 6 | export PKG_CONFIG_PATH=/usr/local/zfs/lib/pkgconfig 7 | sdkpath="$(xcrun --sdk macosx --show-sdk-path)" 8 | CFLAGS=(-I "$sdkpath/usr/include/") 9 | ;; 10 | *) 11 | CFLAGS=() 12 | ;; 13 | esac 14 | 15 | d=$(dirname $0) 16 | bindgen "$d/wrapper.h" \ 17 | -o "$d/src/bindings.rs" \ 18 | --whitelist-function 'nvlist_.*' \ 19 | --whitelist-function 'nvpair_.*' \ 20 | --whitelist-type 'nvlist_.*' \ 21 | --whitelist-type 'uint_t' \ 22 | --whitelist-type 'uchar_t' \ 23 | --whitelist-type 'nv_alloc.*' \ 24 | --whitelist-type 'boolean.*' \ 25 | --whitelist-type 'nvpair.*' \ 26 | --whitelist-type 'nvlist.*' \ 27 | --whitelist-type 'hrtime.*' \ 28 | --whitelist-type 'data_type_.*' \ 29 | --constified-enum-module '.*' \ 30 | --blacklist-type 'va_list' \ 31 | --blacklist-type '_IO_FILE' \ 32 | --blacklist-type 'FILE' \ 33 | --no-recursive-whitelist \ 34 | -- `pkg-config libzfs_core --cflags` "${CFLAGS[@]}" 35 | -------------------------------------------------------------------------------- /nvpair-sys/src/bindings.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen 0.59.1 */ 2 | 3 | pub mod boolean_t { 4 | pub type Type = ::std::os::raw::c_uint; 5 | pub const B_FALSE: Type = 0; 6 | pub const B_TRUE: Type = 1; 7 | } 8 | pub type uchar_t = ::std::os::raw::c_uchar; 9 | pub type uint_t = ::std::os::raw::c_uint; 10 | pub type hrtime_t = ::std::os::raw::c_longlong; 11 | pub mod data_type_t { 12 | pub type Type = ::std::os::raw::c_int; 13 | pub const DATA_TYPE_DONTCARE: Type = -1; 14 | pub const DATA_TYPE_UNKNOWN: Type = 0; 15 | pub const DATA_TYPE_BOOLEAN: Type = 1; 16 | pub const DATA_TYPE_BYTE: Type = 2; 17 | pub const DATA_TYPE_INT16: Type = 3; 18 | pub const DATA_TYPE_UINT16: Type = 4; 19 | pub const DATA_TYPE_INT32: Type = 5; 20 | pub const DATA_TYPE_UINT32: Type = 6; 21 | pub const DATA_TYPE_INT64: Type = 7; 22 | pub const DATA_TYPE_UINT64: Type = 8; 23 | pub const DATA_TYPE_STRING: Type = 9; 24 | pub const DATA_TYPE_BYTE_ARRAY: Type = 10; 25 | pub const DATA_TYPE_INT16_ARRAY: Type = 11; 26 | pub const DATA_TYPE_UINT16_ARRAY: Type = 12; 27 | pub const DATA_TYPE_INT32_ARRAY: Type = 13; 28 | pub const DATA_TYPE_UINT32_ARRAY: Type = 14; 29 | pub const DATA_TYPE_INT64_ARRAY: Type = 15; 30 | pub const DATA_TYPE_UINT64_ARRAY: Type = 16; 31 | pub const DATA_TYPE_STRING_ARRAY: Type = 17; 32 | pub const DATA_TYPE_HRTIME: Type = 18; 33 | pub const DATA_TYPE_NVLIST: Type = 19; 34 | pub const DATA_TYPE_NVLIST_ARRAY: Type = 20; 35 | pub const DATA_TYPE_BOOLEAN_VALUE: Type = 21; 36 | pub const DATA_TYPE_INT8: Type = 22; 37 | pub const DATA_TYPE_UINT8: Type = 23; 38 | pub const DATA_TYPE_BOOLEAN_ARRAY: Type = 24; 39 | pub const DATA_TYPE_INT8_ARRAY: Type = 25; 40 | pub const DATA_TYPE_UINT8_ARRAY: Type = 26; 41 | pub const DATA_TYPE_DOUBLE: Type = 27; 42 | } 43 | #[repr(C)] 44 | pub struct nvpair { 45 | pub nvp_size: i32, 46 | pub nvp_name_sz: i16, 47 | pub nvp_reserve: i16, 48 | pub nvp_value_elem: i32, 49 | pub nvp_type: data_type_t::Type, 50 | } 51 | #[test] 52 | fn bindgen_test_layout_nvpair() { 53 | assert_eq!( 54 | ::std::mem::size_of::(), 55 | 16usize, 56 | concat!("Size of: ", stringify!(nvpair)) 57 | ); 58 | assert_eq!( 59 | ::std::mem::align_of::(), 60 | 4usize, 61 | concat!("Alignment of ", stringify!(nvpair)) 62 | ); 63 | assert_eq!( 64 | unsafe { &(*(::std::ptr::null::())).nvp_size as *const _ as usize }, 65 | 0usize, 66 | concat!( 67 | "Offset of field: ", 68 | stringify!(nvpair), 69 | "::", 70 | stringify!(nvp_size) 71 | ) 72 | ); 73 | assert_eq!( 74 | unsafe { &(*(::std::ptr::null::())).nvp_name_sz as *const _ as usize }, 75 | 4usize, 76 | concat!( 77 | "Offset of field: ", 78 | stringify!(nvpair), 79 | "::", 80 | stringify!(nvp_name_sz) 81 | ) 82 | ); 83 | assert_eq!( 84 | unsafe { &(*(::std::ptr::null::())).nvp_reserve as *const _ as usize }, 85 | 6usize, 86 | concat!( 87 | "Offset of field: ", 88 | stringify!(nvpair), 89 | "::", 90 | stringify!(nvp_reserve) 91 | ) 92 | ); 93 | assert_eq!( 94 | unsafe { &(*(::std::ptr::null::())).nvp_value_elem as *const _ as usize }, 95 | 8usize, 96 | concat!( 97 | "Offset of field: ", 98 | stringify!(nvpair), 99 | "::", 100 | stringify!(nvp_value_elem) 101 | ) 102 | ); 103 | assert_eq!( 104 | unsafe { &(*(::std::ptr::null::())).nvp_type as *const _ as usize }, 105 | 12usize, 106 | concat!( 107 | "Offset of field: ", 108 | stringify!(nvpair), 109 | "::", 110 | stringify!(nvp_type) 111 | ) 112 | ); 113 | } 114 | pub type nvpair_t = nvpair; 115 | #[repr(C)] 116 | pub struct nvlist { 117 | pub nvl_version: i32, 118 | pub nvl_nvflag: u32, 119 | pub nvl_priv: u64, 120 | pub nvl_flag: u32, 121 | pub nvl_pad: i32, 122 | } 123 | #[test] 124 | fn bindgen_test_layout_nvlist() { 125 | assert_eq!( 126 | ::std::mem::size_of::(), 127 | 24usize, 128 | concat!("Size of: ", stringify!(nvlist)) 129 | ); 130 | assert_eq!( 131 | ::std::mem::align_of::(), 132 | 8usize, 133 | concat!("Alignment of ", stringify!(nvlist)) 134 | ); 135 | assert_eq!( 136 | unsafe { &(*(::std::ptr::null::())).nvl_version as *const _ as usize }, 137 | 0usize, 138 | concat!( 139 | "Offset of field: ", 140 | stringify!(nvlist), 141 | "::", 142 | stringify!(nvl_version) 143 | ) 144 | ); 145 | assert_eq!( 146 | unsafe { &(*(::std::ptr::null::())).nvl_nvflag as *const _ as usize }, 147 | 4usize, 148 | concat!( 149 | "Offset of field: ", 150 | stringify!(nvlist), 151 | "::", 152 | stringify!(nvl_nvflag) 153 | ) 154 | ); 155 | assert_eq!( 156 | unsafe { &(*(::std::ptr::null::())).nvl_priv as *const _ as usize }, 157 | 8usize, 158 | concat!( 159 | "Offset of field: ", 160 | stringify!(nvlist), 161 | "::", 162 | stringify!(nvl_priv) 163 | ) 164 | ); 165 | assert_eq!( 166 | unsafe { &(*(::std::ptr::null::())).nvl_flag as *const _ as usize }, 167 | 16usize, 168 | concat!( 169 | "Offset of field: ", 170 | stringify!(nvlist), 171 | "::", 172 | stringify!(nvl_flag) 173 | ) 174 | ); 175 | assert_eq!( 176 | unsafe { &(*(::std::ptr::null::())).nvl_pad as *const _ as usize }, 177 | 20usize, 178 | concat!( 179 | "Offset of field: ", 180 | stringify!(nvlist), 181 | "::", 182 | stringify!(nvl_pad) 183 | ) 184 | ); 185 | } 186 | pub type nvlist_t = nvlist; 187 | pub type nv_alloc_ops_t = nv_alloc_ops; 188 | #[repr(C)] 189 | #[derive(Debug, Copy, Clone)] 190 | pub struct nv_alloc { 191 | pub nva_ops: *const nv_alloc_ops_t, 192 | pub nva_arg: *mut ::std::os::raw::c_void, 193 | } 194 | #[test] 195 | fn bindgen_test_layout_nv_alloc() { 196 | assert_eq!( 197 | ::std::mem::size_of::(), 198 | 16usize, 199 | concat!("Size of: ", stringify!(nv_alloc)) 200 | ); 201 | assert_eq!( 202 | ::std::mem::align_of::(), 203 | 8usize, 204 | concat!("Alignment of ", stringify!(nv_alloc)) 205 | ); 206 | assert_eq!( 207 | unsafe { &(*(::std::ptr::null::())).nva_ops as *const _ as usize }, 208 | 0usize, 209 | concat!( 210 | "Offset of field: ", 211 | stringify!(nv_alloc), 212 | "::", 213 | stringify!(nva_ops) 214 | ) 215 | ); 216 | assert_eq!( 217 | unsafe { &(*(::std::ptr::null::())).nva_arg as *const _ as usize }, 218 | 8usize, 219 | concat!( 220 | "Offset of field: ", 221 | stringify!(nv_alloc), 222 | "::", 223 | stringify!(nva_arg) 224 | ) 225 | ); 226 | } 227 | pub type nv_alloc_t = nv_alloc; 228 | #[repr(C)] 229 | #[derive(Debug, Copy, Clone)] 230 | pub struct nv_alloc_ops { 231 | pub nv_ao_init: ::std::option::Option< 232 | unsafe extern "C" fn( 233 | arg1: *mut nv_alloc_t, 234 | arg2: *mut __va_list_tag, 235 | ) -> ::std::os::raw::c_int, 236 | >, 237 | pub nv_ao_fini: ::std::option::Option, 238 | pub nv_ao_alloc: ::std::option::Option< 239 | unsafe extern "C" fn(arg1: *mut nv_alloc_t, arg2: size_t) -> *mut ::std::os::raw::c_void, 240 | >, 241 | pub nv_ao_free: ::std::option::Option< 242 | unsafe extern "C" fn( 243 | arg1: *mut nv_alloc_t, 244 | arg2: *mut ::std::os::raw::c_void, 245 | arg3: size_t, 246 | ), 247 | >, 248 | pub nv_ao_reset: ::std::option::Option, 249 | } 250 | #[test] 251 | fn bindgen_test_layout_nv_alloc_ops() { 252 | assert_eq!( 253 | ::std::mem::size_of::(), 254 | 40usize, 255 | concat!("Size of: ", stringify!(nv_alloc_ops)) 256 | ); 257 | assert_eq!( 258 | ::std::mem::align_of::(), 259 | 8usize, 260 | concat!("Alignment of ", stringify!(nv_alloc_ops)) 261 | ); 262 | assert_eq!( 263 | unsafe { &(*(::std::ptr::null::())).nv_ao_init as *const _ as usize }, 264 | 0usize, 265 | concat!( 266 | "Offset of field: ", 267 | stringify!(nv_alloc_ops), 268 | "::", 269 | stringify!(nv_ao_init) 270 | ) 271 | ); 272 | assert_eq!( 273 | unsafe { &(*(::std::ptr::null::())).nv_ao_fini as *const _ as usize }, 274 | 8usize, 275 | concat!( 276 | "Offset of field: ", 277 | stringify!(nv_alloc_ops), 278 | "::", 279 | stringify!(nv_ao_fini) 280 | ) 281 | ); 282 | assert_eq!( 283 | unsafe { &(*(::std::ptr::null::())).nv_ao_alloc as *const _ as usize }, 284 | 16usize, 285 | concat!( 286 | "Offset of field: ", 287 | stringify!(nv_alloc_ops), 288 | "::", 289 | stringify!(nv_ao_alloc) 290 | ) 291 | ); 292 | assert_eq!( 293 | unsafe { &(*(::std::ptr::null::())).nv_ao_free as *const _ as usize }, 294 | 24usize, 295 | concat!( 296 | "Offset of field: ", 297 | stringify!(nv_alloc_ops), 298 | "::", 299 | stringify!(nv_ao_free) 300 | ) 301 | ); 302 | assert_eq!( 303 | unsafe { &(*(::std::ptr::null::())).nv_ao_reset as *const _ as usize }, 304 | 32usize, 305 | concat!( 306 | "Offset of field: ", 307 | stringify!(nv_alloc_ops), 308 | "::", 309 | stringify!(nv_ao_reset) 310 | ) 311 | ); 312 | } 313 | extern "C" { 314 | pub fn nvlist_alloc( 315 | arg1: *mut *mut nvlist_t, 316 | arg2: uint_t, 317 | arg3: ::std::os::raw::c_int, 318 | ) -> ::std::os::raw::c_int; 319 | } 320 | extern "C" { 321 | pub fn nvlist_free(arg1: *mut nvlist_t); 322 | } 323 | extern "C" { 324 | pub fn nvlist_size( 325 | arg1: *mut nvlist_t, 326 | arg2: *mut size_t, 327 | arg3: ::std::os::raw::c_int, 328 | ) -> ::std::os::raw::c_int; 329 | } 330 | extern "C" { 331 | pub fn nvlist_pack( 332 | arg1: *mut nvlist_t, 333 | arg2: *mut *mut ::std::os::raw::c_char, 334 | arg3: *mut size_t, 335 | arg4: ::std::os::raw::c_int, 336 | arg5: ::std::os::raw::c_int, 337 | ) -> ::std::os::raw::c_int; 338 | } 339 | extern "C" { 340 | pub fn nvlist_unpack( 341 | arg1: *mut ::std::os::raw::c_char, 342 | arg2: size_t, 343 | arg3: *mut *mut nvlist_t, 344 | arg4: ::std::os::raw::c_int, 345 | ) -> ::std::os::raw::c_int; 346 | } 347 | extern "C" { 348 | pub fn nvlist_dup( 349 | arg1: *mut nvlist_t, 350 | arg2: *mut *mut nvlist_t, 351 | arg3: ::std::os::raw::c_int, 352 | ) -> ::std::os::raw::c_int; 353 | } 354 | extern "C" { 355 | pub fn nvlist_merge( 356 | arg1: *mut nvlist_t, 357 | arg2: *mut nvlist_t, 358 | arg3: ::std::os::raw::c_int, 359 | ) -> ::std::os::raw::c_int; 360 | } 361 | extern "C" { 362 | pub fn nvlist_nvflag(arg1: *mut nvlist_t) -> uint_t; 363 | } 364 | extern "C" { 365 | pub fn nvlist_xalloc( 366 | arg1: *mut *mut nvlist_t, 367 | arg2: uint_t, 368 | arg3: *mut nv_alloc_t, 369 | ) -> ::std::os::raw::c_int; 370 | } 371 | extern "C" { 372 | pub fn nvlist_xpack( 373 | arg1: *mut nvlist_t, 374 | arg2: *mut *mut ::std::os::raw::c_char, 375 | arg3: *mut size_t, 376 | arg4: ::std::os::raw::c_int, 377 | arg5: *mut nv_alloc_t, 378 | ) -> ::std::os::raw::c_int; 379 | } 380 | extern "C" { 381 | pub fn nvlist_xunpack( 382 | arg1: *mut ::std::os::raw::c_char, 383 | arg2: size_t, 384 | arg3: *mut *mut nvlist_t, 385 | arg4: *mut nv_alloc_t, 386 | ) -> ::std::os::raw::c_int; 387 | } 388 | extern "C" { 389 | pub fn nvlist_xdup( 390 | arg1: *mut nvlist_t, 391 | arg2: *mut *mut nvlist_t, 392 | arg3: *mut nv_alloc_t, 393 | ) -> ::std::os::raw::c_int; 394 | } 395 | extern "C" { 396 | pub fn nvlist_lookup_nv_alloc(arg1: *mut nvlist_t) -> *mut nv_alloc_t; 397 | } 398 | extern "C" { 399 | pub fn nvlist_add_nvpair(arg1: *mut nvlist_t, arg2: *mut nvpair_t) -> ::std::os::raw::c_int; 400 | } 401 | extern "C" { 402 | pub fn nvlist_add_boolean( 403 | arg1: *mut nvlist_t, 404 | arg2: *const ::std::os::raw::c_char, 405 | ) -> ::std::os::raw::c_int; 406 | } 407 | extern "C" { 408 | pub fn nvlist_add_boolean_value( 409 | arg1: *mut nvlist_t, 410 | arg2: *const ::std::os::raw::c_char, 411 | arg3: boolean_t::Type, 412 | ) -> ::std::os::raw::c_int; 413 | } 414 | extern "C" { 415 | pub fn nvlist_add_byte( 416 | arg1: *mut nvlist_t, 417 | arg2: *const ::std::os::raw::c_char, 418 | arg3: uchar_t, 419 | ) -> ::std::os::raw::c_int; 420 | } 421 | extern "C" { 422 | pub fn nvlist_add_int8( 423 | arg1: *mut nvlist_t, 424 | arg2: *const ::std::os::raw::c_char, 425 | arg3: i8, 426 | ) -> ::std::os::raw::c_int; 427 | } 428 | extern "C" { 429 | pub fn nvlist_add_uint8( 430 | arg1: *mut nvlist_t, 431 | arg2: *const ::std::os::raw::c_char, 432 | arg3: u8, 433 | ) -> ::std::os::raw::c_int; 434 | } 435 | extern "C" { 436 | pub fn nvlist_add_int16( 437 | arg1: *mut nvlist_t, 438 | arg2: *const ::std::os::raw::c_char, 439 | arg3: i16, 440 | ) -> ::std::os::raw::c_int; 441 | } 442 | extern "C" { 443 | pub fn nvlist_add_uint16( 444 | arg1: *mut nvlist_t, 445 | arg2: *const ::std::os::raw::c_char, 446 | arg3: u16, 447 | ) -> ::std::os::raw::c_int; 448 | } 449 | extern "C" { 450 | pub fn nvlist_add_int32( 451 | arg1: *mut nvlist_t, 452 | arg2: *const ::std::os::raw::c_char, 453 | arg3: i32, 454 | ) -> ::std::os::raw::c_int; 455 | } 456 | extern "C" { 457 | pub fn nvlist_add_uint32( 458 | arg1: *mut nvlist_t, 459 | arg2: *const ::std::os::raw::c_char, 460 | arg3: u32, 461 | ) -> ::std::os::raw::c_int; 462 | } 463 | extern "C" { 464 | pub fn nvlist_add_int64( 465 | arg1: *mut nvlist_t, 466 | arg2: *const ::std::os::raw::c_char, 467 | arg3: i64, 468 | ) -> ::std::os::raw::c_int; 469 | } 470 | extern "C" { 471 | pub fn nvlist_add_uint64( 472 | arg1: *mut nvlist_t, 473 | arg2: *const ::std::os::raw::c_char, 474 | arg3: u64, 475 | ) -> ::std::os::raw::c_int; 476 | } 477 | extern "C" { 478 | pub fn nvlist_add_string( 479 | arg1: *mut nvlist_t, 480 | arg2: *const ::std::os::raw::c_char, 481 | arg3: *const ::std::os::raw::c_char, 482 | ) -> ::std::os::raw::c_int; 483 | } 484 | extern "C" { 485 | pub fn nvlist_add_nvlist( 486 | arg1: *mut nvlist_t, 487 | arg2: *const ::std::os::raw::c_char, 488 | arg3: *mut nvlist_t, 489 | ) -> ::std::os::raw::c_int; 490 | } 491 | extern "C" { 492 | pub fn nvlist_add_boolean_array( 493 | arg1: *mut nvlist_t, 494 | arg2: *const ::std::os::raw::c_char, 495 | arg3: *mut boolean_t::Type, 496 | arg4: uint_t, 497 | ) -> ::std::os::raw::c_int; 498 | } 499 | extern "C" { 500 | pub fn nvlist_add_byte_array( 501 | arg1: *mut nvlist_t, 502 | arg2: *const ::std::os::raw::c_char, 503 | arg3: *mut uchar_t, 504 | arg4: uint_t, 505 | ) -> ::std::os::raw::c_int; 506 | } 507 | extern "C" { 508 | pub fn nvlist_add_int8_array( 509 | arg1: *mut nvlist_t, 510 | arg2: *const ::std::os::raw::c_char, 511 | arg3: *mut i8, 512 | arg4: uint_t, 513 | ) -> ::std::os::raw::c_int; 514 | } 515 | extern "C" { 516 | pub fn nvlist_add_uint8_array( 517 | arg1: *mut nvlist_t, 518 | arg2: *const ::std::os::raw::c_char, 519 | arg3: *mut u8, 520 | arg4: uint_t, 521 | ) -> ::std::os::raw::c_int; 522 | } 523 | extern "C" { 524 | pub fn nvlist_add_int16_array( 525 | arg1: *mut nvlist_t, 526 | arg2: *const ::std::os::raw::c_char, 527 | arg3: *mut i16, 528 | arg4: uint_t, 529 | ) -> ::std::os::raw::c_int; 530 | } 531 | extern "C" { 532 | pub fn nvlist_add_uint16_array( 533 | arg1: *mut nvlist_t, 534 | arg2: *const ::std::os::raw::c_char, 535 | arg3: *mut u16, 536 | arg4: uint_t, 537 | ) -> ::std::os::raw::c_int; 538 | } 539 | extern "C" { 540 | pub fn nvlist_add_int32_array( 541 | arg1: *mut nvlist_t, 542 | arg2: *const ::std::os::raw::c_char, 543 | arg3: *mut i32, 544 | arg4: uint_t, 545 | ) -> ::std::os::raw::c_int; 546 | } 547 | extern "C" { 548 | pub fn nvlist_add_uint32_array( 549 | arg1: *mut nvlist_t, 550 | arg2: *const ::std::os::raw::c_char, 551 | arg3: *mut u32, 552 | arg4: uint_t, 553 | ) -> ::std::os::raw::c_int; 554 | } 555 | extern "C" { 556 | pub fn nvlist_add_int64_array( 557 | arg1: *mut nvlist_t, 558 | arg2: *const ::std::os::raw::c_char, 559 | arg3: *mut i64, 560 | arg4: uint_t, 561 | ) -> ::std::os::raw::c_int; 562 | } 563 | extern "C" { 564 | pub fn nvlist_add_uint64_array( 565 | arg1: *mut nvlist_t, 566 | arg2: *const ::std::os::raw::c_char, 567 | arg3: *mut u64, 568 | arg4: uint_t, 569 | ) -> ::std::os::raw::c_int; 570 | } 571 | extern "C" { 572 | pub fn nvlist_add_string_array( 573 | arg1: *mut nvlist_t, 574 | arg2: *const ::std::os::raw::c_char, 575 | arg3: *const *mut ::std::os::raw::c_char, 576 | arg4: uint_t, 577 | ) -> ::std::os::raw::c_int; 578 | } 579 | extern "C" { 580 | pub fn nvlist_add_nvlist_array( 581 | arg1: *mut nvlist_t, 582 | arg2: *const ::std::os::raw::c_char, 583 | arg3: *mut *mut nvlist_t, 584 | arg4: uint_t, 585 | ) -> ::std::os::raw::c_int; 586 | } 587 | extern "C" { 588 | pub fn nvlist_add_hrtime( 589 | arg1: *mut nvlist_t, 590 | arg2: *const ::std::os::raw::c_char, 591 | arg3: hrtime_t, 592 | ) -> ::std::os::raw::c_int; 593 | } 594 | extern "C" { 595 | pub fn nvlist_add_double( 596 | arg1: *mut nvlist_t, 597 | arg2: *const ::std::os::raw::c_char, 598 | arg3: f64, 599 | ) -> ::std::os::raw::c_int; 600 | } 601 | extern "C" { 602 | pub fn nvlist_remove( 603 | arg1: *mut nvlist_t, 604 | arg2: *const ::std::os::raw::c_char, 605 | arg3: data_type_t::Type, 606 | ) -> ::std::os::raw::c_int; 607 | } 608 | extern "C" { 609 | pub fn nvlist_remove_all( 610 | arg1: *mut nvlist_t, 611 | arg2: *const ::std::os::raw::c_char, 612 | ) -> ::std::os::raw::c_int; 613 | } 614 | extern "C" { 615 | pub fn nvlist_remove_nvpair(arg1: *mut nvlist_t, arg2: *mut nvpair_t) -> ::std::os::raw::c_int; 616 | } 617 | extern "C" { 618 | pub fn nvlist_lookup_boolean( 619 | arg1: *mut nvlist_t, 620 | arg2: *const ::std::os::raw::c_char, 621 | ) -> ::std::os::raw::c_int; 622 | } 623 | extern "C" { 624 | pub fn nvlist_lookup_boolean_value( 625 | arg1: *mut nvlist_t, 626 | arg2: *const ::std::os::raw::c_char, 627 | arg3: *mut boolean_t::Type, 628 | ) -> ::std::os::raw::c_int; 629 | } 630 | extern "C" { 631 | pub fn nvlist_lookup_byte( 632 | arg1: *mut nvlist_t, 633 | arg2: *const ::std::os::raw::c_char, 634 | arg3: *mut uchar_t, 635 | ) -> ::std::os::raw::c_int; 636 | } 637 | extern "C" { 638 | pub fn nvlist_lookup_int8( 639 | arg1: *mut nvlist_t, 640 | arg2: *const ::std::os::raw::c_char, 641 | arg3: *mut i8, 642 | ) -> ::std::os::raw::c_int; 643 | } 644 | extern "C" { 645 | pub fn nvlist_lookup_uint8( 646 | arg1: *mut nvlist_t, 647 | arg2: *const ::std::os::raw::c_char, 648 | arg3: *mut u8, 649 | ) -> ::std::os::raw::c_int; 650 | } 651 | extern "C" { 652 | pub fn nvlist_lookup_int16( 653 | arg1: *mut nvlist_t, 654 | arg2: *const ::std::os::raw::c_char, 655 | arg3: *mut i16, 656 | ) -> ::std::os::raw::c_int; 657 | } 658 | extern "C" { 659 | pub fn nvlist_lookup_uint16( 660 | arg1: *mut nvlist_t, 661 | arg2: *const ::std::os::raw::c_char, 662 | arg3: *mut u16, 663 | ) -> ::std::os::raw::c_int; 664 | } 665 | extern "C" { 666 | pub fn nvlist_lookup_int32( 667 | arg1: *mut nvlist_t, 668 | arg2: *const ::std::os::raw::c_char, 669 | arg3: *mut i32, 670 | ) -> ::std::os::raw::c_int; 671 | } 672 | extern "C" { 673 | pub fn nvlist_lookup_uint32( 674 | arg1: *mut nvlist_t, 675 | arg2: *const ::std::os::raw::c_char, 676 | arg3: *mut u32, 677 | ) -> ::std::os::raw::c_int; 678 | } 679 | extern "C" { 680 | pub fn nvlist_lookup_int64( 681 | arg1: *mut nvlist_t, 682 | arg2: *const ::std::os::raw::c_char, 683 | arg3: *mut i64, 684 | ) -> ::std::os::raw::c_int; 685 | } 686 | extern "C" { 687 | pub fn nvlist_lookup_uint64( 688 | arg1: *mut nvlist_t, 689 | arg2: *const ::std::os::raw::c_char, 690 | arg3: *mut u64, 691 | ) -> ::std::os::raw::c_int; 692 | } 693 | extern "C" { 694 | pub fn nvlist_lookup_string( 695 | arg1: *mut nvlist_t, 696 | arg2: *const ::std::os::raw::c_char, 697 | arg3: *mut *mut ::std::os::raw::c_char, 698 | ) -> ::std::os::raw::c_int; 699 | } 700 | extern "C" { 701 | pub fn nvlist_lookup_nvlist( 702 | arg1: *mut nvlist_t, 703 | arg2: *const ::std::os::raw::c_char, 704 | arg3: *mut *mut nvlist_t, 705 | ) -> ::std::os::raw::c_int; 706 | } 707 | extern "C" { 708 | pub fn nvlist_lookup_boolean_array( 709 | arg1: *mut nvlist_t, 710 | arg2: *const ::std::os::raw::c_char, 711 | arg3: *mut *mut boolean_t::Type, 712 | arg4: *mut uint_t, 713 | ) -> ::std::os::raw::c_int; 714 | } 715 | extern "C" { 716 | pub fn nvlist_lookup_byte_array( 717 | arg1: *mut nvlist_t, 718 | arg2: *const ::std::os::raw::c_char, 719 | arg3: *mut *mut uchar_t, 720 | arg4: *mut uint_t, 721 | ) -> ::std::os::raw::c_int; 722 | } 723 | extern "C" { 724 | pub fn nvlist_lookup_int8_array( 725 | arg1: *mut nvlist_t, 726 | arg2: *const ::std::os::raw::c_char, 727 | arg3: *mut *mut i8, 728 | arg4: *mut uint_t, 729 | ) -> ::std::os::raw::c_int; 730 | } 731 | extern "C" { 732 | pub fn nvlist_lookup_uint8_array( 733 | arg1: *mut nvlist_t, 734 | arg2: *const ::std::os::raw::c_char, 735 | arg3: *mut *mut u8, 736 | arg4: *mut uint_t, 737 | ) -> ::std::os::raw::c_int; 738 | } 739 | extern "C" { 740 | pub fn nvlist_lookup_int16_array( 741 | arg1: *mut nvlist_t, 742 | arg2: *const ::std::os::raw::c_char, 743 | arg3: *mut *mut i16, 744 | arg4: *mut uint_t, 745 | ) -> ::std::os::raw::c_int; 746 | } 747 | extern "C" { 748 | pub fn nvlist_lookup_uint16_array( 749 | arg1: *mut nvlist_t, 750 | arg2: *const ::std::os::raw::c_char, 751 | arg3: *mut *mut u16, 752 | arg4: *mut uint_t, 753 | ) -> ::std::os::raw::c_int; 754 | } 755 | extern "C" { 756 | pub fn nvlist_lookup_int32_array( 757 | arg1: *mut nvlist_t, 758 | arg2: *const ::std::os::raw::c_char, 759 | arg3: *mut *mut i32, 760 | arg4: *mut uint_t, 761 | ) -> ::std::os::raw::c_int; 762 | } 763 | extern "C" { 764 | pub fn nvlist_lookup_uint32_array( 765 | arg1: *mut nvlist_t, 766 | arg2: *const ::std::os::raw::c_char, 767 | arg3: *mut *mut u32, 768 | arg4: *mut uint_t, 769 | ) -> ::std::os::raw::c_int; 770 | } 771 | extern "C" { 772 | pub fn nvlist_lookup_int64_array( 773 | arg1: *mut nvlist_t, 774 | arg2: *const ::std::os::raw::c_char, 775 | arg3: *mut *mut i64, 776 | arg4: *mut uint_t, 777 | ) -> ::std::os::raw::c_int; 778 | } 779 | extern "C" { 780 | pub fn nvlist_lookup_uint64_array( 781 | arg1: *mut nvlist_t, 782 | arg2: *const ::std::os::raw::c_char, 783 | arg3: *mut *mut u64, 784 | arg4: *mut uint_t, 785 | ) -> ::std::os::raw::c_int; 786 | } 787 | extern "C" { 788 | pub fn nvlist_lookup_string_array( 789 | arg1: *mut nvlist_t, 790 | arg2: *const ::std::os::raw::c_char, 791 | arg3: *mut *mut *mut ::std::os::raw::c_char, 792 | arg4: *mut uint_t, 793 | ) -> ::std::os::raw::c_int; 794 | } 795 | extern "C" { 796 | pub fn nvlist_lookup_nvlist_array( 797 | arg1: *mut nvlist_t, 798 | arg2: *const ::std::os::raw::c_char, 799 | arg3: *mut *mut *mut nvlist_t, 800 | arg4: *mut uint_t, 801 | ) -> ::std::os::raw::c_int; 802 | } 803 | extern "C" { 804 | pub fn nvlist_lookup_hrtime( 805 | arg1: *mut nvlist_t, 806 | arg2: *const ::std::os::raw::c_char, 807 | arg3: *mut hrtime_t, 808 | ) -> ::std::os::raw::c_int; 809 | } 810 | extern "C" { 811 | pub fn nvlist_lookup_pairs( 812 | arg1: *mut nvlist_t, 813 | arg2: ::std::os::raw::c_int, 814 | ... 815 | ) -> ::std::os::raw::c_int; 816 | } 817 | extern "C" { 818 | pub fn nvlist_lookup_double( 819 | arg1: *mut nvlist_t, 820 | arg2: *const ::std::os::raw::c_char, 821 | arg3: *mut f64, 822 | ) -> ::std::os::raw::c_int; 823 | } 824 | extern "C" { 825 | pub fn nvlist_lookup_nvpair( 826 | arg1: *mut nvlist_t, 827 | arg2: *const ::std::os::raw::c_char, 828 | arg3: *mut *mut nvpair_t, 829 | ) -> ::std::os::raw::c_int; 830 | } 831 | extern "C" { 832 | pub fn nvlist_lookup_nvpair_embedded_index( 833 | arg1: *mut nvlist_t, 834 | arg2: *const ::std::os::raw::c_char, 835 | arg3: *mut *mut nvpair_t, 836 | arg4: *mut ::std::os::raw::c_int, 837 | arg5: *mut *mut ::std::os::raw::c_char, 838 | ) -> ::std::os::raw::c_int; 839 | } 840 | extern "C" { 841 | pub fn nvlist_exists( 842 | arg1: *mut nvlist_t, 843 | arg2: *const ::std::os::raw::c_char, 844 | ) -> boolean_t::Type; 845 | } 846 | extern "C" { 847 | pub fn nvlist_empty(arg1: *mut nvlist_t) -> boolean_t::Type; 848 | } 849 | extern "C" { 850 | pub fn nvlist_next_nvpair(arg1: *mut nvlist_t, arg2: *mut nvpair_t) -> *mut nvpair_t; 851 | } 852 | extern "C" { 853 | pub fn nvlist_prev_nvpair(arg1: *mut nvlist_t, arg2: *mut nvpair_t) -> *mut nvpair_t; 854 | } 855 | extern "C" { 856 | pub fn nvpair_name(arg1: *mut nvpair_t) -> *mut ::std::os::raw::c_char; 857 | } 858 | extern "C" { 859 | pub fn nvpair_type(arg1: *mut nvpair_t) -> data_type_t::Type; 860 | } 861 | extern "C" { 862 | pub fn nvpair_type_is_array(arg1: *mut nvpair_t) -> ::std::os::raw::c_int; 863 | } 864 | extern "C" { 865 | pub fn nvpair_value_boolean_value( 866 | arg1: *mut nvpair_t, 867 | arg2: *mut boolean_t::Type, 868 | ) -> ::std::os::raw::c_int; 869 | } 870 | extern "C" { 871 | pub fn nvpair_value_byte(arg1: *mut nvpair_t, arg2: *mut uchar_t) -> ::std::os::raw::c_int; 872 | } 873 | extern "C" { 874 | pub fn nvpair_value_int8(arg1: *mut nvpair_t, arg2: *mut i8) -> ::std::os::raw::c_int; 875 | } 876 | extern "C" { 877 | pub fn nvpair_value_uint8(arg1: *mut nvpair_t, arg2: *mut u8) -> ::std::os::raw::c_int; 878 | } 879 | extern "C" { 880 | pub fn nvpair_value_int16(arg1: *mut nvpair_t, arg2: *mut i16) -> ::std::os::raw::c_int; 881 | } 882 | extern "C" { 883 | pub fn nvpair_value_uint16(arg1: *mut nvpair_t, arg2: *mut u16) -> ::std::os::raw::c_int; 884 | } 885 | extern "C" { 886 | pub fn nvpair_value_int32(arg1: *mut nvpair_t, arg2: *mut i32) -> ::std::os::raw::c_int; 887 | } 888 | extern "C" { 889 | pub fn nvpair_value_uint32(arg1: *mut nvpair_t, arg2: *mut u32) -> ::std::os::raw::c_int; 890 | } 891 | extern "C" { 892 | pub fn nvpair_value_int64(arg1: *mut nvpair_t, arg2: *mut i64) -> ::std::os::raw::c_int; 893 | } 894 | extern "C" { 895 | pub fn nvpair_value_uint64(arg1: *mut nvpair_t, arg2: *mut u64) -> ::std::os::raw::c_int; 896 | } 897 | extern "C" { 898 | pub fn nvpair_value_string( 899 | arg1: *mut nvpair_t, 900 | arg2: *mut *mut ::std::os::raw::c_char, 901 | ) -> ::std::os::raw::c_int; 902 | } 903 | extern "C" { 904 | pub fn nvpair_value_nvlist( 905 | arg1: *mut nvpair_t, 906 | arg2: *mut *mut nvlist_t, 907 | ) -> ::std::os::raw::c_int; 908 | } 909 | extern "C" { 910 | pub fn nvpair_value_boolean_array( 911 | arg1: *mut nvpair_t, 912 | arg2: *mut *mut boolean_t::Type, 913 | arg3: *mut uint_t, 914 | ) -> ::std::os::raw::c_int; 915 | } 916 | extern "C" { 917 | pub fn nvpair_value_byte_array( 918 | arg1: *mut nvpair_t, 919 | arg2: *mut *mut uchar_t, 920 | arg3: *mut uint_t, 921 | ) -> ::std::os::raw::c_int; 922 | } 923 | extern "C" { 924 | pub fn nvpair_value_int8_array( 925 | arg1: *mut nvpair_t, 926 | arg2: *mut *mut i8, 927 | arg3: *mut uint_t, 928 | ) -> ::std::os::raw::c_int; 929 | } 930 | extern "C" { 931 | pub fn nvpair_value_uint8_array( 932 | arg1: *mut nvpair_t, 933 | arg2: *mut *mut u8, 934 | arg3: *mut uint_t, 935 | ) -> ::std::os::raw::c_int; 936 | } 937 | extern "C" { 938 | pub fn nvpair_value_int16_array( 939 | arg1: *mut nvpair_t, 940 | arg2: *mut *mut i16, 941 | arg3: *mut uint_t, 942 | ) -> ::std::os::raw::c_int; 943 | } 944 | extern "C" { 945 | pub fn nvpair_value_uint16_array( 946 | arg1: *mut nvpair_t, 947 | arg2: *mut *mut u16, 948 | arg3: *mut uint_t, 949 | ) -> ::std::os::raw::c_int; 950 | } 951 | extern "C" { 952 | pub fn nvpair_value_int32_array( 953 | arg1: *mut nvpair_t, 954 | arg2: *mut *mut i32, 955 | arg3: *mut uint_t, 956 | ) -> ::std::os::raw::c_int; 957 | } 958 | extern "C" { 959 | pub fn nvpair_value_uint32_array( 960 | arg1: *mut nvpair_t, 961 | arg2: *mut *mut u32, 962 | arg3: *mut uint_t, 963 | ) -> ::std::os::raw::c_int; 964 | } 965 | extern "C" { 966 | pub fn nvpair_value_int64_array( 967 | arg1: *mut nvpair_t, 968 | arg2: *mut *mut i64, 969 | arg3: *mut uint_t, 970 | ) -> ::std::os::raw::c_int; 971 | } 972 | extern "C" { 973 | pub fn nvpair_value_uint64_array( 974 | arg1: *mut nvpair_t, 975 | arg2: *mut *mut u64, 976 | arg3: *mut uint_t, 977 | ) -> ::std::os::raw::c_int; 978 | } 979 | extern "C" { 980 | pub fn nvpair_value_string_array( 981 | arg1: *mut nvpair_t, 982 | arg2: *mut *mut *mut ::std::os::raw::c_char, 983 | arg3: *mut uint_t, 984 | ) -> ::std::os::raw::c_int; 985 | } 986 | extern "C" { 987 | pub fn nvpair_value_nvlist_array( 988 | arg1: *mut nvpair_t, 989 | arg2: *mut *mut *mut nvlist_t, 990 | arg3: *mut uint_t, 991 | ) -> ::std::os::raw::c_int; 992 | } 993 | extern "C" { 994 | pub fn nvpair_value_hrtime(arg1: *mut nvpair_t, arg2: *mut hrtime_t) -> ::std::os::raw::c_int; 995 | } 996 | extern "C" { 997 | pub fn nvpair_value_double(arg1: *mut nvpair_t, arg2: *mut f64) -> ::std::os::raw::c_int; 998 | } 999 | -------------------------------------------------------------------------------- /nvpair-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | // workaround for https://github.com/rust-lang/rust-bindgen/issues/1651 5 | #![allow(unknown_lints)] 6 | #![allow(deref_nullptr)] 7 | pub type size_t = ::std::os::raw::c_ulong; 8 | 9 | pub enum __va_list_tag {} 10 | 11 | // TODO: get bindgen to emit these defines 12 | pub const NV_VERSION: ::std::os::raw::c_int = 0; 13 | 14 | pub const NV_ENCODE_NATIVE: ::std::os::raw::c_int = 0; 15 | pub const NV_ENCODE_XDR: ::std::os::raw::c_int = 1; 16 | 17 | pub const NV_UNIQUE_NAME: ::std::os::raw::c_uint = 1; 18 | pub const NV_UNIQUE_NAME_TYPE: ::std::os::raw::c_uint = 2; 19 | 20 | pub const NV_FLAG_NOENTOK: ::std::os::raw::c_int = 1; 21 | 22 | include!("bindings.rs"); 23 | -------------------------------------------------------------------------------- /nvpair-sys/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /nvpair/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nvpair" 3 | version = "0.5.0" 4 | authors = ["Cody P Schafer "] 5 | include = ["**/*.rs", "Cargo.toml"] 6 | documentation = "https://docs.rs/nvpair" 7 | repository = "https://github.com/jmesmon/rust-libzfs" 8 | description = "Work with nvlist and nvpair (using nvpair-sys, libnvpair.so)" 9 | license = "Apache-2.0 OR MIT" 10 | edition = "2018" 11 | 12 | [dependencies] 13 | cstr-argument = "0.1" 14 | nvpair-sys = { path = "../nvpair-sys", version = "0.4.0" } 15 | foreign-types = "0.5.0" 16 | 17 | [badges] 18 | travis-ci = { repository = "jmesmon/rust-libzfs" } 19 | -------------------------------------------------------------------------------- /nvpair/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_debug_implementations, rust_2018_idioms)] 2 | 3 | use cstr_argument::CStrArgument; 4 | use foreign_types::{foreign_type, ForeignType, ForeignTypeRef, Opaque}; 5 | use nvpair_sys as sys; 6 | use std::mem::MaybeUninit; 7 | use std::os::raw::c_int; 8 | use std::{ffi, fmt, io, ptr}; 9 | 10 | #[derive(Debug)] 11 | pub enum NvData<'a> { 12 | Unknown, 13 | Bool, 14 | BoolV(bool), 15 | Byte(u8), 16 | Int8(i8), 17 | Uint8(u8), 18 | Int16(i16), 19 | Uint16(u16), 20 | Int32(i32), 21 | Uint32(u32), 22 | Int64(i64), 23 | Uint64(u64), 24 | Str(&'a ffi::CStr), 25 | NvListRef(&'a NvListRef), 26 | ByteArray(&'a [u8]), 27 | Int8Array(&'a [i8]), 28 | Uint8Array(&'a [u8]), 29 | Int16Array(&'a [i16]), 30 | Uint16Array(&'a [u16]), 31 | Int32Array(&'a [i32]), 32 | Uint32Array(&'a [u32]), 33 | Int64Array(&'a [i64]), 34 | Uint64Array(&'a [u64]), 35 | NvListRefArray(Vec<&'a NvListRef>), 36 | /* TODO: 37 | pub const DATA_TYPE_STRING_ARRAY: Type = 17; 38 | pub const DATA_TYPE_HRTIME: Type = 18; 39 | pub const DATA_TYPE_BOOLEAN_ARRAY: Type = 24; 40 | pub const DATA_TYPE_DOUBLE: Type = 27; 41 | */ 42 | } 43 | 44 | pub trait NvEncode { 45 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()>; 46 | //fn read(NvPair &nv) -> io::Result; 47 | } 48 | 49 | impl NvEncode for bool { 50 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 51 | let name = name.into_cstr(); 52 | let v = unsafe { 53 | sys::nvlist_add_boolean_value( 54 | nv.as_mut_ptr(), 55 | name.as_ref().as_ptr(), 56 | if *self { 57 | sys::boolean_t::B_TRUE 58 | } else { 59 | sys::boolean_t::B_FALSE 60 | }, 61 | ) 62 | }; 63 | if v != 0 { 64 | Err(io::Error::from_raw_os_error(v)) 65 | } else { 66 | Ok(()) 67 | } 68 | } 69 | } 70 | 71 | impl NvEncode for i8 { 72 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 73 | let name = name.into_cstr(); 74 | let v = unsafe { sys::nvlist_add_int8(nv.as_mut_ptr(), name.as_ref().as_ptr(), *self) }; 75 | if v != 0 { 76 | Err(io::Error::from_raw_os_error(v)) 77 | } else { 78 | Ok(()) 79 | } 80 | } 81 | } 82 | 83 | impl NvEncode for u8 { 84 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 85 | let name = name.into_cstr(); 86 | let v = unsafe { sys::nvlist_add_uint8(nv.as_mut_ptr(), name.as_ref().as_ptr(), *self) }; 87 | if v != 0 { 88 | Err(io::Error::from_raw_os_error(v)) 89 | } else { 90 | Ok(()) 91 | } 92 | } 93 | } 94 | 95 | impl NvEncode for i16 { 96 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 97 | let name = name.into_cstr(); 98 | let v = unsafe { sys::nvlist_add_int16(nv.as_mut_ptr(), name.as_ref().as_ptr(), *self) }; 99 | if v != 0 { 100 | Err(io::Error::from_raw_os_error(v)) 101 | } else { 102 | Ok(()) 103 | } 104 | } 105 | } 106 | 107 | impl NvEncode for u16 { 108 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 109 | let name = name.into_cstr(); 110 | let v = unsafe { sys::nvlist_add_uint16(nv.as_mut_ptr(), name.as_ref().as_ptr(), *self) }; 111 | if v != 0 { 112 | Err(io::Error::from_raw_os_error(v)) 113 | } else { 114 | Ok(()) 115 | } 116 | } 117 | } 118 | 119 | impl NvEncode for i32 { 120 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 121 | let name = name.into_cstr(); 122 | let v = unsafe { sys::nvlist_add_int32(nv.as_mut_ptr(), name.as_ref().as_ptr(), *self) }; 123 | if v != 0 { 124 | Err(io::Error::from_raw_os_error(v)) 125 | } else { 126 | Ok(()) 127 | } 128 | } 129 | } 130 | 131 | impl NvEncode for u32 { 132 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 133 | let name = name.into_cstr(); 134 | let v = unsafe { sys::nvlist_add_uint32(nv.as_mut_ptr(), name.as_ref().as_ptr(), *self) }; 135 | if v != 0 { 136 | Err(io::Error::from_raw_os_error(v)) 137 | } else { 138 | Ok(()) 139 | } 140 | } 141 | } 142 | 143 | impl NvEncode for i64 { 144 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 145 | let name = name.into_cstr(); 146 | let v = unsafe { sys::nvlist_add_int64(nv.as_mut_ptr(), name.as_ref().as_ptr(), *self) }; 147 | if v != 0 { 148 | Err(io::Error::from_raw_os_error(v)) 149 | } else { 150 | Ok(()) 151 | } 152 | } 153 | } 154 | 155 | impl NvEncode for u64 { 156 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 157 | let name = name.into_cstr(); 158 | let v = unsafe { sys::nvlist_add_uint64(nv.as_mut_ptr(), name.as_ref().as_ptr(), *self) }; 159 | if v != 0 { 160 | Err(io::Error::from_raw_os_error(v)) 161 | } else { 162 | Ok(()) 163 | } 164 | } 165 | } 166 | 167 | impl NvEncode for [i8] { 168 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 169 | let name = name.into_cstr(); 170 | let v = unsafe { 171 | sys::nvlist_add_int8_array( 172 | nv.as_mut_ptr(), 173 | name.as_ref().as_ptr(), 174 | self.as_ptr() as *mut i8, 175 | self.len() as u32, 176 | ) 177 | }; 178 | if v != 0 { 179 | Err(io::Error::from_raw_os_error(v)) 180 | } else { 181 | Ok(()) 182 | } 183 | } 184 | } 185 | 186 | impl NvEncode for [u8] { 187 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 188 | let name = name.into_cstr(); 189 | let v = unsafe { 190 | sys::nvlist_add_uint8_array( 191 | nv.as_mut_ptr(), 192 | name.as_ref().as_ptr(), 193 | self.as_ptr() as *mut u8, 194 | self.len() as u32, 195 | ) 196 | }; 197 | if v != 0 { 198 | Err(io::Error::from_raw_os_error(v)) 199 | } else { 200 | Ok(()) 201 | } 202 | } 203 | } 204 | 205 | impl NvEncode for [i16] { 206 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 207 | let name = name.into_cstr(); 208 | let v = unsafe { 209 | sys::nvlist_add_int16_array( 210 | nv.as_mut_ptr(), 211 | name.as_ref().as_ptr(), 212 | self.as_ptr() as *mut i16, 213 | self.len() as u32, 214 | ) 215 | }; 216 | if v != 0 { 217 | Err(io::Error::from_raw_os_error(v)) 218 | } else { 219 | Ok(()) 220 | } 221 | } 222 | } 223 | 224 | impl NvEncode for [u16] { 225 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 226 | let name = name.into_cstr(); 227 | let v = unsafe { 228 | sys::nvlist_add_uint16_array( 229 | nv.as_mut_ptr(), 230 | name.as_ref().as_ptr(), 231 | self.as_ptr() as *mut u16, 232 | self.len() as u32, 233 | ) 234 | }; 235 | if v != 0 { 236 | Err(io::Error::from_raw_os_error(v)) 237 | } else { 238 | Ok(()) 239 | } 240 | } 241 | } 242 | 243 | impl NvEncode for [i32] { 244 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 245 | let name = name.into_cstr(); 246 | let v = unsafe { 247 | sys::nvlist_add_int32_array( 248 | nv.as_mut_ptr(), 249 | name.as_ref().as_ptr(), 250 | self.as_ptr() as *mut i32, 251 | self.len() as u32, 252 | ) 253 | }; 254 | if v != 0 { 255 | Err(io::Error::from_raw_os_error(v)) 256 | } else { 257 | Ok(()) 258 | } 259 | } 260 | } 261 | 262 | impl NvEncode for [u32] { 263 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 264 | let name = name.into_cstr(); 265 | let v = unsafe { 266 | sys::nvlist_add_uint32_array( 267 | nv.as_mut_ptr(), 268 | name.as_ref().as_ptr(), 269 | self.as_ptr() as *mut u32, 270 | self.len() as u32, 271 | ) 272 | }; 273 | if v != 0 { 274 | Err(io::Error::from_raw_os_error(v)) 275 | } else { 276 | Ok(()) 277 | } 278 | } 279 | } 280 | 281 | impl NvEncode for [i64] { 282 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 283 | let name = name.into_cstr(); 284 | let v = unsafe { 285 | sys::nvlist_add_int64_array( 286 | nv.as_mut_ptr(), 287 | name.as_ref().as_ptr(), 288 | self.as_ptr() as *mut i64, 289 | self.len() as u32, 290 | ) 291 | }; 292 | if v != 0 { 293 | Err(io::Error::from_raw_os_error(v)) 294 | } else { 295 | Ok(()) 296 | } 297 | } 298 | } 299 | 300 | impl NvEncode for [u64] { 301 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 302 | let name = name.into_cstr(); 303 | let v = unsafe { 304 | sys::nvlist_add_uint64_array( 305 | nv.as_mut_ptr(), 306 | name.as_ref().as_ptr(), 307 | self.as_ptr() as *mut u64, 308 | self.len() as u32, 309 | ) 310 | }; 311 | if v != 0 { 312 | Err(io::Error::from_raw_os_error(v)) 313 | } else { 314 | Ok(()) 315 | } 316 | } 317 | } 318 | 319 | impl NvEncode for ffi::CStr { 320 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 321 | let name = name.into_cstr(); 322 | let v = unsafe { 323 | sys::nvlist_add_string(nv.as_mut_ptr(), name.as_ref().as_ptr(), self.as_ptr()) 324 | }; 325 | if v != 0 { 326 | Err(io::Error::from_raw_os_error(v)) 327 | } else { 328 | Ok(()) 329 | } 330 | } 331 | } 332 | 333 | impl NvEncode for &str { 334 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 335 | std::ffi::CString::new(*self).unwrap().insert_into(name, nv) 336 | } 337 | } 338 | 339 | impl NvEncode for NvListRef { 340 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 341 | let name = name.into_cstr(); 342 | let v = unsafe { 343 | sys::nvlist_add_nvlist( 344 | nv.as_mut_ptr(), 345 | name.as_ref().as_ptr(), 346 | self.as_ptr() as *mut _, 347 | ) 348 | }; 349 | if v != 0 { 350 | Err(io::Error::from_raw_os_error(v)) 351 | } else { 352 | Ok(()) 353 | } 354 | } 355 | } 356 | 357 | impl NvEncode for () { 358 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 359 | let name = name.into_cstr(); 360 | let v = unsafe { sys::nvlist_add_boolean(nv.as_mut_ptr(), name.as_ref().as_ptr()) }; 361 | if v != 0 { 362 | Err(io::Error::from_raw_os_error(v)) 363 | } else { 364 | Ok(()) 365 | } 366 | } 367 | } 368 | 369 | impl NvEncode for str { 370 | fn insert_into(&self, name: S, nv: &mut NvListRef) -> io::Result<()> { 371 | ffi::CString::new(self).unwrap().insert_into(name, nv) 372 | } 373 | } 374 | 375 | #[derive(Debug, Copy, Clone)] 376 | pub enum NvEncoding { 377 | Native, 378 | Xdr, 379 | } 380 | 381 | impl NvEncoding { 382 | fn as_raw(&self) -> c_int { 383 | match self { 384 | NvEncoding::Native => sys::NV_ENCODE_NATIVE, 385 | NvEncoding::Xdr => sys::NV_ENCODE_XDR, 386 | } 387 | } 388 | } 389 | 390 | foreign_type! { 391 | /// An `NvList` 392 | pub unsafe type NvList: Send { 393 | type CType = sys::nvlist; 394 | fn drop = sys::nvlist_free; 395 | } 396 | } 397 | 398 | impl NvList { 399 | /// Try to create a new `NvList` with no options. 400 | /// 401 | /// Returns an error if memory allocation fails 402 | #[doc(alias = "nvlist_alloc")] 403 | pub fn try_new() -> io::Result { 404 | let mut n = ptr::null_mut(); 405 | let v = unsafe { 406 | // TODO: second arg is a bitfield of NV_UNIQUE_NAME|NV_UNIQUE_NAME_TYPE 407 | sys::nvlist_alloc(&mut n, 0, 0) 408 | }; 409 | if v != 0 { 410 | Err(io::Error::from_raw_os_error(v)) 411 | } else { 412 | Ok(unsafe { Self::from_ptr(n) }) 413 | } 414 | } 415 | 416 | /// Try to create a new `NvList` with the `NV_UNIQUE_NAME` constraint 417 | /// 418 | /// Returns an error if memory allocation fails 419 | #[doc(alias = "nvlist_alloc")] 420 | pub fn try_new_unique_names() -> io::Result { 421 | let mut n = ptr::null_mut(); 422 | let v = unsafe { sys::nvlist_alloc(&mut n, sys::NV_UNIQUE_NAME, 0) }; 423 | if v != 0 { 424 | Err(io::Error::from_raw_os_error(v)) 425 | } else { 426 | Ok(unsafe { Self::from_ptr(n) }) 427 | } 428 | } 429 | 430 | /// Try to create a new `NvList` from the packed buffer 431 | /// 432 | /// Returns an error if memory allocation fails 433 | pub fn try_unpack(buf: &[u8]) -> io::Result { 434 | let mut n = ptr::null_mut(); 435 | let len_u64 = buf.len() as u64; 436 | let v = unsafe { nvpair_sys::nvlist_unpack(buf.as_ptr() as *mut _, len_u64, &mut n, 0) }; 437 | if v != 0 { 438 | Err(io::Error::from_raw_os_error(v)) 439 | } else { 440 | Ok(unsafe { Self::from_ptr(n) }) 441 | } 442 | } 443 | 444 | /// Create a new `NvList` with no options 445 | /// 446 | /// # Panics 447 | /// 448 | /// - if [`try_new()`] returns an error 449 | #[doc(alias = "nvlist_alloc")] 450 | pub fn new() -> Self { 451 | Self::try_new().unwrap() 452 | } 453 | 454 | /// Create a new `NvList` with the `NV_UNIQUE_NAME` constraint 455 | /// 456 | /// # Panics 457 | /// 458 | /// - if [`try_new_unique_names()`] returns an error 459 | #[doc(alias = "nvlist_alloc")] 460 | pub fn new_unique_names() -> Self { 461 | Self::try_new_unique_names().unwrap() 462 | } 463 | 464 | pub fn try_clone(&self) -> io::Result { 465 | let mut n = ptr::null_mut(); 466 | let v = unsafe { sys::nvlist_dup(self.as_ptr(), &mut n, 0) }; 467 | if v != 0 { 468 | Err(io::Error::from_raw_os_error(v)) 469 | } else { 470 | Ok(unsafe { Self::from_ptr(n) }) 471 | } 472 | } 473 | } 474 | 475 | impl Default for NvList { 476 | fn default() -> Self { 477 | Self::new() 478 | } 479 | } 480 | 481 | impl Clone for NvList { 482 | fn clone(&self) -> Self { 483 | self.try_clone().unwrap() 484 | } 485 | } 486 | 487 | impl NvListRef { 488 | /// # Safety 489 | /// 490 | /// Must be passed a valid, non-null nvlist pointer that is mutable, with a lifetime of at 491 | /// least `'a` 492 | pub unsafe fn from_mut_ptr<'a>(v: *mut sys::nvlist) -> &'a mut Self { 493 | &mut *(v as *mut Self) 494 | } 495 | 496 | /// # Safety 497 | /// 498 | /// Must be passed a valid, non-null nvlist pointer with a lifetime of at least `'a`. 499 | pub unsafe fn from_ptr<'a>(v: *const sys::nvlist) -> &'a Self { 500 | &*(v as *const Self) 501 | } 502 | 503 | pub fn as_mut_ptr(&mut self) -> *mut sys::nvlist { 504 | unsafe { std::mem::transmute::<&mut NvListRef, *mut sys::nvlist>(self) } 505 | } 506 | 507 | pub fn as_ptr(&self) -> *const sys::nvlist { 508 | unsafe { std::mem::transmute::<&NvListRef, *const sys::nvlist>(self) } 509 | } 510 | 511 | pub fn encoded_size(&self, encoding: NvEncoding) -> io::Result { 512 | let mut l = 0u64; 513 | let v = unsafe { sys::nvlist_size(self.as_ptr() as *mut _, &mut l, encoding.as_raw()) }; 514 | if v != 0 { 515 | Err(io::Error::from_raw_os_error(v)) 516 | } else { 517 | Ok(l) 518 | } 519 | } 520 | 521 | pub fn pack(&self, code: NvEncoding) -> io::Result> { 522 | let size = self.encoded_size(code).unwrap() as usize; 523 | let mut vec = Vec::with_capacity(size); 524 | let mut cap = vec.capacity() as u64; 525 | let mut ptr = vec.as_mut_ptr() as *mut i8; 526 | 527 | let v = unsafe { 528 | let v = nvpair_sys::nvlist_pack( 529 | self.as_ptr() as *mut _, 530 | &mut ptr, 531 | &mut cap, 532 | code.as_raw(), 533 | 0, 534 | ); 535 | vec.set_len(cap as usize); 536 | v 537 | }; 538 | if v != 0 { 539 | Err(io::Error::from_raw_os_error(v)) 540 | } else { 541 | Ok(vec) 542 | } 543 | } 544 | 545 | pub fn is_empty(&self) -> bool { 546 | let v = unsafe { sys::nvlist_empty(self.as_ptr() as *mut _) }; 547 | v != sys::boolean_t::B_FALSE 548 | } 549 | 550 | pub fn add_boolean(&mut self, name: S) -> io::Result<()> { 551 | let name = name.into_cstr(); 552 | let v = unsafe { sys::nvlist_add_boolean(self.as_mut_ptr(), name.as_ref().as_ptr()) }; 553 | if v != 0 { 554 | Err(io::Error::from_raw_os_error(v)) 555 | } else { 556 | Ok(()) 557 | } 558 | } 559 | 560 | pub fn first(&self) -> Option<&NvPair> { 561 | let np = unsafe { sys::nvlist_next_nvpair(self.as_ptr() as *mut _, ptr::null_mut()) }; 562 | if np.is_null() { 563 | None 564 | } else { 565 | Some(unsafe { NvPair::from_ptr(np) }) 566 | } 567 | } 568 | 569 | pub fn iter(&self) -> NvListIter<'_> { 570 | NvListIter { 571 | parent: self, 572 | pos: ptr::null_mut(), 573 | } 574 | } 575 | 576 | pub fn exists(&self, name: S) -> bool { 577 | let name = name.into_cstr(); 578 | let v = unsafe { sys::nvlist_exists(self.as_ptr() as *mut _, name.as_ref().as_ptr()) }; 579 | v != sys::boolean_t::B_FALSE 580 | } 581 | 582 | /* 583 | // not allowed because `pair` is borrowed from `self`. Need to fiddle around so that we can 584 | // check: 585 | // - `pair` is from `self` 586 | // - `pair` is the only outstanding reference to this pair (need by-value semantics) 587 | pub fn remove(&mut self, pair: &NvPair) -> io::Result<()> 588 | { 589 | let v = unsafe { sys::nvlist_remove_nvpair(self.as_mut_ptr(), pair.as_ptr())}; 590 | if v != 0 { 591 | Err(io::Error::from_raw_os_error(v)) 592 | } else { 593 | Ok(()) 594 | } 595 | } 596 | */ 597 | 598 | pub fn lookup(&self, name: S) -> io::Result<&NvPair> { 599 | let name = name.into_cstr(); 600 | let mut n = ptr::null_mut(); 601 | let v = unsafe { 602 | sys::nvlist_lookup_nvpair(self.as_ptr() as *mut _, name.as_ref().as_ptr(), &mut n) 603 | }; 604 | if v != 0 { 605 | Err(io::Error::from_raw_os_error(v)) 606 | } else { 607 | Ok(unsafe { NvPair::from_ptr(n) }) 608 | } 609 | } 610 | 611 | pub fn try_to_owned(&self) -> io::Result { 612 | let mut n = MaybeUninit::uninit(); 613 | let v = unsafe { sys::nvlist_dup(self.as_ptr() as *mut _, n.as_mut_ptr(), 0) }; 614 | if v != 0 { 615 | Err(io::Error::from_raw_os_error(v)) 616 | } else { 617 | Ok(unsafe { NvList::from_ptr(n.assume_init()) }) 618 | } 619 | } 620 | 621 | pub fn lookup_nvlist(&self, name: S) -> io::Result { 622 | let name = name.into_cstr(); 623 | 624 | let mut n = MaybeUninit::uninit(); 625 | let v = unsafe { 626 | sys::nvlist_lookup_nvlist( 627 | self.as_ptr() as *mut _, 628 | name.as_ref().as_ptr(), 629 | n.as_mut_ptr(), 630 | ) 631 | }; 632 | if v != 0 { 633 | Err(io::Error::from_raw_os_error(v)) 634 | } else { 635 | let r = unsafe { NvList::from_ptr(n.assume_init()) }; 636 | Ok(r) 637 | } 638 | } 639 | 640 | pub fn lookup_string(&self, name: S) -> io::Result { 641 | let name = name.into_cstr(); 642 | let mut n = MaybeUninit::uninit(); 643 | let v = unsafe { 644 | sys::nvlist_lookup_string( 645 | self.as_ptr() as *mut _, 646 | name.as_ref().as_ptr(), 647 | n.as_mut_ptr(), 648 | ) 649 | }; 650 | 651 | if v != 0 { 652 | Err(io::Error::from_raw_os_error(v)) 653 | } else { 654 | let s = unsafe { ffi::CStr::from_ptr(n.assume_init()).to_owned() }; 655 | Ok(s) 656 | } 657 | } 658 | 659 | pub fn lookup_uint64(&self, name: S) -> io::Result { 660 | let name = name.into_cstr(); 661 | let mut n = MaybeUninit::uninit(); 662 | let v = unsafe { 663 | sys::nvlist_lookup_uint64( 664 | self.as_ptr() as *mut _, 665 | name.as_ref().as_ptr(), 666 | n.as_mut_ptr(), 667 | ) 668 | }; 669 | if v != 0 { 670 | Err(io::Error::from_raw_os_error(v)) 671 | } else { 672 | Ok(unsafe { n.assume_init() }) 673 | } 674 | } 675 | 676 | pub fn lookup_nvlist_array(&self, name: S) -> io::Result> { 677 | let name = name.into_cstr(); 678 | let mut n = ptr::null_mut(); 679 | let mut len = 0; 680 | let v = unsafe { 681 | sys::nvlist_lookup_nvlist_array( 682 | self.as_ptr() as *mut _, 683 | name.as_ref().as_ptr(), 684 | &mut n, 685 | &mut len, 686 | ) 687 | }; 688 | if v != 0 { 689 | Err(io::Error::from_raw_os_error(v)) 690 | } else { 691 | let r = unsafe { 692 | std::slice::from_raw_parts(n, len as usize) 693 | .iter() 694 | .map(|x| NvList::from_ptr(*x)) 695 | .collect() 696 | }; 697 | 698 | Ok(r) 699 | } 700 | } 701 | 702 | pub fn lookup_uint64_array(&self, name: S) -> io::Result> { 703 | let name = name.into_cstr(); 704 | 705 | let mut n = ptr::null_mut(); 706 | let mut len = 0; 707 | let v = unsafe { 708 | sys::nvlist_lookup_uint64_array( 709 | self.as_ptr() as *mut _, 710 | name.as_ref().as_ptr(), 711 | &mut n, 712 | &mut len, 713 | ) 714 | }; 715 | 716 | if v != 0 { 717 | Err(io::Error::from_raw_os_error(v)) 718 | } else { 719 | let r = unsafe { ::std::slice::from_raw_parts(n, len as usize).to_vec() }; 720 | 721 | Ok(r) 722 | } 723 | } 724 | 725 | // TODO: consider renaming to `try_insert()` and having a `insert()` with an inner unwrap. 726 | pub fn insert( 727 | &mut self, 728 | name: S, 729 | data: &D, 730 | ) -> io::Result<()> { 731 | data.insert_into(name, self) 732 | } 733 | } 734 | 735 | impl std::fmt::Debug for NvList { 736 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 737 | f.debug_map() 738 | .entries( 739 | self.iter() 740 | .map(|pair| (pair.name().to_owned().into_string().unwrap(), pair.data())), 741 | ) 742 | .finish() 743 | } 744 | } 745 | 746 | impl std::fmt::Debug for NvListRef { 747 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 748 | f.debug_map() 749 | .entries( 750 | self.iter() 751 | .map(|pair| (pair.name().to_owned().into_string().unwrap(), pair.data())), 752 | ) 753 | .finish() 754 | } 755 | } 756 | 757 | impl<'a> IntoIterator for &'a NvListRef { 758 | type Item = &'a NvPair; 759 | type IntoIter = NvListIter<'a>; 760 | 761 | fn into_iter(self) -> Self::IntoIter { 762 | self.iter() 763 | } 764 | } 765 | 766 | impl<'a> IntoIterator for &'a NvList { 767 | type Item = &'a NvPair; 768 | type IntoIter = NvListIter<'a>; 769 | 770 | fn into_iter(self) -> Self::IntoIter { 771 | self.iter() 772 | } 773 | } 774 | 775 | #[derive(Clone)] 776 | pub struct NvListIter<'a> { 777 | parent: &'a NvListRef, 778 | pos: *mut sys::nvpair, 779 | } 780 | 781 | impl<'a> fmt::Debug for NvListIter<'a> { 782 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 783 | f.debug_list().entries(self.clone()).finish() 784 | } 785 | } 786 | 787 | impl<'a> Iterator for NvListIter<'a> { 788 | type Item = &'a NvPair; 789 | 790 | fn next(&mut self) -> Option { 791 | let np = unsafe { sys::nvlist_next_nvpair(self.parent.as_ptr() as *mut _, self.pos) }; 792 | self.pos = np; 793 | if np.is_null() { 794 | None 795 | } else { 796 | Some(unsafe { NvPair::from_ptr(np) }) 797 | } 798 | } 799 | } 800 | 801 | pub struct NvPair(Opaque); 802 | unsafe impl ForeignTypeRef for NvPair { 803 | type CType = sys::nvpair; 804 | } 805 | 806 | impl NvPair { 807 | pub fn name(&self) -> &ffi::CStr { 808 | unsafe { ffi::CStr::from_ptr(sys::nvpair_name(self.as_ptr())) } 809 | } 810 | 811 | // TODO: consider defering decode here until actually requested by the caller. Users of 812 | // `data` might not care to decode most data types, meaning we're wasting time with the 813 | // various `nvpair_value_*()` calls some of the time. 814 | pub fn data(&self) -> NvData<'_> { 815 | let data_type = unsafe { sys::nvpair_type(self.as_ptr()) }; 816 | 817 | match data_type { 818 | sys::data_type_t::DATA_TYPE_BOOLEAN => NvData::Bool, 819 | sys::data_type_t::DATA_TYPE_BOOLEAN_VALUE => { 820 | let v = unsafe { 821 | let mut v = MaybeUninit::uninit(); 822 | sys::nvpair_value_boolean_value(self.as_ptr(), v.as_mut_ptr()); 823 | v.assume_init() 824 | }; 825 | 826 | NvData::BoolV(v == sys::boolean_t::B_TRUE) 827 | } 828 | sys::data_type_t::DATA_TYPE_BYTE => { 829 | let v = unsafe { 830 | let mut v = MaybeUninit::uninit(); 831 | sys::nvpair_value_byte(self.as_ptr(), v.as_mut_ptr()); 832 | v.assume_init() 833 | }; 834 | 835 | NvData::Byte(v) 836 | } 837 | sys::data_type_t::DATA_TYPE_INT8 => { 838 | let v = unsafe { 839 | let mut v = MaybeUninit::uninit(); 840 | sys::nvpair_value_int8(self.as_ptr(), v.as_mut_ptr()); 841 | v.assume_init() 842 | }; 843 | 844 | NvData::Int8(v) 845 | } 846 | sys::data_type_t::DATA_TYPE_UINT8 => { 847 | let v = unsafe { 848 | let mut v = MaybeUninit::uninit(); 849 | sys::nvpair_value_uint8(self.as_ptr(), v.as_mut_ptr()); 850 | v.assume_init() 851 | }; 852 | 853 | NvData::Uint8(v) 854 | } 855 | sys::data_type_t::DATA_TYPE_INT16 => { 856 | let v = unsafe { 857 | let mut v = MaybeUninit::uninit(); 858 | sys::nvpair_value_int16(self.as_ptr(), v.as_mut_ptr()); 859 | v.assume_init() 860 | }; 861 | 862 | NvData::Int16(v) 863 | } 864 | sys::data_type_t::DATA_TYPE_UINT16 => { 865 | let v = unsafe { 866 | let mut v = MaybeUninit::uninit(); 867 | sys::nvpair_value_uint16(self.as_ptr(), v.as_mut_ptr()); 868 | v.assume_init() 869 | }; 870 | 871 | NvData::Uint16(v) 872 | } 873 | sys::data_type_t::DATA_TYPE_INT32 => { 874 | let v = unsafe { 875 | let mut v = MaybeUninit::uninit(); 876 | sys::nvpair_value_int32(self.as_ptr(), v.as_mut_ptr()); 877 | v.assume_init() 878 | }; 879 | 880 | NvData::Int32(v) 881 | } 882 | sys::data_type_t::DATA_TYPE_UINT32 => { 883 | let v = unsafe { 884 | let mut v = MaybeUninit::uninit(); 885 | sys::nvpair_value_uint32(self.as_ptr(), v.as_mut_ptr()); 886 | v.assume_init() 887 | }; 888 | 889 | NvData::Uint32(v) 890 | } 891 | sys::data_type_t::DATA_TYPE_INT64 => { 892 | let v = unsafe { 893 | let mut v = MaybeUninit::uninit(); 894 | sys::nvpair_value_int64(self.as_ptr(), v.as_mut_ptr()); 895 | v.assume_init() 896 | }; 897 | 898 | NvData::Int64(v) 899 | } 900 | sys::data_type_t::DATA_TYPE_UINT64 => { 901 | let v = unsafe { 902 | let mut v = MaybeUninit::uninit(); 903 | sys::nvpair_value_uint64(self.as_ptr(), v.as_mut_ptr()); 904 | v.assume_init() 905 | }; 906 | 907 | NvData::Uint64(v) 908 | } 909 | sys::data_type_t::DATA_TYPE_STRING => { 910 | let s = unsafe { 911 | let mut n = MaybeUninit::uninit(); 912 | sys::nvpair_value_string(self.as_ptr(), n.as_mut_ptr()); 913 | ffi::CStr::from_ptr(n.assume_init()) 914 | }; 915 | 916 | NvData::Str(s) 917 | } 918 | sys::data_type_t::DATA_TYPE_NVLIST => { 919 | let l = unsafe { 920 | let mut l = MaybeUninit::uninit(); 921 | sys::nvpair_value_nvlist(self.as_ptr(), l.as_mut_ptr()); 922 | NvListRef::from_ptr(l.assume_init()) 923 | }; 924 | 925 | NvData::NvListRef(l) 926 | } 927 | sys::data_type_t::DATA_TYPE_BYTE_ARRAY => { 928 | let slice = unsafe { 929 | let mut array = MaybeUninit::uninit(); 930 | let mut len = MaybeUninit::uninit(); 931 | sys::nvpair_value_byte_array( 932 | self.as_ptr(), 933 | array.as_mut_ptr(), 934 | len.as_mut_ptr(), 935 | ); 936 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 937 | }; 938 | 939 | NvData::ByteArray(slice) 940 | } 941 | sys::data_type_t::DATA_TYPE_INT8_ARRAY => { 942 | let slice = unsafe { 943 | let mut array = MaybeUninit::uninit(); 944 | let mut len = MaybeUninit::uninit(); 945 | sys::nvpair_value_int8_array( 946 | self.as_ptr(), 947 | array.as_mut_ptr(), 948 | len.as_mut_ptr(), 949 | ); 950 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 951 | }; 952 | 953 | NvData::Int8Array(slice) 954 | } 955 | sys::data_type_t::DATA_TYPE_UINT8_ARRAY => { 956 | let slice = unsafe { 957 | let mut array = MaybeUninit::uninit(); 958 | let mut len = MaybeUninit::uninit(); 959 | sys::nvpair_value_uint8_array( 960 | self.as_ptr(), 961 | array.as_mut_ptr(), 962 | len.as_mut_ptr(), 963 | ); 964 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 965 | }; 966 | 967 | NvData::Uint8Array(slice) 968 | } 969 | sys::data_type_t::DATA_TYPE_INT16_ARRAY => { 970 | let slice = unsafe { 971 | let mut array = MaybeUninit::uninit(); 972 | let mut len = MaybeUninit::uninit(); 973 | sys::nvpair_value_int16_array( 974 | self.as_ptr(), 975 | array.as_mut_ptr(), 976 | len.as_mut_ptr(), 977 | ); 978 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 979 | }; 980 | 981 | NvData::Int16Array(slice) 982 | } 983 | sys::data_type_t::DATA_TYPE_UINT16_ARRAY => { 984 | let slice = unsafe { 985 | let mut array = MaybeUninit::uninit(); 986 | let mut len = MaybeUninit::uninit(); 987 | sys::nvpair_value_uint16_array( 988 | self.as_ptr(), 989 | array.as_mut_ptr(), 990 | len.as_mut_ptr(), 991 | ); 992 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 993 | }; 994 | 995 | NvData::Uint16Array(slice) 996 | } 997 | sys::data_type_t::DATA_TYPE_INT32_ARRAY => { 998 | let slice = unsafe { 999 | let mut array = MaybeUninit::uninit(); 1000 | let mut len = MaybeUninit::uninit(); 1001 | sys::nvpair_value_int32_array( 1002 | self.as_ptr(), 1003 | array.as_mut_ptr(), 1004 | len.as_mut_ptr(), 1005 | ); 1006 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 1007 | }; 1008 | 1009 | NvData::Int32Array(slice) 1010 | } 1011 | sys::data_type_t::DATA_TYPE_UINT32_ARRAY => { 1012 | let slice = unsafe { 1013 | let mut array = MaybeUninit::uninit(); 1014 | let mut len = MaybeUninit::uninit(); 1015 | sys::nvpair_value_uint32_array( 1016 | self.as_ptr(), 1017 | array.as_mut_ptr(), 1018 | len.as_mut_ptr(), 1019 | ); 1020 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 1021 | }; 1022 | 1023 | NvData::Uint32Array(slice) 1024 | } 1025 | sys::data_type_t::DATA_TYPE_INT64_ARRAY => { 1026 | let slice = unsafe { 1027 | let mut array = MaybeUninit::uninit(); 1028 | let mut len = MaybeUninit::uninit(); 1029 | sys::nvpair_value_int64_array( 1030 | self.as_ptr(), 1031 | array.as_mut_ptr(), 1032 | len.as_mut_ptr(), 1033 | ); 1034 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 1035 | }; 1036 | 1037 | NvData::Int64Array(slice) 1038 | } 1039 | sys::data_type_t::DATA_TYPE_UINT64_ARRAY => { 1040 | let slice = unsafe { 1041 | let mut array = MaybeUninit::uninit(); 1042 | let mut len = MaybeUninit::uninit(); 1043 | sys::nvpair_value_uint64_array( 1044 | self.as_ptr(), 1045 | array.as_mut_ptr(), 1046 | len.as_mut_ptr(), 1047 | ); 1048 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 1049 | }; 1050 | 1051 | NvData::Uint64Array(slice) 1052 | } 1053 | sys::data_type_t::DATA_TYPE_NVLIST_ARRAY => { 1054 | let slice = unsafe { 1055 | let mut array = MaybeUninit::uninit(); 1056 | let mut len = MaybeUninit::uninit(); 1057 | sys::nvpair_value_nvlist_array( 1058 | self.as_ptr(), 1059 | array.as_mut_ptr(), 1060 | len.as_mut_ptr(), 1061 | ); 1062 | std::slice::from_raw_parts(array.assume_init(), len.assume_init() as usize) 1063 | }; 1064 | let mut vec = Vec::with_capacity(slice.len()); 1065 | for p in slice { 1066 | vec.push(unsafe { NvListRef::from_ptr(*p) }); 1067 | } 1068 | 1069 | NvData::NvListRefArray(vec) 1070 | } 1071 | /* TODO: 1072 | pub const DATA_TYPE_STRING_ARRAY: Type = 17; 1073 | pub const DATA_TYPE_HRTIME: Type = 18; 1074 | pub const DATA_TYPE_BOOLEAN_ARRAY: Type = 24; 1075 | pub const DATA_TYPE_DOUBLE: Type = 27; 1076 | */ 1077 | _ => NvData::Unknown, 1078 | } 1079 | } 1080 | 1081 | pub fn tuple(&self) -> (&ffi::CStr, NvData<'_>) { 1082 | (self.name(), self.data()) 1083 | } 1084 | } 1085 | 1086 | impl std::fmt::Debug for NvPair { 1087 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 1088 | f.debug_tuple("NvPair") 1089 | .field(&self.name()) 1090 | .field(&self.data()) 1091 | .finish() 1092 | } 1093 | } 1094 | 1095 | impl<'a> NvData<'a> { 1096 | pub fn as_str(&self) -> Option<&ffi::CStr> { 1097 | match self { 1098 | NvData::Str(c) => Some(c), 1099 | _ => None, 1100 | } 1101 | } 1102 | 1103 | pub fn as_string(&self) -> Option { 1104 | self.as_str()?.to_owned().into_string().ok() 1105 | } 1106 | 1107 | pub fn as_list(&self) -> Option<&NvListRef> { 1108 | match self { 1109 | NvData::NvListRef(c) => Some(c), 1110 | _ => None, 1111 | } 1112 | } 1113 | } 1114 | -------------------------------------------------------------------------------- /nvpair/tests/nvpair.rs: -------------------------------------------------------------------------------- 1 | extern crate nvpair; 2 | use std::ffi::{CStr, CString}; 3 | 4 | #[test] 5 | fn new() { 6 | let a = nvpair::NvList::new(); 7 | assert!(a.is_empty()); 8 | } 9 | 10 | #[test] 11 | fn size_empty_native() { 12 | let a = nvpair::NvList::new(); 13 | assert_eq!(a.encoded_size(nvpair::NvEncoding::Native).unwrap(), 16); 14 | } 15 | 16 | #[test] 17 | fn add_boolean() { 18 | let mut a = nvpair::NvList::new(); 19 | a.add_boolean(&b"hi\0"[..]).unwrap(); 20 | assert_eq!(a.encoded_size(nvpair::NvEncoding::Native).unwrap(), 40); 21 | let p = a.first().unwrap(); 22 | assert_eq!(p.name(), CStr::from_bytes_with_nul(&b"hi\0"[..]).unwrap()); 23 | 24 | assert!(a.exists(&b"hi\0"[..])); 25 | assert!(!a.exists("bye")); 26 | } 27 | 28 | #[test] 29 | fn iter() { 30 | let ns = ["one", "two", "three"]; 31 | let mut a = nvpair::NvList::new(); 32 | 33 | for n in ns.iter() { 34 | a.add_boolean(*n).unwrap(); 35 | } 36 | 37 | let mut ct = 0; 38 | for i in a.iter().zip(ns.iter()) { 39 | ct += 1; 40 | let a = i.0.name(); 41 | let b = CString::new(*i.1).unwrap(); 42 | assert_eq!(a, b.as_c_str()); 43 | } 44 | 45 | assert_eq!(ct, ns.len()); 46 | } 47 | 48 | #[test] 49 | fn lookup() { 50 | let ns = ["one", "two", "three"]; 51 | let mut a = nvpair::NvList::new_unique_names(); 52 | 53 | for n in ns.iter() { 54 | a.add_boolean(*n).unwrap(); 55 | } 56 | 57 | a.lookup("one").unwrap(); 58 | } 59 | 60 | #[test] 61 | fn insert() { 62 | let mut a = nvpair::NvList::new_unique_names(); 63 | a.insert("bool1", &true).unwrap(); 64 | a.insert("u32", &6u32).unwrap(); 65 | 66 | let b1 = a.lookup("bool1").expect("lookup of bool1 failed"); 67 | 68 | match b1.data() { 69 | nvpair::NvData::BoolV(v) => { 70 | assert!(v == true); 71 | } 72 | _ => { 73 | panic!("Unexpected type"); 74 | } 75 | } 76 | 77 | let u1 = a.lookup("u32").expect("lookup of u32 failed"); 78 | 79 | match u1.data() { 80 | nvpair::NvData::Uint32(v) => { 81 | assert!(v == 6u32); 82 | } 83 | _ => { 84 | panic!("Unexpected type"); 85 | } 86 | } 87 | 88 | //a.remove(&b1).expect("remove of b1 failed"); 89 | 90 | // FIXME: use option wrapper 91 | //assert!(a.lookup("bool1").is_err()); 92 | } 93 | 94 | #[test] 95 | fn insert_cstr() { 96 | let mut a = nvpair::NvList::new(); 97 | 98 | a.insert("hello", CStr::from_bytes_with_nul(b"bye\0").unwrap()) 99 | .unwrap(); 100 | } 101 | -------------------------------------------------------------------------------- /systest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "systest" 3 | version = "0.1.0" 4 | authors = ["Cody P Schafer "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | zfs-core-sys = { path = "../zfs-core-sys" } 10 | libc = "0.2" 11 | 12 | [build-dependencies] 13 | ctest = "0.2" 14 | -------------------------------------------------------------------------------- /systest/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut cfg = ctest::TestGenerator::new(); 3 | 4 | cfg.header("libzfs_core.h"); 5 | 6 | // TODO: populate via the same code in zfs-core-sys/build.rs 7 | //cfg.include(env!("LIBZFS_CORE_INCDIR")); 8 | // 9 | cfg.generate("../zfs-core-sys/src/lib.rs", "all.rs"); 10 | } 11 | -------------------------------------------------------------------------------- /systest/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(bad_style)] 2 | 3 | use libc::*; 4 | use zfs_core_sys::*; 5 | 6 | include!(concat!(env!("OUT_DIR"), "/all.rs")); 7 | -------------------------------------------------------------------------------- /zfs-core-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zfs-core-sys" 3 | version = "0.5.0" 4 | authors = ["Cody P Schafer "] 5 | include = ["**/*.rs", "Cargo.toml"] 6 | documentation = "https://docs.rs/zfs-core-sys" 7 | repository = "https://github.com/jmesmon/rust-libzfs" 8 | description = "Bindings to libzfs_core (lzc)" 9 | license = "Apache-2.0 OR MIT" 10 | edition = "2018" 11 | 12 | [build-dependencies] 13 | pkg-config = "0.3" 14 | build-env = "0.3" 15 | 16 | [dependencies] 17 | libc = "0.2" 18 | nvpair-sys = { path = "../nvpair-sys", version = "0.4" } 19 | -------------------------------------------------------------------------------- /zfs-core-sys/build-disable.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | 3 | use std::env; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | println!("cargo:rustc-link-lib=zfs_core"); 8 | 9 | let bindings = bindgen::Builder::default() 10 | .header("wrapper.h") 11 | .generate() 12 | .expect("Unable to generate bindings"); 13 | 14 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 15 | bindings 16 | .write_to_file(out_path.join("bindings.rs")) 17 | .expect("Couldn't write bindings!"); 18 | } 19 | -------------------------------------------------------------------------------- /zfs-core-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::OsStr, path::PathBuf, str::FromStr}; 2 | 3 | fn var(s: &str) -> Result { 4 | println!("cargo:rerun-if-env-changed={}", s); 5 | std::env::var(s) 6 | } 7 | 8 | #[derive(Debug)] 9 | enum Lookup { 10 | PkgConfig, 11 | Link, 12 | } 13 | 14 | #[derive(Debug)] 15 | struct LookupParseErr; 16 | 17 | impl FromStr for Lookup { 18 | type Err = LookupParseErr; 19 | 20 | fn from_str(s: &str) -> Result { 21 | match s { 22 | "pkg-config" => Ok(Lookup::PkgConfig), 23 | "link" => Ok(Lookup::Link), 24 | _ => Err(LookupParseErr), 25 | } 26 | } 27 | } 28 | 29 | fn env_var_append>(key: &str, value: V) { 30 | let value = value.as_ref(); 31 | let mut v = if let Some(v) = std::env::var_os(value) { 32 | v 33 | } else { 34 | std::env::set_var(key, value); 35 | return; 36 | }; 37 | 38 | if v.is_empty() { 39 | std::env::set_var(key, value); 40 | return; 41 | } 42 | 43 | v.push(":"); 44 | v.push(value); 45 | std::env::set_var(key, v); 46 | } 47 | 48 | fn main() { 49 | // openzfs on osx: fixed paths, under /usr/local/zfs (has pkg-config for libzfs_core) 50 | 51 | let target_os = var("CARGO_CFG_TARGET_OS").expect("Could not get env var CARGO_CFG_TARGET_OS"); 52 | let mut build_env = build_env::BuildEnv::from_env().expect("Could not determine build_env"); 53 | 54 | let lzc_libdir = build_env.var("LIBZFS_CORE_LIBDIR"); 55 | let lzc_lookup = if lzc_libdir.as_ref().is_some() { 56 | // Implies users want `LIBZFS_CORE_LOOKUP_WITH=link` 57 | Lookup::Link 58 | } else { 59 | let lookup_with = build_env.var("LIBZFS_CORE_LOOKUP_WITH"); 60 | let lookup_with: Option = lookup_with.map(|v| v.to_str().unwrap().parse().unwrap()); 61 | 62 | lookup_with.unwrap_or_else(|| match target_os.as_str() { 63 | // Users have reported that this is required for freebsd and illumos. Not tested by 64 | // Cody P Schafer. 65 | "freebsd" | "illumos" => Lookup::Link, 66 | 67 | // openzfs on osx has the `libzfs_core.pc` file, installed into 68 | // `/usr/local/zfs/lib/pkgconfig`. Users _must_ ensure this is part of their 69 | // `PKG_CONFIG_PATH`. Note that when cross compiling, this may cause some difficulty, 70 | // because the `pkg-config` crate doesn't allow distinguishing right now. We could 71 | // workaround this by hacking up std::env ourselves, or ideally the pkg-config crate would 72 | // use a build-env style lookup to pick the right `PKG_CONFIG_PATH` itself. 73 | // 74 | // Right now, if the link method is _not_ supplied, we tweak PKG_CONFIG_PATH so things 75 | // will automatically work in the common case (with openzfs on osx 2.01 at least) 76 | // 77 | // This will almost certainly behave poorly in the case of cross compilation, where 78 | // users should probably specify a `LIBZFS_CORE_LOOKUP_WITH` explicitly. 79 | "macos" => { 80 | let pc_path = PathBuf::from_str("/usr/local/zfs/lib/pkgconfig").unwrap(); 81 | if pc_path.exists() { 82 | env_var_append("PKG_CONFIG_PATH", pc_path); 83 | } 84 | Lookup::PkgConfig 85 | } 86 | // 87 | // zfs on linux: use pkg-config for libzfs_core (no pc for nvpair) 88 | // default to true otherwise. 89 | _ => Lookup::PkgConfig, 90 | }) 91 | }; 92 | 93 | match lzc_lookup { 94 | Lookup::PkgConfig => { 95 | pkg_config::probe_library("libzfs_core").unwrap(); 96 | } 97 | Lookup::Link => { 98 | if let Some(v) = lzc_libdir { 99 | println!("cargo:rustc-link-search=native={}", v.to_str().unwrap()); 100 | } 101 | println!("cargo:rustc-link-lib=zfs_core"); 102 | } 103 | } 104 | 105 | // FIXME: we don't provide a way to specify the search path for nvpair. One can add search 106 | // paths with RUSTFLAGS or some cargo.toml build target hacking. Consider if we should either 107 | // rely on that mechanism entirely (even for libzfs_core), or add a LIB_DIR env var for 108 | // nvpair/zutil/etc 109 | // 110 | // there is currently no nvpair pkg-config, so unconditionally link 111 | if target_os == "macos" { 112 | // TODO: this is an openzfs on osx specific path. Provide a way to disable 113 | println!("cargo:rustc-link-search=native=/usr/local/zfs/lib"); 114 | } 115 | println!("cargo:rustc-link-lib=nvpair"); 116 | if target_os == "freebsd" { 117 | println!("cargo:rustc-link-lib=dylib:-as-needed=zutil"); 118 | println!("cargo:rustc-link-lib=dylib:-as-needed=spl"); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /zfs-core-sys/generate-bindings: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euf -o pipefail 3 | d=$(dirname $0) 4 | e=false 5 | 6 | optstr="hS:pR:" 7 | usage() { 8 | echo "Usage: $0 [-S source-code-dir] [-p] [-R install-root-dir]" 9 | } 10 | 11 | ARGS=() 12 | # either use system definitions (found via pkg-config) or ones from the zfs source repo 13 | while getopts "$optstr" opt; do 14 | case "$opt" in 15 | h) 16 | usage; exit 0;; 17 | S) # use the source code in location 18 | ARGS=(-I "$OPTARG/include" -I "$OPTARG/lib/libspl/include" -I "$OPTARG/lib/libspl/include/os/linux") 19 | ;; 20 | p) # system pkg-config 21 | ARGS=($(pkg-config --cflags libzfs_core)) 22 | ;; 23 | R) # use this install location 24 | ARGS=(-I "$OPTARG/usr/include" -I "$OPTARG/usr/local/include" -I "$OPTARG/usr/local/include/libzfs" -I "$OPTARG/usr/local/include/libspl") 25 | ;; 26 | \?) e=true ;; 27 | *) >&2 echo "programming bug: $opt unhandled"; exit 1;; 28 | esac 29 | done 30 | 31 | if $e; then 32 | >&2 echo "exiting due to previous errors" 33 | exit 1 34 | fi 35 | 36 | set -x 37 | bindgen "$d/wrapper.h" \ 38 | -o "$d/src/bindings.rs" \ 39 | --whitelist-function 'libzfs_core_.*' \ 40 | --whitelist-function 'lzc_.*' \ 41 | --whitelist-type 'dmu_replay_record' \ 42 | --whitelist-type 'lzc_.*' \ 43 | --whitelist-type 'pool_initialize_func.*' \ 44 | --whitelist-type 'pool_trim_func.*' \ 45 | --whitelist-type 'size_t' \ 46 | --whitelist-type 'uchar_t' \ 47 | --whitelist-type 'uint_t' \ 48 | --whitelist-type 'vdev_.*' \ 49 | --whitelist-type 'zfs_.*' \ 50 | --whitelist-type 'zpool_.*' \ 51 | --whitelist-type 'drr_.*' \ 52 | --whitelist-type 'dmu_.*' \ 53 | --whitelist-type 'diff_type.*' \ 54 | --whitelist-type 'zfs_.*' \ 55 | --whitelist-type 'zinject.*' \ 56 | --whitelist-type 'zio_.*' \ 57 | --whitelist-type 'ddt_.*' \ 58 | --whitelist-var 'L2ARC_.*' \ 59 | --whitelist-var 'SPA_.*' \ 60 | --whitelist-var 'VDEV_.*' \ 61 | --whitelist-var 'ZFS_.*' \ 62 | --whitelist-var 'ZPL_.*' \ 63 | --whitelist-var 'ZPOOL_.*' \ 64 | --whitelist-var 'DMU_.*' \ 65 | --whitelist-var 'DRR_.*' \ 66 | --whitelist-var 'DEFAULT_PBKDF2_ITERATIONS' \ 67 | --whitelist-var 'MIN_PBKDF2_ITERATIONS' \ 68 | --default-enum-style moduleconsts \ 69 | --rustified-enum 'zfs_errno_t' \ 70 | --opaque-type dmu_replay_record \ 71 | --blacklist-type 'va_list' \ 72 | --blacklist-type '_IO_FILE' \ 73 | --blacklist-type 'FILE' \ 74 | --blacklist-type 'nvlist' \ 75 | --blacklist-type '__uint[0-9]+_t' \ 76 | --blacklist-type '__int[0-9]+_t' \ 77 | --size_t-is-usize \ 78 | -- \ 79 | "${ARGS[@]}" 80 | -------------------------------------------------------------------------------- /zfs-core-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(clippy::redundant_static_lifetimes)] 5 | #![allow(deref_nullptr)] 6 | 7 | extern crate nvpair_sys as nvpair; 8 | use nvpair::*; 9 | 10 | include!("bindings.rs"); 11 | -------------------------------------------------------------------------------- /zfs-core-sys/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /zfs-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zfs-core" 3 | version = "0.5.0" 4 | authors = ["Cody P Schafer "] 5 | include = ["**/*.rs", "Cargo.toml"] 6 | documentation = "https://docs.rs/zfs-core" 7 | repository = "https://github.com/jmesmon/rust-libzfs" 8 | description = "Rust interface to libzfs_core (lzc)" 9 | license = "Apache-2.0 OR MIT" 10 | edition = "2018" 11 | 12 | [features] 13 | v2_00 = [] 14 | 15 | [dependencies] 16 | nvpair = { path = "../nvpair", version = "0.5.0" } 17 | zfs-core-sys = { path = "../zfs-core-sys", version = "0.5.0" } 18 | cstr-argument = "0.1" 19 | foreign-types = "0.5.0" 20 | rand = "0.8" 21 | snafu = "0.6" 22 | 23 | [dev-dependencies] 24 | os_pipe = "0.9" 25 | tempfile = "3" 26 | libc = "0.2" 27 | -------------------------------------------------------------------------------- /zfs-core/examples/send-small-snap.rs: -------------------------------------------------------------------------------- 1 | //! generate a small snapshot for testing parsing dmu_replay_records 2 | use std::os::unix::io::AsRawFd; 3 | 4 | fn main() { 5 | let mut args = std::env::args().into_iter(); 6 | args.next().expect("no prgm name"); 7 | let snap_to_make = args.next().expect("missing arg"); 8 | 9 | let lzc = zfs_core::Zfs::new().expect("could not init zfs"); 10 | 11 | let at_pos = snap_to_make 12 | .find('@') 13 | .expect("could not find '@' in snap_to_make"); 14 | 15 | let fs_name = &snap_to_make[..at_pos]; 16 | 17 | let prop_nv = nvpair::NvList::new(); 18 | lzc.create(fs_name, zfs_core::DataSetType::Zfs, &prop_nv) 19 | .expect("could not create snap_to_make"); 20 | 21 | lzc.snapshot([&snap_to_make[..]].iter().cloned()) 22 | .expect("snapshot failed"); 23 | 24 | let stdout = std::io::stdout(); 25 | let sl = stdout.lock(); 26 | 27 | eprintln!("sending on stdout"); 28 | 29 | lzc.send::<_, &str>(snap_to_make, None, sl.as_raw_fd(), Default::default()) 30 | .expect("send failed"); 31 | 32 | eprintln!("done"); 33 | } 34 | -------------------------------------------------------------------------------- /zfs-core/examples/sync-hang.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let z = zfs_core::Zfs::new().unwrap(); 3 | 4 | z.sync("testpool", true).unwrap(); 5 | println!("COMPLETE"); 6 | } 7 | -------------------------------------------------------------------------------- /zfs-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_debug_implementations, rust_2018_idioms)] 2 | 3 | use cstr_argument::CStrArgument; 4 | use foreign_types::ForeignType; 5 | use nvpair::{NvList, NvListIter, NvListRef}; 6 | use snafu::Snafu; 7 | use std::convert::TryInto; 8 | use std::marker::PhantomData; 9 | use std::os::unix::io::RawFd; 10 | use std::{ffi, fmt, io, ptr}; 11 | use zfs_core_sys as sys; 12 | 13 | // TODO: consider splitting this into specific error kinds per operation 14 | #[derive(Debug, Snafu)] 15 | pub enum Error { 16 | #[snafu(display("libzfs_core call failed with {}", source))] 17 | Io { source: io::Error }, 18 | #[snafu(display("libzfs_core call failed for these entries {}", source))] 19 | List { source: ErrorList }, 20 | } 21 | 22 | //pub type Result = std::result::Result; 23 | 24 | /// A handle to work with Zfs pools, datasets, etc 25 | // Note: the Drop for this makes clone-by-copy unsafe. Could clone by just calling new(). 26 | // 27 | // Internally, libzfs_core maintains a refcount for the `libzfs_core_init()` and 28 | // `libzfs_core_fini()` calls, so we need the init to match fini. Alternatively, we could use a 29 | // single init and never fini. 30 | pub struct Zfs { 31 | i: PhantomData<()>, 32 | } 33 | 34 | impl fmt::Debug for Zfs { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | f.debug_struct("Zfs").finish() 37 | } 38 | } 39 | 40 | #[derive(Debug)] 41 | pub enum DataSetType { 42 | Zfs, 43 | Zvol, 44 | } 45 | 46 | impl DataSetType { 47 | fn as_raw(&self) -> ::std::os::raw::c_uint { 48 | match self { 49 | DataSetType::Zfs => sys::lzc_dataset_type::LZC_DATSET_TYPE_ZFS, 50 | DataSetType::Zvol => sys::lzc_dataset_type::LZC_DATSET_TYPE_ZVOL, 51 | } 52 | } 53 | } 54 | 55 | /// Generic list of errors return from various `lzc_*` calls. 56 | /// 57 | /// The first item (the `name`) is the thing we were operating on (creating, destroying, etc) that cause the error, 58 | /// and the second (the `error`) is the translated error code 59 | /// 60 | /// Note that there is a special `name` "N_MORE_ERRORS" which is a count of errors not listed 61 | #[derive(Debug)] 62 | pub struct ErrorList { 63 | nv: NvList, 64 | } 65 | 66 | impl ErrorList { 67 | pub fn iter(&self) -> ErrorListIter<'_> { 68 | self.into_iter() 69 | } 70 | } 71 | 72 | impl std::error::Error for ErrorList {} 73 | 74 | impl From for ErrorList { 75 | fn from(nv: NvList) -> Self { 76 | // TODO: consider examining shape of the error list here 77 | Self { nv } 78 | } 79 | } 80 | 81 | impl AsRef for ErrorList { 82 | fn as_ref(&self) -> &NvList { 83 | &self.nv 84 | } 85 | } 86 | 87 | impl AsMut for ErrorList { 88 | fn as_mut(&mut self) -> &mut NvList { 89 | &mut self.nv 90 | } 91 | } 92 | 93 | impl<'a> IntoIterator for &'a ErrorList { 94 | type Item = (&'a ffi::CStr, io::Error); 95 | type IntoIter = ErrorListIter<'a>; 96 | 97 | fn into_iter(self) -> Self::IntoIter { 98 | ErrorListIter { 99 | nvi: self.nv.iter(), 100 | } 101 | } 102 | } 103 | 104 | #[derive(Debug, Clone)] 105 | pub struct ErrorListIter<'a> { 106 | nvi: NvListIter<'a>, 107 | } 108 | 109 | impl<'a> fmt::Display for ErrorListIter<'a> { 110 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 111 | fmt.debug_list().entries(self.clone()).finish() 112 | } 113 | } 114 | 115 | impl fmt::Display for ErrorList { 116 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 117 | fmt::Debug::fmt(&self.into_iter(), fmt) 118 | } 119 | } 120 | 121 | impl<'a> Iterator for ErrorListIter<'a> { 122 | type Item = (&'a ffi::CStr, io::Error); 123 | 124 | fn next(&mut self) -> Option { 125 | match self.nvi.next() { 126 | Some(np) => { 127 | let name = np.name(); 128 | let data = np.data(); 129 | 130 | match data { 131 | nvpair::NvData::Int32(v) => Some((name, io::Error::from_raw_os_error(v))), 132 | _ => { 133 | // TODO: consider validating early. alternately: consider emitting 134 | // something reasonable here. we're already an error path, so being 100% 135 | // precise is probably not required. 136 | panic!("unhandled error type for name {:?}: {:?}", name, data); 137 | } 138 | } 139 | } 140 | None => None, 141 | } 142 | } 143 | } 144 | 145 | impl Zfs { 146 | /// Create a handle to the Zfs subsystem 147 | #[doc(alias = "libzfs_core_init")] 148 | pub fn new() -> io::Result { 149 | let v = unsafe { sys::libzfs_core_init() }; 150 | 151 | if v != 0 { 152 | Err(io::Error::from_raw_os_error(v)) 153 | } else { 154 | Ok(Self { i: PhantomData }) 155 | } 156 | } 157 | 158 | /// Create a new dataset of the given type with `props` set as properties 159 | /// 160 | /// Corresponds to `lzc_create()` 161 | #[doc(alias = "lzc_create")] 162 | pub fn create( 163 | &self, 164 | name: S, 165 | dataset_type: DataSetType, 166 | props: &NvList, 167 | ) -> io::Result<()> { 168 | let name = name.into_cstr(); 169 | let v = unsafe { 170 | sys::lzc_create( 171 | name.as_ref().as_ptr(), 172 | dataset_type.as_raw(), 173 | props.as_ptr() as *mut _, 174 | ptr::null_mut(), 175 | 0, 176 | ) 177 | }; 178 | 179 | if v != 0 { 180 | Err(io::Error::from_raw_os_error(v)) 181 | } else { 182 | Ok(()) 183 | } 184 | } 185 | 186 | /// Corresponds to `lzc_clone()` 187 | #[doc(alias = "lzc_clone")] 188 | pub fn clone_dataset( 189 | &self, 190 | name: S, 191 | origin: S2, 192 | props: &mut NvListRef, 193 | ) -> io::Result<()> { 194 | let name = name.into_cstr(); 195 | let origin = origin.into_cstr(); 196 | let v = unsafe { 197 | sys::lzc_clone( 198 | name.as_ref().as_ptr(), 199 | origin.as_ref().as_ptr(), 200 | props.as_mut_ptr(), 201 | ) 202 | }; 203 | if v != 0 { 204 | Err(io::Error::from_raw_os_error(v)) 205 | } else { 206 | Ok(()) 207 | } 208 | } 209 | 210 | // TODO: avoid using an out-param for `snap_name_buf` 211 | // `snap_name_buf` is filled by a cstring 212 | /// Corresponds to `lzc_promote()` 213 | #[doc(alias = "lzc_promote")] 214 | pub fn promote(&self, fsname: S, snap_name_buf: &mut [u8]) -> io::Result<()> { 215 | let fsname = fsname.into_cstr(); 216 | let v = unsafe { 217 | sys::lzc_promote( 218 | fsname.as_ref().as_ptr(), 219 | snap_name_buf.as_mut_ptr() as *mut _, 220 | snap_name_buf.len().try_into().unwrap(), 221 | ) 222 | }; 223 | if v != 0 { 224 | Err(io::Error::from_raw_os_error(v)) 225 | } else { 226 | Ok(()) 227 | } 228 | } 229 | 230 | /// Corresponds to `lzc_rename()` 231 | #[doc(alias = "lzc_rename")] 232 | pub fn rename(&self, source: S, target: T) -> io::Result<()> { 233 | let source = source.into_cstr(); 234 | let target = target.into_cstr(); 235 | 236 | let v = unsafe { sys::lzc_rename(source.as_ref().as_ptr(), target.as_ref().as_ptr()) }; 237 | if v != 0 { 238 | Err(io::Error::from_raw_os_error(v)) 239 | } else { 240 | Ok(()) 241 | } 242 | } 243 | 244 | /// Destroy the given dataset (which may be a filesystem, snapshot, bookmark, volume, etc) 245 | /// 246 | /// Corresponds to `lzc_destroy()` 247 | #[doc(alias = "lzc_destroy")] 248 | pub fn destroy(&self, name: S) -> io::Result<()> { 249 | let name = name.into_cstr(); 250 | let v = unsafe { sys::lzc_destroy(name.as_ref().as_ptr()) }; 251 | 252 | if v != 0 { 253 | Err(io::Error::from_raw_os_error(v)) 254 | } else { 255 | Ok(()) 256 | } 257 | } 258 | 259 | /// Create snapshot(s) 260 | /// 261 | /// The snapshots must be from the same pool, and must not reference the same dataset (iow: 262 | /// cannot create 2 snapshots of the same filesystem). 263 | /// 264 | /// Corresponds to `lzc_snapshot()`. 265 | #[doc(alias = "lzc_snapshot")] 266 | #[doc(alias = "snapshot_raw")] 267 | pub fn snapshot, S: CStrArgument>( 268 | &self, 269 | snaps: I, 270 | ) -> Result<(), Error> { 271 | let mut arg = NvList::new(); 272 | 273 | for i in snaps { 274 | arg.insert(i.into_cstr().as_ref(), &()).unwrap(); 275 | } 276 | 277 | let props = NvList::new(); 278 | match self.snapshot_raw(&arg, &props) { 279 | Ok(()) => Ok(()), 280 | Err(Ok(v)) => Err(Error::Io { source: v }), 281 | Err(Err(v)) => Err(Error::List { source: v.into() }), 282 | } 283 | } 284 | 285 | /// Create snapshot(s). `snaps` is a list of `bool` (not `boolean_value`) entries, the names of 286 | /// which correspond to snapshot names. 287 | /// 288 | /// The snapshots must be from the same pool, and must not reference the same dataset (iow: 289 | /// cannot create 2 snapshots of the same filesystem with a single call). 290 | /// 291 | /// Corresponds to `lzc_snapshot()`. 292 | // TODO: this is a fairly raw interface, consider abstracting (or at least adding some 293 | // restrictions on the NvLists). 294 | #[doc(alias = "lzc_snapshot")] 295 | pub fn snapshot_raw( 296 | &self, 297 | snaps: &NvList, 298 | props: &NvList, 299 | ) -> Result<(), Result> { 300 | let mut nv = ptr::null_mut(); 301 | let v = unsafe { 302 | sys::lzc_snapshot(snaps.as_ptr() as *mut _, props.as_ptr() as *mut _, &mut nv) 303 | }; 304 | 305 | if v != 0 { 306 | if nv.is_null() { 307 | Err(Ok(io::Error::from_raw_os_error(v))) 308 | } else { 309 | Err(Err(unsafe { NvList::from_ptr(nv) })) 310 | } 311 | } else { 312 | Ok(()) 313 | } 314 | } 315 | 316 | #[doc(alias = "destroy_snaps_raw")] 317 | #[doc(alias = "lzc_destroy_snaps")] 318 | pub fn destroy_snaps, S: CStrArgument>( 319 | &self, 320 | snaps: I, 321 | defer: Defer, 322 | ) -> Result<(), (io::Error, NvList)> { 323 | let mut snaps_nv = NvList::new(); 324 | 325 | for snap in snaps { 326 | snaps_nv.insert(snap, &()).unwrap(); 327 | } 328 | 329 | self.destroy_snaps_raw(&snaps_nv, defer) 330 | } 331 | 332 | /// Corresponds to `lzc_destroy_snaps()` 333 | #[doc(alias = "lzc_destroy_snaps")] 334 | pub fn destroy_snaps_raw( 335 | &self, 336 | snaps: &NvList, 337 | defer: Defer, 338 | ) -> Result<(), (io::Error, NvList)> { 339 | let mut nv = ptr::null_mut(); 340 | let v = unsafe { 341 | sys::lzc_destroy_snaps( 342 | snaps.as_ptr() as *mut _, 343 | bool::from(defer) as sys::boolean_t::Type, 344 | &mut nv, 345 | ) 346 | }; 347 | 348 | if v != 0 { 349 | Err((io::Error::from_raw_os_error(v), unsafe { 350 | NvList::from_ptr(nv) 351 | })) 352 | } else { 353 | Ok(()) 354 | } 355 | } 356 | 357 | /// Corresponds to `lzc_snaprange_space()` 358 | #[doc(alias = "lzc_snaprange_space")] 359 | pub fn snaprange_space( 360 | &self, 361 | first_snap: F, 362 | last_snap: L, 363 | ) -> io::Result { 364 | let first_snap = first_snap.into_cstr(); 365 | let last_snap = last_snap.into_cstr(); 366 | 367 | let mut out = 0; 368 | let v = unsafe { 369 | sys::lzc_snaprange_space( 370 | first_snap.as_ref().as_ptr(), 371 | last_snap.as_ref().as_ptr(), 372 | &mut out, 373 | ) 374 | }; 375 | 376 | if v != 0 { 377 | Err(io::Error::from_raw_os_error(v)) 378 | } else { 379 | Ok(out) 380 | } 381 | } 382 | 383 | /// Check if a dataset (a filesystem, or a volume, or a snapshot) 384 | /// with the given name exists. 385 | /// 386 | /// Note: cannot check for bookmarks 387 | /// 388 | /// Corresponds to `lzc_exists()`. 389 | #[doc(alias = "lzc_exists")] 390 | pub fn exists(&self, name: S) -> bool { 391 | let name = name.into_cstr(); 392 | let v = unsafe { sys::lzc_exists(name.as_ref().as_ptr()) }; 393 | v != 0 394 | } 395 | 396 | // 0.8.? 397 | /// Corresponds to `lzc_sync()`. 398 | #[doc(alias = "lzc_sync")] 399 | pub fn sync(&self, pool_name: S, force: bool) -> io::Result<()> { 400 | let pool_name = pool_name.into_cstr(); 401 | let mut args = NvList::new_unique_names(); 402 | 403 | // note: always include for compat with <=2.0.0 404 | args.insert("force", &force).unwrap(); 405 | 406 | let v = unsafe { 407 | sys::lzc_sync( 408 | pool_name.as_ref().as_ptr(), 409 | args.as_ptr() as *mut _, 410 | ptr::null_mut(), 411 | ) 412 | }; 413 | if v != 0 { 414 | Err(io::Error::from_raw_os_error(v)) 415 | } else { 416 | Ok(()) 417 | } 418 | } 419 | 420 | /// Create "user holds" on snapshots. If there is a hold on a snapshot, 421 | /// the snapshot can not be destroyed. (However, it can be marked for deletion 422 | /// by lzc_destroy_snaps(defer=B_TRUE).) 423 | /// 424 | /// The keys in the nvlist are snapshot names. 425 | /// The snapshots must all be in the same pool. 426 | /// The value is the name of the hold (string type). 427 | #[doc(alias = "lzc_hold")] 428 | pub fn hold_raw( 429 | &self, 430 | holds: &NvListRef, 431 | cleanup_fd: Option, 432 | ) -> Result<(), Result> { 433 | let mut errs = ptr::null_mut(); 434 | let v = unsafe { 435 | sys::lzc_hold( 436 | holds.as_ptr() as *mut _, 437 | cleanup_fd.unwrap_or(-1), 438 | &mut errs, 439 | ) 440 | }; 441 | if v != 0 { 442 | // if we have an error list, the return value error is just one of the errors in the 443 | // list. 444 | if errs.is_null() { 445 | Err(Ok(io::Error::from_raw_os_error(v))) 446 | } else { 447 | Err(Err(unsafe { NvList::from_ptr(errs) })) 448 | } 449 | } else { 450 | Ok(()) 451 | } 452 | } 453 | 454 | /// Create a set of holds, each on a given snapshot 455 | /// 456 | /// Related: [`get_holds`], [`release`], [`hold_raw`], [`release_raw`]. 457 | /// 458 | /// Corresponds to `lzc_hold`. 459 | #[doc(alias = "lzc_hold")] 460 | pub fn hold<'a, H, S, N>(&self, holds: H, cleanup_fd: Option) -> Result<(), Error> 461 | where 462 | H: IntoIterator, 463 | S: 'a + CStrArgument + Clone, 464 | N: 'a + CStrArgument + Clone, 465 | { 466 | let mut holds_nv = NvList::new(); 467 | 468 | for he in holds { 469 | let ds = he.0.clone().into_cstr(); 470 | let name = he.1.clone().into_cstr(); 471 | holds_nv.insert(ds.as_ref(), name.as_ref()).unwrap(); 472 | } 473 | 474 | match self.hold_raw(&holds_nv, cleanup_fd) { 475 | Ok(()) => Ok(()), 476 | Err(Ok(v)) => Err(Error::Io { source: v }), 477 | Err(Err(v)) => Err(Error::List { source: v.into() }), 478 | } 479 | } 480 | 481 | /// Release holds from various snapshots 482 | /// 483 | /// The holds nvlist is `[(snap_name, [hold_names])]`, allowing multiple holds for multiple 484 | /// snapshots to be released with one call. 485 | /// 486 | /// Related: [`release`] 487 | /// 488 | /// Corresponds to `lzc_release`. 489 | #[doc(alias = "lzc_release")] 490 | pub fn release_raw(&self, holds: &NvListRef) -> Result<(), Result> { 491 | let mut errs = ptr::null_mut(); 492 | let v = unsafe { sys::lzc_release(holds.as_ptr() as *mut _, &mut errs) }; 493 | if v != 0 { 494 | if errs.is_null() { 495 | Err(Ok(io::Error::from_raw_os_error(v))) 496 | } else { 497 | Err(Err(unsafe { NvList::from_ptr(errs) })) 498 | } 499 | } else { 500 | Ok(()) 501 | } 502 | } 503 | 504 | /// For a list of datasets, release one or more holds by name 505 | /// 506 | /// Corresponds to `lzc_release`. 507 | #[doc(alias = "lzc_release")] 508 | pub fn release<'a, F, C, H, N>(&self, holds: F) -> Result<(), Error> 509 | where 510 | F: IntoIterator, 511 | C: 'a + CStrArgument + Clone, 512 | H: 'a + IntoIterator + Clone, 513 | N: 'a + CStrArgument + Clone, 514 | { 515 | let mut r_nv = NvList::new(); 516 | 517 | for hi in holds { 518 | let mut hold_nv = NvList::new(); 519 | 520 | for hold_name in hi.1.clone() { 521 | hold_nv.insert(hold_name, &()).unwrap(); 522 | } 523 | 524 | r_nv.insert(hi.0.clone(), hold_nv.as_ref()).unwrap(); 525 | } 526 | 527 | match self.release_raw(&r_nv) { 528 | Ok(()) => Ok(()), 529 | Err(Ok(v)) => Err(Error::Io { source: v }), 530 | Err(Err(v)) => Err(Error::List { source: v.into() }), 531 | } 532 | } 533 | 534 | /// Get the holds for a given snapshot 535 | /// 536 | /// The returned nvlist is `[(hold_name: String, unix_timestamp_seconds: u64)]`, where the unix 537 | /// timestamp is when the hold was created. 538 | /// 539 | /// Corresponds to `lzc_get_holds()` 540 | #[doc(alias = "lzc_get_holds")] 541 | pub fn get_holds(&self, snapname: S) -> io::Result { 542 | let snapname = snapname.into_cstr(); 543 | let mut holds = ptr::null_mut(); 544 | let v = unsafe { sys::lzc_get_holds(snapname.as_ref().as_ptr(), &mut holds) }; 545 | if v != 0 { 546 | Err(io::Error::from_raw_os_error(v)) 547 | } else { 548 | Ok(HoldList::new(unsafe { NvList::from_ptr(holds) })) 549 | } 550 | } 551 | 552 | /// Send the described stream 553 | /// 554 | /// Internally, is a wrapper around [`send_resume_redacted()`] 555 | /// 556 | /// Corresponds to `lzc_send()` 557 | #[doc(alias = "lzc_send")] 558 | pub fn send( 559 | &self, 560 | snapname: S, 561 | from: Option, 562 | fd: RawFd, 563 | flags: SendFlags, 564 | ) -> io::Result<()> { 565 | let snapname = snapname.into_cstr(); 566 | let from = from.map(|a| a.into_cstr()); 567 | 568 | let v = unsafe { 569 | sys::lzc_send( 570 | snapname.as_ref().as_ptr(), 571 | from.map_or(ptr::null(), |x| x.as_ref().as_ptr()), 572 | fd, 573 | flags.into(), 574 | ) 575 | }; 576 | if v != 0 { 577 | Err(io::Error::from_raw_os_error(v)) 578 | } else { 579 | Ok(()) 580 | } 581 | } 582 | 583 | /// Send the described redacted stream 584 | /// 585 | /// Internally, is a wrapper around [`send_resume_redacted()`] 586 | /// 587 | /// Corresponds to `lzc_send_redacted()` 588 | #[doc(alias = "lzc_send_redacted")] 589 | #[cfg(features = "v2_00")] 590 | pub fn send_redacted( 591 | &self, 592 | snapname: S, 593 | from: F, 594 | fd: RawFd, 595 | redactbook: R, 596 | flags: SendFlags, 597 | ) -> io::Result<()> { 598 | let snapname = snapname.into_cstr(); 599 | let from = from.into_cstr(); 600 | let redactbook = redactbook.into_cstr(); 601 | 602 | let v = unsafe { 603 | sys::lzc_send_redacted( 604 | snapname.as_ref().as_ptr(), 605 | from.as_ref().as_ptr(), 606 | fd, 607 | redactbook.as_ref().as_ptr(), 608 | flags.into(), 609 | ) 610 | }; 611 | if v != 0 { 612 | Err(io::Error::from_raw_os_error(v)) 613 | } else { 614 | Ok(()) 615 | } 616 | } 617 | 618 | /// Send the described stream with resume information 619 | /// 620 | /// Internally, this is a wrapper around [`send_resume_redacted()`]. 621 | /// 622 | /// Corresponds to `lzc_send_resume()` 623 | #[doc(alias = "lzc_send_resume")] 624 | pub fn send_resume( 625 | &self, 626 | snapname: S, 627 | from: F, 628 | fd: RawFd, 629 | flags: SendFlags, 630 | resume_obj: u64, 631 | resume_off: u64, 632 | ) -> io::Result<()> { 633 | let snapname = snapname.into_cstr(); 634 | let from = from.into_cstr(); 635 | 636 | let v = unsafe { 637 | sys::lzc_send_resume( 638 | snapname.as_ref().as_ptr(), 639 | from.as_ref().as_ptr(), 640 | fd, 641 | flags.into(), 642 | resume_obj, 643 | resume_off, 644 | ) 645 | }; 646 | if v != 0 { 647 | Err(io::Error::from_raw_os_error(v)) 648 | } else { 649 | Ok(()) 650 | } 651 | } 652 | 653 | /// Send the described stream with resume and redact info 654 | /// 655 | /// Corresponds to `lzc_send_resume_redacted()` 656 | #[doc(alias = "lzc_send_resume_redacted")] 657 | #[cfg(features = "v2_00")] 658 | pub fn send_resume_redacted( 659 | &self, 660 | snapname: S, 661 | from: F, 662 | fd: RawFd, 663 | flags: SendFlags, 664 | resume_obj: u64, 665 | resume_off: u64, 666 | redactbook: R, 667 | ) -> io::Result<()> { 668 | let snapname = snapname.into_cstr(); 669 | let from = from.into_cstr(); 670 | let redactbook = redactbook.into_cstr(); 671 | 672 | let r = unsafe { 673 | sys::lzc_send_resume_redacted( 674 | snapname.as_ref().as_ptr(), 675 | from.as_ref().as_ptr(), 676 | fd, 677 | flags.into(), 678 | resume_obj, 679 | resume_off, 680 | redactbook.as_ref().as_ptr(), 681 | ) 682 | }; 683 | 684 | if r != 0 { 685 | Err(io::Error::from_raw_os_error(r)) 686 | } else { 687 | Ok(()) 688 | } 689 | } 690 | 691 | /// Estimate the size of a send stream 692 | /// 693 | /// Corresponds to `lzc_send_space_resume_redacted()` 694 | // FIXME: many parameters should probably be `Option` 695 | // TODO: consider passing arguments here as a struct so we can use names 696 | #[doc(alias = "lzc_send_space_resume_redacted")] 697 | #[cfg(features = "v2_00")] 698 | pub fn send_space_resume_redacted( 699 | &self, 700 | snapname: S, 701 | from: F, 702 | flags: SendFlags, 703 | resume_obj: u64, 704 | resume_off: u64, 705 | resume_bytes: u64, 706 | redactbook: R, 707 | fd: RawFd, 708 | ) -> io::Result { 709 | let snapname = snapname.into_cstr(); 710 | let from = from.into_cstr(); 711 | 712 | let mut space = 0; 713 | 714 | let r = unsafe { 715 | sys::lzc_send_space_resume_redacted( 716 | snapname.as_ref().as_ptr(), 717 | from.as_ref().as_ptr(), 718 | flags.into(), 719 | resume_obj, 720 | resume_off, 721 | resume_bytes, 722 | redactbook, 723 | fd, 724 | &mut space, 725 | ) 726 | }; 727 | 728 | if r != 0 { 729 | Err(io::Error::from_raw_os_error(r)) 730 | } else { 731 | Ok(space) 732 | } 733 | } 734 | 735 | /// Estimate the size of the stream to be sent if [`send`] were called with the same arguments 736 | /// 737 | /// Internally, this is a wrapper around [`send_space_resume_redacted()`]. 738 | /// 739 | /// Corresponds to `lzc_send_space()` 740 | #[doc(alias = "lzc_send_space")] 741 | pub fn send_space( 742 | &self, 743 | snapname: S, 744 | from: F, 745 | flags: SendFlags, 746 | ) -> io::Result { 747 | let snapname = snapname.into_cstr(); 748 | let from = from.into_cstr(); 749 | 750 | let mut space = 0; 751 | let r = unsafe { 752 | sys::lzc_send_space( 753 | snapname.as_ref().as_ptr(), 754 | from.as_ref().as_ptr(), 755 | flags.into(), 756 | &mut space, 757 | ) 758 | }; 759 | 760 | if r != 0 { 761 | Err(io::Error::from_raw_os_error(r)) 762 | } else { 763 | Ok(space) 764 | } 765 | } 766 | 767 | /// Corresponds to `lzc_receive()` 768 | #[doc(alias = "lzc_receive")] 769 | pub fn receive( 770 | &self, 771 | snapname: S, 772 | props: Option<&NvListRef>, 773 | origin: Option, 774 | force: bool, 775 | raw: bool, 776 | fd: RawFd, 777 | ) -> io::Result<()> { 778 | let snapname = snapname.into_cstr(); 779 | let origin = origin.map(|x| x.into_cstr()); 780 | 781 | let r = unsafe { 782 | sys::lzc_receive( 783 | snapname.as_ref().as_ptr(), 784 | props.map_or(ptr::null_mut(), |x| x.as_ptr() as *mut _), 785 | origin.map_or(ptr::null(), |x| x.as_ref().as_ptr()), 786 | if force { 1 } else { 0 }, 787 | if raw { 1 } else { 0 }, 788 | fd, 789 | ) 790 | }; 791 | 792 | if r != 0 { 793 | Err(io::Error::from_raw_os_error(r)) 794 | } else { 795 | Ok(()) 796 | } 797 | } 798 | 799 | /// Corresponds to `lzc_receive_resumable()` 800 | // internally, only a flag differs from `recv` 801 | // consider implimenting something that takes `resumeable` as a flag 802 | #[doc(alias = "lzc_receive_resumable")] 803 | pub fn receive_resumable( 804 | &self, 805 | snapname: S, 806 | props: &NvListRef, 807 | origin: O, 808 | force: bool, 809 | raw: bool, 810 | fd: RawFd, 811 | ) -> io::Result<()> { 812 | let snapname = snapname.into_cstr(); 813 | let origin = origin.into_cstr(); 814 | 815 | let r = unsafe { 816 | sys::lzc_receive_resumable( 817 | snapname.as_ref().as_ptr(), 818 | props.as_ptr() as *mut _, 819 | origin.as_ref().as_ptr(), 820 | if force { 1 } else { 0 }, 821 | if raw { 1 } else { 0 }, 822 | fd, 823 | ) 824 | }; 825 | 826 | if r != 0 { 827 | Err(io::Error::from_raw_os_error(r)) 828 | } else { 829 | Ok(()) 830 | } 831 | } 832 | 833 | /* 834 | pub fn receive_with_header(&self, snapname: S, props: &NvListRef, origin: O, force: bool, resumeable: bool, raw: bool, fd: RawFd, begin_record: &DmuReplayRecordRef) -> io::Result<()> { 835 | unimplemented!() 836 | } 837 | */ 838 | 839 | /* 840 | pub fn receive_one(&self, snapname: S, cmdprops: &NvListRef, wkey: Option<&[u8]>, origin: O, force: bool, resumeable: bool, raw: bool, input_fd: RawFd, begin_record: &DmuReplayRecordRef) -> io::Result<(/* bytes */ u64, /* errflags */u64, /* errors */ NvList)> { 841 | unimplemented!() 842 | } 843 | */ 844 | 845 | /* 846 | pub fn receive_with_cmdprops(&self, snapname: S, cmdprops: &NvListRef, wkey: Option<&[u8]>, origin: O, force: bool, resumeable: bool, raw: bool, input_fd: RawFd, begin_record: &DmuReplayRecordRef) -> io::Result<(/* bytes */ u64, /* errflags */u64, /* errors */ NvList)> { 847 | unimplemented!() 848 | } 849 | */ 850 | 851 | /// Corresponds to `lzc_rollback()` 852 | #[doc(alias = "lzc_rollback")] 853 | pub fn rollback(&self, fsname: S) -> io::Result { 854 | let fsname = fsname.into_cstr(); 855 | let mut rname = vec![0u8; sys::ZFS_MAX_DATASET_NAME_LEN as usize + 1]; 856 | 857 | let r = unsafe { 858 | sys::lzc_rollback( 859 | fsname.as_ref().as_ptr(), 860 | rname.as_mut_ptr() as *mut std::os::raw::c_char, 861 | rname.len() as std::os::raw::c_int, 862 | ) 863 | }; 864 | 865 | if r != 0 { 866 | Err(io::Error::from_raw_os_error(r)) 867 | } else { 868 | let p = rname.iter().position(|x| *x == b'\0').unwrap(); 869 | rname.resize(p, 0); 870 | Ok(std::ffi::CString::new(rname).unwrap()) 871 | } 872 | } 873 | 874 | /// Corresponds to `lzc_rollback_to()` 875 | #[doc(alias = "lzc_rollback_to")] 876 | pub fn rollback_to( 877 | &self, 878 | fsname: F, 879 | snapname: S, 880 | ) -> io::Result<()> { 881 | let fsname = fsname.into_cstr(); 882 | let snapname = snapname.into_cstr(); 883 | 884 | let r = 885 | unsafe { sys::lzc_rollback_to(fsname.as_ref().as_ptr(), snapname.as_ref().as_ptr()) }; 886 | 887 | if r != 0 { 888 | Err(io::Error::from_raw_os_error(r)) 889 | } else { 890 | Ok(()) 891 | } 892 | } 893 | 894 | /// Create bookmarks from existing snapshot or bookmark 895 | #[doc(alias = "lzc_bookmark")] 896 | pub fn bookmark, D: CStrArgument, S: CStrArgument>( 897 | &self, 898 | bookmarks: I, 899 | ) -> Result<(), ErrorList> { 900 | let mut bookmarks_nv = NvList::new(); 901 | 902 | for (new_bm, src) in bookmarks { 903 | let src = src.into_cstr(); 904 | bookmarks_nv.insert(new_bm, src.as_ref()).unwrap(); 905 | } 906 | 907 | match self.bookmark_raw(&bookmarks_nv) { 908 | Ok(()) => Ok(()), 909 | Err((_, err_nv)) => Err(ErrorList::from(err_nv)), 910 | } 911 | } 912 | 913 | /// Create bookmarks from existing snapshot or bookmark 914 | /// 915 | /// The `bookmarks` nvlist is `[(full_name_of_new_bookmark, 916 | /// full_name_of_source_snap_or_bookmark)]`. 917 | /// 918 | /// Corresponds to `lzc_bookmark()` 919 | #[doc(alias = "lzc_bookmark")] 920 | pub fn bookmark_raw(&self, bookmarks: &NvListRef) -> Result<(), (io::Error, NvList)> { 921 | let mut err = ptr::null_mut(); 922 | let r = unsafe { sys::lzc_bookmark(bookmarks.as_ptr() as *mut _, &mut err) }; 923 | 924 | if r != 0 { 925 | Err((io::Error::from_raw_os_error(r), unsafe { 926 | NvList::from_ptr(err) 927 | })) 928 | } else { 929 | Ok(()) 930 | } 931 | } 932 | 933 | /// Retreive bookmarks for the given filesystem 934 | /// 935 | /// `props` is a list of `[(prop_name, ())]`, where `prop_name` names a property on a bookmark. 936 | /// All the named properties are returned in the return value as the values of each bookmark. 937 | /// 938 | /// Corresponds to `lzc_get_bookmarks()` 939 | #[doc(alias = "lzc_get_bookmarks")] 940 | pub fn get_bookmarks_raw( 941 | &self, 942 | fsname: F, 943 | props: &NvListRef, 944 | ) -> io::Result { 945 | let mut res = ptr::null_mut(); 946 | let fsname = fsname.into_cstr(); 947 | 948 | let r = unsafe { 949 | sys::lzc_get_bookmarks(fsname.as_ref().as_ptr(), props.as_ptr() as *mut _, &mut res) 950 | }; 951 | 952 | if r != 0 { 953 | Err(io::Error::from_raw_os_error(r)) 954 | } else { 955 | Ok(unsafe { NvList::from_ptr(res) }) 956 | } 957 | } 958 | 959 | /// Corresponds to `lzc_get_bookmark_props()` 960 | #[doc(alias = "lzc_get_bookmark_props")] 961 | pub fn get_bookmark_props(&self, bookmark: B) -> io::Result { 962 | let mut res = ptr::null_mut(); 963 | let bookmark = bookmark.into_cstr(); 964 | 965 | let r = unsafe { sys::lzc_get_bookmark_props(bookmark.as_ref().as_ptr(), &mut res) }; 966 | 967 | if r != 0 { 968 | Err(io::Error::from_raw_os_error(r)) 969 | } else { 970 | Ok(unsafe { NvList::from_ptr(res) }) 971 | } 972 | } 973 | 974 | /// Corresponds to `lzc_destroy_bookmarks()` 975 | #[doc(alias = "lzc_destroy_bookmarks")] 976 | pub fn destroy_bookmarks(&self, bookmarks: &NvListRef) -> Result<(), (io::Error, NvList)> { 977 | let mut errs = ptr::null_mut(); 978 | 979 | let r = unsafe { sys::lzc_destroy_bookmarks(bookmarks.as_ptr() as *mut _, &mut errs) }; 980 | 981 | if r != 0 { 982 | Err((io::Error::from_raw_os_error(r), unsafe { 983 | NvList::from_ptr(errs) 984 | })) 985 | } else { 986 | Ok(()) 987 | } 988 | } 989 | 990 | /// Execute a channel program 991 | /// 992 | /// root privlidges are required to execute a channel program 993 | /// 994 | /// Corresponds to `lzc_channel_program()` 995 | // 0.8.? 996 | #[doc(alias = "lzc_channel_program")] 997 | pub fn channel_program( 998 | &self, 999 | pool: P, 1000 | program: R, 1001 | instruction_limit: u64, 1002 | memlimit: u64, 1003 | args: &NvListRef, 1004 | ) -> io::Result { 1005 | let mut out_nv = ptr::null_mut(); 1006 | 1007 | let pool = pool.into_cstr(); 1008 | let program = program.into_cstr(); 1009 | 1010 | let r = unsafe { 1011 | sys::lzc_channel_program( 1012 | pool.as_ref().as_ptr(), 1013 | program.as_ref().as_ptr(), 1014 | instruction_limit, 1015 | memlimit, 1016 | args.as_ptr() as *mut _, 1017 | &mut out_nv, 1018 | ) 1019 | }; 1020 | 1021 | if r != 0 { 1022 | Err(io::Error::from_raw_os_error(r)) 1023 | } else { 1024 | Ok(unsafe { NvList::from_ptr(out_nv) }) 1025 | } 1026 | } 1027 | 1028 | /// Execute a read-only channel program 1029 | /// 1030 | /// root privlidges are required to execute a channel program (even a read-only one) 1031 | /// 1032 | /// Corresponds to `lzc_channel_program_nosync()` 1033 | #[doc(alias = "lzc_channel_program_nosync")] 1034 | pub fn channel_program_nosync( 1035 | &self, 1036 | pool: P, 1037 | program: R, 1038 | instruction_limit: u64, 1039 | memlimit: u64, 1040 | args: &NvListRef, 1041 | ) -> io::Result { 1042 | let mut out_nv = ptr::null_mut(); 1043 | 1044 | let pool = pool.into_cstr(); 1045 | let program = program.into_cstr(); 1046 | 1047 | let r = unsafe { 1048 | sys::lzc_channel_program_nosync( 1049 | pool.as_ref().as_ptr(), 1050 | program.as_ref().as_ptr(), 1051 | instruction_limit, 1052 | memlimit, 1053 | args.as_ptr() as *mut _, 1054 | &mut out_nv, 1055 | ) 1056 | }; 1057 | 1058 | if r != 0 { 1059 | Err(io::Error::from_raw_os_error(r)) 1060 | } else { 1061 | Ok(unsafe { NvList::from_ptr(out_nv) }) 1062 | } 1063 | } 1064 | 1065 | /// Create a pool checkpoint 1066 | /// 1067 | /// Corresponds to `lzc_pool_checkpoint()` 1068 | /// 1069 | // FIXME: libzfs_core.c lists the specific error returns 1070 | // 0.8.? 1071 | #[doc(alias = "lzc_pool_checkpoint")] 1072 | pub fn pool_checkpoint(&self, pool: P) -> io::Result<()> { 1073 | let pool = pool.into_cstr(); 1074 | 1075 | let r = unsafe { sys::lzc_pool_checkpoint(pool.as_ref().as_ptr()) }; 1076 | 1077 | if r != 0 { 1078 | Err(io::Error::from_raw_os_error(r)) 1079 | } else { 1080 | Ok(()) 1081 | } 1082 | } 1083 | 1084 | /// Discard the pool checkpoint 1085 | /// 1086 | /// Corresponds to `lzc_pool_checkpoint_discard()` 1087 | #[doc(alias = "lzc_pool_checkpoint_discard")] 1088 | pub fn pool_checkpoint_discard(&self, pool: P) -> io::Result<()> { 1089 | let pool = pool.into_cstr(); 1090 | 1091 | let r = unsafe { sys::lzc_pool_checkpoint_discard(pool.as_ref().as_ptr()) }; 1092 | 1093 | if r != 0 { 1094 | Err(io::Error::from_raw_os_error(r)) 1095 | } else { 1096 | Ok(()) 1097 | } 1098 | } 1099 | 1100 | /// Corresponds to `lzc_load_key()` 1101 | #[doc(alias = "lzc_load_key")] 1102 | pub fn load_key( 1103 | &self, 1104 | fsname: F, 1105 | noop: bool, 1106 | keydata: &[u8], 1107 | ) -> io::Result<()> { 1108 | let fsname = fsname.into_cstr(); 1109 | 1110 | let r = unsafe { 1111 | sys::lzc_load_key( 1112 | fsname.as_ref().as_ptr(), 1113 | if noop { 1114 | sys::boolean_t::B_TRUE 1115 | } else { 1116 | sys::boolean_t::B_FALSE 1117 | }, 1118 | keydata.as_ptr() as *mut _, 1119 | keydata.len().try_into().unwrap(), 1120 | ) 1121 | }; 1122 | 1123 | if r != 0 { 1124 | Err(io::Error::from_raw_os_error(r)) 1125 | } else { 1126 | Ok(()) 1127 | } 1128 | } 1129 | 1130 | /// Corresponds to `lzc_unload_key()` 1131 | #[doc(alias = "lzc_unload_key")] 1132 | pub fn unload_key(&self, fsname: F) -> io::Result<()> { 1133 | let fsname = fsname.into_cstr(); 1134 | 1135 | let r = unsafe { sys::lzc_unload_key(fsname.as_ref().as_ptr()) }; 1136 | 1137 | if r != 0 { 1138 | Err(io::Error::from_raw_os_error(r)) 1139 | } else { 1140 | Ok(()) 1141 | } 1142 | } 1143 | 1144 | /// Corresponds to `lzc_change_key()` 1145 | #[doc(alias = "lzc_change_key")] 1146 | pub fn change_key( 1147 | &self, 1148 | fsname: F, 1149 | crypt_cmd: u64, 1150 | props: &NvListRef, 1151 | keydata: Option<&[u8]>, 1152 | ) -> io::Result<()> { 1153 | let fsname = fsname.into_cstr(); 1154 | 1155 | let (k, l) = keydata.map_or((ptr::null_mut(), 0), |v| (v.as_ptr() as *mut _, v.len())); 1156 | let r = unsafe { 1157 | sys::lzc_change_key( 1158 | fsname.as_ref().as_ptr(), 1159 | crypt_cmd, 1160 | props.as_ptr() as *mut _, 1161 | k, 1162 | l.try_into().unwrap(), 1163 | ) 1164 | }; 1165 | 1166 | if r != 0 { 1167 | Err(io::Error::from_raw_os_error(r)) 1168 | } else { 1169 | Ok(()) 1170 | } 1171 | } 1172 | 1173 | /// Corresponds to `lzc_reopen()` 1174 | // 0.8.0 1175 | #[doc(alias = "lzc_reopen")] 1176 | pub fn reopen(&self, pool: P, scrub_restart: bool) -> io::Result<()> { 1177 | let pool = pool.into_cstr(); 1178 | 1179 | let r = unsafe { 1180 | sys::lzc_reopen( 1181 | pool.as_ref().as_ptr(), 1182 | if scrub_restart { 1183 | sys::boolean_t::B_TRUE 1184 | } else { 1185 | sys::boolean_t::B_FALSE 1186 | }, 1187 | ) 1188 | }; 1189 | 1190 | if r != 0 { 1191 | Err(io::Error::from_raw_os_error(r)) 1192 | } else { 1193 | Ok(()) 1194 | } 1195 | } 1196 | 1197 | /// Corresponds to `lzc_initialize()` 1198 | // 0.8.0 1199 | #[doc(alias = "lzc_initialize")] 1200 | pub fn initialize( 1201 | &self, 1202 | pool: P, 1203 | initialize_func: PoolInitializeFunc, 1204 | vdevs: &NvListRef, 1205 | ) -> Result<(), (io::Error, NvList)> { 1206 | let pool = pool.into_cstr(); 1207 | 1208 | let mut err_nv = ptr::null_mut(); 1209 | let r = unsafe { 1210 | sys::lzc_initialize( 1211 | pool.as_ref().as_ptr(), 1212 | initialize_func.as_raw(), 1213 | vdevs.as_ptr() as *mut _, 1214 | &mut err_nv, 1215 | ) 1216 | }; 1217 | 1218 | if r != 0 { 1219 | Err((io::Error::from_raw_os_error(r), unsafe { 1220 | NvList::from_ptr(err_nv) 1221 | })) 1222 | } else { 1223 | Ok(()) 1224 | } 1225 | } 1226 | 1227 | /// Corresponds to `lzc_trim()` 1228 | // 0.8.0 1229 | #[doc(alias = "lzc_trim")] 1230 | pub fn trim( 1231 | &self, 1232 | pool: P, 1233 | pool_trim_func: PoolTrimFunc, 1234 | rate: u64, 1235 | secure: bool, 1236 | vdevs: &NvListRef, 1237 | ) -> Result<(), (io::Error, NvList)> { 1238 | let pool = pool.into_cstr(); 1239 | 1240 | let mut err_nv = ptr::null_mut(); 1241 | let r = unsafe { 1242 | sys::lzc_trim( 1243 | pool.as_ref().as_ptr(), 1244 | pool_trim_func.as_raw(), 1245 | rate, 1246 | if secure { 1247 | sys::boolean_t::B_TRUE 1248 | } else { 1249 | sys::boolean_t::B_FALSE 1250 | }, 1251 | vdevs.as_ptr() as *mut _, 1252 | &mut err_nv, 1253 | ) 1254 | }; 1255 | 1256 | if r != 0 { 1257 | Err((io::Error::from_raw_os_error(r), unsafe { 1258 | NvList::from_ptr(err_nv) 1259 | })) 1260 | } else { 1261 | Ok(()) 1262 | } 1263 | } 1264 | 1265 | /// Corresponds to `lzc_redact()` 1266 | #[cfg(features = "v2_00")] 1267 | #[doc(alias = "lzc_redact")] 1268 | pub fn redact( 1269 | &self, 1270 | snapname: S, 1271 | bookname: B, 1272 | snapnv: &NvListRef, 1273 | ) -> io::Result<()> { 1274 | let snapname = snapname.into_cstr(); 1275 | let bookname = bookname.into_cstr(); 1276 | 1277 | let r = unsafe { 1278 | sys::lzc_redact( 1279 | snapname.as_ref().as_ptr(), 1280 | bookname.as_ref().as_ptr(), 1281 | snapnv.as_ptr() as *mut _, 1282 | ) 1283 | }; 1284 | 1285 | if r != 0 { 1286 | Err(io::Error::from_raw_os_error(r)) 1287 | } else { 1288 | Ok(()) 1289 | } 1290 | } 1291 | 1292 | /// Corresponds to `lzc_wait()` 1293 | #[cfg(features = "v2_00")] 1294 | #[doc(alias = "lzc_wait")] 1295 | pub fn wait(&self, pool: P, activity: WaitActivity) -> io::Result { 1296 | let pool = pool.into_cstr(); 1297 | 1298 | let mut waited = sys::boolean_t::B_FALSE; 1299 | let r = unsafe { sys::lzc_wait(pool.as_ref().as_ptr(), activity.as_raw(), &mut waited) }; 1300 | 1301 | if r != 0 { 1302 | Err(io::Error::from_raw_os_error(r)) 1303 | } else { 1304 | Ok(waited != sys::boolean_t::B_FALSE) 1305 | } 1306 | } 1307 | 1308 | /// Corresponds to `lzc_wait_tag()` 1309 | #[cfg(features = "v2_00")] 1310 | #[doc(alias = "lzc_wait_tag")] 1311 | pub fn wait_tag( 1312 | &self, 1313 | pool: P, 1314 | activity: WaitActivity, 1315 | tag: u64, 1316 | ) -> io::Result { 1317 | let pool = pool.into_cstr(); 1318 | 1319 | let mut waited = sys::boolean_t::B_FALSE; 1320 | let r = unsafe { 1321 | sys::lzc_wait_tag(pool.as_ref().as_ptr(), activity.as_raw(), tag, &mut waited) 1322 | }; 1323 | 1324 | if r != 0 { 1325 | Err(io::Error::from_raw_os_error(r)) 1326 | } else { 1327 | Ok(waited != sys::boolean_t::B_FALSE) 1328 | } 1329 | } 1330 | 1331 | /// Corresponds to `lzc_wait_fs()` 1332 | #[cfg(features = "v2_00")] 1333 | #[doc(alias = "lzc_wait_fs")] 1334 | pub fn wait_fs(&self, fs: F, activity: WaitActivity) -> io::Result { 1335 | let fs = fs.into_cstr(); 1336 | 1337 | let mut waited = sys::boolean_t::B_FALSE; 1338 | let r = 1339 | unsafe { sys::lzc_wait_fs(fs.as_ref().as_ptr(), activity.as_raw(), tag, &mut waited) }; 1340 | 1341 | if r != 0 { 1342 | Err(io::Error::from_raw_os_error(r)) 1343 | } else { 1344 | Ok(waited != sys::boolean_t::B_FALSE) 1345 | } 1346 | } 1347 | 1348 | /// Corresponds to `lzc_set_bootenv()` 1349 | #[cfg(features = "v2_00")] 1350 | #[doc(alias = "lzc_set_bootenv")] 1351 | pub fn set_bootenv( 1352 | &self, 1353 | pool: P, 1354 | env: &NvListRef, 1355 | ) -> io::Result<()> { 1356 | let pool = pool.into_cstr(); 1357 | let v = unsafe { sys::lzc_set_bootenv(pool.as_ref().as_ptr(), env.as_ptr()) }; 1358 | if v != 0 { 1359 | Err(io::Error::from_raw_os_error(v)) 1360 | } else { 1361 | Ok(()) 1362 | } 1363 | } 1364 | 1365 | /// Corresponds `lzc_get_bootenv()` 1366 | #[cfg(features = "v2_00")] 1367 | #[doc(alias = "lzc_get_bootenv")] 1368 | pub fn get_bootenv(&self, pool: P) -> io::Result { 1369 | let pool = pool.into_cstr(); 1370 | let mut env = ptr::null_mut(); 1371 | let v = unsafe { sys::lzc_get_bootenv(pool.as_ref().as_ptr(), &mut env) }; 1372 | if v != 0 { 1373 | Err(io::Error::from_raw_os_error(v)) 1374 | } else { 1375 | Ok(unsafe { NvList::from_ptr(env) }) 1376 | } 1377 | } 1378 | } 1379 | 1380 | impl Drop for Zfs { 1381 | fn drop(&mut self) { 1382 | unsafe { sys::libzfs_core_fini() } 1383 | } 1384 | } 1385 | 1386 | #[derive(Debug, PartialEq)] 1387 | pub enum PoolInitializeFunc { 1388 | Start, 1389 | Cancel, 1390 | Suspend, 1391 | } 1392 | 1393 | impl PoolInitializeFunc { 1394 | pub fn as_raw(&self) -> sys::pool_initialize_func_t { 1395 | use sys::pool_initialize_func as ifc; 1396 | use PoolInitializeFunc::*; 1397 | 1398 | match self { 1399 | Start => ifc::POOL_INITIALIZE_START, 1400 | Cancel => ifc::POOL_INITIALIZE_CANCEL, 1401 | Suspend => ifc::POOL_INITIALIZE_SUSPEND, 1402 | } 1403 | } 1404 | } 1405 | 1406 | #[derive(Debug, PartialEq)] 1407 | pub enum PoolTrimFunc { 1408 | Start, 1409 | Cancel, 1410 | Suspend, 1411 | } 1412 | 1413 | impl PoolTrimFunc { 1414 | pub fn as_raw(&self) -> sys::pool_trim_func_t { 1415 | use sys::pool_trim_func as ptf; 1416 | use PoolTrimFunc::*; 1417 | match self { 1418 | Start => ptf::POOL_TRIM_START, 1419 | Cancel => ptf::POOL_TRIM_CANCEL, 1420 | Suspend => ptf::POOL_TRIM_SUSPEND, 1421 | } 1422 | } 1423 | } 1424 | 1425 | #[derive(Debug, PartialEq)] 1426 | pub enum WaitActivity { 1427 | Discard, 1428 | Free, 1429 | Initialize, 1430 | Replace, 1431 | Remove, 1432 | Resliver, 1433 | Scrub, 1434 | Trim, 1435 | } 1436 | 1437 | #[derive(Debug, Default, Clone, Copy, PartialEq)] 1438 | pub struct SendFlags { 1439 | pub embed_data: bool, 1440 | pub large_block: bool, 1441 | pub compress: bool, 1442 | pub raw: bool, 1443 | 1444 | #[cfg(features = "v2_00")] 1445 | pub saved: bool, 1446 | } 1447 | 1448 | impl From for u32 { 1449 | fn from(sf: SendFlags) -> Self { 1450 | let mut f = 0; 1451 | if sf.embed_data { 1452 | f |= sys::lzc_send_flags::LZC_SEND_FLAG_EMBED_DATA; 1453 | } 1454 | if sf.large_block { 1455 | f |= sys::lzc_send_flags::LZC_SEND_FLAG_LARGE_BLOCK; 1456 | } 1457 | if sf.compress { 1458 | f |= sys::lzc_send_flags::LZC_SEND_FLAG_COMPRESS; 1459 | } 1460 | if sf.raw { 1461 | f |= sys::lzc_send_flags::LZC_SEND_FLAG_RAW; 1462 | } 1463 | #[cfg(features = "v2_00")] 1464 | if sf.saved { 1465 | f |= sys::lzc_send_flags::LZC_SEND_FLAG_SAVED; 1466 | } 1467 | 1468 | f 1469 | } 1470 | } 1471 | 1472 | #[derive(Debug, Clone, Copy, PartialEq)] 1473 | pub enum Defer { 1474 | No, 1475 | Yes, 1476 | } 1477 | 1478 | impl Default for Defer { 1479 | fn default() -> Self { 1480 | Defer::No 1481 | } 1482 | } 1483 | 1484 | impl From for bool { 1485 | fn from(d: Defer) -> Self { 1486 | match d { 1487 | Defer::No => false, 1488 | Defer::Yes => true, 1489 | } 1490 | } 1491 | } 1492 | 1493 | /// A list of holds for a given snapshot 1494 | #[derive(Debug)] 1495 | pub struct HoldList { 1496 | nv: NvList, 1497 | } 1498 | 1499 | impl HoldList { 1500 | fn new(nv: NvList) -> Self { 1501 | Self { nv } 1502 | } 1503 | } 1504 | 1505 | impl From for NvList { 1506 | fn from(hl: HoldList) -> Self { 1507 | hl.nv 1508 | } 1509 | } 1510 | 1511 | impl AsRef for HoldList { 1512 | fn as_ref(&self) -> &NvListRef { 1513 | &self.nv 1514 | } 1515 | } 1516 | 1517 | /// Iterator of holds in the [`HoldList`] 1518 | #[derive(Debug)] 1519 | pub struct HoldListIter<'a> { 1520 | iter: NvListIter<'a>, 1521 | } 1522 | 1523 | impl<'a> IntoIterator for &'a HoldList { 1524 | type Item = (&'a ffi::CStr, std::time::SystemTime); 1525 | type IntoIter = HoldListIter<'a>; 1526 | fn into_iter(self) -> Self::IntoIter { 1527 | HoldListIter { 1528 | iter: (&self.nv).into_iter(), 1529 | } 1530 | } 1531 | } 1532 | 1533 | impl<'a> Iterator for HoldListIter<'a> { 1534 | type Item = (&'a ffi::CStr, std::time::SystemTime); 1535 | 1536 | fn next(&mut self) -> Option { 1537 | match self.iter.next() { 1538 | Some(nvp) => { 1539 | let t = match nvp.data() { 1540 | nvpair::NvData::Uint64(time_sec) => { 1541 | std::time::UNIX_EPOCH + std::time::Duration::from_secs(time_sec) 1542 | } 1543 | v => panic!("unexpected datatype in hold list {:?}", v), 1544 | }; 1545 | Some((nvp.name(), t)) 1546 | } 1547 | None => None, 1548 | } 1549 | } 1550 | } 1551 | -------------------------------------------------------------------------------- /zfs-core/test-prepare: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -euf -o pipefail 3 | 4 | if [ $# -ne 1 ]; then 5 | >& 2 echo "usage: $(basename $0) pool-name" 6 | exit 1 7 | fi 8 | 9 | if [ -z "$SUDO_USER" ]; then 10 | echo "User unknown" 11 | exit 2 12 | fi 13 | 14 | base="$1" 15 | 16 | set -x 17 | #truncate --size 2G "test-pool-$base.img" 18 | dd if=/dev/zero of=test-pool-$base.img bs=1 count=0 seek=$((1024*1024*1024*2)) 19 | zpool create "$base" "$PWD/test-pool-$base.img" 20 | # openzfsonosx 1.9.4: mount,create 21 | # automatically get destroy for created datasets? 22 | # zfsonlinux 0.8.3: mount,create,destroy 23 | zfs allow -uld "$SUDO_USER" mount,create,destroy,rename,snapshot,hold,release,send,receive,rollback "$base" 24 | set +x 25 | 26 | echo "export ZFS_TEMPFS=$base" 27 | -------------------------------------------------------------------------------- /zfs-core/tests/basic.rs: -------------------------------------------------------------------------------- 1 | extern crate zfs_core as zfs; 2 | 3 | use rand::distributions::Alphanumeric; 4 | use rand::{thread_rng, Rng}; 5 | use std::io; 6 | use std::io::Seek; 7 | use std::os::unix::io::AsRawFd; 8 | 9 | fn have_root_privs() -> bool { 10 | // this might not be totally accurate 11 | unsafe { libc::geteuid() == 0 } 12 | } 13 | 14 | struct TempFs { 15 | path: String, 16 | } 17 | 18 | // How many times should we (re)try finding an unused random name? It should be 19 | // enough that an attacker will run out of luck before we run out of patience. 20 | const NUM_RETRIES: u32 = 1 << 31; 21 | // How many characters should we include in a random file name? It needs to 22 | // be enough to dissuade an attacker from trying to preemptively create names 23 | // of that length, but not so huge that we unnecessarily drain the random number 24 | // generator of entropy. 25 | const NUM_RAND_CHARS: usize = 12; 26 | 27 | fn tmp_zpool_name() -> String { 28 | std::env::var("ZFS_TEMPFS").expect( 29 | "ZFS_TEMPFS should be set to a zfs filesystem that can be used for temporary datasets", 30 | ) 31 | } 32 | 33 | impl TempFs { 34 | fn with_base(base: &str, prefix: &str) -> io::Result { 35 | let z = zfs::Zfs::new()?; 36 | let nv = nvpair::NvList::try_new()?; 37 | 38 | let mut rng = thread_rng(); 39 | for _ in 0..NUM_RETRIES { 40 | let suffix: String = (&mut rng) 41 | .sample_iter(Alphanumeric) 42 | .take(NUM_RAND_CHARS) 43 | .map(|x| x as char) 44 | .collect(); 45 | 46 | let mut path = base.to_owned(); 47 | path.push_str("/"); 48 | 49 | if !prefix.is_empty() { 50 | path.push_str(prefix); 51 | path.push_str("-"); 52 | } 53 | path.push_str(&suffix); 54 | 55 | match z.create(&path, zfs::DataSetType::Zfs, &nv) { 56 | Ok(_) => return Ok(TempFs { path }), 57 | Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {} 58 | Err(e) => return Err(e), 59 | } 60 | } 61 | 62 | Err(io::Error::new( 63 | io::ErrorKind::AlreadyExists, 64 | "too many temporary filesystems already exist", 65 | )) 66 | } 67 | 68 | pub fn new(prefix: &str) -> io::Result { 69 | Self::with_base(&tmp_zpool_name(), prefix) 70 | } 71 | 72 | pub fn path(&self) -> &str { 73 | &self.path 74 | } 75 | 76 | /* 77 | pub fn mount(&self) -> TempMount { 78 | TempMount::new(self) 79 | } 80 | */ 81 | } 82 | 83 | /* 84 | struct TempMount<'a> { 85 | tempfs: &'a TempFs, 86 | mount_dir: tempfile::TempDir, 87 | } 88 | 89 | impl<'a> TempMount<'a> { 90 | pub fn new(tempfs: &'a TempFs) -> Self { 91 | let mount_dir = tempfile::tempdir().unwrap(); 92 | 93 | std::process::Command::new("mount") 94 | .arg("-t").arg("zfs") 95 | .arg(&tempfs.path) 96 | .arg(mount_dir.path()) 97 | .status().unwrap(); 98 | 99 | Self { 100 | tempfs, 101 | mount_dir, 102 | } 103 | } 104 | } 105 | 106 | impl<'a> Drop for TempMount<'a> { 107 | fn drop(&mut self) { 108 | if let Err(e) = std::process::Command::new("umount") 109 | .arg(self.mount_dir.path()).status() { 110 | eprintln!("Could not unmount TempMount: {:?}: {}", self.mount_dir.path(), e); 111 | } 112 | } 113 | } 114 | */ 115 | 116 | impl Drop for TempFs { 117 | fn drop(&mut self) { 118 | let z = zfs::Zfs::new().unwrap(); 119 | if let Err(e) = z.destroy(&self.path) { 120 | eprintln!("Could not destroy TempFs {}: {}", self.path, e); 121 | } 122 | } 123 | } 124 | 125 | #[test] 126 | fn new() { 127 | let _ = zfs::Zfs::new().unwrap(); 128 | } 129 | 130 | #[test] 131 | fn create_destroy() { 132 | let tmpfs = TempFs::new("create").unwrap(); 133 | 134 | let mut b = tmpfs.path().to_owned(); 135 | b.push_str("/"); 136 | b.push_str("create"); 137 | 138 | let z = zfs::Zfs::new().unwrap(); 139 | let nv = nvpair::NvList::new(); 140 | z.create(&b, zfs::DataSetType::Zfs, &nv) 141 | .expect(&format!("create {:?} failed", b)); 142 | 143 | assert_eq!(z.exists(&b), true); 144 | let mut b2 = b.clone(); 145 | b2.push_str("fooie"); 146 | assert_eq!(z.exists(&b2), false); 147 | 148 | z.destroy(&b).unwrap(); 149 | } 150 | 151 | #[test] 152 | fn rename() { 153 | let tmpfs = TempFs::new("rename").unwrap(); 154 | 155 | let mut b = tmpfs.path().to_owned(); 156 | b.push_str("/"); 157 | let mut b_new = b.clone(); 158 | b.push_str("orig"); 159 | b_new.push_str("new"); 160 | 161 | let z = zfs::Zfs::new().unwrap(); 162 | let nv = nvpair::NvList::new(); 163 | z.create(&b, zfs::DataSetType::Zfs, &nv) 164 | .expect(&format!("create {:?} failed", b)); 165 | 166 | assert_eq!(z.exists(&b), true); 167 | 168 | z.rename(&b, &b_new).unwrap(); 169 | assert_eq!(z.exists(&b), false); 170 | assert_eq!(z.exists(&b_new), true); 171 | 172 | z.destroy(&b_new).unwrap(); 173 | } 174 | 175 | #[test] 176 | fn snapshot() { 177 | let tmpfs = TempFs::new("snapshot").unwrap(); 178 | 179 | let mut b = tmpfs.path().to_owned(); 180 | b.push_str("/"); 181 | let mut b_new = b.clone(); 182 | b.push_str("orig"); 183 | b_new.push_str("clone"); 184 | 185 | let z = zfs::Zfs::new().unwrap(); 186 | let nv = nvpair::NvList::new(); 187 | z.create(&b, zfs::DataSetType::Zfs, &nv) 188 | .expect(&format!("create {:?} failed", b)); 189 | 190 | assert_eq!(z.exists(&b), true); 191 | 192 | let mut b_snap = b.clone(); 193 | b_snap.push_str("@a"); 194 | z.snapshot([b_snap.as_str()].iter().cloned()).unwrap(); 195 | 196 | assert_eq!(z.exists(&b), true); 197 | assert_eq!(z.exists(&b_snap), true); 198 | 199 | z.destroy_snaps([b_snap.as_str()].iter().cloned(), zfs::Defer::No) 200 | .unwrap(); 201 | z.destroy(&b).unwrap(); 202 | } 203 | 204 | #[test] 205 | fn snapshot_multi() { 206 | let tmpfs = TempFs::new("snapshot_multi").unwrap(); 207 | 208 | let mut b = tmpfs.path().to_owned(); 209 | b.push_str("/"); 210 | let mut b_alt = b.clone(); 211 | b.push_str("1"); 212 | b_alt.push_str("2"); 213 | 214 | let z = zfs::Zfs::new().unwrap(); 215 | let nv = nvpair::NvList::new(); 216 | z.create(&b, zfs::DataSetType::Zfs, &nv) 217 | .expect(&format!("create {:?} failed", b)); 218 | z.create(&b_alt, zfs::DataSetType::Zfs, &nv) 219 | .expect(&format!("create {:?} failed", b_alt)); 220 | 221 | assert_eq!(z.exists(&b), true); 222 | assert_eq!(z.exists(&b_alt), true); 223 | 224 | let mut b_snap1 = b.clone(); 225 | b_snap1.push_str("@a"); 226 | let mut b_snap2 = b_alt.clone(); 227 | b_snap2.push_str("@b"); 228 | z.snapshot([b_snap1.as_str(), b_snap2.as_str()].iter().cloned()) 229 | .unwrap(); 230 | 231 | assert_eq!(z.exists(&b), true); 232 | assert_eq!(z.exists(&b_snap1), true); 233 | assert_eq!(z.exists(&b_snap2), true); 234 | 235 | z.destroy_snaps( 236 | [b_snap1.as_str(), b_snap2.as_str()].iter().cloned(), 237 | zfs::Defer::No, 238 | ) 239 | .unwrap(); 240 | z.destroy(&b).unwrap(); 241 | } 242 | 243 | #[test] 244 | fn hold_raw() { 245 | let tmpfs = TempFs::new("hold-raw").unwrap(); 246 | 247 | let mut b = tmpfs.path().to_owned(); 248 | b.push_str("/"); 249 | let mut b_alt = b.clone(); 250 | b.push_str("1"); 251 | b_alt.push_str("2"); 252 | 253 | let z = zfs::Zfs::new().unwrap(); 254 | let nv = nvpair::NvList::new(); 255 | z.create(&b, zfs::DataSetType::Zfs, &nv) 256 | .expect(&format!("create {:?} failed", b)); 257 | z.create(&b_alt, zfs::DataSetType::Zfs, &nv) 258 | .expect(&format!("create {:?} failed", b_alt)); 259 | 260 | assert_eq!(z.exists(&b), true); 261 | assert_eq!(z.exists(&b_alt), true); 262 | 263 | let mut b_snap1 = b.clone(); 264 | b_snap1.push_str("@a"); 265 | let mut b_snap2 = b_alt.clone(); 266 | b_snap2.push_str("@b"); 267 | z.snapshot([b_snap1.as_str(), b_snap2.as_str()].iter().cloned()) 268 | .unwrap(); 269 | 270 | assert_eq!(z.exists(&b), true); 271 | assert_eq!(z.exists(&b_snap1), true); 272 | assert_eq!(z.exists(&b_snap2), true); 273 | 274 | let mut hold_snaps = nvpair::NvList::new(); 275 | hold_snaps.insert(&b_snap1, "hold-hello").unwrap(); 276 | z.hold_raw(&hold_snaps, None).unwrap(); 277 | 278 | z.destroy_snaps( 279 | [b_snap1.as_str(), b_snap2.as_str()].iter().cloned(), 280 | zfs::Defer::Yes, 281 | ) 282 | .unwrap(); 283 | 284 | assert_eq!(z.exists(&b_snap1), true); 285 | assert_eq!(z.exists(&b_snap2), false); 286 | 287 | let mut release_snaps = nvpair::NvList::new(); 288 | let mut holds_for_snap = nvpair::NvList::new(); 289 | holds_for_snap.insert("hold-hello", &()).unwrap(); 290 | release_snaps 291 | .insert(&b_snap1, holds_for_snap.as_ref()) 292 | .unwrap(); 293 | z.release_raw(&release_snaps).unwrap(); 294 | 295 | assert_eq!(z.exists(&b_snap1), false); 296 | assert_eq!(z.exists(&b_snap2), false); 297 | 298 | z.destroy(&b).unwrap(); 299 | } 300 | 301 | /// hold: EINVAL: path doesn't look like a snapshot (no `@`) 302 | #[test] 303 | fn hold_not_snap() { 304 | let tmpfs = TempFs::new("hold_not_snap").unwrap(); 305 | 306 | let z = zfs::Zfs::new().unwrap(); 307 | let e = z.hold( 308 | [(tmpfs.path().to_owned() + "/2", "doesn't look like snapshot")].iter(), 309 | None, 310 | ); 311 | 312 | let e = if let Err(e) = e { 313 | e 314 | } else { 315 | panic!("expected an error"); 316 | }; 317 | 318 | // XXX: macos vs linux zfs difference 319 | // I would have expected to get a list of errors here instead of a single error, like the macos 320 | // variant. This might be a bug in our code, or some change in how lzc handles errors for 321 | // single results 322 | match e { 323 | // openzfs 2.0.0: 324 | zfs_core::Error::Io { source: e } => { 325 | assert_eq!(e.kind(), io::ErrorKind::InvalidInput); 326 | } 327 | // openzfs 1.9.4: 328 | zfs_core::Error::List { source: el } => { 329 | let mut hm = std::collections::HashMap::new(); 330 | hm.insert(tmpfs.path().to_owned() + "/2", io::ErrorKind::InvalidInput); 331 | 332 | for (name, error) in el.iter() { 333 | match hm.remove(name.to_str().unwrap()) { 334 | Some(v) => { 335 | assert_eq!(error.kind(), v); 336 | } 337 | None => panic!(), 338 | } 339 | } 340 | } 341 | } 342 | } 343 | 344 | /// hold: NotFound: snapshot named doesn't exist 345 | #[test] 346 | fn hold_not_exist() { 347 | let tmpfs = TempFs::new("hold_not_exist").unwrap(); 348 | 349 | let z = zfs::Zfs::new().unwrap(); 350 | 351 | // FIXME: when run with root perms, this does not return an error. 352 | let e = z.hold( 353 | [ 354 | (tmpfs.path().to_owned() + "/1@snap", "snap doesn't exist"), 355 | (tmpfs.path().to_owned() + "/2@snap", "snap doesn't exist"), 356 | ] 357 | .iter(), 358 | None, 359 | ); 360 | 361 | let e = if let Err(e) = e { 362 | e 363 | } else { 364 | // macos hits this for some reason 365 | /* 366 | #[cfg(target_os = "macos")] 367 | { 368 | eprintln!("macos zfs 1.9.4 is for some reason totally cool with creating holds on non-existent snaps"); 369 | return; 370 | } 371 | */ 372 | 373 | panic!("expected an error, got {:?}", e); 374 | }; 375 | 376 | // XXX: macos vs linux zfs difference 377 | // macos for some reason returns `Ok(())`, which is somewhat concerning 378 | // linux (zfs 2.0.0) doesn't appear to return our error list, which is also concerning 379 | match e { 380 | // zfs 2.0.0 on linux: 381 | zfs_core::Error::Io { source: e } => { 382 | assert_eq!(e.kind(), io::ErrorKind::NotFound); 383 | } 384 | // _expected_ result 385 | /* 386 | zfs_core::Error::List { source: el } => { 387 | let mut hm = std::collections::HashMap::new(); 388 | hm.insert(tmpfs.path().to_owned() + "/1@snap", io::ErrorKind::NotFound); 389 | 390 | for (name, error) in el.iter() { 391 | match hm.remove(name.to_str().unwrap()) { 392 | Some(v) => { 393 | assert_eq!(error.kind(), v); 394 | } 395 | None => panic!() 396 | } 397 | } 398 | } 399 | */ 400 | _ => { 401 | panic!("unexpected error kind: {:?}", e); 402 | } 403 | } 404 | } 405 | 406 | #[test] 407 | fn hold_ok() { 408 | let tmpfs = TempFs::new("hold_ok").unwrap(); 409 | let z = zfs::Zfs::new().unwrap(); 410 | 411 | let props = nvpair::NvList::new(); 412 | z.create( 413 | tmpfs.path().to_owned() + "/1", 414 | zfs::DataSetType::Zfs, 415 | &props, 416 | ) 417 | .unwrap(); 418 | z.snapshot([tmpfs.path().to_owned() + "/1@snap"].iter().cloned()) 419 | .unwrap(); 420 | 421 | z.hold( 422 | [(tmpfs.path().to_owned() + "/1@snap", "test-hold-ok")].iter(), 423 | None, 424 | ) 425 | .unwrap(); 426 | 427 | z.release( 428 | [( 429 | tmpfs.path().to_owned() + "/1@snap", 430 | ["test-hold-ok"].iter().cloned(), 431 | )] 432 | .iter(), 433 | ) 434 | .unwrap(); 435 | } 436 | 437 | #[test] 438 | fn get_holds() { 439 | let tmpfs = TempFs::new("get_holds").unwrap(); 440 | let z = zfs::Zfs::new().unwrap(); 441 | 442 | let props = nvpair::NvList::new(); 443 | z.create( 444 | tmpfs.path().to_owned() + "/1", 445 | zfs::DataSetType::Zfs, 446 | &props, 447 | ) 448 | .unwrap(); 449 | z.snapshot([tmpfs.path().to_owned() + "/1@snap"].iter().cloned()) 450 | .unwrap(); 451 | 452 | z.hold( 453 | [(tmpfs.path().to_owned() + "/1@snap", "test-hold-ok")].iter(), 454 | None, 455 | ) 456 | .unwrap(); 457 | 458 | let holds = z.get_holds(tmpfs.path().to_owned() + "/1@snap").unwrap(); 459 | 460 | let mut expected_holds = std::collections::HashSet::new(); 461 | expected_holds.insert("test-hold-ok"); 462 | for h in holds.as_ref() { 463 | let (v, d) = h.tuple(); 464 | 465 | if let nvpair::NvData::Uint64(_) = d { 466 | // ok 467 | } else { 468 | panic!("unexpected data for hold {:?}: {:?}", v, d); 469 | } 470 | assert_eq!(true, expected_holds.remove(v.to_str().unwrap())); 471 | } 472 | 473 | z.release( 474 | [( 475 | tmpfs.path().to_owned() + "/1@snap", 476 | ["test-hold-ok"].iter().cloned(), 477 | )] 478 | .iter(), 479 | ) 480 | .unwrap(); 481 | } 482 | 483 | #[test] 484 | fn send_recv() { 485 | let tmpfs = TempFs::new("send_recv").unwrap(); 486 | let mut fs1 = tmpfs.path().to_owned(); 487 | fs1.push_str("/"); 488 | let mut fs2 = fs1.clone(); 489 | fs1.push_str("1"); 490 | fs2.push_str("2"); 491 | 492 | let z = zfs::Zfs::new().unwrap(); 493 | let nv = nvpair::NvList::new(); 494 | z.create(&fs1, zfs::DataSetType::Zfs, &nv) 495 | .expect(&format!("create {:?} failed", fs1)); 496 | 497 | assert_eq!(z.exists(&fs1), true); 498 | assert_eq!(z.exists(&fs2), false); 499 | 500 | let mut snap1 = fs1.clone(); 501 | snap1.push_str("@a"); 502 | z.snapshot([snap1.as_str()].iter().cloned()).unwrap(); 503 | 504 | let mut snap2 = fs2.clone(); 505 | snap2.push_str("@b"); 506 | 507 | let mut stream = tempfile::tempfile().unwrap(); 508 | z.send::<_, &str>(&snap1, None, stream.as_raw_fd(), zfs::SendFlags::default()) 509 | .unwrap(); 510 | stream.seek(io::SeekFrom::Start(0)).unwrap(); 511 | z.receive::<_, &str>(&snap2, None, None, false, false, stream.as_raw_fd()) 512 | .unwrap(); 513 | 514 | assert_eq!(z.exists(&fs1), true); 515 | assert_eq!(z.exists(&fs2), true); 516 | assert_eq!(z.exists(&snap1), true); 517 | assert_eq!(z.exists(&snap2), true); 518 | 519 | z.destroy(&snap1).unwrap(); 520 | z.destroy(&snap2).unwrap(); 521 | z.destroy(&fs1).unwrap(); 522 | z.destroy(&fs2).unwrap(); 523 | } 524 | 525 | #[test] 526 | fn rollback() { 527 | let tmpfs = TempFs::new("rollback").unwrap(); 528 | let mut fs1 = tmpfs.path().to_owned(); 529 | fs1.push_str("/"); 530 | fs1.push_str("1"); 531 | 532 | let z = zfs::Zfs::new().unwrap(); 533 | let nv = nvpair::NvList::new(); 534 | z.create(&fs1, zfs::DataSetType::Zfs, &nv) 535 | .expect(&format!("create {:?} failed", fs1)); 536 | 537 | assert_eq!(z.exists(&fs1), true); 538 | 539 | let props = nvpair::NvList::new(); 540 | let mut snap1 = fs1.clone(); 541 | 542 | { 543 | snap1.push_str("@a"); 544 | let mut snaps = nvpair::NvList::new(); 545 | snaps.insert(&snap1, &()).unwrap(); 546 | z.snapshot_raw(&snaps, &props).unwrap(); 547 | } 548 | 549 | let mut snap2 = fs1.clone(); 550 | 551 | { 552 | snap2.push_str("@b"); 553 | let mut snaps = nvpair::NvList::new(); 554 | snaps.insert(&snap2, &()).unwrap(); 555 | z.snapshot_raw(&snaps, &props).unwrap(); 556 | } 557 | 558 | assert_eq!(z.rollback(&fs1).unwrap().to_str().unwrap(), snap2); 559 | 560 | assert_eq!(z.exists(&fs1), true); 561 | assert_eq!(z.exists(&snap1), true); 562 | assert_eq!(z.exists(&snap2), true); 563 | 564 | z.destroy(&snap2).unwrap(); 565 | z.destroy(&snap1).unwrap(); 566 | z.destroy(&fs1).unwrap(); 567 | } 568 | 569 | #[test] 570 | fn rollback_to() { 571 | let tmpfs = TempFs::new("rollback_to").unwrap(); 572 | let mut fs1 = tmpfs.path().to_owned(); 573 | fs1.push_str("/"); 574 | fs1.push_str("1"); 575 | 576 | let z = zfs::Zfs::new().unwrap(); 577 | let nv = nvpair::NvList::new(); 578 | z.create(&fs1, zfs::DataSetType::Zfs, &nv) 579 | .expect(&format!("create {:?} failed", fs1)); 580 | 581 | assert_eq!(z.exists(&fs1), true); 582 | 583 | let mut snap1 = fs1.clone(); 584 | snap1.push_str("@a"); 585 | z.snapshot([&snap1].iter().cloned()).unwrap(); 586 | 587 | let mut snap2 = fs1.clone(); 588 | snap2.push_str("@b"); 589 | z.snapshot([&snap2].iter().cloned()).unwrap(); 590 | 591 | z.destroy(&snap2).unwrap(); 592 | z.rollback_to(&fs1, &snap1).unwrap(); 593 | 594 | assert_eq!(z.exists(&fs1), true); 595 | assert_eq!(z.exists(&snap1), true); 596 | assert_eq!(z.exists(&snap2), false); 597 | 598 | z.destroy(&snap1).unwrap(); 599 | z.destroy(&fs1).unwrap(); 600 | } 601 | #[cfg(features = "v2_00")] 602 | #[test] 603 | fn bootenv() { 604 | let z = zfs::Zfs::new().unwrap(); 605 | let pool = tmp_zpool_name(); 606 | 607 | let r = z.bootenv(pool); 608 | 609 | println!("{:?}", r); 610 | panic!(); 611 | } 612 | 613 | // WARNING: root perms only 614 | #[test] 615 | fn channel_program_nosync_list() { 616 | if !have_root_privs() { 617 | eprintln!("skipping channel_program_nosync_list, need root privs"); 618 | return; 619 | } 620 | 621 | let tmpfs = TempFs::new("channel_program_nosync_list").unwrap(); 622 | let z = zfs::Zfs::new().unwrap(); 623 | 624 | let props = nvpair::NvList::new(); 625 | z.create( 626 | tmpfs.path().to_owned() + "/1", 627 | zfs::DataSetType::Zfs, 628 | &props, 629 | ) 630 | .unwrap(); 631 | 632 | let prgm = r#" 633 | function collect(...) 634 | local arr = {} 635 | local i = 1 636 | for v in ... do 637 | arr[i] = v 638 | i = i + 1 639 | end 640 | return arr 641 | end 642 | 643 | args = ... 644 | return collect(zfs.list.children(args["x"])) 645 | "#; 646 | 647 | let mut args_nv = nvpair::NvList::new(); 648 | args_nv.insert("x", tmpfs.path()).unwrap(); 649 | 650 | let res = z 651 | .channel_program_nosync( 652 | tmp_zpool_name(), 653 | std::ffi::CString::new(prgm).unwrap().as_ref(), 654 | 0xfffff, 655 | 0xfffff, 656 | &args_nv, 657 | ) 658 | .unwrap(); 659 | 660 | let mut expected_children = std::collections::HashSet::new(); 661 | expected_children.insert(tmpfs.path().to_owned() + "/1"); 662 | 663 | for ch in &res { 664 | let (v, d) = ch.tuple(); 665 | if let nvpair::NvData::Bool = d { 666 | } else { 667 | panic!("unexpected data for {:?}: {:?}", v, d); 668 | } 669 | 670 | assert_eq!(expected_children.remove(v.to_str().unwrap()), true); 671 | } 672 | } 673 | -------------------------------------------------------------------------------- /zfs-drr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zfs-drr" 3 | version = "0.1.0" 4 | authors = ["Cody P Schafer "] 5 | edition = "2018" 6 | description = "ZFS DMU Replay Record handling (data for send/recv)" 7 | license = "Apache-2.0 OR MIT" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /zfs-drr/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! DMU Replay Record handling 2 | //! 3 | //! `zfs send` and `zfs receive` exchange data formatted as `dmu_replay_record`s. These 4 | --------------------------------------------------------------------------------