fingerprint 1.3.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
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
- require 'optparse'
24
- require 'pathname'
25
- require 'fingerprint'
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
- OPTIONS = {
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
- ARGV.options do |o|
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
Binary file
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
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 = Recordset.new
32
- copy_recordset = SparseRecordset.new(copy)
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
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
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 (options = {}, &block)
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} = #{sprintf('%0.2f%', processed_count.to_f / total_count.to_f * 100.0)}; Byte #{processed_size} / #{total_size} = #{sprintf('%0.2f%', processed_size.to_f / total_size.to_f * 100.0)}"
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 = {}, &block)
80
- # New API that takes two Recordsets...
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 = Recordset.new
103
+ master_recordset = RecordSet.new
85
104
  master_recordset.parse(master_file)
86
105
 
87
- copy_recordset = Recordset.new
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 = {}, &block)
115
+ def self.verify(master, copy, **options, &block)
97
116
  error_count = 0
98
117
 
99
- errors = options.delete(:recordset) || Recordset.new
118
+ errors = options.delete(:recordset) || RecordSet.new
100
119
  if options[:output]
101
- errors = RecordsetPrinter.new(errors, options[:output])
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 copy
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
- errors << Record.new(:summary, nil, {
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