├── .gitattributes ├── .clang-format ├── .gitignore ├── .github └── workflows │ ├── clang-format.yml │ └── test.yml ├── hash_list_checksum ├── test_entropy ├── README.markdown ├── stressdrive.xcodeproj └── project.pbxproj └── stressdrive.c /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -crlf -merge 2 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /pkg/ 2 | 3 | build/ 4 | *.pbxuser 5 | *.mode1v3 6 | *.mode2v3 7 | *.perspective 8 | *.perspectivev3 9 | project.xcworkspace/ 10 | xcuserdata/ 11 | 12 | /stressdrive 13 | -------------------------------------------------------------------------------- /.github/workflows/clang-format.yml: -------------------------------------------------------------------------------- 1 | name: clang-format 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: clang-format 11 | run: clang-format --dry-run --Werror *.c 12 | -------------------------------------------------------------------------------- /hash_list_checksum: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: ASCII-8BIT 3 | 4 | require 'digest' 5 | 6 | BLOCK_SIZE = 2**30 # 1GB 7 | CHUNK_SIZE = 2**15 # 32KB 8 | 9 | abort 'Expected one path to checksum' unless ARGV.length == 1 10 | 11 | root_digest = Digest::SHA1.new 12 | File.open(ARGV.first, 'rb') do |f| 13 | buf = '' 14 | until f.eof? 15 | block_digest = Digest::SHA1.new 16 | (BLOCK_SIZE / CHUNK_SIZE).times do 17 | break unless f.read(CHUNK_SIZE, buf) 18 | 19 | block_digest.update(buf) 20 | end 21 | root_digest.update(block_digest.digest) 22 | end 23 | end 24 | puts root_digest.hexdigest 25 | -------------------------------------------------------------------------------- /test_entropy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# -ne 1 ]]; then 4 | echo "Usage: $0 path" >&2 5 | exit 1 6 | fi 7 | 8 | file_path="$1" 9 | 10 | original_size=$(cat "$file_path" | wc -c) 11 | 12 | if command -v pv &> /dev/null; then 13 | cat(){ 14 | pv --size "$original_size" $1 15 | } 16 | fi 17 | 18 | gzipped_size=$(cat "$file_path" | gzip --best --stdout | wc -c) 19 | bzipped_size=$(cat "$file_path" | bzip2 --best --stdout | wc -c) 20 | 21 | echo "original: $original_size" 22 | echo "gzippped: $gzipped_size ($(( gzipped_size * 100 / original_size ))%)" 23 | echo "bzippped: $bzipped_size ($(( bzipped_size * 100 / original_size ))%)" 24 | 25 | if (( gzipped_size < original_size || bzipped_size < original_size )); then 26 | exit 1 27 | fi 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | check: 7 | runs-on: "${{ matrix.os }}" 8 | strategy: 9 | matrix: 10 | os: 11 | - macos-latest 12 | - ubuntu-latest 13 | fail-fast: false 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: build 17 | run: | 18 | if [[ "$RUNNER_OS" == "macOS" ]]; then 19 | brew install openssl@3 20 | export OPENSSL_DIR=$(brew --prefix openssl@3) 21 | gcc stressdrive.c -o stressdrive -lcrypto -framework IOKit -framework CoreServices -I"$OPENSSL_DIR/include" -L"$OPENSSL_DIR/lib" 22 | elif [[ "$RUNNER_OS" == "Linux" ]]; then 23 | sudo apt-get install libssl-dev 24 | gcc stressdrive.c -o stressdrive -lcrypto 25 | fi 26 | - name: create ram disk 27 | run: | 28 | if [[ "$RUNNER_OS" == "macOS" ]]; then 29 | # xargs to trim whitespace 30 | ram_disk=$(hdiutil attach -nomount ram://$(( 4 * 1024 * 1024 * 1024 / 512 )) | xargs) 31 | ln -s "${ram_disk/disk/rdisk}" ram_disk 32 | elif [[ "$RUNNER_OS" == "Linux" ]]; then 33 | mkdir tmpfs 34 | sudo mount -t tmpfs -o noswap,size=4g tmpfs tmpfs 35 | dd if=/dev/zero of=tmpfs/data bs=1G count=4 36 | ram_disk=$(sudo losetup --find --show tmpfs/data) 37 | sudo chmod 777 "$ram_disk" 38 | ln -s "$ram_disk" ram_disk 39 | fi 40 | - name: test ram disk entropy when filled with zero(e)s 41 | run: | 42 | head -c $(( 256 * 1024 * 1024 )) ram_disk > 256M 43 | ! ./test_entropy 256M 44 | - name: run stressdrive on ram disk 45 | shell: bash -eo pipefail {0} 46 | run: | 47 | ./stressdrive ram_disk | tee output 48 | - name: check the hash list checksum 49 | run: | 50 | grep $(./hash_list_checksum ram_disk) output 51 | - name: test ram disk entropy when filled with random data 52 | run: | 53 | head -c $(( 256 * 1024 * 1024 )) ram_disk > 256M 54 | ./test_entropy 256M 55 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | `stressdrive` is a macOS and Linux command-line tool meant to verify correct operation of a drive. It does so by filling a drive up with random data and ensuring all the data can be correctly read back. 4 | 5 | It was written to verify correct operation of [de-duping SSDs](https://web.archive.org/web/20170309100511/http://www.storagemojo.com/2011/06/27/de-dup-too-much-of-good-thing/), but it can be used with normal HDDs or any rewritable block storage device. 6 | 7 | **DANGER:** `stressdrive` will overwrite, without warning, all data on the given drive. Be sure to double-check the drive you're aiming it at (`diskutil list` or Disk Utility.app > Select Drive > Info > Disk Identifier). 8 | 9 | ## Usage 10 | 11 | sudo ./stressdrive /dev/rdiskN 12 | 13 | ### Run Only Against Entire, Unmounted, Physical Devices 14 | 15 | `stressdrive` should always be run against **entire unmounted physical devices**. 16 | 17 | Practically: your device path should always be in the form of `/dev/rdiskX` (not `/dev/rdiskXsX`). stressdrive's results can only be trusted if it was allowed to fill the entire device to the device's advertised information-theoretic maximum. 18 | 19 | Imagine pointing stressdrive at just a logical partition. If the drive failed during the test it's possible to get back a clean read of the random data just written, while a block outside the device's partition is no longer correct. That would not be an accurate test result. 20 | 21 | ## Sample Run 22 | 23 | Here's stressdrive running against a 2 GB USB Flash drive: 24 | 25 | $ sudo ./stressdrive /dev/rdisk999 26 | Password: 27 | disk block size: 512 28 | disk block count: 3948424 29 | buffer size: 8388608 30 | succesfully created no idle assertion 31 | writing random data to /dev/rdisk999 32 | writing 100.0% (3948424 of 3948424) 00:03:54 33 | 6519594c7bf64d5e4e087cfbc5ba6324d25e8c0d <= SHA-1 of written data 34 | verifying written data 35 | reading 100.0% (3948424 of 3948424) 00:01:24 36 | 6519594c7bf64d5e4e087cfbc5ba6324d25e8c0d <= SHA-1 of read data 37 | SUCCESS 38 | succesfully released no idle assertion 39 | 40 | ## Installing 41 | ### macOS 42 | #### using macports 43 | 44 | This will probably need sudo: 45 | 46 | port install stressdrive 47 | 48 | ## Building 49 | 50 | ### macOS 51 | #### using homebrew 52 | 53 | First, you'll need OpenSSL, which you should install via homebrew: 54 | 55 | brew install openssl 56 | 57 | Then you can just: 58 | 59 | xcodebuild 60 | 61 | Or compile it directly: 62 | 63 | gcc stressdrive.c -o stressdrive -lcrypto -framework IOKit -framework CoreServices -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib 64 | 65 | #### using macports 66 | 67 | Same commands for macports: 68 | 69 | port install openssl 70 | 71 | xcodebuild PREFIX=/opt/local SEARCH_PREFIX=/opt/local 72 | 73 | gcc stressdrive.c -o stressdrive -lcrypto -framework IOKit -framework CoreServices -I/opt/local/include -L/opt/local/lib 74 | 75 | ### Ubuntu 76 | 77 | sudo apt-get install libssl-dev # You will need openssl headers 78 | 79 | gcc stressdrive.c -o stressdrive -std=c99 -lcrypto 80 | 81 | ## FAQ 82 | 83 | ### "How is this better than Disk Utility's 'Zero Out Data'?" 84 | 85 | Some SSD's de-duplicate stored blocks. For these "filling" it with zeros if actually just modifying one or two actual mapping blocks over and over again. It's not a real test of the SSD's hardware. 86 | 87 | ### "How is this better than Disk Utility's '7-Pass Erase'?" 88 | 89 | `stressdrive` only overwrites the drive with data once (so it's 7x faster) and then verifies all the data is correctly read back (which Disk Utility doesn't do at all). 90 | 91 | Jens Ayton [informs me](https://twitter.com/ahruman/status/136930141568905217) 7-Pass Erase uses fixed patterns, so de-duping may be an issue there as well. 92 | 93 | ### "Pshaw! I could do this with dd, /dev/random & shasum!" 94 | 95 | Indeed you could. I prefer a minimal focused tool whose operation is fixed, its source simple+readable and offers good built-in progress reporting. 96 | 97 | ### When I list my disks I do not see "rdiskX", I only see "diskX" 98 | 99 | When you list the disks on your computer you are shown the Block Devices. When you access Block Devices via "diskX", reads and writes are buffered through the OS. 100 | 101 | Unix systems also allow direct and unbuffered access to the Raw Devices using the format "rdiskX". stressdrive requires raw access to ensure fast, precise, and predictable disk operations. 102 | 103 | Once you've identified the top-level (physical, not synthesized) drive identifier that looks like 'diskX', simply add an 'r' for use by the app. For example, if your `diskutil list` shows `/dev/disk4` as your target, use `/dev/rdisk4` in your command. 104 | 105 | ## Version History 106 | 107 | ### v1.4: 2023-11-23 [download](https://github.com/rentzsch/stressdrive/releases/download/1.4/stressdrive-mac-1.4.zip) 108 | 109 | - [NEW] Store a checksum every 1GB, allowing faster reporting of drive failure. It requires 20 bytes per gigabyte, so 80K for a 4TB drive. ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/13)) 110 | 111 | - [NEW] Apply an exclusive advisory lock on device after opening, making running two instances on the same drive impossible ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/14)) 112 | 113 | - [DOC] Add instructions for installing using macports ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/11)) 114 | 115 | - [FIX] Include sys/file.h missing at least on ubuntu ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/17)) 116 | 117 | - [FIX] Upgrade checkCount to uint64_t to avoid precision loss warning ([rentzsch](https://github.com/rentzsch/stressdrive/commit/e3e0e2b91305596197a1b6e9fb10552fd3146387)) 118 | 119 | - [DEV] Refactor xcode project to allow different prefix, update instructions for macOS ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/10)) 120 | 121 | - [DEV] Mention sleep in idle assertion message and variable name ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/12)) 122 | 123 | - [FIX] Xcode v12.5, Homebrew on Apple Silicon ([rentzsch](https://github.com/rentzsch/stressdrive/commit/5a2a001b7dbc16e99a7de0e50bed9eda50dca31f)) 124 | 125 | - [DEV] Xcode 15 bottoms out at 10.13 as a minimum deployment target ([rentzsch](https://github.com/rentzsch/stressdrive/commit/7e0e0f26a6390a0c026089a5601fcbc62f08d05a)) 126 | 127 | - [DOC] Use archive.org link for 404 storagemojo.com article ([rentzsch](https://github.com/rentzsch/stressdrive/commit/ed7fce674ff1c32e3c6cb5319f56cb8bfbf9a2f5)) 128 | 129 | ### v1.3.2: 2020-07-05 [download](https://github.com/rentzsch/stressdrive/releases/download/1.3.2/stressdrive-mac-1.3.2.zip) 130 | 131 | - [CHANGE] Target oldest supported macOS (10.6). ([rentzsch](https://github.com/rentzsch/stressdrive/commit/0dcbe7d8bb356b379276370c54879b9ba75884b3)) 132 | 133 | ### v1.3.1: 2020-07-04 [download](https://github.com/rentzsch/stressdrive/releases/download/1.3.1/stressdrive-mac-1.3.1.zip) 134 | 135 | - [DEV] [OpenSSL v1.1.0 now requires dynamic allocation]() of cipher contexts via `EVP_CIPHER_CTX_new()`. ([rentzsch](https://github.com/rentzsch/stressdrive/commit/b3462490ef6817d89b55f6f6eb209d2319e8d842)) 136 | - [DEV] Upgrade to Xcode v11.2.1. ([rentzsch](https://github.com/rentzsch/stressdrive/commit/1d7e6a5918cc903a99337fdc402d3eb343818969)) 137 | 138 | ### v1.3: 2018-02-19 139 | 140 | - [NEW] Display speed alongside progress. ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/9)) 141 | 142 | ### v1.2.1: 2018-01-04 [download](https://github.com/rentzsch/stressdrive/releases/download/1.2.1/stressdrive-mac-1.2.1.zip) 143 | 144 | - [FIX] Statically link libcrypto. ([rentzsch](https://github.com/rentzsch/stressdrive/commit/30eac57352c49d3ebf8d980f12b3369b316f5c97)) 145 | 146 | ### v1.2: 2018-01-03 [download](https://github.com/rentzsch/stressdrive/releases/download/1.2/stressdrive-mac-1.2.zip) 147 | 148 | - [NEW] Linux support. ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/8)) 149 | - [NEW] Better progress display: elapsed time and ETA. ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/8)) 150 | - [NEW] Use AES 128 CBC with a random key and initialization vector as a much faster source of data sans fixed patterns. ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/8)) 151 | - [NEW] Don't allow the Mac to idle (sleep) while running. ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/8)) 152 | - [NEW] Print version alongside usage. ([rentzsch](https://github.com/rentzsch/stressdrive/commit/77253b193308b0670209fa9801d2ecb851a811b6)) 153 | - [CHANGE] Remove speed scaling in favor of a simpler and as fast fixed 8MB copy buffer. ([Ivan Kuchin](https://github.com/rentzsch/stressdrive/pull/8)) 154 | - [FIX] Possible overflow in speedscale. ([Doug Russell](https://github.com/rentzsch/stressdrive/pull/3)) 155 | - [FIX] Xcode project references Homebrew's OpenSSL in a non-version-specific way (so it doesn't break on every update). ([rentzsch](https://github.com/rentzsch/stressdrive/commit/7575853194793d3ee718252f08a7af52853f5424)) 156 | 157 | ### v1.1: 2011-11-17 [download](https://github.com/rentzsch/stressdrive/archive/1.1.zip) 158 | 159 | - [NEW] Speed scaling, which increases the copy buffer to the maximum that's still evenly divisible by the drive's capacity. ([rentzsch](https://github.com/rentzsch/stressdrive/commit/a3f4598af5f9957100613ff66240628bb0ab2078)) 160 | 161 | ### v1.0: 2011-11-16 [download](https://github.com/rentzsch/stressdrive/archive/1.0.zip) 162 | 163 | - Initial release. 164 | -------------------------------------------------------------------------------- /stressdrive.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8DD76FAC0486AB0100D96B5E /* stressdrive.c in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* stressdrive.c */; settings = {ATTRIBUTES = (); }; }; 11 | B2250B1318A79A8C0037C038 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2250B1218A79A8C0037C038 /* CoreFoundation.framework */; }; 12 | B2250B1518A79A910037C038 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2250B1418A79A910037C038 /* IOKit.framework */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXFileReference section */ 16 | 08FB7796FE84155DC02AAC07 /* stressdrive.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = stressdrive.c; sourceTree = ""; }; 17 | 8DD76FB20486AB0100D96B5E /* stressdrive */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = stressdrive; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | B2250B1218A79A8C0037C038 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 19 | B2250B1418A79A910037C038 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 20 | /* End PBXFileReference section */ 21 | 22 | /* Begin PBXFrameworksBuildPhase section */ 23 | 8DD76FAD0486AB0100D96B5E /* Frameworks */ = { 24 | isa = PBXFrameworksBuildPhase; 25 | buildActionMask = 2147483647; 26 | files = ( 27 | B2250B1518A79A910037C038 /* IOKit.framework in Frameworks */, 28 | B2250B1318A79A8C0037C038 /* CoreFoundation.framework in Frameworks */, 29 | ); 30 | runOnlyForDeploymentPostprocessing = 0; 31 | }; 32 | /* End PBXFrameworksBuildPhase section */ 33 | 34 | /* Begin PBXGroup section */ 35 | 08FB7794FE84155DC02AAC07 /* stressdrive */ = { 36 | isa = PBXGroup; 37 | children = ( 38 | 08FB7796FE84155DC02AAC07 /* stressdrive.c */, 39 | B2250B1418A79A910037C038 /* IOKit.framework */, 40 | B2250B1218A79A8C0037C038 /* CoreFoundation.framework */, 41 | 1AB674ADFE9D54B511CA2CBB /* Products */, 42 | ); 43 | name = stressdrive; 44 | sourceTree = ""; 45 | }; 46 | 1AB674ADFE9D54B511CA2CBB /* Products */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | 8DD76FB20486AB0100D96B5E /* stressdrive */, 50 | ); 51 | name = Products; 52 | sourceTree = ""; 53 | }; 54 | /* End PBXGroup section */ 55 | 56 | /* Begin PBXNativeTarget section */ 57 | 8DD76FA90486AB0100D96B5E /* stressdrive */ = { 58 | isa = PBXNativeTarget; 59 | buildConfigurationList = 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "stressdrive" */; 60 | buildPhases = ( 61 | 8DD76FAB0486AB0100D96B5E /* Sources */, 62 | 8DD76FAD0486AB0100D96B5E /* Frameworks */, 63 | ); 64 | buildRules = ( 65 | ); 66 | dependencies = ( 67 | ); 68 | name = stressdrive; 69 | productInstallPath = "$(HOME)/bin"; 70 | productName = stressdrive; 71 | productReference = 8DD76FB20486AB0100D96B5E /* stressdrive */; 72 | productType = "com.apple.product-type.tool"; 73 | }; 74 | /* End PBXNativeTarget section */ 75 | 76 | /* Begin PBXProject section */ 77 | 08FB7793FE84155DC02AAC07 /* Project object */ = { 78 | isa = PBXProject; 79 | attributes = { 80 | LastUpgradeCheck = 1250; 81 | }; 82 | buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "stressdrive" */; 83 | compatibilityVersion = "Xcode 3.2"; 84 | developmentRegion = en; 85 | hasScannedForEncodings = 1; 86 | knownRegions = ( 87 | en, 88 | de, 89 | ja, 90 | fr, 91 | Base, 92 | ); 93 | mainGroup = 08FB7794FE84155DC02AAC07 /* stressdrive */; 94 | projectDirPath = ""; 95 | projectRoot = ""; 96 | targets = ( 97 | 8DD76FA90486AB0100D96B5E /* stressdrive */, 98 | ); 99 | }; 100 | /* End PBXProject section */ 101 | 102 | /* Begin PBXSourcesBuildPhase section */ 103 | 8DD76FAB0486AB0100D96B5E /* Sources */ = { 104 | isa = PBXSourcesBuildPhase; 105 | buildActionMask = 2147483647; 106 | files = ( 107 | 8DD76FAC0486AB0100D96B5E /* stressdrive.c in Sources */, 108 | ); 109 | runOnlyForDeploymentPostprocessing = 0; 110 | }; 111 | /* End PBXSourcesBuildPhase section */ 112 | 113 | /* Begin XCBuildConfiguration section */ 114 | 1DEB928608733DD80010E9CD /* Debug */ = { 115 | isa = XCBuildConfiguration; 116 | buildSettings = { 117 | ALWAYS_SEARCH_USER_PATHS = NO; 118 | CLANG_ENABLE_OBJC_WEAK = YES; 119 | CODE_SIGN_IDENTITY = "-"; 120 | COPY_PHASE_STRIP = NO; 121 | GCC_DYNAMIC_NO_PIC = NO; 122 | GCC_MODEL_TUNING = G5; 123 | GCC_OPTIMIZATION_LEVEL = 0; 124 | HEADER_SEARCH_PATHS = "$(SEARCH_PREFIX)/include"; 125 | INSTALL_PATH = "$(PREFIX)/bin"; 126 | LIBRARY_SEARCH_PATHS = "$(SEARCH_PREFIX)/lib"; 127 | MACOSX_DEPLOYMENT_TARGET = 10.13; 128 | OTHER_LDFLAGS = "-lcrypto"; 129 | PREFIX = /opt/homebrew; 130 | PRODUCT_NAME = stressdrive; 131 | SEARCH_PREFIX = "$(PREFIX)/opt/openssl"; 132 | }; 133 | name = Debug; 134 | }; 135 | 1DEB928708733DD80010E9CD /* Release */ = { 136 | isa = XCBuildConfiguration; 137 | buildSettings = { 138 | ALWAYS_SEARCH_USER_PATHS = NO; 139 | CLANG_ENABLE_OBJC_WEAK = YES; 140 | CODE_SIGN_IDENTITY = "-"; 141 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 142 | GCC_MODEL_TUNING = G5; 143 | HEADER_SEARCH_PATHS = "$(SEARCH_PREFIX)/include"; 144 | INSTALL_PATH = "$(PREFIX)/bin"; 145 | LIBRARY_SEARCH_PATHS = "$(SEARCH_PREFIX)/lib"; 146 | MACOSX_DEPLOYMENT_TARGET = 10.13; 147 | OTHER_LDFLAGS = "-lcrypto"; 148 | PREFIX = /opt/homebrew; 149 | PRODUCT_NAME = stressdrive; 150 | SEARCH_PREFIX = "$(PREFIX)/opt/openssl"; 151 | }; 152 | name = Release; 153 | }; 154 | 1DEB928A08733DD80010E9CD /* Debug */ = { 155 | isa = XCBuildConfiguration; 156 | buildSettings = { 157 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 158 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 159 | CLANG_WARN_BOOL_CONVERSION = YES; 160 | CLANG_WARN_COMMA = YES; 161 | CLANG_WARN_CONSTANT_CONVERSION = YES; 162 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 163 | CLANG_WARN_EMPTY_BODY = YES; 164 | CLANG_WARN_ENUM_CONVERSION = YES; 165 | CLANG_WARN_INFINITE_RECURSION = YES; 166 | CLANG_WARN_INT_CONVERSION = YES; 167 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 168 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 169 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 170 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 171 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 172 | CLANG_WARN_STRICT_PROTOTYPES = YES; 173 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 174 | CLANG_WARN_UNREACHABLE_CODE = YES; 175 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 176 | ENABLE_STRICT_OBJC_MSGSEND = YES; 177 | ENABLE_TESTABILITY = YES; 178 | GCC_C_LANGUAGE_STANDARD = gnu99; 179 | GCC_NO_COMMON_BLOCKS = YES; 180 | GCC_OPTIMIZATION_LEVEL = 0; 181 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 182 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 183 | GCC_WARN_UNDECLARED_SELECTOR = YES; 184 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 185 | GCC_WARN_UNUSED_FUNCTION = YES; 186 | GCC_WARN_UNUSED_VARIABLE = YES; 187 | MACOSX_DEPLOYMENT_TARGET = 10.13; 188 | ONLY_ACTIVE_ARCH = YES; 189 | SDKROOT = macosx; 190 | }; 191 | name = Debug; 192 | }; 193 | 1DEB928B08733DD80010E9CD /* Release */ = { 194 | isa = XCBuildConfiguration; 195 | buildSettings = { 196 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 197 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 198 | CLANG_WARN_BOOL_CONVERSION = YES; 199 | CLANG_WARN_COMMA = YES; 200 | CLANG_WARN_CONSTANT_CONVERSION = YES; 201 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 202 | CLANG_WARN_EMPTY_BODY = YES; 203 | CLANG_WARN_ENUM_CONVERSION = YES; 204 | CLANG_WARN_INFINITE_RECURSION = YES; 205 | CLANG_WARN_INT_CONVERSION = YES; 206 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 207 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 208 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 209 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 210 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 211 | CLANG_WARN_STRICT_PROTOTYPES = YES; 212 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 213 | CLANG_WARN_UNREACHABLE_CODE = YES; 214 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 215 | ENABLE_STRICT_OBJC_MSGSEND = YES; 216 | GCC_C_LANGUAGE_STANDARD = gnu99; 217 | GCC_NO_COMMON_BLOCKS = YES; 218 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 219 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 220 | GCC_WARN_UNDECLARED_SELECTOR = YES; 221 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 222 | GCC_WARN_UNUSED_FUNCTION = YES; 223 | GCC_WARN_UNUSED_VARIABLE = YES; 224 | MACOSX_DEPLOYMENT_TARGET = 10.13; 225 | SDKROOT = macosx; 226 | }; 227 | name = Release; 228 | }; 229 | /* End XCBuildConfiguration section */ 230 | 231 | /* Begin XCConfigurationList section */ 232 | 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "stressdrive" */ = { 233 | isa = XCConfigurationList; 234 | buildConfigurations = ( 235 | 1DEB928608733DD80010E9CD /* Debug */, 236 | 1DEB928708733DD80010E9CD /* Release */, 237 | ); 238 | defaultConfigurationIsVisible = 0; 239 | defaultConfigurationName = Release; 240 | }; 241 | 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "stressdrive" */ = { 242 | isa = XCConfigurationList; 243 | buildConfigurations = ( 244 | 1DEB928A08733DD80010E9CD /* Debug */, 245 | 1DEB928B08733DD80010E9CD /* Release */, 246 | ); 247 | defaultConfigurationIsVisible = 0; 248 | defaultConfigurationName = Release; 249 | }; 250 | /* End XCConfigurationList section */ 251 | }; 252 | rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; 253 | } 254 | -------------------------------------------------------------------------------- /stressdrive.c: -------------------------------------------------------------------------------- 1 | // stressdrive.c 1.4 2 | // Copyright (c) 2011-2023 Jonathan 'Wolf' Rentzsch: http://rentzsch.com 3 | // Some rights reserved: http://opensource.org/licenses/mit 4 | // https://github.com/rentzsch/stressdrive 5 | 6 | #define _BSD_SOURCE 7 | #define _DEFAULT_SOURCE 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef __APPLE__ 28 | #import 29 | #include 30 | #endif 31 | 32 | #ifdef __linux__ 33 | #include 34 | #endif 35 | 36 | #define HASH_DIGEST_LENGTH SHA_DIGEST_LENGTH 37 | #define HASH_INIT_FUNCTION EVP_sha1 38 | 39 | #define CIPHER_KEY_SIZE (128 / 8) 40 | #define CIPHER_BLOCK_SIZE AES_BLOCK_SIZE 41 | #define CIPHER_INIT_FUNCTION EVP_aes_128_ctr 42 | 43 | #define EXIT_CALL_FAILED 2 44 | 45 | #define MAX(a, b) \ 46 | ({ \ 47 | __typeof__(a) _a = (a); \ 48 | __typeof__(b) _b = (b); \ 49 | _a > _b ? _a : _b; \ 50 | }) 51 | 52 | #define MIN(a, b) \ 53 | ({ \ 54 | __typeof__(a) _a = (a); \ 55 | __typeof__(b) _b = (b); \ 56 | _a < _b ? _a : _b; \ 57 | }) 58 | 59 | #define KILO 1000 60 | #define MEGA 1000000 61 | #define GIGA 1000000000 62 | 63 | #ifdef CLOCK_UPTIME_RAW 64 | #define MONOTONIC_CLOCK_ID CLOCK_UPTIME_RAW 65 | #else 66 | #define MONOTONIC_CLOCK_ID CLOCK_MONOTONIC 67 | #endif 68 | 69 | typedef struct { 70 | uint64_t total; 71 | const char *name; 72 | uint64_t start, last_display; 73 | } PROGRESS_CTX; 74 | 75 | uint64_t monotonic_time_ms() { 76 | struct timespec ts; 77 | clock_gettime(MONOTONIC_CLOCK_ID, &ts); 78 | return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 79 | } 80 | 81 | void print_ms_human(uint64_t ms, const char *prefix, const char *suffix) { 82 | uint64_t s = ms / 1000; 83 | uint64_t m = s / 60; 84 | uint64_t h = m / 60; 85 | printf("%s%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 "%s", prefix, h, m % 60, 86 | s % 60, suffix); 87 | } 88 | 89 | void PROGRESS_Init(PROGRESS_CTX *ctx, uint64_t total, const char *name) { 90 | ctx->total = total; 91 | ctx->name = name; 92 | ctx->start = monotonic_time_ms(); 93 | ctx->last_display = 0; 94 | } 95 | 96 | void _PROGRESS_Print(PROGRESS_CTX *ctx, uint64_t now, uint64_t current, 97 | uint32_t blockSize) { 98 | double complete = (double)current / (double)ctx->total; 99 | printf("\r%s %.1f%% (%" PRIu64 " of %" PRIu64 ")", ctx->name, 100 | complete * 100.0, current, ctx->total); 101 | 102 | uint64_t elapsed = now - ctx->start; 103 | print_ms_human(elapsed, " ", ""); 104 | 105 | if (elapsed > 0) { 106 | double speed = (double)current * blockSize * 1000 / elapsed; 107 | 108 | if (speed > GIGA) { 109 | printf(" (%.1f GB/s)", speed / GIGA); 110 | } else if (speed > MEGA) { 111 | printf(" (%.1f MB/s)", speed / MEGA); 112 | } else if (speed > KILO) { 113 | printf(" (%.1f KB/s)", speed / KILO); 114 | } else { 115 | printf(" (%.1f B/s)", speed); 116 | } 117 | } 118 | 119 | if (current != ctx->total && elapsed > 10000 && complete > 0.001) { 120 | uint64_t eta = (1 / complete - 1) * elapsed; 121 | print_ms_human(eta, " (ETA: ", ")"); 122 | } 123 | 124 | printf("\e[K"); 125 | fflush(stdout); 126 | } 127 | 128 | void PROGRESS_Update(PROGRESS_CTX *ctx, uint64_t current, uint32_t blockSize) { 129 | uint64_t now = monotonic_time_ms(); 130 | if (now - ctx->last_display < 1000) 131 | return; 132 | ctx->last_display = now; 133 | _PROGRESS_Print(ctx, now, current, blockSize); 134 | } 135 | 136 | void PROGRESS_Finish(PROGRESS_CTX *ctx, uint32_t blockSize) { 137 | uint64_t now = monotonic_time_ms(); 138 | _PROGRESS_Print(ctx, now, ctx->total, blockSize); 139 | printf("\n"); 140 | } 141 | 142 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 143 | #define EVP_MD_CTX_new EVP_MD_CTX_create 144 | #define EVP_MD_CTX_free EVP_MD_CTX_destroy 145 | #endif 146 | 147 | void DIGEST_Init(EVP_MD_CTX *digestContext) { 148 | if (1 != EVP_DigestInit_ex(digestContext, HASH_INIT_FUNCTION(), NULL)) { 149 | fprintf(stderr, "Digest initialisation failed\n"); 150 | exit(EXIT_CALL_FAILED); 151 | } 152 | } 153 | 154 | void DIGEST_Update(EVP_MD_CTX *digestContext, const void *d, size_t cnt) { 155 | if (1 != EVP_DigestUpdate(digestContext, d, cnt)) { 156 | fprintf(stderr, "Digest update failed\n"); 157 | exit(EXIT_CALL_FAILED); 158 | } 159 | } 160 | 161 | void DIGEST_Final(EVP_MD_CTX *digestContext, unsigned char *digest) { 162 | if (1 != EVP_DigestFinal_ex(digestContext, digest, NULL)) { 163 | fprintf(stderr, "Digest finalisation failed\n"); 164 | exit(EXIT_CALL_FAILED); 165 | } 166 | } 167 | 168 | void DIGEST_Print(unsigned char *digest, const char *name) { 169 | for (size_t i = 0; i < HASH_DIGEST_LENGTH; i++) { 170 | printf("%02x", digest[i]); 171 | } 172 | printf(" <= root hash digest of %s data\n", name); 173 | } 174 | 175 | #define BUFFER_COUNT 2 176 | 177 | typedef enum { 178 | Generate, 179 | Process, 180 | Read, 181 | Hash, 182 | } Action; 183 | 184 | typedef struct { 185 | uint8_t *data; 186 | Action action; 187 | bool written, hashed; 188 | pthread_mutex_t mutex; 189 | pthread_cond_t cond; 190 | } Buffer; 191 | 192 | typedef struct { 193 | Buffer buffers[BUFFER_COUNT]; 194 | uint64_t blockCount; 195 | uint16_t bufferBlocks; 196 | uint32_t blockSize; 197 | PROGRESS_CTX *progress; 198 | EVP_CIPHER_CTX *cipher; 199 | unsigned char *cipherInput; 200 | int fd; 201 | } Shared; 202 | 203 | void *generator_thread(void *arg) { 204 | Shared *shared = (Shared *)arg; 205 | 206 | uint64_t blockCount = shared->blockCount; 207 | uint16_t bufferBlocks = shared->bufferBlocks; 208 | uint32_t blockSize = shared->blockSize; 209 | PROGRESS_CTX *progress = shared->progress; 210 | EVP_CIPHER_CTX *cipher = shared->cipher; 211 | unsigned char *cipherInput = shared->cipherInput; 212 | 213 | int bufferIndex = 0; 214 | Buffer *buffer = &shared->buffers[0]; 215 | for (uint64_t blockIndex = 0; blockIndex < blockCount; 216 | blockIndex += bufferBlocks) { 217 | if (blockIndex) 218 | PROGRESS_Update(progress, blockIndex, blockSize); 219 | 220 | uint32_t size = 221 | (uint32_t)MIN(bufferBlocks, blockCount - blockIndex) * blockSize; 222 | 223 | if (buffer->action != Generate) { 224 | pthread_mutex_lock(&buffer->mutex); 225 | while (buffer->action != Generate) { 226 | pthread_cond_wait(&buffer->cond, &buffer->mutex); 227 | } 228 | pthread_mutex_unlock(&buffer->mutex); 229 | } 230 | 231 | int outSize; 232 | if (!EVP_EncryptUpdate(cipher, buffer->data, &outSize, cipherInput, 233 | size)) { 234 | fprintf(stderr, "EVP_EncryptUpdate() failed\n"); 235 | exit(EXIT_CALL_FAILED); 236 | } 237 | if (outSize != size) { 238 | fprintf(stderr, 239 | "EVP_EncryptUpdate() returned %d instead of %u bytes\n", 240 | outSize, size); 241 | exit(EXIT_CALL_FAILED); 242 | } 243 | 244 | pthread_mutex_lock(&buffer->mutex); 245 | buffer->action = Process; 246 | pthread_cond_broadcast(&buffer->cond); 247 | pthread_mutex_unlock(&buffer->mutex); 248 | 249 | bufferIndex = (bufferIndex + 1) % BUFFER_COUNT; 250 | buffer = &shared->buffers[bufferIndex]; 251 | } 252 | 253 | return NULL; 254 | } 255 | 256 | void *writer_thread(void *arg) { 257 | Shared *shared = (Shared *)arg; 258 | 259 | uint64_t blockCount = shared->blockCount; 260 | uint16_t bufferBlocks = shared->bufferBlocks; 261 | uint32_t blockSize = shared->blockSize; 262 | int fd = shared->fd; 263 | 264 | int bufferIndex = 0; 265 | Buffer *buffer = &shared->buffers[0]; 266 | for (uint64_t blockIndex = 0; blockIndex < blockCount; 267 | blockIndex += bufferBlocks) { 268 | uint32_t size = 269 | (uint32_t)MIN(bufferBlocks, blockCount - blockIndex) * blockSize; 270 | 271 | if (buffer->action != Process || buffer->written) { 272 | pthread_mutex_lock(&buffer->mutex); 273 | while (buffer->action != Process || buffer->written) { 274 | pthread_cond_wait(&buffer->cond, &buffer->mutex); 275 | } 276 | pthread_mutex_unlock(&buffer->mutex); 277 | } 278 | 279 | if (write(fd, buffer->data, size) != size) { 280 | perror("write() failed"); 281 | exit(EXIT_CALL_FAILED); 282 | } 283 | 284 | pthread_mutex_lock(&buffer->mutex); 285 | if (buffer->hashed) { 286 | buffer->action = Generate; 287 | buffer->hashed = false; 288 | } else { 289 | buffer->written = true; 290 | } 291 | pthread_cond_broadcast(&buffer->cond); 292 | pthread_mutex_unlock(&buffer->mutex); 293 | 294 | bufferIndex = (bufferIndex + 1) % BUFFER_COUNT; 295 | buffer = &shared->buffers[bufferIndex]; 296 | } 297 | 298 | return NULL; 299 | } 300 | 301 | void *reader_thread(void *arg) { 302 | Shared *shared = (Shared *)arg; 303 | 304 | uint64_t blockCount = shared->blockCount; 305 | uint16_t bufferBlocks = shared->bufferBlocks; 306 | uint32_t blockSize = shared->blockSize; 307 | PROGRESS_CTX *progress = shared->progress; 308 | int fd = shared->fd; 309 | 310 | int bufferIndex = 0; 311 | Buffer *buffer = &shared->buffers[0]; 312 | for (uint64_t blockIndex = 0; blockIndex < blockCount; 313 | blockIndex += bufferBlocks) { 314 | if (blockIndex) 315 | PROGRESS_Update(progress, blockIndex, blockSize); 316 | 317 | uint32_t size = 318 | (uint32_t)MIN(bufferBlocks, blockCount - blockIndex) * blockSize; 319 | 320 | if (buffer->action != Read) { 321 | pthread_mutex_lock(&buffer->mutex); 322 | while (buffer->action != Read) { 323 | pthread_cond_wait(&buffer->cond, &buffer->mutex); 324 | } 325 | pthread_mutex_unlock(&buffer->mutex); 326 | } 327 | 328 | if (read(fd, buffer->data, size) != size) { 329 | perror("read() failed"); 330 | exit(EXIT_CALL_FAILED); 331 | } 332 | 333 | pthread_mutex_lock(&buffer->mutex); 334 | buffer->action = Hash; 335 | pthread_cond_broadcast(&buffer->cond); 336 | pthread_mutex_unlock(&buffer->mutex); 337 | 338 | bufferIndex = (bufferIndex + 1) % BUFFER_COUNT; 339 | buffer = &shared->buffers[bufferIndex]; 340 | } 341 | 342 | return NULL; 343 | } 344 | 345 | int main(int argc, const char *argv[]) { 346 | if (argc != 2) { 347 | fprintf(stderr, "stressdrive v1.4\n"); 348 | #ifdef __APPLE__ 349 | fprintf(stderr, "Usage: sudo %s /dev/rdiskN\n", argv[0]); 350 | #else 351 | fprintf(stderr, "Usage: sudo %s /dev/sdX\n", argv[0]); 352 | #endif 353 | exit(EXIT_FAILURE); 354 | } 355 | 356 | const char *drivePath = argv[1]; 357 | int fd = open(drivePath, O_RDWR); 358 | if (fd == -1) { 359 | perror("open() failed"); 360 | exit(EXIT_CALL_FAILED); 361 | } 362 | 363 | if (flock(fd, LOCK_EX | LOCK_NB) == -1) { 364 | perror("flock() failed"); 365 | exit(EXIT_CALL_FAILED); 366 | } 367 | 368 | uint32_t blockSize; 369 | #ifdef DKIOCGETBLOCKSIZE 370 | if (ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize) == -1) { 371 | #else 372 | if (ioctl(fd, BLKSSZGET, &blockSize) == -1) { 373 | #endif 374 | perror("getting block size using ioctl failed"); 375 | exit(EXIT_CALL_FAILED); 376 | } 377 | printf("disk block size: %u\n", blockSize); 378 | 379 | uint64_t blockCount; 380 | #ifdef DKIOCGETBLOCKCOUNT 381 | if (ioctl(fd, DKIOCGETBLOCKCOUNT, &blockCount) == -1) { 382 | #else 383 | if (ioctl(fd, BLKGETSIZE64, &blockCount) != -1) { 384 | blockCount /= blockSize; 385 | } else { 386 | #endif 387 | perror("getting block count using ioctl failed"); 388 | exit(EXIT_CALL_FAILED); 389 | } 390 | printf("disk block count: %" PRIu64 "\n", blockCount); 391 | 392 | uint32_t bufferSize = MAX(blockSize, 8 * 1024 * 1024); 393 | printf("buffer size: %u\n", bufferSize); 394 | 395 | uint16_t bufferBlocks = bufferSize / blockSize; 396 | uint32_t checkFrequency = 1024 * 1024 * 1024 / blockSize; 397 | uint64_t checkCount = (blockCount + checkFrequency - 1) / checkFrequency; 398 | uint8_t *checkDigests = malloc(checkCount * HASH_DIGEST_LENGTH); 399 | if (checkDigests == NULL) { 400 | perror("malloc() failed"); 401 | exit(EXIT_CALL_FAILED); 402 | } 403 | 404 | #ifdef __APPLE__ 405 | IOPMAssertionID noIdleSleepAssertionID; 406 | IOReturn noIdleSleepAssertionCreated = IOPMAssertionCreateWithName( 407 | kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, 408 | CFSTR("stressdrive running"), &noIdleSleepAssertionID); 409 | if (kIOReturnSuccess == noIdleSleepAssertionCreated) { 410 | printf("succesfully created no idle sleep assertion\n"); 411 | } else { 412 | printf("failed to create no idle sleep assertion\n"); 413 | } 414 | #endif 415 | 416 | EVP_MD_CTX *digestContext, *rootDigestContext; 417 | if ((digestContext = EVP_MD_CTX_new()) == NULL || 418 | (rootDigestContext = EVP_MD_CTX_new()) == NULL) { 419 | fprintf(stderr, "Digest context creation failed\n"); 420 | exit(EXIT_CALL_FAILED); 421 | } 422 | 423 | PROGRESS_CTX progress; 424 | 425 | unsigned char cipherKey[CIPHER_KEY_SIZE]; 426 | if (!RAND_bytes(cipherKey, CIPHER_KEY_SIZE)) { 427 | fprintf(stderr, "RAND_bytes() failed\n"); 428 | exit(EXIT_CALL_FAILED); 429 | } 430 | 431 | unsigned char cipherIv[CIPHER_BLOCK_SIZE]; 432 | if (!RAND_bytes(cipherIv, CIPHER_BLOCK_SIZE)) { 433 | fprintf(stderr, "RAND_bytes() failed\n"); 434 | exit(EXIT_CALL_FAILED); 435 | } 436 | 437 | EVP_CIPHER_CTX *cipher = EVP_CIPHER_CTX_new(); 438 | if (!cipher) { 439 | fprintf(stderr, "EVP_CIPHER_CTX_new() failed\n"); 440 | exit(EXIT_CALL_FAILED); 441 | } 442 | if (!EVP_EncryptInit(cipher, CIPHER_INIT_FUNCTION(), cipherKey, cipherIv)) { 443 | fprintf(stderr, "EVP_EncryptInit() failed\n"); 444 | exit(EXIT_CALL_FAILED); 445 | } 446 | 447 | unsigned char *cipherInput = malloc(bufferSize); 448 | memset(cipherInput, 0, bufferSize); 449 | 450 | Shared *shared = &(Shared){ 451 | .blockCount = blockCount, 452 | .bufferBlocks = bufferBlocks, 453 | .blockSize = blockSize, 454 | .progress = &progress, 455 | .cipher = cipher, 456 | .cipherInput = cipherInput, 457 | .fd = fd, 458 | }; 459 | 460 | for (int i = 0; i < BUFFER_COUNT; i++) { 461 | if ((shared->buffers[i].data = malloc(bufferSize)) == NULL) { 462 | perror("malloc() failed"); 463 | exit(EXIT_CALL_FAILED); 464 | } 465 | shared->buffers[i].action = Generate; 466 | if (pthread_mutex_init(&shared->buffers[i].mutex, NULL) != 0) { 467 | perror("pthread_mutex_init() failed"); 468 | exit(EXIT_CALL_FAILED); 469 | } 470 | if (pthread_cond_init(&shared->buffers[i].cond, NULL) != 0) { 471 | perror("pthread_cond_init() failed"); 472 | exit(EXIT_CALL_FAILED); 473 | } 474 | } 475 | 476 | pthread_t t_reader, t_generator, t_writer; 477 | int bufferIndex; 478 | Buffer *buffer; 479 | 480 | printf("writing random data to %s\n", drivePath); 481 | DIGEST_Init(digestContext); 482 | PROGRESS_Init(&progress, blockCount, "writing"); 483 | 484 | pthread_create(&t_generator, NULL, generator_thread, shared); 485 | pthread_create(&t_writer, NULL, writer_thread, shared); 486 | 487 | bufferIndex = 0; 488 | buffer = &shared->buffers[0]; 489 | for (uint64_t blockIndex = 0; blockIndex < blockCount; 490 | blockIndex += bufferBlocks) { 491 | uint32_t size = 492 | (uint32_t)MIN(bufferBlocks, blockCount - blockIndex) * blockSize; 493 | 494 | if (buffer->action != Process || buffer->hashed) { 495 | pthread_mutex_lock(&buffer->mutex); 496 | while (buffer->action != Process || buffer->hashed) { 497 | pthread_cond_wait(&buffer->cond, &buffer->mutex); 498 | } 499 | pthread_mutex_unlock(&buffer->mutex); 500 | } 501 | 502 | DIGEST_Update(digestContext, buffer->data, size); 503 | 504 | pthread_mutex_lock(&buffer->mutex); 505 | if (buffer->written) { 506 | buffer->action = Generate; 507 | buffer->written = false; 508 | } else { 509 | buffer->hashed = true; 510 | } 511 | pthread_cond_broadcast(&buffer->cond); 512 | pthread_mutex_unlock(&buffer->mutex); 513 | 514 | bufferIndex = (bufferIndex + 1) % BUFFER_COUNT; 515 | buffer = &shared->buffers[bufferIndex]; 516 | 517 | uint64_t hashedBlocks = blockIndex + bufferBlocks; 518 | if (hashedBlocks % checkFrequency == 0 || hashedBlocks >= blockCount) { 519 | uint64_t checkIndex = blockIndex / checkFrequency; 520 | DIGEST_Final(digestContext, 521 | checkDigests + checkIndex * HASH_DIGEST_LENGTH); 522 | if (hashedBlocks < blockCount) { 523 | DIGEST_Init(digestContext); 524 | } 525 | } 526 | } 527 | 528 | pthread_join(t_writer, NULL); 529 | pthread_join(t_generator, NULL); 530 | 531 | PROGRESS_Finish(&progress, blockSize); 532 | EVP_CIPHER_CTX_free(cipher); 533 | 534 | uint8_t writtenHashDigest[HASH_DIGEST_LENGTH]; 535 | DIGEST_Init(rootDigestContext); 536 | DIGEST_Update(rootDigestContext, checkDigests, 537 | checkCount * HASH_DIGEST_LENGTH); 538 | DIGEST_Final(rootDigestContext, writtenHashDigest); 539 | DIGEST_Print(writtenHashDigest, "written"); 540 | 541 | if (lseek(fd, 0LL, SEEK_SET) != 0LL) { 542 | perror("lseek() failed"); 543 | exit(EXIT_CALL_FAILED); 544 | } 545 | 546 | int exitCode = EXIT_SUCCESS; 547 | uint8_t readHashDigest[HASH_DIGEST_LENGTH]; 548 | 549 | for (int i = 0; i < BUFFER_COUNT; i++) { 550 | shared->buffers[i].action = Read; 551 | } 552 | 553 | printf("verifying written data\n"); 554 | DIGEST_Init(digestContext); 555 | DIGEST_Init(rootDigestContext); 556 | PROGRESS_Init(&progress, blockCount, "reading"); 557 | 558 | pthread_create(&t_reader, NULL, reader_thread, shared); 559 | 560 | bufferIndex = 0; 561 | buffer = &shared->buffers[0]; 562 | for (uint64_t blockIndex = 0; blockIndex < blockCount; 563 | blockIndex += bufferBlocks) { 564 | uint32_t size = 565 | (uint32_t)MIN(bufferBlocks, blockCount - blockIndex) * blockSize; 566 | 567 | if (buffer->action != Hash) { 568 | pthread_mutex_lock(&buffer->mutex); 569 | while (buffer->action != Hash) { 570 | pthread_cond_wait(&buffer->cond, &buffer->mutex); 571 | } 572 | pthread_mutex_unlock(&buffer->mutex); 573 | } 574 | 575 | DIGEST_Update(digestContext, buffer->data, size); 576 | 577 | pthread_mutex_lock(&buffer->mutex); 578 | buffer->action = Read; 579 | pthread_cond_broadcast(&buffer->cond); 580 | pthread_mutex_unlock(&buffer->mutex); 581 | 582 | bufferIndex = (bufferIndex + 1) % BUFFER_COUNT; 583 | buffer = &shared->buffers[bufferIndex]; 584 | 585 | uint64_t hashedBlocks = blockIndex + bufferBlocks; 586 | if (hashedBlocks % checkFrequency == 0 || hashedBlocks >= blockCount) { 587 | uint64_t checkIndex = blockIndex / checkFrequency; 588 | DIGEST_Final(digestContext, readHashDigest); 589 | DIGEST_Update(rootDigestContext, readHashDigest, 590 | HASH_DIGEST_LENGTH); 591 | if (bcmp(checkDigests + checkIndex * HASH_DIGEST_LENGTH, 592 | readHashDigest, HASH_DIGEST_LENGTH) != 0) { 593 | printf("\nFailed intermediate checksum for bytes %" PRIu64 594 | "...%" PRIu64 "\n", 595 | (blockIndex + bufferBlocks - checkFrequency) * blockSize, 596 | blockIndex * blockSize + size); 597 | exitCode = EXIT_FAILURE; 598 | } 599 | if (hashedBlocks < blockCount) { 600 | DIGEST_Init(digestContext); 601 | } 602 | } 603 | } 604 | 605 | pthread_join(t_reader, NULL); 606 | 607 | PROGRESS_Finish(&progress, blockSize); 608 | DIGEST_Final(rootDigestContext, readHashDigest); 609 | DIGEST_Print(readHashDigest, "read"); 610 | EVP_MD_CTX_free(digestContext); 611 | EVP_MD_CTX_free(rootDigestContext); 612 | 613 | if (exitCode == EXIT_SUCCESS && 614 | bcmp(writtenHashDigest, readHashDigest, HASH_DIGEST_LENGTH) == 0) { 615 | printf("SUCCESS\n"); 616 | } else { 617 | printf("FAILURE\n"); 618 | exitCode = EXIT_FAILURE; 619 | } 620 | 621 | #ifdef __APPLE__ 622 | if (kIOReturnSuccess == noIdleSleepAssertionCreated) { 623 | if (kIOReturnSuccess == IOPMAssertionRelease(noIdleSleepAssertionID)) { 624 | printf("succesfully released no idle sleep assertion\n"); 625 | } else { 626 | printf("failed to release no idle sleep assertion\n"); 627 | } 628 | } 629 | #endif 630 | 631 | for (int i = 0; i < BUFFER_COUNT; i++) { 632 | free(shared->buffers[i].data); 633 | pthread_mutex_destroy(&shared->buffers[i].mutex); 634 | pthread_cond_destroy(&shared->buffers[i].cond); 635 | } 636 | 637 | free(checkDigests); 638 | close(fd); 639 | 640 | return exitCode; 641 | } 642 | --------------------------------------------------------------------------------