├── .gitignore ├── .travis.yml ├── API.md ├── Cargo.lock ├── Cargo.toml ├── FORMAT.md ├── LICENSE ├── README.md └── src ├── blockdev.rs ├── consts.rs ├── dbus_api.rs ├── dmdevice.rs ├── froyo.rs ├── main.rs ├── mirror.rs ├── raid.rs ├── serialize.rs ├── thin.rs ├── types.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | dist: trusty 4 | 5 | before_install: 6 | - sudo apt-get -qq update 7 | - sudo apt-get install -y libdbus-1-3 libdbus-1-dev 8 | 9 | rust: 10 | - stable 11 | - beta 12 | - nightly 13 | script: 14 | - cargo build --verbose 15 | - | 16 | [ $TRAVIS_RUST_VERSION != nightly ] || 17 | cargo build --verbose --features clippy 18 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | ## Froyo API 2 | 3 | Froyo has a DBus API. The Froyo command-line program uses it, and 4 | other clients can also use it, both to get information and to 5 | configure new or existing Froyo devices. 6 | 7 | The Froyo service registers the name `org.freedesktop.Froyo1` on the 8 | system's DBus System bus. 9 | 10 | ### Root Object path 11 | 12 | `/org/freedesktop/froyo` 13 | 14 | ##### Method: `Create` 15 | 16 | In Args: `Name`(string), `Blockdevs`(array(string)), `Force`(bool) 17 | 18 | Out Args: `FroyodevPath`(string) 19 | 20 | Create a Froyodev from the given blockdevs. Froyo will refuse to 21 | create the device if it thinks data is present on any of the block 22 | devices, unless Force is true. (Force will not override other 23 | creation errors.) 24 | Clients should not set `Force` to `true` without getting a secondary 25 | confirmation of intent that Froyo can overwrite the contents of the 26 | block device. 27 | 28 | Returns the object path of the newly created Froyodev. 29 | 30 | ### Froyodev paths 31 | 32 | `/org/freedesktop/froyo/` 33 | 34 | Each Froyodev present on the system will have an object here based on 35 | its uuid. These can be enumerated using the DBus `ObjectManager` API. 36 | Property changes will cause `PropertiesChanged` signals except where 37 | noted. 38 | 39 | ##### RO Property: `Name` (string) 40 | 41 | The friendly name of the Froyodev. 42 | 43 | ##### Method: `SetName` 44 | 45 | In Args: `NewName`(string) 46 | 47 | Change the friendly name of the Froyo device. 48 | 49 | ##### RO Property: `RemainingSectors` (u64) 50 | ##### RO Property: `TotalSectors` (u64) 51 | 52 | The remaining and total 512-byte sectors that the 53 | Froyodev currently has available for user data. Both of these numbers 54 | may change, as more or less data is stored on the Froyodev, and also 55 | increase or decrease if block devices are added or removed from the 56 | Froyodev. 57 | 58 | Due to frequently changing, these properties do not emit 59 | `PropertyChanged` signals. Clients looking to track almost-full 60 | conditions should track signals from the Status property and look for 61 | write-throttling, as shown by bit 11 of `RunningStatus`. 62 | 63 | ##### RO Property: `Status` (u32) 64 | ##### RO Property: `RunningStatus` (u32) 65 | 66 | `Status` of 0 indicates the Froyodev is successfully online. Bits 67 | set in Status indicate the Froyodev has failed to start due to an 68 | issue with a particular area: 69 | 70 | | Bit | Description 71 | |-----|---------------- 72 | |0-7 |Block device-level failure. Too few valid block devices that make up the Froyodev are present. This `u8` indicates how many more devices are needed to start the Froyodev in non-redundant condition. 73 | |8 |Raid Failure. The redundancy layer has failed. 74 | |9 |ThinPool failure - Meta. Something is wrong with the metadata device or its data. 75 | |10 |ThinPool failure - Data. Something is wrong with the thinpool data device. 76 | |11 |Thin volume failure. An error occurred when creating the thin volume. 77 | |12 |Filesystem failure. The filesystem has experienced a failure. 78 | |13 |Initializing. Not online because it is still initializing. 79 | |14-31|Reserved or unenumerated failure. 80 | 81 | `RunningStatus` is also returned, but will only be valid if 82 | `Status` is 0 -- that is, if the Froyodev is started. If 83 | `RunningStatus` is 0, the Froyodev's operation is normal. Bits set 84 | in `RunningStatus` indicate the device is running non-optimally in 85 | some way: 86 | 87 | | Bit | Description 88 | |-----|---------------- 89 | |0-7 |Missing Block devices. This `u8` indicates how many more devices are needed for full operation. 90 | |8 |Non-redundant. This will likely be set if the above field is nonzero, but may not be if the Froyodev had a redundancy of 2 or greater to start. 91 | |9 |Cannot reshape. The Froyodev is non-redundant and does not have enough free space to re-establish redundancy without additional resources. See the `Reshape` command. 92 | |10 |Reshaping. The Froyodev is currently reshaping. Read and write performance may be affected. 93 | |11 |Throttled. The Froyodev's write speed has been throttled to avoid running out of space. 94 | |12-31|Reserved or unenumerated issue that does not prevent operation. 95 | 96 | ##### RO Property: `BlockDevices` 97 | 98 | `Array(string, u32)` 99 | 100 | The paths of the block devices making up the Froyodev. the `u32` indicates 101 | each block device's status: 102 | 103 | | Value | Description 104 | |-------|---------------- 105 | |0 | Good, in use 106 | |1 | Good, not in use 107 | |2 | Bad 108 | |3 | Not present 109 | 110 | ##### Method: `AddBlockDevice` 111 | 112 | In Args: `BlockDevicePath`(string), `Force`(bool) 113 | 114 | Adds the given block device to the Froyodev. The `Force` parameter's 115 | behavior is similar to its use in the `Create` method. 116 | 117 | ##### Method: `RemoveBlockDevice` 118 | 119 | In Args: `BlockDevicePath`(string), `Wipe` (bool) 120 | 121 | Removes the given block device from the Froyodev. If the block device 122 | was in-use, this will cause the Froyodev to become non-redundant. This 123 | call will not succeed if removing the block device would cause the 124 | Froyodev to fail. 125 | 126 | If `Wipe` is false, then the block device still thinks it is part of 127 | the Froyodev, and may be added back later without a full rebuild. If 128 | `Wipe` is true, the Froyo signature on the block device is wiped, and 129 | re-adding it to the Froyodev will treat it as a never-before-seen 130 | block device. 131 | 132 | ##### Method: `Reshape` 133 | 134 | No In or Out arguments 135 | 136 | The Froyodev will reconfigure itself in the background to operate 137 | redundantly with the block devices it currently has available. This 138 | will likely fail if bit 9 (`Cannot Reshape`) in the `Status` 139 | property's `RunningStatus` field is set. Reshape operation will 140 | begin immediately and will impact the performance of other I/O 141 | operations to the Froyodev. 142 | 143 | After reshape, all bad or not present block devices are no longer 144 | tracked as part of the Froyodev. 145 | -------------------------------------------------------------------------------- /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 = "aho-corasick" 7 | version = "0.5.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.6.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "ae206c860259479b73b3abc98b66da811843056ed570ed79eb95d3d9b2524325" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.1.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 25 | 26 | [[package]] 27 | name = "bitflags" 28 | version = "0.4.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" 31 | 32 | [[package]] 33 | name = "bitflags" 34 | version = "1.3.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 37 | 38 | [[package]] 39 | name = "build_const" 40 | version = "0.2.2" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" 43 | 44 | [[package]] 45 | name = "byteorder" 46 | version = "0.3.13" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "29b2aa490a8f546381308d68fc79e6bd753cd3ad839f7a7172897f1feedfa175" 49 | 50 | [[package]] 51 | name = "bytesize" 52 | version = "0.1.3" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "16d794c5fe594cfa8fbe8ae274de4048176c69f2d9ac5e637166e73b71d460b8" 55 | 56 | [[package]] 57 | name = "cc" 58 | version = "1.0.73" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 61 | 62 | [[package]] 63 | name = "cfg-if" 64 | version = "1.0.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 67 | 68 | [[package]] 69 | name = "chrono" 70 | version = "0.4.19" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 73 | dependencies = [ 74 | "libc", 75 | "num-integer", 76 | "num-traits", 77 | "time", 78 | "winapi 0.3.9", 79 | ] 80 | 81 | [[package]] 82 | name = "clap" 83 | version = "1.3.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "982add9ce9d06b0713b963651564b4754982f1d712719943dd085abd6690cddf" 86 | dependencies = [ 87 | "ansi_term", 88 | "strsim", 89 | ] 90 | 91 | [[package]] 92 | name = "crc" 93 | version = "1.8.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" 96 | dependencies = [ 97 | "build_const", 98 | ] 99 | 100 | [[package]] 101 | name = "custom_derive" 102 | version = "0.1.7" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" 105 | 106 | [[package]] 107 | name = "dbus" 108 | version = "0.3.4" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "94d266a872aaf68b50d02083c429a3686935ab6ab54824290509cdc422673eaf" 111 | dependencies = [ 112 | "libc", 113 | "pkg-config", 114 | ] 115 | 116 | [[package]] 117 | name = "devicemapper" 118 | version = "0.4.7" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "03513f106bb58138deac278c61903f417cbb601b9d3fa93456b7f05c18eb007c" 121 | dependencies = [ 122 | "bitflags 0.4.0", 123 | "custom_derive", 124 | "env_logger", 125 | "libc", 126 | "log 0.3.9", 127 | "newtype_derive", 128 | "nix 0.5.1", 129 | "serde 0.9.15", 130 | ] 131 | 132 | [[package]] 133 | name = "dirs-next" 134 | version = "2.0.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 137 | dependencies = [ 138 | "cfg-if", 139 | "dirs-sys-next", 140 | ] 141 | 142 | [[package]] 143 | name = "dirs-sys-next" 144 | version = "0.1.2" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 147 | dependencies = [ 148 | "libc", 149 | "redox_users", 150 | "winapi 0.3.9", 151 | ] 152 | 153 | [[package]] 154 | name = "env_logger" 155 | version = "0.3.5" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" 158 | dependencies = [ 159 | "log 0.3.9", 160 | "regex", 161 | ] 162 | 163 | [[package]] 164 | name = "froyo" 165 | version = "0.2.0" 166 | dependencies = [ 167 | "byteorder", 168 | "bytesize", 169 | "chrono", 170 | "clap", 171 | "crc", 172 | "dbus", 173 | "devicemapper", 174 | "macro-attr-2018", 175 | "newtype-derive-2018", 176 | "nix 0.23.1", 177 | "serde 1.0.137", 178 | "serde_json", 179 | "term", 180 | "uuid", 181 | ] 182 | 183 | [[package]] 184 | name = "generics" 185 | version = "0.4.4" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "9b68c8c0cd22a26e4524fa3847c0bb2368a824c6d10e40b10b26f88516d7ed8b" 188 | 189 | [[package]] 190 | name = "getrandom" 191 | version = "0.2.7" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 194 | dependencies = [ 195 | "cfg-if", 196 | "libc", 197 | "wasi 0.11.0+wasi-snapshot-preview1", 198 | ] 199 | 200 | [[package]] 201 | name = "itoa" 202 | version = "1.0.2" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 205 | 206 | [[package]] 207 | name = "kernel32-sys" 208 | version = "0.2.2" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 211 | dependencies = [ 212 | "winapi 0.2.8", 213 | "winapi-build", 214 | ] 215 | 216 | [[package]] 217 | name = "libc" 218 | version = "0.2.126" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 221 | 222 | [[package]] 223 | name = "log" 224 | version = "0.3.9" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 227 | dependencies = [ 228 | "log 0.4.17", 229 | ] 230 | 231 | [[package]] 232 | name = "log" 233 | version = "0.4.17" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 236 | dependencies = [ 237 | "cfg-if", 238 | ] 239 | 240 | [[package]] 241 | name = "macro-attr-2018" 242 | version = "2.1.2" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "66fbbf2d3635c91b436074cf14a7c8948dc608e193c9d2b389bdc64aae2ca37b" 245 | 246 | [[package]] 247 | name = "memchr" 248 | version = "0.1.11" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" 251 | dependencies = [ 252 | "libc", 253 | ] 254 | 255 | [[package]] 256 | name = "memoffset" 257 | version = "0.6.5" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 260 | dependencies = [ 261 | "autocfg", 262 | ] 263 | 264 | [[package]] 265 | name = "newtype-derive-2018" 266 | version = "0.0.6" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "0af02b00781848e56676f09b64c3007491cef7c9468992662bc2623fb126895f" 269 | dependencies = [ 270 | "generics", 271 | ] 272 | 273 | [[package]] 274 | name = "newtype_derive" 275 | version = "0.1.6" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "ac8cd24d9f185bb7223958d8c1ff7a961b74b1953fd05dba7cc568a63b3861ec" 278 | dependencies = [ 279 | "rustc_version", 280 | ] 281 | 282 | [[package]] 283 | name = "nix" 284 | version = "0.5.1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" 287 | dependencies = [ 288 | "bitflags 0.4.0", 289 | "libc", 290 | ] 291 | 292 | [[package]] 293 | name = "nix" 294 | version = "0.23.1" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" 297 | dependencies = [ 298 | "bitflags 1.3.2", 299 | "cc", 300 | "cfg-if", 301 | "libc", 302 | "memoffset", 303 | ] 304 | 305 | [[package]] 306 | name = "num-integer" 307 | version = "0.1.45" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 310 | dependencies = [ 311 | "autocfg", 312 | "num-traits", 313 | ] 314 | 315 | [[package]] 316 | name = "num-traits" 317 | version = "0.2.15" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 320 | dependencies = [ 321 | "autocfg", 322 | ] 323 | 324 | [[package]] 325 | name = "pkg-config" 326 | version = "0.3.25" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 329 | 330 | [[package]] 331 | name = "proc-macro2" 332 | version = "1.0.40" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 335 | dependencies = [ 336 | "unicode-ident", 337 | ] 338 | 339 | [[package]] 340 | name = "quote" 341 | version = "1.0.20" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 344 | dependencies = [ 345 | "proc-macro2", 346 | ] 347 | 348 | [[package]] 349 | name = "redox_syscall" 350 | version = "0.2.13" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 353 | dependencies = [ 354 | "bitflags 1.3.2", 355 | ] 356 | 357 | [[package]] 358 | name = "redox_users" 359 | version = "0.4.3" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 362 | dependencies = [ 363 | "getrandom", 364 | "redox_syscall", 365 | "thiserror", 366 | ] 367 | 368 | [[package]] 369 | name = "regex" 370 | version = "0.1.80" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" 373 | dependencies = [ 374 | "aho-corasick", 375 | "memchr", 376 | "regex-syntax", 377 | "thread_local", 378 | "utf8-ranges", 379 | ] 380 | 381 | [[package]] 382 | name = "regex-syntax" 383 | version = "0.3.9" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" 386 | 387 | [[package]] 388 | name = "rustc_version" 389 | version = "0.1.7" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" 392 | dependencies = [ 393 | "semver", 394 | ] 395 | 396 | [[package]] 397 | name = "rustversion" 398 | version = "1.0.7" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" 401 | 402 | [[package]] 403 | name = "ryu" 404 | version = "1.0.10" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" 407 | 408 | [[package]] 409 | name = "semver" 410 | version = "0.1.20" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" 413 | 414 | [[package]] 415 | name = "serde" 416 | version = "0.9.15" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "34b623917345a631dc9608d5194cc206b3fe6c3554cd1c75b937e55e285254af" 419 | 420 | [[package]] 421 | name = "serde" 422 | version = "1.0.137" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" 425 | dependencies = [ 426 | "serde_derive", 427 | ] 428 | 429 | [[package]] 430 | name = "serde_derive" 431 | version = "1.0.137" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" 434 | dependencies = [ 435 | "proc-macro2", 436 | "quote", 437 | "syn", 438 | ] 439 | 440 | [[package]] 441 | name = "serde_json" 442 | version = "1.0.81" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" 445 | dependencies = [ 446 | "itoa", 447 | "ryu", 448 | "serde 1.0.137", 449 | ] 450 | 451 | [[package]] 452 | name = "strsim" 453 | version = "0.4.1" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "0d5f575d5ced6634a5c4cb842163dab907dc7e9148b28dc482d81b8855cbe985" 456 | 457 | [[package]] 458 | name = "syn" 459 | version = "1.0.98" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 462 | dependencies = [ 463 | "proc-macro2", 464 | "quote", 465 | "unicode-ident", 466 | ] 467 | 468 | [[package]] 469 | name = "term" 470 | version = "0.7.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 473 | dependencies = [ 474 | "dirs-next", 475 | "rustversion", 476 | "winapi 0.3.9", 477 | ] 478 | 479 | [[package]] 480 | name = "thiserror" 481 | version = "1.0.31" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" 484 | dependencies = [ 485 | "thiserror-impl", 486 | ] 487 | 488 | [[package]] 489 | name = "thiserror-impl" 490 | version = "1.0.31" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" 493 | dependencies = [ 494 | "proc-macro2", 495 | "quote", 496 | "syn", 497 | ] 498 | 499 | [[package]] 500 | name = "thread-id" 501 | version = "2.0.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" 504 | dependencies = [ 505 | "kernel32-sys", 506 | "libc", 507 | ] 508 | 509 | [[package]] 510 | name = "thread_local" 511 | version = "0.2.7" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" 514 | dependencies = [ 515 | "thread-id", 516 | ] 517 | 518 | [[package]] 519 | name = "time" 520 | version = "0.1.44" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 523 | dependencies = [ 524 | "libc", 525 | "wasi 0.10.0+wasi-snapshot-preview1", 526 | "winapi 0.3.9", 527 | ] 528 | 529 | [[package]] 530 | name = "unicode-ident" 531 | version = "1.0.1" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 534 | 535 | [[package]] 536 | name = "utf8-ranges" 537 | version = "0.1.3" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" 540 | 541 | [[package]] 542 | name = "uuid" 543 | version = "1.1.2" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" 546 | dependencies = [ 547 | "getrandom", 548 | "serde 1.0.137", 549 | ] 550 | 551 | [[package]] 552 | name = "wasi" 553 | version = "0.10.0+wasi-snapshot-preview1" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 556 | 557 | [[package]] 558 | name = "wasi" 559 | version = "0.11.0+wasi-snapshot-preview1" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 562 | 563 | [[package]] 564 | name = "winapi" 565 | version = "0.2.8" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 568 | 569 | [[package]] 570 | name = "winapi" 571 | version = "0.3.9" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 574 | dependencies = [ 575 | "winapi-i686-pc-windows-gnu", 576 | "winapi-x86_64-pc-windows-gnu", 577 | ] 578 | 579 | [[package]] 580 | name = "winapi-build" 581 | version = "0.1.1" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 584 | 585 | [[package]] 586 | name = "winapi-i686-pc-windows-gnu" 587 | version = "0.4.0" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 590 | 591 | [[package]] 592 | name = "winapi-x86_64-pc-windows-gnu" 593 | version = "0.4.0" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 596 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "froyo" 3 | version = "0.2.0" 4 | authors = ["Andy Grover "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | devicemapper = "0.4.7" 9 | clap = "1" 10 | nix = "0.23.0" 11 | crc = "1" 12 | byteorder = "0.3.13" 13 | uuid = { version = "1.1.2", features = ["serde", "v4"] } 14 | chrono = "0.4.19" 15 | serde = { version = "1.0.130", features = ["derive"] } 16 | serde_json = "1.0.72" 17 | bytesize = "0.1.1" 18 | dbus = "0.3" 19 | term = "0.7.0" 20 | newtype-derive-2018 = "0.0.6" 21 | macro-attr-2018 = "2.1.2" 22 | -------------------------------------------------------------------------------- /FORMAT.md: -------------------------------------------------------------------------------- 1 | ## On-disk format for Froyo 2 | 3 | Froyo takes 1MiB (2048 512-byte sectors) from the start and end of each 4 | base block device for its configuration. 5 | 6 | The zones are identical (lengths in 512-byte sectors): 7 | 8 | | offset | length | description 9 | |--------|--------|------- 10 | |0 |1 |unused 11 | |1 |1 |signature block 12 | |2 |6 |unused 13 | |8 |1020 |metadata area A (MDAA) 14 | |1028 |1020 |metadata area B (MDAB) 15 | 16 | 17 | The format of the signature block is (lengths in bytes): 18 | 19 | All values little-endian. 20 | 21 | |offset |length |description 22 | |--------|--------|----------- 23 | |0 |4 |CRC32 of signature block (bytes at offset 4 len 508) 24 | |4 |16 |Froyo signature ```!IamFroy0\x86\xffGO\x02^\x41``` 25 | |20 |8 |Device size in 512-byte sectors (u64) 26 | |28 |4 |flags 27 | |32 |32 |Hex UUID for the block device 28 | |64 |8 |MDAA UNIX timestamp (seconds since Jan 1 1970) (u64) 29 | |72 |4 |MDAA nanoseconds (u32) 30 | |76 |4 |MDAA used length in bytes (u32) 31 | |80 |4 |MDAA CRC32 32 | |84 |12 |unused 33 | |96 |8 |MDAB UNIX timestamp (seconds since Jan 1 1970) (u64) 34 | |104 |4 |MDAB nanoseconds (u32) 35 | |108 |4 |MDAB used length in bytes (u32) 36 | |112 |4 |MDAB CRC32 37 | |116 |12 |unused 38 | |128 |32 |Hex UUID for the associated Froyodev 39 | |160 |352 |unused 40 | 41 | All "flags" or "unused" ranges are zeroed. 42 | 43 | Data within the metadata areas is stored in JSON format. 44 | 45 | Metadata updates write to the older of the MDAA and MDAB areas. This 46 | is determined by lowest timestamp, and then lowest serial if 47 | timestamps are equal. 48 | 49 | First, the JSON metadata is written to either MDAA or MDAB, as 50 | determined above, in both zones. 51 | 52 | Second, the chosen MDA's timestamp, nanosecond, length, and CRC fields 53 | are updated: timestamp gets the UNIX timestamp. Nanoseconds is the 54 | current nanoseconds within the second. Length is set to the length of 55 | the metadata, and the CRC32 is calculated on the data up to that 56 | length. 57 | 58 | (Note: The same timestamp, nanosecond, length, and CRC MDA values must 59 | be used when writing the same metadata to all blockdevs in a 60 | froyodev. Other fields within the sig block may vary, as may which 61 | MDA slot is being updated, if not all blockdevs were added at the same 62 | time.) 63 | 64 | Third, the sig block's CRC32 is calculated. 65 | 66 | Finally, the updated sig block is written to the start and end zones, 67 | in that order. Now would be a good time for a flush/FUA. 68 | 69 | ### JSON Metadata 70 | 71 | Froyo is implemented using layers of devicemapper devices: 72 | 73 | |Layer |Description 74 | |-------|----------- 75 | |0 | Block devices given to Froyo to use 76 | |1 | linear targets that divide blockdev into one or more pairs of raid meta and data devices 77 | |2 | raid5 targets that build redundant storage on top of layer 1 78 | |3 | two linear targets for thin-meta and thin-data, and the thin-pool that uses them (actually two layers) 79 | |4 | Thin volumes allocated out of the thin pool 80 | 81 | Plus additional temporary devices as needed 82 | 83 | ##### Example JSON 84 | 85 | ```json 86 | { 87 | "name": "froyodev-1", 88 | "id": "4a8390f9b22a4c8ba6d38f0de894e8da", 89 | "block_devs": { 90 | "53b754ec804142ca8a6b8752b3a94049": { 91 | "path": "/dev/vdc", 92 | "sectors": 16777216 93 | }, 94 | "8e8d1998f2ad469fbad00038a0843477": { 95 | "path": "/dev/vdb", 96 | "sectors": 16777216 97 | }, 98 | "9bc53cef46a2486abbd99fb92e6ae89e": { 99 | "path": "/dev/vde", 100 | "sectors": 25165824 101 | }, 102 | "df85a23bff4146dd844b45deae37d480": { 103 | "path": "/dev/vdd", 104 | "sectors": 23068672 105 | } 106 | }, 107 | "raid_devs": { 108 | "2a498c1b3ad346c2a0b588b09726bb09": { 109 | "stripe_sectors": 2048, 110 | "region_sectors": 8192, 111 | "length": 50313216, 112 | "members": [ 113 | { 114 | "meta_segments": [ 115 | { 116 | "start": 2048, 117 | "length": 32 118 | } 119 | ], 120 | "data_segments": [ 121 | { 122 | "start": 2080, 123 | "length": 16771072 124 | } 125 | ], 126 | "parent": "53b754ec804142ca8a6b8752b3a94049" 127 | }, 128 | { 129 | "meta_segments": [ 130 | { 131 | "start": 2048, 132 | "length": 32 133 | } 134 | ], 135 | "data_segments": [ 136 | { 137 | "start": 2080, 138 | "length": 16771072 139 | } 140 | ], 141 | "parent": "8e8d1998f2ad469fbad00038a0843477" 142 | }, 143 | { 144 | "meta_segments": [ 145 | { 146 | "start": 2048, 147 | "length": 32 148 | } 149 | ], 150 | "data_segments": [ 151 | { 152 | "start": 2080, 153 | "length": 16771072 154 | } 155 | ], 156 | "parent": "9bc53cef46a2486abbd99fb92e6ae89e" 157 | }, 158 | { 159 | "meta_segments": [ 160 | { 161 | "start": 2048, 162 | "length": 32 163 | } 164 | ], 165 | "data_segments": [ 166 | { 167 | "start": 2080, 168 | "length": 16771072 169 | } 170 | ], 171 | "parent": "df85a23bff4146dd844b45deae37d480" 172 | } 173 | ] 174 | }, 175 | "5ea3c67e31a74ca9b22250e8732fc6eb": { 176 | "stripe_sectors": 2048, 177 | "region_sectors": 8192, 178 | "length": 6291456, 179 | "members": [ 180 | { 181 | "meta_segments": [ 182 | { 183 | "start": 16773152, 184 | "length": 32 185 | } 186 | ], 187 | "data_segments": [ 188 | { 189 | "start": 16773184, 190 | "length": 6291456 191 | } 192 | ], 193 | "parent": "9bc53cef46a2486abbd99fb92e6ae89e" 194 | }, 195 | { 196 | "meta_segments": [ 197 | { 198 | "start": 16773152, 199 | "length": 32 200 | } 201 | ], 202 | "data_segments": [ 203 | { 204 | "start": 16773184, 205 | "length": 6291456 206 | } 207 | ], 208 | "parent": "df85a23bff4146dd844b45deae37d480" 209 | } 210 | ] 211 | } 212 | }, 213 | "thin_pool_dev": { 214 | "data_block_size": 2048, 215 | "low_water_blocks": 512, 216 | "meta_dev": { 217 | "id": "cfab24ed3ca4469ca6b506d25910b077", 218 | "segments": [ 219 | { 220 | "start": 0, 221 | "length": 8192, 222 | "parent": "2a498c1b3ad346c2a0b588b09726bb09" 223 | } 224 | ] 225 | }, 226 | "data_dev": { 227 | "id": "f948ad65a5164d36a03c35f7a59f1efb", 228 | "segments": [ 229 | { 230 | "start": 8192, 231 | "length": 1048576, 232 | "parent": "2a498c1b3ad346c2a0b588b09726bb09" 233 | } 234 | ] 235 | } 236 | }, 237 | "thin_devs": [ 238 | { 239 | "thin_number": 0, 240 | "fs": "xfs", 241 | "size": 2147483648 242 | } 243 | ] 244 | } 245 | ``` 246 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | 375 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/agrover/froyo.svg?branch=master)](https://travis-ci.org/agrover/froyo) 2 | 3 | # Froyo 4 | 5 | Semi-automated bulk storage management 6 | 7 | You give Froyo block devices (hard drives) and it gives you a single 8 | redundant filesystem. You may add any size drive to expand it. When a 9 | drive goes bad, your data is automatically reshaped in the background 10 | across the remaining drives (if space allows). You can replace older, 11 | smaller drives with bigger new ones while the filesystem remains online. 12 | 13 | Drobo + Free + You = Froyo! 14 | 15 | ### Dev Resources 16 | 17 | Use GitHub for issue reporting and Pull Requests. 18 | 19 | IRC: `#froyo` on irc.freenode.org 20 | 21 | Mailing list: `froyo@lists.fedorahosted.org` 22 | 23 | ## Trying it out (Caution highly recommended, may eat data!) 24 | 25 | 1. Compile Froyo 26 | 1. In one terminal, run `froyo -d dev dbus_server`. 27 | 1. In another terminal, use other commands, such as `froyo create`, `froyo list`, 28 | `froyo status `, `froyo add ` and `froyo remove 29 | `. To mount and use froyodevs, mount block devices in `/dev/froyo`. 30 | 31 | ### Things that work 32 | 33 | 1. Create a froyodev 34 | 1. Destroy a froyodev 35 | 1. Deactivate a froyodev 36 | 1. Rename a froyodev 37 | 1. Add another drive to an existing froyodev 38 | 1. Remove a drive from an existing froyodev 39 | 1. Extending thin device when threshold is hit 40 | 1. List froyodevs and get status on a froyodev 41 | 42 | ### Things that are implemented but don't work 43 | 44 | 1. Reshape smaller to re-establish redundancy after a disk is removed 45 | 46 | ### Things that don't yet work 47 | 48 | 1. Reshape bigger to use new disks 49 | 1. Extending filesystem when it nears capacity 50 | 1. Slowing writes to avoid running out of thin data blocks 51 | 1. Automatically starting Froyo service 52 | 1. Automatically checking layers for errors when setting up 53 | -------------------------------------------------------------------------------- /src/blockdev.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | use std::cell::RefCell; 6 | use std::cmp::min; 7 | use std::cmp::Ordering; 8 | use std::collections::BTreeMap; 9 | use std::fs::{read_dir, OpenOptions}; 10 | use std::io; 11 | use std::io::{ErrorKind, Read, Seek, SeekFrom, Write}; 12 | use std::path::{Path, PathBuf}; 13 | use std::rc::{Rc, Weak}; 14 | use std::str::FromStr; 15 | 16 | use byteorder::{ByteOrder, LittleEndian}; 17 | use bytesize::ByteSize; 18 | use chrono::{DateTime, NaiveDateTime, Utc, MIN_DATETIME}; 19 | use crc::crc32; 20 | use devicemapper::{Device, DM}; 21 | use nix::sys::stat; 22 | use uuid::Uuid; 23 | 24 | use crate::consts::*; 25 | use crate::dmdevice::DmDevice; 26 | use crate::types::{FroyoError, FroyoResult, SectorOffset, Sectors, SumSectors}; 27 | use crate::util::blkdev_size; 28 | 29 | pub use crate::serialize::{BlockDevSave, LinearDevSave, LinearSegment}; 30 | 31 | #[derive(Debug, Clone, Copy, PartialEq)] 32 | pub struct Mda { 33 | pub last_updated: DateTime, 34 | length: u32, 35 | crc: u32, 36 | offset: SectorOffset, 37 | } 38 | 39 | #[derive(Debug, Clone, PartialEq)] 40 | pub struct BlockDev { 41 | pub froyodev_id: Uuid, 42 | pub dev: Device, 43 | pub id: Uuid, 44 | pub path: PathBuf, 45 | pub sectors: Sectors, 46 | pub mdaa: Mda, 47 | pub mdab: Mda, 48 | // Key is meta_dev dm name 49 | pub linear_devs: BTreeMap>>, 50 | } 51 | 52 | #[derive(Debug, Clone)] 53 | pub enum BlockMember { 54 | Present(Rc>), 55 | Absent(BlockDevSave), 56 | } 57 | 58 | impl BlockMember { 59 | pub fn present(&self) -> Option>> { 60 | match *self { 61 | BlockMember::Present(ref x) => Some(x.clone()), 62 | BlockMember::Absent(_) => None, 63 | } 64 | } 65 | } 66 | 67 | impl BlockDev { 68 | pub fn new(froyodev_id: Uuid, path: &Path, force: bool) -> FroyoResult { 69 | let pstat = stat::stat(path)?; 70 | 71 | if pstat.st_mode & 0x6000 != 0x6000 { 72 | return Err(FroyoError::Io(io::Error::new( 73 | ErrorKind::InvalidInput, 74 | format!("{} is not a block device", path.display()), 75 | ))); 76 | } 77 | 78 | let dev = Device::from_str(&path.to_string_lossy())?; 79 | 80 | // map_err so we can improve the error message 81 | let mut f = OpenOptions::new().read(true).open(path).map_err(|_| { 82 | io::Error::new( 83 | ErrorKind::PermissionDenied, 84 | format!("Could not open {}", path.display()), 85 | ) 86 | })?; 87 | 88 | if !force { 89 | let mut buf = [0u8; 4096]; 90 | f.read_exact(&mut buf)?; 91 | 92 | if buf.iter().any(|x| *x != 0) { 93 | return Err(FroyoError::Io(io::Error::new( 94 | ErrorKind::InvalidInput, 95 | format!( 96 | "First 4K of {} is not zeroed, need to use --force", 97 | path.display() 98 | ), 99 | ))); 100 | } 101 | } 102 | 103 | let dev_size = blkdev_size(&f)?; 104 | if dev_size < MIN_DEV_SIZE { 105 | return Err(FroyoError::Io(io::Error::new( 106 | ErrorKind::InvalidInput, 107 | format!( 108 | "{} too small, {} minimum", 109 | path.display(), 110 | ByteSize::b(MIN_DEV_SIZE as usize).to_string(true) 111 | ), 112 | ))); 113 | } 114 | 115 | let mut bd = BlockDev { 116 | froyodev_id, 117 | id: Uuid::new_v4(), 118 | dev, 119 | path: path.to_owned(), 120 | sectors: Sectors(dev_size / SECTOR_SIZE), 121 | mdaa: Mda { 122 | last_updated: MIN_DATETIME, 123 | length: 0, 124 | crc: 0, 125 | offset: MDAA_ZONE_OFFSET, 126 | }, 127 | mdab: Mda { 128 | last_updated: MIN_DATETIME, 129 | length: 0, 130 | crc: 0, 131 | offset: MDAB_ZONE_OFFSET, 132 | }, 133 | linear_devs: BTreeMap::new(), 134 | }; 135 | 136 | bd.write_mda_header()?; 137 | 138 | Ok(bd) 139 | } 140 | 141 | pub fn setup(path: &Path) -> FroyoResult { 142 | let dev = Device::from_str(&path.to_string_lossy())?; 143 | 144 | // map_err so we can improve the error message 145 | let mut f = OpenOptions::new().read(true).open(path).map_err(|_| { 146 | io::Error::new( 147 | ErrorKind::PermissionDenied, 148 | format!("Could not open {}", path.display()), 149 | ) 150 | })?; 151 | 152 | let mut buf = [0u8; HEADER_SIZE as usize]; 153 | f.seek(SeekFrom::Start(SECTOR_SIZE))?; 154 | f.read_exact(&mut buf)?; 155 | 156 | if &buf[4..20] != FRO_MAGIC { 157 | return Err(FroyoError::Io(io::Error::new( 158 | ErrorKind::InvalidInput, 159 | format!("{} is not a Froyo device", path.display()), 160 | ))); 161 | } 162 | 163 | let crc = crc32::checksum_ieee(&buf[4..HEADER_SIZE as usize]); 164 | if crc != LittleEndian::read_u32(&buf[..4]) { 165 | dbgp!("{} Froyo header CRC failed", path.display()); 166 | return Err(FroyoError::Io(io::Error::new( 167 | ErrorKind::InvalidInput, 168 | format!("{} Froyo header CRC failed", path.display()), 169 | ))); 170 | // TODO: Try to read end-of-disk copy 171 | } 172 | 173 | let sectors = Sectors(blkdev_size(&f)? / SECTOR_SIZE); 174 | 175 | let id = Uuid::from_slice(&buf[32..64]).unwrap(); 176 | let froyodev_id = Uuid::from_slice(&buf[128..160]).unwrap(); 177 | 178 | Ok(BlockDev { 179 | froyodev_id, 180 | id, 181 | dev, 182 | path: path.to_owned(), 183 | sectors, 184 | mdaa: Mda { 185 | last_updated: DateTime::::from_utc( 186 | NaiveDateTime::from_timestamp( 187 | LittleEndian::read_u64(&buf[64..72]) as i64, 188 | LittleEndian::read_u32(&buf[72..76]), 189 | ), 190 | Utc, 191 | ), 192 | length: LittleEndian::read_u32(&buf[76..80]), 193 | crc: LittleEndian::read_u32(&buf[80..84]), 194 | offset: MDAA_ZONE_OFFSET, 195 | }, 196 | mdab: Mda { 197 | last_updated: DateTime::::from_utc( 198 | NaiveDateTime::from_timestamp( 199 | LittleEndian::read_u64(&buf[96..104]) as i64, 200 | LittleEndian::read_u32(&buf[104..108]), 201 | ), 202 | Utc, 203 | ), 204 | length: LittleEndian::read_u32(&buf[108..112]), 205 | crc: LittleEndian::read_u32(&buf[112..116]), 206 | offset: MDAB_ZONE_OFFSET, 207 | }, 208 | linear_devs: BTreeMap::new(), // Not initialized until metadata is read 209 | }) 210 | } 211 | 212 | pub fn to_save(&self) -> BlockDevSave { 213 | BlockDevSave { 214 | path: self.path.clone(), 215 | sectors: self.sectors, 216 | } 217 | } 218 | 219 | pub fn find_all() -> FroyoResult> { 220 | Ok(read_dir("/dev")? 221 | .into_iter() 222 | .filter_map(|dir_e| { 223 | if let Ok(..) = dir_e { 224 | Some(dir_e.unwrap().path()) 225 | } else { 226 | None 227 | } 228 | }) 229 | .filter_map(|path| BlockDev::setup(&path).ok()) 230 | .collect::>()) 231 | } 232 | 233 | fn used_areas(&self) -> Vec<(SectorOffset, Sectors)> { 234 | // Flag start and end mda zones as used 235 | let mut used = vec![ 236 | (SectorOffset(0), MDA_ZONE_SECTORS), 237 | ( 238 | SectorOffset(*self.sectors - *MDA_ZONE_SECTORS), 239 | MDA_ZONE_SECTORS, 240 | ), 241 | ]; 242 | 243 | for dev in self.linear_devs.values() { 244 | for seg in &dev.borrow().meta_segments { 245 | used.push((seg.start, seg.length)); 246 | } 247 | for seg in &dev.borrow().data_segments { 248 | used.push((seg.start, seg.length)); 249 | } 250 | } 251 | used.sort(); 252 | 253 | used 254 | } 255 | 256 | pub fn avail_areas(&self) -> Vec<(SectorOffset, Sectors)> { 257 | let mut free = Vec::new(); 258 | 259 | // Insert an entry to mark the end so the fold works correctly 260 | let mut used = self.used_areas(); 261 | used.push((SectorOffset(*self.sectors), Sectors(0))); 262 | 263 | used.into_iter() 264 | .fold(SectorOffset(0), |prev_end, (start, len)| { 265 | if prev_end < start { 266 | free.push((prev_end, Sectors(*start - *prev_end))) 267 | } 268 | SectorOffset(*start + *len) 269 | }); 270 | 271 | free 272 | } 273 | 274 | pub fn largest_avail_area(&self) -> Option<(SectorOffset, Sectors)> { 275 | self.avail_areas().into_iter().max_by_key(|&(_, len)| len) 276 | } 277 | 278 | // Read metadata from newest Mda 279 | pub fn read_mdax(&self) -> FroyoResult> { 280 | let younger_mda = match self.mdaa.last_updated.cmp(&self.mdab.last_updated) { 281 | Ordering::Less => &self.mdab, 282 | Ordering::Greater => &self.mdaa, 283 | Ordering::Equal => &self.mdab, 284 | }; 285 | 286 | if younger_mda.last_updated == MIN_DATETIME { 287 | return Err(FroyoError::Io(io::Error::new( 288 | ErrorKind::InvalidInput, 289 | "Neither Mda region is in use", 290 | ))); 291 | } 292 | 293 | let mut f = OpenOptions::new().read(true).open(&self.path)?; 294 | let mut buf = vec![0; younger_mda.length as usize]; 295 | 296 | // read metadata from disk 297 | f.seek(SeekFrom::Start(*younger_mda.offset * SECTOR_SIZE))?; 298 | f.read_exact(&mut buf)?; 299 | 300 | if younger_mda.crc != crc32::checksum_ieee(&buf) { 301 | return Err(FroyoError::Io(io::Error::new( 302 | ErrorKind::InvalidInput, 303 | "Froyo Mda CRC failed", 304 | ))); 305 | // TODO: Read backup copy 306 | } 307 | 308 | Ok(buf) 309 | } 310 | 311 | // Write metadata to least-recently-written Mda 312 | fn write_mdax(&mut self, time: &DateTime, metadata: &[u8]) -> FroyoResult<()> { 313 | let older_mda = match self.mdaa.last_updated.cmp(&self.mdab.last_updated) { 314 | Ordering::Less => &mut self.mdaa, 315 | Ordering::Greater => &mut self.mdab, 316 | Ordering::Equal => &mut self.mdaa, 317 | }; 318 | 319 | if metadata.len() as u64 > *MDAX_ZONE_SECTORS * SECTOR_SIZE { 320 | return Err(FroyoError::Io(io::Error::new( 321 | io::ErrorKind::InvalidInput, 322 | format!("Metadata too large for Mda, {} bytes", metadata.len()), 323 | ))); 324 | } 325 | 326 | older_mda.crc = crc32::checksum_ieee(metadata); 327 | older_mda.length = metadata.len() as u32; 328 | older_mda.last_updated = *time; 329 | 330 | let mut f = OpenOptions::new().write(true).open(&self.path)?; 331 | 332 | // write metadata to disk 333 | f.seek(SeekFrom::Start(*older_mda.offset * SECTOR_SIZE))?; 334 | f.write_all(metadata)?; 335 | f.seek(SeekFrom::End(-(MDA_ZONE_SIZE as i64)))?; 336 | f.seek(SeekFrom::Current((*older_mda.offset * SECTOR_SIZE) as i64))?; 337 | f.write_all(metadata)?; 338 | f.flush()?; 339 | 340 | Ok(()) 341 | } 342 | 343 | fn write_mda_header(&mut self) -> FroyoResult<()> { 344 | let mut buf = [0u8; HEADER_SIZE as usize]; 345 | buf[4..20].clone_from_slice(FRO_MAGIC); 346 | LittleEndian::write_u64(&mut buf[20..28], *self.sectors); 347 | // no flags 348 | buf[32..64].clone_from_slice(self.id.as_bytes()); 349 | 350 | LittleEndian::write_u64(&mut buf[64..72], self.mdaa.last_updated.timestamp() as u64); 351 | LittleEndian::write_u32( 352 | &mut buf[72..76], 353 | self.mdaa.last_updated.timestamp_subsec_nanos(), 354 | ); 355 | LittleEndian::write_u32(&mut buf[76..80], self.mdaa.length); 356 | LittleEndian::write_u32(&mut buf[80..84], self.mdaa.crc); 357 | 358 | LittleEndian::write_u64(&mut buf[96..104], self.mdab.last_updated.timestamp() as u64); 359 | LittleEndian::write_u32( 360 | &mut buf[104..108], 361 | self.mdab.last_updated.timestamp_subsec_nanos(), 362 | ); 363 | LittleEndian::write_u32(&mut buf[108..112], self.mdab.length); 364 | LittleEndian::write_u32(&mut buf[112..116], self.mdab.crc); 365 | 366 | buf[128..160].clone_from_slice(self.froyodev_id.as_bytes()); 367 | 368 | // All done, calc CRC and write 369 | let hdr_crc = crc32::checksum_ieee(&buf[4..HEADER_SIZE as usize]); 370 | LittleEndian::write_u32(&mut buf[..4], hdr_crc); 371 | 372 | BlockDev::write_hdr_buf(&self.path, &buf)?; 373 | 374 | Ok(()) 375 | } 376 | 377 | pub fn wipe_mda_header(&mut self) -> FroyoResult<()> { 378 | let buf = [0u8; HEADER_SIZE as usize]; 379 | BlockDev::write_hdr_buf(&self.path, &buf)?; 380 | Ok(()) 381 | } 382 | 383 | fn write_hdr_buf(path: &Path, buf: &[u8; HEADER_SIZE as usize]) -> FroyoResult<()> { 384 | let mut f = OpenOptions::new().write(true).open(path)?; 385 | let zeroed = [0u8; (SECTOR_SIZE * 8) as usize]; 386 | 387 | // Write 4K header to head & tail. Froyo stuff goes in sector 1. 388 | f.write_all(&zeroed[..SECTOR_SIZE as usize])?; 389 | f.write_all(buf)?; 390 | f.write_all(&zeroed[(SECTOR_SIZE * 2) as usize..])?; 391 | f.seek(SeekFrom::End(-(MDA_ZONE_SIZE as i64)))?; 392 | f.write_all(&zeroed[..SECTOR_SIZE as usize])?; 393 | f.write_all(buf)?; 394 | f.write_all(&zeroed[(SECTOR_SIZE * 2) as usize..])?; 395 | f.flush()?; 396 | 397 | Ok(()) 398 | } 399 | 400 | pub fn save_state(&mut self, time: &DateTime, metadata: &[u8]) -> FroyoResult<()> { 401 | self.write_mdax(time, metadata)?; 402 | self.write_mda_header()?; 403 | 404 | Ok(()) 405 | } 406 | 407 | /// Get the "x:y" device string for this blockdev 408 | pub fn dstr(&self) -> String { 409 | format!("{}:{}", self.dev.major, self.dev.minor) 410 | } 411 | 412 | // Find some sector ranges that could be allocated. If more 413 | // sectors are needed than our capacity, return partial results. 414 | pub fn get_some_space(&self, size: Sectors) -> (Sectors, Vec<(SectorOffset, Sectors)>) { 415 | let mut segs = Vec::new(); 416 | let mut needed = size; 417 | 418 | for (start, len) in self.avail_areas() { 419 | if needed == Sectors(0) { 420 | break; 421 | } 422 | 423 | let to_use = min(needed, len); 424 | 425 | segs.push((start, to_use)); 426 | needed = needed - to_use; 427 | } 428 | 429 | (size - needed, segs) 430 | } 431 | } 432 | 433 | #[derive(Debug, Clone)] 434 | pub struct BlockDevs(pub BTreeMap); 435 | 436 | impl BlockDevs { 437 | pub fn to_save(&self) -> BTreeMap { 438 | self.0 439 | .iter() 440 | .map(|(id, bd)| match *bd { 441 | BlockMember::Present(ref bd) => (*id, bd.borrow().to_save()), 442 | BlockMember::Absent(ref sbd) => (*id, sbd.clone()), 443 | }) 444 | .collect() 445 | } 446 | 447 | pub fn wipe(&mut self) -> FroyoResult<()> { 448 | for bd in self.0.values().filter_map(|bm| bm.present()) { 449 | if let Err(e) = bd.borrow_mut().wipe_mda_header() { 450 | // keep going! 451 | dbgp!("Error when wiping header: {}", e); 452 | } 453 | } 454 | 455 | Ok(()) 456 | } 457 | 458 | // Unused (non-redundant) space left on blockdevs 459 | pub fn unused_space(&self) -> Sectors { 460 | self.avail_areas() 461 | .iter() 462 | .map(|&(_, _, len)| len) 463 | .sum_sectors() 464 | } 465 | 466 | pub fn avail_areas(&self) -> Vec<(Rc>, SectorOffset, Sectors)> { 467 | self.0 468 | .values() 469 | .filter_map(|bd| bd.present()) 470 | .flat_map(|bd| { 471 | let areas = bd.borrow().avail_areas(); 472 | areas 473 | .into_iter() 474 | .map(|(offset, len)| (bd.clone(), offset, len)) 475 | .collect::>() 476 | }) 477 | .collect() 478 | } 479 | 480 | pub fn get_linear_segments( 481 | &self, 482 | size: Sectors, 483 | ) -> Option>, LinearSegment)>> { 484 | let mut needed: Sectors = size; 485 | let mut segs = Vec::new(); 486 | 487 | for bd in self.0.values().filter_map(|bm| bm.present()) { 488 | if needed == Sectors(0) { 489 | break; 490 | } 491 | 492 | let (gotten, r_segs) = bd.borrow().get_some_space(needed); 493 | segs.extend( 494 | r_segs 495 | .iter() 496 | .map(|&(start, len)| (bd.clone(), LinearSegment::new(start, len))), 497 | ); 498 | needed = needed - gotten; 499 | } 500 | 501 | match *needed { 502 | 0 => Some(segs), 503 | _ => None, 504 | } 505 | } 506 | } 507 | 508 | impl LinearSegment { 509 | pub fn new(start: SectorOffset, length: Sectors) -> LinearSegment { 510 | LinearSegment { start, length } 511 | } 512 | } 513 | 514 | // A LinearDev contains two mappings within a single blockdev. This is 515 | // primarily used for making RaidDevs. 516 | #[derive(Debug, Clone)] 517 | pub struct LinearDev { 518 | pub meta_dev: DmDevice, 519 | meta_segments: Vec, 520 | pub data_dev: DmDevice, 521 | data_segments: Vec, 522 | pub parent: Weak>, 523 | } 524 | 525 | impl PartialEq for LinearDev { 526 | fn eq(&self, other: &LinearDev) -> bool { 527 | self.meta_dev == other.meta_dev 528 | && self.meta_segments == other.meta_segments 529 | && self.data_dev == other.data_dev 530 | && self.data_segments == other.data_segments 531 | && self.parent.upgrade().unwrap() == other.parent.upgrade().unwrap() 532 | } 533 | } 534 | 535 | impl LinearDev { 536 | pub fn new( 537 | dm: &DM, 538 | name: &str, 539 | blockdev: &Rc>, 540 | meta_segments: &[LinearSegment], 541 | data_segments: &[LinearSegment], 542 | ) -> FroyoResult { 543 | let ld = Self::setup(dm, name, blockdev, meta_segments, data_segments)?; 544 | ld.meta_dev.clear()?; 545 | Ok(ld) 546 | } 547 | 548 | pub fn setup( 549 | dm: &DM, 550 | name: &str, 551 | blockdev: &Rc>, 552 | meta_segments: &[LinearSegment], 553 | data_segments: &[LinearSegment], 554 | ) -> FroyoResult { 555 | let dev = blockdev.borrow().dev; 556 | 557 | // meta 558 | let mut table = Vec::new(); 559 | let mut offset = SectorOffset(0); 560 | for seg in meta_segments { 561 | let line = ( 562 | *offset, 563 | *seg.length, 564 | "linear", 565 | format!("{}:{} {}", dev.major, dev.minor, *seg.start), 566 | ); 567 | table.push(line); 568 | offset = offset + SectorOffset(*seg.length); 569 | } 570 | 571 | let meta_dm_name = format!("froyo-linear-meta-{}", name); 572 | let meta_dev = DmDevice::new(dm, &meta_dm_name, &*table)?; 573 | 574 | // data 575 | let mut table = Vec::new(); 576 | let mut offset = SectorOffset(0); 577 | for seg in data_segments { 578 | let line = ( 579 | *offset, 580 | *seg.length, 581 | "linear", 582 | format!("{}:{} {}", dev.major, dev.minor, *seg.start), 583 | ); 584 | table.push(line); 585 | offset = offset + SectorOffset(*seg.length); 586 | } 587 | 588 | let data_dm_name = format!("froyo-linear-data-{}", name); 589 | let data_dev = DmDevice::new(dm, &data_dm_name, &table)?; 590 | 591 | Ok(LinearDev { 592 | meta_dev, 593 | meta_segments: meta_segments.to_vec(), 594 | data_dev, 595 | data_segments: data_segments.to_vec(), 596 | parent: Rc::downgrade(blockdev), 597 | }) 598 | } 599 | 600 | pub fn teardown(&self, dm: &DM) -> FroyoResult<()> { 601 | self.meta_dev.teardown(dm)?; 602 | self.data_dev.teardown(dm)?; 603 | Ok(()) 604 | } 605 | 606 | pub fn to_save(&self) -> LinearDevSave { 607 | let p_rc = self.parent.upgrade().unwrap(); 608 | let parent = p_rc.borrow().id; 609 | LinearDevSave { 610 | meta_segments: self.meta_segments.clone(), 611 | data_segments: self.data_segments.clone(), 612 | parent, 613 | } 614 | } 615 | 616 | pub fn metadata_length(&self) -> Sectors { 617 | self.meta_segments.iter().map(|x| x.length).sum_sectors() 618 | } 619 | 620 | pub fn data_length(&self) -> Sectors { 621 | self.data_segments.iter().map(|x| x.length).sum_sectors() 622 | } 623 | } 624 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | use crate::types::{SectorOffset, Sectors}; 6 | 7 | pub const DBUS_TIMEOUT: i32 = 20000; // millieconds 8 | 9 | pub const REDUNDANCY: usize = 1; 10 | pub const MIN_BLK_DEVS: usize = 2; 11 | 12 | const MEGA: u64 = 1024 * 1024; 13 | const GIGA: u64 = MEGA * 1024; 14 | const TERA: u64 = GIGA * 1024; 15 | 16 | // Before increasing, either check if md-raid is good for wider, or 17 | // change zone creation to limit width 18 | pub const MAX_BLK_DEVS: usize = 8; 19 | 20 | pub const SECTOR_SIZE: u64 = 512; 21 | pub const HEADER_SIZE: u64 = 512; 22 | pub const MDA_ZONE_SIZE: u64 = MEGA; 23 | pub const MDA_ZONE_SECTORS: Sectors = Sectors(MDA_ZONE_SIZE / SECTOR_SIZE); 24 | pub const MDAX_ZONE_SECTORS: Sectors = Sectors(1020); 25 | pub const MDAA_ZONE_OFFSET: SectorOffset = SectorOffset(8); 26 | pub const MDAB_ZONE_OFFSET: SectorOffset = SectorOffset(1028); 27 | 28 | pub const FRO_MAGIC: &[u8] = b"!IamFroy0\x86\xffGO\x02^\x41"; 29 | pub const STRIPE_SECTORS: Sectors = Sectors(MEGA / SECTOR_SIZE); 30 | 31 | // No devs smaller than around a gig 32 | const MIN_DATA_ZONE_SIZE: u64 = GIGA; 33 | pub const MIN_DATA_ZONE_SECTORS: Sectors = Sectors(MIN_DATA_ZONE_SIZE / SECTOR_SIZE); 34 | pub const MIN_DEV_SIZE: u64 = MIN_DATA_ZONE_SIZE + (2 * MDA_ZONE_SIZE); 35 | 36 | // But also no larger than 1 TiB 37 | const MAX_DATA_ZONE_SIZE: u64 = TERA; 38 | pub const MAX_DATA_ZONE_SECTORS: Sectors = Sectors(MAX_DATA_ZONE_SIZE / SECTOR_SIZE); 39 | 40 | //const MIN_DEV_SECTORS: u64 = MIN_DEV_SIZE / SECTOR_SIZE; 41 | 42 | pub const MAX_REGIONS: u64 = 2 * MEGA; 43 | const DEFAULT_REGION_SIZE: u64 = 4 * MEGA; 44 | pub const DEFAULT_REGION_SECTORS: Sectors = Sectors(DEFAULT_REGION_SIZE / SECTOR_SIZE); 45 | 46 | // Try to create this many raids in a Froyodev. Too many is wasteful 47 | // but too few limits reshape options (at least until dn-raid supports 48 | // reshape) 49 | pub const IDEAL_RAID_COUNT: usize = 10; 50 | 51 | pub const DATA_BLOCK_SIZE: Sectors = Sectors(MEGA / SECTOR_SIZE); 52 | pub const TPOOL_LOW_WATER_BLOCKS: u64 = 512; // 512MiB 53 | 54 | pub const TPOOL_INITIAL_META_SECTORS: Sectors = Sectors(4 * MEGA / SECTOR_SIZE); 55 | pub const TPOOL_INITIAL_DATA_SECTORS: Sectors = Sectors(2 * GIGA / SECTOR_SIZE); 56 | pub const TPOOL_EXTEND_SECTORS: Sectors = Sectors(GIGA / SECTOR_SIZE); 57 | 58 | pub const THIN_INITIAL_SECTORS: Sectors = Sectors(128 * GIGA / SECTOR_SIZE); 59 | -------------------------------------------------------------------------------- /src/dbus_api.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | use std::cell::RefCell; 6 | use std::path::{Path, PathBuf}; 7 | use std::rc::Rc; 8 | use std::sync::Arc; 9 | 10 | use dbus; 11 | use dbus::tree::{EmitsChangedSignal, Factory, Interface, MethodErr, MethodFn, Property, Tree}; 12 | use dbus::MessageItem; 13 | use dbus::{Connection, NameFlag}; 14 | 15 | use crate::blockdev::{BlockDevs, BlockMember}; 16 | use crate::froyo::Froyo; 17 | use crate::types::{FroyoError, FroyoResult}; 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct DbusContext<'a> { 21 | name_prop: Arc>>, 22 | pub remaining_prop: Arc>>, 23 | pub total_prop: Arc>>, 24 | pub status_prop: Arc>>, 25 | pub running_status_prop: Arc>>, 26 | pub block_devices_prop: Arc>>, 27 | } 28 | 29 | impl<'a> DbusContext<'a> { 30 | pub fn update_one(prop: &Arc>>, m: MessageItem) -> FroyoResult<()> { 31 | match prop.set_value(m) { 32 | Ok(_) => Ok(()), // TODO: return signals 33 | Err(()) => Err(FroyoError::Dbus(dbus::Error::new_custom( 34 | "UpdateError", 35 | "Could not update property with value", 36 | ))), 37 | } 38 | } 39 | 40 | pub fn get_block_devices_msgitem(block_devs: &BlockDevs) -> MessageItem { 41 | let mut msg_vec = Vec::new(); 42 | for bd in block_devs.0.values() { 43 | let (bd_path, bd_status) = match *bd { 44 | BlockMember::Present(ref bd) => { 45 | let bd = bd.borrow(); 46 | let status = match bd.linear_devs.len() { 47 | 0 => 1u32, // not in use 48 | _ => 0u32, // in use 49 | }; 50 | (bd.path.to_string_lossy().into_owned(), status) 51 | } 52 | BlockMember::Absent(ref sbd) => (sbd.path.to_string_lossy().into_owned(), 3u32), 53 | }; 54 | 55 | let entry = MessageItem::Struct(vec![bd_path.into(), bd_status.into()]); 56 | msg_vec.push(entry); 57 | } 58 | 59 | MessageItem::new_array(msg_vec).expect("Froyodev with no blockdev members???") 60 | } 61 | } 62 | 63 | fn froyo_interface<'a>(froyo: &Rc>>) -> Interface> { 64 | let f = Factory::new_fn(); 65 | let mut iface = f.interface("org.freedesktop.FroyoDevice1"); 66 | let name_p = iface.add_p_ref(f.property("Name", froyo.borrow().name.to_owned())); 67 | let p_closed_over = name_p.clone(); 68 | let froyo_closed_over = froyo.clone(); 69 | let mut iface = iface.add_m( 70 | f.method("SetName", move |m, _, _| { 71 | let mut items = m.get_items(); 72 | if items.is_empty() { 73 | return Err(MethodErr::no_arg()); 74 | } 75 | 76 | let name = items.pop().ok_or_else(MethodErr::no_arg).and_then(|i| { 77 | i.inner::<&str>() 78 | .map_err(|_| MethodErr::invalid_arg(&i)) 79 | .map(|i| i.to_owned()) 80 | })?; 81 | 82 | let mut froyo = froyo_closed_over.borrow_mut(); 83 | froyo.name = name.clone(); 84 | froyo.save_state().map_err(|err| { 85 | let msg = format!("Saving state failed: {}", err); 86 | MethodErr::failed(&msg) 87 | })?; 88 | 89 | p_closed_over 90 | .set_value(name.into()) 91 | .map_err(|_| MethodErr::invalid_arg(&"name"))?; 92 | Ok(vec![m.method_return()]) 93 | }) 94 | .in_arg(("new_name", "s")), 95 | ); 96 | 97 | let rem_p = iface.add_p_ref( 98 | f.property("RemainingSectors", 0u64) 99 | .emits_changed(EmitsChangedSignal::False), 100 | ); 101 | let tot_p = iface.add_p_ref( 102 | f.property("TotalSectors", 0u64) 103 | .emits_changed(EmitsChangedSignal::False), 104 | ); 105 | let status_p = iface.add_p_ref(f.property("Status", 0u32)); 106 | let running_status_p = iface.add_p_ref(f.property("RunningStatus", 0u32)); 107 | 108 | let froyo_closed_over = froyo.clone(); 109 | let iface = iface.add_m( 110 | f.method("AddBlockDevice", move |m, _, _| { 111 | let mut items = m.get_items(); 112 | if items.len() < 2 { 113 | return Err(MethodErr::no_arg()); 114 | } 115 | 116 | let force: bool = items 117 | .pop() 118 | .ok_or_else(MethodErr::no_arg) 119 | .and_then(|i| i.inner().map_err(|_| MethodErr::invalid_arg(&i)))?; 120 | 121 | let new_dev = items.pop().ok_or_else(MethodErr::no_arg).and_then(|i| { 122 | i.inner::<&str>() 123 | .map_err(|_| MethodErr::invalid_arg(&i)) 124 | .map(|i| i.to_owned()) 125 | })?; 126 | 127 | let mut froyo = froyo_closed_over.borrow_mut(); 128 | froyo 129 | .add_block_device(Path::new(&new_dev), force) 130 | .map_err(|err| { 131 | let msg = format!("Adding block device failed: {}", err); 132 | MethodErr::failed(&msg) 133 | })?; 134 | froyo.save_state().map_err(|err| { 135 | let msg = format!("Saving state failed: {}", err); 136 | MethodErr::failed(&msg) 137 | })?; 138 | Ok(vec![m.method_return()]) 139 | }) 140 | .in_arg(("device_path", "s")) 141 | .in_arg(("force", "b")), 142 | ); 143 | 144 | let froyo_closed_over = froyo.clone(); 145 | let iface = iface.add_m( 146 | f.method("RemoveBlockDevice", move |m, _, _| { 147 | let mut items = m.get_items(); 148 | if items.is_empty() { 149 | return Err(MethodErr::no_arg()); 150 | } 151 | 152 | let wipe: bool = items 153 | .pop() 154 | .ok_or_else(MethodErr::no_arg) 155 | .and_then(|i| i.inner().map_err(|_| MethodErr::invalid_arg(&i)))?; 156 | 157 | let removing_dev = items.pop().ok_or_else(MethodErr::no_arg).and_then(|i| { 158 | i.inner::<&str>() 159 | .map_err(|_| MethodErr::invalid_arg(&i)) 160 | .map(|i| i.to_owned()) 161 | })?; 162 | 163 | let mut froyo = froyo_closed_over.borrow_mut(); 164 | froyo 165 | .remove_block_device(Path::new(&removing_dev), wipe) 166 | .map_err(|err| { 167 | let msg = format!("Removing block device failed: {}", err); 168 | MethodErr::failed(&msg) 169 | })?; 170 | froyo.save_state().map_err(|err| { 171 | let msg = format!("Saving state failed: {}", err); 172 | MethodErr::failed(&msg) 173 | })?; 174 | Ok(vec![m.method_return()]) 175 | }) 176 | .in_arg(("device_path", "s")) 177 | .in_arg(("wipe", "b")), 178 | ); 179 | 180 | let froyo_closed_over = froyo.clone(); 181 | let mut iface = iface.add_m(f.method("Reshape", move |m, _, _| { 182 | let mut froyo = froyo_closed_over.borrow_mut(); 183 | froyo.reshape().map_err(|err| { 184 | let msg = format!("Reshape failed: {}", err); 185 | MethodErr::failed(&msg) 186 | })?; 187 | Ok(vec![m.method_return()]) 188 | })); 189 | 190 | let mut froyo = froyo.borrow_mut(); 191 | 192 | // Need to actually get values b/c I can't figure out how to 193 | // get a 0-length array of struct 194 | let bdev_msg = DbusContext::get_block_devices_msgitem(&froyo.block_devs); 195 | let block_devices_p = iface.add_p_ref(f.property("BlockDevices", bdev_msg)); 196 | 197 | froyo.dbus_context = Some(DbusContext { 198 | name_prop: name_p, 199 | remaining_prop: rem_p, 200 | total_prop: tot_p, 201 | status_prop: status_p, 202 | running_status_prop: running_status_p, 203 | block_devices_prop: block_devices_p, 204 | }); 205 | 206 | iface 207 | } 208 | 209 | pub fn get_base_tree<'a>( 210 | c: &'a Connection, 211 | froyos: &mut Rc>>>>>, 212 | child_tree: &Rc>>>, 213 | ) -> FroyoResult>> { 214 | c.register_name("org.freedesktop.Froyo1", NameFlag::ReplaceExisting as u32) 215 | .unwrap(); 216 | 217 | let f = Factory::new_fn(); 218 | 219 | let base_tree = f.tree(); 220 | 221 | let tree_closed_over = child_tree.clone(); 222 | let froyos_closed_over = froyos.clone(); 223 | let create_method = f 224 | .method("Create", move |m, _, _| { 225 | let f = Factory::new_fn(); 226 | let mut items = m.get_items(); 227 | if items.len() < 3 { 228 | return Err(MethodErr::no_arg()); 229 | } 230 | 231 | let force: bool = items 232 | .pop() 233 | .ok_or_else(MethodErr::no_arg) 234 | .and_then(|i| i.inner().map_err(|_| MethodErr::invalid_arg(&i)))?; 235 | let blockdevs = match items.pop().ok_or_else(MethodErr::no_arg)? { 236 | MessageItem::Array(x, _) => x, 237 | x => return Err(MethodErr::invalid_arg(&x)), 238 | }; 239 | let blockdevs = blockdevs 240 | .into_iter() 241 | .map(|x| PathBuf::from(x.inner::<&str>().unwrap())) 242 | .collect::>(); 243 | 244 | let name = items.pop().ok_or_else(MethodErr::no_arg).and_then(|i| { 245 | i.inner::<&str>() 246 | .map_err(|_| MethodErr::invalid_arg(&i)) 247 | .map(|i| i.to_owned()) 248 | })?; 249 | 250 | let froyo = match Froyo::new(&name, &blockdevs, force) { 251 | Ok(x) => x, 252 | Err(err) => { 253 | let msg = format!("Froyo create failed: {}", err); 254 | return Err(MethodErr::failed(&msg)); 255 | } 256 | }; 257 | froyo.save_state().map_err(|err| { 258 | let msg = format!("Saving state failed: {}", err); 259 | MethodErr::failed(&msg) 260 | })?; 261 | 262 | let froyo = Rc::new(RefCell::new(froyo)); 263 | 264 | let path = format!("/org/freedesktop/froyodevs/{}", froyo.borrow().id); 265 | let obj_path = f 266 | .object_path(path.clone()) 267 | .introspectable() 268 | .add(froyo_interface(&froyo)); 269 | 270 | froyo.borrow().update_dbus().map_err(|err| { 271 | let msg = format!("Updating DBus failed: {}", err); 272 | MethodErr::failed(&msg) 273 | })?; 274 | 275 | c.register_object_path(&path).map_err(|err| { 276 | let msg = format!("registering object path failed: {}", err); 277 | MethodErr::failed(&msg) 278 | })?; 279 | tree_closed_over.borrow_mut().add_o_ref(obj_path); 280 | 281 | let mr = m.method_return().append(path); 282 | 283 | froyos_closed_over.borrow_mut().push(froyo); 284 | Ok(vec![mr]) 285 | }) 286 | .in_arg(("name", "s")) 287 | .in_arg(("blockdevs", "as")) 288 | .in_arg(("force", "b")) 289 | .out_arg(("obj_path", "s")); 290 | 291 | let tree_closed_over = child_tree.clone(); 292 | let froyos_closed_over = froyos.clone(); 293 | let destroy_method = f 294 | .method("Destroy", move |m, _, _| { 295 | let mut items = m.get_items(); 296 | if items.is_empty() { 297 | return Err(MethodErr::no_arg()); 298 | } 299 | 300 | let name = items.pop().ok_or_else(MethodErr::no_arg).and_then(|i| { 301 | i.inner::<&str>() 302 | .map_err(|_| MethodErr::invalid_arg(&i)) 303 | .map(|i| i.to_owned()) 304 | })?; 305 | 306 | let mut froyos = froyos_closed_over.borrow_mut(); 307 | let mut froyos_to_destroy = froyos 308 | .iter() 309 | .enumerate() 310 | .filter_map(|(idx, f)| { 311 | if f.borrow().name == name { 312 | Some((f.clone(), idx)) 313 | } else { 314 | None 315 | } 316 | }) 317 | .collect::>(); 318 | 319 | let (froyo, idx) = match froyos_to_destroy.len() { 320 | 0 => return Err(MethodErr::failed(&format!("Froyodev {} not found", name))), 321 | 1 => froyos_to_destroy.pop().unwrap(), 322 | _ => { 323 | return Err(MethodErr::failed(&format!( 324 | "Multiple Froydevs found with name: {}. \ 325 | Specify froyodev uuid", 326 | name 327 | ))) 328 | } 329 | }; 330 | 331 | let name = format!("/org/freedesktop/froyodevs/{}", froyo.borrow().id); 332 | c.unregister_object_path(&name); 333 | tree_closed_over.borrow_mut().remove(&name.into()); 334 | 335 | froyo.borrow_mut().destroy().map_err(|err| { 336 | let msg = format!("Destroying Froyodev failed: {}", err); 337 | MethodErr::failed(&msg) 338 | })?; 339 | 340 | froyos.remove(idx); 341 | 342 | Ok(vec![m.method_return()]) 343 | }) 344 | .in_arg(("name", "s")); 345 | 346 | let tree_closed_over = child_tree.clone(); 347 | let froyos_closed_over = froyos.clone(); 348 | let teardown_method = f 349 | .method("Teardown", move |m, _, _| { 350 | let mut items = m.get_items(); 351 | if items.is_empty() { 352 | return Err(MethodErr::no_arg()); 353 | } 354 | 355 | let name = items.pop().ok_or_else(MethodErr::no_arg).and_then(|i| { 356 | i.inner::<&str>() 357 | .map_err(|_| MethodErr::invalid_arg(&i)) 358 | .map(|i| i.to_owned()) 359 | })?; 360 | 361 | let mut froyos = froyos_closed_over.borrow_mut(); 362 | let mut froyos_to_teardown = froyos 363 | .iter() 364 | .enumerate() 365 | .filter_map(|(idx, f)| { 366 | if f.borrow().name == name { 367 | Some((f.clone(), idx)) 368 | } else { 369 | None 370 | } 371 | }) 372 | .collect::>(); 373 | 374 | let (froyo, idx) = match froyos_to_teardown.len() { 375 | 0 => return Err(MethodErr::failed(&format!("Froyodev {} not found", name))), 376 | 1 => froyos_to_teardown.pop().unwrap(), 377 | _ => { 378 | return Err(MethodErr::failed(&format!( 379 | "Multiple Froydevs found with name: {}. \ 380 | Specify froyodev uuid", 381 | name 382 | ))) 383 | } 384 | }; 385 | 386 | let name = format!("/org/freedesktop/froyodevs/{}", froyo.borrow().id); 387 | c.unregister_object_path(&name); 388 | tree_closed_over.borrow_mut().remove(&name.into()); 389 | 390 | froyo.borrow_mut().teardown().map_err(|err| { 391 | let msg = format!("Tearing down Froyodev failed: {}", err); 392 | MethodErr::failed(&msg) 393 | })?; 394 | 395 | froyos.remove(idx); 396 | 397 | Ok(vec![m.method_return()]) 398 | }) 399 | .in_arg(("name", "s")); 400 | 401 | let obj_path = f 402 | .object_path("/org/freedesktop/froyo") 403 | .introspectable() 404 | .object_manager() 405 | .add( 406 | f.interface("org.freedesktop.FroyoService1") 407 | .add_m(create_method) 408 | .add_m(destroy_method) 409 | .add_m(teardown_method), 410 | ); 411 | 412 | let base_tree = base_tree.add(obj_path); 413 | base_tree.set_registered(c, true)?; 414 | 415 | Ok(base_tree) 416 | } 417 | 418 | pub fn get_child_tree<'a>( 419 | c: &'a Connection, 420 | froyos: &[Rc>>], 421 | ) -> FroyoResult>>>> { 422 | let f = Factory::new_fn(); 423 | 424 | let tree = froyos.iter().fold(f.tree(), |tree, froyo| { 425 | let path = format!("/org/freedesktop/froyodevs/{}", froyo.borrow().id); 426 | let obj_path = f 427 | .object_path(path) 428 | .introspectable() 429 | .add(froyo_interface(froyo)); 430 | tree.add(obj_path) 431 | }); 432 | 433 | let tree = tree.add( 434 | f.object_path("/org/freedesktop/froyodevs") 435 | .introspectable() 436 | .object_manager(), 437 | ); 438 | 439 | tree.set_registered(c, true)?; 440 | 441 | for froyo in &*froyos { 442 | froyo.borrow().update_dbus()?; 443 | } 444 | 445 | Ok(Rc::new(RefCell::new(tree))) 446 | } 447 | -------------------------------------------------------------------------------- /src/dmdevice.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | use std::borrow::Borrow; 6 | use std::fs::OpenOptions; 7 | use std::io; 8 | use std::io::{ErrorKind, Write}; 9 | 10 | use devicemapper::dm::{DevId, DM}; 11 | use devicemapper::{consts::DmFlags, consts::DM_SUSPEND, types::TargetLine, Device}; 12 | 13 | use crate::consts::*; 14 | use crate::types::{FroyoError, FroyoResult}; 15 | use crate::util::blkdev_size; 16 | 17 | #[derive(Debug, Clone, PartialEq)] 18 | pub struct DmDevice { 19 | pub dm_name: String, 20 | pub dev: Device, 21 | } 22 | 23 | impl DmDevice { 24 | pub fn new(dm: &DM, name: &str, table: &[(u64, u64, T1, T2)]) -> FroyoResult 25 | where 26 | T1: Borrow, 27 | T2: Borrow, 28 | { 29 | let id = &DevId::Name(name); 30 | 31 | let di = match dm.device_status(id) { 32 | Ok(di) => { 33 | dbgp!("Found {}", name); 34 | di 35 | } 36 | Err(_) => { 37 | dm.device_create(name, None, DmFlags::empty())?; 38 | let di = dm.table_load(id, table)?; 39 | dm.device_suspend(id, DmFlags::empty())?; 40 | 41 | dbgp!("Created {}", name); 42 | di 43 | } 44 | }; 45 | 46 | Ok(DmDevice { 47 | dm_name: name.to_owned(), 48 | dev: di.device(), 49 | }) 50 | } 51 | 52 | pub fn dstr(&self) -> String { 53 | format!("{}:{}", self.dev.major, self.dev.minor) 54 | } 55 | 56 | pub fn reload(&self, dm: &DM, table: &[(u64, u64, T1, T2)]) -> FroyoResult<()> 57 | where 58 | T1: Borrow, 59 | T2: Borrow, 60 | { 61 | let id = &DevId::Name(&self.dm_name); 62 | 63 | dm.table_load(id, table)?; 64 | dm.device_suspend(id, DM_SUSPEND)?; 65 | dm.device_suspend(id, DmFlags::empty())?; 66 | 67 | Ok(()) 68 | } 69 | 70 | pub fn suspend(&self, dm: &DM) -> FroyoResult<()> { 71 | dm.device_suspend(&DevId::Name(&self.dm_name), DM_SUSPEND)?; 72 | 73 | Ok(()) 74 | } 75 | 76 | pub fn unsuspend(&self, dm: &DM) -> FroyoResult<()> { 77 | dm.device_suspend(&DevId::Name(&self.dm_name), DmFlags::empty())?; 78 | 79 | Ok(()) 80 | } 81 | 82 | pub fn table_load(&self, dm: &DM, table: &[(u64, u64, T1, T2)]) -> FroyoResult<()> 83 | where 84 | T1: Borrow, 85 | T2: Borrow, 86 | { 87 | dm.table_load(&DevId::Name(&self.dm_name), table)?; 88 | 89 | Ok(()) 90 | } 91 | 92 | pub fn teardown(&self, dm: &DM) -> FroyoResult<()> { 93 | dbgp!("tearing down {}", self.dm_name); 94 | dm.device_remove(&DevId::Name(&self.dm_name), DmFlags::empty())?; 95 | 96 | Ok(()) 97 | } 98 | 99 | pub fn clear(&self) -> FroyoResult<()> { 100 | let pathbuf = self.dev.devnode().unwrap(); 101 | 102 | let mut f = match OpenOptions::new().write(true).open(&pathbuf) { 103 | Err(_) => { 104 | return Err(FroyoError::Io(io::Error::new( 105 | ErrorKind::PermissionDenied, 106 | format!("Could not open {}", pathbuf.display()), 107 | ))) 108 | } 109 | Ok(x) => x, 110 | }; 111 | 112 | let sectors = blkdev_size(&f)? / SECTOR_SIZE; 113 | let buf = vec![0u8; SECTOR_SIZE as usize]; 114 | for _ in 0..sectors { 115 | f.write_all(&buf)?; 116 | } 117 | 118 | Ok(()) 119 | } 120 | 121 | pub fn table_status(&self, dm: &DM) -> FroyoResult> { 122 | let (_, status) = dm.table_status(&DevId::Name(&self.dm_name), DmFlags::empty())?; 123 | 124 | Ok(status) 125 | } 126 | 127 | pub fn message(&self, dm: &DM, message: &str) -> FroyoResult<()> { 128 | dm.target_msg(&DevId::Name(&self.dm_name), 0, message)?; 129 | 130 | Ok(()) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | #![allow(dead_code)] // only temporary, until more stuff is filled in 6 | 7 | // #[macro_use] 8 | // extern crate custom_derive; 9 | // #[macro_use] 10 | // extern crate newtype_derive; 11 | 12 | pub static mut DEBUG: bool = false; 13 | 14 | macro_rules! dbgp { 15 | ($($arg:tt)*) => ( 16 | unsafe { 17 | if crate::DEBUG { 18 | println!($($arg)*) 19 | } 20 | }) 21 | } 22 | 23 | mod blockdev; 24 | mod consts; 25 | mod dbus_api; 26 | mod dmdevice; 27 | mod froyo; 28 | mod mirror; 29 | mod raid; 30 | mod serialize; 31 | mod thin; 32 | mod types; 33 | mod util; 34 | 35 | use std::borrow::Cow; 36 | use std::cell::RefCell; 37 | use std::io::Write; 38 | use std::path::{Path, PathBuf}; 39 | use std::process::exit; 40 | use std::rc::Rc; 41 | 42 | use bytesize::ByteSize; 43 | use chrono::{Duration, Utc, MIN_DATETIME}; 44 | use clap::{crate_version, App, Arg, ArgMatches, SubCommand}; 45 | use dbus::{BusType, Connection, FromMessageItem, Message, MessageItem, Props}; 46 | use dbus::{ConnectionItem, MessageType}; 47 | 48 | use consts::{DBUS_TIMEOUT, SECTOR_SIZE}; 49 | use froyo::Froyo; 50 | use types::{FroyoError, FroyoResult, InternalError}; 51 | 52 | // We are given BlockDevs to start. 53 | // We allocate LinearDevs from each for the meta and data devices. 54 | // We use all these to make RaidDevs. 55 | // We create two RaidLinearDevs from these for meta and data devices. 56 | // We use these to make a ThinPoolDev. 57 | // From that, we allocate a ThinDev. 58 | 59 | trait FroyoDbusConnection { 60 | fn froyo_connect() -> FroyoResult; 61 | fn froyo_paths(&self) -> FroyoResult>; 62 | fn froyo_path(&self, name: &str) -> FroyoResult; 63 | } 64 | 65 | impl FroyoDbusConnection for Connection { 66 | fn froyo_connect() -> FroyoResult { 67 | let c = Connection::get_private(BusType::Session)?; 68 | Ok(c) 69 | } 70 | fn froyo_paths(&self) -> FroyoResult> { 71 | let m = Message::new_method_call( 72 | "org.freedesktop.Froyo1", 73 | "/org/freedesktop/froyodevs", 74 | "org.freedesktop.DBus.ObjectManager", 75 | "GetManagedObjects", 76 | ) 77 | .unwrap(); 78 | let r = self.send_with_reply_and_block(m, DBUS_TIMEOUT)?; 79 | let reply = r.get_items(); 80 | 81 | let mut froyos = Vec::new(); 82 | let array: &Vec = FromMessageItem::from(&reply[0]).unwrap(); 83 | for item in array { 84 | let (k, _) = FromMessageItem::from(item).unwrap(); 85 | let kstr: &str = FromMessageItem::from(k).unwrap(); 86 | if kstr != "/org/freedesktop/froyodevs" { 87 | froyos.push(kstr.to_owned()); 88 | } 89 | } 90 | Ok(froyos) 91 | } 92 | 93 | fn froyo_path(&self, name: &str) -> FroyoResult { 94 | let froyos = self.froyo_paths()?; 95 | 96 | for fpath in &froyos { 97 | let p = Props::new( 98 | self, 99 | "org.freedesktop.Froyo1", 100 | fpath, 101 | "org.freedesktop.FroyoDevice1", 102 | DBUS_TIMEOUT, 103 | ); 104 | let item = p.get("Name").unwrap(); 105 | let froyo_name: &str = FromMessageItem::from(&item).unwrap(); 106 | if name == froyo_name { 107 | return Ok(fpath.to_owned()); 108 | } 109 | } 110 | 111 | Err(FroyoError::Froyo(InternalError( 112 | format!("Froyodev \"{}\" not found", name).into(), 113 | ))) 114 | } 115 | } 116 | 117 | fn list(_args: &ArgMatches) -> FroyoResult<()> { 118 | let c = Connection::froyo_connect()?; 119 | let froyos = c.froyo_paths()?; 120 | 121 | for fpath in &froyos { 122 | let p = Props::new( 123 | &c, 124 | "org.freedesktop.Froyo1", 125 | fpath, 126 | "org.freedesktop.FroyoDevice1", 127 | DBUS_TIMEOUT, 128 | ); 129 | let item = p.get("Name")?; 130 | let name: &str = FromMessageItem::from(&item).unwrap(); 131 | println!("{}", name); 132 | } 133 | 134 | Ok(()) 135 | } 136 | 137 | fn status(args: &ArgMatches) -> FroyoResult<()> { 138 | let name = args.value_of("froyodevname").unwrap(); 139 | let c = Connection::froyo_connect()?; 140 | let fpath = c.froyo_path(name)?; 141 | let p = Props::new( 142 | &c, 143 | "org.freedesktop.Froyo1", 144 | fpath, 145 | "org.freedesktop.FroyoDevice1", 146 | DBUS_TIMEOUT, 147 | ); 148 | let status_msg = p.get("Status")?; 149 | let status: u32 = FromMessageItem::from(&status_msg).unwrap(); 150 | let r_status_msg = p.get("RunningStatus")?; 151 | let r_status: u32 = FromMessageItem::from(&r_status_msg).unwrap(); 152 | 153 | let stat_str: Cow = { 154 | if status != 0 { 155 | let mut stats: Vec> = Vec::new(); 156 | if 0xff & status != 0 { 157 | stats.push(format!("stopped, need {} blockdevs", status & 0xff).into()) 158 | } 159 | if 0x100 & status != 0 { 160 | stats.push("RAID failure".into()) 161 | } 162 | if 0x200 & status != 0 { 163 | stats.push("Thin pool failure: metadata".into()) 164 | } 165 | if 0x400 & status != 0 { 166 | stats.push("Thin pool failure: data".into()) 167 | } 168 | if 0x800 & status != 0 { 169 | stats.push("Thin device failure".into()) 170 | } 171 | if 0x1000 & status != 0 { 172 | stats.push("Filesystem failure".into()) 173 | } 174 | if 0x2000 & status != 0 { 175 | stats.push("Initializing".into()) 176 | } 177 | if 0xffffc000 & status != 0 { 178 | stats.push(format!("Unenumerated failure: {:x}", status).into()) 179 | } 180 | stats.join(", ").into() 181 | } else if r_status != 0 { 182 | let mut stats: Vec> = Vec::new(); 183 | if 0xff & r_status != 0 { 184 | stats.push(format!("missing {} blockdevs", r_status & 0xff).into()) 185 | } 186 | if 0x100 & r_status != 0 { 187 | stats.push("Non-redundant".into()) 188 | } 189 | if 0x200 & r_status != 0 { 190 | stats.push("Cannot reshape".into()) 191 | } else { 192 | stats.push("Can reshape".into()) 193 | } 194 | if 0x400 & r_status != 0 { 195 | stats.push("Reshaping".into()) 196 | } 197 | if 0x800 & r_status != 0 { 198 | stats.push("Throttled".into()) 199 | } 200 | if 0xfffff000 & r_status != 0 { 201 | stats.push(format!("Unenumerated issue: {:x}", r_status).into()) 202 | } 203 | stats.join(", ").into() 204 | } else { 205 | "Running".into() 206 | } 207 | }; 208 | 209 | let space_msg = p.get("RemainingSectors")?; 210 | let space: u64 = FromMessageItem::from(&space_msg).unwrap(); 211 | let space = space * SECTOR_SIZE; 212 | 213 | let total_msg = p.get("TotalSectors")?; 214 | let total: u64 = FromMessageItem::from(&total_msg).unwrap(); 215 | let total = total * SECTOR_SIZE; 216 | 217 | let percent = ((total - space) * 100) / total; 218 | 219 | println!( 220 | "Status: {}, {}% used ({} of {} free)", 221 | stat_str, 222 | percent, 223 | ByteSize::b(space as usize).to_string(true), 224 | ByteSize::b(total as usize).to_string(true) 225 | ); 226 | 227 | let err_msg = "Unexpected format of BlockDevices property"; 228 | let bdevs = p.get("BlockDevices")?; 229 | let bdev_vec: &Vec<_> = bdevs 230 | .inner() 231 | .map_err(|_| FroyoError::Froyo(InternalError(err_msg.into())))?; 232 | println!("Member devices:"); 233 | for bdev in bdev_vec { 234 | let inner_vals: &Vec<_> = bdev 235 | .inner() 236 | .map_err(|_| FroyoError::Froyo(InternalError(err_msg.into())))?; 237 | let name: &str = inner_vals[0] 238 | .inner() 239 | .map_err(|_| FroyoError::Froyo(InternalError(err_msg.into())))?; 240 | let status: u32 = inner_vals[1] 241 | .inner() 242 | .map_err(|_| FroyoError::Froyo(InternalError(err_msg.into())))?; 243 | let status_str = match status { 244 | 0 => "In use", 245 | 1 => "Not in use", 246 | 2 => "Bad", 247 | 3 => "Not present", 248 | _ => "Unknown", 249 | }; 250 | println!("{} {}", name, status_str); 251 | } 252 | 253 | Ok(()) 254 | } 255 | 256 | fn add(args: &ArgMatches) -> FroyoResult<()> { 257 | let name = args.value_of("froyodevname").unwrap(); 258 | let dev_paths: Vec<_> = args 259 | .values_of("devices") 260 | .unwrap() 261 | .into_iter() 262 | .map(|dev| { 263 | if Path::new(dev).is_absolute() { 264 | PathBuf::from(dev) 265 | } else { 266 | PathBuf::from(format!("/dev/{}", dev)) 267 | } 268 | }) 269 | .collect(); 270 | let force = args.is_present("force"); 271 | let c = Connection::froyo_connect()?; 272 | let fpath = c.froyo_path(name)?; 273 | 274 | for path in dev_paths { 275 | let mut m = Message::new_method_call( 276 | "org.freedesktop.Froyo1", 277 | &fpath, 278 | "org.freedesktop.FroyoDevice1", 279 | "AddBlockDevice", 280 | ) 281 | .unwrap(); 282 | m.append_items(&[path.to_string_lossy().into_owned().into(), force.into()]); 283 | c.send_with_reply_and_block(m, DBUS_TIMEOUT)?; 284 | } 285 | 286 | Ok(()) 287 | } 288 | 289 | fn remove(args: &ArgMatches) -> FroyoResult<()> { 290 | let name = args.value_of("froyodevname").unwrap(); 291 | let bd_path = { 292 | let dev = args.value_of("blockdev").unwrap(); 293 | if Path::new(dev).is_absolute() { 294 | PathBuf::from(dev) 295 | } else { 296 | PathBuf::from(format!("/dev/{}", dev)) 297 | } 298 | }; 299 | let wipe = args.is_present("wipe"); 300 | let c = Connection::froyo_connect()?; 301 | let fpath = c.froyo_path(name)?; 302 | 303 | let mut m = Message::new_method_call( 304 | "org.freedesktop.Froyo1", 305 | &fpath, 306 | "org.freedesktop.FroyoDevice1", 307 | "RemoveBlockDevice", 308 | ) 309 | .unwrap(); 310 | m.append_items(&[bd_path.to_string_lossy().into_owned().into(), wipe.into()]); 311 | c.send_with_reply_and_block(m, DBUS_TIMEOUT)?; 312 | 313 | Ok(()) 314 | } 315 | 316 | fn create(args: &ArgMatches) -> FroyoResult<()> { 317 | let name = args.value_of("froyodevname").unwrap(); 318 | let dev_paths: Vec<_> = args 319 | .values_of("devices") 320 | .unwrap() 321 | .into_iter() 322 | .map(|dev| { 323 | if Path::new(dev).is_absolute() { 324 | PathBuf::from(dev) 325 | } else { 326 | PathBuf::from(format!("/dev/{}", dev)) 327 | } 328 | }) 329 | .map(|pb| pb.to_string_lossy().into_owned().into()) 330 | .collect(); 331 | let force = args.is_present("force"); 332 | 333 | let c = Connection::froyo_connect()?; 334 | 335 | let mut m = Message::new_method_call( 336 | "org.freedesktop.Froyo1", 337 | "/org/freedesktop/froyo", 338 | "org.freedesktop.FroyoService1", 339 | "Create", 340 | ) 341 | .unwrap(); 342 | m.append_items(&[ 343 | name.into(), 344 | MessageItem::new_array(dev_paths).unwrap(), 345 | force.into(), 346 | ]); 347 | c.send_with_reply_and_block(m, DBUS_TIMEOUT)?; 348 | 349 | dbgp!("Froyodev {} created", name); 350 | 351 | Ok(()) 352 | } 353 | 354 | fn rename(args: &ArgMatches) -> FroyoResult<()> { 355 | let old_name = args.value_of("froyodev_old_name").unwrap(); 356 | let new_name = args.value_of("froyodev_new_name").unwrap(); 357 | 358 | let c = Connection::froyo_connect()?; 359 | let fpath = c.froyo_path(old_name)?; 360 | 361 | let mut m = Message::new_method_call( 362 | "org.freedesktop.Froyo1", 363 | &fpath, 364 | "org.freedesktop.FroyoDevice1", 365 | "SetName", 366 | ) 367 | .unwrap(); 368 | m.append_items(&[new_name.into()]); 369 | c.send_with_reply_and_block(m, DBUS_TIMEOUT)?; 370 | 371 | dbgp!("Froyodev name {} changed to {}", old_name, new_name); 372 | 373 | Ok(()) 374 | } 375 | 376 | fn reshape(args: &ArgMatches) -> FroyoResult<()> { 377 | let name = args.value_of("froyodev").unwrap(); 378 | 379 | let c = Connection::froyo_connect()?; 380 | let fpath = c.froyo_path(name)?; 381 | 382 | let m = Message::new_method_call( 383 | "org.freedesktop.Froyo1", 384 | &fpath, 385 | "org.freedesktop.FroyoDevice1", 386 | "Reshape", 387 | ) 388 | .unwrap(); 389 | c.send_with_reply_and_block(m, DBUS_TIMEOUT)?; 390 | 391 | dbgp!("Froyodev {} starting reshape", name); 392 | 393 | Ok(()) 394 | } 395 | 396 | fn destroy(args: &ArgMatches) -> FroyoResult<()> { 397 | let name = args.value_of("froyodev").unwrap(); 398 | 399 | let c = Connection::froyo_connect()?; 400 | 401 | let mut m = Message::new_method_call( 402 | "org.freedesktop.Froyo1", 403 | "/org/freedesktop/froyo", 404 | "org.freedesktop.FroyoService1", 405 | "Destroy", 406 | ) 407 | .unwrap(); 408 | m.append_items(&[name.into()]); 409 | c.send_with_reply_and_block(m, DBUS_TIMEOUT)?; 410 | 411 | dbgp!("Froyodev {} destroyed", name); 412 | 413 | Ok(()) 414 | } 415 | 416 | fn teardown(args: &ArgMatches) -> FroyoResult<()> { 417 | let name = args.value_of("froyodev").unwrap(); 418 | 419 | let c = Connection::froyo_connect()?; 420 | 421 | let mut m = Message::new_method_call( 422 | "org.freedesktop.Froyo1", 423 | "/org/freedesktop/froyo", 424 | "org.freedesktop.FroyoService1", 425 | "Teardown", 426 | ) 427 | .unwrap(); 428 | m.append_items(&[name.into()]); 429 | c.send_with_reply_and_block(m, DBUS_TIMEOUT)?; 430 | 431 | dbgp!("Froyodev {} torn down", name); 432 | 433 | Ok(()) 434 | } 435 | 436 | fn dump_meta(args: &ArgMatches) -> FroyoResult<()> { 437 | let name = args.value_of("froyodevname").unwrap(); 438 | match Froyo::find(name)? { 439 | Some(f) => println!("{}", f.to_metadata_pretty()?), 440 | None => { 441 | return Err(FroyoError::Froyo(InternalError( 442 | format!("Froyodev \"{}\" not found", name).into(), 443 | ))) 444 | } 445 | } 446 | 447 | Ok(()) 448 | } 449 | 450 | fn dbus_server(_args: &ArgMatches) -> FroyoResult<()> { 451 | let c = Connection::froyo_connect()?; 452 | let froyos = Froyo::find_all()?; 453 | let froyos = froyos 454 | .into_iter() 455 | .map(|f| Rc::new(RefCell::new(f))) 456 | .collect::>(); 457 | let mut froyos = Rc::new(RefCell::new(froyos)); 458 | 459 | // We can't change a tree from within the tree. So instead 460 | // register two trees, one with Create and Destroy and another for 461 | // querying/changing active froyodevs/ 462 | let child_tree = dbus_api::get_child_tree(&c, &froyos.borrow())?; 463 | let base_tree = dbus_api::get_base_tree(&c, &mut froyos, &child_tree)?; 464 | 465 | // TODO: event loop needs to handle dbus and also dm events (or polling) 466 | // so we can extend/reshape/delay/whatever in a timely fashion 467 | let mut last_time = MIN_DATETIME; 468 | for c_item in c.iter(10000) { 469 | if let ConnectionItem::MethodCall(ref msg) = c_item { 470 | if msg.msg_type() != MessageType::MethodCall { 471 | continue; 472 | } 473 | 474 | if let Some(v) = base_tree.handle(msg) { 475 | // Probably the wisest is to ignore any send errors here - 476 | // maybe the remote has disconnected during our processing. 477 | for m in v { 478 | let _ = c.send(m); 479 | } 480 | } else if let Some(v) = child_tree.borrow().handle(msg) { 481 | for m in v { 482 | let _ = c.send(m); 483 | } 484 | } 485 | } 486 | 487 | let now = Utc::now(); 488 | if now < last_time + Duration::seconds(30) { 489 | continue; 490 | } 491 | 492 | last_time = now; 493 | 494 | for froyo in &*froyos.borrow() { 495 | let mut froyo = froyo.borrow_mut(); 496 | froyo.check_state()?; 497 | froyo.update_dbus()?; 498 | froyo.dump_status()?; 499 | } 500 | } 501 | 502 | Ok(()) 503 | } 504 | 505 | fn write_err(err: FroyoError) -> FroyoResult<()> { 506 | let mut out = term::stderr().expect("could not get stderr"); 507 | 508 | out.fg(term::color::RED)?; 509 | writeln!(out, "{}", err)?; 510 | out.reset()?; 511 | Ok(()) 512 | } 513 | 514 | fn main() { 515 | let matches = App::new("froyo") 516 | .version(&crate_version!()) 517 | .author("Andy Grover ") 518 | .about("Drobo + Free + YOLO") 519 | .arg( 520 | Arg::with_name("debug") 521 | .short("d") 522 | .long("debug") 523 | .help("Print additional output for debugging"), 524 | ) 525 | .subcommand( 526 | SubCommand::with_name("list") 527 | .about("List all froyodevs") 528 | .arg( 529 | Arg::with_name("long") 530 | .short("l") 531 | .long("long") 532 | .help("Use a long listing format"), 533 | ), 534 | ) 535 | .subcommand( 536 | SubCommand::with_name("status") 537 | .about("Get the status of a single froyodev") 538 | .arg( 539 | Arg::with_name("froyodevname") 540 | .required(true) 541 | .help("Froyodev to get info on") 542 | .index(1), 543 | ), 544 | ) 545 | .subcommand( 546 | SubCommand::with_name("add") 547 | .about("Add one or more additional block devices to a froyodev") 548 | .arg( 549 | Arg::with_name("force") 550 | .short("f") 551 | .long("force") 552 | .help("Force"), 553 | ) 554 | .arg( 555 | Arg::with_name("froyodevname") 556 | .help("Froyodev to add the device to") 557 | .required(true) 558 | .index(1), 559 | ) 560 | .arg( 561 | Arg::with_name("devices") 562 | .help("device(s) to add") 563 | .multiple(true) 564 | .required(true) 565 | .index(2), 566 | ), 567 | ) 568 | .subcommand( 569 | SubCommand::with_name("remove") 570 | .about("Remove a block device from a froyodev") 571 | .arg( 572 | Arg::with_name("wipe") 573 | .long("wipe") 574 | .help("No longer track this device as part of the froyodev"), 575 | ) 576 | .arg( 577 | Arg::with_name("froyodevname") 578 | .help("Froyodev to remove the device from") 579 | .required(true) 580 | .index(1), 581 | ) 582 | .arg( 583 | Arg::with_name("blockdev") 584 | .help("Block device to remove") 585 | .required(true) 586 | .index(2), 587 | ), 588 | ) 589 | .subcommand( 590 | SubCommand::with_name("create") 591 | .about("Create a new froyodev") 592 | .arg( 593 | Arg::with_name("force") 594 | .short("f") 595 | .long("force") 596 | .help("Force"), 597 | ) 598 | .arg( 599 | Arg::with_name("froyodevname") 600 | .help("Name of the new froyodev") 601 | .required(true) 602 | .index(1), 603 | ) 604 | .arg( 605 | Arg::with_name("devices") 606 | .help("Initial block device(s) to use") 607 | .multiple(true) 608 | .required(true) 609 | .index(2), 610 | ), 611 | ) 612 | .subcommand( 613 | SubCommand::with_name("rename") 614 | .about("Rename a froyodev") 615 | .arg( 616 | Arg::with_name("froyodev_old_name") 617 | .help("Old name of froyodev") 618 | .required(true) 619 | .index(1), 620 | ) 621 | .arg( 622 | Arg::with_name("froyodev_new_name") 623 | .help("New name of froyodev") 624 | .required(true) 625 | .index(2), 626 | ), 627 | ) 628 | .subcommand( 629 | SubCommand::with_name("destroy") 630 | .about("Destroy a froyodev") 631 | .arg( 632 | Arg::with_name("froyodev") 633 | .help("Froyodev to destroy") 634 | .required(true) 635 | .index(1), 636 | ), 637 | ) 638 | .subcommand( 639 | SubCommand::with_name("reshape") 640 | .about("Manually start a reshape") 641 | .arg( 642 | Arg::with_name("froyodev") 643 | .help("Froyodev to reshape") 644 | .required(true) 645 | .index(1), 646 | ), 647 | ) 648 | .subcommand( 649 | SubCommand::with_name("teardown") 650 | .about("Deactivate a froyodev") 651 | .arg( 652 | Arg::with_name("froyodev") 653 | .help("Name of the froyodev") 654 | .required(true) 655 | .index(1), 656 | ), 657 | ) 658 | .subcommand( 659 | SubCommand::with_name("dev") 660 | .about("Developer/debug commands") 661 | .subcommand( 662 | SubCommand::with_name("dump_meta") 663 | .about("Output the JSON metadata for a froyodev") 664 | .arg( 665 | Arg::with_name("froyodevname") 666 | .help("Name of the froyodev") 667 | .required(true) 668 | .index(1), 669 | ), 670 | ) 671 | .subcommand(SubCommand::with_name("dbus_server").about("Serve the Froyo DBus API")), 672 | ) 673 | .get_matches(); 674 | 675 | if matches.is_present("debug") { 676 | // must use unsafe to change a mut static, sigh 677 | unsafe { DEBUG = true }; 678 | } 679 | 680 | let r = match matches.subcommand() { 681 | ("list", Some(matches)) => list(matches), 682 | ("status", Some(matches)) => status(matches), 683 | ("add", Some(matches)) => add(matches), 684 | ("remove", Some(matches)) => remove(matches), 685 | ("create", Some(matches)) => create(matches), 686 | ("rename", Some(matches)) => rename(matches), 687 | ("destroy", Some(matches)) => destroy(matches), 688 | ("reshape", Some(matches)) => reshape(matches), 689 | ("teardown", Some(matches)) => teardown(matches), 690 | ("dev", Some(matches)) => match matches.subcommand() { 691 | ("dump_meta", Some(matches)) => dump_meta(matches), 692 | ("dbus_server", Some(matches)) => dbus_server(matches), 693 | ("", None) => { 694 | println!("No command given, try \"help\""); 695 | Ok(()) 696 | } 697 | _ => unreachable!(), 698 | }, 699 | ("", None) => { 700 | println!("No command given, try \"help\""); 701 | Ok(()) 702 | } 703 | _ => unreachable!(), 704 | }; 705 | 706 | if let Err(r) = r { 707 | if let Err(e) = write_err(r) { 708 | panic!("Unable to write to stderr: {}", e) 709 | } 710 | 711 | exit(1); 712 | } 713 | } 714 | -------------------------------------------------------------------------------- /src/mirror.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | use std::cell::RefCell; 6 | use std::io; 7 | use std::rc::Rc; 8 | 9 | use devicemapper::DM; 10 | use uuid::Uuid; 11 | 12 | use crate::blockdev::{BlockDev, BlockDevs, LinearSegment}; 13 | use crate::consts::*; 14 | use crate::dmdevice::DmDevice; 15 | use crate::raid::{RaidDev, RaidLinearDev}; 16 | use crate::serialize::{FroyoSave, TempDevSave, TempDevSegmentSave}; 17 | use crate::types::{FroyoError, FroyoResult, InternalError, SectorOffset, Sectors, SumSectors}; 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct MirrorDev { 21 | pub mirror: DmDevice, 22 | pub src: Rc>, 23 | pub dest: Rc>, 24 | // info on raidlineardev in thinpool so we can restore table when done 25 | pub linear_dev: Rc>, 26 | pub linear_dev_idxs: Vec, 27 | } 28 | 29 | impl MirrorDev { 30 | pub fn new( 31 | dm: &DM, 32 | froyo_id: Uuid, 33 | src: Rc>, 34 | dest: Rc>, 35 | length: Sectors, 36 | linear_dev: Rc>, 37 | linear_dev_idxs: &[usize], 38 | ) -> FroyoResult { 39 | let table = ( 40 | 0, 41 | *length, 42 | "raid", 43 | format!( 44 | "raid1 1 {} 2 - {} - {}", 45 | *STRIPE_SECTORS, 46 | src.borrow().dmdev.dstr(), 47 | dest.borrow().dmdev.dstr() 48 | ), 49 | ); 50 | let dm_name = format!("froyo-copymirror-{}", froyo_id); 51 | let mirror_dev = DmDevice::new(dm, &dm_name, &[table])?; 52 | 53 | Ok(MirrorDev { 54 | mirror: mirror_dev, 55 | src, 56 | dest, 57 | linear_dev, 58 | linear_dev_idxs: linear_dev_idxs.to_vec(), 59 | }) 60 | } 61 | 62 | pub fn teardown(&self, dm: &DM) -> FroyoResult<()> { 63 | self.mirror.teardown(dm)?; 64 | self.src.borrow().teardown(dm)?; 65 | self.dest.borrow().teardown(dm)?; 66 | Ok(()) 67 | } 68 | 69 | pub fn is_syncing(&self, dm: &DM) -> FroyoResult { 70 | let mut status = self.mirror.table_status(dm)?; 71 | 72 | // See kernel's dm-raid.txt "Status Output" 73 | let status_line = status.pop().unwrap().3; 74 | let status_vals = status_line.split(' ').collect::>(); 75 | if status_vals.len() < 5 { 76 | return Err(FroyoError::Io(io::Error::new( 77 | io::ErrorKind::InvalidData, 78 | "Kernel returned too few values from raid status", 79 | ))); 80 | } 81 | 82 | dbgp!("status line {}", status_line); 83 | dbgp!("status {}", status_vals[2]); 84 | dbgp!("action {}", status_vals[4]); 85 | 86 | match status_vals[4] { 87 | "idle" => Ok(false), 88 | "resync" => Ok(true), 89 | action => Err(FroyoError::Froyo(InternalError( 90 | format!("Unexpected action: {}", action).into(), 91 | ))), 92 | } 93 | } 94 | } 95 | 96 | // Our TempDev's segments may either be on top of a RaidDev (e.g. if 97 | // we're using the MirrorDev to copy to/from two raids) or it may be 98 | // on top of a BlockDev, if we've resorted to using non-redundant 99 | // scratch space. 100 | #[derive(Debug, Clone)] 101 | pub enum TempLayer { 102 | Block(Rc>), 103 | Raid(Rc>), 104 | } 105 | 106 | impl TempLayer { 107 | pub fn dstr(&self) -> String { 108 | match *self { 109 | TempLayer::Block(ref bd) => bd.borrow().dev.dstr(), 110 | TempLayer::Raid(ref rd) => rd.borrow().dev.dstr(), 111 | } 112 | } 113 | 114 | pub fn block(&self) -> Rc> { 115 | match *self { 116 | TempLayer::Block(ref bd) => bd.clone(), 117 | _ => panic!("should never happen"), 118 | } 119 | } 120 | 121 | pub fn raid(&self) -> Rc> { 122 | match *self { 123 | TempLayer::Raid(ref rd) => rd.clone(), 124 | _ => panic!("should never happen"), 125 | } 126 | } 127 | 128 | pub fn id(&self) -> Uuid { 129 | match *self { 130 | TempLayer::Block(ref bd) => bd.borrow().id, 131 | TempLayer::Raid(ref rd) => rd.borrow().id, 132 | } 133 | } 134 | } 135 | 136 | #[derive(Debug, Clone)] 137 | pub struct TempDev { 138 | pub id: Uuid, 139 | pub dmdev: DmDevice, 140 | pub segments: Vec<(TempLayer, LinearSegment)>, 141 | } 142 | 143 | // A linear device that can span multiple blockdevs. 144 | 145 | impl TempDev { 146 | pub fn new( 147 | dm: &DM, 148 | froyo_id: Uuid, 149 | segments: &[(TempLayer, LinearSegment)], 150 | ) -> FroyoResult { 151 | let mut table = Vec::new(); 152 | let mut offset = SectorOffset(0); 153 | for &(ref dev, seg) in segments { 154 | let line = ( 155 | *offset, 156 | *seg.length, 157 | "linear", 158 | format!("{} {}", dev.dstr(), *seg.start), 159 | ); 160 | table.push(line); 161 | offset = offset + SectorOffset(*seg.length); 162 | } 163 | 164 | let id = Uuid::new_v4(); 165 | let dm_name = format!("froyo-linear-temp-{}-{}", froyo_id, id); 166 | let dmdev = DmDevice::new(dm, &dm_name, &table)?; 167 | 168 | Ok(TempDev { 169 | id, 170 | dmdev, 171 | segments: segments.to_vec(), 172 | }) 173 | } 174 | 175 | pub fn setup( 176 | dm: &DM, 177 | froyo_save: &FroyoSave, 178 | block_devs: &BlockDevs, 179 | ) -> FroyoResult> { 180 | match froyo_save.temp_dev { 181 | None => Ok(None), 182 | Some(ref td) => { 183 | let mut td_segs = Vec::new(); 184 | for seg in &td.segments { 185 | if let Some(bd) = block_devs.0.get(&seg.parent).and_then(|bm| bm.present()) { 186 | td_segs.push(( 187 | TempLayer::Block(bd.clone()), 188 | LinearSegment::new(seg.start, seg.length), 189 | )); 190 | } 191 | } 192 | 193 | let td = TempDev::new(dm, froyo_save.id, &td_segs)?; 194 | Ok(Some(td)) 195 | } 196 | } 197 | } 198 | 199 | pub fn dstr(&self) -> String { 200 | self.dmdev.dstr() 201 | } 202 | 203 | pub fn teardown(&self, dm: &DM) -> FroyoResult<()> { 204 | self.dmdev.teardown(dm) 205 | } 206 | 207 | pub fn length(&self) -> Sectors { 208 | self.segments.iter().map(|&(_, x)| x.length).sum_sectors() 209 | } 210 | 211 | pub fn to_save(&self) -> TempDevSave { 212 | TempDevSave { 213 | id: self.id.to_owned(), 214 | segments: self 215 | .segments 216 | .iter() 217 | .map(|&(ref tl, ls)| TempDevSegmentSave { 218 | parent: tl.id(), 219 | start: ls.start, 220 | length: ls.length, 221 | }) 222 | .collect(), 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/raid.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | use std::cell::RefCell; 6 | use std::cmp::{max, min}; 7 | use std::collections::BTreeMap; 8 | use std::fmt; 9 | use std::io; 10 | use std::io::ErrorKind; 11 | use std::rc::Rc; 12 | 13 | use devicemapper::DM; 14 | use uuid::Uuid; 15 | 16 | use crate::blockdev::{BlockDev, BlockDevs, BlockMember, LinearDev, LinearDevSave, LinearSegment}; 17 | use crate::consts::*; 18 | use crate::dmdevice::DmDevice; 19 | use crate::mirror::TempDev; 20 | use crate::serialize::{FroyoSave, RaidDevSave, RaidLinearDevSave, RaidSegmentSave}; 21 | use crate::types::{FroyoError, FroyoResult, InternalError, SectorOffset, Sectors, SumSectors}; 22 | use crate::util::align_to; 23 | 24 | #[derive(Debug, Clone, PartialEq)] 25 | pub struct RaidDev { 26 | pub id: Uuid, 27 | pub dev: DmDevice, 28 | pub stripe_sectors: Sectors, 29 | pub region_sectors: Sectors, 30 | pub length: Sectors, 31 | pub members: Vec, 32 | used: BTreeMap, 33 | } 34 | 35 | #[derive(Debug, Clone, Copy)] 36 | pub enum RaidStatus { 37 | Good, 38 | Degraded(usize), 39 | Failed, 40 | } 41 | 42 | #[derive(Debug, Clone, Copy)] 43 | pub enum RaidAction { 44 | Idle, 45 | Frozen, 46 | Resync, 47 | Recover, 48 | Check, 49 | Repair, 50 | Reshape, 51 | Unknown, 52 | } 53 | 54 | #[derive(Debug, Clone, PartialEq)] 55 | pub enum RaidMember { 56 | Present(Rc>), 57 | Absent((Uuid, LinearDevSave)), 58 | Removed, 59 | } 60 | 61 | impl RaidMember { 62 | pub fn present(&self) -> Option>> { 63 | match *self { 64 | RaidMember::Present(ref x) => Some(x.clone()), 65 | RaidMember::Absent(_) => None, 66 | RaidMember::Removed => None, 67 | } 68 | } 69 | } 70 | 71 | impl RaidDev { 72 | fn make_raid_params( 73 | devs: &[RaidMember], 74 | stripe: Sectors, 75 | region: Sectors, 76 | rebuild: Option, 77 | ) -> String { 78 | let raid_texts: Vec<_> = devs 79 | .iter() 80 | .map(|dev| match *dev { 81 | RaidMember::Present(ref dev) => { 82 | format!( 83 | "{} {}", 84 | dev.borrow().meta_dev.dstr(), 85 | dev.borrow().data_dev.dstr() 86 | ) 87 | } 88 | RaidMember::Absent(_) => "- -".to_owned(), 89 | RaidMember::Removed => "- -".to_owned(), 90 | }) 91 | .collect(); 92 | 93 | match rebuild { 94 | None => format!( 95 | "raid5_ls 3 {} region_size {} {} {}", 96 | *stripe, 97 | *region, 98 | raid_texts.len(), 99 | raid_texts.join(" ") 100 | ), 101 | Some(idx) => format!( 102 | "raid5_ls 5 {} region_size {} rebuild {} {} {}", 103 | *stripe, 104 | *region, 105 | idx, 106 | raid_texts.len(), 107 | raid_texts.join(" ") 108 | ), 109 | } 110 | } 111 | 112 | pub fn setup( 113 | dm: &DM, 114 | froyo_id: Uuid, 115 | id: Uuid, 116 | devs: Vec, 117 | stripe: Sectors, 118 | region: Sectors, 119 | ) -> FroyoResult { 120 | let present_devs = devs.iter().filter_map(|x| x.present()).count(); 121 | if present_devs < (devs.len() - REDUNDANCY) { 122 | return Err(FroyoError::Io(io::Error::new( 123 | ErrorKind::InvalidInput, 124 | format!( 125 | "Too many missing devs to create raid: {}. Need at least {} of {}", 126 | devs.len() - present_devs, 127 | devs.len() - REDUNDANCY, 128 | devs.len() 129 | ), 130 | ))); 131 | } 132 | 133 | let first_present_dev = devs.iter().find_map(|x| x.present()).unwrap(); 134 | let first_present_dev_len = first_present_dev.borrow().data_length(); 135 | 136 | // Verify all present devs are the same length 137 | if !devs 138 | .iter() 139 | .filter_map(|x| x.present()) 140 | .all(|x| x.borrow().data_length() == first_present_dev_len) 141 | { 142 | return Err(FroyoError::Io(io::Error::new( 143 | ErrorKind::InvalidInput, 144 | "RAID member device sizes differ", 145 | ))); 146 | } 147 | 148 | let target_length = first_present_dev_len * Sectors((devs.len() - REDUNDANCY) as u64); 149 | 150 | let params = Self::make_raid_params(&devs, stripe, region, None); 151 | let raid_table = [(0u64, *target_length, "raid", params)]; 152 | let dm_name = format!("froyo-raid5-{}-{}", froyo_id, id); 153 | let raid_dev = DmDevice::new(dm, &dm_name, &raid_table)?; 154 | 155 | Ok(RaidDev { 156 | id, 157 | dev: raid_dev, 158 | stripe_sectors: stripe, 159 | region_sectors: region, 160 | length: target_length, 161 | members: devs, 162 | used: BTreeMap::new(), 163 | }) 164 | } 165 | 166 | pub fn teardown(&mut self, dm: &DM) -> FroyoResult<()> { 167 | self.dev.teardown(dm)?; 168 | for member in &self.members { 169 | if let RaidMember::Present(ref linear) = *member { 170 | linear.borrow_mut().teardown(dm)? 171 | } 172 | } 173 | 174 | Ok(()) 175 | } 176 | 177 | pub fn destroy(&mut self, dm: &DM) -> FroyoResult<()> { 178 | self.dev.teardown(dm)?; 179 | for member in &self.members { 180 | if let RaidMember::Present(ref linear) = *member { 181 | let linear = linear.borrow_mut(); 182 | linear.teardown(dm)?; 183 | let bd = linear.parent.upgrade().unwrap(); 184 | bd.borrow_mut().linear_devs.remove(&linear.meta_dev.dm_name); 185 | } 186 | } 187 | 188 | Ok(()) 189 | } 190 | 191 | pub fn reload(&mut self, dm: &DM, rebuild: Option) -> FroyoResult<()> { 192 | let params = Self::make_raid_params( 193 | &self.members, 194 | self.stripe_sectors, 195 | self.region_sectors, 196 | rebuild, 197 | ); 198 | let raid_table = [(0u64, *self.length, "raid", params)]; 199 | self.dev.reload(dm, &raid_table)?; 200 | 201 | Ok(()) 202 | } 203 | 204 | pub fn to_save(&self) -> RaidDevSave { 205 | RaidDevSave { 206 | stripe_sectors: self.stripe_sectors, 207 | region_sectors: self.region_sectors, 208 | length: self.length, 209 | member_count: self.members.len(), 210 | members: self 211 | .members 212 | .iter() 213 | .enumerate() 214 | .filter_map(|(position, dev)| match *dev { 215 | RaidMember::Present(ref x) => Some((position, x.borrow().to_save())), 216 | RaidMember::Absent((_, ref sld)) => Some((position, sld.clone())), 217 | RaidMember::Removed => None, 218 | }) 219 | .collect(), 220 | } 221 | } 222 | 223 | fn used_areas(&self) -> Vec<(SectorOffset, Sectors)> { 224 | self.used.iter().map(|(key, val)| (*key, *val)).collect() 225 | } 226 | 227 | fn avail_areas(&self) -> Vec<(SectorOffset, Sectors)> { 228 | let mut used_vec = self.used_areas(); 229 | 230 | used_vec.sort(); 231 | // Insert an entry to mark the end of the raiddev so the fold works 232 | // correctly 233 | used_vec.push((SectorOffset(*self.length), Sectors(0))); 234 | 235 | let mut avail_vec = Vec::new(); 236 | used_vec 237 | .iter() 238 | .fold(SectorOffset(0), |prev_end, &(start, len)| { 239 | if prev_end < start { 240 | avail_vec.push((prev_end, Sectors(*start - *prev_end))); 241 | } 242 | start + SectorOffset(*len) 243 | }); 244 | 245 | avail_vec 246 | } 247 | 248 | fn avail_sectors(&self) -> Sectors { 249 | self.avail_areas() 250 | .into_iter() 251 | .map(|(_, len)| len) 252 | .sum_sectors() 253 | } 254 | 255 | // Is this raiddev a good one to maybe put more stuff on? 256 | pub fn is_safe(&self) -> bool { 257 | self.members.iter().all(|rm| rm.present().is_some()) 258 | } 259 | 260 | pub fn is_empty(&self) -> bool { 261 | self.used.is_empty() 262 | } 263 | 264 | // Find some sector ranges that could be allocated. If more 265 | // sectors are needed than our capacity, return partial results. 266 | pub fn get_some_space(&self, size: Sectors) -> (Sectors, Vec<(SectorOffset, Sectors)>) { 267 | let mut segs = Vec::new(); 268 | let mut needed = size; 269 | 270 | for (start, len) in self.avail_areas() { 271 | if needed == Sectors(0) { 272 | break; 273 | } 274 | 275 | let to_use = min(needed, len); 276 | 277 | segs.push((start, to_use)); 278 | needed = needed - to_use; 279 | } 280 | 281 | (size - needed, segs) 282 | } 283 | 284 | pub fn status(&self) -> FroyoResult<(RaidStatus, RaidAction)> { 285 | let dm = DM::new()?; 286 | 287 | let mut status = self.dev.table_status(&dm)?; 288 | 289 | if status.len() != 1 { 290 | return Err(FroyoError::Io(io::Error::new( 291 | io::ErrorKind::InvalidData, 292 | "Expected 1 line from raid status", 293 | ))); 294 | } 295 | 296 | // See kernel's dm-raid.txt "Status Output" 297 | let status_line = status.pop().unwrap().3; 298 | let status_vals = status_line.split(' ').collect::>(); 299 | if status_vals.len() < 5 { 300 | return Err(FroyoError::Io(io::Error::new( 301 | io::ErrorKind::InvalidData, 302 | "Kernel returned too few values from raid status", 303 | ))); 304 | } 305 | 306 | let mut bad = 0; 307 | for c in status_vals[2].chars() { 308 | match c { 309 | 'A' => {} 310 | 'a' => {} 311 | 'D' => bad += 1, 312 | x => { 313 | return Err(FroyoError::Io(io::Error::new( 314 | ErrorKind::InvalidData, 315 | format!("Kernel returned unknown raid health char '{}'", x), 316 | ))) 317 | } 318 | } 319 | } 320 | 321 | // Status characters indicate if a drive is faulty, but not if 322 | // the raid was configured with missing devices. Add them. 323 | bad += self 324 | .members 325 | .iter() 326 | .filter(|rm| rm.present().is_none()) 327 | .count(); 328 | 329 | let raid_status = match bad { 330 | 0 => RaidStatus::Good, 331 | x @ 1..=REDUNDANCY => RaidStatus::Degraded(x), 332 | _ => RaidStatus::Failed, 333 | }; 334 | 335 | let raid_action = match status_vals[4] { 336 | "idle" => RaidAction::Idle, 337 | "frozen" => RaidAction::Frozen, 338 | "resync" => RaidAction::Resync, 339 | "recover" => RaidAction::Recover, 340 | "check" => RaidAction::Check, 341 | "repair" => RaidAction::Repair, 342 | "reshape" => RaidAction::Reshape, 343 | _ => RaidAction::Unknown, 344 | }; 345 | 346 | Ok((raid_status, raid_action)) 347 | } 348 | 349 | pub fn per_member_size(&self) -> Option<(Sectors, Sectors)> { 350 | // all members should be the same size 351 | if let Some(ld) = self.members.iter().find_map(|rm| rm.present()) { 352 | let ld = ld.borrow(); 353 | return Some((ld.metadata_length(), ld.data_length())); 354 | } 355 | 356 | None 357 | } 358 | 359 | // A new block device has been added. Are we degraded? 360 | // Try to use it! 361 | pub fn new_block_device_added( 362 | &mut self, 363 | dm: &DM, 364 | froyo_id: Uuid, 365 | blockdev: &Rc>, 366 | ) -> FroyoResult<()> { 367 | let (meta_spc, data_spc) = self.per_member_size().unwrap(); 368 | 369 | // get index of slot we should fill 370 | let idx = self 371 | .members 372 | .iter() 373 | .enumerate() 374 | .filter(|&(_, rm)| rm.present().is_none()) 375 | .map(|(idx, _)| idx) 376 | .next(); 377 | 378 | if let Some(idx) = idx { 379 | let needed = meta_spc + data_spc; 380 | let (offset, len) = blockdev 381 | .borrow() 382 | .largest_avail_area() 383 | .unwrap_or((SectorOffset(0), Sectors(0))); 384 | if len >= needed { 385 | let linear = Rc::new(RefCell::new(LinearDev::new( 386 | dm, 387 | &format!("{}-{}-{}", froyo_id, self.id, idx), 388 | blockdev, 389 | &[LinearSegment::new(offset, meta_spc)], 390 | &[LinearSegment::new( 391 | offset + SectorOffset(*meta_spc), 392 | data_spc, 393 | )], 394 | )?)); 395 | 396 | blockdev 397 | .borrow_mut() 398 | .linear_devs 399 | .insert(linear.borrow().meta_dev.dm_name.clone(), linear.clone()); 400 | self.members[idx] = RaidMember::Present(linear); 401 | 402 | self.reload(dm, Some(idx))?; 403 | } 404 | } 405 | 406 | Ok(()) 407 | } 408 | 409 | // If there is an absent member that matches the id of the 410 | // just-found block device, set it up, mark it present, and reload 411 | // the raid. 412 | pub fn block_device_found( 413 | &mut self, 414 | dm: &DM, 415 | froyo_id: Uuid, 416 | blockdev: &Rc>, 417 | ) -> FroyoResult<()> { 418 | let res = self 419 | .members 420 | .iter() 421 | .enumerate() 422 | .filter_map(|(idx, rm)| match *rm { 423 | RaidMember::Present(_) => None, 424 | RaidMember::Absent(ref sld_tuple) => Some((idx, sld_tuple.clone())), 425 | RaidMember::Removed => None, 426 | }) 427 | .find(|&(_, (ref id, _))| *id == blockdev.borrow().id); 428 | 429 | if let Some((idx, (_, sld))) = res { 430 | let linear = Rc::new(RefCell::new(LinearDev::setup( 431 | dm, 432 | &format!("{}-{}-{}", froyo_id, self.id, idx), 433 | blockdev, 434 | &sld.meta_segments, 435 | &sld.data_segments, 436 | )?)); 437 | 438 | blockdev 439 | .borrow_mut() 440 | .linear_devs 441 | .insert(linear.borrow().meta_dev.dm_name.clone(), linear.clone()); 442 | self.members[idx] = RaidMember::Present(linear); 443 | 444 | self.reload(dm, None)?; 445 | } 446 | 447 | Ok(()) 448 | } 449 | } 450 | 451 | #[derive(Debug, Clone)] 452 | pub struct RaidDevs { 453 | pub raids: BTreeMap>>, 454 | 455 | // temp_dev is a linear mapping to non-redundant space. During a 456 | // reshape this may be present, and the saved configuration may 457 | // refer to it. Basically, when building RaidLinearDevs for the 458 | // thinpool meta and data devices, if the thinpooldev segment's 459 | // parent uuid doesn't reference a raiddev, we then check the 460 | // TempDev. If it's not in either then our saved info is corrupt. 461 | pub temp_dev: Option>>, 462 | } 463 | 464 | impl RaidDevs { 465 | pub fn new(dm: &DM, id: Uuid, block_devs: &BlockDevs) -> FroyoResult { 466 | let mut raid_devs = RaidDevs { 467 | raids: BTreeMap::new(), 468 | temp_dev: None, 469 | }; 470 | 471 | raid_devs.create_redundant_zones(dm, id, block_devs)?; 472 | 473 | Ok(raid_devs) 474 | } 475 | 476 | pub fn setup(dm: &DM, froyo_save: &FroyoSave, block_devs: &BlockDevs) -> FroyoResult { 477 | let mut raid_devs = RaidDevs { 478 | raids: BTreeMap::new(), 479 | temp_dev: None, 480 | }; 481 | for (id, srd) in &froyo_save.raid_devs { 482 | let rd = Rc::new(RefCell::new(Self::setup_raiddev( 483 | dm, 484 | froyo_save.id, 485 | *id, 486 | srd, 487 | block_devs, 488 | )?)); 489 | let id = rd.borrow().id; 490 | 491 | if let (RaidStatus::Failed, _) = rd.borrow().status()? { 492 | return Err(FroyoError::Froyo(InternalError( 493 | format!("Froyodev {} has a failed raid", froyo_save.name).into(), 494 | ))); 495 | } 496 | 497 | raid_devs.raids.insert(id, rd); 498 | } 499 | 500 | if let Some(td) = TempDev::setup(dm, froyo_save, block_devs)? { 501 | raid_devs.temp_dev = Some(Rc::new(RefCell::new(td))); 502 | } 503 | 504 | Ok(raid_devs) 505 | } 506 | 507 | fn setup_raiddev( 508 | dm: &DM, 509 | froyo_id: Uuid, 510 | raid_id: Uuid, 511 | raid_save: &RaidDevSave, 512 | block_devs: &BlockDevs, 513 | ) -> FroyoResult { 514 | let mut linear_devs = Vec::new(); 515 | 516 | // Loop through saved struct and setup legs if present in both 517 | // the blockdev list and the raid members list 518 | for count in 0..raid_save.member_count { 519 | match raid_save.members.get(&count) { 520 | Some(sld) => match block_devs.0.get(&sld.parent) { 521 | Some(bm) => match *bm { 522 | BlockMember::Present(ref bd) => { 523 | let ld = Rc::new(RefCell::new(LinearDev::setup( 524 | dm, 525 | &format!("{}-{}-{}", froyo_id, raid_id, count), 526 | bd, 527 | &sld.meta_segments, 528 | &sld.data_segments, 529 | )?)); 530 | bd.borrow_mut() 531 | .linear_devs 532 | .insert(ld.borrow().meta_dev.dm_name.clone(), ld.clone()); 533 | linear_devs.push(RaidMember::Present(ld)); 534 | } 535 | BlockMember::Absent(_) => { 536 | dbgp!("Expected device absent from raid {}", raid_id); 537 | linear_devs.push(RaidMember::Absent((sld.parent, sld.clone()))); 538 | } 539 | }, 540 | None => { 541 | return Err(FroyoError::Io(io::Error::new( 542 | io::ErrorKind::InvalidInput, 543 | format!( 544 | "Invalid metadata, raiddev {} references \ 545 | blockdev {} that is not found in \ 546 | blockdev list", 547 | raid_id, sld.parent 548 | ), 549 | ))) 550 | } 551 | }, 552 | None => { 553 | dbgp!("Raid {} member not present", raid_id); 554 | linear_devs.push(RaidMember::Removed); 555 | } 556 | } 557 | } 558 | 559 | RaidDev::setup( 560 | dm, 561 | froyo_id, 562 | raid_id.to_owned(), 563 | linear_devs, 564 | raid_save.stripe_sectors, 565 | raid_save.region_sectors, 566 | ) 567 | } 568 | 569 | pub fn create_redundant_zones( 570 | &mut self, 571 | dm: &DM, 572 | id: Uuid, 573 | block_devs: &BlockDevs, 574 | ) -> FroyoResult { 575 | let mut new_zones = false; 576 | while let Some(rd) = self.create_redundant_zone(dm, id, block_devs)? { 577 | self.raids.insert(rd.id, Rc::new(RefCell::new(rd))); 578 | new_zones = true; 579 | } 580 | 581 | Ok(new_zones) 582 | } 583 | 584 | // Try to make an as-large-as-possible redundant device from the 585 | // given block devices. 586 | fn create_redundant_zone( 587 | &mut self, 588 | dm: &DM, 589 | id: Uuid, 590 | block_devs: &BlockDevs, 591 | ) -> FroyoResult> { 592 | let scratch_needed = self.scratch_needed(); 593 | 594 | // get common data area size, allowing for Froyo data at start and end 595 | let mut bd_areas: Vec<_> = block_devs 596 | .0 597 | .values() 598 | .filter_map(|bd| bd.present()) 599 | .filter_map(|bd| { 600 | bd.borrow() 601 | .largest_avail_area() 602 | .map(|x| (bd.clone(), x.0, x.1)) 603 | }) 604 | .filter(|&(_, _, len)| len >= scratch_needed) 605 | .map(|(bd, off, len)| (bd, off, len - scratch_needed)) 606 | .filter(|&(_, _, len)| len >= MIN_DATA_ZONE_SECTORS) 607 | .collect(); 608 | 609 | // Not enough devs with room for a raid device 610 | if bd_areas.len() < 2 { 611 | return Ok(None); 612 | } 613 | 614 | // Ensure we leave enough scratch space to handle a reshape 615 | let common_avail_sectors = bd_areas.iter().map(|&(_, _, len)| len).min().unwrap(); 616 | 617 | // Absolute limit on each RAID size. 618 | let common_avail_sectors = min(common_avail_sectors, MAX_DATA_ZONE_SECTORS); 619 | 620 | // Also limit size in order to try to create a certain base 621 | // number of RAIDs, for reshape shenanigans. 622 | // Use size of 2nd largest bdev, which is guaranteed to be 623 | // used fully by raids, unlike the largest. 624 | let second_largest_bdev = { 625 | let mut sizes = block_devs 626 | .0 627 | .values() 628 | .filter_map(|bm| bm.present()) 629 | .map(|bd| bd.borrow().sectors) 630 | .collect::>(); 631 | sizes.sort(); 632 | sizes.pop(); 633 | sizes.pop().unwrap() 634 | }; 635 | let clamped_size = max( 636 | second_largest_bdev / Sectors(IDEAL_RAID_COUNT as u64), 637 | MIN_DATA_ZONE_SECTORS, 638 | ); 639 | let common_avail_sectors = min(common_avail_sectors, clamped_size); 640 | 641 | // Handle raid regions and calc metadata size 642 | let (region_count, region_sectors) = { 643 | let mut region_sectors = DEFAULT_REGION_SECTORS; 644 | while *common_avail_sectors / *region_sectors > MAX_REGIONS { 645 | region_sectors = Sectors(*region_sectors * 2); 646 | } 647 | 648 | let partial_region = if common_avail_sectors % region_sectors == Sectors(0) { 649 | Sectors(0) 650 | } else { 651 | Sectors(1) 652 | }; 653 | 654 | ( 655 | common_avail_sectors / region_sectors + partial_region, 656 | region_sectors, 657 | ) 658 | }; 659 | 660 | // each region needs 1 bit in the write intent bitmap 661 | let mdata_sectors = Sectors( 662 | align_to(8192 + (*region_count / 8), SECTOR_SIZE).next_power_of_two() / SECTOR_SIZE, 663 | ); 664 | // data size must be multiple of stripe size 665 | let data_sectors = (common_avail_sectors - mdata_sectors) & Sectors(!(*STRIPE_SECTORS - 1)); 666 | 667 | let raid_uuid = Uuid::new_v4(); 668 | 669 | let mut linear_devs = Vec::new(); 670 | for (num, &mut (ref mut bd, sector_start, _)) in bd_areas.iter_mut().enumerate() { 671 | let mdata_sector_start = sector_start; 672 | let data_sector_start = SectorOffset(*mdata_sector_start + *mdata_sectors); 673 | 674 | let linear = Rc::new(RefCell::new(LinearDev::new( 675 | dm, 676 | &format!("{}-{}-{}", id, raid_uuid, num), 677 | bd, 678 | &[LinearSegment::new(mdata_sector_start, mdata_sectors)], 679 | &[LinearSegment::new(data_sector_start, data_sectors)], 680 | )?)); 681 | 682 | bd.borrow_mut() 683 | .linear_devs 684 | .insert(linear.borrow().meta_dev.dm_name.clone(), linear.clone()); 685 | 686 | linear_devs.push(RaidMember::Present(linear)); 687 | } 688 | 689 | let raid = RaidDev::setup( 690 | dm, 691 | id, 692 | raid_uuid, 693 | linear_devs, 694 | STRIPE_SECTORS, 695 | region_sectors, 696 | )?; 697 | 698 | Ok(Some(raid)) 699 | } 700 | 701 | pub fn alloc_raid_segments(&self, sectors: Sectors) -> Option> { 702 | let mut needed = sectors; 703 | let mut segs = Vec::new(); 704 | for rd in self.raids.values() { 705 | if needed == Sectors(0) { 706 | break; 707 | } 708 | if !rd.borrow().is_safe() { 709 | continue; 710 | } 711 | let (gotten, r_segs) = rd.borrow().get_some_space(needed); 712 | segs.extend( 713 | r_segs 714 | .iter() 715 | .map(|&(start, len)| RaidSegment::new(start, len, RaidLayer::Raid(rd.clone()))), 716 | ); 717 | needed = needed - gotten; 718 | } 719 | 720 | match *needed { 721 | 0 => Some(segs), 722 | _ => None, 723 | } 724 | } 725 | 726 | pub fn lookup_segment( 727 | &self, 728 | id: Uuid, 729 | start: SectorOffset, 730 | length: Sectors, 731 | ) -> Option { 732 | match self.raids.get(&id) { 733 | Some(rd) => Some(RaidSegment::new(start, length, RaidLayer::Raid(rd.clone()))), 734 | None => { 735 | // Before we give up, check the tempdev. 736 | if let Some(ref tempdev) = self.temp_dev { 737 | if tempdev.borrow().id == id { 738 | return Some(RaidSegment::new( 739 | start, 740 | length, 741 | RaidLayer::Temp(tempdev.clone()), 742 | )); 743 | } 744 | } 745 | None 746 | } 747 | } 748 | } 749 | 750 | // We need scratch space on each drive for half the largest 751 | // raiddev capacity. This is overly generous but let's just do 752 | // this until we have reshape support 753 | fn scratch_needed(&self) -> Sectors { 754 | self.raids 755 | .values() 756 | .map(|rd| rd.borrow().length) 757 | .max() 758 | .unwrap_or(Sectors(0)) 759 | / Sectors(2) 760 | + Sectors(1) 761 | } 762 | 763 | pub fn are_idle(&self) -> bool { 764 | self.raids 765 | .iter() 766 | .map(|(_, rd)| match rd.borrow().status() { 767 | Err(_) => false, 768 | Ok((_, action)) => matches!(action, RaidAction::Idle), 769 | }) 770 | .all(|res| res) 771 | } 772 | 773 | pub fn max_used_raid_sectors(&self) -> Sectors { 774 | self.raids 775 | .iter() 776 | .map(|(_, rd)| { 777 | rd.borrow() 778 | .used_areas() 779 | .into_iter() 780 | .map(|(_, len)| len) 781 | .sum_sectors() 782 | }) 783 | .max() 784 | .unwrap_or(Sectors(0)) 785 | } 786 | 787 | pub fn avail_space(&self) -> Sectors { 788 | self.raids 789 | .values() 790 | .map(|rd| rd.borrow().avail_sectors()) 791 | .sum_sectors() 792 | } 793 | 794 | pub fn total_space(&self) -> Sectors { 795 | self.raids 796 | .values() 797 | .map(|rd| rd.borrow().length) 798 | .sum_sectors() 799 | } 800 | 801 | pub fn add_new_block_device( 802 | &mut self, 803 | froyo_id: Uuid, 804 | blockdev: &Rc>, 805 | ) -> FroyoResult<()> { 806 | let dm = DM::new()?; 807 | 808 | // let existing raids know about the new disk, maybe they're degraded 809 | for raid in self.raids.values_mut() { 810 | raid.borrow_mut() 811 | .new_block_device_added(&dm, froyo_id, blockdev)?; 812 | } 813 | 814 | Ok(()) 815 | } 816 | 817 | pub fn add_existing_block_device( 818 | &mut self, 819 | froyo_id: Uuid, 820 | blockdev: &Rc>, 821 | ) -> FroyoResult<()> { 822 | let dm = DM::new()?; 823 | 824 | for raid in self.raids.values_mut() { 825 | raid.borrow_mut() 826 | .block_device_found(&dm, froyo_id, blockdev)?; 827 | } 828 | 829 | Ok(()) 830 | } 831 | 832 | pub fn teardown(&self, dm: &DM) -> FroyoResult<()> { 833 | for raid in &mut self.raids.values() { 834 | raid.borrow_mut().teardown(dm)? 835 | } 836 | 837 | Ok(()) 838 | } 839 | } 840 | 841 | // A RaidSegment will almost always be on a RaidDev, unless we're 842 | // reshaping and we had to temporarily copy it to a temp area. 843 | #[derive(Debug, Clone)] 844 | pub enum RaidLayer { 845 | Temp(Rc>), 846 | Raid(Rc>), 847 | } 848 | 849 | impl RaidLayer { 850 | pub fn dstr(&self) -> String { 851 | match *self { 852 | RaidLayer::Temp(ref td) => td.borrow().dstr(), 853 | RaidLayer::Raid(ref rd) => rd.borrow().dev.dstr(), 854 | } 855 | } 856 | 857 | pub fn on_temp(&self) -> bool { 858 | matches!(*self, RaidLayer::Temp(_)) 859 | } 860 | 861 | pub fn raid(&self) -> Rc> { 862 | match *self { 863 | RaidLayer::Raid(ref rd) => rd.clone(), 864 | _ => panic!("should never happen"), 865 | } 866 | } 867 | 868 | pub fn id(&self) -> Uuid { 869 | match *self { 870 | RaidLayer::Temp(ref td) => td.borrow().id, 871 | RaidLayer::Raid(ref rd) => rd.borrow().id, 872 | } 873 | } 874 | } 875 | 876 | // Not Clone, b/c that would mess up our parent.used stuff 877 | pub struct RaidSegment { 878 | pub start: SectorOffset, 879 | pub length: Sectors, 880 | pub parent: RaidLayer, 881 | } 882 | 883 | // The derived Debug ends up recursing, so do this instead 884 | impl fmt::Debug for RaidSegment { 885 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 886 | match self.parent { 887 | RaidLayer::Temp(ref p) => write!( 888 | f, 889 | "(temp {}, {}, {})", 890 | *self.start, 891 | *self.length, 892 | p.borrow().dstr() 893 | ), 894 | RaidLayer::Raid(ref p) => write!( 895 | f, 896 | "(raid {}, {}, {})", 897 | *self.start, 898 | *self.length, 899 | p.borrow().id 900 | ), 901 | } 902 | } 903 | } 904 | 905 | impl RaidSegment { 906 | pub fn new(start: SectorOffset, length: Sectors, parent: RaidLayer) -> RaidSegment { 907 | if let RaidLayer::Raid(ref rd) = parent { 908 | rd.borrow_mut().used.insert(start, length); 909 | } 910 | RaidSegment { 911 | start, 912 | length, 913 | parent, 914 | } 915 | } 916 | 917 | // Also update the used map over in self.parent.used. 918 | pub fn update_length(&mut self, length: Sectors) { 919 | let rd = self.parent.raid(); 920 | let mut parent = rd.borrow_mut(); 921 | let entry = parent.used.get_mut(&self.start).unwrap(); 922 | *entry = length; 923 | self.length = length; 924 | } 925 | 926 | pub fn to_save(&self) -> RaidSegmentSave { 927 | RaidSegmentSave { 928 | start: self.start, 929 | length: self.length, 930 | parent: self.parent.id(), 931 | } 932 | } 933 | } 934 | 935 | impl Drop for RaidSegment { 936 | fn drop(&mut self) { 937 | match self.parent { 938 | RaidLayer::Raid(ref rd) => { 939 | rd.borrow_mut().used.remove(&self.start); 940 | } 941 | RaidLayer::Temp(_) => {} 942 | }; 943 | } 944 | } 945 | 946 | #[derive(Debug)] 947 | pub struct RaidLinearDev { 948 | id: Uuid, 949 | pub dev: DmDevice, 950 | pub segments: Vec, 951 | } 952 | 953 | impl RaidLinearDev { 954 | pub fn dm_table(segments: &[RaidSegment]) -> Vec<(u64, u64, String, String)> { 955 | let mut table = Vec::new(); 956 | let mut offset = SectorOffset(0); 957 | for seg in segments { 958 | let dstr = seg.parent.dstr(); 959 | let line = ( 960 | *offset, 961 | *seg.length, 962 | "linear".to_owned(), 963 | format!("{} {}", dstr, *seg.start), 964 | ); 965 | table.push(line); 966 | offset = offset + SectorOffset(*seg.length); 967 | } 968 | 969 | table 970 | } 971 | 972 | pub fn new( 973 | dm: &DM, 974 | name: &str, 975 | id: Uuid, 976 | segments: Vec, 977 | ) -> FroyoResult { 978 | Self::setup(dm, name, id, segments) 979 | } 980 | 981 | pub fn setup( 982 | dm: &DM, 983 | name: &str, 984 | id: Uuid, 985 | segments: Vec, 986 | ) -> FroyoResult { 987 | let table = Self::dm_table(&segments); 988 | let dm_name = format!("froyo-raid-linear-{}", name); 989 | let linear_dev = DmDevice::new(dm, &dm_name, &table)?; 990 | 991 | Ok(RaidLinearDev { 992 | id: id.to_owned(), 993 | dev: linear_dev, 994 | segments, 995 | }) 996 | } 997 | 998 | pub fn teardown(&mut self, dm: &DM) -> FroyoResult<()> { 999 | self.dev.teardown(dm)?; 1000 | 1001 | Ok(()) 1002 | } 1003 | 1004 | pub fn to_save(&self) -> RaidLinearDevSave { 1005 | RaidLinearDevSave { 1006 | id: self.id, 1007 | segments: self.segments.iter().map(|x| x.to_save()).collect(), 1008 | } 1009 | } 1010 | 1011 | pub fn length(&self) -> Sectors { 1012 | self.segments.iter().map(|x| x.length).sum_sectors() 1013 | } 1014 | 1015 | pub fn extend(&mut self, segs: Vec) -> FroyoResult<()> { 1016 | // last existing and first new may be contiguous 1017 | let coalesced_new_first = { 1018 | let old_last = self.segments.last_mut().unwrap(); 1019 | let new_first = segs.first().unwrap(); 1020 | if old_last.parent.id() == new_first.parent.id() 1021 | && (old_last.start + SectorOffset(*old_last.length) == new_first.start) 1022 | { 1023 | let new_len = old_last.length + new_first.length; 1024 | old_last.update_length(new_len); 1025 | true 1026 | } else { 1027 | false 1028 | } 1029 | }; 1030 | 1031 | if coalesced_new_first { 1032 | self.segments.extend(segs.into_iter().skip(1)); 1033 | } else { 1034 | self.segments.extend(segs); 1035 | } 1036 | 1037 | let table = RaidLinearDev::dm_table(&self.segments); 1038 | 1039 | let dm = DM::new()?; 1040 | self.dev.reload(&dm, &table)?; 1041 | 1042 | Ok(()) 1043 | } 1044 | 1045 | pub fn parents(&self) -> BTreeMap>> { 1046 | let mut map = BTreeMap::new(); 1047 | 1048 | for rs in &self.segments { 1049 | if !rs.parent.on_temp() { 1050 | map.insert(rs.parent.id(), rs.parent.raid().clone()); 1051 | } 1052 | } 1053 | 1054 | map 1055 | } 1056 | } 1057 | -------------------------------------------------------------------------------- /src/serialize.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | use std::collections::BTreeMap; 5 | use std::path::PathBuf; 6 | 7 | use crate::types::{DataBlocks, SectorOffset, Sectors}; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | pub struct BlockDevSave { 11 | pub path: PathBuf, 12 | pub sectors: Sectors, 13 | } 14 | 15 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] 16 | pub struct LinearSegment { 17 | pub start: SectorOffset, 18 | pub length: Sectors, 19 | } 20 | 21 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 22 | pub struct LinearDevSave { 23 | pub meta_segments: Vec, 24 | pub data_segments: Vec, 25 | pub parent: Uuid, 26 | } 27 | 28 | #[derive(Debug, Clone, Serialize, Deserialize)] 29 | pub struct FroyoSave { 30 | pub name: String, 31 | pub id: Uuid, 32 | pub block_devs: BTreeMap, 33 | pub raid_devs: BTreeMap, 34 | pub thin_pool_dev: ThinPoolDevSave, 35 | pub thin_devs: Vec, 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | pub temp_dev: Option, 38 | } 39 | 40 | #[derive(Debug, Clone, Serialize, Deserialize)] 41 | pub struct TempDevSegmentSave { 42 | pub parent: Uuid, 43 | pub start: SectorOffset, 44 | pub length: Sectors, 45 | } 46 | 47 | #[derive(Debug, Clone, Serialize, Deserialize)] 48 | pub struct TempDevSave { 49 | pub id: Uuid, 50 | pub segments: Vec, 51 | } 52 | 53 | #[derive(Debug, Clone, Serialize, Deserialize)] 54 | pub struct RaidDevSave { 55 | pub stripe_sectors: Sectors, 56 | pub region_sectors: Sectors, 57 | pub length: Sectors, 58 | pub member_count: usize, 59 | pub members: BTreeMap, 60 | } 61 | 62 | #[derive(Debug, Clone, Serialize, Deserialize)] 63 | pub struct RaidSegmentSave { 64 | pub start: SectorOffset, 65 | pub length: Sectors, 66 | pub parent: Uuid, // RaidDev id 67 | } 68 | 69 | #[derive(Debug, Clone, Serialize, Deserialize)] 70 | pub struct RaidLinearDevSave { 71 | pub id: Uuid, 72 | pub segments: Vec, 73 | } 74 | 75 | #[derive(Debug, Clone, Serialize, Deserialize)] 76 | pub struct ThinPoolDevSave { 77 | pub data_block_size: Sectors, 78 | pub low_water_blocks: DataBlocks, 79 | pub meta_dev: RaidLinearDevSave, 80 | pub data_dev: RaidLinearDevSave, 81 | } 82 | 83 | #[derive(Debug, Clone, Serialize, Deserialize)] 84 | pub struct ThinDevSave { 85 | pub name: String, 86 | pub thin_number: u32, 87 | pub size: Sectors, 88 | } 89 | -------------------------------------------------------------------------------- /src/thin.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | use std::cell::RefCell; 6 | use std::fs; 7 | use std::io; 8 | use std::path::PathBuf; 9 | use std::process::Command; 10 | use std::rc::Rc; 11 | 12 | use devicemapper::dm::{DevId, DM}; 13 | use devicemapper::{consts::DmFlags, consts::DM_SUSPEND, Device}; 14 | use nix::errno::Errno::EEXIST; 15 | use nix::sys::stat::{mknod, umask, Mode, SFlag}; 16 | use uuid::Uuid; 17 | 18 | use crate::consts::*; 19 | use crate::dmdevice::DmDevice; 20 | use crate::raid::{RaidLinearDev, RaidSegment}; 21 | use crate::serialize::{ThinDevSave, ThinPoolDevSave}; 22 | use crate::types::{DataBlocks, FroyoError, FroyoResult, InternalError, Sectors}; 23 | 24 | #[derive(Debug, Clone)] 25 | pub struct ThinPoolDev { 26 | dev: DmDevice, 27 | data_block_size: Sectors, 28 | pub low_water_blocks: DataBlocks, 29 | params: String, 30 | pub meta_dev: Rc>, 31 | pub data_dev: Rc>, 32 | } 33 | 34 | #[derive(Debug, Clone, Copy)] 35 | pub struct ThinPoolBlockUsage { 36 | pub used_meta: u64, 37 | pub total_meta: u64, 38 | pub used_data: DataBlocks, 39 | pub total_data: DataBlocks, 40 | } 41 | 42 | #[derive(Debug, Clone, Copy)] 43 | pub enum ThinPoolStatus { 44 | Good((ThinPoolWorkingStatus, ThinPoolBlockUsage)), 45 | Fail, 46 | } 47 | 48 | #[derive(Debug, Clone, Copy)] 49 | pub enum ThinPoolWorkingStatus { 50 | Good, 51 | ReadOnly, 52 | OutOfSpace, 53 | NeedsCheck, 54 | } 55 | 56 | impl ThinPoolDev { 57 | pub fn new( 58 | dm: &DM, 59 | id: Uuid, 60 | meta_segs: Vec, 61 | data_segs: Vec, 62 | ) -> FroyoResult { 63 | // meta 64 | let meta_name = format!("thin-meta-{}", id); 65 | let meta_raid_dev = RaidLinearDev::new(dm, &meta_name, Uuid::new_v4(), meta_segs)?; 66 | 67 | meta_raid_dev.dev.clear()?; 68 | 69 | // data 70 | let data_name = format!("thin-data-{}", id); 71 | let data_raid_dev = RaidLinearDev::new(dm, &data_name, Uuid::new_v4(), data_segs)?; 72 | 73 | ThinPoolDev::setup( 74 | dm, 75 | id, 76 | DATA_BLOCK_SIZE, 77 | DataBlocks(TPOOL_LOW_WATER_BLOCKS), 78 | meta_raid_dev, 79 | data_raid_dev, 80 | ) 81 | } 82 | 83 | pub fn setup( 84 | dm: &DM, 85 | id: Uuid, 86 | data_block_size: Sectors, 87 | low_water_blocks: DataBlocks, 88 | meta_raid_dev: RaidLinearDev, 89 | data_raid_dev: RaidLinearDev, 90 | ) -> FroyoResult { 91 | let params = format!( 92 | "{} {} {} {} 1 skip_block_zeroing", 93 | meta_raid_dev.dev.dstr(), 94 | data_raid_dev.dev.dstr(), 95 | *data_block_size, 96 | *low_water_blocks 97 | ); 98 | let table = [(0u64, *data_raid_dev.length(), "thin-pool", &*params)]; 99 | 100 | let dm_name = format!("froyo-thin-pool-{}", id); 101 | let pool_dev = DmDevice::new(dm, &dm_name, &table)?; 102 | 103 | let tpool = ThinPoolDev { 104 | dev: pool_dev, 105 | data_block_size, 106 | low_water_blocks, 107 | params: params.clone(), 108 | meta_dev: Rc::new(RefCell::new(meta_raid_dev)), 109 | data_dev: Rc::new(RefCell::new(data_raid_dev)), 110 | }; 111 | 112 | // TODO: if needs_check, run the check 113 | match tpool.status()? { 114 | ThinPoolStatus::Good((ThinPoolWorkingStatus::Good, _)) => {} 115 | ThinPoolStatus::Good((ThinPoolWorkingStatus::NeedsCheck, _)) => { 116 | return Err(FroyoError::Froyo(InternalError( 117 | "Froyodev thin pool needs a check".into(), 118 | ))) 119 | } 120 | bad => { 121 | return Err(FroyoError::Froyo(InternalError( 122 | format!("Froyodev has a failed thin pool: {:?}", bad).into(), 123 | ))) 124 | } 125 | } 126 | 127 | Ok(tpool) 128 | } 129 | 130 | pub fn teardown(&mut self, dm: &DM) -> FroyoResult<()> { 131 | self.dev.teardown(dm)?; 132 | self.meta_dev.borrow_mut().teardown(dm)?; 133 | self.data_dev.borrow_mut().teardown(dm)?; 134 | 135 | Ok(()) 136 | } 137 | 138 | pub fn to_save(&self) -> ThinPoolDevSave { 139 | ThinPoolDevSave { 140 | data_block_size: self.data_block_size, 141 | low_water_blocks: self.low_water_blocks, 142 | meta_dev: self.meta_dev.borrow().to_save(), 143 | data_dev: self.data_dev.borrow().to_save(), 144 | } 145 | } 146 | 147 | pub fn status(&self) -> FroyoResult { 148 | let dm = DM::new()?; 149 | 150 | let mut status = self.dev.table_status(&dm)?; 151 | 152 | if status.len() != 1 { 153 | return Err(FroyoError::Io(io::Error::new( 154 | io::ErrorKind::InvalidData, 155 | "Expected 1 line from thin pool status", 156 | ))); 157 | } 158 | 159 | let status_line = status.pop().unwrap().3; 160 | if status_line.starts_with("Fail") { 161 | return Ok(ThinPoolStatus::Fail); 162 | } 163 | 164 | let status_vals = status_line.split(' ').collect::>(); 165 | if status_vals.len() < 8 { 166 | return Err(FroyoError::Io(io::Error::new( 167 | io::ErrorKind::InvalidData, 168 | "Kernel returned too few values from thin pool status", 169 | ))); 170 | } 171 | 172 | let usage = { 173 | let meta_vals = status_vals[1].split('/').collect::>(); 174 | let data_vals = status_vals[2].split('/').collect::>(); 175 | ThinPoolBlockUsage { 176 | used_meta: meta_vals[0].parse::().unwrap(), 177 | total_meta: meta_vals[1].parse::().unwrap(), 178 | used_data: DataBlocks(data_vals[0].parse::().unwrap()), 179 | total_data: DataBlocks(data_vals[1].parse::().unwrap()), 180 | } 181 | }; 182 | 183 | match status_vals[7] { 184 | "-" => {} 185 | "needs_check" => { 186 | return Ok(ThinPoolStatus::Good(( 187 | ThinPoolWorkingStatus::NeedsCheck, 188 | usage, 189 | ))) 190 | } 191 | _ => { 192 | return Err(FroyoError::Io(io::Error::new( 193 | io::ErrorKind::InvalidData, 194 | "Kernel returned unexpected value in thin pool status", 195 | ))) 196 | } 197 | } 198 | 199 | match status_vals[4] { 200 | "rw" => Ok(ThinPoolStatus::Good((ThinPoolWorkingStatus::Good, usage))), 201 | "ro" => Ok(ThinPoolStatus::Good(( 202 | ThinPoolWorkingStatus::ReadOnly, 203 | usage, 204 | ))), 205 | "out_of_data_space" => Ok(ThinPoolStatus::Good(( 206 | ThinPoolWorkingStatus::OutOfSpace, 207 | usage, 208 | ))), 209 | _ => Err(FroyoError::Io(io::Error::new( 210 | io::ErrorKind::InvalidData, 211 | "Kernel returned unexpected value in thin pool status", 212 | ))), 213 | } 214 | } 215 | 216 | // return size of a data block in bytes 217 | pub fn data_block_size(&self) -> u64 { 218 | *self.data_block_size * SECTOR_SIZE 219 | } 220 | 221 | pub fn sectors_to_blocks(&self, sectors: Sectors) -> DataBlocks { 222 | DataBlocks(*sectors / *self.data_block_size) 223 | } 224 | 225 | pub fn blocks_to_sectors(&self, blocks: DataBlocks) -> Sectors { 226 | Sectors(*blocks * *self.data_block_size) 227 | } 228 | 229 | pub fn extend_data_dev(&mut self, segs: Vec) -> FroyoResult<()> { 230 | self.data_dev.borrow_mut().extend(segs)?; 231 | self.dm_reload()?; 232 | Ok(()) 233 | } 234 | 235 | pub fn extend_meta_dev(&mut self, segs: Vec) -> FroyoResult<()> { 236 | self.meta_dev.borrow_mut().extend(segs)?; 237 | self.dm_reload()?; 238 | Ok(()) 239 | } 240 | 241 | fn dm_reload(&self) -> FroyoResult<()> { 242 | let dm = DM::new()?; 243 | let table = [( 244 | 0u64, 245 | *self.data_dev.borrow().length(), 246 | "thin-pool", 247 | &*self.params, 248 | )]; 249 | 250 | self.dev.reload(&dm, &table)?; 251 | 252 | Ok(()) 253 | } 254 | 255 | pub fn used_sectors(&self) -> Sectors { 256 | self.meta_dev.borrow().length() + self.data_dev.borrow().length() 257 | } 258 | } 259 | 260 | #[derive(Debug, Clone)] 261 | pub struct ThinDev { 262 | dev: DmDevice, 263 | name: String, 264 | pub thin_number: u32, 265 | pub size: Sectors, 266 | dm_name: String, 267 | params: String, 268 | } 269 | 270 | #[derive(Debug, Clone, Copy)] 271 | pub enum ThinStatus { 272 | Good(Sectors), 273 | Fail, 274 | } 275 | 276 | impl ThinDev { 277 | pub fn new( 278 | dm: &DM, 279 | froyo_id: Uuid, 280 | name: &str, 281 | thin_number: u32, 282 | size: Sectors, 283 | pool_dev: &ThinPoolDev, 284 | ) -> FroyoResult { 285 | pool_dev 286 | .dev 287 | .message(dm, &format!("create_thin {}", thin_number))?; 288 | 289 | let mut td = ThinDev::setup(dm, froyo_id, name, thin_number, size, pool_dev)?; 290 | 291 | td.create_fs(name)?; 292 | 293 | Ok(td) 294 | } 295 | 296 | pub fn setup( 297 | dm: &DM, 298 | froyo_id: Uuid, 299 | name: &str, 300 | thin_number: u32, 301 | size: Sectors, 302 | pool_dev: &ThinPoolDev, 303 | ) -> FroyoResult { 304 | let params = format!("{} {}", pool_dev.dev.dstr(), thin_number); 305 | let table = [(0u64, *size, "thin", &*params)]; 306 | 307 | let dm_name = format!("froyo-thin-{}-{}", froyo_id, thin_number); 308 | let thin_dev = DmDevice::new(dm, &dm_name, &table)?; 309 | 310 | ThinDev::create_devnode(name, thin_dev.dev)?; 311 | 312 | let thin = ThinDev { 313 | dev: thin_dev, 314 | name: name.to_owned(), 315 | thin_number, 316 | size, 317 | dm_name, 318 | params: params.clone(), 319 | }; 320 | 321 | if let ThinStatus::Fail = thin.status()? { 322 | return Err(FroyoError::Froyo(InternalError( 323 | "Froyodev thin device is failed".into(), 324 | ))); 325 | } 326 | 327 | Ok(thin) 328 | } 329 | 330 | pub fn teardown(&mut self, dm: &DM) -> FroyoResult<()> { 331 | // Do this first so if devnode is in use this fails before we 332 | // remove the devnode 333 | self.dev.teardown(dm)?; 334 | self.remove_devnode()?; 335 | 336 | Ok(()) 337 | } 338 | 339 | pub fn extend(&mut self, sectors: Sectors) -> FroyoResult<()> { 340 | self.size = self.size + sectors; 341 | 342 | let dm = DM::new()?; 343 | let id = &DevId::Name(&self.dm_name); 344 | 345 | let table = [(0u64, *self.size, "thin", &*self.params)]; 346 | 347 | dm.table_load(id, &table)?; 348 | dm.device_suspend(id, DM_SUSPEND)?; 349 | dm.device_suspend(id, DmFlags::empty())?; 350 | 351 | // TODO: we need to know where it's mounted in order to call 352 | // this 353 | // let output = try!(Command::new("xfs_growfs") 354 | // .arg(&mount_point) 355 | // .output()); 356 | 357 | Ok(()) 358 | } 359 | 360 | pub fn to_save(&self) -> ThinDevSave { 361 | ThinDevSave { 362 | name: self.name.clone(), 363 | thin_number: self.thin_number, 364 | size: self.size, 365 | } 366 | } 367 | 368 | pub fn status(&self) -> FroyoResult { 369 | let dm = DM::new()?; 370 | 371 | let (_, mut status) = dm.table_status(&DevId::Name(&self.dm_name), DmFlags::empty())?; 372 | 373 | if status.len() != 1 { 374 | return Err(FroyoError::Io(io::Error::new( 375 | io::ErrorKind::InvalidData, 376 | "Expected 1 line from thin status", 377 | ))); 378 | } 379 | 380 | // We should either get 1 line or the kernel is broken 381 | let status_line = status.pop().unwrap().3; 382 | if status_line.starts_with("Fail") { 383 | return Ok(ThinStatus::Fail); 384 | } 385 | let status_vals = status_line.split(' ').collect::>(); 386 | 387 | Ok(ThinStatus::Good(Sectors( 388 | status_vals[0].parse::().unwrap(), 389 | ))) 390 | } 391 | 392 | fn create_devnode(name: &str, dev: Device) -> FroyoResult<()> { 393 | let mut pathbuf = PathBuf::from("/dev/froyo"); 394 | 395 | if let Err(e) = fs::create_dir(&pathbuf) { 396 | if e.kind() != io::ErrorKind::AlreadyExists { 397 | return Err(FroyoError::Io(e)); 398 | } 399 | } 400 | 401 | pathbuf.push(name); 402 | 403 | let old_umask = umask(Mode::empty()); 404 | let res = mknod( 405 | &pathbuf, 406 | SFlag::S_IFBLK, 407 | Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IWGRP, 408 | dev.into(), 409 | ); 410 | umask(old_umask); 411 | if let Err(e) = res { 412 | if e != EEXIST { 413 | return Err(FroyoError::Nix(e)); 414 | } 415 | } 416 | 417 | Ok(()) 418 | } 419 | 420 | fn remove_devnode(&mut self) -> FroyoResult<()> { 421 | let mut pathbuf = PathBuf::from("/dev/froyo"); 422 | pathbuf.push(&self.name); 423 | fs::remove_file(&pathbuf)?; 424 | 425 | Ok(()) 426 | } 427 | 428 | fn create_fs(&mut self, name: &str) -> FroyoResult<()> { 429 | let dev_name = format!("/dev/froyo/{}", name); 430 | let output = Command::new("mkfs.xfs").arg("-f").arg(&dev_name).output()?; 431 | 432 | if output.status.success() { 433 | dbgp!("Created xfs filesystem on {}", dev_name) 434 | } else { 435 | return Err(FroyoError::Froyo(InternalError( 436 | format!( 437 | "XFS mkfs error: {}", 438 | String::from_utf8_lossy(&output.stderr) 439 | ) 440 | .into(), 441 | ))); 442 | } 443 | Ok(()) 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | use std::borrow::Cow; 6 | use std::error::Error; 7 | use std::fmt; 8 | use std::io; 9 | use std::ops::{Add, Deref}; 10 | 11 | use dbus; 12 | use macro_attr_2018::macro_attr; 13 | use newtype_derive_2018::*; 14 | use nix; 15 | use serde; 16 | use serde_json; 17 | use term; 18 | 19 | pub type FroyoResult = Result; 20 | 21 | // 22 | // Use distinct 'newtype' types for sectors and sector offsets for type safety. 23 | // When needed, these can still be derefed to u64. 24 | // Derive a bunch of stuff so we can do ops on them. 25 | // 26 | macro_attr! { 27 | #[derive(NewtypeAdd!, NewtypeSub!, 28 | NewtypeBitAnd!, NewtypeNot!, NewtypeDiv!, NewtypeRem!, 29 | NewtypeMul!, 30 | Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] 31 | pub struct Sectors(pub u64); 32 | } 33 | 34 | // `SumSectors` can be discarded once `std::iter::Sum` is stable. 35 | pub trait SumSectors: Iterator 36 | where 37 | Sectors: Add, 38 | { 39 | fn sum_sectors(self) -> Sectors 40 | where 41 | Self: Sized, 42 | { 43 | self.fold(Sectors(0), Add::add) 44 | } 45 | } 46 | 47 | impl SumSectors for T where Sectors: Add {} 48 | 49 | impl Deref for Sectors { 50 | type Target = u64; 51 | fn deref(&self) -> &Self::Target { 52 | &self.0 53 | } 54 | } 55 | 56 | impl serde::Serialize for Sectors { 57 | fn serialize(&self, serializer: S) -> Result 58 | where 59 | S: serde::Serializer, 60 | { 61 | serializer.serialize_u64(**self) 62 | } 63 | } 64 | 65 | impl<'de> serde::Deserialize<'de> for Sectors { 66 | fn deserialize(deserializer: D) -> Result 67 | where 68 | D: serde::de::Deserializer<'de>, 69 | { 70 | let val = serde::Deserialize::deserialize(deserializer)?; 71 | Ok(Sectors(val)) 72 | } 73 | } 74 | 75 | macro_attr! { 76 | #[derive(NewtypeAdd!, NewtypeSub!, 77 | NewtypeBitAnd!, NewtypeNot!, NewtypeDiv!, NewtypeRem!, 78 | NewtypeMul!, 79 | Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] 80 | pub struct SectorOffset(pub u64); 81 | } 82 | 83 | impl Deref for SectorOffset { 84 | type Target = u64; 85 | fn deref(&self) -> &Self::Target { 86 | &self.0 87 | } 88 | } 89 | 90 | impl serde::Serialize for SectorOffset { 91 | fn serialize(&self, serializer: S) -> Result 92 | where 93 | S: serde::Serializer, 94 | { 95 | serializer.serialize_u64(**self) 96 | } 97 | } 98 | 99 | impl<'de> serde::Deserialize<'de> for SectorOffset { 100 | fn deserialize(deserializer: D) -> Result 101 | where 102 | D: serde::de::Deserializer<'de>, 103 | { 104 | let val = serde::Deserialize::deserialize(deserializer)?; 105 | Ok(SectorOffset(val)) 106 | } 107 | } 108 | 109 | // A type for Data Blocks as used by the thin pool. 110 | macro_attr! { 111 | #[derive(NewtypeAdd!, NewtypeSub!, 112 | NewtypeBitAnd!, NewtypeNot!, NewtypeDiv!, NewtypeRem!, 113 | NewtypeMul!, 114 | Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] 115 | pub struct DataBlocks(pub u64); 116 | } 117 | 118 | impl Deref for DataBlocks { 119 | type Target = u64; 120 | fn deref(&self) -> &Self::Target { 121 | &self.0 122 | } 123 | } 124 | 125 | impl serde::Serialize for DataBlocks { 126 | fn serialize(&self, serializer: S) -> Result 127 | where 128 | S: serde::Serializer, 129 | { 130 | serializer.serialize_u64(**self) 131 | } 132 | } 133 | 134 | impl<'de> serde::Deserialize<'de> for DataBlocks { 135 | fn deserialize(deserializer: D) -> Result 136 | where 137 | D: serde::de::Deserializer<'de>, 138 | { 139 | let val = serde::Deserialize::deserialize(deserializer)?; 140 | Ok(DataBlocks(val)) 141 | } 142 | } 143 | 144 | // 145 | // An error type for errors generated within Froyo 146 | // 147 | #[derive(Debug, Clone)] 148 | pub struct InternalError(pub Cow<'static, str>); 149 | 150 | impl fmt::Display for InternalError { 151 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 152 | write!(f, "{}", self.0) 153 | } 154 | } 155 | 156 | impl Error for InternalError { 157 | fn description(&self) -> &str { 158 | &self.0 159 | } 160 | } 161 | 162 | // Define a common error enum. 163 | // See http://blog.burntsushi.net/rust-error-handling/ 164 | #[derive(Debug)] 165 | pub enum FroyoError { 166 | Froyo(InternalError), 167 | Io(io::Error), 168 | Serde(serde_json::error::Error), 169 | Nix(nix::Error), 170 | Dbus(dbus::Error), 171 | Term(term::Error), 172 | } 173 | 174 | impl fmt::Display for FroyoError { 175 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 176 | match *self { 177 | FroyoError::Froyo(ref err) => write!(f, "Froyo error: {}", err.0), 178 | FroyoError::Io(ref err) => write!(f, "IO error: {}", err), 179 | FroyoError::Serde(ref err) => write!(f, "Serde error: {}", err), 180 | FroyoError::Nix(ref err) => write!(f, "Nix error: {}", err), 181 | FroyoError::Dbus(ref err) => { 182 | write!(f, "Dbus error: {}", err.message().unwrap_or("Unknown")) 183 | } 184 | FroyoError::Term(ref err) => write!(f, "Term error: {}", err), 185 | } 186 | } 187 | } 188 | 189 | impl Error for FroyoError {} 190 | 191 | impl From for FroyoError { 192 | fn from(err: InternalError) -> FroyoError { 193 | FroyoError::Froyo(err) 194 | } 195 | } 196 | 197 | impl From for FroyoError { 198 | fn from(err: io::Error) -> FroyoError { 199 | FroyoError::Io(err) 200 | } 201 | } 202 | 203 | impl From for FroyoError { 204 | fn from(err: serde_json::error::Error) -> FroyoError { 205 | FroyoError::Serde(err) 206 | } 207 | } 208 | 209 | impl From for FroyoError { 210 | fn from(err: nix::Error) -> FroyoError { 211 | FroyoError::Nix(err) 212 | } 213 | } 214 | 215 | impl From for FroyoError { 216 | fn from(err: dbus::Error) -> FroyoError { 217 | FroyoError::Dbus(err) 218 | } 219 | } 220 | 221 | impl From for FroyoError { 222 | fn from(err: term::Error) -> FroyoError { 223 | FroyoError::Term(err) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | use std::fs::File; 6 | use std::os::unix::prelude::AsRawFd; 7 | 8 | use nix::ioctl_read; 9 | use uuid::Uuid; 10 | 11 | use crate::types::{FroyoError, FroyoResult}; 12 | 13 | pub fn align_to(num: u64, align_to: u64) -> u64 { 14 | let agn = align_to - 1; 15 | 16 | (num + agn) & !agn 17 | } 18 | 19 | ioctl_read!(blkgetsize64, 0x12, 114, u64); 20 | 21 | pub fn blkdev_size(file: &File) -> FroyoResult { 22 | let mut val: u64 = 0; 23 | 24 | match unsafe { blkgetsize64(file.as_raw_fd(), &mut val) } { 25 | Err(x) => Err(FroyoError::Nix(x)), 26 | Ok(_) => Ok(val), 27 | } 28 | } 29 | 30 | pub fn short_id(id: Uuid) -> String { 31 | let mut shortstr = id 32 | .as_simple() 33 | .encode_lower(&mut Uuid::encode_buffer()) 34 | .to_owned(); 35 | shortstr.truncate(8); 36 | shortstr 37 | } 38 | --------------------------------------------------------------------------------