fingerprint 1.3.1 → 3.1.0
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 +7 -0
- data/bin/fingerprint +6 -215
- data/lib/.DS_Store +0 -0
- data/lib/fingerprint.rb +19 -5
- data/lib/fingerprint/checker.rb +45 -17
- 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 +98 -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 +43 -16
- data/lib/fingerprint/scanner.rb +147 -96
- data/lib/fingerprint/version.rb +2 -9
- metadata +109 -63
- data/README.md +0 -52
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b8e4286765727a4efa88c5ecf4f84ab5d736606a0adae710c32073da69746465
|
4
|
+
data.tar.gz: be66585855747916dd4294736a68cc04d2ff3e1aa49df83d08ec98b4e21bcfee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8772554e075f2b29016fe859c85d0d7082fc66a77f068b02d33ebc084c15efa20a83baa52d48fad3860b18316d2084f515f28933065fd7f0a7f7c9743c27bd39
|
7
|
+
data.tar.gz: 8757e6f7cc91db4a7f80be3485ac61184ed0c8fa9ecaad58f2ba257ff74a2b6d7384888439263d7faafa99d4238712b96ab11ba7e6585d4fe43968a8a6fd7672
|
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,219 +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 'lockfile'
|
27
|
-
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.
|
28
26
|
|
29
|
-
|
30
|
-
:root => "./",
|
31
|
-
:mode => :scan,
|
32
|
-
:output => $stdout,
|
33
|
-
:verbose => false,
|
34
|
-
:force => false,
|
35
|
-
:name => "index.fingerprint",
|
36
|
-
:extended => false,
|
37
|
-
:checksums => Fingerprint::DEFAULT_CHECKSUMS,
|
38
|
-
:lockfile => true
|
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
|
-
o.separator "Low level operations:"
|
70
|
-
|
71
|
-
o.on("-c", "Compare the given fingerprints. Check that the second fingerprint is a superset of the first.") do
|
72
|
-
OPTIONS[:mode] = :check
|
73
|
-
end
|
74
|
-
|
75
|
-
o.on("-o [output-path]", String, "Write the fingerprint output to the given file.") do |path|
|
76
|
-
OPTIONS[:output] = File.open(path, "w")
|
77
|
-
end
|
78
|
-
|
79
|
-
o.on("--verbose", "Verbose output, include additional details in the file transcript.") do
|
80
|
-
OPTIONS[:verbose] = true
|
81
|
-
end
|
82
|
-
|
83
|
-
o.on("--progress", "Print percentage progress to standard error.") do
|
84
|
-
OPTIONS[:progress] = true
|
85
|
-
end
|
86
|
-
|
87
|
-
o.on("--no-lockfile", "Don't use a lockfile to validate access to the fingerprint.") do
|
88
|
-
OPTIONS[:lockfile] = false
|
89
|
-
end
|
90
|
-
|
91
|
-
o.separator ""
|
92
|
-
|
93
|
-
o.on("-x", "Include additional extended information about files and directories.") do
|
94
|
-
OPTIONS[:extended] = true
|
95
|
-
end
|
96
|
-
|
97
|
-
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|
|
98
|
-
OPTIONS[:checksums] = checksums.split(/[\s,]+/)
|
99
|
-
end
|
100
|
-
|
101
|
-
o.separator ""
|
102
|
-
o.separator "Help and Copyright information:"
|
103
|
-
|
104
|
-
o.on_tail("--copy", "Display copyright and warranty information") do
|
105
|
-
$stderr.puts "#{script_name} v#{Fingerprint::VERSION::STRING}. Copyright (c) 2011 Samuel Williams."
|
106
|
-
$stderr.puts "This software is released under the MIT license and comes with ABSOLUTELY NO WARRANTY."
|
107
|
-
$stderr.puts "See http://www.oriontransfer.co.nz/ for more information."
|
108
|
-
exit
|
109
|
-
end
|
110
|
-
|
111
|
-
o.on_tail("-h", "--help", "Show this help message.") do
|
112
|
-
$stderr.puts o
|
113
|
-
exit
|
114
|
-
end
|
115
|
-
end.parse!
|
116
|
-
|
117
|
-
unless File.directory? OPTIONS[:root]
|
118
|
-
$stderr.puts "Path #{OPTIONS[:root]} doesn't exist!"
|
119
|
-
exit(255)
|
120
|
-
end
|
121
|
-
|
122
|
-
if OPTIONS[:checksums].size == 0
|
123
|
-
OPTIONS[:checksums] = ['MD5', 'SHA2.256']
|
124
|
-
end
|
125
|
-
|
126
|
-
# Run some block with the given lock, if requested.
|
127
|
-
def with_lock
|
128
|
-
lockfile_path = nil
|
129
|
-
if OPTIONS[:lockfile] == true
|
130
|
-
lockfile_path = Pathname.new(OPTIONS[:root]) + (OPTIONS[:name] + ".lock")
|
131
|
-
elsif OPTION[:lockfile] != false
|
132
|
-
lockfile_path = OPTIONS[:lockfile]
|
133
|
-
end
|
134
|
-
|
135
|
-
if lockfile_path
|
136
|
-
begin
|
137
|
-
Lockfile.new(lockfile_path, :retries => 0) do
|
138
|
-
yield
|
139
|
-
end
|
140
|
-
rescue Lockfile::MaxTriesLockError
|
141
|
-
$stderr.puts "Could not acquire lock #{lockfile_path}."
|
142
|
-
|
143
|
-
return false
|
144
|
-
end
|
145
|
-
else
|
146
|
-
yield
|
147
|
-
end
|
148
|
-
|
149
|
-
return true
|
150
|
-
end
|
151
|
-
|
152
|
-
case (OPTIONS[:mode])
|
153
|
-
when :analyze
|
154
|
-
result = with_lock do
|
155
|
-
output_file = Pathname.new(OPTIONS[:root]) + OPTIONS[:name]
|
156
|
-
|
157
|
-
if output_file.exist? && !OPTIONS[:force]
|
158
|
-
$stderr.puts "Output file #{output_file} already exists. Aborting."
|
159
|
-
exit(2)
|
160
|
-
end
|
161
|
-
|
162
|
-
options = OPTIONS.dup
|
163
|
-
options[:excludes] = [OPTIONS[:name]]
|
164
|
-
|
165
|
-
finished = false
|
166
|
-
begin
|
167
|
-
File.open(output_file, "w") do |io|
|
168
|
-
options[:output] = io
|
169
|
-
|
170
|
-
Fingerprint::Scanner.scan_paths([OPTIONS[:root]], options)
|
171
|
-
end
|
172
|
-
finished = true
|
173
|
-
ensure
|
174
|
-
FileUtils.rm(output_file) unless finished
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
unless result
|
179
|
-
# Lockfile failure
|
180
|
-
exit(4)
|
181
|
-
end
|
182
|
-
when :verify
|
183
|
-
error_count = 0
|
184
|
-
|
185
|
-
input_file = Pathname.new(OPTIONS[:root]) + OPTIONS[:name]
|
186
|
-
|
187
|
-
unless File.exist? input_file
|
188
|
-
$stderr.puts "Can't find index #{input_file}. Aborting."
|
189
|
-
exit(3)
|
190
|
-
end
|
191
|
-
|
192
|
-
options = OPTIONS.dup
|
193
|
-
|
194
|
-
master = Fingerprint::Recordset.new
|
195
|
-
|
196
|
-
File.open(input_file, "r") do |io|
|
197
|
-
master.parse(io)
|
198
|
-
end
|
199
|
-
|
200
|
-
if master.configuration
|
201
|
-
options.merge!(master.configuration.options)
|
202
|
-
end
|
203
|
-
|
204
|
-
scanner = Fingerprint::Scanner.new([OPTIONS[:root]], options)
|
205
|
-
copy = Fingerprint::SparseRecordset.new(scanner)
|
206
|
-
|
207
|
-
error_count += Fingerprint::Checker::verify(master, copy, options)
|
208
|
-
|
209
|
-
if error_count == 0
|
210
|
-
$stderr.puts "Data verified, 0 errors found."
|
211
|
-
exit(0)
|
212
|
-
else
|
213
|
-
$stderr.puts "Data inconsistent, #{error_count} errors found!"
|
214
|
-
exit(1)
|
215
|
-
end
|
216
|
-
when :scan
|
217
|
-
roots = ARGV
|
218
|
-
roots << Dir.pwd if roots.size == 0
|
219
|
-
|
220
|
-
# Check that all supplied paths exist
|
221
|
-
roots.delete_if do |root|
|
222
|
-
if File.exist? root
|
223
|
-
false
|
224
|
-
else
|
225
|
-
$stderr.puts "Path #{root} doesn't exist, skipping!"
|
226
|
-
true
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
options = OPTIONS.dup
|
231
|
-
|
232
|
-
Fingerprint::Scanner.scan_paths(roots, options)
|
233
|
-
when :check
|
234
|
-
options = OPTIONS.dup
|
235
|
-
|
236
|
-
error_count = Fingerprint::Checker.check_files(ARGV[0], ARGV[1], options)
|
237
|
-
exit(error_count > 0 ? 1 : 0)
|
238
|
-
end
|
29
|
+
Fingerprint::Command.call
|
data/lib/.DS_Store
ADDED
Binary file
|
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
|
@@ -18,25 +18,39 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require 'fingerprint/record'
|
21
22
|
require 'fingerprint/version'
|
22
23
|
require 'fingerprint/scanner'
|
23
24
|
require 'fingerprint/checker'
|
24
25
|
|
25
26
|
module Fingerprint
|
26
27
|
# A helper function to check two paths for consistency. Provides callback from +Fingerprint::Checker+.
|
27
|
-
def self.check_paths(master_path, copy_path, &block)
|
28
|
+
def self.check_paths(master_path, copy_path, **options, &block)
|
28
29
|
master = Scanner.new([master_path])
|
29
30
|
copy = Scanner.new([copy_path])
|
30
31
|
|
31
|
-
master_recordset =
|
32
|
-
copy_recordset =
|
32
|
+
master_recordset = RecordSet.new
|
33
|
+
copy_recordset = SparseRecordSet.new(copy)
|
33
34
|
|
34
35
|
master.scan(master_recordset)
|
35
36
|
|
36
|
-
checker = Checker.new(master_recordset, copy_recordset)
|
37
|
+
checker = Checker.new(master_recordset, copy_recordset, **options)
|
37
38
|
|
38
39
|
checker.check(&block)
|
39
40
|
|
40
41
|
return checker
|
41
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
|
42
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,15 +42,26 @@ 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
|
-
|
49
48
|
total_count = @master.records.count
|
50
49
|
processed_size = 0
|
51
50
|
total_size = @master.records.inject(0) { |count, record| count + (record['file.size'] || 0).to_i }
|
52
51
|
|
52
|
+
if @options[:additions]
|
53
|
+
copy_paths = @copy.paths.dup
|
54
|
+
else
|
55
|
+
copy_paths = {}
|
56
|
+
end
|
57
|
+
|
53
58
|
@master.records.each_with_index do |record, processed_count|
|
59
|
+
copy_paths.delete(record.path)
|
60
|
+
|
61
|
+
if @options[:progress]
|
62
|
+
$stderr.puts "# Checking: #{record.path}"
|
63
|
+
end
|
64
|
+
|
54
65
|
next if record.mode != :file
|
55
66
|
|
56
67
|
result, message = @copy.compare(record)
|
@@ -66,53 +77,64 @@ module Fingerprint
|
|
66
77
|
end
|
67
78
|
|
68
79
|
if @options[:progress]
|
69
|
-
$stderr.puts "# Progress: File #{processed_count} / #{total_count}
|
80
|
+
$stderr.puts "# Progress: File #{processed_count} / #{total_count}; Byte #{processed_size} / #{total_size} = #{sprintf('%0.2f%', processed_size.to_f / total_size.to_f * 100.0)}"
|
70
81
|
|
71
82
|
processed_size += (record['file.size'] || 0).to_i
|
72
83
|
end
|
73
84
|
end
|
85
|
+
|
86
|
+
if @options[:additions]
|
87
|
+
copy_paths.each do |path, record|
|
88
|
+
next unless record.mode == :file || record.mode == :directory
|
89
|
+
|
90
|
+
yield record, :addition, "File added"
|
91
|
+
end
|
92
|
+
end
|
74
93
|
end
|
75
94
|
|
76
95
|
# A list of files which either did not exist in the copy, or had the wrong checksum.
|
77
96
|
attr :failures
|
78
97
|
|
79
|
-
def self.check_files(master, copy, options
|
80
|
-
# New API that takes two
|
98
|
+
def self.check_files(master, copy, **options, &block)
|
99
|
+
# New API that takes two RecordSets...
|
81
100
|
|
82
101
|
File.open(master) do |master_file|
|
83
102
|
File.open(copy) do |copy_file|
|
84
|
-
master_recordset =
|
103
|
+
master_recordset = RecordSet.new
|
85
104
|
master_recordset.parse(master_file)
|
86
105
|
|
87
|
-
copy_recordset =
|
106
|
+
copy_recordset = RecordSet.new
|
88
107
|
copy_recordset.parse(copy_file)
|
89
108
|
|
90
|
-
verify(master_recordset, copy_recordset, options, &block)
|
109
|
+
verify(master_recordset, copy_recordset, **options, &block)
|
91
110
|
end
|
92
111
|
end
|
93
112
|
end
|
94
113
|
|
95
114
|
# Helper function to check two fingerprint files.
|
96
|
-
def self.verify(master, copy, options
|
115
|
+
def self.verify(master, copy, **options, &block)
|
97
116
|
error_count = 0
|
98
117
|
|
99
|
-
errors = options.delete(:recordset) ||
|
118
|
+
errors = options.delete(:recordset) || RecordSet.new
|
100
119
|
if options[:output]
|
101
|
-
errors =
|
120
|
+
errors = RecordSetPrinter.new(errors, options[:output])
|
102
121
|
end
|
103
122
|
|
104
|
-
checker = Checker.new(master, copy, options)
|
123
|
+
checker = Checker.new(master, copy, **options)
|
105
124
|
|
106
125
|
checker.check do |record, result, message|
|
107
126
|
error_count += 1
|
108
|
-
copy = checker.copy.paths[record.path]
|
109
127
|
|
110
128
|
metadata = {
|
111
129
|
'error.code' => result,
|
112
130
|
'error.message' => message
|
113
131
|
}
|
114
132
|
|
115
|
-
if
|
133
|
+
if result == :addition
|
134
|
+
metadata.merge!(record.metadata)
|
135
|
+
|
136
|
+
errors << Record.new(:warning, record.path, metadata)
|
137
|
+
elsif (copy = checker.copy.paths[record.path])
|
116
138
|
changes = record.diff(copy)
|
117
139
|
|
118
140
|
changes.each do |name|
|
@@ -126,7 +148,13 @@ module Fingerprint
|
|
126
148
|
end
|
127
149
|
end
|
128
150
|
|
129
|
-
|
151
|
+
if error_count
|
152
|
+
summary_message = "#{error_count} error(s) detected."
|
153
|
+
else
|
154
|
+
summary_message = "No errors detected"
|
155
|
+
end
|
156
|
+
|
157
|
+
errors << Record.new(:summary, summary_message, {
|
130
158
|
'error.count' => error_count
|
131
159
|
})
|
132
160
|
|
@@ -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
|