Compare commits

..

378 Commits

Author SHA1 Message Date
pepe 77d2feb230 :) 2023-05-13 00:20:51 +00:00
Ming-Hung Tsai 8806dfe4f4 [cache_writeback] Fix space map commits
Fix github issue #138
2022-01-10 23:31:10 +08:00
Joe Thornber 8f9f9c74f6
Merge pull request #193 from arrikto/feature-fix-era-invalidate
[era_invalidate] Don't read the live metadata when the --metadata-snapshot option is provided
2022-01-05 11:45:08 +00:00
Nikos Tsironis 9f30793355 [era_invalidate] Don't read the live metadata when the --metadata-snapshot option is provided
Until now, 'era_invalidate' read the live metadata (superblock), instead
of the metadata snapshot, when using the --metadata-snapshot parameter.

Fix this by passing the location of the metadata snapshot to
'open_metadata()', when a metadata snapshot is used.

Signed-off-by: Nikos Tsironis <ntsironis@arrikto.com>
2022-01-05 12:30:02 +02:00
Joe Thornber cab57534c6
Merge pull request #191 from mingnus/2021-10-20-era-tools-rebase
era-tools in Rust
2021-11-01 09:51:59 +00:00
Ming-Hung Tsai f3c2ade90a [thin_ll_dump] Fix potential segfault while reading invalid subtree roots 2021-10-21 21:58:09 +08:00
Ming-Hung Tsai e5f0acd288 [thin_metadata_size (rust)] First code drop 2021-10-21 17:51:28 +08:00
Ming-Hung Tsai 9ea75ba113 [tests] Move cache_metadata_size tests to Rust
Changes for the Rust version in corresponding to command line changes:
- Disable tests 'all_args_agree' and 'conradictory_info_fails'
- Test conflicts between --nr-blocks and {--device-size|--block-size}
2021-10-21 17:51:28 +08:00
Ming-Hung Tsai 34f6b6fc62 [cache_metadata_size (rust)] First code drop 2021-10-21 17:51:28 +08:00
Ming-Hung Tsai cc89cef43f [era_dump (rust)] Dump the current_writeset that has not been archived
This patch is added in corresponding to the dm-era patch de89afc1 in kernel:
dm era: Recover committed writeset after crash
2021-10-21 17:51:28 +08:00
Ming-Hung Tsai 533e174051 [era_dump (rust)] Support wrapped-around era in logical dump 2021-10-21 17:51:28 +08:00
Ming-Hung Tsai 89e568b897 [era_dump (rust)] Implement logical dump 2021-10-21 17:51:28 +08:00
Ming-Hung Tsai cbc3baba45 [tests] Move era_dump tests to Rust 2021-10-21 17:51:28 +08:00
Ming-Hung Tsai f6eb5173c9 [era_restore] Avoid touching the output file by checking input file earlier
The output file has been checked by the caller, so there's no need
to check the output file again.
2021-10-21 17:51:28 +08:00
Ming-Hung Tsai 8d3f65244d [tests] Move era_restore tests to Rust 2021-10-21 17:51:28 +08:00
Ming-Hung Tsai 4cfe93570c [tests] Add era test fixtures 2021-10-21 17:51:28 +08:00
Ming-Hung Tsai 321fce882f [tests] Move era_check tests to Rust 2021-10-21 17:51:28 +08:00
Ming-Hung Tsai 0215b5fecd [era_check (rust)] Remove unused option 2021-10-21 17:51:28 +08:00
Ming-Hung Tsai 8fa7f96dfd [era_dump/restore (rust)] Support displaying writeset blocks in ranges
- Group adjacent marked blocks into ranges
- Show marked blocks only, and ignore untouched blocks
2021-10-21 17:49:36 +08:00
Ming-Hung Tsai 4559039066 [era_invalidate (rust)] First code drop 2021-10-21 17:04:54 +08:00
Ming-Hung Tsai 0791208ca4 [io_engine (rust)] Open file exclusively 2021-10-21 16:18:14 +08:00
Ming-Hung Tsai 9ab4172790 [cache_restore (rust)] Tidy up 2021-10-21 16:18:14 +08:00
Ming-Hung Tsai 36767bcda6 [era_repair (rust)] First code drop 2021-10-21 16:18:14 +08:00
Ming-Hung Tsai 3a8dc8da2d [xml (rust)] Log invalid attribute name 2021-10-21 16:18:14 +08:00
Ming-Hung Tsai ed7480e96d [era_restore (rust)] First code drop 2021-10-21 16:18:14 +08:00
Ming-Hung Tsai 55a81c0b9f [era_dump (rust)] First code drop 2021-10-21 16:18:14 +08:00
Ming-Hung Tsai 8a1399e3bb [cache_dump (rust)] Make use of the --repair option
Ignore non-fatal errors, and regenerate unavailable dirty bits if --repair was applied.
2021-10-21 16:18:14 +08:00
Ming-Hung Tsai 3d36456a36 [era_check (rust)] First code drop 2021-10-21 16:18:14 +08:00
Ming-Hung Tsai c8a1da1df9 [all] Apply cargo fmt, and fix clippy warnings 2021-10-20 20:23:27 +08:00
Ming-Hung Tsai 13aeefcdeb [thin] Fix typo 2021-10-20 20:23:27 +08:00
Joe Thornber 959b04ecb5 [thin_dump] fix overrides 2021-10-20 13:06:15 +01:00
Joe Thornber 56ea130650 [tests/thin_dump] Update help string 2021-10-19 15:24:16 +01:00
Joe Thornber a568da8c71 [tests/thin_dump] remove some extraneous output to stderr 2021-10-19 15:23:17 +01:00
Joe Thornber c5645d798f [tests/thin_delta] fix bad_option check 2021-10-19 14:25:56 +01:00
Joe Thornber 44d29b0467 [tests/thin_check] Clean up test dir 2021-10-19 14:25:25 +01:00
Joe Thornber 98be38dc4c [tests] Improve stdout/stderr logging when running sub processes 2021-10-19 14:20:47 +01:00
Joe Thornber 13400c2465 [tests/thin_delta] thin_delta should be a c++ command 2021-10-19 14:11:56 +01:00
Joe Thornber 6c5405ccf8 [tests] Get the run_* functions to log what they're doing. 2021-10-18 16:59:17 +01:00
Joe Thornber 024554c987 Merge rust tools into a single pdata_tools exe 2021-10-11 12:07:26 +01:00
Joe Thornber c9b47437f2
Merge pull request #189 from mingnus/2021-08-30-functional-tests
Functional tests and misc fixes
2021-09-30 15:15:21 +01:00
Ming-Hung Tsai d712190db4 [io_engine (rust)] Open file exclusively 2021-09-22 17:07:56 +08:00
Ming-Hung Tsai 6bd7741dfa [cache_repair] Avoid touching the output file by checking input file earlier
The output file has been checked by the caller, so there's no need
to check the output file again.
2021-09-22 17:07:56 +08:00
Ming-Hung Tsai 5abb92838c [tests] Port the remaining cache tests 2021-09-22 17:07:56 +08:00
Ming-Hung Tsai 66c1d629a4 [all (rust)] Keep silent if --quiet was applied 2021-09-22 17:07:56 +08:00
Ming-Hung Tsai d436f35ed3 [thin_dump/thin_repair/thin_restore (rust)] Fix errors in tests
- Move error messages to stderr
- Fix the transaction_id and data_block_size in test fixture,
  and check the data_block_size in thin_restore.
- Disable the missing_something tests for Rust. The transaction_id
  and nr_data_blocks now could be recovered automatically.
- Limit the use of override options in test cases. Only broken
  superblocks could be overridden, and the provided values
  should be compatible to the original metadata.
- Remove unused option
2021-09-22 17:06:28 +08:00
Ming-Hung Tsai c133e62353 [thin_dump] Emit superblock flags 2021-09-22 17:05:59 +08:00
Ming-Hung Tsai 34f927d989 [thin_check (rust)] Make better use of Rust's Result type
Replace the bail_out checking with the returned Result, which helps
decoupling the internal state of Report from application logic.
2021-09-22 17:05:59 +08:00
Ming-Hung Tsai 438730951e [thin_check (rust)] Allow ignoring non-fatal errors in mapping tree 2021-09-22 17:05:59 +08:00
Ming-Hung Tsai a18fd60f3f [thin_check (rust)] Fix auto-repair related errors
- Returns error on metadata leaks
- Clear needs_check flag on success
- Check auto-repair write errors
- Fix file open flags, and correct spelling
2021-09-22 17:05:59 +08:00
Ming-Hung Tsai e7fa012701 [btree] Fix ref-counting on overwritten values 2021-09-22 17:05:59 +08:00
Ming-Hung Tsai f8c40a1fda [thin_check (rust)] Set compatibility between options 2021-09-22 17:05:59 +08:00
Ming-Hung Tsai bb53083271 [array (rust)] Fix building uncontiguous array 2021-09-22 17:05:59 +08:00
Ming-Hung Tsai 2f22a8c55d [all (rust)] Fix errors in testing input option
- Fix file mode bits checking
- Return error reason from stat
2021-09-22 17:05:59 +08:00
Ming-Hung Tsai 4ed2348b36 [cache_restore (rust)] Fix errors in tests
- Move error messages to stderr (fixes unwritable_output_file,
  input_file_not_found, and missing_input_option)
- Validate XML structures implicitly (fixes corrupted_input_data)
- Check output file size (fixes tiny_output_file)
- Allow restoring XML without the hints (for creating test fixtures)
- Provide XML error context
- Remove unused option
2021-09-22 17:05:29 +08:00
Ming-Hung Tsai 59e44667a9 [tests] Port cache_restore tests 2021-09-16 21:55:46 +08:00
Ming-Hung Tsai 9253117132 [tests] Add cache fixtures
- Generate temp xml and metadata files
- Correct existing tests to use cache fixtures
- Fix cache xml generation
2021-09-16 21:55:46 +08:00
Ming-Hung Tsai 0acc57d17f [thin_restore (rust)] Fix errors in tests
- Move error messages to stderr (fixes unwritable_output_file,
  input_file_not_found, and missing_input_option)
