fingerprint 1.4.0 → 3.0.1
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.
- checksums.yaml +5 -5
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.travis.yml +23 -0
- data/GUIDE.md +233 -0
- data/Gemfile +1 -1
- data/README.md +113 -18
- data/Rakefile +6 -0
- data/bin/fingerprint +6 -197
- data/fingerprint.gemspec +8 -7
- data/lib/fingerprint.rb +16 -3
- data/lib/fingerprint/checker.rb +7 -7
- data/lib/fingerprint/checksums.rb +33 -0
- data/lib/fingerprint/command.rb +90 -0
- data/lib/fingerprint/command/analyze.rb +74 -0
- data/lib/fingerprint/command/compare.rb +55 -0
- data/lib/fingerprint/command/duplicates.rb +97 -0
- data/lib/fingerprint/command/scan.rb +61 -0
- data/lib/fingerprint/command/verify.rb +86 -0
- data/lib/fingerprint/find.rb +40 -0
- data/lib/fingerprint/record.rb +20 -1
- data/lib/fingerprint/scanner.rb +134 -91
- data/lib/fingerprint/version.rb +2 -3
- data/spec/fingerprint/command/analyze_spec.rb +48 -0
- data/spec/fingerprint/command/duplicates_spec.rb +53 -0
- data/spec/fingerprint/command/source_fingerprint.rb +32 -0
- data/spec/fingerprint/command/verify_spec.rb +39 -0
- data/spec/fingerprint/corpus/README.md +3 -0
- data/spec/fingerprint/scanner_spec.rb +67 -0
- data/{test/test_fingerprint.rb → spec/fingerprint_spec.rb} +8 -36
- data/spec/spec_helper.rb +31 -0
- metadata +88 -23
- data/bin/fingerprint-diff +0 -145
- data/rakefile.rb +0 -9
data/Rakefile
ADDED
data/bin/fingerprint
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
# Copyright
|
3
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -20,201 +20,10 @@
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
require 'fileutils'
|
23
|
+
# This script takes a given path, and renames it with the given format.
|
24
|
+
# It then ensures that there is a symlink called "latest" that points
|
25
|
+
# to the renamed directory.
|
27
26
|
|
28
|
-
|
29
|
-
:root => "./",
|
30
|
-
:mode => :scan,
|
31
|
-
:output => $stdout,
|
32
|
-
:verbose => false,
|
33
|
-
:force => false,
|
34
|
-
:name => "index.fingerprint",
|
35
|
-
:extended => false,
|
36
|
-
:checksums => Fingerprint::DEFAULT_CHECKSUMS,
|
37
|
-
:additions => false,
|
38
|
-
:failures => :ignore,
|
39
|
-
}
|
27
|
+
require_relative '../lib/fingerprint/command'
|
40
28
|
|
41
|
-
|
42
|
-
script_name = File.basename($0)
|
43
|
-
|
44
|
-
o.banner = "Usage: #{script_name} [options] [path]"
|
45
|
-
o.define_head "This script is used to create and compare file system fingerprints."
|
46
|
-
|
47
|
-
o.separator ""
|
48
|
-
o.separator "Directory analysis and verification:"
|
49
|
-
|
50
|
-
o.on("-a", "--analyze [path]", String, "Generage a fingerprint of the given path and save it for later verification.") do |path|
|
51
|
-
OPTIONS[:mode] = :analyze
|
52
|
-
OPTIONS[:root] = path if path
|
53
|
-
end
|
54
|
-
|
55
|
-
o.on("-v", "--verify [path]", String, "Verify a given path based on a previously saved fingerprint.") do |path|
|
56
|
-
OPTIONS[:mode] = :verify
|
57
|
-
OPTIONS[:root] = path if path
|
58
|
-
end
|
59
|
-
|
60
|
-
o.on("-n name", String, "Specify the name of the fingerprint file.", "Default: #{OPTIONS[:name]}") do |name|
|
61
|
-
OPTIONS[:name] = name
|
62
|
-
end
|
63
|
-
|
64
|
-
o.on("-f", "Force any operation to complete despite warnings.") do
|
65
|
-
OPTIONS[:force] = true
|
66
|
-
end
|
67
|
-
|
68
|
-
o.separator ""
|
69
|
-
|
70
|
-
o.on("-x", "Include additional extended information about files and directories.") do
|
71
|
-
OPTIONS[:extended] = true
|
72
|
-
end
|
73
|
-
|
74
|
-
o.on("-s [checksum1,checksum2]", "Provide a list of the checksum algorithms to use.", "Available: #{Fingerprint::CHECKSUMS.keys.join(', ')}; Default: #{OPTIONS[:checksums].join(', ')}") do |checksums|
|
75
|
-
OPTIONS[:checksums] = checksums.split(/[\s,]+/)
|
76
|
-
end
|
77
|
-
|
78
|
-
o.separator ""
|
79
|
-
o.separator "Compare fingerprints:"
|
80
|
-
|
81
|
-
o.on("-c", "Compare the given fingerprints. Check that the second fingerprint is a superset of the first.") do
|
82
|
-
OPTIONS[:mode] = :check
|
83
|
-
end
|
84
|
-
|
85
|
-
o.on("-A", "Report files that have been added to the second fingerprint.") do
|
86
|
-
OPTIONS[:additions] = true
|
87
|
-
end
|
88
|
-
|
89
|
-
o.separator ""
|
90
|
-
o.separator "Output manipulation:"
|
91
|
-
|
92
|
-
o.on("-o [output-path]", String, "Write the fingerprint output to the given file.") do |path|
|
93
|
-
OPTIONS[:output] = File.open(path, "w")
|
94
|
-
end
|
95
|
-
|
96
|
-
o.on("--verbose", "Verbose output, include additional details in the file transcript.") do
|
97
|
-
OPTIONS[:verbose] = true
|
98
|
-
end
|
99
|
-
|
100
|
-
o.on("--progress", "Print percentage progress to standard error.") do
|
101
|
-
OPTIONS[:progress] = true
|
102
|
-
end
|
103
|
-
|
104
|
-
o.on("--die", "Give a non-zero exit code if errors are detected by check or verify.") do
|
105
|
-
OPTIONS[:failures] = :die
|
106
|
-
end
|
107
|
-
|
108
|
-
o.separator ""
|
109
|
-
o.separator "Help and Copyright information:"
|
110
|
-
|
111
|
-
o.on_tail("--copy", "Display copyright and warranty information") do
|
112
|
-
$stderr.puts "#{script_name} v#{Fingerprint::VERSION}. Copyright (c) 2011 Samuel Williams."
|
113
|
-
$stderr.puts "This software is released under the MIT license and comes with ABSOLUTELY NO WARRANTY."
|
114
|
-
$stderr.puts "See http://www.oriontransfer.co.nz/ for more information."
|
115
|
-
exit
|
116
|
-
end
|
117
|
-
|
118
|
-
o.on_tail("-h", "--help", "Show this help message.") do
|
119
|
-
$stderr.puts o
|
120
|
-
exit
|
121
|
-
end
|
122
|
-
end.parse!
|
123
|
-
|
124
|
-
unless File.directory? OPTIONS[:root]
|
125
|
-
$stderr.puts "Path #{OPTIONS[:root]} doesn't exist!"
|
126
|
-
exit(255)
|
127
|
-
end
|
128
|
-
|
129
|
-
if OPTIONS[:checksums].size == 0
|
130
|
-
OPTIONS[:checksums] = ['MD5', 'SHA2.256']
|
131
|
-
end
|
132
|
-
|
133
|
-
def finish_check(error_count)
|
134
|
-
if error_count == 0
|
135
|
-
$stderr.puts "Data verified, 0 errors found."
|
136
|
-
exit(0)
|
137
|
-
else
|
138
|
-
$stderr.puts "Data inconsistent, #{error_count} errors found!"
|
139
|
-
exit(OPTIONS[:failures] == :die ? 1 : 0)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
case (OPTIONS[:mode])
|
144
|
-
when :analyze
|
145
|
-
output_file = Pathname.new(OPTIONS[:root]) + OPTIONS[:name]
|
146
|
-
|
147
|
-
if output_file.exist? && !OPTIONS[:force]
|
148
|
-
$stderr.puts "Output file #{output_file} already exists. Aborting."
|
149
|
-
exit(2)
|
150
|
-
end
|
151
|
-
|
152
|
-
options = OPTIONS.dup
|
153
|
-
options[:excludes] = [OPTIONS[:name]]
|
154
|
-
|
155
|
-
finished = false
|
156
|
-
begin
|
157
|
-
File.open(output_file, "w") do |io|
|
158
|
-
options[:output] = io
|
159
|
-
|
160
|
-
Fingerprint::Scanner.scan_paths([OPTIONS[:root]], options)
|
161
|
-
end
|
162
|
-
finished = true
|
163
|
-
ensure
|
164
|
-
FileUtils.rm(output_file) unless finished
|
165
|
-
end
|
166
|
-
when :verify
|
167
|
-
error_count = 0
|
168
|
-
|
169
|
-
input_file = Pathname.new(OPTIONS[:root]) + OPTIONS[:name]
|
170
|
-
|
171
|
-
unless File.exist? input_file
|
172
|
-
$stderr.puts "Can't find index #{input_file}. Aborting."
|
173
|
-
exit(3)
|
174
|
-
end
|
175
|
-
|
176
|
-
options = OPTIONS.dup
|
177
|
-
|
178
|
-
master = Fingerprint::RecordSet.new
|
179
|
-
|
180
|
-
File.open(input_file, "r") do |io|
|
181
|
-
master.parse(io)
|
182
|
-
end
|
183
|
-
|
184
|
-
if master.configuration
|
185
|
-
options.merge!(master.configuration.options)
|
186
|
-
end
|
187
|
-
|
188
|
-
scanner = Fingerprint::Scanner.new([OPTIONS[:root]], options)
|
189
|
-
copy = Fingerprint::SparseRecordSet.new(scanner)
|
190
|
-
|
191
|
-
error_count += Fingerprint::Checker::verify(master, copy, options)
|
192
|
-
|
193
|
-
finish_check(error_count)
|
194
|
-
when :scan
|
195
|
-
roots = ARGV
|
196
|
-
roots << Dir.pwd if roots.size == 0
|
197
|
-
|
198
|
-
# Check that all supplied paths exist
|
199
|
-
roots.delete_if do |root|
|
200
|
-
if File.exist? root
|
201
|
-
false
|
202
|
-
else
|
203
|
-
$stderr.puts "Path #{root} doesn't exist, skipping!"
|
204
|
-
true
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
options = OPTIONS.dup
|
209
|
-
|
210
|
-
# Discard output once it has been written to disk:
|
211
|
-
options[:recordset] = nil
|
212
|
-
|
213
|
-
Fingerprint::Scanner.scan_paths(roots, options)
|
214
|
-
when :check
|
215
|
-
options = OPTIONS.dup
|
216
|
-
|
217
|
-
error_count = Fingerprint::Checker.check_files(ARGV[0], ARGV[1], options)
|
218
|
-
|
219
|
-
finish_check(error_count)
|
220
|
-
end
|
29
|
+
Fingerprint::Command.call
|
data/fingerprint.gemspec
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'fingerprint/version'
|
1
|
+
|
2
|
+
require_relative 'lib/fingerprint/version'
|
5
3
|
|
6
4
|
Gem::Specification.new do |spec|
|
7
5
|
spec.name = "fingerprint"
|
@@ -20,8 +18,11 @@ Gem::Specification.new do |spec|
|
|
20
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
19
|
spec.require_paths = ["lib"]
|
22
20
|
|
23
|
-
spec.
|
24
|
-
spec.
|
21
|
+
spec.add_dependency "samovar", "~> 2.0"
|
22
|
+
spec.add_dependency "build-files", "~> 1.2"
|
25
23
|
|
26
|
-
spec.
|
24
|
+
spec.add_development_dependency "covered"
|
25
|
+
spec.add_development_dependency "bundler"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.4"
|
27
|
+
spec.add_development_dependency "rake"
|
27
28
|
end
|
data/lib/fingerprint.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright, 2011, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -25,7 +25,7 @@ require 'fingerprint/checker'
|
|
25
25
|
|
26
26
|
module Fingerprint
|
27
27
|
# A helper function to check two paths for consistency. Provides callback from +Fingerprint::Checker+.
|
28
|
-
def self.check_paths(master_path, copy_path, &block)
|
28
|
+
def self.check_paths(master_path, copy_path, **options, &block)
|
29
29
|
master = Scanner.new([master_path])
|
30
30
|
copy = Scanner.new([copy_path])
|
31
31
|
|
@@ -34,10 +34,23 @@ module Fingerprint
|
|
34
34
|
|
35
35
|
master.scan(master_recordset)
|
36
36
|
|
37
|
-
checker = Checker.new(master_recordset, copy_recordset)
|
37
|
+
checker = Checker.new(master_recordset, copy_recordset, **options)
|
38
38
|
|
39
39
|
checker.check(&block)
|
40
40
|
|
41
41
|
return checker
|
42
42
|
end
|
43
|
+
|
44
|
+
# Returns true if the given paths contain identical files. Useful for expectations, e.g. `expect(Fingerprint).to be_identical(source, destination)`
|
45
|
+
def self.identical?(source, destination, &block)
|
46
|
+
failures = 0
|
47
|
+
|
48
|
+
check_paths(source, destination) do |record, name, message|
|
49
|
+
failures += 1
|
50
|
+
|
51
|
+
yield(record) if block_given?
|
52
|
+
end
|
53
|
+
|
54
|
+
return failures == 0
|
55
|
+
end
|
43
56
|
end
|
data/lib/fingerprint/checker.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright, 2011, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -31,7 +31,7 @@ module Fingerprint
|
|
31
31
|
#
|
32
32
|
# Master and copy are +IO+ objects corresponding to the output produced by +Fingerprint::Scanner+.
|
33
33
|
class Checker
|
34
|
-
def initialize(master, copy, options
|
34
|
+
def initialize(master, copy, **options)
|
35
35
|
@master = master
|
36
36
|
@copy = copy
|
37
37
|
|
@@ -42,7 +42,7 @@ module Fingerprint
|
|
42
42
|
attr :copy
|
43
43
|
|
44
44
|
# Run the checking process.
|
45
|
-
def check
|
45
|
+
def check(&block)
|
46
46
|
# For every file in the src, we check that it exists
|
47
47
|
# in the destination:
|
48
48
|
total_count = @master.records.count
|
@@ -95,7 +95,7 @@ module Fingerprint
|
|
95
95
|
# A list of files which either did not exist in the copy, or had the wrong checksum.
|
96
96
|
attr :failures
|
97
97
|
|
98
|
-
def self.check_files(master, copy, options
|
98
|
+
def self.check_files(master, copy, **options, &block)
|
99
99
|
# New API that takes two RecordSets...
|
100
100
|
|
101
101
|
File.open(master) do |master_file|
|
@@ -106,13 +106,13 @@ module Fingerprint
|
|
106
106
|
copy_recordset = RecordSet.new
|
107
107
|
copy_recordset.parse(copy_file)
|
108
108
|
|
109
|
-
verify(master_recordset, copy_recordset, options, &block)
|
109
|
+
verify(master_recordset, copy_recordset, **options, &block)
|
110
110
|
end
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
114
|
# Helper function to check two fingerprint files.
|
115
|
-
def self.verify(master, copy, options
|
115
|
+
def self.verify(master, copy, **options, &block)
|
116
116
|
error_count = 0
|
117
117
|
|
118
118
|
errors = options.delete(:recordset) || RecordSet.new
|
@@ -120,7 +120,7 @@ module Fingerprint
|
|
120
120
|
errors = RecordSetPrinter.new(errors, options[:output])
|
121
121
|
end
|
122
122
|
|
123
|
-
checker = Checker.new(master, copy, options)
|
123
|
+
checker = Checker.new(master, copy, **options)
|
124
124
|
|
125
125
|
checker.check do |record, result, message|
|
126
126
|
error_count += 1
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
# This script takes a given path, and renames it with the given format.
|
22
|
+
# It then ensures that there is a symlink called "latest" that points
|
23
|
+
# to the renamed directory.
|
24
|
+
|
25
|
+
require 'samovar'
|
26
|
+
|
27
|
+
module Fingerprint
|
28
|
+
module Checksums
|
29
|
+
def self.call(result)
|
30
|
+
result.split(/\s*,\s*/)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
# This script takes a given path, and renames it with the given format.
|
22
|
+
# It then ensures that there is a symlink called "latest" that points
|
23
|
+
# to the renamed directory.
|
24
|
+
|
25
|
+
require 'samovar'
|
26
|
+
|
27
|
+
require_relative 'scanner'
|
28
|
+
|
29
|
+
require_relative 'command/scan'
|
30
|
+
require_relative 'command/analyze'
|
31
|
+
require_relative 'command/verify'
|
32
|
+
require_relative 'command/compare'
|
33
|
+
require_relative 'command/duplicates'
|
34
|
+
|
35
|
+
module Fingerprint
|
36
|
+
module Command
|
37
|
+
def self.call(*args)
|
38
|
+
Top.call(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
class Top < Samovar::Command
|
42
|
+
self.description = "A file checksum analysis and verification tool."
|
43
|
+
|
44
|
+
options do
|
45
|
+
option '--root <path>', "Work in the given root directory."
|
46
|
+
|
47
|
+
option '-o/--output <path>', "Output the transcript to a specific file rather than stdout."
|
48
|
+
|
49
|
+
option '-h/--help', "Print out help information."
|
50
|
+
option '-v/--version', "Print out the application version."
|
51
|
+
end
|
52
|
+
|
53
|
+
def chdir(&block)
|
54
|
+
if root = @options[:root]
|
55
|
+
Dir.chdir(root, &block)
|
56
|
+
else
|
57
|
+
yield
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def output
|
62
|
+
if path = @options[:output]
|
63
|
+
File.open(path, "w")
|
64
|
+
else
|
65
|
+
$stdout
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
nested :command, {
|
70
|
+
'scan' => Scan,
|
71
|
+
'analyze' => Analyze,
|
72
|
+
'verify' => Verify,
|
73
|
+
'compare' => Compare,
|
74
|
+
'duplicates' => Duplicates
|
75
|
+
}, default: 'analyze'
|
76
|
+
|
77
|
+
def call
|
78
|
+
if @options[:version]
|
79
|
+
puts "fingerprint v#{VERSION}"
|
80
|
+
elsif @options[:help]
|
81
|
+
self.print_usage(program_name)
|
82
|
+
else
|
83
|
+
chdir do
|
84
|
+
@command.call
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|