icfs 0.1.0 → 0.1.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.
data/lib/icfs/store_fs.rb CHANGED
@@ -57,6 +57,16 @@ class StoreFs < Store
57
57
  end
58
58
 
59
59
 
60
+ ###############################################
61
+ # (see Store#file_size)
62
+ #
63
+ def file_size(cid, enum, lnum, fnum)
64
+ File.size(_file(cid, enum, lnum, fnum))
65
+ rescue Errno::ENOENT
66
+ return nil
67
+ end
68
+
69
+
60
70
  ###############################################
61
71
  # (see Store#tempfile)
62
72
  #
data/lib/icfs/store_s3.rb CHANGED
@@ -64,6 +64,17 @@ class StoreS3 < Store
64
64
  end
65
65
 
66
66
 
67
+ ###############################################
68
+ # (see Store#file_size)
69
+ def file_size(cid, enum, lnum, fnum)
70
+ key = _file(cid, enum, lnum, fnum)
71
+ resp = @s3.head_object( bucket: @bck, key: key )
72
+ return resp.content_length
73
+ rescue Aws::S3::Errors::NotFound
74
+ return nil
75
+ end
76
+
77
+
67
78
  ###############################################
68
79
  # (see Store#tempfile)
69
80
  #
data/lib/icfs/users.rb CHANGED
@@ -19,6 +19,8 @@ module ICFS
19
19
  #
20
20
  # @abstract
21
21
  #
22
+ # @todo Add cache flush method
23
+ #
22
24
  class Users
23
25
 
24
26
  ###############################################
@@ -17,6 +17,8 @@ module ICFS
17
17
  ##########################################################################
18
18
  # Implements {ICFS::Users Users} using Elasticsearch to cache
19
19
  # details from another {ICFS::Users Users} instance.
20
+ #
21
+ # @todo Add logging
20
22
  #
21
23
  class UsersElastic < Users
22
24
 
@@ -120,12 +122,12 @@ class UsersElastic < Users
120
122
  obj['active'] = true
121
123
 
122
124
  # store in cache
123
- json = Validate.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
125
+ json = Items.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
124
126
  _write(:users, urg, json)
125
127
 
126
128
  # use cached version
127
129
  else
128
- obj = Validate.parse(json, 'User/Role/Group'.freeze, ValUserCache)
130
+ obj = Items.parse(json, 'User/Role/Group'.freeze, ValUserCache)
129
131
  end
130
132
 
131
133
  # expired
@@ -146,7 +148,7 @@ class UsersElastic < Users
146
148
  obj['last'] = now
147
149
 
148
150
  # and store in cache
149
- json = Validate.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
151
+ json = Items.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
150
152
  _write(:users, urg, json)
151
153
  end
152
154
 
data/lib/icfs/users_fs.rb CHANGED
@@ -26,7 +26,7 @@ class UsersFs < Users
26
26
  # read a raw file
27
27
  def _read(fn)
28
28
  json = File.read(File.join(@path, fn + '.json'.freeze))
29
- obj = Validate.parse(json, 'User/Role/Group'.freeze, Users::ValUser)
29
+ obj = Items.parse(json, 'User/Role/Group'.freeze, Users::ValUser)
30
30
  if obj['name'] != fn
31
31
  raise(Error::Values, 'UsersFs user %s name mismatch'.freeze % fn)
32
32
  end
@@ -51,7 +51,7 @@ class UsersFs < Users
51
51
  # (see Users#read)
52
52
  #
53
53
  def read(urg)
54
- Validate.validate(urg, 'User/Role/Group'.freeze, Items::FieldUsergrp)
54
+ Items.validate(urg, 'User/Role/Group'.freeze, Items::FieldUsergrp)
55
55
 
56
56
  # get the base user
57
57
  usr = _read(urg)
@@ -113,7 +113,7 @@ class UsersFs < Users
113
113
  # (see Users#write)
114
114
  #
115
115
  def write(obj)
116
- Validate.validate(obj, 'User/Role/Group'.freeze, Users::ValUser)
116
+ Items.validate(obj, 'User/Role/Group'.freeze, Users::ValUser)
117
117
  json = JSON.pretty_generate(obj)
118
118
 
119
119
  # write to temp file