- Validate XML structures implicitly (fixes corrupted_input_data)
- Check output file size (fixes tiny_output_file)
- Provide XML error context
- Fix mk_valid_input()
2021-09-16 21:55:46 +08:00
Ming-Hung Tsai aaa84aa35a [tests] Enable tests for rust thin_repair 2021-09-16 21:55:46 +08:00
Ming-Hung Tsai ea8dcb54d0 [tests] Fix the file size for testing 2021-09-16 21:55:46 +08:00
Ming-Hung Tsai 737cf2f67d [tests] Refine trait names 2021-09-16 21:55:20 +08:00
Ming-Hung Tsai 47d39d1efa [tests] Do not assume no stderr with thin_dump 2021-09-10 23:50:36 +08:00
Joe Thornber 9aa36f017a
Merge pull request #188 from mingnus/2021-08-05-thin-repair-find-roots
Support broken superblock in thin_repair
2021-09-10 13:59:16 +01:00
Ming-Hung Tsai 0d87619a93 [thin_repair/thin_dump (rust)] Show repairing progress 2021-08-27 17:30:56 +08:00
Ming-Hung Tsai 71d47ef58b [thin/cache (rust)] Validate superblock checksum 2021-08-27 11:17:27 +08:00
Ming-Hung Tsai 8a822cebec [thin_repair/thin_dump (rust)] Support rebuilding superblock 2021-08-27 02:17:57 +08:00
Ming-Hung Tsai 213442bffd [all (rust)] Fix cosmetic names for argument values 2021-08-27 02:17:57 +08:00
Ming-Hung Tsai 434d24d10a [thin_repair (rust)] Support setting missing superblock fields automatically
While rebuilding a superblock, the transaction_id, data_block_size, and
nr_data_blocks could be inherited from the current superblock if available
(assumed it's a multiple-activated copy, so supposed partially valid),
or derived from the probed data mappings and device details, that saves
the hassle of manual input.

Note that either the current superblock or user-overrides must be compatible
with the probed values, thus the filters are applied.
2021-08-27 02:17:57 +08:00
Ming-Hung Tsai 5dad1097c3 [thin_repair (rust)] Add searching for missing roots
Based on the method of commit 9e20465
2021-08-27 02:17:57 +08:00
Ming-Hung Tsai b7132440d0 [space_map (rust)] Encapsulate implementations 2021-08-27 00:08:21 +08:00
Joe Thornber 15189aa28b
Merge pull request #187 from mingnus/2021-08-24-thin-repair-find-roots-fixes
Fix issues in finding roots
2021-08-26 16:23:24 +01:00
Ming-Hung Tsai e6f17d4b4f [thin_repair/thin_dump] Exclude unwanted btree nodes 2021-08-24 22:26:22 +08:00
Ming-Hung Tsai d2d6ab7926 [thin_repair/thin_dump] Check consistency of thin_ids before running a regular dump 2021-08-24 22:26:22 +08:00
Ming-Hung Tsai d1e8168fb6 [thin_repair/thin_dump] Change the label type for empty leaves
Empty leaves now are treated as bottom-level leaves, so that empty
devices could be recovered.
2021-08-24 22:26:22 +08:00
Ming-Hung Tsai c2e6db74b9 [thin_repair/thin_dump] Remove unused function parameter 2021-08-24 22:26:22 +08:00
Ming-Hung Tsai c286041f25 [thin_repair/thin_dump] Fix sorting of data mapping candidates
- Fix the references for sorting. The timestamp statistics is stored
  in node_info corresponding to the second element.
- Fix the timestamp comparison routine. The mapping root with more recent
  blocks should have higher priority.
2021-08-24 22:26:15 +08:00
Joe Thornber b58e42bb95
Merge pull request #185 from mingnus/2021-07-29-btree-builder-fixes
Fix reference counting in btree construction
2021-08-06 08:35:46 +01:00
Ming-Hung Tsai ec44d043e8 [space_map (rust)] Restrict metadata space map size 2021-08-06 13:22:13 +08:00
Ming-Hung Tsai c167403212 [space_map (rust)] Fix the maximum value of reference counts 2021-08-06 13:22:13 +08:00
Ming-Hung Tsai e158dc7601 [btree_builder] Fix issues with under populated shared nodes
A pre-built node could be under populated (less than half-full) due to
following reasons:

- A single shared leaf generated by dm-thin key removal. The residency
  could drop below 50%, until it reaches the merge threshold (33% or 44%,
  depends on its context).
- A shared root, which could have any possible nr_entries.
- Underfull shared nodes (less than 33% residency) caused by kernel issues.

To avoid producing under populated nodes, those kinds of pre-built nodes,
except the roots, will be merged into their siblings.
2021-08-06 13:22:13 +08:00
Ming-Hung Tsai bd39b570ef [btree_builder] Fix reference counts of unshifted child values
The ref counts now are handled in a straightforward method:
The pre-built subtrees serve as snapshots of potentially shared nodes.
They hold an initial ref count on the pre-built nodes and their children.
The temporary ref counts are later dropped at the end of device building.

This way fixes the ref counts of unshifted nodes, thus the 'reserve'
operation introduced by commit 6d16c58 is reverted.
2021-08-05 23:41:23 +08:00
Ming-Hung Tsai 052c9f90ea [thin/cache_repair (rust)] Fix file open options 2021-08-05 23:41:23 +08:00
Joe Thornber 63b8b9fc40 Clarify a namespace.
See issue 184.
2021-08-04 13:46:30 +01:00
Joe Thornber 03d0651d0a
Merge pull request #183 from mingnus/2021-07-08-thin-repair
First pass at thin_repair and cache_repair
2021-07-29 13:22:36 +01:00
Ming-Hung Tsai 3dc01bf962 [tests] Use IntoIterator on the array argument (requires Rust 1.53) 2021-07-27 23:51:20 +08:00
Ming-Hung Tsai 7239204b01 [cache_repair (rust)] First pass at cache_repair 2021-07-27 23:51:20 +08:00
Ming-Hung Tsai 4b9766846e [cache (rust)] Prepare for cache_repair
- Adapt function interfaces for repairing purpose
- Finalize the metadata in the Restorer
- Make the Restorer public
2021-07-27 23:51:20 +08:00
Ming-Hung Tsai 85581cdf5a [thin_repair (rust)] First pass at thin_repair 2021-07-27 23:51:20 +08:00
Ming-Hung Tsai dccd844714 [thin_restore (rust)] Prepare for thin_repair
- Finalize the metadata in the Restorer
- Make the Restorer public
2021-07-27 23:51:20 +08:00
Ming-Hung Tsai d42bde371f [thin_dump (rust)] Prepare for thin_repair
- Adapt function interfaces for repairing purpose
- Pull out reuseable structures
2021-07-27 23:51:20 +08:00
Ming-Hung Tsai 56d7da66aa [thin_dump (rust)] Tidy up
- Do not use shared internals as Defs
- Build Defs with common leaf sequences only, that could forms
  more common Defs between partially shared subtrees.
2021-07-27 23:45:02 +08:00
Ming-Hung Tsai 7002a8ae8d [all (rust)] Pull out structures for intermediate representation
Also fix the data type for thin timestamp
2021-07-21 18:46:06 +08:00
Joe Thornber 6d31d4def2
Merge pull request #182 from mingnus/2021-06-23-functional-tests
Clean up function test commons
2021-07-21 11:27:05 +01:00
Ming-Hung Tsai 222b4f0902 [tests] Do not perform Path to str conversion for compatibility 2021-07-21 18:00:20 +08:00
Ming-Hung Tsai 16190f0f9a [tests] Pull out common submodules
New modules: fixture, process, program, target, and thin
2021-07-21 17:50:34 +08:00
Ming-Hung Tsai 66b49e6f3d [tests] Change the path type to OsString for compatibility 2021-07-21 17:27:13 +08:00
Joe Thornber b92151d527
Merge pull request #181 from mingnus/2021-06-23-functional-tests
Functional tests revised
2021-07-19 14:01:37 +01:00
Ming-Hung Tsai a50c9d97e2 [thin_metadata_unpack] Fix truncated output file on invalid input
Check the input header before creating or truncating the output file
2021-07-09 01:22:22 +08:00
Ming-Hung Tsai 6cecf0f673 [file_utils] Check the file type to prevent unexpected writes by thin_repair 2021-07-09 01:22:22 +08:00
Ming-Hung Tsai 87ada9b493 [tests] Add basic tests for cache_dump 2021-07-09 01:22:22 +08:00
Ming-Hung Tsai f395bab7be [tests] Use traits to specify test parameters
To deal with variety in target attributes and their expected outputs,
the test parameters are categorized into traits, thus the test program
could define test parameters in a more structured way, without having
to pass multiple tightly-coupled parameters to test functions.
2021-07-09 01:22:22 +08:00
Ming-Hung Tsai 12ef69c31b [tests] Pull out common tests on i/o options into reusable modules
- Introduce modules for testing input/output options
- Provide macros for generating test cases
- Hide details of subprocess execution
2021-07-08 22:02:44 +08:00
Ming-Hung Tsai 6660fde3c4 [tests] Refine the test naming and error messages
- Make the naming of test cases less ambiguous, e.g., rename
  "missing_input_file" to "missing_input_arg" or "input_file_not_found"
- Unify the error messages on input/output options
2021-07-08 01:05:15 +08:00
Ming-Hung Tsai d00388f68a [thin_shrink] Support short options 2021-07-07 16:06:04 +08:00
Ming-Hung Tsai 1526ab3472 [all] Apply cargo fmt, and fix clippy warning of branches_sharing_code 2021-07-02 16:17:52 +08:00
Joe Thornber 5ac9ae2dae
Merge pull request #180 from mingnus/2021-06-25-update-deps
Update config scripts and Rust dependencies
2021-06-28 15:44:59 +01:00
Ming-Hung Tsai 8bb4aaef8f [build] Create the destdir for solely installing rust tools 2021-06-28 08:19:30 +08:00
Ming-Hung Tsai 725ad1d9b0 [manpages] Make the footer backward compatible 2021-06-28 08:19:30 +08:00
Ming-Hung Tsai 857e3a7d3d [manpages] Make the header and footer backward compatible 2021-06-28 08:19:30 +08:00
Kay Lin 6f1a6a59dd [manpages] Update txt2man to 1.7.1
Signed-off-by: Kay Lin <i@v2bv.net>
2021-06-26 13:40:56 +08:00
Ming-Hung Tsai 2d201c5483 [build] Update config.guess and config.sub to version 2021-05-24 2021-06-26 13:38:56 +08:00
Ming-Hung Tsai b7c3969747 [build] Update dependencies
- Update fixedbitset to 0.4
- Update indicatif to 0.16
- Update libc to 0.2.*
- Update nix to 0.21
- Update nom to 6.2.*
- Update dependencies with cargo-update
2021-06-26 00:59:37 +08:00
Kay Lin c18cd42d35 [build] Update dependencies
- Update crc32c to 0.6, which allows it to be built on AArch64.

  - Update base64 to 0.13
  - Update byteorder to 0.14
  - Update io-uring to 0.4
  - Update libc to 0.2.83
  - Update nix to 0.19
  - Update nom to 6.0.1
  - Update quick-xml to 0.20
  - Update rand to 0.8
  - Update tempfile to 3.2
  - Update tui to 0.14

Signed-off-by: Kay Lin <i@v2bv.net>
2021-06-26 00:08:58 +08:00
Ming-Hung Tsai 4905c0eb73 [build] Update .gitignore 2021-06-26 00:08:58 +08:00
Joe Thornber cf27a2cf4f
Merge pull request #179 from mingnus/2021-06-03-cache-restore-fixes
Minor fixes
2021-06-23 07:55:59 +01:00
Ming-Hung Tsai 2cb84236d4 [all (rust)] Tidy command line options 2021-06-23 14:33:28 +08:00
Ming-Hung Tsai cd48f00191 [all (rust)] Make sync-io the default
Multithreaded sync-io has performance similar to async-io. Also,
sync-io saves the hassle of setting ulimits to get io_uring working
on some systems (commit ba7fd7b). Now we default to sync-io, and
leave async-io as a hidden option for testing and benchmarking.
2021-06-23 14:33:28 +08:00
Ming-Hung Tsai 361d19adaa [space_map (rust)] Fix cache hit with async-io 2021-06-23 14:33:28 +08:00
Joe Thornber 5dd2e81bf0
Merge pull request #178 from jwakely/boost-iostreams
[build] Remove -lboost_iostreams linker flag
2021-06-23 07:27:19 +01:00
Jonathan Wakely 6b7e66d8f9 [build] Remove -lboost_iostreams linker flag
This was previously needed for thin-provisioning/thin_metadata_pack.cc
but that file was rewritten in Rust and no longer needs Boost. The flag
causes every binary to have a completely redundant depedency on
libboost_iostream.so, which is an issue for RHEL packaging.
2021-06-22 21:23:54 +01:00
Joe Thornber 8e609458c2
Merge pull request #177 from mingnus/2021-06-03-cache-restore-fixes
Fix restoration tools
2021-06-22 09:50:49 +01:00
Ming-Hung Tsai fba2adbe56 [tests] Fix minor test errors
- Fix command line arguments
- Adapt error messages for rust implementations
2021-06-21 23:27:51 +08:00
Ming-Hung Tsai 2bd3c17578 [tests] Fix version string checking 2021-06-21 23:27:51 +08:00
Ming-Hung Tsai bfc7f96d9f [tests] Enable testing the rust targets
This is a temporary solution for development.
Usage: cargo test --features rust_tests
2021-06-21 23:27:51 +08:00
Ming-Hung Tsai 7daff7350a [thin/cache_dump (rust)] Add buffered outputs, and fix command line options 2021-06-21 23:27:51 +08:00
Ming-Hung Tsai c71132c056 [space_map (rust)] Do not use an extra block set for building metadata sm
The blocks storing metadata itself are located continuously within
a certain reserved range, hence there's no need to use a block set
as the representation.
2021-06-21 23:11:57 +08:00
Ming-Hung Tsai 7ab97a9aae [space_map (rust)] Fix nr_free in index entries 2021-06-16 01:39:31 +08:00
Ming-Hung Tsai 4b7b3658ff [thin/cache_restore (rust)] Build the metadata space map in-place
That avoids cloning the source space map
2021-06-16 01:39:31 +08:00
Ming-Hung Tsai 9ab8dfa283 [space_map (rust)] Fix bitmap packing
- Allow packing unaligned number of bitmap entries that could happen
  with the last bitmap. Unused trailing entries are zeroed by memset.
- Fix none_free_before in index_entry
2021-06-16 01:39:31 +08:00
Ming-Hung Tsai 3fda9cc1f8 [thin_restore (rust)] Do not iterate mapping tree leaves twice 2021-06-16 01:39:31 +08:00
Ming-Hung Tsai de7e79fc06 [btree_builder] Rename Builder to BTreeBuilder for clarity 2021-06-16 01:39:31 +08:00
Ming-Hung Tsai 6d16c5816e [btree_builder] Fix reference counts of btree nodes
A leaf node should be counted only if it is referenced by some internal
nodes, since the leaves generated by <def> sections might be unshifted
and merged with exclusive mappings or other shared defs, or they might
not even be used by any of the devices if the xml was tampered. The
internal nodes should be handled in the same manner.

The new 'reserve' operation is designed for this purpose. Applications
could reserve a block for later use, without setting its ref count
immediately. This way saves the hassles of tracking unused leaves.
2021-06-16 01:39:31 +08:00
Ming-Hung Tsai 9e061a03a8 [space_map (rust)] Do not reset search position 2021-06-11 20:51:49 +08:00
Ming-Hung Tsai 88e7f8fd69 [array_builder] Simplify array building process 2021-06-11 20:51:49 +08:00
Joe Thornber 101028ed5f
Merge pull request #176 from mingnus/2021-04-28-coverity-fixes
Clear thin superblock flags in restored metadata
2021-06-09 10:10:44 +01:00
Ming-Hung Tsai 60b65ebe7a [space_map (rust)] Fix uninitialized bytes in index block 2021-06-08 20:17:24 +08:00
Ming-Hung Tsai c32517f827 [thin] Clear superblock flags in restored metadata
The needs_check flag is unnecessary for a restored metadata since
it is assumed clean and has no errors
2021-06-07 19:06:56 +08:00
Joe Thornber 01aac6c1c1
Merge pull request #175 from mingnus/2021-04-28-coverity-fixes
Fix resource leak
2021-06-04 15:22:24 +01:00
Ming-Hung Tsai 429e7f01d7 [file_utils] Fix resource leak 2021-06-04 21:37:02 +08:00
Joe Thornber 2413b5d31f
Merge pull request #174 from mingnus/2021-04-28-coverity-fixes
Fix issues detected by Coverity
2021-06-03 12:11:54 +01:00
Ming-Hung Tsai 8014643b9e [build] Allow running tests without the --enable-testing configuration
- Make the test targets always available (test, unit-test, functional-test)
- Cleanup include paths, and bring back the CPPFLAGS in commit 3e24cff that
  was overwritten by the merge ca8f6df (PR #60)
- Introduce the dev-tools target for building testing/dbg tools individually
- Leave the --enable-testing option for backward compatibility
2021-06-03 18:54:26 +08:00
Ming-Hung Tsai 050eacf4cb [all] Remove unreachable code 2021-06-03 18:54:26 +08:00
Ming-Hung Tsai db52308d85 [build] Remove unused sources from the regular build 2021-06-03 18:54:26 +08:00
Ming-Hung Tsai 25ed2dfc9a [thin_dump] Fix warnings on potential NULL pointer 2021-06-03 18:54:26 +08:00
Ming-Hung Tsai 2e62363446 [all] Fix uninitialized class members 2021-06-03 18:54:26 +08:00
Ming-Hung Tsai 3145a1f4de [thin_metadata_size] Fix potential string overflow 2021-06-03 18:54:26 +08:00
Ming-Hung Tsai 759407445f [thin_show_duplicates] Fix potential errors
- Fix error if no --block-sector provided
- Fix errors on pools without mappings, or zero-length file
2021-06-03 18:50:20 +08:00
Ming-Hung Tsai 75c0a3656c [thin_dump] Fix leaked shared object handle 2021-06-02 03:43:58 +08:00
Ming-Hung Tsai 041ed7858c [build] Fix customized emitter linkage 2021-06-02 00:51:39 +08:00
Ming-Hung Tsai 0004dced93 [thin_show_metadata] Fix out-of-bounds access 2021-06-02 00:51:39 +08:00
Ming-Hung Tsai f7e4a8faa9 [all] Fix resource leaks 2021-06-02 00:51:39 +08:00
Joe Thornber b9df99fd6a
Merge pull request #173 from mingnus/2021-05-12-cache-restore
cache_restore wip
2021-06-01 09:15:38 +01:00
Ming-Hung Tsai b12530f580 [space_map (rust)] Fix nr_allocated tracking in CoreSpaceMap 2021-05-28 20:20:30 +08:00
Ming-Hung Tsai 3a653eaa5f [thin_restore (rust)] Build metadata and data space maps 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai c142cd0d48 [space_map (rust)] Fix space map building
- Fix out-of-bounds index
- Automatically flush queued writes before function return
- Track allocated blocks in write_batcher (might be space consuming)
2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 13d6c72ad9 [cache_restore (rust)] Build metadata space map 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 5ecae3ad88 [tests] Fix numeric literal annotation 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 48d4fc51ed [space_map (rust)] Factor out space_map_metadata 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai d5e6a69af6 [thin (rust)] Fix the unit of metadata_block_size in superblock 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 6a29f6a41a [cache_check (rust)] Fix discard bitset availability checking 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai ce94ba73a5 [cache_restore (rust)] First draft 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai e336b3a63f [math (rust)] Make the functions generic 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 1198a3f713 [cache (rust)] Implement Pack for superblock 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 511ae9e908 [checksum] Support cache and era superblock 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 159dda9659 [thin_restore (rust)] Tidy up with the builder pattern 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 1907dab5ee [cache (rust)] Implement Pack and Default for restoration 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 86e2db3a1a [cache (rust)] Add visitor traits for cache_restore 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 2a77036fa8 [array_builder] First draft
Not tested yet
2021-05-28 03:19:42 +08:00
Ming-Hung Tsai a6e1870b2b [array (rust)] Implement Pack for restoration 2021-05-28 03:19:42 +08:00
Ming-Hung Tsai 7e2d69ede9 [cache_dump (rust)] Allow partially broken dirty bitset 2021-05-28 03:19:42 +08:00
Joe Thornber db48f51049
Merge pull request #172 from mingnus/2021-05-04-thin-dump-fixes
Make thin_dump runnable
2021-05-27 19:03:05 +01:00
Ming-Hung Tsai 2a9e7cf74f [thin_dump (rust)] Split runs at the position with multiple references
That is, an element with multiple references will serve as the beginning
of an atomic run.
2021-05-28 00:18:27 +08:00
Ming-Hung Tsai 30cfcd9a88 [thin_dump (rust)] Use common leaf sequences to pack metadata 2021-05-28 00:07:32 +08:00
Ming-Hung Tsai 4c47fcabbf [thin_dump (rust)] Fix missing outputs 2021-05-27 23:51:37 +08:00
Joe Thornber 4f192cea0f
Merge pull request #171 from mingnus/rust-cache-tools
Fix bugs in array iteration and text outputs
2021-05-13 15:02:41 +01:00
Ming-Hung Tsai 7e53c36d6b [cache (rust)] Fix bugs in array iteration and text outputs
- Fix array indexing
- Fix panic on empty array
- Remove trailing null characters from the policy name
- Change XML tag naming for backward compatibility
2021-05-13 21:36:52 +08:00
Joe Thornber 11c354b3b1
Merge pull request #170 from mingnus/rust-cache-tools
Merge recent changes in cache-tools
2021-05-12 09:29:49 +01:00
Ming-Hung Tsai 1bbb63f06b [cache_check (rust)] Fix discard bitset size checking 2021-05-12 15:50:14 +08:00
Ming-Hung Tsai b7bf82b8f2 [all] Fix newline in version string 2021-05-12 12:03:52 +08:00
Ming-Hung Tsai 965fbb6e8f [all] Apply cargo fmt, and fix clippy warnings 2021-05-11 23:53:31 +08:00
Ming-Hung Tsai 0553a78c04 Add pre-commit hooks 2021-05-11 23:18:43 +08:00
Ming-Hung Tsai 5baeab4a5c Merge branch 'main' into rust-cache-tools 2021-05-11 23:16:08 +08:00
Joe Thornber d9a96758b0
Merge pull request #168 from mingnus/2020-10-09-thin-restore-rewrite
Merge recent changes in thin_restore
2021-05-04 09:21:40 +01:00
Ming-Hung Tsai 43e433149b [all] Apply cargo fmt 2021-05-04 16:10:23 +08:00
Ming-Hung Tsai 4b4584c830 [thin_restore (rust)] Apply several fixes
- Fix reading queued blocks
- Fix unnecessary block shadowing when there's no remaining values
- Prevent superblock from overwritten
- Flush queued writes before updating superblock
2021-05-03 00:07:34 +08:00
Ming-Hung Tsai e9899ac610 Add missing math.rs 2021-04-26 12:47:05 +08:00
Ming-Hung Tsai cf4b937ade [cache_check (rust)] Check space map counts
- Support space map checking and auto-repair
2021-04-23 16:23:21 +08:00
Ming-Hung Tsai 636d50a38d [thin_check (rust)] Pull out space map checking routines 2021-04-23 16:04:56 +08:00
Ming-Hung Tsai e1628f9004 [cache_check (rust)] Add more checks
- Check array indices continuity
- Support ignore_non_fatal
- Report blocknr of IoErrors
- Report array block indeices
2021-04-21 23:35:22 +08:00
Ming-Hung Tsai 239ff7dfa1 [cache_check (rust)] Add more checks
- Report errors
- Support reading partially broken bitset
  - The output is a bitmap of 2-bit entries, indicating availability of bits
2021-04-21 14:09:38 +08:00
Ming-Hung Tsai 3279d8381b [array_walker] Read multiple array blocks at once 2021-04-17 00:10:14 +08:00
Ming-Hung Tsai c17559791f [bitset] Rename bitset module 2021-04-17 00:10:14 +08:00
Ming-Hung Tsai 1964015d81 [array_walker] Handle the whole array block at once
That gives the visitor more controls over the data processing and locking,
and also improves the performance by 10-15%.
2021-04-17 00:06:09 +08:00
Ming-Hung Tsai 95dee9f66d [cache_check (rust)] Do not remap ArrayErrors to keep the error context 2021-04-16 21:46:30 +08:00
Ming-Hung Tsai 9b4a0607ea [cache_check (rust)] Detect structural errors on arrays
- Define error types for arrays
- Propagate low-level errors to tools
- Rename array_block.rs to array.rs
2021-04-14 16:02:50 +08:00
Ming-Hung Tsai ace9c1d1e3 [cache_check (rust)] Add more checks
- check version-2 dirty bitset size and consistency
- check discard bitset size
2021-04-11 22:48:10 +08:00
Ming-Hung Tsai ae630f1fd8 [btree_walker] Fix error returning 2021-04-11 20:18:44 +08:00
Ming-Hung Tsai 860b3ca7d2 [cache_check (rust)] Add more checks
- Check the version-1 dirty flag
- Check mappings against the origin device size, if the cache is clean
- Check superblock fields
2021-04-11 20:18:25 +08:00
Joe Thornber 0119b51a9c Merge branch 'main' of github.com:jthornber/thin-provisioning-tools 2021-04-08 10:47:18 +01:00
Joe Thornber c41b2e8ec3 Some interface changes to let dm-unit use the thin checking code more easily. 2021-04-08 10:45:50 +01:00
Joe Thornber 89372c3fa7
Merge pull request #167 from mingnus/main
btree minor fixes
2021-03-25 09:31:21 +00:00
Ming-Hung Tsai c496e8a4c8 [btree] Remove FIXMEs 2021-03-25 16:42:36 +08:00
Ming-Hung Tsai 8bfe7ee6f3 [btree] Fix rebalancing checks 2021-03-25 15:06:52 +08:00
Joe Thornber 040e3bfc2d Lot's of work on thin_restore 2021-03-24 14:20:20 +00:00
Joe Thornber 12c1c6e1f5 Merge branch 'main' into 2020-10-09-thin-restore-rewrite 2021-03-23 11:22:03 +00:00
Ming-Hung Tsai e0eb8fea87 [btree (rust)] Show out-of-order keys 2021-03-12 12:37:07 +08:00
Joe Thornber 0d46c606dd
Merge pull request #166 from mingnus/main
Fix a syntax error
2021-03-11 17:13:28 +00:00
Ming-Hung Tsai 0ff72374f8 [btree_builder (rust)] Fix the max_entries 2021-03-12 00:42:54 +08:00
Joe Thornber 1b7f43ef9f
Merge pull request #165 from mingnus/main
Fix the max_entries in node_header
2021-03-11 10:54:44 +00:00
Ming-Hung Tsai 7d983e3155 [btree_builder (rust)] Fix the max_entries 2021-03-11 18:24:10 +08:00
Joe Thornber d0675dd7bf Add missing header.
So many errors from one issue
2021-03-04 11:19:04 +00:00
Joe Thornber 7e869bb8e0 Code review of cache_dump/check
Added support for metadata format2 to cache_dump.  Nothing tested.
2021-03-04 11:13:08 +00:00
Joe Thornber e6c6275aea Code review of src/pdata/array* 2021-03-03 10:27:57 +00:00
Joe Thornber fdcc09c27e [cache_dump] Squash some clippy warnings 2021-03-03 09:48:15 +00:00
Joe Thornber 3cf0dba469 [cache_dump] fix how needs_check flag is checked 2021-03-03 09:47:42 +00:00
Ming-Hung Tsai eb3d181f95 [cache_dump (rust)] First draft of cache_dump 2021-03-03 12:27:51 +08:00
Ming-Hung Tsai fde0e0e2b8 [cache (rust)] Add Mapping::is_dirty() 2021-03-03 12:14:46 +08:00
Ming-Hung Tsai 74fcb9d505 [cache (rust)] Fix data types 2021-02-26 23:31:12 +08:00
Joe Thornber 3d9b32e509 Merge branch 'main' of github.com:jthornber/thin-provisioning-tools into main 2021-02-26 15:26:33 +00:00
Joe Thornber d4299a00d0 Make pack_node() and calc_max_entries() public for dm-unit 2021-02-26 15:25:53 +00:00
Joe Thornber 441a000c52
Merge pull request #163 from mingnus/rust-cache-tools
[cache (rust)] Fix merge conflicts
2021-02-24 12:33:50 +00:00
Ming-Hung Tsai a7ecfba2b4 [cache (rust)] Fix merge conflicts 2021-02-24 20:21:10 +08:00
Joe Thornber a4ba01cacd
Merge pull request #160 from mingnus/rust-cache-tools
First draft of cache_check in Rust
2021-02-24 09:53:51 +00:00
Ming-Hung Tsai 2bb3bf65b7 [cache_check (rust)] Implement basic functions 2021-02-24 17:39:11 +08:00
Joe Thornber 78c93ce09b
Merge pull request #162 from mingnus/main-thin-debug
[dbg] Enhance debugging tools
2021-02-22 08:28:07 +00:00
Ming-Hung Tsai e8410dec04 [dbg] Add missing commands to cache/era_debug 2021-02-21 19:16:51 +08:00
Ming-Hung Tsai 29f9182078 [dbg] Remove nested function template to reduce code size 2021-02-21 01:07:55 +08:00
Ming-Hung Tsai a81cef4467 [dbg] Pull out common code into dbg-lib
- Modularize common routines
- Extract the block_dumper interface for displaying blocks
- Remove inheritance from show_traits
2021-02-21 01:04:35 +08:00
Ming-Hung Tsai c95e31bef6 [era_debug] Display bitset entries in run-length fashion 2021-02-21 00:02:57 +08:00
Ming-Hung Tsai afbd913e22 [era] Add era_debug 2021-02-18 23:40:39 +08:00
Ming-Hung Tsai 1bc88bfde8 [cache_debug] Simplify the trait types 2021-02-18 18:20:48 +08:00
Ming-Hung Tsai e3a646e4d2 [dbg] Improve error resilience of debugging tools
Do not open the metadata. The tool interprets user specified blocks one-by-one.
Thus, there's no need to preload the metadata structures.

Also remove the unused --ignore-metadata-space-map option. It was designed
to control the loading of space maps with the old metadata constructors.
2021-02-18 00:00:55 +08:00
Joe Thornber 58cd881340 Fix regression where era_restore wouldn't work with devices.
check_file_exists() had an extra parameter added with a default, which was
the wrong default for era_restore.
2021-02-17 15:15:57 +00:00
Joe Thornber 763b2d14b2 [io_engine] Add Block::zeroed() constructor 2021-02-09 14:34:26 +00:00
Joe Thornber c3c6d37aea Fix a lot of clippy warnings 2021-02-08 10:38:21 +00:00
Joe Thornber e62fa3e835 Merge branch 'main' of github.com:jthornber/thin-provisioning-tools into main 2021-02-04 10:01:17 +00:00
Joe Thornber 6f30d17f04 version.rs includes VERSION directly.
So you no longer need to run ./configure to build the rust tools
2021-02-04 10:00:32 +00:00
Joe Thornber 8dbbe7fe27
Merge pull request #161 from mingnus/main-thin-debug
[dbg] Integrate thin_debug, and add cache_debug
2021-02-04 10:00:09 +00:00
Ming-Hung Tsai 08bd2f47bd [cache_debug] Fork thin_debug into cache_debug 2021-02-04 14:29:18 +08:00
Ming-Hung Tsai a05ac553b6 [dbg] Hide implementations of shared components 2021-02-04 14:28:17 +08:00
Ming-Hung Tsai b9b04dc872 [thin_debug] Factor out reusable componments 2021-02-04 14:19:28 +08:00
Ming-Hung Tsai e046bbf5c4 [thin_debug] Simplify the output format
Turn nested fields into attributes. Also replace spaces in field names
by underscores.
2021-02-03 15:51:52 +08:00
Ming-Hung Tsai 0816430ba0 [thin_debug] Remove the using boost directive to avoid namespace pollution
boost::uint32_t might conflict with the toolchain defined uint32_t.
which causes template argument deduction failed.
2021-02-03 15:51:52 +08:00
Ming-Hung Tsai ab3b2cbda2 [thin_debug] Refine the outputs
- Show the blocknr from node header
- Display the index of node entries
2021-02-03 15:51:52 +08:00
Ming-Hung Tsai 6dc5086643 [thin_debug] Add commands to show space maps 2021-02-03 15:51:52 +08:00
Ming-Hung Tsai 3bfa775887 [thin_debug] Show space map roots in superblock 2021-02-03 15:51:52 +08:00
Ming-Hung Tsai 127f44c66b [thin_debug] Enhance error handling
- Handle exception thrown by commands
- Add help and exit commands
2021-02-03 15:51:52 +08:00
Ming-Hung Tsai 62d09c6752 [thin_debug] Reduce code size by eliminating duplicated types
This patch doesn't have a significant effect - only a few KBs of code
is reduced. However, it's still a nice have.
2021-02-03 15:51:52 +08:00
Ming-Hung Tsai 0ce026caf5 [thin_debug] Integrate thin_debug into the main program 2021-02-03 15:51:52 +08:00
Ming-Hung Tsai faa371c208 [cache (rust)] Implement cache primitives 2021-02-03 15:18:47 +08:00
Ming-Hung Tsai 087a4711a5 [array (rust)] Implement array basis 2021-02-03 15:18:41 +08:00
Joe Thornber 9733ceb949
Merge pull request #158 from mingnus/main
[cache_writeback] Support offset within the source and destination devices
2021-01-20 11:06:21 +00:00
Ming-Hung Tsai 4c17076f09 [cache_writeback] Support offset within the source and destination devices 2021-01-20 12:02:13 +08:00
Joe Thornber 76ac202463
Merge pull request #157 from mingnus/main
[build] Enable building the dev-tools for functional tests
2021-01-12 09:02:43 +00:00
Ming-Hung Tsai 6a2fa73924 [build] Enable building the dev-tools for functional tests
- Factor out the dev-tools into a stand-alone, no-installed program
- Built the dev-tools if --enable-testing is specified
- Remove the --enable-dev-tools configure option
- Allow suffix on the binary name
- Update symlinks
- Cleanup Makefile
2021-01-12 16:08:51 +08:00
Ming-Hung Tsai 8d59a83f7c [functional-tests] Update thin_delta help text 2021-01-12 02:50:46 +08:00
Ming-Hung Tsai ca7e79a828 Merge commit 'b67b587' into main 2021-01-12 02:50:12 +08:00
Joe Thornber 04e0eb3a66 [thin_restore (rust)] rewrite the btree_builder
Now copes with adding shared leaves.
2020-12-09 13:22:32 +00:00
Joe Thornber 443b3c8f0b [io_engine (rust)] get_nr_blocks() wasn't handling block devices.
Now calls file_utils::file_size()
2020-12-02 15:20:14 +00:00
Joe Thornber ba7fd7bd2b [thin_check (rust)] Make --sync-io the default.
For some systems you have to adjust the ulimits to get io_uring
to work, so we now default to using sync io.

Also added --async-io flag.
2020-12-02 11:33:05 +00:00
Joe Thornber 0e4622f337 [Rust tools] squash lots of warnings 2020-12-01 11:50:32 +00:00
Joe Thornber 327fc80fb0 Merge branch 'main' into 2020-10-09-thin-restore-rewrite 2020-11-25 11:20:28 +00:00
Joe Thornber 83eea11d12
Merge pull request #155 from mingnus/thin-check-fix-metadata-leaks
thin_check fixes
2020-11-24 10:30:35 +00:00
Ming-Hung Tsai 565c656ed2 [thin_generate_damage] Do not open a new transaction to prevent ref-count underflow
There's a chance that thin_generate_damage tries to change ref-counts of
space map blocks due to its random nature, which could lead to problems.
If the ref-counts of metadata space map blocks (shadow source) is changed
to zero, then the ref-counts will become underflow after a shadow operation.

In-place space map modification is a way to prevent that value underflow.
An alternative approach is to avoid changing ref-counts of space map blocks.
2020-11-24 18:18:21 +08:00
Ming-Hung Tsai c932a76f08 [unit-tests] Add underfull nodes counting tests 2020-11-24 18:18:21 +08:00
Ming-Hung Tsai 61f07573e1 [metadata_counter] Count under populated nodes if the option is provided 2020-11-24 18:18:21 +08:00
Ming-Hung Tsai 1fe8a0dbde [thin_check] Allow using --clear-needs-check and --skip-mappings together
Although it is not recommended to clear the flag without a full
examination, however, the usage has been documented as an approach
to reduce lvchange run time [1]. For the purpose of backward
compatibility and avoiding boot failure after upgrading thin_check [2],
the limitation is now removed.

[1] https://wiki.archlinux.org/index.php/LVM#Thinly-provisioned_root_volume_device_times_out
[2] Community feedback on previous commit:
    https://github.com/jthornber/thin-provisioning-tools/commit/b278f4f
2020-11-24 18:17:36 +08:00
Ming-Hung Tsai f364de35bc [unit-tests] Fix unflushed trashed blocks and variable referencing 2020-11-24 17:55:17 +08:00
Joe Thornber 5af95167b4
Merge pull request #154 from mingnus/main
thin development tools update
2020-11-24 09:31:23 +00:00
Ming-Hung Tsai becdbbdb49 [build] Update Rust package version 2020-11-24 16:03:58 +08:00
Ming-Hung Tsai 7ceb500fc8 [thin_delta] Support comparing two specific subtrees 2020-11-24 15:58:01 +08:00
Ming-Hung Tsai 1d5b52b0dd [thin_delta] Clean up duplicated code 2020-11-24 15:17:35 +08:00
Ming-Hung Tsai b42408ef41 [thin] Introduce thin_patch_superblock to override superblock fields 2020-11-24 14:57:05 +08:00
Joe Thornber 1ae62adec6 work in progress 2020-11-18 14:33:56 +00:00
Joe Thornber 37ea0280df [thin_restore] first pass at btree_builder.
No tests yet
2020-10-26 12:05:27 +00:00
Joe Thornber f60ae770c2 [thin_explore] Explore devices tree, including path support. 2020-10-15 11:53:09 +01:00
Joe Thornber c42b623e39 Merge branch '2020-08-13-thin-check-rewrite' into main 2020-10-09 11:21:12 +01:00
Joe Thornber e9fbcc31de [thin_dump (rust)] First pass at thin_dump.
Doesn't include --repair.

This includes <def> and <ref> sections for shared regions.
2020-09-28 15:45:13 +01:00
Joe Thornber a88ae3ca18 [thin_check (rust)] factor out device detail 2020-09-25 09:59:16 +01:00
Joe Thornber 66b6a1ba48 [thin_check (rust)] --superblock-only, --skip-mappings, INFO fields 2020-09-24 13:55:58 +01:00
Joe Thornber 34052c540c [thin_check (rust)] Reinstate walk_node_threaded 2020-09-24 09:40:38 +01:00
Joe Thornber b67b587a10 [thin_shrink] Add comment pointing people at Nikhil's PoC 2020-09-22 12:01:17 +01:00
Joe Thornber f4c3098e02 [thin_check (rust)] fix bug in key range splitting.
Ranges were not being ommitted when a block was ommitted due to
being shared and already visited.
2020-09-22 10:47:23 +01:00
Joe Thornber 819fc6d54c [thin_explore] accept a node path on the command line
Helpful to examine thin_check failures.
2020-09-22 10:47:04 +01:00
Joe Thornber b193d19603 [thin_check (rust)] output complete node paths with errors.
This can be used with thin_explore
2020-09-18 11:16:09 +01:00
Joe Thornber bc058f8baf [thin_check (rust)] BTree values must now implement Copy 2020-09-18 10:06:33 +01:00
Joe Thornber bcfb9a73a1 [thin_explore] display ranges where possible 2020-09-18 09:12:51 +01:00
Joe Thornber 8493cf7081 [thin_explore] First code drop 2020-09-16 15:10:01 +01:00
Joe Thornber 5168621f02 [thin_check (rust)] Optimise SyncIoEngine
*_many no longer get/put for each block
2020-09-02 13:28:16 +01:00
Joe Thornber 44142f657a [thin_check (rust)] Add error handling to io_engine interface 2020-09-02 12:57:47 +01:00
Joe Thornber b82307d8a5 [thin_check (rust)] drop O_DIRECT for the sync_io engine.
O_DIRECT slows us down, and there's no correctness reason for having it.
2020-08-21 11:39:41 +01:00
Joe Thornber cda92de441 [thin_check (rust)] Add a threaded version of btree walk.
Bottom level mappings use this if there are few devices.  Performance
is a bit slower for io_uring, and much slower for sync io (which I think
is due to io scheduling which I can't do much about).
2020-08-21 10:10:49 +01:00
Joe Thornber b01a0a46d1 [thin_metadata_pack/unpack] Use Vec::with_capacity() to avoid reallocs.
Gives a small speed boost to both pack and unpack.
2020-08-21 09:14:54 +01:00
Joe Thornber c9a759b4e8 [thin_check (rust)] Use vec::with_capacity() to avoid reallocations. 2020-08-21 09:00:21 +01:00
Joe Thornber 2cc2dffab5 [thin_check (rust)] Make NodeVisitor::visit non mut.
Preparation for making btree_walk multithreaded
2020-08-20 11:05:14 +01:00
Joe Thornber a1c206b774 [thin_check (rust)] NodeVisitor only needs to see leaf nodes 2020-08-20 10:55:38 +01:00
Joe Thornber 936e06e132 [thin_check (rust)] Remove some unused params from NodeVisitor::visit 2020-08-20 10:46:06 +01:00
Joe Thornber 1999343d2f [thin_check (rust)] squash a couple of warnings 2020-08-20 10:33:02 +01:00
Joe Thornber 0372e689e5 Merge branch '2020-08-13-thin-check-rewrite' 2020-08-19 14:32:40 +01:00
Joe Thornber 7834d661e2 [thin_check (rust)] auto repair space map leaks 2020-08-19 14:31:01 +01:00
Joe Thornber cdd0beb527 [thin_check (rust)] Change io_engine trait to use slices rather than Vecs 2020-08-18 12:57:05 +01:00
Joe Thornber 2aa6859502 [thin_check (rust)] add write support to io_engine 2020-08-18 12:52:16 +01:00
Joe Thornber 67a54b4ebc [thin_check (rust)] add --auto-repair switch 2020-08-18 11:47:42 +01:00
Joe Thornber 8eec84fbec [thin_check (rust)] introduce ASpaceMap type alias 2020-08-18 11:06:15 +01:00
Joe Thornber 04f3ba5a33 [thin_check (rust)] Pass ctx to check_space_map 2020-08-18 10:59:04 +01:00
Joe Thornber 4beb2db337 [thin_check (rust)] Factor out check_mapping_bottom_level 2020-08-18 10:53:11 +01:00
Joe Thornber 239ae6b6ec [thin_check (rust)] factor out spawn_progress_thread 2020-08-18 09:48:51 +01:00
Joe Thornber 65799f9a14
Merge pull request #150 from mingnus/thin-check-fix-metadata-leaks
Add space-map counting tests
2020-08-18 08:50:32 +01:00
Joe Thornber e8d7e5cf1e [thin_check (rust)] move report creation to top level 2020-08-17 16:05:06 +01:00
Ming-Hung Tsai 44d025be0c [unit-tests] Add space map counting tests 2020-08-17 22:57:46 +08:00
Ming-Hung Tsai 3c49949796 [space-maps/disk] Support ignoring broken bitmaps on counting index_store 2020-08-17 22:57:26 +08:00
Joe Thornber 5743e3e9ba [thin_check (rust)] Add title method to reports 2020-08-17 15:36:21 +01:00
Joe Thornber 9995751dde [thin_check (rust)] Provide 3 different report types.
ProgressBar for interactive use.
Simple for when not a tty
and quiet for -q
2020-08-17 13:10:32 +01:00
Ming-Hung Tsai 27ca8cc009 [block_counter] Add block_counter::clear() 2020-08-17 15:43:09 +08:00
Ming-Hung Tsai de843991e3 [transaction_manager] Add transaction_manager::commit()
It should be called by metadata::commit() and reserve_metadata_snap()
(issue #73)
2020-08-17 15:43:04 +08:00
Ming-Hung Tsai 9f3823c97d [metadata_checker] Rename function to reflect command line changes 2020-08-14 18:49:41 +08:00
Joe Thornber 7466cd7182 [functional-tests (rust)] TestDir now creates dir in ./
O_DIRECT doesn't work with /tmp/
2020-08-14 10:54:31 +01:00
Joe Thornber e1cfc3866b [thin_check (rust)] Mappings top level weren't being ref counted 2020-08-13 14:43:19 +01:00
Joe Thornber 092447d17a [thin_check (rust)] remove some dead code 2020-08-13 14:30:04 +01:00
Joe Thornber 2fa732a93c [functional tests] Port some of Hank's sh tests to Rust.
Run with 'cargo test'
2020-08-13 14:20:29 +01:00
Joe Thornber a8a2f560ec [build] add symlinks for thin_generate_{mappings/damage} 2020-08-13 13:47:51 +01:00
Joe Thornber b0e7520fbf [thin/superblock (rust)] Unpack flags 2020-08-13 13:46:07 +01:00
Joe Thornber c254ebe384 [functional-tests] Port 3 --auto-repair tests from Scheme to Rust 2020-08-12 13:03:01 +01:00
Joe Thornber 5a16f21199 [functional tests] Fixup a few tests that broke when we bumped the version nr. 2020-08-12 12:33:16 +01:00
Joe Thornber bf202d076b Merge branch '2020-06-13-thin-check-rewrite' 2020-08-12 11:18:28 +01:00
Joe Thornber afa3f2f04d [thin_check (rust)] Rename Spinner -> Reporter 2020-08-12 10:25:06 +01:00
Joe Thornber 544335ae4a [thin_check (rust)] Send all reporting through the Spinner.
This means the spinner doesn't overwrite messages.
2020-08-12 09:35:21 +01:00
Joe Thornber 3757e1d947 [thin_check (rust)] check metadata space map 2020-08-12 08:02:29 +01:00
Joe Thornber e65d2dec6f [thin_check (rust)] Add progress bar 2020-08-11 13:44:33 +01:00
Joe Thornber 34425521e2 [thin_check (rust)] change BTreeWalker to use a space map rather than seen bitset 2020-08-11 10:50:43 +01:00
Joe Thornber 50bde693a1 [thin_check (rust)] Factor out pdata/unpack 2020-08-10 15:42:10 +01:00
Joe Thornber 55ee4bfad8 [thin_check (rust)] replace IndexVisitor with a call to btree_to_map 2020-08-10 14:56:39 +01:00
Joe Thornber e28c602c3d [thin_check (rust)] factor out btree_to_map() fn 2020-08-10 14:45:35 +01:00
Joe Thornber cbc9c2c72a [thin_check (rust)] Improve data_sm handling 2020-08-10 12:56:41 +01:00
Joe Thornber d5444d2255 [thin_check (rust)] sm bitmap entries were being unpacked incorrectly. 2020-08-10 12:55:05 +01:00
Joe Thornber b915257e10 [thin_check (rust)] Fix race in btree walking.
The seen bitset was locked once to test, and separately to insert.
2020-08-10 12:30:12 +01:00
Joe Thornber 4e4b7ca2b1 [thin_check (rust)] add --sync-io flag
Makes it easier to switch between engines
2020-08-10 11:24:50 +01:00
Joe Thornber 0f865856ed [thin_check (rust)] Improve SyncIoEngine.
Now opens the file multiple times so different threads can do io in parallel.
2020-08-10 10:44:47 +01:00
Joe Thornber f0df17af9e [thin_check (rust)] Get SyncIoEngine working again. 2020-08-10 08:59:02 +01:00
Joe Thornber 08e3ea948e [thin_check (rust)] rename block_manager.rs -> io_engine.rs 2020-08-10 08:29:32 +01:00
Joe Thornber fd0c0ffc1d [thin_check (rust)] data space map now checked. 2020-08-08 16:42:32 +01:00
Joe Thornber 4054b1be4c [thin_check (rust)] Switch to BTreeMap.
It's faster.
2020-08-08 14:58:13 +01:00
Joe Thornber 1e4a038b41 [thin_check (rust)] Reimplement CoreSpaceMap
We now use a simple vector of elements that can hold 'nr thin devs'.  Much faster.
2020-08-08 13:29:30 +01:00
Joe Thornber 7cf239b878 [thin_check (rust)] speed up CoreSpaceMap 2020-08-08 12:36:13 +01:00
Joe Thornber ec8f7b7fa8 [thin_check (rust)] Keep track of data block ref counts
as we walk the mapping tree.
2020-08-08 09:54:16 +01:00
Joe Thornber 8f76371bb2 [functional-tests] Fix clippy warnings 2020-08-07 15:41:21 +01:00
Joe Thornber fa4ea3e2d9 [functional-tests] port some of the cache_check tests to Rust 2020-08-07 14:30:00 +01:00
Joe Thornber 4a0582bb5d [thin_check (rust)] start decoding the space maps. 2020-08-06 07:51:48 +01:00
Joe Thornber 904d9b0c84 [functional-tests] port thin_metadata_unpack tests to Rust.
cargo test
2020-08-05 10:00:44 +01:00
Joe Thornber d2678fdf27 [functional-tests] port thin_metadata_pack tests to Rust.
cargo test
2020-08-05 09:09:18 +01:00
Joe Thornber 23568aaa11 [functional-tests] port thin_delta tests to rust.
cargo test
2020-08-05 08:24:52 +01:00
Joe Thornber 197e4ffbfd [thin_check (rust)] Rename ValueType trait to Unpack 2020-08-05 08:01:02 +01:00
Joe Thornber 1d44025584 [thin_check (rust)] Walk the top level and bottom level of the mapping tree separately 2020-08-04 12:11:36 +01:00
Joe Thornber 4ac428128a [functional-tests (rust)] port thin_repair tests to rust.
cargo test
2020-08-04 11:30:46 +01:00
Joe Thornber f56ea2d031 [thin_check (rust)] walk devices tree. 2020-08-03 16:22:08 +01:00
Joe Thornber 1368227a71 [thin_check (rust)] add btree node checks 2020-08-03 15:04:59 +01:00
Joe Thornber cc2582b8b1 [thin_check (rust)] factor out pdata/btree.rs 2020-08-03 12:37:32 +01:00
Joe Thornber cdf19b2454 [functional-tests (rust)] remove comment 2020-08-03 11:00:09 +01:00
Joe Thornber 39822a7165 [functional-tests (rust)] squash warnings 2020-08-03 10:59:19 +01:00
Joe Thornber 9552cb4817 [functional-tests] Port thin_rmap tests to Rust.
cargo test
2020-08-03 10:47:03 +01:00
Joe Thornber ad29fe65fa [functional-tests] Use thin_restore macro 2020-07-31 16:34:04 +01:00
Joe Thornber baf1fe325f [functional-tests] Move thin_dump tests to Rust.
cargo test
2020-07-31 16:31:10 +01:00
Joe Thornber 78db9a24fa [functional-tests (rust)] factor out TestDir 2020-07-31 14:26:22 +01:00
Joe Thornber 084a26bf85 [functional-tests] Recode thin_restore tests in Rust.
Now part of 'cargo test'
2020-07-31 12:12:40 +01:00
Joe Thornber fcfcc60b89 [functional-tests] Move thin_check functional tests to Rust.
They'll be run as part of 'cargo test' now.
2020-07-31 11:04:12 +01:00
Joe Thornber 7243f95380 [thin_check (rust)] Drop nr threads down to 4
We get very little benefit from threads atm.  Need to improve IO handling first.
2020-07-30 10:17:36 +01:00
Joe Thornber de172147d3 [thin_check (rust)] squash a lot of warnings 2020-07-30 10:12:51 +01:00
Joe Thornber f7623e6264 [thin_check (rust)] remove spurious mutex. 2020-07-30 09:59:02 +01:00
Joe Thornber 8146fba9d2 [thin_check (rust)] Move mutex inside IoEngines.
Makes it easier to share an engine between different threads.
2020-07-30 08:47:16 +01:00
Joe Thornber 4f120911d2 [thin_check (rust)] First attempt at multithreading the tree walking.
Still using a single io engine, so little benefit.
2020-07-29 16:38:52 +01:00
Joe Thornber d5597d5d36 [thin_check (rust)] Factor out tree walking code 2020-07-29 11:12:03 +01:00
Joe Thornber e9abdd9c88 [thin_check (rust)] Switch to a different io_uring crate.
This one works.
2020-07-28 12:57:30 +01:00
Joe Thornber a90294e279 [thin_check (rust)] read many blocks at once.
We need to switch to io_uring to really get the benefit of this.
2020-07-28 11:45:25 +01:00
Joe Thornber 062a1b8a2c [thin_check (rust)] Walk mapping tree.
Sync IO, no checks beyond checksumming.
2020-07-28 10:51:48 +01:00
Joe Thornber 1398cf31d1 [thin_check (Rust)] work in progress 2020-07-27 15:53:42 +01:00
Joe Thornber 3cf6307762 Merge branch 'master' into 2020-06-13-thin-check-rewrite 2020-07-27 15:53:26 +01:00
Joe Thornber 5e19029e65 Merge branch '2020-06-19-use-anyhow' into 2020-06-13-thin-check-rewrite 2020-06-22 10:16:27 +01:00
Joe Thornber fdf641aff3 [thin_metadata_{pack,unpak}] use anyhow in toplevel. 2020-06-21 11:50:24 +01:00
Joe Thornber 29d56f62a5 wip 2020-06-14 08:17:46 +01:00
120 changed files with 9816 additions and 4293 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ core
googletest/
bin/pdata_tools
bin/pdata_tools_dev
thin_check
thin_dump
thin_restore

13
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: check-merge-conflict
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/doublify/pre-commit-rust
rev: master
hooks:
- id: fmt
- id: clippy

View File

@ -20,18 +20,22 @@ V=@
PROGRAMS=\
bin/pdata_tools
ifeq ("@TESTING@", "yes")
DEV_TOOLS=\
bin/pdata_tools_dev
TESTLIBS=\
lib/libft.so
endif
.PHONY: all
all: $(PROGRAMS) $(TESTLIBS)
.PHONY: all dev-tools
all: $(PROGRAMS)
dev-tools: $(DEV_TOOLS)
ifeq ("@TESTING@", "yes")
all += $(TESTLIB)
endif
include contrib/Makefile
SOURCE=\
COMMON_SOURCE=\
base/output_file_requirements.cc \
base/application.cc \
base/base64.cc \
@ -50,13 +54,6 @@ SOURCE=\
block-cache/copier.cc \
block-cache/io_engine.cc \
block-cache/mem_pool.cc \
caching/cache_check.cc \
caching/cache_dump.cc \
caching/cache_metadata_size.cc \
caching/cache_repair.cc \
caching/cache_restore.cc \
caching/cache_writeback.cc \
caching/commands.cc \
caching/hint_array.cc \
caching/mapping_array.cc \
caching/metadata.cc \
@ -64,13 +61,8 @@ SOURCE=\
caching/restore_emitter.cc \
caching/superblock.cc \
caching/xml_format.cc \
era/commands.cc \
era/era_array.cc \
era/era_check.cc \
era/era_detail.cc \
era/era_dump.cc \
era/era_invalidate.cc \
era/era_restore.cc \
era/metadata.cc \
era/metadata_dump.cc \
era/restore_emitter.cc \
@ -95,11 +87,7 @@ SOURCE=\
persistent-data/space_map.cc \
persistent-data/transaction_manager.cc \
persistent-data/validators.cc \
thin-provisioning/commands.cc \
thin-provisioning/cache_stream.cc \
thin-provisioning/chunk_stream.cc \
thin-provisioning/device_tree.cc \
thin-provisioning/fixed_chunk_stream.cc \
thin-provisioning/human_readable_format.cc \
thin-provisioning/mapping_tree.cc \
thin-provisioning/metadata.cc \
@ -107,40 +95,71 @@ SOURCE=\
thin-provisioning/metadata_counter.cc \
thin-provisioning/metadata_dumper.cc \
thin-provisioning/override_emitter.cc \
thin-provisioning/pool_stream.cc \
thin-provisioning/restore_emitter.cc \
thin-provisioning/rmap_visitor.cc \
thin-provisioning/superblock.cc \
thin-provisioning/xml_format.cc
TOOLS_SOURCE=\
caching/commands.cc \
caching/cache_check.cc \
caching/cache_dump.cc \
caching/cache_metadata_size.cc \
caching/cache_repair.cc \
caching/cache_restore.cc \
caching/cache_writeback.cc \
era/commands.cc \
era/era_check.cc \
era/era_dump.cc \
era/era_invalidate.cc \
era/era_restore.cc \
thin-provisioning/commands.cc \
thin-provisioning/thin_check.cc \
thin-provisioning/thin_delta.cc \
thin-provisioning/thin_dump.cc \
thin-provisioning/thin_ls.cc \
thin-provisioning/thin_metadata_size.cc \
thin-provisioning/thin_pool.cc \
thin-provisioning/thin_repair.cc \
thin-provisioning/thin_restore.cc \
thin-provisioning/thin_rmap.cc \
thin-provisioning/thin_trim.cc \
thin-provisioning/xml_format.cc
thin-provisioning/thin_trim.cc
DEVTOOLS_SOURCE=\
caching/cache_debug.cc \
caching/devel_commands.cc \
dbg-lib/bitset_block_dumper.cc \
dbg-lib/command_interpreter.cc \
dbg-lib/commands.cc \
dbg-lib/index_block_dumper.cc \
dbg-lib/output_formatter.cc \
dbg-lib/simple_show_traits.cc \
dbg-lib/sm_show_traits.cc \
era/devel_commands.cc \
era/era_debug.cc \
thin-provisioning/cache_stream.cc \
thin-provisioning/chunk_stream.cc \
thin-provisioning/damage_generator.cc \
thin-provisioning/devel_commands.cc \
thin-provisioning/fixed_chunk_stream.cc \
thin-provisioning/pool_stream.cc \
thin-provisioning/thin_debug.cc \
thin-provisioning/thin_generate_damage.cc \
thin-provisioning/thin_generate_mappings.cc \
thin-provisioning/thin_generate_metadata.cc \
thin-provisioning/thin_journal.cc \
thin-provisioning/thin_journal_check.cc \
thin-provisioning/thin_ll_dump.cc \
thin-provisioning/thin_ll_restore.cc \
thin-provisioning/thin_show_duplicates.cc \
thin-provisioning/thin_generate_damage.cc \
thin-provisioning/thin_generate_metadata.cc \
thin-provisioning/thin_generate_mappings.cc \
thin-provisioning/variable_chunk_stream.cc \
thin-provisioning/thin_show_metadata.cc \
thin-provisioning/thin_patch_superblock.cc \
thin-provisioning/thin_pool.cc \
thin-provisioning/thin_scan.cc \
thin-provisioning/thin_show_duplicates.cc \
thin-provisioning/thin_show_metadata.cc \
thin-provisioning/variable_chunk_stream.cc \
ui/ui.cc
ifeq ("@DEVTOOLS@", "yes")
SOURCE+=$(DEVTOOLS_SOURCE)
endif
SOURCE=$(COMMON_SOURCE) $(TOOLS_SOURCE)
DEV_SOURCE=$(COMMON_SOURCE) $(DEVTOOLS_SOURCE)
ifeq ("@STATIC@", "yes")
SOURCE += thin-provisioning/static_library_emitter.cc
@ -153,6 +172,7 @@ CXX:=@CXX@
AR:=@AR@
STRIP:=@STRIP@
OBJECTS:=$(subst .cc,.o,$(SOURCE))
DEV_OBJECTS:=$(subst .cc,.o,$(DEV_SOURCE))
ifeq ("@STATIC@", "yes")
EMITTERS += $(PLUGIN_LIBS)
@ -164,20 +184,14 @@ CFLAGS+=-g -Wall -O3 -fPIC
CFLAGS+=@LFS_FLAGS@
CXXFLAGS+=-g -Wall -fPIC -fno-strict-aliasing -std=c++11
ifeq ("@DEVTOOLS@", "yes")
CXXFLAGS+=-DDEV_TOOLS
endif
CXXFLAGS+=@CXXOPTIMISE_FLAG@
CXXFLAGS+=@CXXDEBUG_FLAG@
CXXFLAGS+=@CXX_STRERROR_FLAG@
CXXFLAGS+=@LFS_FLAGS@
INCLUDES+=-I$(TOP_BUILDDIR) -I$(TOP_DIR) -I$(TOP_DIR)/thin-provisioning
CPPFLAGS?=@CPPFLAGS@
CPPFLAGS+=-I$(TOP_BUILDDIR) -I$(TOP_DIR)
LIBS:=-laio -lexpat -ldl
ifeq ("@DEVTOOLS@", "yes")
LIBS+=-lncurses
endif
DEV_LIBS:=-lncurses
ifeq ("@STATIC_CXX@", "yes")
CXXLIB+=-Wl,-Bstatic -lstdc++ -Wl,-Bdynamic -Wl,--as-needed
@ -201,37 +215,29 @@ INSTALL_DIR = $(INSTALL) -m 755 -d
INSTALL_PROGRAM = $(INSTALL) -m 755
INSTALL_DATA = $(INSTALL) -p -m 644
ifeq ("@TESTING@", "yes")
TEST_INCLUDES=\
-I$(TOP_DIR) \
-Igoogletest/googlemock/include \
-Igoogletest/googletest/include
else
TEST_INCLUDES=
endif
.SUFFIXES: .d .txt .8
%.o: %.cc
@echo " [CXX] $<"
@mkdir -p $(dir $@)
$(V) $(CXX) -c $(INCLUDES) $(CXXFLAGS) -o $@ $<
$(V) $(CXX) -MM -MT $(subst .cc,.o,$<) $(INCLUDES) $(TEST_INCLUDES) $(CXXFLAGS) $< > $*.$$$$; \
$(V) $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) -o $@ $<
$(V) $(CXX) -MM -MT $(subst .cc,.o,$<) $(CPPFLAGS) $(CXXFLAGS) $< > $*.$$$$; \
sed 's,\([^ :]*\)\.o[ :]*,\1.o \1.gmo $* : Makefile ,g' < $*.$$$$ > $*.d; \
$(RM) $*.$$$$
%.o: %.c
@echo " [CC] $<"
@mkdir -p $(dir $@)
$(V) $(CC) -c $(INCLUDES) $(CFLAGS) -o $@ $<
$(V) $(CC) -MM -MT $(subst .cc,.o,$<) $(INCLUDES) $(TEST_INCLUDES) $(CFLAGS) $< > $*.$$$$; \
$(V) $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<
$(V) $(CC) -MM -MT $(subst .cc,.o,$<) $(CPPFLAGS) $(CFLAGS) $< > $*.$$$$; \
sed 's,\([^ :]*\)\.o[ :]*,\1.o \1.gmo $* : Makefile ,g' < $*.$$$$ > $*.d; \
$(RM) $*.$$$$
%.8: %.txt bin/txt2man
@echo " [txt2man] $<"
@mkdir -p $(dir $@)
$(V) bin/txt2man -p -t $(basename $(notdir $<)) $< > $@
$(V) bin/txt2man -t $(basename $(notdir $<)) \
-s 8 -v "System Manager's Manual" -r "Device Mapper Tools" $< > $@
#----------------------------------------------------------------
@ -245,10 +251,16 @@ bin/pdata_tools: $(OBJECTS) $(EMITTERS)
@mkdir -p $(dir $@)
$(V) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $+ $(LIBS) $(CXXLIB)
bin/pdata_tools_dev: $(DEV_OBJECTS)
@echo " [LD] $@"
@mkdir -p $(dir $@)
$(V) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $+ $(LIBS) $(DEV_LIBS) $(CXXLIB)
#----------------------------------------------------------------
DEPEND_FILES=\
$(subst .cc,.d,$(SOURCE)) \
$(subst .cc,.d,$(DEV_SOURCE)) \
$(subst .cc,.d,$(TEST_SOURCE)) \
$(subst .cc,.d,$(CXX_PROGRAM_SOURCE)) \
$(subst .c,.d,$(C_PROGRAM_SOURCE))
@ -259,7 +271,8 @@ clean:
find . -name \*.o -delete
find . -name \*.gmo -delete
find . -name \*.d -delete
$(RM) $(TEST_PROGRAMS) $(PROGRAMS) $(GMOCK_OBJECTS) lib/*.a lib/*.so
$(RM) $(PROGRAMS) $(DEV_TOOLS) lib/*.a lib/*.so
$(RM) man8/*.8
distclean: clean
$(RM) config.cache config.log config.status configure.h version.h Makefile unit-tests/Makefile
@ -330,17 +343,11 @@ install: bin/pdata_tools $(MANPAGES)
$(INSTALL_DATA) man8/era_restore.8 $(MANPATH)/man8
$(INSTALL_DATA) man8/era_invalidate.8 $(MANPATH)/man8
$(INSTALL_DATA) man8/thin_trim.8 $(MANPATH)/man8
ifeq ("@DEVTOOLS@", "yes")
ln -s -f pdata_tools $(BINDIR)/thin_show_duplicates
ln -s -f pdata_tools $(BINDIR)/thin_ll_dump
ln -s -f pdata_tools $(BINDIR)/thin_show_duplicates
ln -s -f pdata_tools $(BINDIR)/thin_generate_metadata
ln -s -f pdata_tools $(BINDIR)/thin_scan
endif
.PHONY: install
ifeq ("@TESTING@", "yes")
#----------------------------------------------------------------
include unit-tests/Makefile
LIBFT_SOURCE=\
@ -356,11 +363,11 @@ lib/libft.so: $(LIBFT_OBJECTS)
.PHONEY: functional-test unit-test
functional-test: bin/pdata_tools lib/libft.so
functional-test: $(PROGRAMS) $(DEV_TOOLS) $(TESTLIBS)
cd functional-tests && ./run-tests run
test: functional-test unit-test
endif
#----------------------------------------------------------------
-include $(DEPEND_FILES)

1301
autoconf/config.guess vendored

File diff suppressed because it is too large Load Diff

2899
autoconf/config.sub vendored

File diff suppressed because it is too large Load Diff

View File

@ -25,8 +25,14 @@ command::die(string const &msg)
}
::uint64_t
command::parse_uint64(string const &str, string const &desc)
command::parse_uint64(char const *str, char const *desc)
{
if (!str) {
ostringstream out;
out << "Couldn't parse " << desc << ": NULL";
die(out.str());
}
try {
// FIXME: check trailing garbage is handled
return lexical_cast<::uint64_t>(str);
@ -47,7 +53,7 @@ application::run(int argc, char **argv)
{
string cmd = get_basename(argv[0]);
if (cmd == string("pdata_tools")) {
if (cmd.find("pdata_tools") == 0) {
argc--;
argv++;

View File

@ -19,7 +19,7 @@ namespace base {
virtual ~command() {}
void die(std::string const &msg);
uint64_t parse_uint64(std::string const &str, std::string const &desc);
uint64_t parse_uint64(char const *str, char const *desc);
virtual void usage(std::ostream &out) const = 0;

View File

@ -71,11 +71,18 @@ void
file_utils::check_file_exists(string const &file, bool must_be_regular_file) {
struct stat info;
int r = ::stat(file.c_str(), &info);
if (r)
throw runtime_error("Couldn't stat file");
if (r) {
ostringstream msg;
msg << file << ": " << base::error_string(errno);
throw runtime_error(msg.str());
}
if (must_be_regular_file && !S_ISREG(info.st_mode))
throw runtime_error("Not a regular file");
if (!S_ISREG(info.st_mode)) {
if (must_be_regular_file)
throw runtime_error("Not a regular file");
if (!S_ISBLK(info.st_mode))
throw runtime_error("Not a block device or regular file");
}
}
file_utils::file_descriptor
@ -116,8 +123,11 @@ file_utils::get_file_length(string const &file) {
uint64_t nr_bytes;
int r = ::stat(file.c_str(), &info);
if (r)
throw runtime_error("Couldn't stat path");
if (r) {
ostringstream msg;
msg << file << ": " << base::error_string(errno);
throw runtime_error(msg.str());
}
if (S_ISREG(info.st_mode))
// It's okay to cast st_size to a uint64_t value.
@ -136,9 +146,11 @@ file_utils::get_file_length(string const &file) {
throw runtime_error("ioctl BLKGETSIZE64 failed");
}
::close(fd);
} else
// FIXME: needs a better message
throw runtime_error("bad path");
} else {
ostringstream msg;
msg << file << ": " << "Not a block device or regular file";
throw runtime_error(msg.str());
}
return nr_bytes;
}
@ -155,8 +167,11 @@ file_utils::zero_superblock(std::string const &path)
throw runtime_error("out of memory");
memset(buffer, 0, SUPERBLOCK_SIZE);
if (::write(fd.fd_, buffer, SUPERBLOCK_SIZE) != SUPERBLOCK_SIZE)
if (::write(fd.fd_, buffer, SUPERBLOCK_SIZE) != SUPERBLOCK_SIZE) {
free(buffer);
throw runtime_error("couldn't zero superblock");
}
free(buffer);
}
//----------------------------------------------------------------

View File

@ -1,5 +1,4 @@
#!/bin/sh
test "$HOME" = ~ || exec ksh $0 "$@" # try ksh if sh too old (not yet POSIX)
# Copyright (C) 2001, 2002, 2003 Marc Vertes
@ -18,7 +17,7 @@ test "$HOME" = ~ || exec ksh $0 "$@" # try ksh if sh too old (not yet POSIX)
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
# txt2man-1.5
# release 1.7.1
usage()
{
@ -27,7 +26,7 @@ NAME
txt2man - convert flat ASCII text to man page format
SYNOPSIS
txt2man [-hpTX] [-t mytitle] [-P pname] [-r rel] [-s sect]
[-v vol] [-I txt] [-B txt] [ifile]
[-v vol] [-I txt] [-B txt] [-d date] [ifile]
DESCRIPTION
txt2man converts the input text into nroff/troff standard man(7)
macros used to format Unix manual pages. Nice pages can be generated
@ -45,26 +44,32 @@ DESCRIPTION
Here is how text patterns are recognized and processed:
Sections These headers are defined by a line in upper case, starting
column 1. If there is one or more leading spaces, a
sub-section will be generated instead.
sub-section will be generated instead. Optionally, the
Section name can be preceded by a blank line. This is useful
for a better visualization of the source text to be used to
generate the manpage.
Paragraphs They must be separated by a blank line, and left aligned.
Alternatively two blank spaces can be used to produce the
same result. This option will provide a better visualization
of the source text to be used to generate the manpage.
Tag list The item definition is separated from the item description
by at least 2 blank spaces, even before a new line, if
definition is too long. Definition will be emphasized
by default.
Bullet list
Bullet list items are defined by the first word being "-"
or "*" or "o".
or "*" or "o".
Enumerated list
The first word must be a number followed by a dot.
The first word must be a number followed by a dot.
Literal display blocks
This paragraph type is used to display unmodified text,
for example source code. It must be separated by a blank
line, and be indented. It is primarily used to format
unmodified source code. It will be printed using fixed font
whenever possible (troff).
This paragraph type is used to display unmodified text,
for example source code. It must be separated by a blank
line and be indented by a TAB. It is primarily used to format
unmodified source code. It will be printed using fixed font
whenever possible (troff).
Cross references
A cross reference (another man page) is defined by a word
followed by a number in parenthesis.
A cross reference (another man page) is defined by a word
followed by a number in parenthesis.
Special sections:
NAME The function or command name and short description are set in
@ -72,37 +77,45 @@ DESCRIPTION
SYNOPSIS This section receives a special treatment to identify command
name, flags and arguments, and propagate corresponding
attributes later in the text. If a C like function is recognized
(word immediately followed by an open parenthesis), txt2man will
print function name in bold font, types in normal font, and
variables in italic font. The whole section will be printed using
a fixed font family (courier) whenever possible (troff).
(word immediately followed by an open parenthesis), txt2man will
print function name in bold font, types in normal font, and
variables in italic font. The whole section will be printed using
a fixed font family (courier) whenever possible (troff).
It is a good practice to embed documentation into source code, by using
comments or constant text variables. txt2man allows to do that, keeping
comments or constant text variables. txt2man allows one to do that, keeping
the document source readable, usable even without further formatting
(i.e. for online help) and easy to write. The result is high quality
and standard complying document.
OPTIONS
-h The option -h displays help.
-d date Set date in header. Defaults to current date.
-P pname Set pname as project name in header. Default to uname -s.
-p Probe title, section name and volume.
-t mytitle Set mytitle as title of generated man page.
-r rel Set rel as project name and release.
-s sect Set sect as section in heading, ususally a value from 1 to 8.
-s sect Set sect as section in heading, usually a value from 1 to 8.
-v vol Set vol as volume name, i.e. "Unix user 's manual".
-I txt Italicize txt in output. Can be specified more than once.
-B txt Emphasize (bold) txt in output. Can be specified more than once.
-T Text result previewing using PAGER, usually more(1).
-X X11 result previewing using gxditview(1).
ENVIRONMENT
PAGER name of paging command, usually more(1), or less(1). If not set
falls back to more(1).
EXAMPLE
PAGER name of paging command, usually more(1), or less(1). If not set
falls back to more(1).
SOURCE_DATE_EPOCH Unix timestamp that is used for date in header instead
of current date.
EXAMPLES
Try this command to format this text itself:
$ txt2man -h 2>&1 | txt2man -T
$ txt2man -h 2>&1 | txt2man -T
The following command will generate a manpage level 1 to foo-1.1.0 program,
from foo.txt file, used as source code to previously mentioned manpage:
$ txt2man -d "15 May 2016" -t foo -r foo-1.1.0 -s 1 -v "show stars on screen" foo.txt > foo.1
HINTS
To obtain an overall good formating of output document, keep paragraphs
To obtain an overall good formatting of output document, keep paragraphs
indented correctly. If you have unwanted bold sections, search for
multiple spaces between words, which are used to identify a tag list
(term followed by a description). Choose also carefully the name of
@ -127,35 +140,53 @@ doprobe=
itxt=
btxt=
post=cat
while getopts :hpTXr:s:t:v:P:I:B: opt
while getopts :d:hpTXr:s:t:v:P:I:B: opt
do
case $opt in
r) rel=$OPTARG;;
t) title=$OPTARG;;
s) section=$OPTARG;;
v) volume=$OPTARG;;
P) sys=$OPTARG;;
p) doprobe=1;;
I) itxt="$OPTARG§$itxt";;
B) btxt=$OPTARG;;
T) post="groff -mandoc -Tlatin1 | ${PAGER:-more}";;
X) post="groff -mandoc -X";;
*) usage; exit;;
(d) date=$OPTARG;;
(r) rel=$OPTARG;;
(t) title=$OPTARG;;
(s) section=$OPTARG;;
(v) volume=$OPTARG;;
(P) sys=$OPTARG;;
(p) doprobe=1;;
(I) itxt="$OPTARG§$itxt";;
(B) btxt="$OPTARG§$btxt";;
(T) post="groff -mandoc -Tlatin1 | ${PAGER:-more}";;
(X) post="groff -mandoc -TX100-12 -rS12";;
(*) usage; exit;;
esac
done
shift $(($OPTIND - 1))
# Compatibility wrapper for BSD/GNU date, for parsing dates
if date -j >/dev/null 2>&1; then
pdate() { date -u -j -f '@%s' "$@"; }
else
pdate() { date -u -d "$@"; }
fi
if [ -n "$SOURCE_DATE_EPOCH" ]; then
date=$(LC_ALL=C pdate "@$SOURCE_DATE_EPOCH" +'%d %B %Y')
fi
date=${date:-$(LC_ALL=C date -u +'%d %B %Y')}
if test "$doprobe"
then
title=${1##*/}; title=${title%.txt}
section="8"
volume="System Manager's Manual"
if grep -q '#include ' $1
then
section=${section:-3}
volume=${volume:-"$sys Programmer's Manual"}
else
section=${section:-1}
volume=${volume:-"$sys Reference Manual"}
fi
# get release from path
#rel=$(pwd | sed 's:/.*[^0-9]/::g; s:/.*::g')
rel="Device Mapper Tools"
rel=${rel:-"$(pwd | sed 's:/.*[^0-9]/::g; s:/.*::g')"}
fi
head=".\\\" Text automatically generated by txt2man
head="\" Text automatically generated by txt2man
.TH $title $section \"$rel\" \"$volume\""
# All tabs converted to spaces
@ -163,11 +194,11 @@ expand $* |
# gawk is needed because use of non standard regexp
gawk --re-interval -v head="$head" -v itxt="$itxt" -v btxt="$btxt" '
BEGIN {
print head
print ".\\" head
avar[1] = btxt; avar[2] = itxt
for (k in avar) {
mark = (k == 1) ? "\\fB" : "\\fI"
split(avar[k], tt, "§")
split(avar[k], tt, "§")
for (i in tt)
if (tt[i] != "")
subwords["\\<" tt[i] "\\>"] = mark tt[i] "\\fP"
@ -179,11 +210,11 @@ BEGIN {
}
{
# to avoid some side effects in regexp
sub(/\.\.\./, "\\.\\.\\.")
gsub(/\.\.\./, "\\.\\.\\.")
# remove spaces in empty lines
sub(/^ +$/,"")
}
/^[[:upper:][:space:]]+$/ {
/^[:space:]*[[:upper:][:digit:]]+[[:upper:][:space:][:digit:][:punct:]]+$/ {
# Section header
if ((in_bd + 0) == 1) {
in_bd = 0
@ -199,8 +230,10 @@ BEGIN {
print ".SS" $0
sub(/^ +/, "")
section = $0
if (section == "SYNOPSIS")
if (section == "SYNOPSIS") {
print ".nf\n.fam C"
in_bd = 1
}
ls = 0 # line start index
pls = 0 # previous line start index
pnzls = 0 # previous non zero line start index
@ -216,7 +249,7 @@ BEGIN {
pnzls = ls
match($0, /[^ ]/)
ls = RSTART
if (pls == 0 && pnzls > 0 && ls > pnzls && $1 !~ /^[0-9\-\*\o]\.*$/) {
if (in_bd == 0 && pls == 0 && pnzls > 0 && ls > pnzls && $1 !~ /^[\-\*o]$|^[0-9]+\.$/) {
# example display block
if (prevblankline == 1) {
print ".PP"
@ -230,8 +263,10 @@ BEGIN {
ind[0] = ls
}
(in_bd + 0) == 1 {
# In example display block
if (ls != 0 && ls < eoff) {
# In block display
if (section == "SYNOPSIS")
;
else if (ls != 0 && ls < eoff) {
# End of litteral display block
in_bd = 0
print ".fam T\n.fi"
@ -244,11 +279,12 @@ section == "NAME" {
section == "SYNOPSIS" {
# Identify arguments of fcts and cmds
if (type["SYNOPSIS"] == "") {
if (index($0, "(") == 0 && index($0, ")") == 0 &&
index($0, "#include") == 0)
type["SYNOPSIS"] = "cmd"
else
if ($0 ~ /\(/)
type["SYNOPSIS"] = "fct"
else if ($1 == "struct" || $2 == "struct")
type["SYNOPSIS"] = "struct"
else if ($1 && $1 !~ /^#|typedef|struct|union|enum/)
type["SYNOPSIS"] = "cmd"
}
if (type["SYNOPSIS"] == "cmd") {
# Line is a command line
@ -263,19 +299,19 @@ section == "SYNOPSIS" {
if (a ~ /^[^\-]/)
subwords["\\<" a "\\>"] = "\\fI" a "\\fP"
}
} else {
} else if (type["SYNOPSIS"] == "fct") {
# Line is a C function definition
if ($1 == "typedef")
subwords["\\<" $2 "\\>"] = "\\fI" $2 "\\fP"
else if ($1 == "#define")
if ($1 == "typedef") {
if ($0 !~ /\(\*/)
subwords["\\<" $2 "\\>"] = "\\fI" $2 "\\fP"
} else if ($1 == "#define")
subwords["\\<" $2 "\\>"] = "\\fI" $2 "\\fP"
for (i = 1; i <= NF; i++) {
if ($i ~ /[\,\)]/) {
if ($i ~ /[,\)];*$/) {
a = $i
sub(/.*\(/, "", a)
gsub(/\W/, "", a)
if (a !~ /^void$/)
subwords["\\<" a "\\>"] = "\\fI" a "\\fP"
subwords["\\<" a "\\>"] = "\\fI" a "\\fP"
}
}
}
@ -298,13 +334,19 @@ section == "SYNOPSIS" {
}
}
# word attributes
for (i in subwords)
gsub(i, subwords[i])
n = asorti(subwords, indices)
for (i = 1; i <= n; i++)
gsub(indices[i], subwords[indices[i]])
# shell options
gsub(/\B\-+\w+(\-\w+)*/, "\\fB&\\fP")
# unprotect dots inside words
gsub(/_dOt_/, ".")
if (section == "SYNOPSIS") {
sub(/^ /, "")
print
next
}
if (match($0, /[^ ] +/) > 0) {
# tag list item
adjust_indent()
@ -347,6 +389,8 @@ section == "SYNOPSIS" {
}
if (section != "SYNOPSIS" || $0 ~ /^ {1,4}/)
sub(/ */,"")
# Protect lines starting by simple quotes
sub(/^'\''/, "\\(cq")
print
}

View File

@ -10,12 +10,14 @@ using namespace std;
copier::copier(io_engine &engine,
string const &src, string const &dest,
sector_t block_size, size_t mem)
sector_t block_size, size_t mem,
sector_t src_offset, sector_t dest_offset)
: pool_(block_size * 512, mem, PAGE_SIZE),
block_size_(block_size),
engine_(engine),
src_handle_(engine_.open_file(src, io_engine::M_READ_ONLY)),
dest_handle_(engine_.open_file(dest, io_engine::M_READ_WRITE)),
src_offset_(src_offset), dest_offset_(dest_offset),
genkey_count_(0)
{
}
@ -45,8 +47,8 @@ copier::issue(copy_op const &op)
auto r = engine_.issue_io(src_handle_,
io_engine::D_READ,
to_sector(op.src_b),
to_sector(op.src_e),
to_src_sector(op.src_b),
to_src_sector(op.src_e),
data,
key);
@ -151,8 +153,8 @@ copier::wait_successful(io_engine::wait_result const &p)
j.op.read_complete = true;
if (!engine_.issue_io(dest_handle_,
io_engine::D_WRITE,
to_sector(j.op.dest_b),
to_sector(j.op.dest_b + (j.op.src_e - j.op.src_b)),
to_dest_sector(j.op.dest_b),
to_dest_sector(j.op.dest_b + (j.op.src_e - j.op.src_b)),
j.data,
it->first)) {
complete(j);
@ -177,9 +179,15 @@ copier::complete(copy_job const &j)
}
sector_t
copier::to_sector(block_address b) const
copier::to_src_sector(block_address b) const
{
return b * block_size_;
return src_offset_ + b * block_size_;
}
sector_t
copier::to_dest_sector(block_address b) const
{
return dest_offset_ + b * block_size_;
}
unsigned

View File

@ -61,13 +61,22 @@ namespace bcache {
public:
copier(io_engine &engine,
std::string const &src, std::string const &dest,
sector_t block_size, size_t mem);
sector_t block_size, size_t mem,
sector_t src_offset, sector_t dest_offset);
~copier();
sector_t get_block_size() const {
return block_size_;
}
sector_t get_src_offset() const {
return src_offset_;
}
sector_t get_dest_offset() const {
return dest_offset_;
}
// Blocks if out of memory.
void issue(copy_op const &op);
@ -83,7 +92,8 @@ namespace bcache {
void wait_();
void complete(copy_job const &j);
sector_t to_sector(block_address b) const;
sector_t to_src_sector(block_address b) const;
sector_t to_dest_sector(block_address b) const;
unsigned genkey();
mempool pool_;
@ -91,6 +101,8 @@ namespace bcache {
io_engine &engine_;
io_engine::handle src_handle_;
io_engine::handle dest_handle_;
sector_t src_offset_;
sector_t dest_offset_;
unsigned genkey_count_;
using job_map = std::map<unsigned, copy_job>;

View File

@ -174,9 +174,6 @@ aio_engine::wait_(timespec *ts)
cbs_.free(cb);
return optional<wait_result>(make_pair(false, context));
}
// shouldn't get here
return optional<wait_result>(make_pair(false, 0));
}
struct timespec

231
caching/cache_debug.cc Normal file
View File

@ -0,0 +1,231 @@
// Copyright (C) 2012 Red Hat, Inc. All rights reserved.
//
// This file is part of the thin-provisioning-tools source.
//
// thin-provisioning-tools is free software: you can redistribute it
// and/or modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// thin-provisioning-tools is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with thin-provisioning-tools. If not, see
// <http://www.gnu.org/licenses/>.
#include <boost/lexical_cast.hpp>
#include <getopt.h>
#include <iostream>
#include <string>
#include "dbg-lib/array_block_dumper.h"
#include "dbg-lib/btree_node_dumper.h"
#include "dbg-lib/bitset_block_dumper.h"
#include "dbg-lib/command_interpreter.h"
#include "dbg-lib/commands.h"
#include "dbg-lib/index_block_dumper.h"
#include "dbg-lib/output_formatter.h"
#include "dbg-lib/sm_show_traits.h"
#include "persistent-data/file_utils.h"
#include "persistent-data/space-maps/disk_structures.h"
#include "caching/commands.h"
#include "caching/metadata.h"
#include "version.h"
using namespace dbg;
using namespace persistent_data;
using namespace std;
using namespace caching;
//----------------------------------------------------------------
namespace {
class help : public dbg::command {
virtual void exec(strings const &args, ostream &out) {
out << "Commands:" << endl
<< " superblock [block#]" << endl
<< " block_node <block# of array block-tree node>" << endl
<< " bitset_block <block# of bitset block>" << endl
<< " index_block <block# of metadata space map root>" << endl
<< " mapping_block <block# of mappings array block>" << endl
<< " exit" << endl;
}
};
class show_superblock : public dbg::command {
public:
explicit show_superblock(block_manager::ptr bm)
: bm_(bm) {
}
virtual void exec(strings const &args, ostream &out) {
if (args.size() > 2)
throw runtime_error("incorrect number of arguments");
block_address b = caching::SUPERBLOCK_LOCATION;
if (args.size() == 2)
b = boost::lexical_cast<block_address>(args[1]);
caching::superblock sb = read_superblock(bm_, b);
formatter::ptr f = create_xml_formatter();
ostringstream version;
field(*f, "csum", sb.csum);
field(*f, "flags", sb.flags.encode());
field(*f, "blocknr", sb.blocknr);
field(*f, "uuid", sb.uuid); // FIXME: delimit, and handle non-printable chars
field(*f, "magic", sb.magic);
field(*f, "version", sb.version);
field(*f, "policy_name", reinterpret_cast<char const*>(sb.policy_name));
version << sb.policy_version[0] << "."
<< sb.policy_version[1] << "."
<< sb.policy_version[2];
field(*f, "policy_version", version.str().c_str());
field(*f, "policy_hint_size", sb.policy_hint_size);
sm_disk_detail::sm_root_disk const *d;
sm_disk_detail::sm_root v;
{
d = reinterpret_cast<sm_disk_detail::sm_root_disk const *>(sb.metadata_space_map_root);
sm_disk_detail::sm_root_traits::unpack(*d, v);
formatter::ptr f2 = create_xml_formatter();
sm_root_show_traits::show(f2, "value", v);
f->child("metadata_space_map_root", f2);
}
field(*f, "mapping_root", sb.mapping_root);
if (sb.version >= 2)
field(*f, "dirty_root", *sb.dirty_root);
field(*f, "hint_root", sb.hint_root);
field(*f, "discard_root", sb.discard_root);
field(*f, "discard_block_size", sb.discard_block_size);
field(*f, "discard_nr_blocks", sb.discard_nr_blocks);
field(*f, "data_block_size", sb.data_block_size);
field(*f, "metadata_block_size", sb.metadata_block_size);
field(*f, "cache_blocks", sb.cache_blocks);
field(*f, "compat_flags", sb.compat_flags);
field(*f, "compat_ro_flags", sb.compat_ro_flags);
field(*f, "incompat_flags", sb.incompat_flags);
field(*f, "read_hits", sb.read_hits);
field(*f, "read_misses", sb.read_misses);
field(*f, "write_hits", sb.write_hits);
field(*f, "write_misses", sb.write_misses);
f->output(out, 0);
}
private:
block_manager::ptr bm_;
};
class mapping_show_traits : public caching::mapping_traits {
public:
typedef mapping_traits value_trait;
static void show(formatter::ptr f, string const &key, caching::mapping const &value) {
field(*f, "oblock", value.oblock_);
field(*f, "flags", value.flags_);
}
};
//--------------------------------
template <typename ShowTraits>
dbg::command::ptr
create_btree_node_handler(block_manager::ptr bm) {
return create_block_handler(bm, create_btree_node_dumper<ShowTraits>());
}
template <typename ShowTraits>
dbg::command::ptr
create_array_block_handler(block_manager::ptr bm,
typename ShowTraits::value_trait::ref_counter rc) {
return create_block_handler(bm, create_array_block_dumper<ShowTraits>(rc));
}
dbg::command::ptr
create_bitset_block_handler(block_manager::ptr bm) {
return create_block_handler(bm, create_bitset_block_dumper());
}
dbg::command::ptr
create_index_block_handler(block_manager::ptr bm) {
return create_block_handler(bm, create_index_block_dumper());
}
int debug(string const &path) {
using dbg::command;
try {
block_manager::ptr bm = open_bm(path, block_manager::READ_ONLY);
command_interpreter::ptr interp = create_command_interpreter(cin, cout);
interp->register_command("hello", create_hello_handler());
interp->register_command("superblock", command::ptr(new show_superblock(bm)));
interp->register_command("block_node", create_btree_node_handler<uint64_show_traits>(bm));
interp->register_command("bitset_block", create_bitset_block_handler(bm));
interp->register_command("index_block", create_index_block_handler(bm));
interp->register_command("mapping_block", create_array_block_handler<mapping_show_traits>(bm,
mapping_traits::ref_counter()));
interp->register_command("help", command::ptr(new help));
interp->register_command("exit", create_exit_handler(interp));
interp->enter_main_loop();
} catch (std::exception &e) {
cerr << e.what();
return 1;
}
return 0;
}
}
//----------------------------------------------------------------
cache_debug_cmd::cache_debug_cmd()
: command("cache_debug")
{
}
void
cache_debug_cmd::usage(std::ostream &out) const
{
out << "Usage: " << get_name() << " {device|file}" << endl
<< "Options:" << endl
<< " {-h|--help}" << endl
<< " {-V|--version}" << endl;
}
int
cache_debug_cmd::run(int argc, char **argv)
{
int c;
const char shortopts[] = "hV";
const struct option longopts[] = {
{ "help", no_argument, NULL, 'h'},
{ "version", no_argument, NULL, 'V'},
{ NULL, no_argument, NULL, 0 }
};
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
switch(c) {
case 'h':
usage(cout);
return 0;
case 'V':
cerr << THIN_PROVISIONING_TOOLS_VERSION << endl;
return 0;
}
}
if (argc == optind) {
usage(cerr);
exit(1);
}
return debug(argv[optind]);
}
//----------------------------------------------------------------

View File

@ -32,7 +32,7 @@ namespace {
int repair(string const &old_path, string const &new_path) {
bool metadata_touched = false;
try {
file_utils::check_file_exists(new_path, false);
file_utils::check_file_exists(old_path, false);
metadata_touched = true;
metadata_dump(open_metadata_for_read(old_path),
output_emitter(new_path),

View File

@ -31,6 +31,8 @@ namespace {
flags()
: cache_size(4 * 1024 * 1024),
sort_buffers(16 * 1024),
origin_dev_offset(0),
fast_dev_offset(0),
list_failed_blocks(false),
update_metadata(true) {
}
@ -52,6 +54,8 @@ namespace {
maybe_string metadata_dev;
maybe_string origin_dev;
maybe_string fast_dev;
sector_t origin_dev_offset;
sector_t fast_dev_offset;
bool list_failed_blocks;
bool update_metadata;
};
@ -297,13 +301,15 @@ namespace {
int writeback_(flags const &f) {
block_manager::ptr bm = open_bm(*f.metadata_dev, block_manager::READ_WRITE);
metadata md(bm);
metadata md(bm, true);
// FIXME: we're going to have to copy runs to get the through put with small block sizes
unsigned max_ios = f.cache_size / (md.sb_.data_block_size << SECTOR_SHIFT);
aio_engine engine(max_ios);
copier c(engine, *f.fast_dev, *f.origin_dev,
md.sb_.data_block_size, f.cache_size);
md.sb_.data_block_size, f.cache_size,
f.fast_dev_offset >> SECTOR_SHIFT,
f.origin_dev_offset >> SECTOR_SHIFT);
auto bar = create_progress_bar("Copying data");
copy_visitor cv(c, f.sort_buffers, clean_shutdown(md), f.list_failed_blocks,
@ -364,6 +370,8 @@ cache_writeback_cmd::usage(std::ostream &out) const
<< "\t\t--buffer-size-meg <size>\n"
<< "\t\t--list-failed-blocks\n"
<< "\t\t--no-metadata-update\n"
<< "\t\t--origin-device-offset <bytes>\n"
<< "\t\t--fast-device-offset <bytes>\n"
<< "Options:\n"
<< " {-h|--help}\n"
<< " {-V|--version}" << endl;
@ -382,6 +390,8 @@ cache_writeback_cmd::run(int argc, char **argv)
{ "buffer-size-meg", required_argument, NULL, 3 },
{ "list-failed-blocks", no_argument, NULL, 4 },
{ "no-metadata-update", no_argument, NULL, 5 },
{ "origin-device-offset", required_argument, NULL, 6 },
{ "fast-device-offset", required_argument, NULL, 7 },
{ "help", no_argument, NULL, 'h'},
{ "version", no_argument, NULL, 'V'},
{ NULL, no_argument, NULL, 0 }
@ -413,6 +423,14 @@ cache_writeback_cmd::run(int argc, char **argv)
fs.update_metadata = false;
break;
case 6:
fs.origin_dev_offset = parse_uint64(optarg, "origin dev offset");
break;
case 7:
fs.fast_dev_offset = parse_uint64(optarg, "fast dev offset");
break;
case 'h':
usage(cout);
return 0;
@ -452,6 +470,13 @@ cache_writeback_cmd::run(int argc, char **argv)
return 1;
}
if (fs.origin_dev_offset & (SECTOR_SHIFT - 1) ||
fs.fast_dev_offset & (SECTOR_SHIFT - 1)) {
cerr << "Offset must be sector-aligned\n\n";
usage(cerr);
return 1;
}
return writeback(fs);
}

View File

@ -70,6 +70,18 @@ namespace caching {
virtual int run(int argc, char **argv);
};
//------------------------------------------------------
class cache_debug_cmd : public base::command {
public:
cache_debug_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
//------------------------------------------------------
void register_cache_commands(base::application &app);
}

14
caching/devel_commands.cc Normal file
View File

@ -0,0 +1,14 @@
#include "caching/commands.h"
using namespace base;
using namespace caching;
//----------------------------------------------------------------
void
caching::register_cache_commands(application &app)
{
app.add_cmd(command::ptr(new cache_debug_cmd));
}
//----------------------------------------------------------------

View File

@ -56,9 +56,6 @@ namespace {
default:
throw runtime_error("invalid hint width");
}
// never get here
return std::shared_ptr<array_base>();
}
//--------------------------------
@ -93,9 +90,6 @@ namespace {
default:
throw runtime_error("invalid hint width");
}
// never get here
return std::shared_ptr<array_base>();
}
//--------------------------------

View File

@ -45,9 +45,9 @@ metadata::metadata(block_manager::ptr bm, open_type ot, unsigned metadata_versio
}
}
metadata::metadata(block_manager::ptr bm)
metadata::metadata(block_manager::ptr bm, bool read_space_map)
{
open_metadata(bm);
open_metadata(bm, read_space_map);
}
void
@ -90,11 +90,16 @@ metadata::create_metadata(block_manager::ptr bm, unsigned metadata_version)
}
void
metadata::open_metadata(block_manager::ptr bm)
metadata::open_metadata(block_manager::ptr bm, bool read_space_map)
{
tm_ = open_tm(bm);
sb_ = read_superblock(tm_->get_bm());
if (read_space_map) {
metadata_sm_ = open_metadata_sm(*tm_, &sb_.metadata_space_map_root);
tm_->set_sm(metadata_sm_);
}
mappings_ = mapping_array::ptr(
new mapping_array(*tm_,
mapping_array::ref_counter(),

View File

@ -27,7 +27,7 @@ namespace caching {
typedef std::shared_ptr<metadata> ptr;
metadata(block_manager::ptr bm, open_type ot, unsigned metadata_version = 2); // Create only
metadata(block_manager::ptr bm);
metadata(block_manager::ptr bm, bool read_space_map = false);
void commit(bool clean_shutdown = true);
void setup_hint_array(size_t width);
@ -46,7 +46,7 @@ namespace caching {
void init_superblock();
void create_metadata(block_manager::ptr bm, unsigned metadata_version);
void open_metadata(block_manager::ptr bm);
void open_metadata(block_manager::ptr bm, bool read_space_map);
void commit_space_map();
void commit_mappings();

View File

@ -150,14 +150,6 @@ AC_ARG_ENABLE(testing,
TESTING=$enableval, TESTING=no)
AC_MSG_RESULT($TESTING)
################################################################################
dnl -- Enable development tools
AC_MSG_CHECKING(whether to enable development tools)
AC_ARG_ENABLE(dev-tools,
AC_HELP_STRING(--enable-dev-tools, [enable development tools in the makefile]),
DEVTOOLS=$enableval, DEVTOOLS=no)
AC_MSG_RESULT($DEVTOOLS)
################################################################################
dnl -- Enable static libstdc++
AC_MSG_CHECKING(whether to statically link libstdc++)
@ -214,6 +206,5 @@ Makefile
contrib/Makefile
unit-tests/Makefile
version.h
src/version.rs
])
AC_OUTPUT

View File

@ -18,7 +18,7 @@ contrib/%.a: contrib/%.o
$(V)echo " [AR] $@"
$(V)$(AR) rcs $@ $^
contrib/%.so: contrib/%.a
contrib/%.so: contrib/%.o
$(V)echo " [LD] $@"
$(V)$(CC) -shared -Wl,-soname,$@ -o $@ $<

View File

@ -0,0 +1,53 @@
#include "dbg-lib/block_dumper.h"
#include "dbg-lib/output_formatter.h"
#include "persistent-data/data-structures/array_block.h"
//----------------------------------------------------------------
namespace dbg {
using persistent_data::block_manager;
using persistent_data::array_block;
template <typename ShowTraits>
class array_block_dumper : public block_dumper {
public:
array_block_dumper(typename ShowTraits::value_trait::ref_counter rc)
: rc_(rc) {
}
virtual void show(block_manager::read_ref &rr, std::ostream &out) {
rblock b(rr, rc_);
show_array_entries(b, out);
}
private:
typedef array_block<typename ShowTraits::value_trait, block_manager::read_ref> rblock;
void show_array_entries(rblock const& b, std::ostream &out) {
formatter::ptr f = create_xml_formatter();
uint32_t nr_entries = b.nr_entries();
field(*f, "max_entries", b.max_entries());
field(*f, "nr_entries", nr_entries);
field(*f, "value_size", b.value_size());
for (unsigned i = 0; i < nr_entries; i++) {
formatter::ptr f2 = create_xml_formatter();
ShowTraits::show(f2, "value", b.get(i));
f->child(boost::lexical_cast<std::string>(i), f2);
}
f->output(out, 0);
}
typename ShowTraits::value_trait::ref_counter rc_;
};
template <typename ShowTraits>
block_dumper::ptr
create_array_block_dumper(typename ShowTraits::value_trait::ref_counter rc) {
return block_dumper::ptr(new array_block_dumper<ShowTraits>(rc));
}
}
//----------------------------------------------------------------

View File

@ -0,0 +1,137 @@
#include "dbg-lib/bitset_block_dumper.h"
#include "dbg-lib/output_formatter.h"
#include "persistent-data/data-structures/array_block.h"
#include "persistent-data/data-structures/simple_traits.h"
using namespace dbg;
using namespace persistent_data;
//----------------------------------------------------------------
namespace {
class bitset_block_dumper : public dbg::block_dumper {
typedef array_block<uint64_traits, block_manager::read_ref> rblock;
public:
explicit bitset_block_dumper()
: BITS_PER_ARRAY_ENTRY(64) {
}
virtual void show(block_manager::read_ref &rr, ostream &out) {
rblock b(rr, rc_);
show_bitset_entries(b, out);
}
private:
void show_bitset_entries(rblock const& b, ostream &out) {
formatter::ptr f = create_xml_formatter();
uint32_t nr_entries = b.nr_entries();
field(*f, "max_entries", b.max_entries());
field(*f, "nr_entries", nr_entries);
field(*f, "value_size", b.value_size());
uint32_t end_pos = b.nr_entries() * BITS_PER_ARRAY_ENTRY;
std::pair<uint32_t, uint32_t> range = next_set_bits(b, 0);
for (; range.first < end_pos; range = next_set_bits(b, range.second)) {
formatter::ptr f2 = create_xml_formatter();
field(*f2, "begin", range.first);
field(*f2, "end", range.second);
f->child("set_bits", f2);
}
f->output(out, 0);
}
// Returns the range of set bits, starts from the offset.
pair<uint32_t, uint32_t> next_set_bits(rblock const &b, uint32_t offset) {
uint32_t end_pos = b.nr_entries() * BITS_PER_ARRAY_ENTRY;
uint32_t begin = find_first_set(b, offset);
if (begin == end_pos) // not found
return make_pair(end_pos, end_pos);
uint32_t end = find_first_unset(b, begin + 1);
return make_pair(begin, end);
}
// Returns the position (zero-based) of the first bit set
// in the array block, starts from the offset.
// Returns the pass-the-end position if not found.
uint32_t find_first_set(rblock const &b, uint32_t offset) {
uint32_t entry = offset / BITS_PER_ARRAY_ENTRY;
uint32_t nr_entries = b.nr_entries();
if (entry >= nr_entries)
return entry * BITS_PER_ARRAY_ENTRY;
uint32_t idx = offset % BITS_PER_ARRAY_ENTRY;
uint64_t v = b.get(entry++) >> idx;
while (!v && entry < nr_entries) {
v = b.get(entry++);
idx = 0;
}
if (!v) // not found
return entry * BITS_PER_ARRAY_ENTRY;
return (entry - 1) * BITS_PER_ARRAY_ENTRY + idx + ffsll(static_cast<long long>(v)) - 1;
}
// Returns the position (zero-based) of the first zero bit
// in the array block, starts from the offset.
// Returns the pass-the-end position if not found.
// FIXME: improve efficiency
uint32_t find_first_unset(rblock const& b, uint32_t offset) {
uint32_t entry = offset / BITS_PER_ARRAY_ENTRY;
uint32_t nr_entries = b.nr_entries();
if (entry >= nr_entries)
return entry * BITS_PER_ARRAY_ENTRY;
uint32_t idx = offset % BITS_PER_ARRAY_ENTRY;
uint64_t v = b.get(entry++);
while (all_bits_set(v, idx) && entry < nr_entries) {
v = b.get(entry++);
idx = 0;
}
if (all_bits_set(v, idx)) // not found
return entry * BITS_PER_ARRAY_ENTRY;
return (entry - 1) * BITS_PER_ARRAY_ENTRY + idx + count_leading_bits(v, idx);
}
// Returns true if all the bits beyond the position are set.
bool all_bits_set(uint64_t v, uint32_t offset) {
return (v >> offset) == (numeric_limits<uint64_t>::max() >> offset);
}
// Counts the number of leading 1's in the given value, starts from the offset
// FIXME: improve efficiency
uint32_t count_leading_bits(uint64_t v, uint32_t offset) {
uint32_t count = 0;
v >>= offset;
while (v & 0x1) {
v >>= 1;
count++;
}
return count;
}
block_manager::ptr bm_;
uint64_traits::ref_counter rc_;
const uint32_t BITS_PER_ARRAY_ENTRY;
};
}
//----------------------------------------------------------------
block_dumper::ptr
dbg::create_bitset_block_dumper() {
return block_dumper::ptr(new bitset_block_dumper());
}
//----------------------------------------------------------------

View File

@ -0,0 +1,14 @@
#ifndef DBG_BITSET_BLOCK_DUMPER
#define DBG_BITSET_BLOCK_DUMPER
#include "dbg-lib/block_dumper.h"
//----------------------------------------------------------------
namespace dbg {
block_dumper::ptr create_bitset_block_dumper();
}
//----------------------------------------------------------------
#endif

21
dbg-lib/block_dumper.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef DBG_BLOCK_DUMPER_H
#define DBG_BLOCK_DUMPER_H
#include "persistent-data/block.h"
//----------------------------------------------------------------
namespace dbg {
class block_dumper {
public:
typedef std::shared_ptr<block_dumper> ptr;
// pass the read_ref by reference since the caller already held the ref-count
virtual void show(persistent_data::block_manager::read_ref &rr,
std::ostream &out) = 0;
};
}
//----------------------------------------------------------------
#endif

View File

@ -0,0 +1,57 @@
#ifndef DBG_BTREE_NODE_DUMPER
#define DBG_BTREE_NODE_DUMPER
#include "dbg-lib/block_dumper.h"
#include "dbg-lib/simple_show_traits.h"
#include "persistent-data/data-structures/btree.h"
//----------------------------------------------------------------
namespace dbg {
using persistent_data::block_manager;
using persistent_data::btree_detail::node_ref;
template <typename ShowTraits>
class btree_node_dumper : public block_dumper {
public:
virtual void show(block_manager::read_ref &rr, std::ostream &out) {
node_ref<uint64_traits> n = btree_detail::to_node<uint64_traits>(rr);
if (n.get_type() == INTERNAL)
btree_node_dumper<uint64_show_traits>::show_node(n, out);
else {
node_ref<typename ShowTraits::value_trait> n = btree_detail::to_node<typename ShowTraits::value_trait>(rr);
show_node(n, out);
}
}
static void show_node(node_ref<typename ShowTraits::value_trait> n, std::ostream &out) {
formatter::ptr f = create_xml_formatter();
field(*f, "csum", n.get_checksum());
field(*f, "blocknr", n.get_block_nr());
field(*f, "type", n.get_type() == INTERNAL ? "internal" : "leaf");
field(*f, "nr_entries", n.get_nr_entries());
field(*f, "max_entries", n.get_max_entries());
field(*f, "value_size", n.get_value_size());
for (unsigned i = 0; i < n.get_nr_entries(); i++) {
formatter::ptr f2 = create_xml_formatter();
field(*f2, "key", n.key_at(i));
ShowTraits::show(f2, "value", n.value_at(i));
f->child(boost::lexical_cast<std::string>(i), f2);
}
f->output(out, 0);
}
};
template <typename ShowTraits>
block_dumper::ptr
create_btree_node_dumper() {
return block_dumper::ptr(new btree_node_dumper<ShowTraits>());
}
}
//----------------------------------------------------------------
#endif

View File

@ -0,0 +1,88 @@
#include "dbg-lib/command_interpreter.h"
using namespace dbg;
using namespace std;
//----------------------------------------------------------------
namespace {
class command_interpreter_impl : public command_interpreter {
public:
typedef std::shared_ptr<command_interpreter> ptr;
command_interpreter_impl(std::istream &in, std::ostream &out)
: in_(in),
out_(out),
exit_(false) {
}
void register_command(std::string const &str, command::ptr cmd) {
commands_.insert(make_pair(str, cmd));
}
void enter_main_loop() {
while (!exit_)
do_once();
}
void exit_main_loop() {
exit_ = true;
}
private:
void do_once();
std::istream &in_;
std::ostream &out_;
std::map <std::string, command::ptr> commands_;
bool exit_;
};
//--------------------------------
strings read_input(std::istream &in)
{
using namespace boost::algorithm;
std::string input;
getline(in, input);
strings toks;
split(toks, input, is_any_of(" \t"), token_compress_on);
return toks;
}
}
//----------------------------------------------------------------
void command_interpreter_impl::do_once()
{
if (in_.eof())
throw runtime_error("input closed");
out_ << "> ";
strings args = read_input(in_);
std::map<std::string, command::ptr>::iterator it;
it = commands_.find(args[0]);
if (it == commands_.end())
out_ << "Unrecognised command" << endl;
else {
try {
it->second->exec(args, out_);
} catch (std::exception &e) {
cerr << e.what() << endl;
}
}
}
//----------------------------------------------------------------
command_interpreter::ptr
dbg::create_command_interpreter(std::istream &in, std::ostream &out)
{
return command_interpreter::ptr(new command_interpreter_impl(in, out));
}
//----------------------------------------------------------------

View File

@ -0,0 +1,40 @@
#ifndef DBG_COMMAND_INTERPRETER
#define DBG_COMMAND_INTERPRETER
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <iostream>
#include <map>
#include <string>
#include <vector>
//----------------------------------------------------------------
namespace dbg {
typedef std::vector<std::string> strings;
class command {
public:
typedef std::shared_ptr<command> ptr;
virtual ~command() {}
virtual void exec(strings const &args, std::ostream &out) = 0;
};
class command_interpreter {
public:
typedef std::shared_ptr<command_interpreter> ptr;
virtual void register_command(std::string const &str, command::ptr cmd) = 0;
virtual void enter_main_loop() = 0;
virtual void exit_main_loop() = 0;
};
command_interpreter::ptr
create_command_interpreter(std::istream &in, std::ostream &out);
}
//----------------------------------------------------------------
#endif

79
dbg-lib/commands.cc Normal file
View File

@ -0,0 +1,79 @@
#include "dbg-lib/commands.h"
#include "persistent-data/block.h"
#include <boost/lexical_cast.hpp>
#include <stdexcept>
using namespace dbg;
using namespace persistent_data;
using namespace std;
//----------------------------------------------------------------
namespace {
class hello : public dbg::command {
virtual void exec(dbg::strings const &args, ostream &out) {
out << "Hello, world!" << endl;
}
};
class exit_handler : public dbg::command {
public:
exit_handler(command_interpreter::ptr interpreter)
: interpreter_(interpreter) {
}
virtual void exec(dbg::strings const &args, ostream &out) {
out << "Goodbye!" << endl;
interpreter_->exit_main_loop();
}
private:
command_interpreter::ptr interpreter_;
};
class block_handler : public dbg::command {
public:
block_handler(block_manager::ptr bm, block_dumper::ptr dumper)
: bm_(bm), dumper_(dumper) {
}
virtual void exec(dbg::strings const &args, ostream &out) {
if (args.size() != 2)
throw runtime_error("incorrect number of arguments");
block_address block = boost::lexical_cast<block_address>(args[1]);
// no checksum validation for debugging purpose
block_manager::read_ref rr = bm_->read_lock(block);
dumper_->show(rr, out);
}
private:
block_manager::ptr bm_;
block_dumper::ptr dumper_;
};
}
//----------------------------------------------------------------
command::ptr
dbg::create_hello_handler()
{
return command::ptr(new hello());
}
command::ptr
dbg::create_exit_handler(command_interpreter::ptr interp)
{
return command::ptr(new exit_handler(interp));
}
command::ptr
dbg::create_block_handler(block_manager::ptr bm, block_dumper::ptr dumper)
{
return command::ptr(new block_handler(bm, dumper));
}
//----------------------------------------------------------------

24
dbg-lib/commands.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef DBG_COMMANDS_H
#define DBG_COMMANDS_H
#include "dbg-lib/command_interpreter.h"
#include "dbg-lib/block_dumper.h"
#include "persistent-data/block.h"
//----------------------------------------------------------------
namespace dbg {
dbg::command::ptr
create_hello_handler();
dbg::command::ptr
create_exit_handler(dbg::command_interpreter::ptr interp);
dbg::command::ptr
create_block_handler(persistent_data::block_manager::ptr bm,
dbg::block_dumper::ptr dumper);
}
//----------------------------------------------------------------
#endif

View File

@ -0,0 +1,51 @@
#include "dbg-lib/index_block_dumper.h"
#include "dbg-lib/output_formatter.h"
#include "dbg-lib/sm_show_traits.h"
#include "persistent-data/space-maps/disk_structures.h"
using namespace dbg;
//----------------------------------------------------------------
namespace {
using persistent_data::block_manager;
class index_block_dumper : public dbg::block_dumper {
public:
virtual void show(block_manager::read_ref &rr, std::ostream &out) {
sm_disk_detail::metadata_index const *mdi =
reinterpret_cast<sm_disk_detail::metadata_index const *>(rr.data());
show_metadata_index(mdi, sm_disk_detail::MAX_METADATA_BITMAPS, out);
}
private:
void show_metadata_index(sm_disk_detail::metadata_index const *mdi, unsigned nr_indexes, std::ostream &out) {
formatter::ptr f = create_xml_formatter();
field(*f, "csum", to_cpu<uint32_t>(mdi->csum_));
field(*f, "padding", to_cpu<uint32_t>(mdi->padding_));
field(*f, "blocknr", to_cpu<uint64_t>(mdi->blocknr_));
sm_disk_detail::index_entry ie;
for (unsigned i = 0; i < nr_indexes; i++) {
sm_disk_detail::index_entry_traits::unpack(*(mdi->index + i), ie);
if (!ie.blocknr_ && !ie.nr_free_ && !ie.none_free_before_)
continue;
formatter::ptr f2 = create_xml_formatter();
index_entry_show_traits::show(f2, "value", ie);
f->child(boost::lexical_cast<string>(i), f2);
}
f->output(out, 0);
}
};
}
//----------------------------------------------------------------
block_dumper::ptr
dbg::create_index_block_dumper() {
return block_dumper::ptr(new index_block_dumper());
}
//----------------------------------------------------------------

View File

@ -0,0 +1,10 @@
#include "dbg-lib/block_dumper.h"
//----------------------------------------------------------------
namespace dbg {
block_dumper::ptr
create_index_block_dumper();
}
//----------------------------------------------------------------

View File

@ -0,0 +1,86 @@
#include <string>
#include "dbg-lib/output_formatter.h"
using namespace dbg;
using namespace std;
//----------------------------------------------------------------
namespace {
class abstract_formatter : public formatter {
typedef boost::optional<std::string> maybe_string;
void field(std::string const &name, std::string const &value) {
fields_.push_back(field_type(name, value));
}
void child(std::string const &name, formatter::ptr t) {
children_.push_back(field_type(name, t));
}
protected:
typedef boost::variant<std::string, ptr> value;
typedef boost::tuple<std::string, value> field_type;
std::vector<field_type> fields_;
std::vector<field_type> children_;
};
class xml_formatter : public abstract_formatter {
public:
void output(std::ostream &out, int depth = 0,
boost::optional<std::string> name = boost::none);
};
void indent(int depth, std::ostream &out) {
for (int i = 0; i < depth * 2; i++)
out << ' ';
}
}
//----------------------------------------------------------------
void xml_formatter::output(std::ostream &out,
int depth,
boost::optional<std::string> name) {
indent(depth, out);
out << "<fields";
if (name && (*name).length())
out << " id=\"" << *name << "\"";
/* output non-child fields */
std::vector<field_type>::const_iterator it;
for (it = fields_.begin(); it != fields_.end(); ++it) {
if (string const *s = boost::get<string>(&it->get<1>())) {
out << " " << it->get<0>() << "=\"" << *s << "\"";
}
}
if (children_.size() == 0) {
out << " />" << endl;
return;
}
/* output child fields */
out << ">" << endl;
for (it = children_.begin(); it != children_.end(); ++it) {
if (!boost::get<string>(&it->get<1>())) {
formatter::ptr f = boost::get<formatter::ptr>(it->get<1>());
f->output(out, depth + 1, it->get<0>());
}
}
indent(depth, out);
out << "</fields>" << endl;
}
//----------------------------------------------------------------
formatter::ptr
dbg::create_xml_formatter()
{
return formatter::ptr(new xml_formatter());
}
//----------------------------------------------------------------

View File

@ -0,0 +1,38 @@
#ifndef DBG_OUTPUT_FORMATTER_H
#define DBG_OUTPUT_FORMATTER_H
#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/variant.hpp>
#include <memory>
#include <vector>
#include <string>
//----------------------------------------------------------------
namespace dbg {
class formatter {
public:
typedef std::shared_ptr<formatter> ptr;
virtual ~formatter() {}
virtual void field(std::string const &name, std::string const &value) = 0;
virtual void child(std::string const &name, formatter::ptr t) = 0;
virtual void output(std::ostream &out, int depth = 0,
boost::optional<std::string> name = boost::none) = 0;
};
template <typename T>
void
field(formatter &t, std::string const &name, T const &value) {
t.field(name, boost::lexical_cast<std::string>(value));
}
formatter::ptr create_xml_formatter();
}
//----------------------------------------------------------------
#endif

View File

@ -0,0 +1,19 @@
#include "dbg-lib/simple_show_traits.h"
using namespace dbg;
//----------------------------------------------------------------
void
uint32_show_traits::show(formatter::ptr f, string const &key, uint32_t const &value)
{
field(*f, key, boost::lexical_cast<string>(value));
}
void
uint64_show_traits::show(formatter::ptr f, string const &key, uint64_t const &value)
{
field(*f, key, boost::lexical_cast<string>(value));
}
//----------------------------------------------------------------

View File

@ -0,0 +1,27 @@
#ifndef DBG_SIMPLE_SHOW_TRAITS_H
#define DBG_SIMPLE_SHOW_TRAITS_H
#include "dbg-lib/output_formatter.h"
#include "persistent-data/data-structures/simple_traits.h"
//----------------------------------------------------------------
namespace dbg {
class uint32_show_traits {
public:
typedef persistent_data::uint32_traits value_trait;
static void show(dbg::formatter::ptr f, std::string const &key, uint32_t const &value);
};
class uint64_show_traits {
public:
typedef persistent_data::uint64_traits value_trait;
static void show(dbg::formatter::ptr f, std::string const &key, uint64_t const &value);
};
}
//----------------------------------------------------------------
#endif

27
dbg-lib/sm_show_traits.cc Normal file
View File

@ -0,0 +1,27 @@
#include "dbg-lib/sm_show_traits.h"
using namespace dbg;
using namespace std;
//----------------------------------------------------------------
void
index_entry_show_traits::show(formatter::ptr f, string const &key,
persistent_data::sm_disk_detail::index_entry const &value)
{
field(*f, "blocknr", value.blocknr_);
field(*f, "nr_free", value.nr_free_);
field(*f, "none_free_before", value.none_free_before_);
}
void
sm_root_show_traits::show(formatter::ptr f, string const &key,
persistent_data::sm_disk_detail::sm_root const &value)
{
field(*f, "nr_blocks", value.nr_blocks_);
field(*f, "nr_allocated", value.nr_allocated_);
field(*f, "bitmap_root", value.bitmap_root_);
field(*f, "ref_count_root", value.ref_count_root_);
}
//----------------------------------------------------------------

29
dbg-lib/sm_show_traits.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef DBG_SM_SHOW_TRAITS_H
#define DBG_SM_SHOW_TRAITS_H
#include "dbg-lib/output_formatter.h"
#include "persistent-data/space-maps/disk_structures.h"
//----------------------------------------------------------------
namespace dbg {
class index_entry_show_traits {
public:
typedef persistent_data::sm_disk_detail::index_entry_traits value_trait;
static void show(dbg::formatter::ptr f, std::string const &key,
persistent_data::sm_disk_detail::index_entry const &value);
};
class sm_root_show_traits {
public:
typedef persistent_data::sm_disk_detail::sm_root_traits value_trait;
static void show(dbg::formatter::ptr f, std::string const &key,
persistent_data::sm_disk_detail::sm_root const &value);
};
}
//----------------------------------------------------------------
#endif

View File

@ -34,6 +34,18 @@ namespace era {
virtual int run(int argc, char **argv);
};
//------------------------------------------------------
class era_debug_cmd : public base::command {
public:
era_debug_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
//------------------------------------------------------
void register_era_commands(base::application &app);
}

14
era/devel_commands.cc Normal file
View File

@ -0,0 +1,14 @@
#include "era/commands.h"
using namespace base;
using namespace era;
//----------------------------------------------------------------
void
era::register_era_commands(base::application &app)
{
app.add_cmd(command::ptr(new era_debug_cmd));
}
//----------------------------------------------------------------

228
era/era_debug.cc Normal file
View File

@ -0,0 +1,228 @@
// Copyright (C) 2012 Red Hat, Inc. All rights reserved.
//
// This file is part of the thin-provisioning-tools source.
//
// thin-provisioning-tools is free software: you can redistribute it
// and/or modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// thin-provisioning-tools is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with thin-provisioning-tools. If not, see
// <http://www.gnu.org/licenses/>.
#include <boost/lexical_cast.hpp>
#include <getopt.h>
#include <iostream>
#include <string>
#include "dbg-lib/array_block_dumper.h"
#include "dbg-lib/btree_node_dumper.h"
#include "dbg-lib/bitset_block_dumper.h"
#include "dbg-lib/command_interpreter.h"
#include "dbg-lib/commands.h"
#include "dbg-lib/index_block_dumper.h"
#include "dbg-lib/output_formatter.h"
#include "dbg-lib/sm_show_traits.h"
#include "persistent-data/file_utils.h"
#include "persistent-data/space-maps/disk_structures.h"
#include "era/commands.h"
#include "era/metadata.h"
#include "version.h"
using namespace dbg;
using namespace persistent_data;
using namespace std;
using namespace era;
//----------------------------------------------------------------
namespace {
class help : public dbg::command {
virtual void exec(strings const &args, ostream &out) {
out << "Commands:" << endl
<< " superblock [block#]" << endl
<< " block_node <block# of array block-tree node>" << endl
<< " bitset_block <block# of bitset block>" << endl
<< " era_block <block# of era array block>" << endl
<< " index_block <block# of metadata space map root>" << endl
<< " writeset_node <block# of writeset tree node>" << endl
<< " exit" << endl;
}
};
// for displaying the writeset tree
class writeset_show_traits : public era::era_detail_traits {
public:
typedef era_detail_traits value_trait;
static void show(formatter::ptr f, string const &key, era_detail const &value) {
field(*f, "nr_bits", value.nr_bits);
field(*f, "writeset_root", value.writeset_root);
}
};
class show_superblock : public dbg::command {
public:
explicit show_superblock(block_manager::ptr bm)
: bm_(bm) {
}
virtual void exec(strings const &args, ostream &out) {
if (args.size() > 2)
throw runtime_error("incorrect number of arguments");
block_address b = era::SUPERBLOCK_LOCATION;
if (args.size() == 2)
b = boost::lexical_cast<block_address>(args[1]);
era::superblock sb = read_superblock(bm_, b);
formatter::ptr f = create_xml_formatter();
ostringstream version;
field(*f, "csum", sb.csum);
field(*f, "flags", sb.flags.encode());
field(*f, "blocknr", sb.blocknr);
field(*f, "uuid", sb.uuid); // FIXME: delimit, and handle non-printable chars
field(*f, "magic", sb.magic);
field(*f, "version", sb.version);
sm_disk_detail::sm_root_disk const *d;
sm_disk_detail::sm_root v;
{
d = reinterpret_cast<sm_disk_detail::sm_root_disk const *>(sb.metadata_space_map_root);
sm_disk_detail::sm_root_traits::unpack(*d, v);
formatter::ptr f2 = create_xml_formatter();
sm_root_show_traits::show(f2, "value", v);
f->child("metadata_space_map_root", f2);
}
field(*f, "data_block_size", sb.data_block_size);
field(*f, "metadata_block_size", sb.metadata_block_size);
field(*f, "nr_blocks", sb.nr_blocks);
field(*f, "current_era", sb.current_era);
{
formatter::ptr f2 = create_xml_formatter();
writeset_show_traits::show(f2, "value", sb.current_detail);
f->child("current_writeset", f2);
}
field(*f, "writeset_tree_root", sb.writeset_tree_root);
field(*f, "era_array_root", sb.era_array_root);
if (sb.metadata_snap)
field(*f, "metadata_snap", *sb.metadata_snap);
f->output(out, 0);
}
private:
block_manager::ptr bm_;
};
//--------------------------------
template <typename ShowTraits>
dbg::command::ptr
create_btree_node_handler(block_manager::ptr bm) {
return create_block_handler(bm, create_btree_node_dumper<ShowTraits>());
}
template <typename ShowTraits>
dbg::command::ptr
create_array_block_handler(block_manager::ptr bm,
typename ShowTraits::value_trait::ref_counter rc) {
return create_block_handler(bm, create_array_block_dumper<ShowTraits>(rc));
}
dbg::command::ptr
create_bitset_block_handler(block_manager::ptr bm) {
return create_block_handler(bm, create_bitset_block_dumper());
}
dbg::command::ptr
create_index_block_handler(block_manager::ptr bm) {
return create_block_handler(bm, create_index_block_dumper());
}
int debug(string const &path) {
using dbg::command;
try {
block_manager::ptr bm = open_bm(path, block_manager::READ_ONLY);
transaction_manager::ptr null_tm = open_tm(bm, era::SUPERBLOCK_LOCATION);
command_interpreter::ptr interp = create_command_interpreter(cin, cout);
interp->register_command("hello", create_hello_handler());
interp->register_command("superblock", command::ptr(new show_superblock(bm)));
interp->register_command("block_node", create_btree_node_handler<uint64_show_traits>(bm));
interp->register_command("bitset_block", create_bitset_block_handler(bm));
interp->register_command("era_block", create_array_block_handler<uint32_show_traits>(bm,
uint32_traits::ref_counter()));
interp->register_command("index_block", create_index_block_handler(bm));
interp->register_command("writeset_node", create_btree_node_handler<writeset_show_traits>(bm));
interp->register_command("help", command::ptr(new help));
interp->register_command("exit", create_exit_handler(interp));
interp->enter_main_loop();
} catch (std::exception &e) {
cerr << e.what();
return 1;
}
return 0;
}
}
//----------------------------------------------------------------
era_debug_cmd::era_debug_cmd()
: command("era_debug")
{
}
void
era_debug_cmd::usage(std::ostream &out) const
{
out << "Usage: " << get_name() << " {device|file}" << endl
<< "Options:" << endl
<< " {-h|--help}" << endl
<< " {-V|--version}" << endl;
}
int
era_debug_cmd::run(int argc, char **argv)
{
int c;
const char shortopts[] = "hV";
const struct option longopts[] = {
{ "help", no_argument, NULL, 'h'},
{ "version", no_argument, NULL, 'V'},
{ NULL, no_argument, NULL, 0 }
};
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
switch(c) {
case 'h':
usage(cout);
return 0;
case 'V':
cerr << THIN_PROVISIONING_TOOLS_VERSION << endl;
return 0;
}
}
if (argc == optind) {
usage(cerr);
exit(1);
}
return debug(argv[optind]);
}
//----------------------------------------------------------------

View File

@ -22,6 +22,14 @@ namespace era {
uint64_t writeset_root;
};
inline bool operator==(era_detail const& lhs, era_detail const& rhs) {
return lhs.nr_bits == rhs.nr_bits && lhs.writeset_root == rhs.writeset_root;
}
inline bool operator!=(era_detail const& lhs, era_detail const& rhs) {
return !(lhs == rhs);
}
struct era_detail_ref_counter {
era_detail_ref_counter(persistent_data::transaction_manager::ptr tm)
: tm_(tm) {
@ -31,7 +39,7 @@ namespace era {
tm_->get_sm()->inc(d.writeset_root);
}
void dec(persistent_data::block_address b) {
void dec(era_detail const &d) {
// I don't think we ever do this in the tools
throw std::runtime_error("not implemented");
}

View File

@ -37,7 +37,7 @@ namespace {
bool metadata_touched = false;
try {
block_manager::ptr bm = open_bm(*fs.output, block_manager::READ_WRITE);
file_utils::check_file_exists(*fs.output);
file_utils::check_file_exists(*fs.input);
metadata_touched = true;
metadata::ptr md(new metadata(bm, metadata::CREATE));
emitter::ptr restorer = create_restore_emitter(*md);

View File

@ -44,7 +44,7 @@ metadata::metadata(block_manager::ptr bm, open_type ot)
metadata::metadata(block_manager::ptr bm, block_address metadata_snap)
{
open_metadata(bm);
open_metadata(bm, metadata_snap);
}
void

View File

@ -14,6 +14,7 @@ namespace {
: md_(md),
in_superblock_(false),
in_writeset_(false),
era_(0),
in_era_array_(false) {
}

View File

@ -49,335 +49,13 @@
;; to run.
(define (register-cache-tests) #t)
;;;-----------------------------------------------------------
;;; cache_check scenarios
;;;-----------------------------------------------------------
(define-scenario (cache-check v)
"cache_check -V"
(run-ok-rcv (stdout _) (cache-check "-V")
(assert-equal tools-version stdout)))
(define-scenario (cache-check version)
"cache_check --version"
(run-ok-rcv (stdout _) (cache-check "--version")
(assert-equal tools-version stdout)))
(define-scenario (cache-check h)
"cache_check -h"
(run-ok-rcv (stdout _) (cache-check "-h")
(assert-equal cache-check-help stdout)))
(define-scenario (cache-check help)
"cache_check --help"
(run-ok-rcv (stdout _) (cache-check "--help")
(assert-equal cache-check-help stdout)))
(define-scenario (cache-check must-specify-metadata)
"Metadata file must be specified"
(run-fail-rcv (_ stderr) (cache-check)
(assert-equal
(string-append "No input file provided.\n"
cache-check-help)
stderr)))
(define-scenario (cache-check no-such-metadata)
"Metadata file doesn't exist."
(let ((bad-path "/arbitrary/filename"))
(run-fail-rcv (_ stderr) (cache-check bad-path)
(assert-starts-with
(string-append bad-path ": No such file or directory")
stderr))))
(define-scenario (cache-check metadata-file-cannot-be-a-directory)
"Metadata file must not be a directory"
(let ((bad-path "/tmp"))
(run-fail-rcv (_ stderr) (cache-check bad-path)
(assert-starts-with
(string-append bad-path ": Not a block device or regular file")
stderr))))
(define-scenario (cache-check unreadable-metadata)
"Metadata file exists, but is unreadable."
(with-valid-metadata (md)
(run-ok "chmod" "-r" md)
(run-fail-rcv (_ stderr) (cache-check md)
(assert-starts-with "syscall 'open' failed: Permission denied" stderr))))
(define-scenario (cache-check fails-with-corrupt-metadata)
"Fail with corrupt superblock"
(with-corrupt-metadata (md)
(run-fail (cache-check md))))
(define-scenario (cache-check failing-q)
"Fail quietly with -q"
(with-corrupt-metadata (md)
(run-fail-rcv (stdout stderr) (cache-check "-q" md)
(assert-eof stdout)
(assert-eof stderr))))
(define-scenario (cache-check failing-quiet)
"Fail quietly with --quiet"
(with-corrupt-metadata (md)
(run-fail-rcv (stdout stderr) (cache-check "--quiet" md)
(assert-eof stdout)
(assert-eof stderr))))
(define-scenario (cache-check valid-metadata-passes)
"A valid metadata area passes"
(with-valid-metadata (md)
(run-ok (cache-check md))))
(define-scenario (cache-check bad-metadata-version)
"Invalid metadata version fails"
(with-cache-xml (xml)
(with-empty-metadata (md)
(cache-restore "-i" xml "-o" md "--debug-override-metadata-version" "12345")
(run-fail (cache-check md)))))
(define-scenario (cache-check tiny-metadata)
"Prints helpful message in case tiny metadata given"
(with-temp-file-sized ((md "cache.bin" 1024))
(run-fail-rcv (_ stderr) (cache-check md)
(assert-starts-with "Metadata device/file too small. Is this binary metadata?" stderr))))
(define-scenario (cache-check spot-accidental-xml-data)
"Prints helpful message if XML metadata given"
(with-cache-xml (xml)
(system (fmt #f "man bash >> " xml))
(run-fail-rcv (_ stderr) (cache-check xml)
(assert-matches ".*This looks like XML. cache_check only checks the binary metadata format." stderr))))
;;;-----------------------------------------------------------
;;; cache_restore scenarios
;;;-----------------------------------------------------------
(define-scenario (cache-restore v)
"print version (-V flag)"
(run-ok-rcv (stdout _) (cache-restore "-V")
(assert-equal tools-version stdout)))
(define-scenario (cache-restore version)
"print version (--version flags)"
(run-ok-rcv (stdout _) (cache-restore "--version")
(assert-equal tools-version stdout)))
(define-scenario (cache-restore h)
"cache_restore -h"
(run-ok-rcv (stdout _) (cache-restore "-h")
(assert-equal cache-restore-help stdout)))
(define-scenario (cache-restore help)
"cache_restore --help"
(run-ok-rcv (stdout _) (cache-restore "--help")
(assert-equal cache-restore-help stdout)))
(define-scenario (cache-restore no-input-file)
"forget to specify an input file"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (cache-restore "-o" md)
(assert-starts-with "No input file provided." stderr))))
(define-scenario (cache-restore missing-input-file)
"the input file can't be found"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (cache-restore "-i no-such-file -o" md)
(assert-superblock-all-zeroes md)
(assert-starts-with "Couldn't stat file" stderr))))
(define-scenario (cache-restore garbage-input-file)
"the input file is just zeroes"
(with-empty-metadata (md)
(with-temp-file-sized ((xml "cache.xml" 4096))
(run-fail-rcv (_ stderr) (cache-restore "-i" xml "-o" md)
(assert-superblock-all-zeroes md)))))
(define-scenario (cache-restore missing-output-file)
"the output file can't be found"
(with-cache-xml (xml)
(run-fail-rcv (_ stderr) (cache-restore "-i" xml)
(assert-starts-with "No output file provided." stderr))))
(define-scenario (cache-restore tiny-output-file)
"Fails if the output file is too small."
(with-temp-file-sized ((md "cache.bin" (* 1024 4)))
(with-cache-xml (xml)
(run-fail-rcv (_ stderr) (cache-restore "-i" xml "-o" md)
(assert-starts-with cache-restore-outfile-too-small-text stderr)))))
(define-scenario (cache-restore successfully-restores)
"Restore succeeds."
(with-empty-metadata (md)
(with-cache-xml (xml)
(run-ok (cache-restore "-i" xml "-o" md)))))
(define-scenario (cache-restore q)
"cache_restore accepts -q"
(with-empty-metadata (md)
(with-cache-xml (xml)
(run-ok-rcv (stdout stderr) (cache-restore "-i" xml "-o" md "-q")
(assert-eof stdout)
(assert-eof stderr)))))
(define-scenario (cache-restore quiet)
"cache_restore accepts --quiet"
(with-empty-metadata (md)
(with-cache-xml (xml)
(run-ok-rcv (stdout stderr) (cache-restore "-i" xml "-o" md "--quiet")
(assert-eof stdout)
(assert-eof stderr)))))
(define-scenario (cache-restore override-metadata-version)
"we can set any metadata version"
(with-empty-metadata (md)
(with-cache-xml (xml)
(run-ok
(cache-restore "-i" xml "-o" md "--debug-override-metadata-version 10298")))))
(define-scenario (cache-restore omit-clean-shutdown)
"accepts --omit-clean-shutdown"
(with-empty-metadata (md)
(with-cache-xml (xml)
(run-ok
(cache-restore "-i" xml "-o" md "--omit-clean-shutdown")))))
;;;-----------------------------------------------------------
;;; cache_dump scenarios
;;;-----------------------------------------------------------
(define-scenario (cache-dump v)
"print version (-V flag)"
(run-ok-rcv (stdout _) (cache-dump "-V")
(assert-equal tools-version stdout)))
(define-scenario (cache-dump version)
"print version (--version flags)"
(run-ok-rcv (stdout _) (cache-dump "--version")
(assert-equal tools-version stdout)))
(define-scenario (cache-dump h)
"cache_dump -h"
(run-ok-rcv (stdout _) (cache-dump "-h")
(assert-equal cache-dump-help stdout)))
(define-scenario (cache-dump help)
"cache_dump --help"
(run-ok-rcv (stdout _) (cache-dump "--help")
(assert-equal cache-dump-help stdout)))
(define-scenario (cache-dump missing-input-file)
"Fails with missing input file."
(run-fail-rcv (stdout stderr) (cache-dump)
(assert-starts-with "No input file provided." stderr)))
(define-scenario (cache-dump small-input-file)
"Fails with small input file"
(with-temp-file-sized ((md "cache.bin" 512))
(run-fail
(cache-dump md))))
(define-scenario (cache-dump restore-is-noop)
"cache_dump followed by cache_restore is a noop."
(with-valid-metadata (md)
(run-ok-rcv (d1-stdout _) (cache-dump md)
(with-temp-file-containing ((xml "cache.xml" d1-stdout))
(run-ok (cache-restore "-i" xml "-o" md))
(run-ok-rcv (d2-stdout _) (cache-dump md)
(assert-equal d1-stdout d2-stdout))))))
;;;-----------------------------------------------------------
;;; cache_metadata_size scenarios
;;;-----------------------------------------------------------
(define-scenario (cache-metadata-size v)
"cache_metadata_size -V"
(run-ok-rcv (stdout _) (cache-metadata-size "-V")
(assert-equal tools-version stdout)))
(define-scenario (cache-metadata-size version)
"cache_metadata_size --version"
(run-ok-rcv (stdout _) (cache-metadata-size "--version")
(assert-equal tools-version stdout)))
(define-scenario (cache-metadata-size h)
"cache_metadata_size -h"
(run-ok-rcv (stdout _) (cache-metadata-size "-h")
(assert-equal cache-metadata-size-help stdout)))
(define-scenario (cache-metadata-size help)
"cache_metadata_size --help"
(run-ok-rcv (stdout _) (cache-metadata-size "--help")
(assert-equal cache-metadata-size-help stdout)))
(define-scenario (cache-metadata-size no-args)
"No arguments specified causes fail"
(run-fail-rcv (_ stderr) (cache-metadata-size)
(assert-equal "Please specify either --device-size and --block-size, or --nr-blocks."
stderr)))
(define-scenario (cache-metadata-size device-size-only)
"Just --device-size causes fail"
(run-fail-rcv (_ stderr) (cache-metadata-size "--device-size" (to-bytes (meg 100)))
(assert-equal "If you specify --device-size you must also give --block-size."
stderr)))
(define-scenario (cache-metadata-size block-size-only)
"Just --block-size causes fail"
(run-fail-rcv (_ stderr) (cache-metadata-size "--block-size" 128)
(assert-equal "If you specify --block-size you must also give --device-size."
stderr)))
(define-scenario (cache-metadata-size conradictory-info-fails)
"Contradictory info causes fail"
(run-fail-rcv (_ stderr) (cache-metadata-size "--device-size 102400 --block-size 1000 --nr-blocks 6")
(assert-equal "Contradictory arguments given, --nr-blocks doesn't match the --device-size and --block-size."
stderr)))
(define-scenario (cache-metadata-size all-args-agree)
"All args agreeing succeeds"
(run-ok-rcv (stdout stderr) (cache-metadata-size "--device-size" 102400 "--block-size" 100 "--nr-blocks" 1024)
(assert-equal "8248 sectors" stdout)
(assert-eof stderr)))
(define-scenario (cache-metadata-size nr-blocks-alone)
"Just --nr-blocks succeeds"
(run-ok-rcv (stdout stderr) (cache-metadata-size "--nr-blocks" 1024)
(assert-equal "8248 sectors" stdout)
(assert-eof stderr)))
(define-scenario (cache-metadata-size dev-size-and-block-size-succeeds)
"Specifying --device-size with --block-size succeeds"
(run-ok-rcv (stdout stderr) (cache-metadata-size "--device-size" 102400 "--block-size" 100)
(assert-equal "8248 sectors" stdout)
(assert-eof stderr)))
(define-scenario (cache-metadata-size big-config)
"A big configuration succeeds"
(run-ok-rcv (stdout stderr) (cache-metadata-size "--nr-blocks 67108864")
(assert-equal "3678208 sectors" stdout)
(assert-eof stderr)))
;;;-----------------------------------------------------------
;;; cache_repair scenarios
;;;-----------------------------------------------------------
(define-scenario (cache-repair missing-input-file)
"the input file can't be found"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (cache-repair "-i no-such-file -o" md)
(assert-superblock-all-zeroes md)
(assert-starts-with "Couldn't stat path" stderr))))
(define-scenario (cache-repair garbage-input-file)
"the input file is just zeroes"
(with-empty-metadata (md1)
(with-corrupt-metadata (md2)
(run-fail-rcv (_ stderr) (cache-repair "-i" md1 "-o" md2)
(assert-superblock-all-zeroes md2)))))
(define-scenario (cache-repair missing-output-file)
"the output file can't be found"
(with-cache-xml (xml)
(run-fail-rcv (_ stderr) (cache-repair "-i" xml)
(assert-starts-with "No output file provided." stderr))))
)

View File

@ -43,153 +43,9 @@
(define (register-era-tests) #t)
;;;-----------------------------------------------------------
;;; era_check scenarios
;;;-----------------------------------------------------------
(define-scenario (era-check v)
"era_check -V"
(run-ok-rcv (stdout _) (era-check "-V")
(assert-equal tools-version stdout)))
(define-scenario (era-check version)
"era_check --version"
(run-ok-rcv (stdout _) (era-check "--version")
(assert-equal tools-version stdout)))
(define-scenario (era-check h)
"era_check -h"
(run-ok-rcv (stdout _) (era-check "-h")
(assert-equal era-check-help stdout)))
(define-scenario (era-check help)
"era_check --help"
(run-ok-rcv (stdout _) (era-check "--help")
(assert-equal era-check-help stdout)))
(define-scenario (era-check no-device-specified)
"Fail if no device specified"
(run-fail-rcv (_ stderr) (era-check)
(assert-starts-with "No input file provided." stderr)))
(define-scenario (era-check dev-not-exist)
"Fail if specified device doesn't exist"
(run-fail-rcv (_ stderr) (era-check "/dev/unlikely")
(assert-starts-with "/dev/unlikely: No such file or directory" stderr)))
(define-scenario (era-check dev-is-a-directory)
"Fail if given a directory instead of a file or device"
(run-fail-rcv (_ stderr) (era-check "/tmp")
(assert-starts-with "/tmp: Not a block device or regular file" stderr)))
(define-scenario (era-check bad-permissions)
"Fail if given a device with inadequate access permissions"
(with-temp-file-sized ((md "era.bin" (meg 4)))
(run-ok "chmod -r" md)
(run-fail-rcv (_ stderr) (era-check md)
(assert-starts-with "syscall 'open' failed: Permission denied" stderr))))
(define-scenario (era-check empty-dev)
"Fail if given a file of zeroes"
(with-empty-metadata (md)
(run-fail (era-check md))))
(define-scenario (era-check quiet)
"Fail should give no output if --quiet"
(with-empty-metadata (md)
(run-fail-rcv (stdout stderr) (era-check "--quiet" md)
(assert-eof stdout)
(assert-eof stderr))))
(define-scenario (era-check q)
"Fail should give no output if -q"
(with-empty-metadata (md)
(run-fail-rcv (stdout stderr) (era-check "-q" md)
(assert-eof stdout)
(assert-eof stderr))))
(define-scenario (era-check tiny-metadata)
"Prints helpful message in case tiny metadata given"
(with-temp-file-sized ((md "era.bin" 1024))
(run-fail-rcv (_ stderr) (era-check md)
(assert-starts-with "Metadata device/file too small. Is this binary metadata?" stderr))))
(define-scenario (era-check spot-accidental-xml-data)
"Prints helpful message if XML metadata given"
(with-era-xml (xml)
(system (fmt #f "man bash >> " xml))
(run-fail-rcv (_ stderr) (era-check xml)
(assert-matches ".*This looks like XML. era_check only checks the binary metadata format." stderr))))
;;;-----------------------------------------------------------
;;; era_restore scenarios
;;;-----------------------------------------------------------
(define-scenario (era-restore v)
"era_restore -V"
(run-ok-rcv (stdout _) (era-restore "-V")
(assert-equal tools-version stdout)))
(define-scenario (era-restore version)
"era_restore --version"
(run-ok-rcv (stdout _) (era-restore "--version")
(assert-equal tools-version stdout)))
(define-scenario (era-restore h)
"era_restore -h"
(run-ok-rcv (stdout _) (era-restore "-h")
(assert-equal era-restore-help stdout)))
(define-scenario (era-restore help)
"era_restore --help"
(run-ok-rcv (stdout _) (era-restore "--help")
(assert-equal era-restore-help stdout)))
(define-scenario (era-restore input-unspecified)
"Fails if no xml specified"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (era-restore "-o" md)
(assert-starts-with "No input file provided." stderr))))
(define-scenario (era-restore missing-input-file)
"the input file can't be found"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (era-restore "-i no-such-file -o" md)
(assert-superblock-all-zeroes md)
(assert-starts-with "Couldn't stat file" stderr))))
(define-scenario (era-restore garbage-input-file)
"the input file is just zeroes"
(with-empty-metadata (md)
(with-temp-file-sized ((xml "era.xml" 4096))
(run-fail-rcv (_ stderr) (era-restore "-i " xml "-o" md)
(assert-superblock-all-zeroes md)))))
(define-scenario (era-restore output-unspecified)
"Fails if no metadata dev specified"
(with-era-xml (xml)
(run-fail-rcv (_ stderr) (era-restore "-i" xml)
(assert-starts-with "No output file provided." stderr))))
(define-scenario (era-restore success)
"Succeeds with xml and metadata"
(with-era-xml (xml)
(with-empty-metadata (md)
(run-ok (era-restore "-i" xml "-o" md)))))
(define-scenario (era-restore quiet)
"No output with --quiet (succeeding)"
(with-era-xml (xml)
(with-empty-metadata (md)
(run-ok-rcv (stdout stderr) (era-restore "--quiet" "-i" xml "-o" md)
(assert-eof stdout)
(assert-eof stderr)))))
(define-scenario (era-restore q)
"No output with -q (succeeding)"
(with-era-xml (xml)
(with-empty-metadata (md)
(run-ok-rcv (stdout stderr) (era-restore "-q" "-i" xml "-o" md)
(assert-eof stdout)
(assert-eof stderr)))))
(define-scenario (era-restore quiet-fail)
"No output with --quiet (failing)"
@ -197,7 +53,9 @@
(with-empty-metadata (md)
(run-fail-rcv (stdout stderr) (era-restore "--quiet" "-i" bad-xml "-o" md)
(assert-eof stdout)
(assert-starts-with "Couldn't stat file" stderr)))))
(assert-starts-with
(string-append bad-xml ": No such file or directory")
stderr)))))
(define-scenario (era-restore q-fail)
"No output with --q(failing)"
@ -205,22 +63,7 @@
(with-empty-metadata (md)
(run-fail-rcv (stdout stderr) (era-restore "-q" "-i" bad-xml "-o" md)
(assert-eof stdout)
(assert-starts-with "Couldn't stat file" stderr)))))
;;;-----------------------------------------------------------
;;; era_dump scenarios
;;;-----------------------------------------------------------
(define-scenario (era-dump small-input-file)
"Fails with small input file"
(with-temp-file-sized ((md "era.bin" 512))
(run-fail (era-dump md))))
(define-scenario (era-dump restore-is-noop)
"era_dump followed by era_restore is a noop."
(with-valid-metadata (md)
(run-ok-rcv (d1-stdout _) (era-dump md)
(with-temp-file-containing ((xml "era.xml" d1-stdout))
(run-ok (era-restore "-i" xml "-o" md))
(run-ok-rcv (d2-stdout _) (era-dump md)
(assert-equal d1-stdout d2-stdout))))))
(assert-starts-with
(string-append bad-xml ": No such file or directory")
stderr)))))
)

View File

@ -1,10 +1,8 @@
(import (rnrs)
(test-runner)
(cache-functional-tests)
(era-functional-tests)
(thin-functional-tests))
(era-functional-tests))
(register-thin-tests)
(register-cache-tests)
(register-era-tests)

View File

@ -14,8 +14,7 @@
(only (srfi s1 lists) break)
(regex)
(srfi s8 receive)
(temp-file)
(thin-functional-tests))
(temp-file))
;;------------------------------------------------

View File

@ -1,635 +0,0 @@
(library
(thin-functional-tests)
(export register-thin-tests)
(import
(chezscheme)
(bcache block-manager)
(disk-units)
(fmt fmt)
(functional-tests)
(process)
(scenario-string-constants)
(temp-file)
(thin xml)
(srfi s8 receive))
(define-tool thin-check)
(define-tool thin-delta)
(define-tool thin-dump)
(define-tool thin-restore)
(define-tool thin-rmap)
(define-tool thin-repair)
(define-tool thin-metadata-pack)
(define-tool thin-metadata-unpack)
(define-syntax with-thin-xml
(syntax-rules ()
((_ (v) b1 b2 ...)
(with-temp-file-containing ((v "thin.xml" (fmt #f (generate-xml 10 1000))))
b1 b2 ...))))
(define-syntax with-valid-metadata
(syntax-rules ()
((_ (md) b1 b2 ...)
(with-temp-file-sized ((md "thin.bin" (meg 4)))
(with-thin-xml (xml)
(run-ok (thin-restore "-i" xml "-o" md))
b1 b2 ...)))))
;;; It would be nice if the metadata was at least similar to valid data.
;;; Here I'm just using the start of the ls binary as 'random' data.
(define-syntax with-corrupt-metadata
(syntax-rules ()
((_ (md) b1 b2 ...)
(with-temp-file-sized ((md "thin.bin" (meg 4)))
(system (fmt #f "dd if=/usr/bin/ls of=" md " bs=4096 > /dev/null 2>&1"))
b1 b2 ...))))
(define-syntax with-empty-metadata
(syntax-rules ()
((_ (md) b1 b2 ...)
(with-temp-file-sized ((md "thin.bin" (meg 4)))
b1 b2 ...))))
(define (damage-superblock md)
(system (string-append "dd if=/dev/zero of=" md " bs=4K count=1 conv=notrunc > /dev/null 2>&1")))
(define-syntax with-damaged-superblock
(syntax-rules ()
((_ (md) b1 b2 ...)
(with-valid-metadata (md)
(damage-superblock md)
b1 b2 ...))))
;; We have to export something that forces all the initialisation expressions
;; to run.
(define (register-thin-tests) #t)
;;;-----------------------------------------------------------
;;; thin_check scenarios
;;;-----------------------------------------------------------
(define-scenario (thin-check v)
"thin_check -V"
(run-ok-rcv (stdout _) (thin-check "-V")
(assert-equal tools-version stdout)))
(define-scenario (thin-check version)
"thin_check --version"
(run-ok-rcv (stdout _) (thin-check "--version")
(assert-equal tools-version stdout)))
(define-scenario (thin-check h)
"print help (-h)"
(run-ok-rcv (stdout _) (thin-check "-h")
(assert-equal thin-check-help stdout)))
(define-scenario (thin-check help)
"print help (--help)"
(run-ok-rcv (stdout _) (thin-check "--help")
(assert-equal thin-check-help stdout)))
(define-scenario (thin-check bad-option)
"Unrecognised option should cause failure"
(run-fail (thin-check "--hedgehogs-only")))
(define-scenario (thin-check incompatible-options auto-repair)
"Incompatible options should cause failure"
(with-valid-metadata (md)
(run-fail (thin-check "--auto-repair" "-m" md))
(run-fail (thin-check "--auto-repair" "--override-mapping-root 123" md))
(run-fail (thin-check "--auto-repair" "--super-block-only" md))
(run-fail (thin-check "--auto-repair" "--skip-mappings" md))
(run-fail (thin-check "--auto-repair" "--ignore-non-fatal-errors" md))))
(define-scenario (thin-check incompatible-options clear-needs-check-flag)
"Incompatible options should cause failure"
(with-valid-metadata (md)
(run-fail (thin-check "--clear-needs-check-flag" "-m" md))
(run-fail (thin-check "--clear-needs-check-flag" "--override-mapping-root 123" md))
(run-fail (thin-check "--clear-needs-check-flag" "--super-block-only" md))
(run-fail (thin-check "--clear-needs-check-flag" "--skip-mappings" md))
(run-fail (thin-check "--clear-needs-check-flag" "--ignore-non-fatal-errors" md))))
(define-scenario (thin-check superblock-only-valid)
"--super-block-only check passes on valid metadata"
(with-valid-metadata (md)
(run-ok (thin-check "--super-block-only" md))))
(define-scenario (thin-check superblock-only-invalid)
"--super-block-only check fails with corrupt metadata"
(with-corrupt-metadata (md)
(run-fail (thin-check "--super-block-only" md))))
(define-scenario (thin-check skip-mappings-valid)
"--skip-mappings check passes on valid metadata"
(with-valid-metadata (md)
(run-ok (thin-check "--skip-mappings" md))))
(define-scenario (thin-check ignore-non-fatal-errors)
"--ignore-non-fatal-errors check passes on valid metadata"
(with-valid-metadata (md)
(run-ok (thin-check "--ignore-non-fatal-errors" md))))
(define-scenario (thin-check quiet)
"--quiet should give no output"
(with-valid-metadata (md)
(run-ok-rcv (stdout stderr) (thin-check "--quiet" md)
(assert-eof stdout)
(assert-eof stderr))))
(define-scenario (thin-check clear-needs-check-flag)
"Accepts --clear-needs-check-flag"
(with-valid-metadata (md)
(run-ok (thin-check "--clear-needs-check-flag" md))))
(define-scenario (thin-check auto-repair)
"Accepts --auto-repair"
(with-valid-metadata (md)
(run-ok (thin-check "--auto-repair" md))))
(define-scenario (thin-check tiny-metadata)
"Prints helpful message in case tiny metadata given"
(with-temp-file-sized ((md "thin.bin" 1024))
(run-fail-rcv (_ stderr) (thin-check md)
(assert-starts-with "Metadata device/file too small. Is this binary metadata?" stderr))))
(define-scenario (thin-check spot-accidental-xml-data)
"Prints helpful message if XML metadata given"
(with-thin-xml (xml)
(system (fmt #f "man bash >> " xml))
(run-fail-rcv (_ stderr) (thin-check xml)
(assert-matches ".*This looks like XML. thin_check only checks the binary metadata format." stderr))))
(define-scenario (thin-check info-fields)
"Outputs info fields"
(with-valid-metadata (md)
(run-ok-rcv (stdout stderr) (thin-check md)
(assert-matches ".*TRANSACTION_ID=[0-9]+.*" stdout)
(assert-matches ".*METADATA_FREE_BLOCKS=[0-9]+.*" stdout))))
;;;-----------------------------------------------------------
;;; thin_restore scenarios
;;;-----------------------------------------------------------
(define-scenario (thin-restore print-version-v)
"print help (-V)"
(run-ok-rcv (stdout _) (thin-restore "-V")
(assert-equal tools-version stdout)))
(define-scenario (thin-restore print-version-long)
"print help (--version)"
(run-ok-rcv (stdout _) (thin-restore "--version")
(assert-equal tools-version stdout)))
(define-scenario (thin-restore h)
"print help (-h)"
(run-ok-rcv (stdout _) (thin-restore "-h")
(assert-equal thin-restore-help stdout)))
(define-scenario (thin-restore help)
"print help (-h)"
(run-ok-rcv (stdout _) (thin-restore "--help")
(assert-equal thin-restore-help stdout)))
(define-scenario (thin-restore no-input-file)
"forget to specify an input file"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-restore "-o" md)
(assert-starts-with "No input file provided." stderr))))
(define-scenario (thin-restore missing-input-file)
"the input file can't be found"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-restore "-i no-such-file -o" md)
(assert-superblock-all-zeroes md)
(assert-starts-with "Couldn't stat file" stderr))))
(define-scenario (thin-restore garbage-input-file)
"the input file is just zeroes"
(with-empty-metadata (md)
(with-temp-file-sized ((xml "thin.xml" 4096))
(run-fail-rcv (_ stderr) (thin-restore "-i " xml "-o" md)
(assert-superblock-all-zeroes md)))))
(define-scenario (thin-restore missing-output-file)
"the output file can't be found"
(with-thin-xml (xml)
(run-fail-rcv (_ stderr) (thin-restore "-i " xml)
(assert-starts-with "No output file provided." stderr))))
(define-scenario (thin-restore tiny-output-file)
"Fails if the output file is too small."
(with-temp-file-sized ((md "thin.bin" 4096))
(with-thin-xml (xml)
(run-fail-rcv (_ stderr) (thin-restore "-i" xml "-o" md)
(assert-starts-with thin-restore-outfile-too-small-text stderr)))))
(define-scenario (thin-restore q)
"thin_restore accepts -q"
(with-empty-metadata (md)
(with-thin-xml (xml)
(run-ok-rcv (stdout _) (thin-restore "-i" xml "-o" md "-q")
(assert-eof stdout)))))
(define-scenario (thin-restore quiet)
"thin_restore accepts --quiet"
(with-empty-metadata (md)
(with-thin-xml (xml)
(run-ok-rcv (stdout _) (thin-restore "-i" xml "-o" md "--quiet")
(assert-eof stdout)))))
(define-scenario (thin-restore override transaction-id)
"thin_restore obeys the --transaction-id override"
(with-empty-metadata (md)
(with-thin-xml (xml)
(run-ok-rcv (stdout stderr) (thin-restore "--transaction-id 2345" "-i" xml "-o" md)
(assert-eof stderr))
(run-ok-rcv (stdout stderr) (thin-dump md)
(assert-matches ".*transaction=\"2345\"" stdout)))))
(define-scenario (thin-restore override data-block-size)
"thin_restore obeys the --data-block-size override"
(with-empty-metadata (md)
(with-thin-xml (xml)
(run-ok-rcv (stdout stderr) (thin-restore "--data-block-size 8192" "-i" xml "-o" md)
(assert-eof stderr))
(run-ok-rcv (stdout stderr) (thin-dump md)
(assert-matches ".*data_block_size=\"8192\"" stdout)))))
(define-scenario (thin-restore override nr-data-blocks)
"thin_restore obeys the --nr-data-blocks override"
(with-empty-metadata (md)
(with-thin-xml (xml)
(run-ok-rcv (stdout stderr) (thin-restore "--nr-data-blocks 234500" "-i" xml "-o" md)
(assert-eof stderr))
(run-ok-rcv (stdout stderr) (thin-dump md)
(assert-matches ".*nr_data_blocks=\"234500\"" stdout)))))
;;;-----------------------------------------------------------
;;; thin_dump scenarios
;;;-----------------------------------------------------------
(define-scenario (thin-dump small-input-file)
"Fails with small input file"
(with-temp-file-sized ((md "thin.bin" 512))
(run-fail (thin-dump md))))
(define-scenario (thin-dump restore-is-noop)
"thin_dump followed by thin_restore is a noop."
(with-valid-metadata (md)
(run-ok-rcv (d1-stdout _) (thin-dump md)
(with-temp-file-containing ((xml "thin.xml" d1-stdout))
(run-ok (thin-restore "-i" xml "-o" md))
(run-ok-rcv (d2-stdout _) (thin-dump md)
(assert-equal d1-stdout d2-stdout))))))
(define-scenario (thin-dump no-stderr)
"thin_dump of clean data does not output error messages to stderr"
(with-valid-metadata (md)
(run-ok-rcv (stdout stderr) (thin-dump md)
(assert-eof stderr))))
(define-scenario (thin-dump override transaction-id)
"thin_dump obeys the --transaction-id override"
(with-valid-metadata (md)
(run-ok-rcv (stdout stderr) (thin-dump "--transaction-id 2345" md)
(assert-eof stderr)
(assert-matches ".*transaction=\"2345\"" stdout))))
(define-scenario (thin-dump override data-block-size)
"thin_dump obeys the --data-block-size override"
(with-valid-metadata (md)
(run-ok-rcv (stdout stderr) (thin-dump "--data-block-size 8192" md)
(assert-eof stderr)
(assert-matches ".*data_block_size=\"8192\"" stdout))))
(define-scenario (thin-dump override nr-data-blocks)
"thin_dump obeys the --nr-data-blocks override"
(with-valid-metadata (md)
(run-ok-rcv (stdout stderr) (thin-dump "--nr-data-blocks 234500" md)
(assert-eof stderr)
(assert-matches ".*nr_data_blocks=\"234500\"" stdout))))
(define-scenario (thin-dump repair-superblock succeeds)
"thin_dump can restore a missing superblock"
(with-valid-metadata (md)
(run-ok-rcv (expected-xml stderr) (thin-dump "--transaction-id=5" "--data-block-size=128" "--nr-data-blocks=4096000" md)
(damage-superblock md)
(run-ok-rcv (repaired-xml stderr) (thin-dump "--repair" "--transaction-id=5" "--data-block-size=128" "--nr-data-blocks=4096000" md)
(assert-eof stderr)
(assert-equal expected-xml repaired-xml)))))
(define-scenario (thin-dump repair-superblock missing-transaction-id)
"--transaction-id is mandatory if the superblock is damaged"
(with-damaged-superblock (md)
(run-fail-rcv (_ stderr) (thin-dump "--repair" "--data-block-size=128" "--nr-data-blocks=4096000" md)
(assert-matches ".*transaction id.*" stderr))))
(define-scenario (thin-dump repair-superblock missing-data-block-size)
"--data-block-size is mandatory if the superblock is damaged"
(with-damaged-superblock (md)
(run-fail-rcv (_ stderr) (thin-dump "--repair" "--transaction-id=5" "--nr-data-blocks=4096000" md)
(assert-matches ".*data block size.*" stderr))))
(define-scenario (thin-dump repair-superblock missing-nr-data-blocks)
"--nr-data-blocks is mandatory if the superblock is damaged"
(with-damaged-superblock (md)
(run-fail-rcv (_ stderr) (thin-dump "--repair" "--transaction-id=5" "--data-block-size=128" md)
(assert-matches ".*nr data blocks.*" stderr))))
;;;-----------------------------------------------------------
;;; thin_rmap scenarios
;;;-----------------------------------------------------------
(define-scenario (thin-rmap v)
"thin_rmap accepts -V"
(run-ok-rcv (stdout _) (thin-rmap "-V")
(assert-equal tools-version stdout)))
(define-scenario (thin-rmap version)
"thin_rmap accepts --version"
(run-ok-rcv (stdout _) (thin-rmap "--version")
(assert-equal tools-version stdout)))
(define-scenario (thin-rmap h)
"thin_rmap accepts -h"
(run-ok-rcv (stdout _) (thin-rmap "-h")
(assert-equal thin-rmap-help stdout)))
(define-scenario (thin-rmap help)
"thin_rmap accepts --help"
(run-ok-rcv (stdout _) (thin-rmap "--help")
(assert-equal thin-rmap-help stdout)))
(define-scenario (thin-rmap unrecognised-flag)
"thin_rmap complains with bad flags."
(run-fail (thin-rmap "--unleash-the-hedgehogs")))
(define-scenario (thin-rmap valid-region-format-should-pass)
"thin_rmap with a valid region format should pass."
(with-valid-metadata (md)
(run-ok
(thin-rmap "--region 23..7890" md))))
(define-scenario (thin-rmap invalid-region-should-fail)
"thin_rmap with an invalid region format should fail."
(for-each (lambda (pattern)
(with-valid-metadata (md)
(run-fail (thin-rmap "--region" pattern md))))
'("23,7890" "23..six" "found..7890" "89..88" "89..89" "89.." "" "89...99")))
(define-scenario (thin-rmap multiple-regions-should-pass)
"thin_rmap should handle multiple regions."
(with-valid-metadata (md)
(run-ok (thin-rmap "--region 1..23 --region 45..78" md))))
(define-scenario (thin-rmap handles-junk-input)
"Fail gracefully if given nonsense"
(with-thin-xml (xml)
(run-fail-rcv (_ stderr) (thin-rmap "--region 0..-1" xml)
#t)))
;;;-----------------------------------------------------------
;;; thin_delta scenarios
;;;-----------------------------------------------------------
(define-scenario (thin-delta v)
"thin_delta accepts -V"
(run-ok-rcv (stdout _) (thin-delta "-V")
(assert-equal tools-version stdout)))
(define-scenario (thin-delta version)
"thin_delta accepts --version"
(run-ok-rcv (stdout _) (thin-delta "--version")
(assert-equal tools-version stdout)))
(define-scenario (thin-delta h)
"thin_delta accepts -h"
(run-ok-rcv (stdout _) (thin-delta "-h")
(assert-equal thin-delta-help stdout)))
(define-scenario (thin-delta help)
"thin_delta accepts --help"
(run-ok-rcv (stdout _) (thin-delta "--help")
(assert-equal thin-delta-help stdout)))
(define-scenario (thin-delta unrecognised-option)
"Unrecognised option should cause failure"
(with-valid-metadata (md)
(run-fail-rcv (stdout stderr) (thin-delta "--unleash-the-hedgehogs")
(assert-matches ".*thin_delta: unrecognized option '--unleash-the-hedgehogs" stderr))))
(define-scenario (thin-delta snap1-unspecified)
"Fails without --snap1 fails"
(run-fail-rcv (_ stderr) (thin-delta "--snap2 45 foo")
(assert-starts-with "--snap1 not specified." stderr)))
(define-scenario (thin-delta snap2-unspecified)
"Fails without --snap2 fails"
(run-fail-rcv (_ stderr) (thin-delta "--snap1 45 foo")
(assert-starts-with "--snap2 not specified." stderr)))
(define-scenario (thin-delta device-unspecified)
"Fails if no device given"
(run-fail-rcv (_ stderr) (thin-delta "--snap1 45 --snap2 46")
(assert-starts-with "No input device provided." stderr)))
;;;-----------------------------------------------------------
;;; thin_repair scenarios
;;;-----------------------------------------------------------
(define-scenario (thin-repair dont-repair-xml)
"Fails gracefully if run on XML rather than metadata"
(with-thin-xml (xml)
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-repair "-i" xml "-o" md)
#t))))
(define-scenario (thin-repair missing-input-file)
"the input file can't be found"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-repair "-i no-such-file -o" md)
(assert-superblock-all-zeroes md)
(assert-starts-with "Couldn't stat file" stderr))))
(define-scenario (thin-repair garbage-input-file)
"the input file is just zeroes"
(with-empty-metadata (md1)
(with-corrupt-metadata (md2)
(run-fail-rcv (_ stderr) (thin-repair "-i " md1 "-o" md2)
(assert-superblock-all-zeroes md2)))))
(define-scenario (thin-repair missing-output-file)
"the output file can't be found"
(with-thin-xml (xml)
(run-fail-rcv (_ stderr) (thin-repair "-i " xml)
(assert-starts-with "No output file provided." stderr))))
(define-scenario (thin-repair override transaction-id)
"thin_repair obeys the --transaction-id override"
(with-valid-metadata (md1)
(with-empty-metadata (md2)
(run-ok-rcv (stdout stderr) (thin-repair "--transaction-id 2345" "-i" md1 "-o" md2)
(assert-eof stderr))
(run-ok-rcv (stdout stderr) (thin-dump md2)
(assert-matches ".*transaction=\"2345\"" stdout)))))
(define-scenario (thin-repair override data-block-size)
"thin_repair obeys the --data-block-size override"
(with-valid-metadata (md1)
(with-empty-metadata (md2)
(run-ok-rcv (stdout stderr) (thin-repair "--data-block-size 8192" "-i" md1 "-o" md2)
(assert-eof stderr))
(run-ok-rcv (stdout stderr) (thin-dump md2)
(assert-matches ".*data_block_size=\"8192\"" stdout)))))
(define-scenario (thin-repair override nr-data-blocks)
"thin_repair obeys the --nr-data-blocks override"
(with-valid-metadata (md1)
(with-empty-metadata (md2)
(run-ok-rcv (stdout stderr) (thin-repair "--nr-data-blocks 234500" "-i" md1 "-o" md2)
(assert-eof stderr))
(run-ok-rcv (stdout stderr) (thin-dump md2)
(assert-matches ".*nr_data_blocks=\"234500\"" stdout)))))
(define-scenario (thin-repair superblock succeeds)
"thin_repair can restore a missing superblock"
(with-valid-metadata (md1)
(run-ok-rcv (expected-xml stderr) (thin-dump "--transaction-id=5" "--data-block-size=128" "--nr-data-blocks=4096000" md1)
(damage-superblock md1)
(with-empty-metadata (md2)
(run-ok-rcv (_ stderr) (thin-repair "--transaction-id=5" "--data-block-size=128" "--nr-data-blocks=4096000" "-i" md1 "-o" md2)
(assert-eof stderr))
(run-ok-rcv (repaired-xml stderr) (thin-dump md2)
(assert-eof stderr)
(assert-equal expected-xml repaired-xml))))))
(define-scenario (thin-repair superblock missing-transaction-id)
"--transaction-id is mandatory if the superblock is damaged"
(with-damaged-superblock (md1)
(with-empty-metadata (md2)
(run-fail-rcv (_ stderr) (thin-repair "--data-block-size=128" "--nr-data-blocks=4096000" "-i" md1 "-o" md2)
(assert-matches ".*transaction id.*" stderr)))))
(define-scenario (thin-repair superblock missing-data-block-size)
"--data-block-size is mandatory if the superblock is damaged"
(with-damaged-superblock (md1)
(with-empty-metadata (md2)
(run-fail-rcv (_ stderr) (thin-repair "--transaction-id=5" "--nr-data-blocks=4096000" "-i" md1 "-o" md2)
(assert-matches ".*data block size.*" stderr)))))
(define-scenario (thin-repair superblock missing-nr-data-blocks)
"--nr-data-blocks is mandatory if the superblock is damaged"
(with-damaged-superblock (md1)
(with-empty-metadata (md2)
(run-fail-rcv (_ stderr) (thin-repair "--transaction-id=5" "--data-block-size=128" "-i" md1 "-o" md2)
(assert-matches ".*nr data blocks.*" stderr)))))
;;;-----------------------------------------------------------
;;; thin_metadata_pack scenarios
;;;-----------------------------------------------------------
(define-scenario (thin-metadata-pack version)
"accepts --version"
(run-ok-rcv (stdout _) (thin-metadata-pack "--version")
(assert-equal "thin_metadata_pack 0.9.0-rc2" stdout)))
(define-scenario (thin-metadata-pack h)
"accepts -h"
(run-ok-rcv (stdout _) (thin-metadata-pack "-h")
(assert-equal thin-metadata-pack-help stdout)))
(define-scenario (thin-metadata-pack help)
"accepts --help"
(run-ok-rcv (stdout _) (thin-metadata-pack "--help")
(assert-equal thin-metadata-pack-help stdout)))
(define-scenario (thin-metadata-pack unrecognised-option)
"Unrecognised option should cause failure"
(with-valid-metadata (md)
(run-fail-rcv (stdout stderr) (thin-metadata-pack "--unleash-the-hedgehogs")
(assert-starts-with "error: Found argument '--unleash-the-hedgehogs'" stderr))))
(define-scenario (thin-metadata-pack missing-input-file)
"the input file wasn't specified"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-metadata-pack "-o " md)
(assert-starts-with "error: The following required arguments were not provided:\n -i <DEV>" stderr))))
(define-scenario (thin-metadata-pack no-such-input-file)
"the input file can't be found"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-metadata-pack "-i no-such-file -o" md)
(assert-starts-with "Couldn't find input file" stderr))))
(define-scenario (thin-metadata-pack missing-output-file)
"the output file wasn't specified"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-metadata-pack "-i" md)
(assert-starts-with "error: The following required arguments were not provided:\n -o <FILE>" stderr))))
;;;-----------------------------------------------------------
;;; thin_metadata_unpack scenarios
;;;-----------------------------------------------------------
(define-scenario (thin-metadata-unpack version)
"accepts --version"
(run-ok-rcv (stdout _) (thin-metadata-unpack "--version")
(assert-equal "thin_metadata_unpack 0.9.0-rc2" stdout)))
(define-scenario (thin-metadata-unpack h)
"accepts -h"
(run-ok-rcv (stdout _) (thin-metadata-unpack "-h")
(assert-equal thin-metadata-unpack-help stdout)))
(define-scenario (thin-metadata-unpack help)
"accepts --help"
(run-ok-rcv (stdout _) (thin-metadata-unpack "--help")
(assert-equal thin-metadata-unpack-help stdout)))
(define-scenario (thin-metadata-unpack unrecognised-option)
"Unrecognised option should cause failure"
(with-valid-metadata (md)
(run-fail-rcv (stdout stderr) (thin-metadata-unpack "--unleash-the-hedgehogs")
(assert-starts-with "error: Found argument '--unleash-the-hedgehogs'" stderr))))
(define-scenario (thin-metadata-unpack missing-input-file)
"the input file wasn't specified"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-metadata-unpack "-o " md)
(assert-starts-with "error: The following required arguments were not provided:\n -i <DEV>" stderr))))
(define-scenario (thin-metadata-unpack no-such-input-file)
"the input file can't be found"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-metadata-unpack "-i no-such-file -o" md)
(assert-starts-with "Couldn't find input file" stderr))))
(define-scenario (thin-metadata-unpack missing-output-file)
"the output file wasn't specified"
(with-empty-metadata (md)
(run-fail-rcv (_ stderr) (thin-metadata-unpack "-i" md)
(assert-starts-with "error: The following required arguments were not provided:\n -o <FILE>" stderr))))
(define-scenario (thin-metadata-unpack garbage-input-file)
"the input file is just zeroes"
(with-empty-metadata (bad-pack)
(run-fail-rcv (_ stderr) (thin-metadata-unpack "-i " bad-pack "-o junk")
(assert-starts-with "Not a pack file." stderr))))
;;;-----------------------------------------------------------
;;; thin_metadata_pack/unpack end to end scenario
;;;-----------------------------------------------------------)
(define-scenario (thin-metadata-pack end-to-end)
"pack -> unpack recovers metadata"
(let ((pack-file "md.pack"))
(with-valid-metadata (md-in)
(with-empty-metadata (md-out)
(run-ok (thin-metadata-pack "-i" md-in "-o" pack-file))
(run-ok (thin-metadata-unpack "-i" pack-file "-o" md-out))
(run-ok-rcv (dump1 _) (thin-dump md-in)
(run-ok-rcv (dump2 _) (thin-dump md-out)
(assert-equal dump1 dump2)))))))
)

View File

@ -62,6 +62,10 @@ namespace persistent_data {
return stop_on_error_;
}
virtual void clear() {
counts_.clear();
}
private:
count_map counts_;
bool stop_on_error_;
@ -86,6 +90,11 @@ namespace persistent_data {
base::run_set<block_address> const& get_visited() const {
return visited_;
}
virtual void clear() {
visited_.clear();
}
private:
base::run_set<block_address> visited_;
};

View File

@ -104,7 +104,7 @@ namespace persistent_data {
template <typename ValueTraits>
class array : public array_base {
public:
class block_ref_counter : public ref_counter<uint64_t> {
class block_ref_counter : public persistent_data::ref_counter<uint64_t> {
public:
block_ref_counter(space_map::ptr sm,
array<ValueTraits> &a)

View File

@ -145,11 +145,11 @@ namespace persistent_data {
{
internal_node n = spine.get_node<block_traits>();
// compact the path if there's only one child
if (n.get_nr_entries() == 1) {
block_address b = n.value_at(0);
read_ref child = tm_.read_lock(b, validator_);
// FIXME: is it safe?
::memcpy(n.raw(), child.data(), read_ref::BLOCK_SIZE);
tm_.get_sm()->dec(child.get_location());
@ -341,7 +341,6 @@ namespace persistent_data {
if (nr_left < nr_right) {
int s = nr_left - target_left;
// FIXME: signed & unsigned comparison
if (s < 0 && nr_center < static_cast<unsigned>(-s)) {
// not enough in central node
left.move_entries(center, -nr_center);

View File

@ -338,7 +338,7 @@ namespace persistent_data {
unsigned nr_right = rhs.get_nr_entries();
unsigned max_entries = get_max_entries();
if (nr_left - count > max_entries || nr_right - count > max_entries)
if (nr_left - count > max_entries || nr_right + count > max_entries)
throw runtime_error("too many entries");
if (count > 0) {
@ -693,9 +693,15 @@ namespace persistent_data {
leaf_node n = spine.template get_node<ValueTraits>();
if (need_insert)
n.insert_at(index, key[Levels - 1], value);
else
// FIXME: check if we're overwriting with the same value.
n.set_value(index, value);
else {
typename ValueTraits::value_type old_value = n.value_at(index);
if (value != old_value) {
// do decrement the old value if it already exists
rc_.dec(old_value);
n.set_value(index, value);
}
}
root_ = spine.get_root();
@ -981,11 +987,6 @@ namespace persistent_data {
if (i < 0 || leaf.key_at(i) != key)
i++;
// do decrement the old value if it already exists
// FIXME: I'm not sure about this, I don't understand the |inc| reference
if (static_cast<unsigned>(i) < leaf.get_nr_entries() && leaf.key_at(i) == key && inc) {
// dec old entry
}
*index = i;
return ((static_cast<unsigned>(i) >= leaf.get_nr_entries()) ||

View File

@ -14,12 +14,14 @@ namespace persistent_data {
public:
typedef btree<Levels, ValueTraits> tree;
counting_visitor(block_counter &bc, ValueCounter &vc)
counting_visitor(block_counter &bc, ValueCounter &vc,
bool ignore_non_fatal = false)
: bc_(bc),
vc_(vc),
error_outcome_(bc.stop_on_error() ?
tree::visitor::RETHROW_EXCEPTION :
tree::visitor::EXCEPTION_HANDLED) {
tree::visitor::EXCEPTION_HANDLED),
ignore_non_fatal_(ignore_non_fatal) {
}
virtual bool visit_internal(node_location const &l,
@ -66,7 +68,7 @@ namespace persistent_data {
if (!checker_.check_block_nr(n) ||
!checker_.check_value_size(n) ||
!checker_.check_max_entries(n) ||
!checker_.check_nr_entries(n, l.is_sub_root()) ||
!check_nr_entries(l, n) ||
!checker_.check_ordered_keys(n) ||
!checker_.check_parent_key(n, l.is_sub_root() ? boost::optional<uint64_t>() : l.key))
return false;
@ -83,7 +85,7 @@ namespace persistent_data {
if (!checker_.check_block_nr(n) ||
!checker_.check_value_size(n) ||
!checker_.check_max_entries(n) ||
!checker_.check_nr_entries(n, l.is_sub_root()) ||
!check_nr_entries(l, n) ||
!checker_.check_ordered_keys(n) ||
!checker_.check_parent_key(n, l.is_sub_root() ? boost::optional<uint64_t>() : l.key) ||
!checker_.check_leaf_key(n, last_leaf_key_[l.level()]))
@ -109,11 +111,18 @@ namespace persistent_data {
return !seen;
}
template <typename ValueTraits2>
bool check_nr_entries(node_location const &loc,
btree_detail::node_ref<ValueTraits2> const &n) {
return ignore_non_fatal_ || checker_.check_nr_entries(n, loc.is_sub_root());
}
block_counter &bc_;
ValueCounter &vc_;
btree_node_checker checker_;
boost::optional<uint64_t> last_leaf_key_[Levels];
error_outcome error_outcome_;
bool ignore_non_fatal_;
};
}
@ -141,8 +150,9 @@ namespace persistent_data {
// walked. This walk should only be done once you're sure the tree
// is not corrupt.
template <unsigned Levels, typename ValueTraits, typename ValueCounter>
void count_btree_blocks(btree<Levels, ValueTraits> const &tree, block_counter &bc, ValueCounter &vc) {
btree_count_detail::counting_visitor<Levels, ValueTraits, ValueCounter> v(bc, vc);
void count_btree_blocks(btree<Levels, ValueTraits> const &tree, block_counter &bc, ValueCounter &vc,
bool ignore_non_fatal = false) {
btree_count_detail::counting_visitor<Levels, ValueTraits, ValueCounter> v(bc, vc, ignore_non_fatal);
tree.visit_depth_first(v);
}
}

View File

@ -666,9 +666,14 @@ namespace {
if (!ie.blocknr_)
return;
block_manager::read_ref rr = tm_.read_lock(ie.blocknr_, bitmap_validator_);
if (rr.data())
bc_.inc(ie.blocknr_);
try {
block_manager::read_ref rr = tm_.read_lock(ie.blocknr_, bitmap_validator_);
if (rr.data())
bc_.inc(ie.blocknr_);
} catch (std::exception &e) {
if (bc_.stop_on_error())
throw;
}
}
private:

View File

@ -42,6 +42,16 @@ namespace persistent_data {
uint32_t none_free_before_;
};
inline bool operator==(index_entry const& lhs, index_entry const& rhs) {
// The return value doesn't matter, since the ref-counts of bitmap blocks
// are managed by shadow operations.
return false;
}
inline bool operator!=(index_entry const& lhs, index_entry const& rhs) {
return !(lhs == rhs);
}
struct index_entry_traits {
typedef index_entry_disk disk_type;
typedef index_entry value_type;

View File

@ -37,6 +37,13 @@ transaction_manager::~transaction_manager()
{
}
void
transaction_manager::commit()
{
wipe_shadow_table();
bm_->flush();
}
transaction_manager::write_ref
transaction_manager::begin(block_address superblock, validator v)
{

View File

@ -42,6 +42,8 @@ namespace persistent_data {
space_map::ptr sm);
~transaction_manager();
void commit();
// Drop the superblock reference to commit
write_ref begin(block_address superblock, validator v);
write_ref new_block(validator v);

View File

@ -1,2 +0,0 @@
pub const TOOLS_VERSION: &str = @THIN_PROVISIONING_TOOLS_VERSION@;

157
tests/cache_check.rs Normal file
View File

@ -0,0 +1,157 @@
use anyhow::Result;
mod common;
use common::cache::*;
use common::common_args::*;
use common::fixture::*;
use common::input_arg::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
//------------------------------------------
const USAGE: &str = "cache_check 0.9.0
USAGE:
cache_check [FLAGS] <INPUT>
FLAGS:
--auto-repair Auto repair trivial issues.
--ignore-non-fatal-errors Only return a non-zero exit code if a fatal error is found.
-q, --quiet Suppress output messages, return only exit code.
--super-block-only Only check the superblock.
--skip-discards Don't check the discard bitset
--skip-hints Don't check the hint array
-h, --help Prints help information
-V, --version Prints version information
ARGS:
<INPUT> Specify the input device to check";
//------------------------------------------
struct CacheCheck;
impl<'a> Program<'a> for CacheCheck {
fn name() -> &'a str {
"cache_check"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
cache_check_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::InputArg
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for CacheCheck {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_md(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
msg::BAD_SUPERBLOCK
}
}
impl<'a> MetadataReader<'a> for CacheCheck {}
//------------------------------------------
test_accepts_help!(CacheCheck);
test_accepts_version!(CacheCheck);
test_rejects_bad_option!(CacheCheck);
test_missing_input_arg!(CacheCheck);
test_input_file_not_found!(CacheCheck);
test_input_cannot_be_a_directory!(CacheCheck);
test_unreadable_input_file!(CacheCheck);
test_help_message_for_tiny_input_file!(CacheCheck);
test_spot_xml_data!(CacheCheck);
test_corrupted_input_data!(CacheCheck);
//------------------------------------------
#[test]
fn failing_q() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?;
let output = run_fail_raw(cache_check_cmd(args!["-q", &md]))?;
assert_eq!(output.stdout.len(), 0);
eprintln!(
"stderr = '{}'",
std::str::from_utf8(&output.stderr).unwrap()
);
assert_eq!(output.stderr.len(), 0);
Ok(())
}
#[test]
fn failing_quiet() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?;
let output = run_fail_raw(cache_check_cmd(args!["--quiet", &md]))?;
assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0);
Ok(())
}
#[test]
fn valid_metadata_passes() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
run_ok(cache_check_cmd(args![&md]))?;
Ok(())
}
// FIXME: put back in, I don't want to add the --debug- arg to the
// tool again, so we should have a little library function for tweaking
// metadata version.
/*
#[test]
fn bad_metadata_version() -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&mut td)?;
run_ok(
cache_restore_cmd(
args![
"-i",
&xml,
"-o",
&md,
"--debug-override-metadata-version",
"12345"
],
))?;
run_fail(cache_check_cmd(args![&md]))?;
Ok(())
}
*/

119
tests/cache_dump.rs Normal file
View File

@ -0,0 +1,119 @@
use anyhow::Result;
use std::fs::OpenOptions;
use std::io::Write;
mod common;
use common::cache::*;
use common::common_args::*;
use common::fixture::*;
use common::input_arg::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
//------------------------------------------
const USAGE: &str = "cache_dump 0.9.0
Dump the cache metadata to stdout in XML format
USAGE:
cache_dump [FLAGS] [OPTIONS] <INPUT>
FLAGS:
-r, --repair Repair the metadata whilst dumping it
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-o, --output <FILE> Specify the output file rather than stdout
ARGS:
<INPUT> Specify the input device to dump";
//------------------------------------------
struct CacheDump;
impl<'a> Program<'a> for CacheDump {
fn name() -> &'a str {
"cache_dump"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
cache_dump_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::InputArg
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for CacheDump {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_md(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
msg::BAD_SUPERBLOCK
}
}
//------------------------------------------
test_accepts_help!(CacheDump);
test_accepts_version!(CacheDump);
test_rejects_bad_option!(CacheDump);
test_missing_input_arg!(CacheDump);
test_input_file_not_found!(CacheDump);
test_input_cannot_be_a_directory!(CacheDump);
test_unreadable_input_file!(CacheDump);
//------------------------------------------
// TODO: share with thin_dump
#[test]
fn dump_restore_cycle() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let output = run_ok_raw(cache_dump_cmd(args![&md]))?;
let xml = td.mk_path("meta.xml");
let mut file = OpenOptions::new()
.read(false)
.write(true)
.create(true)
.open(&xml)?;
file.write_all(&output.stdout[0..])?;
drop(file);
let md2 = mk_zeroed_md(&mut td)?;
run_ok(cache_restore_cmd(args!["-i", &xml, "-o", &md2]))?;
let output2 = run_ok_raw(cache_dump_cmd(args![&md2]))?;
assert_eq!(output.stdout, output2.stdout);
Ok(())
}

View File

@ -0,0 +1,186 @@
use anyhow::Result;
mod common;
use common::common_args::*;
use common::process::*;
use common::program::*;
use common::target::*;
//------------------------------------------
const USAGE: &str = "cache_metadata_size 0.9.0
Estimate the size of the metadata device needed for a given configuration.
USAGE:
cache_metadata_size [OPTIONS] <--device-size <SECTORS> --block-size <SECTORS> | --nr-blocks <NUM>>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--block-size <SECTORS> Specify the size of each cache block
--device-size <SECTORS> Specify total size of the fast device used in the cache
--max-hint-width <BYTES> Specity the per-block hint width [default: 4]
--nr-blocks <NUM> Specify the number of cache blocks";
//------------------------------------------
struct CacheMetadataSize;
impl<'a> Program<'a> for CacheMetadataSize {
fn name() -> &'a str {
"cache_metadata_size"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
cache_metadata_size_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::InputArg
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
//------------------------------------------
test_accepts_help!(CacheMetadataSize);
test_accepts_version!(CacheMetadataSize);
test_rejects_bad_option!(CacheMetadataSize);
//------------------------------------------
#[test]
fn no_args() -> Result<()> {
let _stderr = run_fail(cache_metadata_size_cmd([""; 0]))?;
Ok(())
}
#[test]
fn device_size_only() -> Result<()> {
let _stderr = run_fail(cache_metadata_size_cmd(args!["--device-size", "204800"]))?;
Ok(())
}
#[test]
fn block_size_only() -> Result<()> {
let _stderr = run_fail(cache_metadata_size_cmd(args!["--block-size", "128"]))?;
Ok(())
}
/*
#[test]
fn conradictory_info_fails() -> Result<()> {
let stderr = run_fail(cache_metadata_size_cmd(
args![
"--device-size",
"102400",
"--block-size",
"1000",
"--nr-blocks",
"6"
],
))?;
assert_eq!(stderr, "Contradictory arguments given, --nr-blocks doesn't match the --device-size and --block-size.");
Ok(())
}
#[test]
fn all_args_agree() -> Result<()> {
let out = run_ok_raw(cache_metadata_size_cmd(
args![
"--device-size",
"102400",
"--block-size",
"100",
"--nr-blocks",
"1024"
],
))?;
let stdout = std::str::from_utf8(&out.stdout[..])
.unwrap()
.trim_end_matches(|c| c == '\n' || c == '\r')
.to_string();
assert_eq!(stdout, "8248 sectors");
assert_eq!(out.stderr.len(), 0);
Ok(())
}
*/
#[test]
fn dev_size_and_nr_blocks_conflicts() -> Result<()> {
run_fail(cache_metadata_size_cmd(args![
"--device-size",
"102400",
"--nr-blocks",
"1024"
]))?;
Ok(())
}
#[test]
fn block_size_and_nr_blocks_conflicts() -> Result<()> {
run_fail(cache_metadata_size_cmd(args![
"--block-size",
"100",
"--nr-blocks",
"1024"
]))?;
Ok(())
}
#[test]
fn nr_blocks_alone() -> Result<()> {
let out = run_ok_raw(cache_metadata_size_cmd(args!["--nr-blocks", "1024"]))?;
let stdout = std::str::from_utf8(&out.stdout[..])
.unwrap()
.trim_end_matches(|c| c == '\n' || c == '\r')
.to_string();
assert_eq!(stdout, "8248 sectors");
assert_eq!(out.stderr.len(), 0);
Ok(())
}
#[test]
fn dev_size_and_block_size_succeeds() -> Result<()> {
let out = run_ok_raw(cache_metadata_size_cmd(args![
"--device-size",
"102400",
"--block-size",
"100"
]))?;
let stdout = std::str::from_utf8(&out.stdout[..])
.unwrap()
.trim_end_matches(|c| c == '\n' || c == '\r')
.to_string();
assert_eq!(stdout, "8248 sectors");
assert_eq!(out.stderr.len(), 0);
Ok(())
}
#[test]
fn large_nr_blocks() -> Result<()> {
let out = run_ok_raw(cache_metadata_size_cmd(args!["--nr-blocks", "67108864"]))?;
let stdout = std::str::from_utf8(&out.stdout[..])
.unwrap()
.trim_end_matches(|c| c == '\n' || c == '\r')
.to_string();
assert_eq!(stdout, "3678208 sectors");
assert_eq!(out.stderr.len(), 0);
Ok(())
}
//------------------------------------------

102
tests/cache_repair.rs Normal file
View File

@ -0,0 +1,102 @@
use anyhow::Result;
mod common;
use common::cache::*;
use common::common_args::*;
use common::input_arg::*;
use common::output_option::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
//------------------------------------------
const USAGE: &str = "cache_repair 0.9.0
Repair binary cache metadata, and write it to a different device or file
USAGE:
cache_repair [FLAGS] --input <FILE> --output <FILE>
FLAGS:
-q, --quiet Suppress output messages, return only exit code.
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-i, --input <FILE> Specify the input device
-o, --output <FILE> Specify the output device";
//-----------------------------------------
struct CacheRepair;
impl<'a> Program<'a> for CacheRepair {
fn name() -> &'a str {
"cache_repair"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
cache_repair_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::IoOptions
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for CacheRepair {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_md(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
"bad checksum in superblock"
}
}
impl<'a> OutputProgram<'a> for CacheRepair {
fn missing_output_arg() -> &'a str {
msg::MISSING_OUTPUT_ARG
}
}
impl<'a> MetadataWriter<'a> for CacheRepair {
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
}
//-----------------------------------------
test_accepts_help!(CacheRepair);
test_accepts_version!(CacheRepair);
test_rejects_bad_option!(CacheRepair);
test_input_file_not_found!(CacheRepair);
test_input_cannot_be_a_directory!(CacheRepair);
test_corrupted_input_data!(CacheRepair);
test_missing_output_option!(CacheRepair);
//-----------------------------------------

180
tests/cache_restore.rs Normal file
View File

@ -0,0 +1,180 @@
use anyhow::Result;
mod common;
use common::cache::*;
use common::common_args::*;
use common::fixture::*;
use common::input_arg::*;
use common::output_option::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
//------------------------------------------
const USAGE: &str = "cache_restore 0.9.0
Convert XML format metadata to binary.
USAGE:
cache_restore [FLAGS] --input <FILE> --output <FILE>
FLAGS:
-q, --quiet Suppress output messages, return only exit code.
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-i, --input <FILE> Specify the input xml
-o, --output <FILE> Specify the output device to check";
//------------------------------------------
struct CacheRestore;
impl<'a> Program<'a> for CacheRestore {
fn name() -> &'a str {
"thin_restore"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
cache_restore_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::IoOptions
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for CacheRestore {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_xml(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
"" // we don't intent to verify error messages of XML parsing
}
}
impl<'a> OutputProgram<'a> for CacheRestore {
fn missing_output_arg() -> &'a str {
msg::MISSING_OUTPUT_ARG
}
}
impl<'a> MetadataWriter<'a> for CacheRestore {
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
}
//-----------------------------------------
test_accepts_help!(CacheRestore);
test_accepts_version!(CacheRestore);
test_missing_input_option!(CacheRestore);
test_input_file_not_found!(CacheRestore);
test_corrupted_input_data!(CacheRestore);
test_missing_output_option!(CacheRestore);
test_tiny_output_file!(CacheRestore);
test_unwritable_output_file!(CacheRestore);
//-----------------------------------------
// TODO: share with thin_restore, era_restore
fn quiet_flag(flag: &str) -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&mut td)?;
let output = run_ok_raw(cache_restore_cmd(args!["-i", &xml, "-o", &md, flag]))?;
assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0);
Ok(())
}
#[test]
fn accepts_q() -> Result<()> {
quiet_flag("-q")
}
#[test]
fn accepts_quiet() -> Result<()> {
quiet_flag("--quiet")
}
//-----------------------------------------
#[test]
fn successfully_restores() -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&mut td)?;
run_ok(cache_restore_cmd(args!["-i", &xml, "-o", &md]))?;
Ok(())
}
// FIXME: finish
/*
#[test]
fn override_metadata_version() -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&mut td)?;
run_ok(
cache_restore_cmd(
args![
"-i",
&xml,
"-o",
&md,
"--debug-override-metadata-version",
"10298"
],
))?;
Ok(())
}
*/
// FIXME: finish
/*
#[test]
fn accepts_omit_clean_shutdown() -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&mut td)?;
run_ok(
cache_restore_cmd(
args!["-i", &xml, "-o", &md, "--omit-clean-shutdown"],
))?;
Ok(())
}
*/
//-----------------------------------------

35
tests/common/cache.rs Normal file
View File

@ -0,0 +1,35 @@
use anyhow::Result;
use std::path::PathBuf;
use thinp::file_utils;
//use thinp::io_engine::*;
use crate::args;
use crate::common::cache_xml_generator::{write_xml, CacheGen};
use crate::common::process::*;
use crate::common::target::*;
use crate::common::test_dir::TestDir;
//-----------------------------------------------
pub fn mk_valid_xml(td: &mut TestDir) -> Result<PathBuf> {
let xml = td.mk_path("meta.xml");
let mut gen = CacheGen::new(512, 128, 1024, 80, 50); // bs, cblocks, oblocks, res, dirty
write_xml(&xml, &mut gen)?;
Ok(xml)
}
pub fn mk_valid_md(td: &mut TestDir) -> Result<PathBuf> {
let xml = td.mk_path("meta.xml");
let md = td.mk_path("meta.bin");
let mut gen = CacheGen::new(512, 4096, 32768, 80, 50);
write_xml(&xml, &mut gen)?;
let _file = file_utils::create_sized_file(&md, 4096 * 4096);
run_ok(cache_restore_cmd(args!["-i", &xml, "-o", &md]))?;
Ok(md)
}
//-----------------------------------------------

View File

@ -0,0 +1,95 @@
use anyhow::Result;
use rand::prelude::*;
use std::collections::HashSet;
use std::fs::OpenOptions;
use std::path::Path;
use thinp::cache::ir::{self, MetadataVisitor};
use thinp::cache::xml;
//------------------------------------------
pub trait XmlGen {
fn generate_xml(&mut self, v: &mut dyn MetadataVisitor) -> Result<()>;
}
pub fn write_xml(path: &Path, g: &mut dyn XmlGen) -> Result<()> {
let xml_out = OpenOptions::new()
.read(false)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
let mut w = xml::XmlWriter::new(xml_out);
g.generate_xml(&mut w)
}
pub struct CacheGen {
block_size: u32,
nr_cache_blocks: u32,
nr_origin_blocks: u64,
percent_resident: u8,
percent_dirty: u8,
}
impl CacheGen {
pub fn new(
block_size: u32,
nr_cache_blocks: u32,
nr_origin_blocks: u64,
percent_resident: u8,
percent_dirty: u8,
) -> Self {
CacheGen {
block_size,
nr_cache_blocks,
nr_origin_blocks,
percent_resident,
percent_dirty,
}
}
}
impl XmlGen for CacheGen {
fn generate_xml(&mut self, v: &mut dyn MetadataVisitor) -> Result<()> {
v.superblock_b(&ir::Superblock {
uuid: "".to_string(),
block_size: self.block_size,
nr_cache_blocks: self.nr_cache_blocks,
policy: "smq".to_string(),
hint_width: 4,
})?;
let nr_resident = (self.nr_cache_blocks * self.percent_resident as u32) / 100u32;
let mut cblocks = (0..self.nr_cache_blocks).collect::<Vec<u32>>();
cblocks.shuffle(&mut rand::thread_rng());
cblocks.truncate(nr_resident as usize);
cblocks.sort_unstable();
v.mappings_b()?;
{
let mut used = HashSet::new();
let mut rng = rand::thread_rng();
for cblock in cblocks {
let mut oblock = 0u64;
while used.contains(&oblock) {
oblock = rng.gen_range(0..self.nr_origin_blocks);
}
used.insert(oblock);
// FIXME: dirty should vary
v.mapping(&ir::Map {
cblock,
oblock,
dirty: false,
})?;
}
}
v.mappings_e()?;
v.superblock_e()?;
Ok(())
}
}
//------------------------------------------

103
tests/common/common_args.rs Normal file
View File

@ -0,0 +1,103 @@
use anyhow::Result;
use thinp::version::tools_version;
use crate::args;
use crate::common::process::*;
use crate::common::program::*;
//------------------------------------------
// help
pub fn test_help_short<'a, P>() -> Result<()>
where
P: Program<'a>,
{
let stdout = run_ok(P::cmd(args!["-h"]))?;
assert_eq!(stdout, P::usage());
Ok(())
}
pub fn test_help_long<'a, P>() -> Result<()>
where
P: Program<'a>,
{
let stdout = run_ok(P::cmd(vec!["--help"]))?;
assert_eq!(stdout, P::usage());
Ok(())
}
#[macro_export]
macro_rules! test_accepts_help {
($program: ident) => {
#[test]
fn accepts_h() -> Result<()> {
test_help_short::<$program>()
}
#[test]
fn accepts_help() -> Result<()> {
test_help_long::<$program>()
}
};
}
//------------------------------------------
// version
pub fn test_version_short<'a, P>() -> Result<()>
where
P: Program<'a>,
{
let stdout = run_ok(P::cmd(args!["-V"]))?;
assert!(stdout.contains(tools_version()));
Ok(())
}
pub fn test_version_long<'a, P>() -> Result<()>
where
P: Program<'a>,
{
let stdout = run_ok(P::cmd(args!["--version"]))?;
assert!(stdout.contains(tools_version()));
Ok(())
}
#[macro_export]
macro_rules! test_accepts_version {
($program: ident) => {
#[test]
fn accepts_v() -> Result<()> {
test_version_short::<$program>()
}
#[test]
fn accepts_version() -> Result<()> {
test_version_long::<$program>()
}
};
}
//------------------------------------------
pub fn test_rejects_bad_option<'a, P>() -> Result<()>
where
P: Program<'a>,
{
let option = "--hedgehogs-only";
let stderr = run_fail(P::cmd(args![option]))?;
assert!(stderr.contains(&P::bad_option_hint(option)));
Ok(())
}
#[macro_export]
macro_rules! test_rejects_bad_option {
($program: ident) => {
#[test]
fn rejects_bad_option() -> Result<()> {
test_rejects_bad_option::<$program>()
}
};
}
//------------------------------------------

34
tests/common/era.rs Normal file
View File

@ -0,0 +1,34 @@
use anyhow::Result;
use std::path::PathBuf;
use thinp::file_utils;
use crate::args;
use crate::common::era_xml_generator::{write_xml, CleanShutdownMeta};
use crate::common::process::*;
use crate::common::target::*;
use crate::common::test_dir::TestDir;
//-----------------------------------------------
pub fn mk_valid_xml(td: &mut TestDir) -> Result<PathBuf> {
let xml = td.mk_path("meta.xml");
let mut gen = CleanShutdownMeta::new(128, 256, 32, 4); // bs, nr_blocks, era, nr_wsets
write_xml(&xml, &mut gen)?;
Ok(xml)
}
pub fn mk_valid_md(td: &mut TestDir) -> Result<PathBuf> {
let xml = td.mk_path("meta.xml");
let md = td.mk_path("meta.bin");
let mut gen = CleanShutdownMeta::new(128, 256, 32, 4);
write_xml(&xml, &mut gen)?;
let _file = file_utils::create_sized_file(&md, 4096 * 4096);
run_ok(era_restore_cmd(args!["-i", &xml, "-o", &md]))?;
Ok(md)
}
//-----------------------------------------------

View File

@ -0,0 +1,157 @@
use anyhow::Result;
use rand::prelude::*;
use std::fs::OpenOptions;
use std::path::Path;
use thinp::era::ir::{self, MetadataVisitor};
use thinp::era::xml;
//------------------------------------------
pub trait XmlGen {
fn generate_xml(&mut self, v: &mut dyn MetadataVisitor) -> Result<()>;
}
pub fn write_xml(path: &Path, g: &mut dyn XmlGen) -> Result<()> {
let xml_out = OpenOptions::new()
.read(false)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
let mut w = xml::XmlWriter::new(xml_out, false);
g.generate_xml(&mut w)
}
//------------------------------------------
// Ordered sequence generator where each element has an independent probability
// of being present.
struct IndependentSequence {
begin: u32,
end: u32,
prob: u32,
rng: ThreadRng,
}
impl IndependentSequence {
fn new(begin: u32, end: u32, prob: u32) -> IndependentSequence {
IndependentSequence {
begin,
end,
prob,
rng: rand::thread_rng(),
}
}
}
impl Iterator for IndependentSequence {
type Item = std::ops::Range<u32>;
// FIXME: reduce complexity
fn next(&mut self) -> Option<std::ops::Range<u32>> {
if self.begin >= self.end {
return None;
}
let mut b = self.begin;
while b < self.end && self.rng.gen_range(0..100) >= self.prob {
b += 1;
}
if b == self.end {
return None;
}
let mut e = b + 1;
while e < self.end && self.rng.gen_range(0..100) < self.prob {
e += 1;
}
self.begin = e + 1;
Some(std::ops::Range { start: b, end: e })
}
}
//------------------------------------------
fn create_superblock(block_size: u32, nr_blocks: u32, current_era: u32) -> ir::Superblock {
ir::Superblock {
uuid: "".to_string(),
block_size,
nr_blocks,
current_era,
}
}
pub struct CleanShutdownMeta {
block_size: u32,
nr_blocks: u32,
current_era: u32,
nr_writesets: u32,
}
impl CleanShutdownMeta {
pub fn new(block_size: u32, nr_blocks: u32, current_era: u32, nr_writesets: u32) -> Self {
CleanShutdownMeta {
block_size,
nr_blocks,
current_era,
nr_writesets,
}
}
fn generate_writeset(v: &mut dyn MetadataVisitor, ws: &ir::Writeset) -> Result<()> {
v.writeset_b(ws)?;
let gen = IndependentSequence::new(0, ws.nr_bits, 10);
for seq in gen {
v.writeset_blocks(&ir::MarkedBlocks {
begin: seq.start,
len: seq.end - seq.start,
})?;
}
v.writeset_e()?;
Ok(())
}
fn generate_era_array(v: &mut dyn MetadataVisitor, nr_blocks: u32, max_era: u32) -> Result<()> {
let mut rng = rand::thread_rng();
v.era_b()?;
for b in 0..nr_blocks {
let era = rng.gen_range(0..max_era);
v.era(&ir::Era { block: b, era })?;
}
v.era_e()?;
Ok(())
}
}
impl XmlGen for CleanShutdownMeta {
fn generate_xml(&mut self, v: &mut dyn MetadataVisitor) -> Result<()> {
v.superblock_b(&create_superblock(
self.block_size,
self.nr_blocks,
self.current_era,
))?;
let era_low = self.current_era - self.nr_writesets + 1;
for era in era_low..self.current_era + 1 {
Self::generate_writeset(
v,
&ir::Writeset {
era,
nr_bits: self.nr_blocks,
},
)?;
}
Self::generate_era_array(v, self.nr_blocks, era_low)?;
v.superblock_e()?;
Ok(())
}
}
//------------------------------------------

69
tests/common/fixture.rs Normal file
View File

@ -0,0 +1,69 @@
use anyhow::Result;
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::path::PathBuf;
use thinp::file_utils;
use crate::common::test_dir::TestDir;
//------------------------------------------
pub fn mk_zeroed_md(td: &mut TestDir) -> Result<PathBuf> {
let md = td.mk_path("meta.bin");
eprintln!("path = {:?}", md);
let _file = file_utils::create_sized_file(&md, 1024 * 1024 * 16);
Ok(md)
}
pub fn damage_superblock(path: &PathBuf) -> Result<()> {
let mut output = OpenOptions::new().read(false).write(true).open(path)?;
let buf = [0u8; 512];
output.write_all(&buf)?;
Ok(())
}
//------------------------------------------
pub fn md5(md: &PathBuf) -> Result<String> {
let output = duct::cmd!("md5sum", "-b", &md).stdout_capture().run()?;
let csum = std::str::from_utf8(&output.stdout[0..])?.to_string();
let csum = csum.split_ascii_whitespace().next().unwrap().to_string();
Ok(csum)
}
// This checksums the file before and after the thunk is run to
// ensure it is unchanged.
pub fn ensure_untouched<F>(p: &PathBuf, thunk: F) -> Result<()>
where
F: Fn() -> Result<()>,
{
let csum = md5(p)?;
thunk()?;
assert_eq!(csum, md5(p)?);
Ok(())
}
pub fn superblock_all_zeroes(path: &PathBuf) -> Result<bool> {
let mut input = OpenOptions::new().read(true).write(false).open(path)?;
let mut buf = vec![0; 4096];
input.read_exact(&mut buf[0..])?;
for b in buf {
if b != 0 {
return Ok(false);
}
}
Ok(true)
}
pub fn ensure_superblock_zeroed<F>(p: &PathBuf, thunk: F) -> Result<()>
where
F: Fn() -> Result<()>,
{
thunk()?;
assert!(superblock_all_zeroes(p)?);
Ok(())
}
//------------------------------------------

296
tests/common/input_arg.rs Normal file
View File

@ -0,0 +1,296 @@
use anyhow::Result;
use std::ffi::OsStr;
use thinp::file_utils;
use crate::args;
use crate::common::fixture::*;
use crate::common::process::*;
use crate::common::program::*;
use crate::common::test_dir::*;
use crate::common::thin_xml_generator::{write_xml, FragmentedS};
//------------------------------------------
// wrappers
type ArgsBuilder = fn(&mut TestDir, &OsStr, &dyn Fn(&[&OsStr]) -> Result<()>) -> Result<()>;
fn with_output_md_untouched(
td: &mut TestDir,
input: &OsStr,
thunk: &dyn Fn(&[&OsStr]) -> Result<()>,
) -> Result<()> {
let output = mk_zeroed_md(td)?;
ensure_untouched(&output, || {
let args = args!["-i", input, "-o", &output];
thunk(&args)
})
}
fn with_output_superblock_zeroed(
td: &mut TestDir,
input: &OsStr,
thunk: &dyn Fn(&[&OsStr]) -> Result<()>,
) -> Result<()> {
let output = mk_zeroed_md(td)?;
ensure_superblock_zeroed(&output, || {
let args = args!["-i", input, "-o", &output];
thunk(&args)
})
}
fn input_arg_only(
_td: &mut TestDir,
input: &OsStr,
thunk: &dyn Fn(&[&OsStr]) -> Result<()>,
) -> Result<()> {
let args = args![input];
thunk(&args)
}
fn build_args_fn(t: ArgType) -> Result<ArgsBuilder> {
match t {
ArgType::InputArg => Ok(input_arg_only),
ArgType::IoOptions => Ok(with_output_md_untouched),
}
}
//------------------------------------------
// test invalid arguments
pub fn test_missing_input_arg<'a, P>() -> Result<()>
where
P: InputProgram<'a>,
{
let args: [&str; 0] = [];
let stderr = run_fail(P::cmd(args))?;
assert!(stderr.contains(P::missing_input_arg()));
Ok(())
}
#[macro_export]
macro_rules! test_missing_input_arg {
($program: ident) => {
#[test]
fn missing_input_arg() -> Result<()> {
test_missing_input_arg::<$program>()
}
};
}
pub fn test_missing_input_option<'a, P>() -> Result<()>
where
P: InputProgram<'a>,
{
let mut td = TestDir::new()?;
let output = mk_zeroed_md(&mut td)?;
ensure_untouched(&output, || {
let stderr = run_fail(P::cmd(args!["-o", &output]))?;
assert!(stderr.contains(P::missing_input_arg()));
Ok(())
})
}
#[macro_export]
macro_rules! test_missing_input_option {
($program: ident) => {
#[test]
fn missing_input_option() -> Result<()> {
test_missing_input_option::<$program>()
}
};
}
pub fn test_input_file_not_found<'a, P>() -> Result<()>
where
P: InputProgram<'a>,
{
let mut td = TestDir::new()?;
let wrapper = build_args_fn(P::arg_type())?;
wrapper(&mut td, "no-such-file".as_ref(), &|args: &[&OsStr]| {
let stderr = run_fail(P::cmd(args))?;
assert!(stderr.contains(P::file_not_found()));
Ok(())
})
}
#[macro_export]
macro_rules! test_input_file_not_found {
($program: ident) => {
#[test]
fn input_file_not_found() -> Result<()> {
test_input_file_not_found::<$program>()
}
};
}
pub fn test_input_cannot_be_a_directory<'a, P>() -> Result<()>
where
P: InputProgram<'a>,
{
let mut td = TestDir::new()?;
let wrapper = build_args_fn(P::arg_type())?;
wrapper(&mut td, "/tmp".as_ref(), &|args: &[&OsStr]| {
let stderr = run_fail(P::cmd(args))?;
assert!(stderr.contains("Not a block device or regular file"));
Ok(())
})
}
#[macro_export]
macro_rules! test_input_cannot_be_a_directory {
($program: ident) => {
#[test]
fn input_cannot_be_a_directory() -> Result<()> {
test_input_cannot_be_a_directory::<$program>()
}
};
}
pub fn test_unreadable_input_file<'a, P>() -> Result<()>
where
P: InputProgram<'a>,
{
let mut td = TestDir::new()?;
// input an unreadable file
let input = P::mk_valid_input(&mut td)?;
duct::cmd!("chmod", "-r", &input).run()?;
let wrapper = build_args_fn(P::arg_type())?;
wrapper(&mut td, input.as_ref(), &|args: &[&OsStr]| {
let stderr = run_fail(P::cmd(args))?;
assert!(stderr.contains("Permission denied"));
Ok(())
})
}
#[macro_export]
macro_rules! test_unreadable_input_file {
($program: ident) => {
#[test]
fn unreadable_input_file() -> Result<()> {
test_unreadable_input_file::<$program>()
}
};
}
//------------------------------------------
// test invalid content
pub fn test_tiny_input_file<'a, P>() -> Result<()>
where
P: MetadataReader<'a>,
{
let mut td = TestDir::new()?;
let input = td.mk_path("meta.bin");
file_utils::create_sized_file(&input, 1024)?;
let wrapper = build_args_fn(P::arg_type())?;
wrapper(&mut td, input.as_ref(), &|args: &[&OsStr]| {
run_fail(P::cmd(args))?;
Ok(())
})
}
#[macro_export]
macro_rules! test_tiny_input_file {
($program: ident) => {
#[test]
fn tiny_input_file() -> Result<()> {
test_tiny_input_file::<$program>()
}
};
}
pub fn test_help_message_for_tiny_input_file<'a, P>() -> Result<()>
where
P: MetadataReader<'a>,
{
let mut td = TestDir::new()?;
let input = td.mk_path("meta.bin");
file_utils::create_sized_file(&input, 1024)?;
let wrapper = build_args_fn(P::arg_type())?;
wrapper(&mut td, input.as_ref(), &|args: &[&OsStr]| {
let stderr = run_fail(P::cmd(args))?;
eprintln!("actual: {:?}", stderr);
assert!(stderr.contains("Metadata device/file too small. Is this binary metadata?"));
Ok(())
})
}
#[macro_export]
macro_rules! test_help_message_for_tiny_input_file {
($program: ident) => {
#[test]
fn prints_help_message_for_tiny_input_file() -> Result<()> {
test_help_message_for_tiny_input_file::<$program>()
}
};
}
pub fn test_spot_xml_data<'a, P>() -> Result<()>
where
P: MetadataReader<'a>,
{
let mut td = TestDir::new()?;
// input a large xml file
let input = td.mk_path("meta.xml");
let mut gen = FragmentedS::new(4, 10240);
write_xml(&input, &mut gen)?;
let wrapper = build_args_fn(P::arg_type())?;
wrapper(&mut td, input.as_ref(), &|args: &[&OsStr]| {
let stderr = run_fail(P::cmd(args))?;
let msg =
"This looks like XML. This tool only checks the binary metadata format.".to_string();
assert!(stderr.contains(&msg));
Ok(())
})
}
#[macro_export]
macro_rules! test_spot_xml_data {
($program: ident) => {
#[test]
fn spot_xml_data() -> Result<()> {
test_spot_xml_data::<$program>()
}
};
}
pub fn test_corrupted_input_data<'a, P>() -> Result<()>
where
P: InputProgram<'a>,
{
let mut td = TestDir::new()?;
let input = mk_zeroed_md(&mut td)?;
let wrapper = match P::arg_type() {
ArgType::InputArg => input_arg_only,
ArgType::IoOptions => with_output_superblock_zeroed,
};
wrapper(&mut td, input.as_ref(), &|args: &[&OsStr]| {
let stderr = run_fail(P::cmd(args))?;
assert!(stderr.contains(P::corrupted_input()));
Ok(())
})
}
#[macro_export]
macro_rules! test_corrupted_input_data {
($program: ident) => {
#[test]
fn corrupted_input_data() -> Result<()> {
test_corrupted_input_data::<$program>()
}
};
}
//------------------------------------------

18
tests/common/mod.rs Normal file
View File

@ -0,0 +1,18 @@
// suppress all the false alarms by cargo test
// https://github.com/rust-lang/rust/issues/46379
#![allow(dead_code)]
pub mod cache;
pub mod cache_xml_generator;
pub mod common_args;
pub mod era;
pub mod era_xml_generator;
pub mod fixture;
pub mod input_arg;
pub mod output_option;
pub mod process;
pub mod program;
pub mod target;
pub mod test_dir;
pub mod thin;
pub mod thin_xml_generator;

View File

@ -0,0 +1,133 @@
use anyhow::Result;
use thinp::file_utils;
use crate::args;
use crate::common::process::*;
use crate::common::program::*;
use crate::common::test_dir::*;
//-----------------------------------------
// test invalid arguments
pub fn test_missing_output_option<'a, P>() -> Result<()>
where
P: OutputProgram<'a>,
{
let mut td = TestDir::new()?;
let input = P::mk_valid_input(&mut td)?;
let stderr = run_fail(P::cmd(args!["-i", &input]))?;
assert!(stderr.contains(P::missing_output_arg()));
Ok(())
}
#[macro_export]
macro_rules! test_missing_output_option {
($program: ident) => {
#[test]
fn missing_output_option() -> Result<()> {
test_missing_output_option::<$program>()
}
};
}
pub fn test_output_file_not_found<'a, P>() -> Result<()>
where
P: MetadataWriter<'a>,
{
let mut td = TestDir::new()?;
let input = P::mk_valid_input(&mut td)?;
let cmd = P::cmd(args!["-i", &input, "-o", "no-such-file"]);
let stderr = run_fail(cmd)?;
assert!(stderr.contains(<P as MetadataWriter>::file_not_found()));
Ok(())
}
#[macro_export]
macro_rules! test_output_file_not_found {
($program: ident) => {
#[test]
fn output_file_not_found() -> Result<()> {
test_output_file_not_found::<$program>()
}
};
}
pub fn test_output_cannot_be_a_directory<'a, P>() -> Result<()>
where
P: OutputProgram<'a>,
{
let mut td = TestDir::new()?;
let input = P::mk_valid_input(&mut td)?;
let stderr = run_fail(P::cmd(args!["-i", &input, "-o", "/tmp"]))?;
assert!(stderr.contains("Not a block device or regular file"));
Ok(())
}
#[macro_export]
macro_rules! test_output_cannot_be_a_directory {
($program: ident) => {
#[test]
fn output_cannot_be_a_directory() -> Result<()> {
test_output_cannot_be_a_directory::<$program>()
}
};
}
pub fn test_unwritable_output_file<'a, P>() -> Result<()>
where
P: OutputProgram<'a>,
{
let mut td = TestDir::new()?;
let input = P::mk_valid_input(&mut td)?;
let output = td.mk_path("meta.bin");
let _file = file_utils::create_sized_file(&output, 4_194_304);
duct::cmd!("chmod", "-w", &output).run()?;
let stderr = run_fail(P::cmd(args!["-i", &input, "-o", &output]))?;
assert!(stderr.contains("Permission denied"));
Ok(())
}
#[macro_export]
macro_rules! test_unwritable_output_file {
($program: ident) => {
#[test]
fn unwritable_output_file() -> Result<()> {
test_unwritable_output_file::<$program>()
}
};
}
//----------------------------------------
// test invalid content
// currently thin/cache_restore only
pub fn test_tiny_output_file<'a, P>() -> Result<()>
where
P: MetadataWriter<'a>,
{
let mut td = TestDir::new()?;
let input = P::mk_valid_input(&mut td)?;
let output = td.mk_path("meta.bin");
let _file = file_utils::create_sized_file(&output, 4096);
let stderr = run_fail(P::cmd(args!["-i", &input, "-o", &output]))?;
assert!(stderr.contains("Output file too small"));
Ok(())
}
#[macro_export]
macro_rules! test_tiny_output_file {
($program: ident) => {
#[test]
fn tiny_output_file() -> Result<()> {
test_tiny_output_file::<$program>()
}
};
}
//-----------------------------------------

121
tests/common/process.rs Normal file
View File

@ -0,0 +1,121 @@
use anyhow::Result;
use std::ffi::OsString;
use std::fmt;
use std::process;
//------------------------------------------
#[macro_export]
macro_rules! args {
( $( $arg: expr ),* ) => {
{
use std::ffi::OsStr;
let args = [$( OsStr::new($arg) ),*];
args
}
};
}
/// Holds a set of arguments for a shell command
#[derive(Debug)]
pub struct Command {
program: OsString,
args: Vec<OsString>,
}
#[macro_export]
macro_rules! cmd {
( $program:expr $(, $arg:expr )* $(,)? ) => {
{
// use std::ffi::OsString;
let args: &[OsString] = &[$( Into::<OsString>::into($arg) ),*];
Command::new($program, args)
}
};
}
impl Command {
pub fn new(program: OsString, args: Vec<OsString>) -> Self {
Command { program, args }
}
fn to_expr(&self) -> duct::Expression {
duct::cmd(&self.program, &self.args)
}
}
impl fmt::Display for Command {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.program.clone().into_string().unwrap())?;
for a in &self.args {
write!(f, " {}", a.clone().into_string().unwrap())?;
}
Ok(())
}
}
fn log_output(output: &process::Output) {
use std::str::from_utf8;
if !output.stdout.is_empty() {
eprintln!("stdout: \n{}<<END>>", from_utf8(&output.stdout).unwrap());
}
if !output.stderr.is_empty() {
eprintln!("stderr: \n{}<<END>>", from_utf8(&output.stderr).unwrap());
}
}
// Returns stdout. The command must return zero.
pub fn run_ok(command: Command) -> Result<String> {
eprintln!("run_ok: {}", command);
let command = command.to_expr().stdout_capture().stderr_capture();
let output = command.run()?;
log_output(&output);
assert!(output.status.success());
let stdout = std::str::from_utf8(&output.stdout[..])
.unwrap()
.trim_end_matches(|c| c == '\n' || c == '\r')
.to_string();
Ok(stdout)
}
// Returns the entire output. The command must return zero.
pub fn run_ok_raw(command: Command) -> Result<std::process::Output> {
eprintln!("run_ok_raw: {}", command);
let command = command.to_expr().stdout_capture().stderr_capture();
let output = command.run()?;
log_output(&output);
assert!(output.status.success());
Ok(output)
}
// Returns stderr, a non zero status must be returned
pub fn run_fail(command: Command) -> Result<String> {
eprintln!("run_fail: {}", command);
let command = command.to_expr().stdout_capture().stderr_capture();
let output = command.unchecked().run()?;
log_output(&output);
assert!(!output.status.success());
let stderr = std::str::from_utf8(&output.stderr[..])
.unwrap()
.trim_end_matches(|c| c == '\n' || c == '\r')
.to_string();
Ok(stderr)
}
// Returns the entire output, a non zero status must be returned
pub fn run_fail_raw(command: Command) -> Result<std::process::Output> {
eprintln!("run_fail_raw: {}", command);
let command = command.to_expr().stdout_capture().stderr_capture();
let output = command.unchecked().run()?;
log_output(&output);
assert!(!output.status.success());
Ok(output)
}
//------------------------------------------

52
tests/common/program.rs Normal file
View File

@ -0,0 +1,52 @@
use anyhow::Result;
use std::path::PathBuf;
pub use crate::common::process::*;
use crate::common::test_dir::TestDir;
//------------------------------------------
pub enum ArgType {
InputArg,
IoOptions,
}
pub trait Program<'a> {
fn name() -> &'a str;
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>;
fn usage() -> &'a str;
fn arg_type() -> ArgType;
// error messages
fn bad_option_hint(option: &str) -> String;
}
pub trait InputProgram<'a>: Program<'a> {
fn mk_valid_input(td: &mut TestDir) -> Result<PathBuf>;
// error messages
fn missing_input_arg() -> &'a str;
fn file_not_found() -> &'a str;
fn corrupted_input() -> &'a str;
}
pub trait MetadataReader<'a>: InputProgram<'a> {}
pub trait OutputProgram<'a>: InputProgram<'a> {
// error messages
fn missing_output_arg() -> &'a str;
}
// programs that write existed files
pub trait MetadataWriter<'a>: OutputProgram<'a> {
// error messages
fn file_not_found() -> &'a str;
}
// programs that create output files (O_CREAT)
pub trait MetadataCreator<'a>: OutputProgram<'a> {}
//------------------------------------------

215
tests/common/target.rs Normal file
View File

@ -0,0 +1,215 @@
use std::ffi::OsString;
use std::path::PathBuf;
use crate::common::process::*;
//------------------------------------------
pub fn cpp_cmd<S, I>(cmd: S, args: I) -> Command
where
S: Into<OsString>,
I: IntoIterator,
I::Item: Into<OsString>,
{
let mut bin = PathBuf::from("bin");
bin.push(Into::<OsString>::into(cmd));
let mut args_ = Vec::new();
for a in args {
args_.push(Into::<OsString>::into(a));
}
Command::new(Into::<OsString>::into(bin.as_path()), args_)
}
pub fn rust_cmd<S, I>(cmd: S, args: I) -> Command
where
S: Into<OsString>,
I: IntoIterator,
I::Item: Into<OsString>,
{
const RUST_PATH: &str = env!(concat!("CARGO_BIN_EXE_", "pdata_tools"));
let mut all_args = vec![Into::<OsString>::into(cmd)];
for a in args {
all_args.push(Into::<OsString>::into(a));
}
Command::new(Into::<OsString>::into(RUST_PATH), all_args)
}
pub fn thin_check_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("thin_check", args)
}
pub fn thin_rmap_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
cpp_cmd("thin_rmap", args)
}
pub fn thin_generate_metadata_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
cpp_cmd("thin_generate_metadata", args)
}
pub fn thin_generate_mappings_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
cpp_cmd("thin_generate_mappings", args)
}
pub fn thin_generate_damage_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
cpp_cmd("thin_generate_damage", args)
}
pub fn thin_restore_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
// rust_cmd("thin_restore", args)
cpp_cmd("thin_restore", args)
}
pub fn thin_repair_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("thin_restore", args)
}
pub fn thin_dump_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("thin_dump", args)
}
pub fn thin_delta_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
cpp_cmd("thin_delta", args)
}
pub fn thin_metadata_pack_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("thin_metadata_pack", args)
}
pub fn thin_metadata_unpack_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("thin_metadata_unpack", args)
}
pub fn cache_check_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("cache_check", args)
}
pub fn cache_dump_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("cache_dump", args)
}
pub fn cache_metadata_size_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("cache_metadata_size", args)
}
pub fn cache_restore_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("cache_restore", args)
}
pub fn cache_repair_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("cache_repair", args)
}
pub fn era_check_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("era_check", args)
}
pub fn era_dump_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("era_dump", args)
}
pub fn era_restore_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("era_restore", args)
}
pub fn era_repair_cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<OsString>,
{
rust_cmd("era_repair", args)
}
//------------------------------------------
pub mod msg {
pub const FILE_NOT_FOUND: &str = "Couldn't find input file";
pub const MISSING_INPUT_ARG: &str = "The following required arguments were not provided"; // TODO: be specific
pub const MISSING_OUTPUT_ARG: &str = "The following required arguments were not provided"; // TODO: be specific
pub const BAD_SUPERBLOCK: &str = "bad checksum in superblock";
pub fn bad_option_hint(option: &str) -> String {
format!("Found argument '{}' which wasn't expected", option)
}
}
//------------------------------------------

67
tests/common/test_dir.rs Normal file
View File

@ -0,0 +1,67 @@
use anyhow::{anyhow, Result};
use rand::prelude::*;
use std::fs;
use std::path::PathBuf;
//---------------------------------------
pub struct TestDir {
dir: PathBuf,
files: Vec<PathBuf>,
clean_up: bool,
file_count: usize,
}
fn mk_dir(prefix: &str) -> Result<PathBuf> {
for _n in 0..100 {
let mut p = PathBuf::new();
let nr = rand::thread_rng().gen_range(1000000..9999999);
p.push(format!("./{}_{}", prefix, nr));
if let Ok(()) = fs::create_dir(&p) {
return Ok(p);
}
}
Err(anyhow!("Couldn't create test directory"))
}
impl TestDir {
pub fn new() -> Result<TestDir> {
let dir = mk_dir("test_fixture")?;
Ok(TestDir {
dir,
files: Vec::new(),
clean_up: true,
file_count: 0,
})
}
pub fn dont_clean_up(&mut self) {
self.clean_up = false;
}
pub fn mk_path(&mut self, file: &str) -> PathBuf {
let mut p = PathBuf::new();
p.push(&self.dir);
p.push(PathBuf::from(format!("{:02}_{}", self.file_count, file)));
self.files.push(p.clone());
self.file_count += 1;
p
}
}
impl Drop for TestDir {
fn drop(&mut self) {
if self.clean_up {
while let Some(f) = self.files.pop() {
// It's not guaranteed that the path generated was actually created.
let _ignore = fs::remove_file(f);
}
fs::remove_dir(&self.dir).expect("couldn't remove test directory");
} else {
eprintln!("leaving test directory: {:?}", self.dir);
}
}
}
//---------------------------------------

126
tests/common/thin.rs Normal file
View File

@ -0,0 +1,126 @@
use anyhow::Result;
use std::path::PathBuf;
use thinp::file_utils;
use thinp::io_engine::*;
use crate::args;
use crate::common::fixture::*;
use crate::common::process::*;
use crate::common::target::*;
use crate::common::test_dir::TestDir;
use crate::common::thin_xml_generator::{write_xml, SingleThinS};
//-----------------------------------------------
pub fn mk_valid_xml(td: &mut TestDir) -> Result<PathBuf> {
let xml = td.mk_path("meta.xml");
let mut gen = SingleThinS::new(0, 1024, 2048, 2048);
write_xml(&xml, &mut gen)?;
Ok(xml)
}
pub fn mk_valid_md(td: &mut TestDir) -> Result<PathBuf> {
let xml = td.mk_path("meta.xml");
let md = td.mk_path("meta.bin");
let mut gen = SingleThinS::new(0, 1024, 20480, 20480);
write_xml(&xml, &mut gen)?;
let _file = file_utils::create_sized_file(&md, 4096 * 4096);
run_ok(thin_restore_cmd(args!["-i", &xml, "-o", &md]))?;
Ok(md)
}
//-----------------------------------------------
// FIXME: replace mk_valid_md with this?
pub fn prep_metadata(td: &mut TestDir) -> Result<PathBuf> {
let md = mk_zeroed_md(td)?;
let args = args!["-o", &md, "--format", "--nr-data-blocks", "102400"];
run_ok(thin_generate_metadata_cmd(args))?;
// Create a 2GB device
let args = args!["-o", &md, "--create-thin", "1"];
run_ok(thin_generate_metadata_cmd(args))?;
let args = args![
"-o",
&md,
"--dev-id",
"1",
"--size",
"2097152",
"--rw=randwrite",
"--seq-nr=16"
];
run_ok(thin_generate_mappings_cmd(args))?;
// Take a few snapshots.
let mut snap_id = 2;
for _i in 0..10 {
// take a snapshot
let snap_id_str = snap_id.to_string();
let args = args!["-o", &md, "--create-snap", &snap_id_str, "--origin", "1"];
run_ok(thin_generate_metadata_cmd(args))?;
// partially overwrite the origin (64MB)
let args = args![
"-o",
&md,
"--dev-id",
"1",
"--size",
"2097152",
"--io-size",
"131072",
"--rw=randwrite",
"--seq-nr=16"
];
run_ok(thin_generate_mappings_cmd(args))?;
snap_id += 1;
}
Ok(md)
}
pub fn set_needs_check(md: &PathBuf) -> Result<()> {
let args = args!["-o", &md, "--set-needs-check"];
run_ok(thin_generate_metadata_cmd(args))?;
Ok(())
}
pub fn generate_metadata_leaks(
md: &PathBuf,
nr_blocks: u64,
expected: u32,
actual: u32,
) -> Result<()> {
let nr_blocks_str = nr_blocks.to_string();
let expected_str = expected.to_string();
let actual_str = actual.to_string();
let args = args![
"-o",
&md,
"--create-metadata-leaks",
"--nr-blocks",
&nr_blocks_str,
"--expected",
&expected_str,
"--actual",
&actual_str
];
run_ok(thin_generate_damage_cmd(args))?;
Ok(())
}
pub fn get_needs_check(md: &PathBuf) -> Result<bool> {
use thinp::thin::superblock::*;
let engine = SyncIoEngine::new(md, 1, false)?;
let sb = read_superblock(&engine, SUPERBLOCK_LOCATION)?;
Ok(sb.flags.needs_check)
}
//-----------------------------------------------

