Created
March 14, 2017 22:11
-
-
Save garybernhardt/a5e166653605c43b048cffbf5333edf3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# This script tests par2 recovery when the par2 files themselves are corrupted. | |
# Process: | |
# 1. Generate a file containing all 256 possible bytes. | |
# (More would be better, but it gets slow fast.) | |
# 2. Generate par2 data for the file. | |
# 3. Individually corrupt each par2 file at each offset. | |
# (Write byte 0 unless the offset already contains byte 0; then, write byte 255.) | |
# (Writing each possible byte would be better, but it gets slow fast.) | |
# 4. After each corruption, verify the par2 data, then reverse the corruption. | |
# 5. Produce a summary of what the par2 verification commands' output, along with the frequencies of each output string. | |
# | |
# par2 0.6.14 passes this test. | |
require "fileutils" | |
TEMP_DIR = "temp_dir" | |
RESULTS = Hash.new { 0 } | |
def in_temp_dir(&block) | |
if Dir.exists?(TEMP_DIR) | |
FileUtils.rm_rf(TEMP_DIR) | |
end | |
Dir.mkdir(TEMP_DIR) | |
Dir.chdir(TEMP_DIR, &block) | |
end | |
def create_par_archive | |
# Write a file containing all 256 bytes. | |
test_data = (0...256).map(&:chr).join("") | |
File.write("data_file", test_data) | |
# Create the par archive. | |
`par2 create -R data.par2 data_file` | |
# Corrupt the data file (the par archive is now the only source of correct data.) | |
File.write("data_file", "x", 0) | |
end | |
def corrupt_file_at_all_offsets(path) | |
size = File.stat(path).size | |
# Corrupt each byte of the file separately. | |
(0...size).each do |offset| | |
# Back up existing character. | |
old_char = File.read(path, 1, offset) | |
# Generate a new character to corrupt with. | |
corruption_char = old_char == 0.chr ? 255.chr : 0.chr | |
# Corrupt file. | |
File.write(path, corruption_char, offset) | |
# Verify. Par2 exits with 0 on success, 1 if it can recover, and 2 if it | |
# couldn't recover. Because we're verifying a corrupted file, we always | |
# expect exit status 1. | |
result = `par2 verify data.par2` | |
unless $?.exitstatus == 1 | |
puts | |
puts result | |
puts | |
raise RuntimeError.new("failed") | |
end | |
# Tally the par2 verification output text by the number of times it | |
# occurred. | |
RESULTS[result] += 1 | |
# Restore. | |
File.write(path, old_char, offset) | |
# Print progress. | |
puts "#{path} %2i%%" % (100 * offset.to_f / size) | |
end | |
end | |
def summarize | |
RESULTS.to_a.sort_by { |message, times| times }.each do |message, times| | |
puts | |
puts "==== This message occurred #{times} times ".ljust(79, "=") | |
puts | |
puts message | |
end | |
end | |
def main | |
in_temp_dir do | |
create_par_archive | |
Dir["*.par2"].each do |path| | |
corrupt_file_at_all_offsets(path) | |
end | |
summarize | |
end | |
end | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment