safeguard 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/Gemfile +10 -0
- data/Rakefile +2 -43
- data/bin/safeguard +1 -4
- data/lib/safeguard/command/add.rb +14 -16
- data/lib/safeguard/command/hash.rb +22 -8
- data/lib/safeguard/command/init.rb +7 -7
- data/lib/safeguard/command/verify.rb +17 -17
- data/lib/safeguard/command.rb +14 -31
- data/lib/safeguard/core_ext/file.rb +23 -0
- data/lib/safeguard/digest/crc32.rb +46 -0
- data/lib/safeguard/digest.rb +52 -3
- data/lib/safeguard/hasher.rb +60 -0
- data/lib/safeguard/repository/hash_table.rb +54 -0
- data/lib/safeguard/repository.rb +66 -26
- data/lib/safeguard/verifier.rb +88 -0
- data/lib/safeguard/version.rb +1 -1
- data/lib/safeguard.rb +5 -0
- data/safeguard.gemspec +5 -0
- metadata +48 -4
- data/lib/safeguard/hash_table.rb +0 -71
data/.gitignore
CHANGED
data/Gemfile
ADDED
data/Rakefile
CHANGED
@@ -1,44 +1,3 @@
|
|
1
|
-
|
2
|
-
gem_dir = File.join this_dir, 'gem'
|
3
|
-
spec_file = File.join this_dir, 'safeguard.gemspec'
|
1
|
+
require 'rookie'
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
task :mkdir do
|
8
|
-
FileUtils.mkdir_p gem_dir
|
9
|
-
end
|
10
|
-
|
11
|
-
task :gem => :mkdir do
|
12
|
-
gem_file = File.join this_dir, Gem::Builder.new(spec).build
|
13
|
-
FileUtils.mv gem_file, gem_dir
|
14
|
-
end
|
15
|
-
|
16
|
-
namespace :gem do
|
17
|
-
|
18
|
-
task :build => :gem
|
19
|
-
|
20
|
-
gem_file = File.join gem_dir, "#{spec.name}-#{spec.version}.gem"
|
21
|
-
|
22
|
-
task :push => :gem do
|
23
|
-
sh "gem push #{gem_file}"
|
24
|
-
end
|
25
|
-
|
26
|
-
task :install => :gem do
|
27
|
-
sh "gem install #{gem_file}"
|
28
|
-
end
|
29
|
-
|
30
|
-
task :uninstall do
|
31
|
-
sh "gem uninstall #{spec.name}"
|
32
|
-
end
|
33
|
-
|
34
|
-
task :clean do
|
35
|
-
FileUtils.rm_rf gem_dir
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
task :clean => 'gem:clean'
|
41
|
-
|
42
|
-
task :setup => [ 'gem:install', :clean ]
|
43
|
-
|
44
|
-
task :default => :setup
|
3
|
+
Rookie::Tasks.new 'safeguard.gemspec'
|
data/bin/safeguard
CHANGED
@@ -1,29 +1,27 @@
|
|
1
1
|
require 'safeguard/command'
|
2
|
+
require 'safeguard/digest'
|
2
3
|
|
3
4
|
module Safeguard
|
4
|
-
|
5
|
+
class Command
|
5
6
|
|
6
7
|
# Adds files to a Repository.
|
7
|
-
|
8
|
+
class Add < Command
|
8
9
|
|
9
|
-
|
10
|
+
opt :functions, '--use', Symbol,
|
11
|
+
"Algorithm used to calculate the file's checksum. " <<
|
12
|
+
"Currently supported: #{Digest::SUPPORTED_ALGORITHMS.join(', ')}",
|
13
|
+
arity: [1,0], on_multiple: :append
|
14
|
+
|
15
|
+
opt :force, '--force', 'Rehash files that are already in the repository'
|
10
16
|
|
11
17
|
# For every argument, try to add it to the Repository in the current
|
12
18
|
# directory.
|
13
|
-
|
14
|
-
repo = Repository.new
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
puts "Adding #{filename}..."
|
19
|
-
repo.track filename
|
20
|
-
# If an exception is raised, count will not be incremented.
|
21
|
-
count += 1
|
22
|
-
rescue => e
|
23
|
-
puts e.message
|
24
|
-
end
|
19
|
+
action do |options, args|
|
20
|
+
repo = Repository.new options.dir
|
21
|
+
repo.before_save do
|
22
|
+
repo.add_files! *args, :functions => options.functions,
|
23
|
+
:force => options.force?
|
25
24
|
end
|
26
|
-
puts "Added #{count} files to repository."
|
27
25
|
end
|
28
26
|
|
29
27
|
end
|
@@ -2,17 +2,31 @@ require 'safeguard/command'
|
|
2
2
|
require 'safeguard/digest'
|
3
3
|
|
4
4
|
module Safeguard
|
5
|
-
|
5
|
+
class Command
|
6
6
|
|
7
|
-
# Outputs the
|
8
|
-
|
7
|
+
# Outputs the checksum of files.
|
8
|
+
#
|
9
|
+
# $ safeguard hash *.mp3
|
10
|
+
#
|
11
|
+
# The hash function can be specified with the --hash-function option. The
|
12
|
+
# default is SHA1, but MD5 and CRC32 are also supported.
|
13
|
+
#
|
14
|
+
# $ safeguard --hash-function md5 hash *.mp3
|
15
|
+
class Hash < Command
|
9
16
|
|
10
|
-
|
17
|
+
opt :func, '--hash-function', '--hash-functions', Symbol,
|
18
|
+
"Algorithm to use to calculate the file's checksum. " <<
|
19
|
+
"Currently supported: #{Digest::SUPPORTED_ALGORITHMS.join(', ')}",
|
20
|
+
default: :sha1, arity: [1,-1]
|
11
21
|
|
12
|
-
# For every argument, outputs its
|
13
|
-
|
14
|
-
|
15
|
-
|
22
|
+
# For every argument, outputs its checksum if it exists as a file.
|
23
|
+
action do |options, args|
|
24
|
+
hasher = Hasher.new *args, :functions => options.func
|
25
|
+
hasher.each do |file, hash_data|
|
26
|
+
puts "#{file}:"
|
27
|
+
hasher.functions.each do |function|
|
28
|
+
puts "\t#{function} => #{hash_data[function]}"
|
29
|
+
end
|
16
30
|
end
|
17
31
|
end
|
18
32
|
|
@@ -3,20 +3,20 @@ require 'safeguard/repository'
|
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
5
|
module Safeguard
|
6
|
-
|
6
|
+
class Command
|
7
7
|
|
8
8
|
# Initializes a Repository in a given directory.
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
#
|
10
|
+
# $ safeguard init .
|
11
|
+
class Init < Command
|
12
12
|
|
13
13
|
# Initializes a Safeguard Repository in a directory, which is either the
|
14
14
|
# current directory, if +args+ is empty, or the directory designated by
|
15
15
|
# the last element in +args+.
|
16
|
-
|
17
|
-
|
18
|
-
dir = File.expand_path args.pop
|
16
|
+
action do |options, args|
|
17
|
+
dir = File.expand_path(args.pop || options.dir)
|
19
18
|
repo = Repository.new(dir)
|
19
|
+
repo.create_directory!
|
20
20
|
puts "Initialized safeguard repository in #{repo.dir}"
|
21
21
|
end
|
22
22
|
|
@@ -1,33 +1,33 @@
|
|
1
1
|
require 'safeguard/command'
|
2
2
|
|
3
3
|
module Safeguard
|
4
|
-
|
4
|
+
class Command
|
5
5
|
|
6
6
|
# Verifies the files present in a Repository.
|
7
|
-
|
7
|
+
class Verify < Command
|
8
8
|
|
9
|
-
|
9
|
+
opt :functions, '--use', Symbol,
|
10
|
+
"Algorithm used to calculate the file's checksum. " <<
|
11
|
+
"Currently supported: #{Digest::SUPPORTED_ALGORITHMS.join(', ')}",
|
12
|
+
arity: [1,0], on_multiple: :append
|
10
13
|
|
11
14
|
# Verify the files passed as arguments using information from the
|
12
15
|
# Repository in the current directory.
|
13
|
-
|
14
|
-
repo = Repository.new
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
action do |options, args|
|
17
|
+
repo = Repository.new options.dir
|
18
|
+
functions = options.functions
|
19
|
+
results = repo.verify_files *args, functions: functions
|
20
|
+
results.keys.each do |file|
|
21
|
+
puts "#{file}:"
|
22
|
+
results[file].keys.each do |function|
|
23
|
+
value = results[file][function]
|
24
|
+
value = "File not in repository" if value == :file_missing
|
25
|
+
value = "Hash not in repository" if value == :hash_missing
|
26
|
+
puts "\t#{function} => #{value}"
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
26
30
|
|
27
|
-
def self.display_result(filename, result)
|
28
|
-
puts "#{filename} => #{result ? 'OK' : 'Mismatch'}"
|
29
|
-
end
|
30
|
-
|
31
31
|
end
|
32
32
|
|
33
33
|
end
|
data/lib/safeguard/command.rb
CHANGED
@@ -1,42 +1,25 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Manages the commands that may be given to Safeguard.
|
4
|
-
module Command
|
5
|
-
|
6
|
-
instance_eval do
|
1
|
+
require 'acclaim/command'
|
2
|
+
require 'acclaim/version'
|
7
3
|
|
8
|
-
|
9
|
-
def commands
|
10
|
-
@commands ||= {}
|
11
|
-
end
|
12
|
-
|
13
|
-
alias :all :commands
|
4
|
+
module Safeguard
|
14
5
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
commands[name] = command_module
|
20
|
-
end
|
6
|
+
# Main Safeguard command. Currently just invokes the init command.
|
7
|
+
#
|
8
|
+
# $ safeguard .
|
9
|
+
class Command < Acclaim::Command
|
21
10
|
|
22
|
-
|
23
|
-
|
24
|
-
def find(command_name)
|
25
|
-
name = command_name.to_s.strip.downcase
|
26
|
-
commands[name].tap do |command|
|
27
|
-
raise "unsupported command: #{name}" if command.nil?
|
28
|
-
end
|
29
|
-
end
|
11
|
+
help
|
12
|
+
version Acclaim::Version::STRING
|
30
13
|
|
31
|
-
|
32
|
-
|
33
|
-
def invoke(command_name, *args)
|
34
|
-
find(command_name).execute(*args)
|
35
|
-
end
|
14
|
+
opt :dir, '-D', '--dir', '--directory', 'Directory in which the repository is.',
|
15
|
+
default: Dir.pwd, arity: [1,0]
|
36
16
|
|
17
|
+
action do |options, args|
|
18
|
+
Init.execute options, args
|
37
19
|
end
|
38
20
|
|
39
21
|
end
|
22
|
+
|
40
23
|
end
|
41
24
|
|
42
25
|
require 'safeguard/command/add'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Safeguard
|
2
|
+
module CoreExt
|
3
|
+
module File
|
4
|
+
|
5
|
+
# Default size of chunks in bytes. A megabyte is equal to 2^20 bytes, or
|
6
|
+
# 1,048,576 bytes.
|
7
|
+
DEFAULT_CHUNK_SIZE = 2 ** 20
|
8
|
+
|
9
|
+
# Reads a file piece by piece, yielding every chunk until the end of the
|
10
|
+
# file. Will yield chunks of DEFAULT_CHUNK_SIZE bytes, unless specified.
|
11
|
+
def each_chunk(chunk_size = DEFAULT_CHUNK_SIZE)
|
12
|
+
# A chunk_size of zero will cause an infinite loop. It will keep reading
|
13
|
+
# 0 bytes, so the pointer will never change and the end of the file will
|
14
|
+
# never be reached.
|
15
|
+
bytes = case chunk_size when nil, 0 then nil else chunk_size.abs end
|
16
|
+
yield read bytes until eof?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
::File.send :include, Safeguard::CoreExt::File
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
module Safeguard
|
5
|
+
module Digest
|
6
|
+
|
7
|
+
# Digest implementation of CRC32 using Zlib.
|
8
|
+
class CRC32 < ::Digest::Class
|
9
|
+
|
10
|
+
include ::Digest::Instance
|
11
|
+
|
12
|
+
INITIAL_VALUE = 0
|
13
|
+
|
14
|
+
attr_reader :crc32
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
reset
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset
|
21
|
+
@crc32 = INITIAL_VALUE
|
22
|
+
end
|
23
|
+
|
24
|
+
def update(str)
|
25
|
+
@crc32 = Zlib.crc32(str, crc32)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
alias << update
|
30
|
+
|
31
|
+
def finish
|
32
|
+
[ crc32 ].pack 'N'
|
33
|
+
end
|
34
|
+
|
35
|
+
def digest_length
|
36
|
+
1
|
37
|
+
end
|
38
|
+
|
39
|
+
def block_length
|
40
|
+
1
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/safeguard/digest.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'safeguard/core_ext/file'
|
2
|
+
require 'safeguard/digest/crc32'
|
1
3
|
require 'openssl'
|
2
4
|
|
3
5
|
module Safeguard
|
@@ -5,9 +7,56 @@ module Safeguard
|
|
5
7
|
# Encapsulates checksum computation for files using a set of hash functions.
|
6
8
|
module Digest
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
10
|
+
SUPPORTED_ALGORITHMS = %w(sha1 md5 crc32).map!(&:to_sym).freeze
|
11
|
+
|
12
|
+
# Digests a file using an +algorithm+.
|
13
|
+
#
|
14
|
+
# Algorithms are expected include the Digest::Instance module.
|
15
|
+
#
|
16
|
+
# OpenSSL::Digest::SHA1.include? Digest::Instance
|
17
|
+
# => true
|
18
|
+
def self.digest_with(algorithm, filename)
|
19
|
+
digest = algorithm.new
|
20
|
+
File.open(filename, 'rb') do |file|
|
21
|
+
file.each_chunk do |chunk|
|
22
|
+
digest << chunk
|
23
|
+
end
|
24
|
+
end
|
25
|
+
digest.hexdigest!
|
26
|
+
end
|
27
|
+
|
28
|
+
private_class_method :digest_with
|
29
|
+
|
30
|
+
# Computes the SHA1 sum of the given file.
|
31
|
+
def self.sha1(*args)
|
32
|
+
digest_with OpenSSL::Digest::SHA1, *args
|
33
|
+
end
|
34
|
+
|
35
|
+
# Computes the MD5 sum of the given file.
|
36
|
+
def self.md5(*args)
|
37
|
+
digest_with OpenSSL::Digest::MD5, *args
|
38
|
+
end
|
39
|
+
|
40
|
+
# Computes the CRC32 sum of the given file.
|
41
|
+
def self.crc32(*args)
|
42
|
+
# Read file in binary mode. Doesn't make any difference in *nix, but Ruby
|
43
|
+
# will attempt to convert line endings if the file is opened in text mode
|
44
|
+
# in other platforms.
|
45
|
+
digest_with Safeguard::Digest::CRC32, *args
|
46
|
+
end
|
47
|
+
|
48
|
+
# Digests a file using a hash function, which can be the symbol of any
|
49
|
+
# Digest module method that takes a file. Uses SHA1 by default.
|
50
|
+
#
|
51
|
+
# Safeguard::Digest.file(file, :md5)
|
52
|
+
#
|
53
|
+
# Is equivalent to:
|
54
|
+
#
|
55
|
+
# Safeguard::Digest.md5(file)
|
56
|
+
def self.file(file, hash_function = :sha1)
|
57
|
+
f = hash_function.to_sym
|
58
|
+
raise ArgumentError, "Unsupported hash function: #{f}" unless respond_to? f
|
59
|
+
send hash_function, file
|
11
60
|
end
|
12
61
|
|
13
62
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'safeguard/digest'
|
2
|
+
require 'ribbon'
|
3
|
+
require 'ribbon/core_ext/array'
|
4
|
+
|
5
|
+
module Safeguard
|
6
|
+
|
7
|
+
# Hashes a set of files.
|
8
|
+
class Hasher
|
9
|
+
|
10
|
+
# The files which will be hashed.
|
11
|
+
attr_accessor :files
|
12
|
+
|
13
|
+
# The hash functions to use.
|
14
|
+
attr_accessor :functions
|
15
|
+
|
16
|
+
# Initializes a new hasher with the given files. Last argument can be an
|
17
|
+
# options hash or ribbon which specifies the hash functions to use:
|
18
|
+
#
|
19
|
+
# files = %w(file1 file2 fil3)
|
20
|
+
# Hasher.new *files, :functions => :sha1
|
21
|
+
# Hasher.new *files, :functions => [ :sha1, :md5, :crc32 ]
|
22
|
+
def initialize(*args)
|
23
|
+
ribbon = args.extract_options_as_ribbon!
|
24
|
+
funcs = ribbon.functions? do
|
25
|
+
raise ArgumentError, 'No hash functions specified'
|
26
|
+
end
|
27
|
+
self.functions = [*funcs]
|
28
|
+
self.files = args
|
29
|
+
end
|
30
|
+
|
31
|
+
# Calculates the hash of each file. Updates the cached hash results.
|
32
|
+
def hash_files!
|
33
|
+
results = Ribbon.new
|
34
|
+
files.each do |file|
|
35
|
+
functions.each do |function|
|
36
|
+
results[file][function] = Safeguard::Digest.file file, function
|
37
|
+
end
|
38
|
+
end
|
39
|
+
results = Ribbon[results]
|
40
|
+
results.wrap_all!
|
41
|
+
@results = results
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the cached results containing the hashes calculated for each file.
|
45
|
+
#
|
46
|
+
# In order to force hash recalculation, call #hash_files!.
|
47
|
+
def results
|
48
|
+
@results ||= hash_files!
|
49
|
+
end
|
50
|
+
|
51
|
+
# Yields file => hash data pairs or returns an enumerator.
|
52
|
+
def each(&block)
|
53
|
+
Ribbon[results].each &block
|
54
|
+
end
|
55
|
+
|
56
|
+
include Enumerable
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'safeguard/digest'
|
2
|
+
require 'ribbon'
|
3
|
+
require 'ribbon/core_ext'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module Safeguard
|
7
|
+
class Repository
|
8
|
+
|
9
|
+
# Holds filename => checksum pairs.
|
10
|
+
class HashTable
|
11
|
+
|
12
|
+
# Initializes this hash table with the contents of the given Ribbon.
|
13
|
+
def initialize(ribbon = nil, &block)
|
14
|
+
merge! ribbon, &block if ribbon
|
15
|
+
end
|
16
|
+
|
17
|
+
# Saves the HashTable to a YAML file.
|
18
|
+
def save(filename)
|
19
|
+
File.open(filename, 'w') do |file|
|
20
|
+
file.puts ribbon.to_yaml
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Loads the HashTable from a YAML file.
|
25
|
+
def self.load(filename)
|
26
|
+
new Ribbon::Wrapper.from_yaml File.read(filename)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Merges this hash table's data with the other's.
|
30
|
+
def merge!(other)
|
31
|
+
other = other.ribbon if other.is_a? HashTable
|
32
|
+
Ribbon.deep_merge! ribbon, other
|
33
|
+
end
|
34
|
+
|
35
|
+
# Looks up the checksum data for the given +filename+.
|
36
|
+
def [](filename)
|
37
|
+
ribbon[filename]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a list of files present in this hash table.
|
41
|
+
def files
|
42
|
+
ribbon.keys
|
43
|
+
end
|
44
|
+
|
45
|
+
# The underlying wrapped ribbon used to store filenames and their
|
46
|
+
# associated hashes.
|
47
|
+
def ribbon
|
48
|
+
@ribbon ||= Ribbon::Wrapper.new
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/safeguard/repository.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
require 'safeguard/
|
1
|
+
require 'safeguard/hasher'
|
2
|
+
require 'safeguard/repository/hash_table'
|
3
|
+
require 'safeguard/verifier'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'ribbon'
|
6
|
+
require 'ribbon/core_ext'
|
2
7
|
|
3
8
|
module Safeguard
|
4
9
|
|
@@ -15,17 +20,19 @@ module Safeguard
|
|
15
20
|
alias :dir :directory
|
16
21
|
|
17
22
|
# Initializes a Safeguard repository in or from the given directory.
|
18
|
-
#
|
19
|
-
# If the given directory does not contain a repository directory, whose name
|
20
|
-
# is defined by the DIRECTORY_NAME constant, it will be created.
|
21
23
|
def initialize(dir)
|
22
24
|
self.directory = Repository.directory_in(dir)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates a directory for this repository, if one doesn't exist. The
|
28
|
+
# directory will have the name defined in the DIRECTORY_NAME constant.
|
29
|
+
def create_directory!
|
23
30
|
FileUtils.mkdir_p directory
|
24
31
|
end
|
25
32
|
|
26
|
-
#
|
33
|
+
# This repository's HashTable. Lazily loaded from disk.
|
27
34
|
def hash_table
|
28
|
-
|
35
|
+
@table ||= load_hash_table!
|
29
36
|
end
|
30
37
|
|
31
38
|
# Returns the name of the HashTable file relative to this repository.
|
@@ -33,30 +40,46 @@ module Safeguard
|
|
33
40
|
File.join directory, HASH_TABLE_FILE_NAME
|
34
41
|
end
|
35
42
|
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
# Saves this Repository's HashTable with the filename returned by
|
44
|
+
# #hash_table_file_name.
|
45
|
+
def save_hash_table
|
46
|
+
hash_table.save hash_table_file_name
|
47
|
+
end
|
48
|
+
|
49
|
+
# Calls the block and ensures that all data is persisted afterwards.
|
50
|
+
def before_save(&block)
|
51
|
+
if block.arity == 1 then block.call self else instance_eval &block end
|
52
|
+
ensure
|
53
|
+
save_hash_table
|
44
54
|
end
|
45
55
|
|
46
|
-
#
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
|
56
|
+
# Calculates the checksum of the given files and stores the results. +args+
|
57
|
+
# will be used to instantiate a new Hasher.
|
58
|
+
def add_files!(*args)
|
59
|
+
ribbon = args.extract_ribbon!
|
60
|
+
hasher = Hasher.new *args, ribbon
|
61
|
+
hasher.files.delete_if do |file|
|
62
|
+
hash_table.files.include? file
|
63
|
+
end unless ribbon.force?
|
64
|
+
results = hasher.results
|
65
|
+
hash_table.merge! results
|
51
66
|
end
|
52
67
|
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
68
|
+
# Creates a Hasher for the files present in this repository.
|
69
|
+
def create_hasher_with(functions)
|
70
|
+
Hasher.new *hash_table.files, :functions => functions
|
71
|
+
end
|
72
|
+
|
73
|
+
# Creates a verifier using this repository's hash table.
|
74
|
+
def create_verifier_with(*args)
|
75
|
+
ribbon = args.extract_wrapped_ribbon!
|
76
|
+
Verifier.new *args, ribbon.merge!(hash_table: hash_table)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Recalculates the checksum of the given files and compares them to the
|
80
|
+
# stored values. +args+ will be used to instantiate a new Verifier.
|
81
|
+
def verify_files(*args)
|
82
|
+
create_verifier_with(*args).results
|
60
83
|
end
|
61
84
|
|
62
85
|
# Returns the path to the repository relative to the given +dir+.
|
@@ -64,10 +87,27 @@ module Safeguard
|
|
64
87
|
File.join File.expand_path(dir), DIRECTORY_NAME
|
65
88
|
end
|
66
89
|
|
90
|
+
# Checks whether or not this repository's directory has been created and
|
91
|
+
# initialized.
|
92
|
+
def initialized?
|
93
|
+
File.directory? dir
|
94
|
+
end
|
95
|
+
|
67
96
|
# Checks whether or not a repository has been created in the given +dir+.
|
68
97
|
def self.initialized?(dir)
|
69
98
|
File.directory? directory_in(dir)
|
70
99
|
end
|
71
100
|
|
101
|
+
private
|
102
|
+
|
103
|
+
# Loads the hash table from file. Creates a new one if unable to do so.
|
104
|
+
def load_hash_table!
|
105
|
+
HashTable.load hash_table_file_name
|
106
|
+
rescue
|
107
|
+
# TODO: handle all error cases properly. This can overwrite a perfectly
|
108
|
+
# good hash table for incredibly sily reasons.
|
109
|
+
HashTable.new
|
110
|
+
end
|
111
|
+
|
72
112
|
end
|
73
113
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'safeguard/hasher'
|
2
|
+
require 'safeguard/repository/hash_table'
|
3
|
+
require 'ribbon'
|
4
|
+
require 'ribbon/core_ext/array'
|
5
|
+
|
6
|
+
module Safeguard
|
7
|
+
|
8
|
+
# Rehashes a set of files and compares the results to the stored hashes.
|
9
|
+
class Verifier
|
10
|
+
|
11
|
+
# The hasher used to calculate the checksum of the files.
|
12
|
+
attr_accessor :hasher
|
13
|
+
|
14
|
+
# Hash table to compare results against.
|
15
|
+
attr_accessor :hash_table
|
16
|
+
|
17
|
+
# Initializes a new verifier with the given files. Last argument can be an
|
18
|
+
# options hash or ribbon which specifies the hash table to verify against
|
19
|
+
# and the Hasher to use. If a hasher isn't specified, a new one will be
|
20
|
+
# created using the files and options given. If no files were given, the
|
21
|
+
# hash table's list of files will be used.
|
22
|
+
#
|
23
|
+
# files = %w(file1 file2 file3)
|
24
|
+
# hasher = Hasher.new *files, :functions => :crc32
|
25
|
+
# table = hasher.results
|
26
|
+
#
|
27
|
+
# Verifier.new :hasher => hasher, :hash_table => table
|
28
|
+
# Verifier.new :functions => [ :crc32, :md5 ], :hash_table => table
|
29
|
+
# Verifier.new *files, :functions => :crc32, :hash_table => table
|
30
|
+
def initialize(*args)
|
31
|
+
ribbon = args.extract_ribbon!
|
32
|
+
table = ribbon.hash_table? do
|
33
|
+
raise ArgumentError, 'No hash table to verify against'
|
34
|
+
end
|
35
|
+
table = table.ribbon if Repository::HashTable === table
|
36
|
+
self.hash_table = Ribbon[table]
|
37
|
+
hash_table.wrap_all!
|
38
|
+
args = hash_table.keys if args.empty?
|
39
|
+
self.hasher = ribbon.hasher? do
|
40
|
+
Hasher.new *args, ribbon
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Uses the hasher to recalculate the hashes of the files using the specified
|
45
|
+
# functions and compares the results with the data on the hash table.
|
46
|
+
#
|
47
|
+
# If a given file is not present in the hash table, the result of all
|
48
|
+
# comparisons will be the <tt>:file_missing</tt> symbol. If the value of a
|
49
|
+
# given hash for a given file is not present in the hash table, the result
|
50
|
+
# of the comparison will be the <tt>:hash_missing</tt> symbol.
|
51
|
+
def verify!
|
52
|
+
results = Ribbon.new
|
53
|
+
hasher.each do |file, hash_data|
|
54
|
+
hasher.functions.each do |function|
|
55
|
+
results[file][function] = if hash_table.has_key? file
|
56
|
+
if hash_table[file].has_key? function
|
57
|
+
hash_data[function] == hash_table[file][function]
|
58
|
+
else
|
59
|
+
:hash_missing
|
60
|
+
end
|
61
|
+
else
|
62
|
+
:file_missing
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
results = Ribbon[results]
|
67
|
+
results.wrap_all!
|
68
|
+
@results = results
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the cached data containing the results of the comparison betweeb
|
72
|
+
# the recalculated hashes of the files with the data in the hash table.
|
73
|
+
#
|
74
|
+
# To force recomputation, call the #verify! method.
|
75
|
+
def results
|
76
|
+
@results ||= verify!
|
77
|
+
end
|
78
|
+
|
79
|
+
# Yields file => comparison data pairs, or returns an enumerator.
|
80
|
+
def each(&block)
|
81
|
+
Ribbon[results].each &block
|
82
|
+
end
|
83
|
+
|
84
|
+
include Enumerable
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
data/lib/safeguard/version.rb
CHANGED
data/lib/safeguard.rb
CHANGED
data/safeguard.gemspec
CHANGED
@@ -17,4 +17,9 @@ Gem::Specification.new('safeguard') do |gem|
|
|
17
17
|
gem.files = `git ls-files`.split "\n"
|
18
18
|
gem.executables = `git ls-files -- bin/*`.split("\n").map &File.method(:basename)
|
19
19
|
|
20
|
+
gem.add_runtime_dependency 'acclaim'
|
21
|
+
gem.add_runtime_dependency 'ribbon'
|
22
|
+
|
23
|
+
gem.add_development_dependency 'rookie'
|
24
|
+
|
20
25
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safeguard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,41 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
12
|
+
date: 2012-01-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: acclaim
|
16
|
+
requirement: &6175680 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *6175680
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ribbon
|
27
|
+
requirement: &6174640 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *6174640
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rookie
|
38
|
+
requirement: &6174080 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *6174080
|
14
47
|
description: Hash-based file integrity verification utility
|
15
48
|
email: matheus.a.m.moreira@gmail.com
|
16
49
|
executables:
|
@@ -20,6 +53,7 @@ extra_rdoc_files: []
|
|
20
53
|
files:
|
21
54
|
- .gitignore
|
22
55
|
- .rvmrc
|
56
|
+
- Gemfile
|
23
57
|
- LICENSE.GPLv3
|
24
58
|
- README.markdown
|
25
59
|
- Rakefile
|
@@ -30,9 +64,13 @@ files:
|
|
30
64
|
- lib/safeguard/command/hash.rb
|
31
65
|
- lib/safeguard/command/init.rb
|
32
66
|
- lib/safeguard/command/verify.rb
|
67
|
+
- lib/safeguard/core_ext/file.rb
|
33
68
|
- lib/safeguard/digest.rb
|
34
|
-
- lib/safeguard/
|
69
|
+
- lib/safeguard/digest/crc32.rb
|
70
|
+
- lib/safeguard/hasher.rb
|
35
71
|
- lib/safeguard/repository.rb
|
72
|
+
- lib/safeguard/repository/hash_table.rb
|
73
|
+
- lib/safeguard/verifier.rb
|
36
74
|
- lib/safeguard/version.rb
|
37
75
|
- safeguard.gemspec
|
38
76
|
homepage: https://github.com/matheusmoreira/safeguard
|
@@ -47,12 +85,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
85
|
- - ! '>='
|
48
86
|
- !ruby/object:Gem::Version
|
49
87
|
version: '0'
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
hash: -397877248667330061
|
50
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
92
|
none: false
|
52
93
|
requirements:
|
53
94
|
- - ! '>='
|
54
95
|
- !ruby/object:Gem::Version
|
55
96
|
version: '0'
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
hash: -397877248667330061
|
56
100
|
requirements: []
|
57
101
|
rubyforge_project:
|
58
102
|
rubygems_version: 1.8.10
|
data/lib/safeguard/hash_table.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
require 'safeguard/digest'
|
2
|
-
require 'yaml'
|
3
|
-
|
4
|
-
module Safeguard
|
5
|
-
|
6
|
-
# Holds filename => checksum pairs.
|
7
|
-
class HashTable
|
8
|
-
|
9
|
-
# Initializes an empty HashTable.
|
10
|
-
def initialize
|
11
|
-
@table = {}
|
12
|
-
end
|
13
|
-
|
14
|
-
# Saves the HashTable to a YAML file.
|
15
|
-
def save(filename)
|
16
|
-
File.open("#{filename}.yaml", 'w') do |file|
|
17
|
-
file.puts to_yaml
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# Loads the HashTable from a YAML file.
|
22
|
-
def self.load(filename)
|
23
|
-
YAML::load_file "#{filename}.yaml"
|
24
|
-
end
|
25
|
-
|
26
|
-
# Associates the given +filename+ to the computed checksum of the file it
|
27
|
-
# refers to.
|
28
|
-
def <<(filename)
|
29
|
-
@table[filename] = Digest.file filename
|
30
|
-
end
|
31
|
-
|
32
|
-
alias :add :<<
|
33
|
-
|
34
|
-
# Looks up the checksum for the given +filename+.
|
35
|
-
def [](filename)
|
36
|
-
@table[filename]
|
37
|
-
end
|
38
|
-
|
39
|
-
# Recalculates the hash and compares it to the original hash associated with
|
40
|
-
# the given filename.
|
41
|
-
#
|
42
|
-
# If a hash for the given file isn't stored in this table, an exception will
|
43
|
-
# be raised.
|
44
|
-
def verify(filename)
|
45
|
-
hash = @table[filename]
|
46
|
-
raise "File not in repository: #{filename}" unless hash
|
47
|
-
Digest.file(filename) == hash
|
48
|
-
end
|
49
|
-
|
50
|
-
# Verifies all files stored in this table and returns a hash of results
|
51
|
-
# associating a filename with either +true+, when the file's recalculated
|
52
|
-
# hash is equal to the hash stored in this table, or +false+, when
|
53
|
-
# otherwise.
|
54
|
-
#
|
55
|
-
# If a block is given, the filename and the result will be yielded instead.
|
56
|
-
def verify_all
|
57
|
-
files = @table.keys
|
58
|
-
if block_given?
|
59
|
-
files.each { |file| yield file, verify(file) }
|
60
|
-
else
|
61
|
-
{}.tap do |results|
|
62
|
-
files.each do |file|
|
63
|
-
results[file] = verify file
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
end
|