View File

@ -0,0 +1,538 @@
use anyhow::{anyhow, Result};
use rand::prelude::*;
use std::collections::VecDeque;
use std::fs::OpenOptions;
use std::ops::Range;
use std::path::Path;
use thinp::thin::ir::{self, MetadataVisitor};
use thinp::thin::xml;
//------------------------------------------
pub trait XmlGen {
fn generate_xml(&mut self, v: &mut dyn MetadataVisitor) -> Result<()>;
}
pub fn write_xml(path: &Path, g: &mut dyn XmlGen) -> Result<()> {
let xml_out = OpenOptions::new()
.read(false)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
let mut w = xml::XmlWriter::new(xml_out);
g.generate_xml(&mut w)
}
fn common_sb(nr_blocks: u64) -> ir::Superblock {
ir::Superblock {
uuid: "".to_string(),
time: 0,
transaction: 1,
flags: None,
version: None,
data_block_size: 128,
nr_data_blocks: nr_blocks,
metadata_snap: None,
}
}
//------------------------------------------
pub struct EmptyPoolS {}
impl XmlGen for EmptyPoolS {
fn generate_xml(&mut self, v: &mut dyn MetadataVisitor) -> Result<()> {
v.superblock_b(&common_sb(1024))?;
v.superblock_e()?;
Ok(())
}
}
//------------------------------------------
pub struct SingleThinS {
pub offset: u64,
pub len: u64,
pub old_nr_data_blocks: u64,
pub new_nr_data_blocks: u64,
}
impl SingleThinS {
pub fn new(offset: u64, len: u64, old_nr_data_blocks: u64, new_nr_data_blocks: u64) -> Self {
SingleThinS {
offset,
len,
old_nr_data_blocks,
new_nr_data_blocks,
}
}
}
impl XmlGen for SingleThinS {
fn generate_xml(&mut self, v: &mut dyn MetadataVisitor) -> Result<()> {
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
v.device_b(&ir::Device {
dev_id: 0,
mapped_blocks: self.len,
transaction: 0,
creation_time: 0,
snap_time: 0,
})?;
v.map(&ir::Map {
thin_begin: 0,
data_begin: self.offset,
time: 0,
len: self.len,
})?;
v.device_e()?;
v.superblock_e()?;
Ok(())
}
}
//------------------------------------------
pub struct FragmentedS {
pub nr_thins: u32,
pub thin_size: u64,
pub old_nr_data_blocks: u64,
pub new_nr_data_blocks: u64,
}
impl FragmentedS {
pub fn new(nr_thins: u32, thin_size: u64) -> Self {
let old_size = (nr_thins as u64) * thin_size;
FragmentedS {
nr_thins,
thin_size,
old_nr_data_blocks: (nr_thins as u64) * thin_size,
new_nr_data_blocks: old_size * 3 / 4,
}
}
}
#[derive(Clone)]
struct ThinRun {
thin_id: u32,
thin_begin: u64,
len: u64,
}
#[derive(Clone, Debug, Copy)]
struct MappedRun {
thin_id: u32,
thin_begin: u64,
data_begin: u64,
len: u64,
}
fn mk_runs(thin_id: u32, total_len: u64, run_len: std::ops::Range<u64>) -> Vec<ThinRun> {
let mut runs = Vec::new();
let mut b = 0u64;
while b < total_len {
let len = u64::min(
total_len - b,
thread_rng().gen_range(run_len.start..run_len.end),
);
runs.push(ThinRun {
thin_id,
thin_begin: b,
len,
});
b += len;
}
runs
}
impl XmlGen for FragmentedS {
fn generate_xml(&mut self, v: &mut dyn MetadataVisitor) -> Result<()> {
// Allocate each thin fully, in runs between 1 and 16.
let mut runs = Vec::new();
for thin in 0..self.nr_thins {
runs.append(&mut mk_runs(thin, self.thin_size, 1..17));
}
// Shuffle
runs.shuffle(&mut rand::thread_rng());
// map across the data
let mut maps = Vec::new();
let mut b = 0;
for r in &runs {
maps.push(MappedRun {
thin_id: r.thin_id,
thin_begin: r.thin_begin,
data_begin: b,
len: r.len,
});
b += r.len;
}
// drop half the mappings, which leaves us free runs
let mut dropped = Vec::new();
for (i, m) in maps.iter().enumerate() {
if i % 2 == 0 {
dropped.push(*m);
}
}
// Unshuffle. This isn't strictly necc. but makes the xml
// more readable.
use std::cmp::Ordering;
maps.sort_by(|&l, &r| match l.thin_id.cmp(&r.thin_id) {
Ordering::Equal => l.thin_begin.cmp(&r.thin_begin),
o => o,
});
// write the xml
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
for thin in 0..self.nr_thins {
v.device_b(&ir::Device {
dev_id: thin,
mapped_blocks: self.thin_size,
transaction: 0,
creation_time: 0,
snap_time: 0,
})?;
for m in &dropped {
if m.thin_id != thin {
continue;
}
v.map(&ir::Map {
thin_begin: m.thin_begin,
data_begin: m.data_begin,
time: 0,
len: m.len,
})?;
}
v.device_e()?;
}
v.superblock_e()?;
Ok(())
}
}
//------------------------------------------
struct Allocator {
runs: VecDeque<Range<u64>>,
}
impl Allocator {
fn new_shuffled(total_len: u64, run_len: Range<u64>) -> Allocator {
let mut runs = Vec::new();
let mut b = 0u64;
while b < total_len {
let len = u64::min(
total_len - b,
thread_rng().gen_range(run_len.start..run_len.end),
);
runs.push(b..(b + len));
b += len;
}
runs.shuffle(&mut thread_rng());
let runs: VecDeque<Range<u64>> = runs.iter().cloned().collect();
Allocator { runs }
}
#[allow(dead_code)]
fn is_empty(&self) -> bool {
self.runs.is_empty()
}
fn alloc(&mut self, len: u64) -> Result<Vec<Range<u64>>> {
let mut len = len;
let mut runs = Vec::new();
while len > 0 {
let r = self.runs.pop_front();
if r.is_none() {
return Err(anyhow!("could not allocate; out of space"));
}
let r = r.unwrap();
let rlen = r.end - r.start;
if len < rlen {
runs.push(r.start..(r.start + len));
// We need to push something back.
self.runs.push_front((r.start + len)..r.end);
len = 0;
} else {
runs.push(r.start..r.end);
len -= rlen;
}
}
Ok(runs)
}
}
// Having explicitly unmapped regions makes it easier to
// apply snapshots.
#[derive(Clone)]
enum Run {
Mapped { data_begin: u64, len: u64 },
UnMapped { len: u64 },
}
impl Run {
#[allow(dead_code)]
fn len(&self) -> u64 {
match self {
Run::Mapped {
data_begin: _data_begin,
len,
} => *len,
Run::UnMapped { len } => *len,
}
}
fn split(&self, n: u64) -> (Option<Run>, Option<Run>) {
if n == 0 {
(None, Some(self.clone()))
} else if self.len() <= n {
(Some(self.clone()), None)
} else {
match self {
Run::Mapped { data_begin, len } => (
Some(Run::Mapped {
data_begin: *data_begin,
len: n,
}),
Some(Run::Mapped {
data_begin: data_begin + n,
len: len - n,
}),
),
Run::UnMapped { len } => (
Some(Run::UnMapped { len: n }),
Some(Run::UnMapped { len: len - n }),
),
}
}
}
}
#[derive(Clone)]
struct ThinDev {
thin_id: u32,
dev_size: u64,
runs: Vec<Run>,
}
impl ThinDev {
fn emit(&self, v: &mut dyn MetadataVisitor) -> Result<()> {
v.device_b(&ir::Device {
dev_id: self.thin_id,
mapped_blocks: self.dev_size,
transaction: 0,
creation_time: 0,
snap_time: 0,
})?;
let mut b = 0;
for r in &self.runs {
match r {
Run::Mapped { data_begin, len } => {
v.map(&ir::Map {
thin_begin: b,
data_begin: *data_begin,
time: 0,
len: *len,
})?;
b += len;
}
Run::UnMapped { len } => {
b += len;
}
}
}
v.device_e()?;
Ok(())
}
}
#[derive(Clone)]
enum SnapRunType {
Same,
Diff,
Hole,
}
#[derive(Clone)]
struct SnapRun(SnapRunType, u64);
fn mk_origin(thin_id: u32, total_len: u64, allocator: &mut Allocator) -> Result<ThinDev> {
let mut runs = Vec::new();
let mut b = 0;
while b < total_len {
let len = u64::min(thread_rng().gen_range(16..64), total_len - b);
match thread_rng().gen_range(0..2) {
0 => {
for data in allocator.alloc(len)? {
assert!(data.end >= data.start);
runs.push(Run::Mapped {
data_begin: data.start,
len: data.end - data.start,
});
}
}
1 => {
runs.push(Run::UnMapped { len });
}
_ => {
return Err(anyhow!("bad value returned from rng"));
}
};
b += len;
}
Ok(ThinDev {
thin_id,
dev_size: total_len,
runs,
})
}
fn mk_snap_mapping(
total_len: u64,
run_len: Range<u64>,
same_percent: usize,
diff_percent: usize,
) -> Vec<SnapRun> {
let mut runs = Vec::new();
let mut b = 0u64;
while b < total_len {
let len = u64::min(
total_len - b,
thread_rng().gen_range(run_len.start..run_len.end),
);
let n = thread_rng().gen_range(0..100);
if n < same_percent {
runs.push(SnapRun(SnapRunType::Same, len));
} else if n < diff_percent {
runs.push(SnapRun(SnapRunType::Diff, len));
} else {
runs.push(SnapRun(SnapRunType::Hole, len));
}
b += len;
}
runs
}
fn split_runs(mut n: u64, runs: &[Run]) -> (Vec<Run>, Vec<Run>) {
let mut before = Vec::new();
let mut after = Vec::new();
for r in runs {
match r.split(n) {
(Some(lhs), None) => {
before.push(lhs);
}
(Some(lhs), Some(rhs)) => {
before.push(lhs);
after.push(rhs);
}
(None, Some(rhs)) => {
after.push(rhs);
}
(None, None) => {}
}
n -= r.len();
}
(before, after)
}
fn apply_snap_runs(
origin: &[Run],
snap: &[SnapRun],
allocator: &mut Allocator,
) -> Result<Vec<Run>> {
let mut origin = origin.to_owned();
let mut runs = Vec::new();
for SnapRun(st, slen) in snap {
let (os, rest) = split_runs(*slen, &origin);
match st {
SnapRunType::Same => {
for o in os {
runs.push(o);
}
}
SnapRunType::Diff => {
for data in allocator.alloc(*slen)? {
runs.push(Run::Mapped {
data_begin: data.start,
len: data.end - data.start,
});
}
}
SnapRunType::Hole => {
runs.push(Run::UnMapped { len: *slen });
}
}
origin = rest;
}
Ok(runs)
}
// Snapshots share mappings, not neccessarily the entire ranges.
pub struct SnapS {
pub len: u64,
pub nr_snaps: u32,
// Snaps will differ from the origin by this percentage
pub percent_change: usize,
pub old_nr_data_blocks: u64,
pub new_nr_data_blocks: u64,
}
impl SnapS {
pub fn new(len: u64, nr_snaps: u32, percent_change: usize) -> Self {
let delta = len * (nr_snaps as u64) * (percent_change as u64) / 100;
let old_nr_data_blocks = len + 3 * delta;
let new_nr_data_blocks = len + 2 * delta;
SnapS {
len,
nr_snaps,
percent_change,
old_nr_data_blocks,
new_nr_data_blocks,
}
}
}
impl XmlGen for SnapS {
fn generate_xml(&mut self, v: &mut dyn MetadataVisitor) -> Result<()> {
let mut allocator = Allocator::new_shuffled(self.old_nr_data_blocks, 64..512);
let origin = mk_origin(0, self.len, &mut allocator)?;
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
origin.emit(v)?;
v.superblock_e()?;
Ok(())
}
}
//------------------------------------------

