safeguard 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|