@@ -0,0 +1,177 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ require_relative '../store_fs'
13
+ #
14
+ module ICFS
15
+
16
+ module Utils
17
+
18
+ ##########################################################################
19
+ # Backup and restore utilities
20
+ #
21
+ class Backup
22
+
23
+ ###############################################
24
+ # Instance
25
+ #
26
+ # @param cache [Cache] The live cache
27
+ # @param store [Store] The live store
28
+ # @param log [Logger] Where to log
29
+ #
30
+ def initialize(cache, store, log)
31
+ @cache = cache
32
+ @store = store
33
+ @log = log
34
+ end
35
+
36
+
37
+ ###############################################
38
+ # Copy an item
39
+ #
40
+ def _item(dest, title, read, write, args, val=nil)
41
+ @log.debug('ICFS copy: %s'.freeze % title)
42
+
43
+ # read the item
44
+ json = @store.send(read, *args)
45
+ if !json
46
+ @log.warn('ICFS copy: %s is missing'.freeze % title)
47
+ return nil
48
+ end
49
+
50
+ # parse the item if requested
51
+ if val
52
+ obj = JSON.parse(json)
53
+ err = Validate.check(obj, val)
54
+ if err
55
+ @log.error('ICFS copy: %s bad format'.freeze % title)
56
+ return nil
57
+ end
58
+ end
59
+
60
+ # write the item
61
+ dest.send(write, *args, json)
62
+ return obj
63
+
64
+ rescue JSON::ParserError
65
+ @log.error('ICFS copy: %s bad JSON'.freeze % title)
66
+ return nil
67
+ end # def _item
68
+ private :_item
69
+
70
+
71
+ ###############################################
72
+ # Transfer a case to another store
73
+ #
74
+ # @param cid [String] The case ID
75
+ # @param dest [Store] The destination store
76
+ # @param lnum_max [Integer] The highest log
77
+ # @param lnum_min [Integer] The lowest log
78
+ #
79
+ def copy(cid, dest, lnum_max, lnum_min=1)
80
+ @log.info('ICFS copy: %s %d-%d'.freeze % [cid, lnum_min, lnum_max])
81
+ if lnum_min > lnum_max
82
+ raise ArgumentError, 'ICFS copy, log num min is larger than max'.freeze
83
+ end
84
+
85
+ # each log
86
+ lnum = lnum_min
87
+ while lnum <= lnum_max
88
+
89
+ # copy the log
90
+ log = _copy_item(dest,
91
+ 'log %d'.freeze % lnum,
92
+ :log_read,
93
+ :log_write,
94
+ [cid, lnum],
95
+ Items::ItemLog
96
+ )
97
+ if !log
98
+ lnum += 1
99
+ next
100
+ end
101
+
102
+ # entry
103
+ enum = log['entry']['num']
104
+ _copy_item(dest,
105
+ 'entry %d-%d'.freeze % [enum, lnum],
106
+ :entry_read,
107
+ :entry_write,
108
+ [cid, lnum]
109
+ )
110
+
111
+ # index
112
+ if log['index']
113
+ xnum = log['index']['num']
114
+ _copy_item(dest,
115
+ 'index %d-%d'.freeze % [xnum, lnum],
116
+ :index_read,
117
+ :index_write,
118
+ [cid, xnum, lnum]
119
+ )
120
+ end
121
+
122
+ # action
123
+ if log['action']
124
+ anum = log['action']['lnum']
125
+ _copy_item(dest,
126
+ 'action %d-%d'.freeze % [anum, lnum],
127
+ :action_read,
128
+ :action_write,
129
+ [cid, anum, lnum]
130
+ )
131
+ end
132
+
133
+ # case
134
+ if log['case_hash']
135
+ _copy_item(dest,
136
+ 'case %d'.freeze, % lnum,
137
+ :case_read,
138
+ :case_write,
139
+ [cid, lnum]
140
+ )
141
+ end
142
+
143
+ # files
144
+ if log['files_hash']
145
+ log['files_hash'].each_index do |fnum|
146
+
147
+ @log.debug('ICFS copy: file %d-%d-%d'.freeze % [enum, lnum, fnum])
148
+
149
+ # read
150
+ fi = @store.file_read(cid, enum, lnum, fnum)
151
+ if !fi
152
+ @log.warn('ICFS copy: file %d-%d-%d missing'.freeze %
153
+ [enum, lnum, fnum])
154
+ next
155
+ end
156
+
157
+ # copy
158
+ tmp = dest.tempfile
159
+ IO.copy_stream(fi, tmp)
160
+ @store.close(fi)
161
+
162
+ # write
163
+ dest.file_write(cid, enum, lnum, fnum, tmp)
164
+ end
165
+ end
166
+
167
+ lnum += 1
168
+ end
169
+
170
+ end # def copy()
171
+
172
+
173
+ end # class ICFS::Utils::Backup
174
+
175
+ end # module ICFS::Utils
176
+
177
+ end # module ICFS
@@ -0,0 +1,290 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ #
13
+ module ICFS
14
+
15
+
16
+ ##########################################################################
17
+ # Low level utilities for working directly with ICFS systems.
18
+ #
19
+ # @todo Archive/move case tool written
20
+ #
21
+ module Utils
22
+
23
+
24
+ ##########################################################################
25
+ # Check a case for errors
26
+ #
27
+ # @todo Make a function to auto find the last log
28
+ #
29
+ class Check
30
+
31
+
32
+ ###############################################
33
+ # Instance
34
+ #
35
+ # @param store [Store] The store to check
36
+ # @param log [Logger] Where to log
37
+ #
38
+ def initialize(store, log)
39
+ @store = store
40
+ @log = log
41
+ end
42
+
43
+
44
+ ###############################################
45
+ # check an object
46
+ #
47
+ def _item(title, read, read_args, hist, hash, val, con)
48
+
49
+ @log.debug('ICFS check: %s'.freeze % title)
50
+
51
+ # read
52
+ json = @store.send(read, *read_args)
53
+ if !json
54
+ if hist
55
+ @log.warn('ICFS check: %s is missing and historical'.freeze % title)
56
+ else
57
+ @log.error('ICFS check: %s is missing and current'.freeze % title)
58
+ end
59
+ return nil
60
+ end
61
+
62
+ # hash
63
+ if hash
64
+ if hash != ICFS.hash(json)
65
+ @log.error('ICFS check: %s hash bad'.freeze % title)
66
+ end
67
+ else
68
+ @log.warn('ICFS check: %s hash unverified'.freeze % title)
69
+ end
70
+
71
+ # parse
72
+ obj = JSON.parse(json)
73
+ err = Validate.check(obj, val)
74
+ if err
75
+ @log.error('ICFS check: %s bad format'.freeze % title)
76
+ return nil
77
+ end
78
+
79
+ # inconsistent
80
+ con.each do |name, num|
81
+ if obj[name] != read_args[num]
82
+ @log.error('ICFS check: %s inconsistent'.freeze % title)
83
+ return nil
84
+ end
85
+ end
86
+
87
+ return obj
88
+
89
+ rescue JSON::ParserError
90
+ @log.error('ICFS check: %s bad JSON'.freeze % title)
91
+ return nil
92
+ end # def _item()
93
+ private :_item
94
+
95
+
96
+ ###############################################
97
+ # Check a case
98
+ #
99
+ # @param cur_log [Integer] The last log
100
+ # @param cur_hash [String] The hash of the last log
101
+ #
102
+ def check(cid, cur_log, cur_hash, opts={})
103
+ @log.info('ICFS check: case %s'.freeze % cid)
104
+
105
+ ent_cur = Set.new
106
+ cse_cur = false
107
+ idx_cur = Set.new
108
+ act_cur = Set.new
109
+ file_cur = Set.new
110
+
111
+
112
+ # go thru the logs from most current
113
+ lnum = cur_log
114
+ hash_log = cur_hash
115
+ time_log = Time.now.to_i
116
+ while( lnum > 0 )
117
+
118
+ # log
119
+ log = _item(
120
+ 'log %d'.freeze % lnum,
121
+ :log_read,
122
+ [cid, lnum],
123
+ false,
124
+ hash_log,
125
+ Items::ItemLog,
126
+ [
127
+ ['caseid'.freeze, 0].freeze,
128
+ ['log'.freeze, 1].freeze
129
+ ].freeze,
130
+ )
131
+ if !log
132
+ hash_log = nil
133
+ lnum = lnum - 1
134
+ next
135
+ end
136
+
137
+ # check that time decreases
138
+ if log['time'] > time_log
139
+ @log.warn('ICFS check: log %d time inconsistent'.freeze % lnum)
140
+ end
141
+
142
+ # entry
143
+ enum = log['entry']['num']
144
+ ent = _item(
145
+ 'entry %d-%d' % [enum, lnum],
146
+ :entry_read,
147
+ [cid, enum, lnum],
148
+ ent_cur.include?(enum),
149
+ log['entry']['hash'],
150
+ Items::ItemEntry,
151
+ [
152
+ ['caseid'.freeze, 0].freeze,
153
+ ['entry'.freeze, 1].freeze,
154
+ ['log'.freeze, 2].freeze
155
+ ].freeze,
156
+ )
157
+
158
+ # current entry
159
+ unless ent_cur.include?(enum)
160
+ ent_cur.add(enum)
161
+ if ent['files']
162
+ ent['files'].each do |fd|
163
+ file_cur.add( '%d-%d-%d'.freeze % [enum, fd['num'], fd['log']] )
164
+ end
165
+ end
166
+ end
167
+
168
+ # index
169
+ if log['index']
170
+ xnum = log['index']['num']
171
+ idx = _item(
172
+ 'index %d-%d'. freeze % [xnum, lnum],
173
+ :index_read,
174
+ [cid, xnum, lnum],
175
+ idx_cur.include?(xnum),
176
+ log['index']['hash'],
177
+ Items::ItemIndex,
178
+ [
179
+ ['caseid'.freeze, 0].freeze,
180
+ ['index'.freeze, 1].freeze,
181
+ ['log'.freeze, 2].freeze
182
+ ]
183
+ )
184
+ idx_cur.add(xnum)
185
+ end
186
+
187
+ # action
188
+ if log['action']
189
+ anum = log['action']['num']
190
+ act = _item(
191
+ 'action %d-%d'.freeze % [anum, lnum],
192
+ :action_read,
193
+ [cid, anum, lnum],
194
+ act_cur.include?(anum),
195
+ log['action']['hash'],
196
+ Items::ItemAction,
197
+ [
198
+ ['caseid'.freeze, 0].freeze,
199
+ ['action'.freeze, 1].freeze,
200
+ ['log'.freeze, 2].freeze
201
+ ]
202
+ )
203
+ act_cur.add(anum)
204
+ end
205
+
206
+ # case
207
+ if log['case_hash']
208
+ cse = _item(
209
+ 'case %d'.freeze % lnum,
210
+ :case_read,
211
+ [cid, lnum],
212
+ cse_cur,
213
+ log['case_hash'],
214
+ Items::ItemCase,
215
+ [
216
+ ['caseid'.freeze, 0].freeze,
217
+ ['log'.freeze, 1].freeze
218
+ ]
219
+ )
220
+ cse_cur = true
221
+ end
222
+
223
+ # files
224
+ if log['files_hash']
225
+ fnum = 0
226
+ log['files_hash'].each do |hash|
227
+ fnum = fnum + 1
228
+ fn = '%d-%d-%d'.freeze % [enum, fnum, lnum]
229
+ cur = file_cur.include?(fn)
230
+ file_cur.delete(fn) if cur
231
+
232
+ @log.debug('ICFS check: file %s'.freeze % fn)
233
+
234
+ # read/size
235
+ if opts[:hash_all] || (cur && opts[:hash_current])
236
+ fi = @store.file_read(cid, enum, lnum, fnum)
237
+ elsif opts[:stat_all] || (cur && opts[:stat_current])
238
+ fi = @store.file_size(cur, enum, lnum, fnum)
239
+ else
240
+ fi = true
241
+ end
242
+
243
+ # missing
244
+ if !fi
245
+ if cur
246
+ @log.error('ICFS check: file %s missing and current'.freeze %
247
+ fn)
248
+ else
249
+ @log.warn('ICFS check: file %s missing and historical'.freeze %
250
+ fn)
251
+ end
252
+ end
253
+
254
+ # hash
255
+ if fi.is_a?(File)
256
+ # check
257
+ if hash != ICFS.hash_temp(fi)
258
+ @log.error('ICFS check: file %s hash bad'.freeze % fn)
259
+ end
260
+
261
+ # close
262
+ if fi.respond_to?(:close!)
263
+ fi.close!
264
+ else
265
+ fi.close
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ # previous log
272
+ lnum = lnum - 1
273
+ hash_log = log['prev']
274
+ end
275
+
276
+ # check for any non-existant current files
277
+ unless file_cur.empty?
278
+ file_cur.each do |fn|
279
+ @log.error('ICFS check: file %s current but not logged'.freeze % fn)
280
+ end
281
+ end
282
+
283
+ @log.debug('ICFS check: case %s complete'.freeze % cid)
284
+ end # def check()
285
+
286
+
287
+ end # class ICFS::Utils::Check
288
+
289
+ end # module ICFS::Utils
290
+ end # module ICFS