118
tests/era_check.rs Normal file
View File

@ -0,0 +1,118 @@
use anyhow::Result;
mod common;
use common::cache::*;
use common::common_args::*;
use common::fixture::*;
use common::input_arg::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
//------------------------------------------
const USAGE: &str = "era_check 0.9.0
USAGE:
era_check [FLAGS] <INPUT>
FLAGS:
--ignore-non-fatal-errors Only return a non-zero exit code if a fatal error is found.
-q, --quiet Suppress output messages, return only exit code.
--super-block-only Only check the superblock.
-h, --help Prints help information
-V, --version Prints version information
ARGS:
<INPUT> Specify the input device to check";
//------------------------------------------
struct EraCheck;
impl<'a> Program<'a> for EraCheck {
fn name() -> &'a str {
"era_check"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
era_check_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::InputArg
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for EraCheck {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_md(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
msg::BAD_SUPERBLOCK
}
}
impl<'a> MetadataReader<'a> for EraCheck {}
//------------------------------------------
test_accepts_help!(EraCheck);
test_accepts_version!(EraCheck);
test_rejects_bad_option!(EraCheck);
test_missing_input_arg!(EraCheck);
test_input_file_not_found!(EraCheck);
test_input_cannot_be_a_directory!(EraCheck);
test_unreadable_input_file!(EraCheck);
test_help_message_for_tiny_input_file!(EraCheck);
test_spot_xml_data!(EraCheck);
test_corrupted_input_data!(EraCheck);
//------------------------------------------
#[test]
fn failing_q() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?;
let output = run_fail_raw(era_check_cmd(args!["-q", &md]))?;
assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0);
Ok(())
}
#[test]
fn failing_quiet() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?;
let output = run_fail_raw(era_check_cmd(args!["--quiet", &md]))?;
assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0);
Ok(())
}
//------------------------------------------

123
tests/era_dump.rs Normal file
View File

@ -0,0 +1,123 @@
use anyhow::Result;
use std::fs::OpenOptions;
use std::io::Write;
mod common;
use common::common_args::*;
use common::era::*;
use common::fixture::*;
use common::input_arg::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
//------------------------------------------
const USAGE: &str = "era_dump 0.9.0
Dump the era metadata to stdout in XML format
USAGE:
era_dump [FLAGS] [OPTIONS] <INPUT>
FLAGS:
--logical Fold any unprocessed write sets into the final era array
-r, --repair Repair the metadata whilst dumping it
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-o, --output <FILE> Specify the output file rather than stdout
ARGS:
<INPUT> Specify the input device to dump";
//------------------------------------------
struct EraDump;
impl<'a> Program<'a> for EraDump {
fn name() -> &'a str {
"era_dump"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
era_dump_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::InputArg
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for EraDump {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_md(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
msg::BAD_SUPERBLOCK
}
}
impl<'a> MetadataReader<'a> for EraDump {}
//------------------------------------------
test_accepts_help!(EraDump);
test_accepts_version!(EraDump);
test_rejects_bad_option!(EraDump);
test_missing_input_arg!(EraDump);
test_input_file_not_found!(EraDump);
test_input_cannot_be_a_directory!(EraDump);
test_unreadable_input_file!(EraDump);
test_tiny_input_file!(EraDump);
//------------------------------------------
// TODO: share with thin_dump
#[test]
fn dump_restore_cycle() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let output = run_ok_raw(era_dump_cmd(args![&md]))?;
let xml = td.mk_path("meta.xml");
let mut file = OpenOptions::new()
.read(false)
.write(true)
.create(true)
.open(&xml)?;
file.write_all(&output.stdout[0..])?;
drop(file);
let md2 = mk_zeroed_md(&mut td)?;
run_ok(era_restore_cmd(args!["-i", &xml, "-o", &md2]))?;
let output2 = run_ok_raw(era_dump_cmd(args![&md2]))?;
assert_eq!(output.stdout, output2.stdout);
Ok(())
}

143
tests/era_restore.rs Normal file
View File

@ -0,0 +1,143 @@
use anyhow::Result;
mod common;
use common::common_args::*;
use common::era::*;
use common::fixture::*;
use common::input_arg::*;
use common::output_option::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
//------------------------------------------
const USAGE: &str = "era_restore 0.9.0
Convert XML format metadata to binary.
USAGE:
era_restore [FLAGS] --input <FILE> --output <FILE>
FLAGS:
-q, --quiet Suppress output messages, return only exit code.
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-i, --input <FILE> Specify the input xml
-o, --output <FILE> Specify the output device to check";
//------------------------------------------
struct EraRestore;
impl<'a> Program<'a> for EraRestore {
fn name() -> &'a str {
"era_restore"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
era_restore_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::IoOptions
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for EraRestore {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_xml(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
"" // we don't intent to verify error messages of XML parsing
}
}
impl<'a> OutputProgram<'a> for EraRestore {
fn missing_output_arg() -> &'a str {
msg::MISSING_OUTPUT_ARG
}
}
impl<'a> MetadataWriter<'a> for EraRestore {
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
}
//-----------------------------------------
test_accepts_help!(EraRestore);
test_accepts_version!(EraRestore);
test_missing_input_option!(EraRestore);
test_input_file_not_found!(EraRestore);
test_corrupted_input_data!(EraRestore);
test_missing_output_option!(EraRestore);
test_tiny_output_file!(EraRestore);
test_unwritable_output_file!(EraRestore);
//-----------------------------------------
// TODO: share with thin_restore, era_restore
fn quiet_flag(flag: &str) -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&mut td)?;
let output = run_ok_raw(era_restore_cmd(args!["-i", &xml, "-o", &md, flag]))?;
assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0);
Ok(())
}
#[test]
fn accepts_q() -> Result<()> {
quiet_flag("-q")
}
#[test]
fn accepts_quiet() -> Result<()> {
quiet_flag("--quiet")
}
//-----------------------------------------
#[test]
fn successfully_restores() -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&mut td)?;
run_ok(era_restore_cmd(args!["-i", &xml, "-o", &md]))?;
Ok(())
}
//-----------------------------------------

365
tests/thin_check.rs Normal file
View File

@ -0,0 +1,365 @@
use anyhow::Result;
mod common;
use common::common_args::*;
use common::fixture::*;
use common::input_arg::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
use common::thin::*;
//------------------------------------------
const USAGE: &str = "thin_check 0.9.0
Validates thin provisioning metadata on a device or file.
USAGE:
thin_check [FLAGS] [OPTIONS] <INPUT>
FLAGS:
--auto-repair Auto repair trivial issues.
--clear-needs-check-flag Clears the 'needs_check' flag in the superblock
--ignore-non-fatal-errors Only return a non-zero exit code if a fatal error is found.
-m, --metadata-snapshot Check the metadata snapshot on a live pool
-q, --quiet Suppress output messages, return only exit code.
--super-block-only Only check the superblock.
--skip-mappings Don't check the mapping tree
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--override-mapping-root <OVERRIDE_MAPPING_ROOT> Specify a mapping root to use
ARGS:
<INPUT> Specify the input device to check";
//-----------------------------------------
struct ThinCheck;
impl<'a> Program<'a> for ThinCheck {
fn name() -> &'a str {
"thin_check"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
thin_check_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::InputArg
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for ThinCheck {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_md(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
msg::BAD_SUPERBLOCK
}
}
impl<'a> MetadataReader<'_> for ThinCheck {}
//------------------------------------------
test_accepts_help!(ThinCheck);
test_accepts_version!(ThinCheck);
test_rejects_bad_option!(ThinCheck);
test_missing_input_arg!(ThinCheck);
test_input_file_not_found!(ThinCheck);
test_input_cannot_be_a_directory!(ThinCheck);
test_unreadable_input_file!(ThinCheck);
test_help_message_for_tiny_input_file!(ThinCheck);
test_spot_xml_data!(ThinCheck);
test_corrupted_input_data!(ThinCheck);
//------------------------------------------
// test exclusive flags
fn accepts_flag(flag: &str) -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
run_ok(thin_check_cmd(args![flag, &md]))?;
Ok(())
}
#[test]
fn accepts_superblock_only() -> Result<()> {
accepts_flag("--super-block-only")
}
#[test]
fn accepts_skip_mappings() -> Result<()> {
accepts_flag("--skip-mappings")
}
#[test]
fn accepts_ignore_non_fatal_errors() -> Result<()> {
accepts_flag("--ignore-non-fatal-errors")
}
#[test]
fn accepts_clear_needs_check_flag() -> Result<()> {
accepts_flag("--clear-needs-check-flag")
}
#[test]
fn accepts_auto_repair() -> Result<()> {
accepts_flag("--auto-repair")
}
//------------------------------------------
// test the --quiet flag
#[test]
fn accepts_quiet() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let output = run_ok_raw(thin_check_cmd(args!["--quiet", &md]))?;
if !output.stdout.is_empty() {
eprintln!("stdout: {:?}", &std::str::from_utf8(&output.stdout));
}
if !output.stderr.is_empty() {
eprintln!("stderr: {:?}", &std::str::from_utf8(&output.stderr));
}
assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0);
Ok(())
}
//------------------------------------------
// test superblock-block-only
#[test]
fn detects_corrupt_superblock_with_superblock_only() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?;
let _stderr = run_fail(thin_check_cmd(args!["--super-block-only", &md]))?;
Ok(())
}
//------------------------------------------
// test info outputs
#[test]
fn prints_info_fields() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let stdout = run_ok(thin_check_cmd(args![&md]))?;
eprintln!("info: {:?}", stdout);
assert!(stdout.contains("TRANSACTION_ID="));
assert!(stdout.contains("METADATA_FREE_BLOCKS="));
Ok(())
}
//------------------------------------------
// test compatibility between options
#[test]
fn auto_repair_incompatible_opts() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
run_fail(thin_check_cmd(args!["--auto-repair", "-m", &md]))?;
run_fail(thin_check_cmd(args![
"--auto-repair",
"--override-mapping-root",
"123",
&md
]))?;
run_fail(thin_check_cmd(args![
"--auto-repair",
"--super-block-only",
&md
]))?;
run_fail(thin_check_cmd(args![
"--auto-repair",
"--skip-mappings",
&md
]))?;
run_fail(thin_check_cmd(args![
"--auto-repair",
"--ignore-non-fatal-errors",
&md
]))?;
Ok(())
}
#[test]
fn clear_needs_check_incompatible_opts() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
run_fail(thin_check_cmd(args!["--clear-needs-check-flag", "-m", &md]))?;
run_fail(thin_check_cmd(args![
"--clear-needs-check-flag",
"--override-mapping-root",
"123",
&md
]))?;
run_fail(thin_check_cmd(args![
"--clear-needs-check-flag",
"--super-block-only",
&md
]))?;
run_fail(thin_check_cmd(args![
"--clear-needs-check-flag",
"--ignore-non-fatal-errors",
&md
]))?;
Ok(())
}
//------------------------------------------
// test clear-needs-check
#[test]
fn clear_needs_check() -> Result<()> {
let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?;
set_needs_check(&md)?;
assert!(get_needs_check(&md)?);
run_ok(thin_check_cmd(args!["--clear-needs-check-flag", &md]))?;
assert!(!get_needs_check(&md)?);
Ok(())
}
#[test]
fn no_clear_needs_check_if_error() -> Result<()> {
let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?;
set_needs_check(&md)?;
generate_metadata_leaks(&md, 1, 0, 1)?;
run_fail(thin_check_cmd(args!["--clear-needs-check-flag", &md]))?;
assert!(get_needs_check(&md)?);
Ok(())
}
#[test]
fn clear_needs_check_if_skip_mappings() -> Result<()> {
let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?;
set_needs_check(&md)?;
generate_metadata_leaks(&md, 1, 0, 1)?;
assert!(get_needs_check(&md)?);
run_ok(thin_check_cmd(args![
"--clear-needs-check-flag",
"--skip-mappings",
&md
]))?;
assert!(!get_needs_check(&md)?);
Ok(())
}
//------------------------------------------
// test ignore-non-fatal-errors
#[test]
fn metadata_leaks_are_non_fatal() -> Result<()> {
let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?;
generate_metadata_leaks(&md, 1, 0, 1)?;
run_fail(thin_check_cmd(args![&md]))?;
run_ok(thin_check_cmd(args!["--ignore-non-fatal-errors", &md]))?;
Ok(())
}
#[test]
fn fatal_errors_cant_be_ignored() -> Result<()> {
let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?;
generate_metadata_leaks(&md, 1, 1, 0)?;
ensure_untouched(&md, || {
run_fail(thin_check_cmd(args!["--ignore-non-fatal-errors", &md]))?;
Ok(())
})
}
//------------------------------------------
// test auto-repair
#[test]
fn auto_repair() -> Result<()> {
let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?;
eprintln!("here 0");
// auto-repair should have no effect on good metadata.
ensure_untouched(&md, || {
// run_ok(thin_check_cmd(args!["--auto-repair", &md]))?;
run_ok(thin_check_cmd(args![&md]))?;
Ok(())
})?;
eprintln!("here 0.5");
generate_metadata_leaks(&md, 16, 0, 1)?;
eprintln!("here 1");
run_fail(thin_check_cmd(args![&md]))?;
eprintln!("here 2");
run_ok(thin_check_cmd(args!["--auto-repair", &md]))?;
eprintln!("here 3");
run_ok(thin_check_cmd(args![&md]))?;
Ok(())
}
#[test]
fn auto_repair_has_limits() -> Result<()> {
let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?;
generate_metadata_leaks(&md, 16, 1, 0)?;
ensure_untouched(&md, || {
run_fail(thin_check_cmd(args!["--auto-repair", &md]))?;
Ok(())
})?;
Ok(())
}
#[test]
fn auto_repair_clears_needs_check() -> Result<()> {
let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?;
set_needs_check(&md)?;
run_ok(thin_check_cmd(args!["--auto-repair", &md]))?;
assert!(!get_needs_check(&md)?);
Ok(())
}
//------------------------------------------

87
tests/thin_delta.rs Normal file
View File

@ -0,0 +1,87 @@
use anyhow::Result;
mod common;
use common::common_args::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
use common::thin::*;
//------------------------------------------
const USAGE: &str = "Usage: thin_delta [options] <device or file>\n\
Options:\n \
{--thin1, --snap1, --root1}\n \
{--thin2, --snap2, --root2}\n \
{-m, --metadata-snap} [block#]\n \
{--verbose}\n \
{-h|--help}\n \
{-V|--version}";
//------------------------------------------
struct ThinDelta;
impl<'a> Program<'a> for ThinDelta {
fn name() -> &'a str {
"thin_delta"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
cpp_cmd("thin_delta", args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::InputArg
}
fn bad_option_hint(option: &str) -> String {
format!("unrecognized option '{}'", option)
}
}
//------------------------------------------
test_accepts_help!(ThinDelta);
test_accepts_version!(ThinDelta);
test_rejects_bad_option!(ThinDelta);
//------------------------------------------
#[test]
fn snap1_unspecified() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let stderr = run_fail(thin_delta_cmd(args!["--snap2", "45", &md]))?;
assert!(stderr.contains("--snap1 or --root1 not specified"));
Ok(())
}
#[test]
fn snap2_unspecified() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let stderr = run_fail(thin_delta_cmd(args!["--snap1", "45", &md]))?;
assert!(stderr.contains("--snap2 or --root2 not specified"));
Ok(())
}
#[test]
fn dev_unspecified() -> Result<()> {
let stderr = run_fail(thin_delta_cmd(args!["--snap1", "45", "--snap2", "46"]))?;
// TODO: replace with msg::MISSING_INPUT_ARG once the rust version is ready
assert!(stderr.contains("No input file provided"));
Ok(())
}
//------------------------------------------

244
tests/thin_dump.rs Normal file
View File

@ -0,0 +1,244 @@
use anyhow::Result;
use std::fs::OpenOptions;
use std::io::Write;
mod common;
use common::common_args::*;
use common::fixture::*;
use common::input_arg::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
use common::thin::*;
//------------------------------------------
const USAGE: &str = "thin_dump 0.9.0
Dump thin-provisioning metadata to stdout in XML format
USAGE:
thin_dump [FLAGS] [OPTIONS] <INPUT>
FLAGS:
-q, --quiet Suppress output messages, return only exit code.
-r, --repair Repair the metadata whilst dumping it
--skip-mappings Do not dump the mappings
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--data-block-size <SECTORS> Provide the data block size for repairing
-m, --metadata-snapshot <METADATA_SNAPSHOT> Access the metadata snapshot on a live pool
--nr-data-blocks <NUM> Override the number of data blocks if needed
-o, --output <FILE> Specify the output file rather than stdout
--transaction-id <NUM> Override the transaction id if needed
ARGS:
<INPUT> Specify the input device to dump";
//-----------------------------------------
struct ThinDump;
impl<'a> Program<'a> for ThinDump {
fn name() -> &'a str {
"thin_dump"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
thin_dump_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::InputArg
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for ThinDump {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_md(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
msg::BAD_SUPERBLOCK
}
}
//------------------------------------------
test_accepts_help!(ThinDump);
test_accepts_version!(ThinDump);
test_rejects_bad_option!(ThinDump);
test_missing_input_arg!(ThinDump);
test_input_file_not_found!(ThinDump);
test_input_cannot_be_a_directory!(ThinDump);
test_unreadable_input_file!(ThinDump);
//------------------------------------------
// test dump & restore cycle
#[test]
fn dump_restore_cycle() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let output = run_ok_raw(thin_dump_cmd(args![&md]))?;
let xml = td.mk_path("meta.xml");
let mut file = OpenOptions::new()
.read(false)
.write(true)
.create(true)
.open(&xml)?;
file.write_all(&output.stdout[0..])?;
drop(file);
let md2 = mk_zeroed_md(&mut td)?;
run_ok(thin_restore_cmd(args!["-i", &xml, "-o", &md2]))?;
let output2 = run_ok_raw(thin_dump_cmd(args![&md2]))?;
assert_eq!(output.stdout, output2.stdout);
Ok(())
}
//------------------------------------------
// test no stderr with a normal dump
#[test]
#[cfg(not(feature = "rust_tests"))]
fn no_stderr() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let output = run_ok_raw(thin_dump_cmd(args![&md]))?;
assert_eq!(output.stderr.len(), 0);
Ok(())
}
//------------------------------------------
// test superblock overriding & repair
// TODO: share with thin_repair
fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let output = run_ok_raw(thin_dump_cmd(args![&md, flag, value]))?;
assert_eq!(output.stderr.len(), 0);
assert!(std::str::from_utf8(&output.stdout[0..])?.contains(pattern));
Ok(())
}
#[test]
fn override_transaction_id() -> Result<()> {
override_something("--transaction-id", "2345", "transaction=\"2345\"")
}
#[test]
fn override_data_block_size() -> Result<()> {
override_something("--data-block-size", "8192", "data_block_size=\"8192\"")
}
#[test]
fn override_nr_data_blocks() -> Result<()> {
override_something("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"")
}
// FIXME: duplicate with superblock_succeeds in thin_repair.rs
#[test]
fn repair_superblock() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
let before = run_ok_raw(thin_dump_cmd(args![&md]))?;
damage_superblock(&md)?;
let after = run_ok_raw(thin_dump_cmd(args![
"--repair",
"--transaction-id=1",
"--data-block-size=128",
"--nr-data-blocks=20480",
&md
]))?;
if !cfg!(feature = "rust_tests") {
assert_eq!(after.stderr.len(), 0);
}
assert_eq!(before.stdout, after.stdout);
Ok(())
}
//------------------------------------------
// test compatibility between options
// TODO: share with thin_repair
#[test]
fn missing_transaction_id() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
damage_superblock(&md)?;
let stderr = run_fail(thin_dump_cmd(args![
"--repair",
"--data-block-size=128",
"--nr-data-blocks=20480",
&md
]))?;
assert!(stderr.contains("transaction id"));
Ok(())
}
#[test]
fn missing_data_block_size() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
damage_superblock(&md)?;
let stderr = run_fail(thin_dump_cmd(args![
"--repair",
"--transaction-id=1",
"--nr-data-blocks=20480",
&md
]))?;
assert!(stderr.contains("data block size"));
Ok(())
}
#[test]
fn missing_nr_data_blocks() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
damage_superblock(&md)?;
let stderr = run_fail(thin_dump_cmd(args![
"--repair",
"--transaction-id=1",
"--data-block-size=128",
&md
]))?;
assert!(stderr.contains("nr data blocks"));
Ok(())
}
//------------------------------------------

View File

@ -0,0 +1,96 @@
use anyhow::Result;
mod common;
use common::common_args::*;
use common::input_arg::*;
use common::output_option::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
use common::thin::*;
//------------------------------------------
const USAGE: &str = concat!(
"thin_metadata_pack ",
include_str!("../VERSION"),
"Produces a compressed file of thin metadata. Only packs metadata blocks that are actually used.\n\
\n\
USAGE:\n \
thin_metadata_pack -i <DEV> -o <FILE>\n\
\n\
FLAGS:\n \
-h, --help Prints help information\n \
-V, --version Prints version information\n\
\n\
OPTIONS:\n \
-i <DEV> Specify thinp metadata binary device/file\n \
-o <FILE> Specify packed output file"
);
//------------------------------------------
struct ThinMetadataPack;
impl<'a> Program<'a> for ThinMetadataPack {
fn name() -> &'a str {
"thin_metadata_pack"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
thin_metadata_pack_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::IoOptions
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for ThinMetadataPack {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_md(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
msg::BAD_SUPERBLOCK
}
}
impl<'a> OutputProgram<'a> for ThinMetadataPack {
fn missing_output_arg() -> &'a str {
msg::MISSING_OUTPUT_ARG
}
}
//------------------------------------------
test_accepts_help!(ThinMetadataPack);
test_accepts_version!(ThinMetadataPack);
test_rejects_bad_option!(ThinMetadataPack);
test_missing_input_option!(ThinMetadataPack);
test_missing_output_option!(ThinMetadataPack);
test_input_file_not_found!(ThinMetadataPack);
//-----------------------------------------

View File

@ -0,0 +1,128 @@
use anyhow::Result;
mod common;
use common::common_args::*;
use common::fixture::*;
use common::input_arg::*;
use common::output_option::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
use common::thin::*;
//------------------------------------------
const USAGE: &str = concat!(
"thin_metadata_unpack ",
include_str!("../VERSION"),
"Unpack a compressed file of thin metadata.\n\
\n\
USAGE:\n \
thin_metadata_unpack -i <DEV> -o <FILE>\n\
\n\
FLAGS:\n \
-h, --help Prints help information\n \
-V, --version Prints version information\n\
\n\
OPTIONS:\n \
-i <DEV> Specify thinp metadata binary device/file\n \
-o <FILE> Specify packed output file"
);
//------------------------------------------
struct ThinMetadataUnpack;
impl<'a> Program<'a> for ThinMetadataUnpack {
fn name() -> &'a str {
"thin_metadata_pack"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
thin_metadata_unpack_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::IoOptions
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for ThinMetadataUnpack {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_zeroed_md(td) // FIXME: make a real pack file
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
"Not a pack file"
}
}
impl<'a> OutputProgram<'a> for ThinMetadataUnpack {
fn missing_output_arg() -> &'a str {
msg::MISSING_OUTPUT_ARG
}
}
//------------------------------------------
test_accepts_help!(ThinMetadataUnpack);
test_accepts_version!(ThinMetadataUnpack);
test_rejects_bad_option!(ThinMetadataUnpack);
test_missing_input_option!(ThinMetadataUnpack);
test_input_file_not_found!(ThinMetadataUnpack);
test_corrupted_input_data!(ThinMetadataUnpack);
test_missing_output_option!(ThinMetadataUnpack);
//------------------------------------------
// TODO: share with thin_restore/cache_restore/era_restore
#[test]
fn end_to_end() -> Result<()> {
let mut td = TestDir::new()?;
let md_in = mk_valid_md(&mut td)?;
let md_out = mk_zeroed_md(&mut td)?;
run_ok(thin_metadata_pack_cmd(args![
"-i",
&md_in,
"-o",
"meta.pack"
]))?;
run_ok(thin_metadata_unpack_cmd(args![
"-i",
"meta.pack",
"-o",
&md_out
]))?;
let dump1 = run_ok(thin_dump_cmd(args![&md_in]))?;
let dump2 = run_ok(thin_dump_cmd(args![&md_out]))?;
assert_eq!(dump1, dump2);
Ok(())
}
//------------------------------------------

224
tests/thin_repair.rs Normal file
View File

@ -0,0 +1,224 @@
use anyhow::Result;
mod common;
use common::common_args::*;
use common::fixture::*;
use common::input_arg::*;
use common::output_option::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
use common::thin::*;
//------------------------------------------
const USAGE: &str = "Usage: thin_repair [options] {device|file}\n\
Options:\n \
{-h|--help}\n \
{-i|--input} <input metadata (binary format)>\n \
{-o|--output} <output metadata (binary format)>\n \
{--transaction-id} <natural>\n \
{--data-block-size} <natural>\n \
{--nr-data-blocks} <natural>\n \
{-V|--version}";
//-----------------------------------------
struct ThinRepair;
impl<'a> Program<'a> for ThinRepair {
fn name() -> &'a str {
"thin_repair"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
thin_repair_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::IoOptions
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for ThinRepair {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_md(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
#[cfg(not(feature = "rust_tests"))]
fn corrupted_input() -> &'a str {
"The following field needs to be provided on the command line due to corruption in the superblock"
}
#[cfg(feature = "rust_tests")]
fn corrupted_input() -> &'a str {
"data block size needs to be provided due to corruption in the superblock"
}
}
impl<'a> OutputProgram<'a> for ThinRepair {
fn missing_output_arg() -> &'a str {
msg::MISSING_OUTPUT_ARG
}
}
impl<'a> MetadataWriter<'a> for ThinRepair {
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
}
//-----------------------------------------
test_accepts_help!(ThinRepair);
test_accepts_version!(ThinRepair);
test_rejects_bad_option!(ThinRepair);
test_input_file_not_found!(ThinRepair);
test_input_cannot_be_a_directory!(ThinRepair);
test_corrupted_input_data!(ThinRepair);
test_missing_output_option!(ThinRepair);
//-----------------------------------------
// test output to a small file
// TODO: share with thin_restore
#[test]
fn dont_repair_xml() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?;
let xml = mk_valid_xml(&mut td)?;
run_fail(thin_repair_cmd(args!["-i", &xml, "-o", &md]))?;
Ok(())
}
//-----------------------------------------
// TODO: share with thin_dump
#[cfg(not(feature = "rust_tests"))]
fn override_thing(flag: &str, val: &str, pattern: &str) -> Result<()> {
let mut td = TestDir::new()?;
let md1 = mk_valid_md(&mut td)?;
let md2 = mk_zeroed_md(&mut td)?;
let output = run_ok_raw(thin_repair_cmd(args![flag, val, "-i", &md1, "-o", &md2]))?;
assert_eq!(output.stderr.len(), 0);
let output = run_ok(thin_dump_cmd(args![&md2]))?;
assert!(output.contains(pattern));
Ok(())
}
#[test]
#[cfg(not(feature = "rust_tests"))]
fn override_transaction_id() -> Result<()> {
override_thing("--transaction-id", "2345", "transaction=\"2345\"")
}
#[test]
#[cfg(not(feature = "rust_tests"))]
fn override_data_block_size() -> Result<()> {
override_thing("--data-block-size", "8192", "data_block_size=\"8192\"")
}
#[test]
#[cfg(not(feature = "rust_tests"))]
fn override_nr_data_blocks() -> Result<()> {
override_thing("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"")
}
// FIXME: that's repair_superblock in thin_dump.rs
#[test]
fn superblock_succeeds() -> Result<()> {
let mut td = TestDir::new()?;
let md1 = mk_valid_md(&mut td)?;
let original = run_ok_raw(thin_dump_cmd(args![&md1]))?;
if !cfg!(feature = "rust_tests") {
assert_eq!(original.stderr.len(), 0);
}
damage_superblock(&md1)?;
let md2 = mk_zeroed_md(&mut td)?;
run_ok(thin_repair_cmd(args![
"--transaction-id=1",
"--data-block-size=128",
"--nr-data-blocks=20480",
"-i",
&md1,
"-o",
&md2
]))?;
let repaired = run_ok_raw(thin_dump_cmd(args![&md2]))?;
if !cfg!(feature = "rust_tests") {
assert_eq!(repaired.stderr.len(), 0);
}
assert_eq!(original.stdout, repaired.stdout);
Ok(())
}
//-----------------------------------------
// TODO: share with thin_dump
fn missing_thing(flag1: &str, flag2: &str, pattern: &str) -> Result<()> {
let mut td = TestDir::new()?;
let md1 = mk_valid_md(&mut td)?;
damage_superblock(&md1)?;
let md2 = mk_zeroed_md(&mut td)?;
let stderr = run_fail(thin_repair_cmd(args![flag1, flag2, "-i", &md1, "-o", &md2]))?;
assert!(stderr.contains(pattern));
Ok(())
}
#[test]
#[cfg(not(feature = "rust_tests"))]
fn missing_transaction_id() -> Result<()> {
missing_thing(
"--data-block-size=128",
"--nr-data-blocks=20480",
"transaction id",
)
}
#[test]
fn missing_data_block_size() -> Result<()> {
missing_thing(
"--transaction-id=1",
"--nr-data-blocks=20480",
"data block size",
)
}
#[test]
#[cfg(not(feature = "rust_tests"))]
fn missing_nr_data_blocks() -> Result<()> {
missing_thing(
"--transaction-id=1",
"--data-block-size=128",
"nr data blocks",
)
}
//-----------------------------------------

162
tests/thin_restore.rs Normal file
View File

@ -0,0 +1,162 @@
use anyhow::Result;
mod common;
use common::common_args::*;
use common::fixture::*;
use common::input_arg::*;
use common::output_option::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
use common::thin::*;
//------------------------------------------
const USAGE: &str = "Usage: thin_restore [options]\n\
Options:\n \
{-h|--help}\n \
{-i|--input} <input xml file>\n \
{-o|--output} <output device or file>\n \
{--transaction-id} <natural>\n \
{--data-block-size} <natural>\n \
{--nr-data-blocks} <natural>\n \
{-q|--quiet}\n \
{-V|--version}";
//------------------------------------------
struct ThinRestore;
impl<'a> Program<'a> for ThinRestore {
fn name() -> &'a str {
"thin_restore"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
thin_restore_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::IoOptions
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
impl<'a> InputProgram<'a> for ThinRestore {
fn mk_valid_input(td: &mut TestDir) -> Result<std::path::PathBuf> {
mk_valid_xml(td)
}
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
fn missing_input_arg() -> &'a str {
msg::MISSING_INPUT_ARG
}
fn corrupted_input() -> &'a str {
"" // we don't intent to verify error messages of XML parsing
}
}
impl<'a> OutputProgram<'a> for ThinRestore {
fn missing_output_arg() -> &'a str {
msg::MISSING_OUTPUT_ARG
}
}
impl<'a> MetadataWriter<'a> for ThinRestore {
fn file_not_found() -> &'a str {
msg::FILE_NOT_FOUND
}
}
//-----------------------------------------
test_accepts_help!(ThinRestore);
test_accepts_version!(ThinRestore);
test_missing_input_option!(ThinRestore);
test_input_file_not_found!(ThinRestore);
test_corrupted_input_data!(ThinRestore);
test_missing_output_option!(ThinRestore);
test_tiny_output_file!(ThinRestore);
test_unwritable_output_file!(ThinRestore);
//-----------------------------------------
// TODO: share with cache_restore, era_restore
fn quiet_flag(flag: &str) -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&mut td)?;
let output = run_ok_raw(thin_restore_cmd(args!["-i", &xml, "-o", &md, flag]))?;
assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0);
Ok(())
}
#[test]
fn accepts_q() -> Result<()> {
quiet_flag("-q")
}
#[test]
fn accepts_quiet() -> Result<()> {
quiet_flag("--quiet")
}
//-----------------------------------------
// TODO: share with thin_dump
#[cfg(not(feature = "rust_tests"))]
fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&mut td)?;
run_ok(thin_restore_cmd(args!["-i", &xml, "-o", &md, flag, value]))?;
let output = run_ok(thin_dump_cmd(args![&md]))?;
assert!(output.contains(pattern));
Ok(())
}
#[test]
#[cfg(not(feature = "rust_tests"))]
fn override_transaction_id() -> Result<()> {
override_something("--transaction-id", "2345", "transaction=\"2345\"")
}
#[test]
#[cfg(not(feature = "rust_tests"))]
fn override_data_block_size() -> Result<()> {
override_something("--data-block-size", "8192", "data_block_size=\"8192\"")
}
#[test]
#[cfg(not(feature = "rust_tests"))]
fn override_nr_data_blocks() -> Result<()> {
override_something("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"")
}
//-----------------------------------------

107
tests/thin_rmap.rs Normal file
View File

@ -0,0 +1,107 @@
use anyhow::Result;
mod common;
use common::common_args::*;
use common::process::*;
use common::program::*;
use common::target::*;
use common::test_dir::*;
use common::thin::*;
//------------------------------------------
const USAGE: &str = "Usage: thin_rmap [options] {device|file}\n\
Options:\n \
{-h|--help}\n \
{-V|--version}\n \
{--region <block range>}*\n\
Where:\n \
<block range> is of the form <begin>..<one-past-the-end>\n \
for example 5..45 denotes blocks 5 to 44 inclusive, but not block 45";
//------------------------------------------
struct ThinRmap;
impl<'a> Program<'a> for ThinRmap {
fn name() -> &'a str {
"thin_rmap"
}
fn cmd<I>(args: I) -> Command
where
I: IntoIterator,
I::Item: Into<std::ffi::OsString>,
{
thin_rmap_cmd(args)
}
fn usage() -> &'a str {
USAGE
}
fn arg_type() -> ArgType {
ArgType::InputArg
}
fn bad_option_hint(option: &str) -> String {
msg::bad_option_hint(option)
}
}
//------------------------------------------
test_accepts_help!(ThinRmap);
test_accepts_version!(ThinRmap);
test_rejects_bad_option!(ThinRmap);
//------------------------------------------
#[test]
fn valid_region_format_should_pass() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
run_ok(thin_rmap_cmd(args!["--region", "23..7890", &md]))?;
Ok(())
}
#[test]
fn invalid_regions_should_fail() -> Result<()> {
let invalid_regions = [
"23,7890",
"23..six",
"found..7890",
"89..88",
"89..89",
"89..",
"",
"89...99",
];
for r in &invalid_regions {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
run_fail(thin_rmap_cmd(args![r, &md]))?;
}
Ok(())
}
#[test]
fn multiple_regions_should_pass() -> Result<()> {
let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?;
run_ok(thin_rmap_cmd(args![
"--region", "1..23", "--region", "45..78", &md
]))?;
Ok(())
}
#[test]
fn junk_input() -> Result<()> {
let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?;
run_fail(thin_rmap_cmd(args!["--region", "0..-1", &xml]))?;
Ok(())
}
//------------------------------------------

View File

@ -1,15 +1,17 @@
use anyhow::{anyhow, Result};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use rand::prelude::*;
use std::collections::VecDeque;
use std::fs::OpenOptions;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::ops::Range;
use std::path::{Path, PathBuf};
use tempfile::tempdir;
use std::path::Path;
use thinp::file_utils;
use thinp::thin::xml::{self, Visit};
use thinp::thin::ir::{self, MetadataVisitor, Visit};
use thinp::thin::xml;
mod common;
use common::test_dir::*;
use common::thin_xml_generator::{write_xml, EmptyPoolS, FragmentedS, SingleThinS, SnapS, XmlGen};
//------------------------------------
@ -90,8 +92,8 @@ struct ThinXmlVisitor<'a, V: ThinVisitor> {
thin_id: Option<u32>,
}
impl<'a, V: ThinVisitor> xml::MetadataVisitor for ThinXmlVisitor<'a, V> {
fn superblock_b(&mut self, sb: &xml::Superblock) -> Result<Visit> {
impl<'a, V: ThinVisitor> MetadataVisitor for ThinXmlVisitor<'a, V> {
fn superblock_b(&mut self, sb: &ir::Superblock) -> Result<Visit> {
self.block_size = Some(sb.data_block_size);
Ok(Visit::Continue)
}
@ -100,7 +102,15 @@ impl<'a, V: ThinVisitor> xml::MetadataVisitor for ThinXmlVisitor<'a, V> {
Ok(Visit::Continue)
}
fn device_b(&mut self, d: &xml::Device) -> Result<Visit> {
fn def_shared_b(&mut self, _name: &str) -> Result<Visit> {
todo!();
}
fn def_shared_e(&mut self) -> Result<Visit> {
todo!();
}
fn device_b(&mut self, d: &ir::Device) -> Result<Visit> {
self.thin_id = Some(d.dev_id);
Ok(Visit::Continue)
}
@ -109,7 +119,7 @@ impl<'a, V: ThinVisitor> xml::MetadataVisitor for ThinXmlVisitor<'a, V> {
Ok(Visit::Continue)
}
fn map(&mut self, m: &xml::Map) -> Result<Visit> {
fn map(&mut self, m: &ir::Map) -> Result<Visit> {
for i in 0..m.len {
let block = ThinBlock {
thin_id: self.thin_id.unwrap(),
@ -122,6 +132,10 @@ impl<'a, V: ThinVisitor> xml::MetadataVisitor for ThinXmlVisitor<'a, V> {
Ok(Visit::Continue)
}
fn ref_shared(&mut self, _name: &str) -> Result<Visit> {
todo!();
}
fn eof(&mut self) -> Result<Visit> {
Ok(Visit::Stop)
}
@ -251,25 +265,6 @@ impl<'a, R: Read + Seek> ThinVisitor for Verifier<'a, R> {
//------------------------------------
fn mk_path(dir: &Path, file: &str) -> PathBuf {
let mut p = PathBuf::new();
p.push(dir);
p.push(PathBuf::from(file));
p
}
fn generate_xml(path: &Path, g: &mut dyn Scenario) -> Result<()> {
let xml_out = OpenOptions::new()
.read(false)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
let mut w = xml::XmlWriter::new(xml_out);
g.generate_xml(&mut w)
}
fn create_data_file(data_path: &Path, xml_path: &Path) -> Result<()> {
let input = OpenOptions::new().read(true).write(false).open(xml_path)?;
@ -304,17 +299,19 @@ fn verify(xml_path: &Path, data_path: &Path, seed: u64) -> Result<()> {
}
trait Scenario {
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()>;
fn get_new_nr_blocks(&self) -> u64;
}
fn test_shrink(scenario: &mut dyn Scenario) -> Result<()> {
let dir = tempdir()?;
let xml_before = mk_path(dir.path(), "before.xml");
let xml_after = mk_path(dir.path(), "after.xml");
let data_path = mk_path(dir.path(), "metadata.bin");
fn test_shrink<S>(scenario: &mut S) -> Result<()>
where
S: Scenario + XmlGen,
{
let mut td = TestDir::new()?;
let xml_before = td.mk_path("before.xml");
let xml_after = td.mk_path("after.xml");
let data_path = td.mk_path("metadata.bin");
generate_xml(&xml_before, scenario)?;
write_xml(&xml_before, scenario)?;
create_data_file(&data_path, &xml_before)?;
let mut rng = rand::thread_rng();
@ -332,28 +329,7 @@ fn test_shrink(scenario: &mut dyn Scenario) -> Result<()> {
//------------------------------------
fn common_sb(nr_blocks: u64) -> xml::Superblock {
xml::Superblock {
uuid: "".to_string(),
time: 0,
transaction: 0,
flags: None,
version: None,
data_block_size: 32,
nr_data_blocks: nr_blocks,
metadata_snap: None,
}
}
struct EmptyPoolS {}
impl Scenario for EmptyPoolS {
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
v.superblock_b(&common_sb(1024))?;
v.superblock_e()?;
Ok(())
}
fn get_new_nr_blocks(&self) -> u64 {
512
}
@ -367,45 +343,7 @@ fn shrink_empty_pool() -> Result<()> {
//------------------------------------
struct SingleThinS {
offset: u64,
len: u64,
old_nr_data_blocks: u64,
new_nr_data_blocks: u64,
}
impl SingleThinS {
fn new(offset: u64, len: u64, old_nr_data_blocks: u64, new_nr_data_blocks: u64) -> Self {
SingleThinS {
offset,
len,
old_nr_data_blocks,
new_nr_data_blocks,
}
}
}
impl Scenario for SingleThinS {
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
v.device_b(&xml::Device {
dev_id: 0,
mapped_blocks: self.len,
transaction: 0,
creation_time: 0,
snap_time: 0,
})?;
v.map(&xml::Map {
thin_begin: 0,
data_begin: self.offset,
time: 0,
len: self.len,
})?;
v.device_e()?;
v.superblock_e()?;
Ok(())
}
fn get_new_nr_blocks(&self) -> u64 {
self.new_nr_data_blocks
}
@ -452,128 +390,7 @@ fn shrink_insufficient_space() -> Result<()> {
//------------------------------------
struct FragmentedS {
nr_thins: u32,
thin_size: u64,
old_nr_data_blocks: u64,
new_nr_data_blocks: u64,
}
impl FragmentedS {
fn new(nr_thins: u32, thin_size: u64) -> Self {
let old_size = (nr_thins as u64) * thin_size;
FragmentedS {
nr_thins,
thin_size,
old_nr_data_blocks: (nr_thins as u64) * thin_size,
new_nr_data_blocks: old_size * 3 / 4,
}
}
}
#[derive(Clone)]
struct ThinRun {
thin_id: u32,
thin_begin: u64,
len: u64,
}
#[derive(Clone, Debug, Copy)]
struct MappedRun {
thin_id: u32,
thin_begin: u64,
data_begin: u64,
len: u64,
}
fn mk_runs(thin_id: u32, total_len: u64, run_len: std::ops::Range<u64>) -> Vec<ThinRun> {
let mut runs = Vec::new();
let mut b = 0u64;
while b < total_len {
let len = u64::min(
total_len - b,
thread_rng().gen_range(run_len.start, run_len.end),
);
runs.push(ThinRun {
thin_id: thin_id,
thin_begin: b,
len,
});
b += len;
}
runs
}
impl Scenario for FragmentedS {
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
// Allocate each thin fully, in runs between 1 and 16.
let mut runs = Vec::new();
for thin in 0..self.nr_thins {
runs.append(&mut mk_runs(thin, self.thin_size, 1..17));
}
// Shuffle
runs.shuffle(&mut rand::thread_rng());
// map across the data
let mut maps = Vec::new();
let mut b = 0;
for r in &runs {
maps.push(MappedRun {
thin_id: r.thin_id,
thin_begin: r.thin_begin,
data_begin: b,
len: r.len,
});
b += r.len;
}
// drop half the mappings, which leaves us free runs
let mut dropped = Vec::new();
for i in 0..maps.len() {
if i % 2 == 0 {
dropped.push(maps[i].clone());
}
}
// Unshuffle. This isn't strictly necc. but makes the xml
// more readable.
use std::cmp::Ordering;
maps.sort_by(|&l, &r| match l.thin_id.cmp(&r.thin_id) {
Ordering::Equal => l.thin_begin.cmp(&r.thin_begin),
o => o,
});
// write the xml
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
for thin in 0..self.nr_thins {
v.device_b(&xml::Device {
dev_id: thin,
mapped_blocks: self.thin_size,
transaction: 0,
creation_time: 0,
snap_time: 0,
})?;
for m in &dropped {
if m.thin_id != thin {
continue;
}
v.map(&xml::Map {
thin_begin: m.thin_begin,
data_begin: m.data_begin,
time: 0,
len: m.len,
})?;
}
v.device_e()?;
}
v.superblock_e()?;
Ok(())
}
fn get_new_nr_blocks(&self) -> u64 {
self.new_nr_data_blocks
}
@ -605,321 +422,7 @@ fn shrink_fragmented_thin_64() -> Result<()> {
//------------------------------------
struct Allocator {
runs: VecDeque<Range<u64>>,
}
impl Allocator {
fn new_shuffled(total_len: u64, run_len: Range<u64>) -> Allocator {
let mut runs = Vec::new();
let mut b = 0u64;
while b < total_len {
let len = u64::min(
total_len - b,
thread_rng().gen_range(run_len.start, run_len.end),
);
runs.push(b..(b + len));
b += len;
}
runs.shuffle(&mut thread_rng());
let runs: VecDeque<Range<u64>> = runs.iter().map(|r| r.clone()).collect();
Allocator { runs }
}
fn is_empty(&self) -> bool {
self.runs.is_empty()
}
fn alloc(&mut self, len: u64) -> Result<Vec<Range<u64>>> {
let mut len = len;
let mut runs = Vec::new();
while len > 0 {
let r = self.runs.pop_front();
if r.is_none() {
return Err(anyhow!("could not allocate; out of space"));
}
let mut r = r.unwrap();
let rlen = r.end - r.start;
if len < rlen {
runs.push(r.start..(r.start + len));
// We need to push something back.
self.runs.push_front((r.start + len)..r.end);
len = 0;
} else {
runs.push(r.start..r.end);
len -= rlen;
}
}
Ok(runs)
}
}
// Having explicitly unmapped regions makes it easier to
// apply snapshots.
#[derive(Clone)]
enum Run {
Mapped { data_begin: u64, len: u64 },
UnMapped { len: u64 },
}
impl Run {
fn len(&self) -> u64 {
match self {
Run::Mapped {
data_begin: _data_begin,
len,
} => *len,
Run::UnMapped { len } => *len,
}
}
fn split(&self, n: u64) -> (Option<Run>, Option<Run>) {
if n == 0 {
return (None, Some(self.clone()));
} else {
if self.len() <= n {
return (Some(self.clone()), None);
} else {
match self {
Run::Mapped { data_begin, len } => (
Some(Run::Mapped {
data_begin: *data_begin,
len: n,
}),
Some(Run::Mapped {
data_begin: data_begin + n,
len: len - n,
}),
),
Run::UnMapped { len } => (
Some(Run::UnMapped { len: n }),
Some(Run::UnMapped { len: len - n }),
),
}
}
}
}
}
#[derive(Clone)]
struct ThinDev {
thin_id: u32,
dev_size: u64,
runs: Vec<Run>,
}
impl ThinDev {
fn emit(&self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
v.device_b(&xml::Device {
dev_id: self.thin_id,
mapped_blocks: self.dev_size,
transaction: 0,
creation_time: 0,
snap_time: 0,
})?;
let mut b = 0;
for r in &self.runs {
match r {
Run::Mapped { data_begin, len } => {
v.map(&xml::Map {
thin_begin: b,
data_begin: *data_begin,
time: 0,
len: *len,
})?;
b += len;
}
Run::UnMapped { len } => {
b += len;
}
}
}
v.device_e()?;
Ok(())
}
}
#[derive(Clone)]
enum SnapRunType {
Same,
Diff,
Hole,
}
#[derive(Clone)]
struct SnapRun(SnapRunType, u64);
fn mk_origin(thin_id: u32, total_len: u64, allocator: &mut Allocator) -> Result<ThinDev> {
let mut runs = Vec::new();
let mut b = 0;
while b < total_len {
let len = u64::min(thread_rng().gen_range(16, 64), total_len - b);
match thread_rng().gen_range(0, 2) {
0 => {
for data in allocator.alloc(len)? {
assert!(data.end >= data.start);
runs.push(Run::Mapped {
data_begin: data.start,
len: data.end - data.start,
});
}
}
1 => {
runs.push(Run::UnMapped { len });
}
_ => {
return Err(anyhow!("bad value returned from rng"));
}
};
b += len;
}
Ok(ThinDev {
thin_id,
dev_size: total_len,
runs,
})
}
fn mk_snap_mapping(
total_len: u64,
run_len: Range<u64>,
same_percent: usize,
diff_percent: usize,
) -> Vec<SnapRun> {
let mut runs = Vec::new();
let mut b = 0u64;
while b < total_len {
let len = u64::min(
total_len - b,
thread_rng().gen_range(run_len.start, run_len.end),
);
let n = thread_rng().gen_range(0, 100);
if n < same_percent {
runs.push(SnapRun(SnapRunType::Same, len));
} else if n < diff_percent {
runs.push(SnapRun(SnapRunType::Diff, len));
} else {
runs.push(SnapRun(SnapRunType::Hole, len));
}
b += len;
}
runs
}
fn split_runs(mut n: u64, runs: &Vec<Run>) -> (Vec<Run>, Vec<Run>) {
let mut before = Vec::new();
let mut after = Vec::new();
for r in runs {
match r.split(n) {
(Some(lhs), None) => {
before.push(lhs);
}
(Some(lhs), Some(rhs)) => {
before.push(lhs);
after.push(rhs);
}
(None, Some(rhs)) => {
after.push(rhs);
}
(None, None) => {}
}
n -= r.len();
}
(before, after)
}
fn apply_snap_runs(
origin: &Vec<Run>,
snap: &Vec<SnapRun>,
allocator: &mut Allocator,
) -> Result<Vec<Run>> {
let mut origin = origin.clone();
let mut runs = Vec::new();
for SnapRun(st, slen) in snap {
let (os, rest) = split_runs(*slen, &origin);
match st {
SnapRunType::Same => {
for o in os {
runs.push(o);
}
}
SnapRunType::Diff => {
for data in allocator.alloc(*slen)? {
runs.push(Run::Mapped {
data_begin: data.start,
len: data.end - data.start,
});
}
}
SnapRunType::Hole => {
runs.push(Run::UnMapped { len: *slen });
}
}
origin = rest;
}
Ok(runs)
}
// Snapshots share mappings, not neccessarily the entire ranges.
struct SnapS {
len: u64,
nr_snaps: u32,
// Snaps will differ from the origin by this percentage
percent_change: usize,
old_nr_data_blocks: u64,
new_nr_data_blocks: u64,
}
impl SnapS {
fn new(len: u64, nr_snaps: u32, percent_change: usize) -> Self {
let delta = len * (nr_snaps as u64) * (percent_change as u64) / 100;
let old_nr_data_blocks = len + 3 * delta;
let new_nr_data_blocks = len + 2 * delta;
SnapS {
len,
nr_snaps,
percent_change,
old_nr_data_blocks,
new_nr_data_blocks,
}
}
}
impl Scenario for SnapS {
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
let mut allocator = Allocator::new_shuffled(self.old_nr_data_blocks, 64..512);
let origin = mk_origin(0, self.len, &mut allocator)?;
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
origin.emit(v)?;
v.superblock_e()?;
Ok(())
}
fn get_new_nr_blocks(&self) -> u64 {
self.new_nr_data_blocks
}

View File

@ -62,7 +62,7 @@ chunk const &
cache_stream::get()
{
chunk_wrapper *w = new chunk_wrapper(*this);
return w->c_;
return w->c_; // wrapper will get freed by the put method
}
void

View File

@ -17,18 +17,6 @@ thin_provisioning::register_thin_commands(base::application &app)
app.add_cmd(command::ptr(new thin_repair_cmd()));
app.add_cmd(command::ptr(new thin_rmap_cmd()));
app.add_cmd(command::ptr(new thin_trim_cmd()));
#ifdef DEV_TOOLS
app.add_cmd(command::ptr(new thin_ll_dump_cmd()));
app.add_cmd(command::ptr(new thin_ll_restore_cmd()));
app.add_cmd(command::ptr(new thin_scan_cmd()));
app.add_cmd(command::ptr(new thin_generate_damage_cmd()));
app.add_cmd(command::ptr(new thin_generate_metadata_cmd()));
app.add_cmd(command::ptr(new thin_generate_mappings_cmd()));
app.add_cmd(command::ptr(new thin_show_duplicates_cmd()));
app.add_cmd(command::ptr(new thin_show_metadata_cmd()));
app.add_cmd(command::ptr(new thin_journal_cmd()));
#endif
}
//----------------------------------------------------------------

View File

@ -71,34 +71,11 @@ namespace thin_provisioning {
virtual int run(int argc, char **argv);
};
#ifdef DEV_TOOLS
class thin_ll_dump_cmd : public base::command {
//------------------------------------------------------
class thin_debug_cmd : public base::command {
public:
thin_ll_dump_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
class thin_ll_restore_cmd : public base::command {
public:
thin_ll_restore_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
class thin_scan_cmd : public base::command {
public:
thin_scan_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
class thin_show_duplicates_cmd : public base::command {
public:
thin_show_duplicates_cmd();
thin_debug_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
@ -124,6 +101,52 @@ namespace thin_provisioning {
virtual int run(int argc, char **argv);
};
class thin_journal_cmd : public base::command {
public:
thin_journal_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
class thin_ll_dump_cmd : public base::command {
public:
thin_ll_dump_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
class thin_ll_restore_cmd : public base::command {
public:
thin_ll_restore_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
class thin_patch_superblock_cmd : public base::command {
public:
thin_patch_superblock_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
class thin_scan_cmd : public base::command {
public:
thin_scan_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
class thin_show_duplicates_cmd : public base::command {
public:
thin_show_duplicates_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
class thin_show_metadata_cmd : public base::command {
public:
thin_show_metadata_cmd();
@ -131,14 +154,6 @@ namespace thin_provisioning {
virtual int run(int argc, char **argv);
};
class thin_journal_cmd : public base::command {
public:
thin_journal_cmd();
virtual void usage(std::ostream &out) const;
virtual int run(int argc, char **argv);
};
#endif
void register_thin_commands(base::application &app);
}

View File

@ -63,6 +63,14 @@ void damage_generator::create_metadata_leaks(block_address nr_leaks,
std::set<block_address> leaks;
find_blocks(md_->metadata_sm_, nr_leaks, expected, leaks);
block_counter bc(true);
md_->metadata_sm_->count_metadata(bc);
block_address nr_blocks = md_->metadata_sm_->get_nr_blocks();
for (block_address b = 0; b < nr_blocks; b++) {
if (bc.get_count(b))
md_->tm_->mark_shadowed(b);
}
for (auto const &b : leaks)
md_->metadata_sm_->set_count(b, actual);
}

View File

@ -0,0 +1,24 @@
#include "thin-provisioning/commands.h"
using namespace base;
using namespace thin_provisioning;
//----------------------------------------------------------------
void
thin_provisioning::register_thin_commands(base::application &app)
{
app.add_cmd(command::ptr(new thin_debug_cmd()));
app.add_cmd(command::ptr(new thin_generate_damage_cmd()));
app.add_cmd(command::ptr(new thin_generate_mappings_cmd()));
app.add_cmd(command::ptr(new thin_generate_metadata_cmd()));
app.add_cmd(command::ptr(new thin_journal_cmd()));
app.add_cmd(command::ptr(new thin_ll_dump_cmd()));
app.add_cmd(command::ptr(new thin_ll_restore_cmd()));
app.add_cmd(command::ptr(new thin_patch_superblock_cmd()));
app.add_cmd(command::ptr(new thin_scan_cmd()));
app.add_cmd(command::ptr(new thin_show_duplicates_cmd()));
app.add_cmd(command::ptr(new thin_show_metadata_cmd()));
}
//----------------------------------------------------------------

View File

@ -25,6 +25,14 @@ namespace thin_provisioning {
uint32_t snapshotted_time_;
};
inline bool operator==(device_details const& lhs, device_details const& rhs) {
return false; // device_details are not comparable
}
inline bool operator!=(device_details const& lhs, device_details const& rhs) {
return !(lhs == rhs);
}
struct device_details_traits {
typedef device_details_disk disk_type;
typedef device_details value_type;

View File

@ -24,6 +24,14 @@ namespace thin_provisioning {
uint32_t time_;
};
inline bool operator==(block_time const& lhs, block_time const& rhs) {
return lhs.block_ == rhs.block_;
}
inline bool operator!=(block_time const& lhs, block_time const& rhs) {
return !(lhs == rhs);
}
class block_time_ref_counter {
public:
block_time_ref_counter(space_map::ptr sm);

Some files were not shown because too many files have changed in this diff Show More