lnbackup 2.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/bin/lnbackup +187 -0
- data/bin/lnmount +16 -0
- data/bin/lnstat +4 -0
- data/bin/lnumount +4 -0
- data/bin/pc_backup +66 -0
- data/bin/sql_backup.sh +123 -0
- data/lib/lnbackup/backup.rb +1938 -0
- data/lib/lnbackup/freespace.rb +73 -0
- data/lib/lnbackup/util.rb +123 -0
- data/lib/lnbackup/version.rb +3 -0
- data/lib/lnbackup.rb +21 -0
- metadata +63 -0
@@ -0,0 +1,1938 @@
|
|
1
|
+
module LnBackup
|
2
|
+
CONFIG_D = '/etc/lnbackup.d/'
|
3
|
+
LOG_FILE = '/var/log/lnbackup'
|
4
|
+
STATUS_FILE_PREF = '/var/log/lnbackup.status'
|
5
|
+
PCS_STATUS = '/var/log/lnbackup-pcs.status'
|
6
|
+
LILO_PATH = '/sbin/lilo'
|
7
|
+
|
8
|
+
#### return codes ####
|
9
|
+
BACKUP_OK = 0 # 0 -- ok
|
10
|
+
BACKUP_EXISTS = 1 # 1 -- warning
|
11
|
+
FAILED_TO_FREE_SPACE = 12 # 12... -- error codes
|
12
|
+
NO_BACKUP_DIR = 13
|
13
|
+
MOUNT_FAILED = 14
|
14
|
+
INVALID_BACKUP = 15
|
15
|
+
PRE_COMMAND_FAILED = 16
|
16
|
+
POST_COMMAND_FAILED = 17
|
17
|
+
FSCK_FAILED = 18
|
18
|
+
SMBMOUNT_FAILED = 19
|
19
|
+
LNBACKUP_RUNNING = 20
|
20
|
+
DEVICE_NOT_FOUND = 21
|
21
|
+
PIDFILE_FAILED = 22
|
22
|
+
DEVICE_FULL = 100 # when running in --no-delete mode and we run out of disk space
|
23
|
+
|
24
|
+
MESSAGES = {
|
25
|
+
BACKUP_EXISTS => 'backup already exists',
|
26
|
+
FAILED_TO_FREE_SPACE => 'failed to free space',
|
27
|
+
NO_BACKUP_DIR => 'no backup dir',
|
28
|
+
MOUNT_FAILED => 'mount failed',
|
29
|
+
INVALID_BACKUP => 'invalid backup name',
|
30
|
+
PRE_COMMAND_FAILED => 'pre backup cmd failed',
|
31
|
+
POST_COMMAND_FAILED => 'post backup cmd failed',
|
32
|
+
FSCK_FAILED => 'fsck failed',
|
33
|
+
LNBACKUP_RUNNING => 'lnbackup already running',
|
34
|
+
DEVICE_NOT_FOUND => 'backup device not found',
|
35
|
+
DEVICE_FULL => 'device full',
|
36
|
+
PIDFILE_FAILED => 'failed to create pid/lock file'
|
37
|
+
}
|
38
|
+
|
39
|
+
class LnBackup
|
40
|
+
|
41
|
+
attr :stats
|
42
|
+
|
43
|
+
def conf_val(key)
|
44
|
+
raise "no @cur_config!" unless @cur_config
|
45
|
+
@cur_config[key] || @config[key]
|
46
|
+
end
|
47
|
+
|
48
|
+
#### inicializace a konfigurace ####
|
49
|
+
def load_config
|
50
|
+
@config = {}
|
51
|
+
p "config dir: #{@config_dir}"
|
52
|
+
@log.debug { "config dir: #{@config_dir}" }
|
53
|
+
Dir[(@config_dir+File::SEPARATOR).squeeze('/')+'*'].sort { |a,b|
|
54
|
+
a.split(File::SEPARATOR)[-1][1..2].to_i <=> b.split(File::SEPARATOR)[-1][1..2].to_i
|
55
|
+
}.each do |conf|
|
56
|
+
next if conf =~ /\/[^\/]*\.[^\/]*$/
|
57
|
+
if FileTest.file?(conf)
|
58
|
+
@log.debug { "\tadding config file: #{conf}" }
|
59
|
+
@config.append( eval( File.open(conf).read ) )
|
60
|
+
end
|
61
|
+
end
|
62
|
+
@pcb = @config[:pcb]
|
63
|
+
@config[:mount_point].gsub!(%r{(.)/$},'\1')
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize( args={ :log_level => Logger::INFO,
|
67
|
+
:test_mode => false,
|
68
|
+
:config_dir => CONFIG_D,
|
69
|
+
:log_file => LOG_FILE,
|
70
|
+
:status_file_pref => STATUS_FILE_PREF,
|
71
|
+
:source_prefix => '',
|
72
|
+
:target_dir_name => nil,
|
73
|
+
:delay => nil,
|
74
|
+
:delay_denom => nil,
|
75
|
+
:no_delete => false,
|
76
|
+
:no_acl => false,
|
77
|
+
:max_iter => 5,
|
78
|
+
} )
|
79
|
+
log_level, @test_mode, config_dir, log_file,
|
80
|
+
@status_file_pref, @source_prefix, @target_dir_name, @delay, @delay_denom,
|
81
|
+
@no_delete, @no_acl, @max_iter =
|
82
|
+
args.values_at( :log_level, :test_mode, :config_dir, :log_file,
|
83
|
+
:status_file_pref, :source_prefix, :target_dir_name, :delay, :delay_denom,
|
84
|
+
:no_delete, :no_acl, :max_iter )
|
85
|
+
|
86
|
+
@log = nil
|
87
|
+
begin
|
88
|
+
@log = Logger.new( log_file == '-' ? STDOUT : log_file )
|
89
|
+
rescue => e #Errno::EACCES, Errno::EROFS
|
90
|
+
$stderr.puts "Exception #{e.class.to_s}: #{e.message}."
|
91
|
+
$stderr.puts "\tUsing STDERR for logging."
|
92
|
+
@log = Logger.new( STDERR )
|
93
|
+
@log.debug("TEST")
|
94
|
+
end
|
95
|
+
@log.level = log_level
|
96
|
+
@log.info { "Running in test mode." } if @test_mode
|
97
|
+
|
98
|
+
@exclude_list = []
|
99
|
+
@last_file = nil
|
100
|
+
@config_dir = config_dir
|
101
|
+
load_config
|
102
|
+
end
|
103
|
+
|
104
|
+
def config_init( name )
|
105
|
+
@backup_name = name
|
106
|
+
name = name.intern if name.class==String
|
107
|
+
|
108
|
+
@status_file = @status_file_pref + '-' + @backup_name
|
109
|
+
|
110
|
+
# otestujeme, jestli je definovana vybrana zaloha zadaneho jmena
|
111
|
+
if not @config.key?(name)
|
112
|
+
@log.fatal { "No config for backup '#{@backup_name}', exiting!" }
|
113
|
+
return INVALID_BACKUP
|
114
|
+
end
|
115
|
+
@log.debug{ $HAVE_ACL ? "ACL support" : "no ACL support" }
|
116
|
+
|
117
|
+
# @cur_config bude obsahovat konfiguraci vybrane zalohy
|
118
|
+
@cur_config = @config[name]
|
119
|
+
@cur_config[:mount_point].gsub!(%r{(.)/$},'\1') if @cur_config.key?(:mount_point)
|
120
|
+
|
121
|
+
# find partition by label if necessary
|
122
|
+
if conf_val(:device_label) or conf_val(:device_uuid)
|
123
|
+
label = conf_val(:device_label) ? conf_val(:device_label) : conf_val(:device_uuid)
|
124
|
+
device = find_dev_by( conf_val(:device), label,
|
125
|
+
conf_val(:device_label) ? :label : :uuid )
|
126
|
+
disk = disk_for_device( device )
|
127
|
+
@log.debug { "label/uuid: #{label.inspect}, device: #{device}, disk: #{disk}" }
|
128
|
+
@log.error { "no device with given label/uuid #{label.inspect}" } unless device
|
129
|
+
|
130
|
+
return DEVICE_NOT_FOUND if device.nil? or disk.nil?
|
131
|
+
@cur_config[:device], @cur_config[:disk] = [device, disk]
|
132
|
+
end
|
133
|
+
return 0
|
134
|
+
end
|
135
|
+
|
136
|
+
#### hledani starych backupu ####
|
137
|
+
def find_backups( name=nil, any=false ) # TODO --- pripravit na moznost hodinovych zaloh
|
138
|
+
dest = nil
|
139
|
+
hourly = nil
|
140
|
+
# budto mame zadano jmeno zalohy, na kterou se ptame, nebo se pouzije ta,
|
141
|
+
# s kterou bylo spusteno run_backup
|
142
|
+
if name
|
143
|
+
name_s = name.class == String ? name.intern : name
|
144
|
+
if !@config.key?(name_s)
|
145
|
+
@log.fatal { "No config for backup '#{name_s.to_s}'" }
|
146
|
+
return []
|
147
|
+
end
|
148
|
+
dest = @config[name_s].key?(:target_dir) ? @config[name_s][:target_dir] : conf_val(:mount_point)
|
149
|
+
hourly = @config[name_s][:hourly]
|
150
|
+
else
|
151
|
+
name = @target_dir_name
|
152
|
+
dest = @cur_config.key?(:target_dir) ? @cur_config[:target_dir] : conf_val(:mount_point)
|
153
|
+
hourly = @cur_config[:hourly]
|
154
|
+
end
|
155
|
+
if any
|
156
|
+
return Dir["#{dest}/backup/*/*/*"].sort
|
157
|
+
else
|
158
|
+
return Dir["#{dest}/backup/*/*/*/#{name}"].sort
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
#### kopirovaci rutiny ####
|
163
|
+
def do_cp_a( src, dest )
|
164
|
+
find_exclude?(src)
|
165
|
+
|
166
|
+
@log.debug { "cp -a #{src} #{dest}" }
|
167
|
+
|
168
|
+
if !@test_mode
|
169
|
+
ret, out, err = system_catch_stdin_stderr('/bin/cp', '-a', src, dest)
|
170
|
+
if ret != 0
|
171
|
+
@log.info { "/bin/cp failed: '#{err}' (#{src}), making space..." }
|
172
|
+
|
173
|
+
make_some_space if @space_calc.much_used?
|
174
|
+
|
175
|
+
ret, out, err = system_catch_stdin_stderr('/bin/cp', '-a', src, dest)
|
176
|
+
if ret != 0
|
177
|
+
@log.error { "do_cp_a: file copy failed ( '/bin/cp', '-a', #{src}, #{dest} ), out: #{out}, error: #{err}" }
|
178
|
+
@log.error { "\t #{src} not copied" }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def do_cp_dir(src, dest)
|
185
|
+
find_exclude?(src + '/')
|
186
|
+
|
187
|
+
@log.debug { "FileUtils.mkpath #{dest}" }
|
188
|
+
|
189
|
+
if !@test_mode
|
190
|
+
begin
|
191
|
+
FileUtils.mkpath(dest)
|
192
|
+
rescue Errno::ENOSPC
|
193
|
+
@log.info { "FileUtils.mkpath: Errno::ENOSPC (#{src}), making space..." }
|
194
|
+
make_some_space and retry
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def do_hardlink(last, src, dest)
|
200
|
+
find_exclude?(src)
|
201
|
+
|
202
|
+
@log.debug { "File.link #{last}, #{dest}" }
|
203
|
+
if !@test_mode
|
204
|
+
begin
|
205
|
+
File.link(last, dest)
|
206
|
+
rescue Errno::ENOSPC
|
207
|
+
@log.info { "File.link: Errno::ENOSPC (#{src}), making space..." }
|
208
|
+
make_some_space and retry
|
209
|
+
rescue => err
|
210
|
+
@log.error { "File.link (#{last} -> #{dest}) error: #{err.message} --> skipping from backup" } # TODO: nastavit indikator chyby!
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def do_copy(src,dest)
|
216
|
+
find_exclude?(src)
|
217
|
+
|
218
|
+
@log.debug { "do_copy( #{src}, #{dest})" }
|
219
|
+
|
220
|
+
begin
|
221
|
+
# nejdrive udelame misto
|
222
|
+
make_space_for(src)
|
223
|
+
# pak kopirujeme
|
224
|
+
copy_preserve( src, dest ) if !@test_mode
|
225
|
+
|
226
|
+
rescue Errno::ENOSPC # behem kopirovani nastala vyjimka -- doslo misto
|
227
|
+
@log.info { "copy_preserve: Errno::ENOSPC (#{src}), making space..." }
|
228
|
+
make_some_space and # nejdriv udelame nejake misto, protoze make_space_for
|
229
|
+
# nemusi nic smazat, napriklad pokud je mezitim zdroj
|
230
|
+
# soubor smazan
|
231
|
+
make_space_for(src) and retry
|
232
|
+
# pak udelame misto pro dany soubor (muze byl
|
233
|
+
# veliky) a zkusime to znovu
|
234
|
+
rescue SysCopyFailure => err
|
235
|
+
# syscopy selhal
|
236
|
+
@log.info { "copy_preserve: SysCopyFailure (#{src}), checking space..." }
|
237
|
+
# muze se stat, ze selze, protoze nema misto, nebo z neznameho duvodu (treba Errno::EIO)
|
238
|
+
begin
|
239
|
+
if @space_calc.can_backup?(src)
|
240
|
+
# mistem to nebylo --> chyba
|
241
|
+
@log.error { "copy_preserve: SysCopyFailure (#{src}), enough space --> skipping file" }
|
242
|
+
return false
|
243
|
+
else
|
244
|
+
# TODO: sem by se vubec nemelo dojit (na dojiti mista je samostatna vyjimka ENOSPC)
|
245
|
+
# asi nam nekdo pod rukama zabral misto, zkusime znovu
|
246
|
+
@log.info { "copy_preserve: Making space (#{src})..." }
|
247
|
+
make_space_for(src) and retry
|
248
|
+
end
|
249
|
+
rescue => e
|
250
|
+
@log.error { "do_copy: exception #{e.class}: #{e.message} when handling exception" }
|
251
|
+
@log.error { "skipping file #{src}" }
|
252
|
+
return false
|
253
|
+
end
|
254
|
+
rescue Errno::EOVERFLOW
|
255
|
+
# soubor je moc velky, neni zazalohovan
|
256
|
+
@log.error { "copy_preserve: Errno::EOVERFLOW (#{src}), file too large --> skipping file" }
|
257
|
+
return false
|
258
|
+
rescue Errno::ENOENT
|
259
|
+
# soubor nam zmizel pod rukama behem zalohovani
|
260
|
+
@log.warn { "copy_preserve: Errno::ENOENT (#{src}), file deleted during backup" }
|
261
|
+
return false
|
262
|
+
end
|
263
|
+
return true
|
264
|
+
end
|
265
|
+
|
266
|
+
#
|
267
|
+
# resolve symlinks in file path
|
268
|
+
#
|
269
|
+
def realpath(file)
|
270
|
+
if not File.respond_to?(:readlink) then
|
271
|
+
return file
|
272
|
+
end
|
273
|
+
|
274
|
+
file = File.expand_path(file)
|
275
|
+
return file if file == '/'
|
276
|
+
|
277
|
+
total = ''
|
278
|
+
file.split(File::SEPARATOR).each do |comp|
|
279
|
+
next if comp.empty?
|
280
|
+
total << File::SEPARATOR + comp
|
281
|
+
if File.symlink?(total) then
|
282
|
+
begin
|
283
|
+
total = File.expand_path( File.readlink(total), File.dirname(total) )
|
284
|
+
end while File.symlink?(total)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
return total
|
289
|
+
end
|
290
|
+
|
291
|
+
#### uvolnovani mista ####
|
292
|
+
def make_space_for( path )
|
293
|
+
while not @space_calc.can_backup?(path) do
|
294
|
+
@log.debug { "make_space_for: Not enough space for #{path}, removing oldest." }
|
295
|
+
remove_oldest
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def make_some_space
|
300
|
+
remove_oldest
|
301
|
+
while @space_calc.much_used?
|
302
|
+
remove_oldest
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def remove_oldest
|
307
|
+
if @no_delete
|
308
|
+
@log.fatal { "backup device is full, terminating..." }
|
309
|
+
raise MakeSpaceFailure.new(DEVICE_FULL)
|
310
|
+
end
|
311
|
+
backups = find_backups(nil,true)
|
312
|
+
|
313
|
+
oldest = nil
|
314
|
+
# hledame nejstarsi zalohu, ktera neni na seznamu chranenych
|
315
|
+
backups.each do |backup|
|
316
|
+
if not @dont_delete.index(backup)
|
317
|
+
oldest = backup
|
318
|
+
break
|
319
|
+
end
|
320
|
+
@log.debug { "not deleting #{backup}" }
|
321
|
+
end
|
322
|
+
|
323
|
+
if oldest
|
324
|
+
oldest.sub!("/#{@backup_name}$",'')
|
325
|
+
if FileTest.directory?(oldest)
|
326
|
+
@log.info { "free blocks: %s files: %s" % @space_calc.get_free }
|
327
|
+
@log.info { "removing oldest: '#{oldest}'" }
|
328
|
+
|
329
|
+
if not @test_mode
|
330
|
+
if Process.euid != 0
|
331
|
+
@log.debug { "/bin/chmod -R u+rwX #{oldest}" }
|
332
|
+
system('/bin/chmod', '-R', 'u+rwX', oldest)
|
333
|
+
end
|
334
|
+
@log.debug { "/bin/rm -rf #{oldest}" } or raise MakeSpaceFailure.new(FAILED_TO_FREE_SPACE)
|
335
|
+
system('/bin/rm', '-rf', oldest) or raise MakeSpaceFailure.new(FAILED_TO_FREE_SPACE)
|
336
|
+
|
337
|
+
# mazeme prazdne adresare
|
338
|
+
begin
|
339
|
+
oldest.sub!(%r'/\d\d$','')
|
340
|
+
Dir.rmdir(oldest) # mazeme mesic
|
341
|
+
oldest.sub!(%r'/\d\d$','')
|
342
|
+
Dir.rmdir(oldest) # mazeme rok
|
343
|
+
rescue
|
344
|
+
# odchytávame výjimku mazání neprázdného adresáře
|
345
|
+
end
|
346
|
+
end
|
347
|
+
@log.info { "removing oldest done" }
|
348
|
+
@log.info { "free blocks: %s files: %s" % @space_calc.get_free }
|
349
|
+
return true
|
350
|
+
end
|
351
|
+
@log.fatal { "remove_oldest: FileTest.directory?(#{oldest}) failed, not removing (processing file: #{@last_file})" }
|
352
|
+
else
|
353
|
+
@log.fatal { "remove_oldest: No backup to be removed found (processing file: #{@last_file})." }
|
354
|
+
end
|
355
|
+
|
356
|
+
@log.fatal { "remove_oldest failed" }
|
357
|
+
raise MakeSpaceFailure.new(FAILED_TO_FREE_SPACE)
|
358
|
+
end
|
359
|
+
|
360
|
+
#### spusteni programu se zachycenim stdout a stderr ####
|
361
|
+
def _system_catch_stdin_stderr(*args)
|
362
|
+
args.unshift( nil )
|
363
|
+
system_catch_stdin_stderr_with_input( args )
|
364
|
+
end
|
365
|
+
|
366
|
+
def system_catch_stdin_stderr(*args)
|
367
|
+
args = args.collect {|a| a.to_s}
|
368
|
+
|
369
|
+
pipe_peer_in, pipe_me_out = IO.pipe
|
370
|
+
pipe_me_in, pipe_peer_out = IO.pipe
|
371
|
+
pipe_me_error_in, pipe_peer_error_out = IO.pipe
|
372
|
+
|
373
|
+
pid = nil
|
374
|
+
begin
|
375
|
+
Thread.exclusive do
|
376
|
+
STDOUT.flush
|
377
|
+
STDERR.flush
|
378
|
+
|
379
|
+
pid = fork {
|
380
|
+
STDIN.reopen(pipe_peer_in)
|
381
|
+
STDOUT.reopen(pipe_peer_out)
|
382
|
+
STDERR.reopen(pipe_peer_error_out)
|
383
|
+
pipe_me_out.close
|
384
|
+
pipe_me_in.close
|
385
|
+
pipe_me_error_in.close
|
386
|
+
|
387
|
+
begin
|
388
|
+
exec(*args)
|
389
|
+
rescue
|
390
|
+
exit!(255)
|
391
|
+
end
|
392
|
+
}
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
pipe_peer_in.close
|
397
|
+
pipe_peer_out.close
|
398
|
+
pipe_peer_error_out.close
|
399
|
+
pipe_me_out.sync = true
|
400
|
+
|
401
|
+
pipe_me_out.close
|
402
|
+
got_stdin = pipe_me_in.read
|
403
|
+
pipe_me_in.close unless pipe_me_in.closed?
|
404
|
+
got_stderr = pipe_me_error_in.read
|
405
|
+
pipe_me_error_in.close unless pipe_me_error_in.closed?
|
406
|
+
|
407
|
+
p, status = Process.waitpid2(pid)
|
408
|
+
return [status >> 8, got_stdin, got_stderr]
|
409
|
+
end
|
410
|
+
|
411
|
+
def system_catch_stdin_stderr_with_input(input, *args)
|
412
|
+
args = args.collect {|a| a.to_s}
|
413
|
+
|
414
|
+
pipe_peer_in, pipe_me_out = IO.pipe
|
415
|
+
pipe_me_in, pipe_peer_out = IO.pipe
|
416
|
+
pipe_me_error_in, pipe_peer_error_out = IO.pipe
|
417
|
+
|
418
|
+
pid = nil
|
419
|
+
begin
|
420
|
+
Thread.exclusive do
|
421
|
+
STDOUT.flush
|
422
|
+
STDERR.flush
|
423
|
+
|
424
|
+
pid = fork {
|
425
|
+
STDIN.reopen(pipe_peer_in)
|
426
|
+
STDOUT.reopen(pipe_peer_out)
|
427
|
+
STDERR.reopen(pipe_peer_error_out)
|
428
|
+
pipe_me_out.close
|
429
|
+
pipe_me_in.close
|
430
|
+
pipe_me_error_in.close
|
431
|
+
|
432
|
+
begin
|
433
|
+
exec(*args)
|
434
|
+
rescue
|
435
|
+
exit!(255)
|
436
|
+
end
|
437
|
+
}
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
pipe_peer_in.close
|
442
|
+
pipe_peer_out.close
|
443
|
+
pipe_peer_error_out.close
|
444
|
+
|
445
|
+
pipe_me_out.sync = true
|
446
|
+
pipe_me_out.print( input ) if input != nil
|
447
|
+
pipe_me_out.close
|
448
|
+
|
449
|
+
got_stdin = pipe_me_in.read
|
450
|
+
pipe_me_in.close unless pipe_me_in.closed?
|
451
|
+
got_stderr = pipe_me_error_in.read
|
452
|
+
pipe_me_error_in.close unless pipe_me_error_in.closed?
|
453
|
+
|
454
|
+
p, status = Process.waitpid2(pid)
|
455
|
+
return [status >> 8, got_stdin, got_stderr]
|
456
|
+
end
|
457
|
+
|
458
|
+
# volani tune2fs
|
459
|
+
def tune2fs(dev)
|
460
|
+
@log.debug { "calling tune2fs on #{dev}" }
|
461
|
+
if Process.euid != 0
|
462
|
+
ret, out, err = system_catch_stdin_stderr( '/usr/bin/sudo', '/sbin/tune2fs', '-l', dev )
|
463
|
+
else
|
464
|
+
ret, out, err = system_catch_stdin_stderr( '/sbin/tune2fs', '-l', dev )
|
465
|
+
end
|
466
|
+
if ret != 0
|
467
|
+
@log.error { "tune2fs failed with exit code: '#{ret}'" }
|
468
|
+
@log.error { "\tstdout: #{out}" }
|
469
|
+
@log.error { "\tstderr: #{err}" }
|
470
|
+
return {}
|
471
|
+
end
|
472
|
+
results = {}
|
473
|
+
out.split("\n").each do |line|
|
474
|
+
if line =~ /^([^:]+):\s*(.*)$/
|
475
|
+
results[$1] = $2
|
476
|
+
end
|
477
|
+
end
|
478
|
+
return results
|
479
|
+
end
|
480
|
+
|
481
|
+
# resetovani pocitadla mountu
|
482
|
+
def reset_mount_count( dev ) # TODO error handling
|
483
|
+
@log.info { "reseting mount count on #{dev}" }
|
484
|
+
ret, out, err = system_catch_stdin_stderr( '/sbin/tune2fs', '-C', '0', dev )
|
485
|
+
if ret != 0
|
486
|
+
@log.error { "tune2fs failed with exit code: '#{ret}'" }
|
487
|
+
@log.error { "\tstdout: #{out}" }
|
488
|
+
@log.error { "\tstderr: #{err}" }
|
489
|
+
return false
|
490
|
+
end
|
491
|
+
return true
|
492
|
+
end
|
493
|
+
|
494
|
+
def disk_for_device( device )
|
495
|
+
# TODO: udelat poradne
|
496
|
+
if device =~ %r{^(/dev/[hs]d[a-z])[0-9]$}
|
497
|
+
return $1
|
498
|
+
elsif device =~ %r{^(/dev/.*?)p[0-9]$}
|
499
|
+
return $1
|
500
|
+
end
|
501
|
+
return nil
|
502
|
+
end
|
503
|
+
|
504
|
+
# nastavovani labelu: tune2fs -L MUFF2 /dev/sdb1
|
505
|
+
def find_dev_by( device_mask, label, by=:label )
|
506
|
+
found = false
|
507
|
+
device = nil
|
508
|
+
if Array === device_mask
|
509
|
+
devices = device_mask
|
510
|
+
else
|
511
|
+
devices = Dir[ device_mask ]
|
512
|
+
end
|
513
|
+
|
514
|
+
File.readlines('/proc/partitions')[2..-1].each do |line|
|
515
|
+
major, minor, blocks, name = line.sub(/^\s+/,'').split(/\s+/)
|
516
|
+
|
517
|
+
dev = File.join('/dev', name)
|
518
|
+
# test na masku
|
519
|
+
next unless devices.index( dev )
|
520
|
+
|
521
|
+
if FileTest.blockdev?( dev )
|
522
|
+
@log.debug( "find_dev_by: checking #{dev}" )
|
523
|
+
dump = tune2fs( dev )
|
524
|
+
if (
|
525
|
+
((by == :label) && (String === label) && ( dump['Filesystem volume name'] =~ /^#{label}$/ )) ||
|
526
|
+
((by == :label) && (Array === label) && ( label.index(dump['Filesystem volume name'] ))) ||
|
527
|
+
((by == :uuid) && (String === label) && ( dump['Filesystem UUID'] == label )) ||
|
528
|
+
((by == :uuid) && (Array === label) && ( label.index(dump['Filesystem UUID'] )))
|
529
|
+
)
|
530
|
+
if found
|
531
|
+
@log.error{ "found at least two devices with given label #{label.inspect}: #{dev} and #{device}" }
|
532
|
+
return nil
|
533
|
+
else
|
534
|
+
found = true
|
535
|
+
device = dev
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
return device
|
541
|
+
end
|
542
|
+
|
543
|
+
#### pomocne rutiny pro praci s crypto loop ####
|
544
|
+
def find_loop(max=8)
|
545
|
+
ret, out, err = system_catch_stdin_stderr('/sbin/losetup', '-a')
|
546
|
+
return nil if ret != 0
|
547
|
+
|
548
|
+
loops = out.collect do |l|
|
549
|
+
(dev,x) = l.split(':',2)
|
550
|
+
dev.sub(%r|^/dev/loop|,'').to_i
|
551
|
+
end
|
552
|
+
|
553
|
+
for i in 0..max
|
554
|
+
if not loops.index(i)
|
555
|
+
return "/dev/loop#{i}"
|
556
|
+
end
|
557
|
+
end
|
558
|
+
return nil
|
559
|
+
end
|
560
|
+
|
561
|
+
def mk_loop( dev, passwd, size=0, crypto='aes-256' )
|
562
|
+
if dev !~ %r|/dev/|
|
563
|
+
system_catch_stdin_stderr( '/bin/dd', 'if=/dev/zero', "of=#{dev}", 'bs=1M', "count=#{size}" )
|
564
|
+
end
|
565
|
+
|
566
|
+
loop_dev = find_loop
|
567
|
+
system_catch_stdin_stderr_with_input( passwd+"\n", '/sbin/losetup', '-p', '0', '-e', crypto, loop_dev, dev )
|
568
|
+
system_catch_stdin_stderr( 'mkfs.ext3', loop_dev )
|
569
|
+
system_catch_stdin_stderr( '/sbin/losetup', '-d', loop_dev )
|
570
|
+
end
|
571
|
+
|
572
|
+
#### pomocne File rutiny ####
|
573
|
+
def restore_dir_attributes(dirs,depth=0)
|
574
|
+
#@log.debug { "restore_dir_attributes: depth=#{depth}" }
|
575
|
+
return dirs.find_all do |dir, stat, access_acl, default_acl, d|
|
576
|
+
next true if d<depth
|
577
|
+
|
578
|
+
@log.debug { "chown, utime, chmod #{dir}" }
|
579
|
+
if not @test_mode
|
580
|
+
begin
|
581
|
+
File.chown(stat.uid, stat.gid, dir)
|
582
|
+
rescue => error
|
583
|
+
@log.warn { "restore_attributes: chown #{dir} got error and didn't restore owner': #{error.message}\n" }
|
584
|
+
end
|
585
|
+
begin
|
586
|
+
File.chmod(stat.mode, dir)
|
587
|
+
rescue => error
|
588
|
+
@log.warn { "restore_attributes: chmod #{dir} got error and didn't restore rights': #{error.message}\n" }
|
589
|
+
end
|
590
|
+
if $HAVE_ACL and not @no_acl
|
591
|
+
begin
|
592
|
+
access_acl.set_file(dir) if access_acl
|
593
|
+
default_acl.set_default(dir) if default_acl
|
594
|
+
rescue => error
|
595
|
+
@log.warn { "restore_attributes: couldn't restore ACLs: #{error.message}\n" }
|
596
|
+
end
|
597
|
+
end
|
598
|
+
begin
|
599
|
+
File.utime(stat.atime, stat.mtime, dir)
|
600
|
+
rescue => error
|
601
|
+
@log.warn { "restore_attributes: utime #{dir} got error and didn't restore times': #{error.message}\n" }
|
602
|
+
end
|
603
|
+
end
|
604
|
+
false
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
def copy_preserve( from, to )
|
609
|
+
begin
|
610
|
+
if not File.syscopy2( from, to ) # Errno::ENOSPC se siri ven
|
611
|
+
File.unlink( to )
|
612
|
+
raise SysCopyFailure.new( "syscopy2 returned false when copying #{from} --> #{to}" )
|
613
|
+
end
|
614
|
+
rescue Errno::EIO => e # tak tohle znamena poradnej pruser....
|
615
|
+
msg = "i/o error copying #{from} : #{e.message}"
|
616
|
+
@log.error { msg }
|
617
|
+
raise SysCopyFailure( msg )
|
618
|
+
rescue Errno::ETXTBSY => e # (windows) maji zamklej soubor?
|
619
|
+
msg = "text file busy #{from} : #{e.message}"
|
620
|
+
@log.error { msg }
|
621
|
+
raise SysCopyFailure.new( msg )
|
622
|
+
end
|
623
|
+
|
624
|
+
st = nil
|
625
|
+
begin
|
626
|
+
st = File.stat( from )
|
627
|
+
rescue => error
|
628
|
+
@log.warn { "copy_preserve: File.stat #{from} got error and failed: #{error.message}\n" }
|
629
|
+
@log.warn { "copy_preserve: NOT restoring owner, rights, times and ACLs on #{to}\n" }
|
630
|
+
end
|
631
|
+
|
632
|
+
if st
|
633
|
+
begin
|
634
|
+
File.chown( st.uid, st.gid, to )
|
635
|
+
rescue => error
|
636
|
+
@log.warn { "copy_preserve: chown #{to} got error and didn't restore owner: #{error.message}\n" }
|
637
|
+
end
|
638
|
+
begin
|
639
|
+
File.chmod( st.mode, to )
|
640
|
+
rescue => error
|
641
|
+
@log.warn { "copy_preserve: chmod #{to} got error and didn't restore rights: #{error.message}\n" }
|
642
|
+
end
|
643
|
+
if $HAVE_ACL and not @no_acl
|
644
|
+
begin
|
645
|
+
acl = get_access_acl( from )
|
646
|
+
acl.set_file( to ) if acl
|
647
|
+
rescue => error
|
648
|
+
@log.warn { "copy_preserve: setfacl #{to} got error and didn't restore ACLs: #{error.message}\n" }
|
649
|
+
end
|
650
|
+
end
|
651
|
+
begin
|
652
|
+
File.utime( st.atime, st.mtime, to )
|
653
|
+
rescue => error
|
654
|
+
@log.warn { "copy_preserve: utime #{to} got error and didn't restore times: #{error.message}\n" }
|
655
|
+
end
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
def same_file?(f1, f2)
|
660
|
+
begin
|
661
|
+
result = ( !File.symlink?(f1) and
|
662
|
+
!File.symlink?(f2) and
|
663
|
+
File.file?(f1) and
|
664
|
+
File.file?(f2) and
|
665
|
+
(s1 = File.stat(f1)).size == (s2 = File.stat(f2)).size and
|
666
|
+
( (s1.mtime - s2.mtime) <= 1 ) and
|
667
|
+
s1.uid == s2.uid and
|
668
|
+
s1.gid == s2.gid and
|
669
|
+
s1.mode == s2.mode )
|
670
|
+
|
671
|
+
return result unless result
|
672
|
+
|
673
|
+
if $HAVE_ACL and not @no_acl
|
674
|
+
return false unless ACL.from_file(f1).to_text == ACL.from_file(f2).to_text
|
675
|
+
# default ACL neresime, protoze tady mame jen soubory
|
676
|
+
#return false unless ACL.default(f1).to_text == ACL.default(f2).to_text
|
677
|
+
end
|
678
|
+
return true
|
679
|
+
rescue => error
|
680
|
+
@log.warn { "same_file?(#{f1}, #{f2}) got error and returned false: #{error.message}\n" }
|
681
|
+
return false
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
#### rizeni chodu Find ####
|
686
|
+
|
687
|
+
# hledame 1. match a podle neho se ridime, cili pod klicem :exclude ZALEZI NA PORADI
|
688
|
+
# a na predni mista davame konkretnejsi pravidla a az na ne pravidla obecnejsi
|
689
|
+
def find_exclude?( path )
|
690
|
+
return if @skip_excludes
|
691
|
+
# spocteme si relativni cestu
|
692
|
+
rel_path = path.dup
|
693
|
+
if rel_path.index( @exclude_root ) == 0
|
694
|
+
if @exclude_root == '/'
|
695
|
+
rel_path[0,1] = ''
|
696
|
+
else
|
697
|
+
rel_path[0,@exclude_root.length+1] = '' # odmazeme vcetne lomitka
|
698
|
+
end
|
699
|
+
else
|
700
|
+
rel_path = nil # muzeme nastavit nil, protoze nil =~ /cokoliv/ je false
|
701
|
+
end
|
702
|
+
# matchujeme
|
703
|
+
@exclude_list.each do |a|
|
704
|
+
if (a[2] ? path : rel_path) =~ a[1]
|
705
|
+
if a[0]
|
706
|
+
@log.debug { "excluding(#{a[1].source}): #{path}" }
|
707
|
+
Find.prune
|
708
|
+
else
|
709
|
+
return
|
710
|
+
end
|
711
|
+
end
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
# absolute=>true bude se matchovat absolutni (cela cesta)
|
716
|
+
# absolute=>false bude se matchovat cesta relativni ke klici :dir
|
717
|
+
def set_exclude(str, absolute=false)
|
718
|
+
if str == '!'
|
719
|
+
@exclude_list = []
|
720
|
+
return
|
721
|
+
end
|
722
|
+
exclude_pattern = true
|
723
|
+
if str =~ /^\+\s*(.*)$/
|
724
|
+
exclude_pattern = false
|
725
|
+
str = $1
|
726
|
+
elsif str =~ /^-\s*(.*)$/
|
727
|
+
exclude_pattern = true
|
728
|
+
str = $1
|
729
|
+
end
|
730
|
+
@exclude_list << [exclude_pattern, Regexp.new('^'+str), absolute]
|
731
|
+
end
|
732
|
+
|
733
|
+
def umount_fsck_mount
|
734
|
+
if umount_backup
|
735
|
+
check_fsck or return print_error_stats( FSCK_FAILED )
|
736
|
+
end
|
737
|
+
mount_backup or return print_error_stats( MOUNT_FAILED )
|
738
|
+
return 0
|
739
|
+
end
|
740
|
+
|
741
|
+
#### zalohovani se vsim vsudy ####
|
742
|
+
def go_backup( name, no_mirror )
|
743
|
+
if ((res = config_init(name)) != 0) || ((res = umount_fsck_mount) != 0)
|
744
|
+
return res
|
745
|
+
end
|
746
|
+
|
747
|
+
res = run_backup
|
748
|
+
if (res == BACKUP_OK)
|
749
|
+
# pokud probehla v poradku 1. faze muzeme pokracovat mirrorem
|
750
|
+
if not no_mirror
|
751
|
+
mirror_res = create_mirror
|
752
|
+
res = mirror_res unless mirror_res == nil
|
753
|
+
end
|
754
|
+
else
|
755
|
+
print_error_stats( res )
|
756
|
+
end
|
757
|
+
|
758
|
+
umount_backup(true)
|
759
|
+
return res
|
760
|
+
end
|
761
|
+
|
762
|
+
def detect_running(lock_file_name)
|
763
|
+
if FileTest.exists?(lock_file_name)
|
764
|
+
@log.debug { "detect_running: lock file #{lock_file_name} exists" }
|
765
|
+
pid = File.open(lock_file_name).read(nil).chomp.to_i rescue nil
|
766
|
+
if pid == nil
|
767
|
+
@log.error { "detect_running: invalid PID in #{lock_file_name}" }
|
768
|
+
@log.warn { "detect_running: assuming lnbackup not running" }
|
769
|
+
return false
|
770
|
+
end
|
771
|
+
if FileTest.exists?(cmd_file="/proc/#{pid}/cmdline")
|
772
|
+
cmd_line = File.open(cmd_file).read(nil) rescue ''
|
773
|
+
@log.debug { "detect_running: process ##{pid}' command line: #{cmd_line}" }
|
774
|
+
if (cmd_line =~ /\/lnbackup/)
|
775
|
+
@log.error { "lnbackup already running, pid: #{pid}, command line: #{cmd_line}" }
|
776
|
+
return true
|
777
|
+
else
|
778
|
+
@log.warn { "lnbackup probably not running lock_file_name: #{lock_file_name}" }
|
779
|
+
@log.warn { "\t\tpid: #{pid}, command line: #{cmd_line}" }
|
780
|
+
return false
|
781
|
+
end
|
782
|
+
else
|
783
|
+
@log.debug { "detect_running: #{lock_file_name} does not exist" }
|
784
|
+
return false
|
785
|
+
end
|
786
|
+
else
|
787
|
+
return false
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
# Hledame v predchozich zalohach soubor jmena podle tmp_src.
|
792
|
+
# Divame, jestli nalezeny souboj je "stejny" jako ten, ktery
|
793
|
+
# mame zalohovat.
|
794
|
+
# Pokud ano, vratime ho, jinak vratime nil.
|
795
|
+
# Hledani zarazime, pokud najdeme soubor na stejne ceste,
|
796
|
+
# ktery neni "stejny"
|
797
|
+
#
|
798
|
+
# TODO: hledani by se melo take zastavit, kdyz se podivame do posledni
|
799
|
+
# dokoncene zalohy -- na to potrebujeme strojive zpracovatelny status
|
800
|
+
# file pro predchozi zalohy --> ukol k reseni
|
801
|
+
def find_same_prev_backup( src, tmp_src, all_backups )
|
802
|
+
max_iter = @max_iter
|
803
|
+
all_backups.reverse.each do |prev_backup|
|
804
|
+
last = File.expand_path(tmp_src, prev_backup)
|
805
|
+
return last if same_file?(src,last)
|
806
|
+
return false if FileTest.exists?(last)
|
807
|
+
|
808
|
+
max_iter -= 1
|
809
|
+
return false if max_iter == 0
|
810
|
+
end
|
811
|
+
return nil
|
812
|
+
end
|
813
|
+
|
814
|
+
def get_access_acl(file) ($HAVE_ACL and not @no_acl) ? (ACL::from_file(file) rescue nil) : nil; end
|
815
|
+
def get_default_acl(file) ($HAVE_ACL and not @no_acl) ? (ACL::default(file) rescue nil) : nil; end
|
816
|
+
|
817
|
+
#### hlavni zalohovaci rutina ####
|
818
|
+
def run_backup
|
819
|
+
begin
|
820
|
+
@log.info { "running backup #{@backup_name}" }
|
821
|
+
|
822
|
+
@skip_excludes = false
|
823
|
+
lock_file = nil
|
824
|
+
pre_command_ok = true
|
825
|
+
|
826
|
+
@backup_start = Time.now
|
827
|
+
|
828
|
+
@stats = {
|
829
|
+
:size => 0, :total_size => 0, :blocks => 0,
|
830
|
+
:f_same => 0, :f_changed => 0, :f_new => 0,
|
831
|
+
:file => 0, :dir => 0, :symlink => 0, :blockdev => 0, :chardev => 0, :socket => 0, :pipe => 0,
|
832
|
+
}
|
833
|
+
|
834
|
+
# POZOR: behovy zamek delame az PO vykonani pre-command
|
835
|
+
|
836
|
+
# podivame se po pripadnem prikazu, ktery bychom meli udelat pred zalohovanim
|
837
|
+
if @cur_config.key?(:pre_command)
|
838
|
+
ret, out, err = system_catch_stdin_stderr(@cur_config[:pre_command])
|
839
|
+
if ret != 0
|
840
|
+
@log.fatal { "pre command failed: '#{@cur_config[:pre_command]}" }
|
841
|
+
@log.fatal { "\texit code: '#{ret}'" }
|
842
|
+
@log.fatal { "\tstdout: #{out}" }
|
843
|
+
@log.fatal { "\tstderr: #{err}" }
|
844
|
+
pre_command_ok = false
|
845
|
+
return PRE_COMMAND_FAILED
|
846
|
+
end
|
847
|
+
end
|
848
|
+
|
849
|
+
# cilovy adresar pro zalohy je budto gobalni mount_point nebo target_dir
|
850
|
+
# vybrane zalohy + datum + (hodina) + nazev zalohy
|
851
|
+
pre_dest_tmp = @cur_config.key?(:target_dir) ? @cur_config[:target_dir] : conf_val(:mount_point)
|
852
|
+
if not File.directory?(pre_dest_tmp)
|
853
|
+
@log.fatal { "backup root '#{pre_dest_tmp}' does not exist" }
|
854
|
+
return NO_BACKUP_DIR
|
855
|
+
end
|
856
|
+
begin
|
857
|
+
lock_file_name = File.join(pre_dest_tmp,'LNBACKUP_RUNNING')
|
858
|
+
return LNBACKUP_RUNNING if detect_running(lock_file_name)
|
859
|
+
lock_file = File.open( lock_file_name, 'w' )
|
860
|
+
lock_file.puts($$)
|
861
|
+
lock_file.flush
|
862
|
+
rescue => e
|
863
|
+
return PIDFILE_FAILED
|
864
|
+
end
|
865
|
+
|
866
|
+
# udaj o rezervovanem miste bereme prednosti z konkretni zalohy,
|
867
|
+
# pokud neni nakonfigurovan, tak globalne
|
868
|
+
files_reserved = conf_val(:files_reserved)
|
869
|
+
blocks_reserved = conf_val(:blocks_reserved)
|
870
|
+
@space_calc = FreeSpaceCalc.new( pre_dest_tmp, files_reserved, blocks_reserved, @log )
|
871
|
+
|
872
|
+
dest_tmp = File.join( pre_dest_tmp, 'backup').squeeze('/')
|
873
|
+
@log.debug { "dest_tmp: #{dest_tmp}" }
|
874
|
+
|
875
|
+
# nemazeme posledni backup
|
876
|
+
@dont_delete = []
|
877
|
+
prev_backup = ( all_backups = find_backups )[-1]
|
878
|
+
if prev_backup
|
879
|
+
@dont_delete << prev_backup.gsub(/\/[^\/]*$/,'')
|
880
|
+
end
|
881
|
+
|
882
|
+
@dest = File.join( dest_tmp,
|
883
|
+
Date.today.backup_dir(@cur_config[:hourly]),
|
884
|
+
@target_dir_name ).squeeze('/')
|
885
|
+
# a nemazeme ani to, co zrovna zalohujeme
|
886
|
+
@dont_delete << @dest.gsub(/\/[^\/]*$/,'')
|
887
|
+
|
888
|
+
if File.directory?(@dest)
|
889
|
+
@log.fatal { "today's/this hour's backup (#{@dest}) already exists --> exiting" }
|
890
|
+
return BACKUP_EXISTS
|
891
|
+
else
|
892
|
+
begin
|
893
|
+
begin
|
894
|
+
FileUtils.mkpath(@dest) unless @test_mode
|
895
|
+
rescue Errno::ENOSPC
|
896
|
+
make_some_space and retry
|
897
|
+
end
|
898
|
+
rescue => e
|
899
|
+
@log.fatal { "can't make backup dir '#{@dest}'" }
|
900
|
+
@log.fatal { "mkpath #{@dest} raised exception #{e.class}:'#{e.message}'" }
|
901
|
+
@log.fatal { e.backtrace.join("\n") }
|
902
|
+
return NO_BACKUP_DIR
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
@log.debug { "previous backup: #{prev_backup}" }
|
907
|
+
@log.debug { "dont_delete: #{@dont_delete.join(',')}" }
|
908
|
+
|
909
|
+
# adresare je nutno vyrobit, nez do nich nasypeme soubory,
|
910
|
+
# ale nastavit jejich atributy musime az po souborech,
|
911
|
+
dirs_to_restore = []
|
912
|
+
|
913
|
+
dirs = Array.new
|
914
|
+
dirs_hash = Hash.new
|
915
|
+
@cur_config[:dirs].each do |dir|
|
916
|
+
tmp_dir = @source_prefix ? File.join( @source_prefix, dir[:dir] ) : dir[:dir]
|
917
|
+
dirs << [ rp = realpath(tmp_dir).gsub(/(.)\/+$/,'\1') , dir[:fs_type], dir[:exclude] ]
|
918
|
+
dirs_hash[rp] = true
|
919
|
+
end
|
920
|
+
|
921
|
+
dirs.each do |path,fs_type,exclude|
|
922
|
+
path = File.expand_path(path)
|
923
|
+
|
924
|
+
# inicializace excludes
|
925
|
+
@exclude_root = path
|
926
|
+
@exclude_list = []
|
927
|
+
exclude.each { |ex| set_exclude(ex) } if exclude
|
928
|
+
@exclude_list.unshift( [true, /^#{Regexp.escape(dest_tmp)}\/./, true] )
|
929
|
+
@exclude_list.unshift( [true, /^#{Regexp.escape(lock_file_name)}$/, true] )
|
930
|
+
@log.debug { "Excluding: #{@exclude_list.to_s}" }
|
931
|
+
@log.debug { "exclude_root: #{@exclude_root}" }
|
932
|
+
|
933
|
+
# overime, ze zdroj existuje
|
934
|
+
begin
|
935
|
+
File.stat(path)
|
936
|
+
rescue Errno::ENOENT
|
937
|
+
@log.error { "path #{path} not found --> skipping from backup" }
|
938
|
+
next
|
939
|
+
rescue Errno::EACCES
|
940
|
+
@log.error { "path #{path} no rights to access file --> skipping from backup" }
|
941
|
+
next
|
942
|
+
rescue => error
|
943
|
+
@log.error { "path #{path} : File.stat got error #{error.class} : #{error.message} --> skipping from backup" }
|
944
|
+
next
|
945
|
+
end
|
946
|
+
|
947
|
+
# projit cestu, udelat linky a adresare
|
948
|
+
@log.debug { "resolving links for #{path}" }
|
949
|
+
if path != '/'
|
950
|
+
file = path
|
951
|
+
|
952
|
+
total = ''
|
953
|
+
file.split(File::SEPARATOR).each do |piece|
|
954
|
+
next if piece == ""
|
955
|
+
total << File::SEPARATOR + piece
|
956
|
+
if File.symlink?(total) then
|
957
|
+
begin
|
958
|
+
# dokud se jedna o symlink, tak musime linkovat
|
959
|
+
do_cp_a(total, File.join(@dest,total))
|
960
|
+
|
961
|
+
total = File.expand_path( File.readlink(total), File.dirname(total) )
|
962
|
+
end while File.symlink?(total)
|
963
|
+
# a nakonec udelame adresar
|
964
|
+
else
|
965
|
+
# pokud se nejedna o symlink, udelame adresar
|
966
|
+
tgt = File.join(@dest,total).squeeze('/')
|
967
|
+
do_cp_dir( total, tgt )
|
968
|
+
# ulozime si ho do seznamu pro pozdejsi nastaveni vlastnosti
|
969
|
+
dirs_to_restore << [ tgt, File.stat(total), get_access_acl(total), get_default_acl(total), 0 ]
|
970
|
+
end
|
971
|
+
end
|
972
|
+
path = total
|
973
|
+
end
|
974
|
+
|
975
|
+
# overime, ze existuje i cesta, kam nas zavedly odkazy a
|
976
|
+
# ulozime si device pro budouci porovnavani
|
977
|
+
dev = nil
|
978
|
+
begin
|
979
|
+
dev = File.stat(path).dev
|
980
|
+
rescue Errno::ENOENT
|
981
|
+
@log.error { "path #{path} not found --> skipping from backup" }
|
982
|
+
next
|
983
|
+
end
|
984
|
+
|
985
|
+
# overime si, ze mame tento adresar zalohovat (napr. home pres nfs)
|
986
|
+
@log.debug { "checking fs_type for #{path}" }
|
987
|
+
if (fs_type == :local) and (dev <= 700) # HRUBY ODHAD --> TODO: poradne
|
988
|
+
@log.info { "#{path} is not local (dev=#{dev}), skipping" }
|
989
|
+
next
|
990
|
+
end
|
991
|
+
|
992
|
+
@log.debug { "running on directory #{path}" }
|
993
|
+
last_depth = 0
|
994
|
+
Find.find3( path ) do |src,depth|
|
995
|
+
if @delay_denom
|
996
|
+
if @delay and (rand(@delay_denom) == 0)
|
997
|
+
sleep(@delay)
|
998
|
+
end
|
999
|
+
elsif @delay
|
1000
|
+
sleep(@delay)
|
1001
|
+
end
|
1002
|
+
dirs_to_restore = restore_dir_attributes(dirs_to_restore, depth+1) if last_depth>depth
|
1003
|
+
last_depth = depth
|
1004
|
+
|
1005
|
+
@last_file = src
|
1006
|
+
src = File.expand_path('./'+src, '/')
|
1007
|
+
tmp_src = './' + src
|
1008
|
+
dest = File.expand_path(tmp_src, @dest)
|
1009
|
+
|
1010
|
+
begin
|
1011
|
+
file_stat = File.lstat(src)
|
1012
|
+
rescue => error
|
1013
|
+
@log.error { "can not stat #{src}: #{error.class}: #{error.message} --> skipping from backup" }
|
1014
|
+
Find.prune
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
# POZOR !!
|
1018
|
+
# nelze volat file_stat.readable?, protoze:
|
1019
|
+
# irb(main):016:0* File.lstat('/etc/asterisk/cdr_manager.conf').readable?
|
1020
|
+
# => false
|
1021
|
+
# irb(main):017:0>
|
1022
|
+
# irb(main):018:0*
|
1023
|
+
# irb(main):019:0* File.readable?('/etc/asterisk/cdr_manager.conf')
|
1024
|
+
# => true
|
1025
|
+
# irb(main):020:0> system('ls -l /etc/asterisk/cdr_manager.conf')
|
1026
|
+
# -rw-rw---- 1 asterisk asterisk 59 2005-12-08 00:58 /etc/asterisk/cdr_manager.conf
|
1027
|
+
# => true
|
1028
|
+
|
1029
|
+
# if not file_stat.readable? and not file_stat.symlink?
|
1030
|
+
if not File.readable?(src) and not file_stat.symlink?
|
1031
|
+
@log.error { "can not read #{src} --> skipping from backup" }
|
1032
|
+
Find.prune
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
if file_stat.symlink? or file_stat.blockdev? or file_stat.chardev? or
|
1036
|
+
file_stat.socket? or file_stat.pipe?
|
1037
|
+
# symlink, blockdev, chardev, socket, pipe zalohujeme pomoci 'cp -a'
|
1038
|
+
do_cp_a(src, dest)
|
1039
|
+
if file_stat.symlink?
|
1040
|
+
@stats[:symlink] += 1
|
1041
|
+
elsif file_stat.blockdev?
|
1042
|
+
@stats[:blockdev] += 1
|
1043
|
+
elsif file_stat.chardev?
|
1044
|
+
@stats[:chardev] += 1
|
1045
|
+
elsif file_stat.socket?
|
1046
|
+
@stats[:socket] += 1
|
1047
|
+
elsif file_stat.pipe?
|
1048
|
+
@stats[:pipe] += 1
|
1049
|
+
end
|
1050
|
+
elsif file_stat.directory?
|
1051
|
+
# adresar
|
1052
|
+
|
1053
|
+
# preskocime koren, protoze uz je vytvoren
|
1054
|
+
if src != path
|
1055
|
+
# preskocime v pripade vicenasobneho dosazeni stejneho adresare
|
1056
|
+
Find.prune if dirs_hash.key?(src)
|
1057
|
+
@stats[:dir] += 1
|
1058
|
+
|
1059
|
+
do_cp_dir(src, dest)
|
1060
|
+
# ulozime si ho do seznamu pro pozdejsi nastaveni vlastnosti
|
1061
|
+
dirs_to_restore << [dest, file_stat, get_access_acl(src), get_default_acl(src), depth]
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
# kontrola zastaveni noreni pri zmene device
|
1065
|
+
if dev != (new_dev = file_stat.dev)
|
1066
|
+
@log.debug { "filesystem border: #{src} old=#{dev}, new=#{new_dev}" }
|
1067
|
+
case fs_type
|
1068
|
+
when :local
|
1069
|
+
# pri zmene device otestujeme, ze je lokalni
|
1070
|
+
# konec pokud device neni lokalni
|
1071
|
+
if new_dev <= 700 # HRUBY ODHAD --> TODO: poradne
|
1072
|
+
@log.info { "#{src} is not local (dev=#{new_dev}), skipping" }
|
1073
|
+
Find.prune
|
1074
|
+
end
|
1075
|
+
# 0x300 ---> ide
|
1076
|
+
# 2304 ---> md0
|
1077
|
+
# 26625 ---> hwraid
|
1078
|
+
# 9 --> autofs
|
1079
|
+
# 7 --> pts
|
1080
|
+
# 2 --> proc
|
1081
|
+
# 0xa --> nfs
|
1082
|
+
when :single
|
1083
|
+
# single --> koncime, jakmile je podadresar z jineho device
|
1084
|
+
Find.prune
|
1085
|
+
when :all
|
1086
|
+
# all --> bereme vsechno
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
else # normalni soubor
|
1090
|
+
@stats[:file] += 1
|
1091
|
+
@stats[:total_size] += file_stat.size
|
1092
|
+
# overime, jestli mame drivejsi zalohu souboru a jestli nebyl zmenen
|
1093
|
+
backuped = false
|
1094
|
+
new = false
|
1095
|
+
last = nil
|
1096
|
+
if prev_backup
|
1097
|
+
# hledat file i v predchozich zalohach: viz. TODO OTESTOVAT!!
|
1098
|
+
#last = File.expand_path(tmp_src, prev_backup)
|
1099
|
+
#if same_file?(src,last)
|
1100
|
+
|
1101
|
+
# hledame v drivejsich zalohach ...
|
1102
|
+
last = find_same_prev_backup(src, tmp_src, all_backups)
|
1103
|
+
if last # last neni ani 'nil', ano 'false'
|
1104
|
+
do_hardlink(last, src, dest)
|
1105
|
+
backuped = true
|
1106
|
+
@stats[:f_same] += 1
|
1107
|
+
end
|
1108
|
+
end
|
1109
|
+
if not backuped
|
1110
|
+
# pokud jsme prosli az sem, budeme soubor kopirovat
|
1111
|
+
if do_copy(src,dest)
|
1112
|
+
if prev_backup
|
1113
|
+
if last.nil? # nil --> vubec jsme nenasli
|
1114
|
+
@stats[:f_new] += 1
|
1115
|
+
@log.debug { "new file: #{src}" }
|
1116
|
+
else # false --> nasli jsme, ale byl zmenen
|
1117
|
+
@stats[:f_changed] += 1
|
1118
|
+
@log.debug { "changed file: #{src}" }
|
1119
|
+
end
|
1120
|
+
else
|
1121
|
+
# nehlasime 'new file' u 1. zaloh
|
1122
|
+
@stats[:f_new] += 1
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
if @test_mode
|
1126
|
+
# v test modu odhadujeme velikost podle zdroje
|
1127
|
+
@stats[:size] += file_stat.size
|
1128
|
+
@stats[:blocks] += file_stat.blocks
|
1129
|
+
else
|
1130
|
+
@stats[:size] += File.size(dest)
|
1131
|
+
@stats[:blocks] += File.stat(dest).blocks
|
1132
|
+
end
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
end
|
1137
|
+
end
|
1138
|
+
restore_dir_attributes(dirs_to_restore)
|
1139
|
+
|
1140
|
+
over_all_status = BACKUP_OK
|
1141
|
+
|
1142
|
+
@log.info { "Backup '#{@target_dir_name}' complete succesfully." }
|
1143
|
+
print_stats( over_all_status )
|
1144
|
+
rescue MakeSpaceFailure => e
|
1145
|
+
return e.code # in this case we return the error code given by exception
|
1146
|
+
# ( possibly ignoring status code from post_command )
|
1147
|
+
ensure
|
1148
|
+
if lock_file
|
1149
|
+
begin
|
1150
|
+
lock_file.close
|
1151
|
+
ensure
|
1152
|
+
File.delete( lock_file_name )
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
if pre_command_ok and @cur_config.key?(:post_command)
|
1157
|
+
ret, out, err = system_catch_stdin_stderr(@cur_config[:post_command])
|
1158
|
+
if ret != 0
|
1159
|
+
@log.error { "post command failed: '#{@cur_config[:post_command]}" }
|
1160
|
+
@log.error { "\texit code: '#{ret}'" }
|
1161
|
+
@log.error { "\tstdout: #{out}" }
|
1162
|
+
@log.error { "\tstderr: #{err}" }
|
1163
|
+
over_all_status = POST_COMMAND_FAILED
|
1164
|
+
end
|
1165
|
+
end
|
1166
|
+
end
|
1167
|
+
return over_all_status
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
def print_stats( code )
|
1171
|
+
sum = 0
|
1172
|
+
[:file, :dir, :symlink, :blockdev, :chardev, :socket, :pipe].each { |s| sum += @stats[s] }
|
1173
|
+
|
1174
|
+
status = []
|
1175
|
+
status << "lnbackup statistics for: #{@target_dir_name}"
|
1176
|
+
status << "backup config: #{@backup_name}"
|
1177
|
+
status << "overall status code: #{code}"
|
1178
|
+
status << "overall status message: #{MESSAGES[code]}"
|
1179
|
+
status << "running in: TEST MODE" if @test_mode
|
1180
|
+
status << ""
|
1181
|
+
status << "backup start: #{@backup_start}"
|
1182
|
+
status << "backup end: #{Time.now}"
|
1183
|
+
status << "total size: #{@stats[:total_size]}"
|
1184
|
+
status << "backup size: #{@stats[:size]}"
|
1185
|
+
status << "backup blocks: #{@stats[:blocks]}"
|
1186
|
+
status << ""
|
1187
|
+
status << "files unmodified: #{@stats[:f_same]}"
|
1188
|
+
status << "files changed: #{@stats[:f_changed]}"
|
1189
|
+
status << "files new: #{@stats[:f_new]}"
|
1190
|
+
status << ""
|
1191
|
+
status << "total objects: #{sum}"
|
1192
|
+
status << "files: #{@stats[:file]}"
|
1193
|
+
status << "dirs: #{@stats[:dir]}"
|
1194
|
+
status << "symlinks: #{@stats[:symlink]}"
|
1195
|
+
status << "blockdevs: #{@stats[:blockdev]}"
|
1196
|
+
status << "chardevs: #{@stats[:chardev]}"
|
1197
|
+
status << "sockets: #{@stats[:socket]}"
|
1198
|
+
status << "pipes: #{@stats[:pipe]}"
|
1199
|
+
print_status_array(status)
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
def print_error_stats(err)
|
1203
|
+
status = []
|
1204
|
+
status << "error code: #{err}"
|
1205
|
+
status << "message: #{MESSAGES[err]}"
|
1206
|
+
print_status_array(status)
|
1207
|
+
return err
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
# prints status information to log file, stdout and to status file
|
1211
|
+
def print_status_array( status, mode='w' )
|
1212
|
+
@log.info { "Status information follows" }
|
1213
|
+
status_hash = {}
|
1214
|
+
status.each do |line|
|
1215
|
+
puts line
|
1216
|
+
@log << line + "\n"
|
1217
|
+
|
1218
|
+
key, value = line.chomp.split(':',2).collect{ |a| a.strip }
|
1219
|
+
status_hash[key] = value
|
1220
|
+
end
|
1221
|
+
status_hash['uuid'] = @part_uuid
|
1222
|
+
status_hash['label'] = @part_label
|
1223
|
+
begin
|
1224
|
+
File.open(@status_file,mode) do |f|
|
1225
|
+
status.each { |line| f.puts line }
|
1226
|
+
end
|
1227
|
+
rescue => error
|
1228
|
+
@log.error { "error writing status file #{@status}: #{error.message}" }
|
1229
|
+
end
|
1230
|
+
# binary status file
|
1231
|
+
begin
|
1232
|
+
# read the status array
|
1233
|
+
status_array = Marshal.restore( File.open(@status_file+'.bin').read(nil) ) rescue []
|
1234
|
+
|
1235
|
+
# adding to last status information -- take the last status info, modify it and write back
|
1236
|
+
if mode == 'a'
|
1237
|
+
status_array[0] ||= {}
|
1238
|
+
status_array[0].update( status_hash )
|
1239
|
+
|
1240
|
+
# writing new status information -- add new element to the beginning
|
1241
|
+
else
|
1242
|
+
status_array.unshift( status_hash )
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
# write the status array
|
1246
|
+
File.open(@status_file+'.bin','w') { |f| f << Marshal.dump( status_array ) }
|
1247
|
+
rescue => error
|
1248
|
+
@log.error { "error writing binary status file #{@status}: #{error.message}" }
|
1249
|
+
end
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
def parse_stats
|
1253
|
+
stats = {}
|
1254
|
+
File.open(@status_file,'r').readlines.each do |line|
|
1255
|
+
key, value = line.chomp.split(':',2).collect{ |a| a.strip }
|
1256
|
+
stats[key] = value
|
1257
|
+
end
|
1258
|
+
return stats
|
1259
|
+
end
|
1260
|
+
|
1261
|
+
def size2str (size)
|
1262
|
+
size = size.to_i
|
1263
|
+
if size < 1024
|
1264
|
+
"#{size} "
|
1265
|
+
elsif size < 1048576
|
1266
|
+
"%.1fK" % (size / 1024.0)
|
1267
|
+
elsif size < 1073741824
|
1268
|
+
"%.1fM" % (size / 1048576.0)
|
1269
|
+
else
|
1270
|
+
"%.1fG" % (size / 1073741824.0)
|
1271
|
+
end
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
def backup_partitions
|
1275
|
+
devices = Dir[ @config[:device] ]
|
1276
|
+
backups = @config.keys.find_all {|k| Hash===@config[k] and @config[k].key?(:dirs) and not @config[k][:dont_check] }
|
1277
|
+
out_backup_devices = []
|
1278
|
+
devs = {}
|
1279
|
+
for backup in backups
|
1280
|
+
devs[backup] = devices
|
1281
|
+
if @config[backup][:device]
|
1282
|
+
devs[backup] = Dir[ @config[backup][:device] ]
|
1283
|
+
end
|
1284
|
+
if @config[backup][:device_label]
|
1285
|
+
devs[backup] = [ find_dev_by(devs[backup], @config[backup][:device_label], :label) ]
|
1286
|
+
end
|
1287
|
+
if @config[backup][:device_uuid]
|
1288
|
+
devs[backup] = [ find_dev_by(devs[backup], @config[backup][:device_uuid], :uuid) ]
|
1289
|
+
end
|
1290
|
+
out_backup_devices |= devs[backup]
|
1291
|
+
end
|
1292
|
+
return out_backup_devices
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
def backup_partition?(partition)
|
1296
|
+
return backup_partitions.include?(partition)
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
def nagios_check_all
|
1300
|
+
#backups = @config[:check_backups] || [:localhost]
|
1301
|
+
total_message = ''
|
1302
|
+
total_message_bad = ''
|
1303
|
+
worst_result = 0
|
1304
|
+
for backup in @config.keys.find_all {|k| Hash===@config[k] and @config[k].key?(:dirs) and not @config[k][:dont_check] }
|
1305
|
+
result, message = nagios_check( backup.to_s )
|
1306
|
+
total_message << '[' << message << '] '
|
1307
|
+
total_message_bad << '[' << message << '] ' if result != 0
|
1308
|
+
worst_result = result > worst_result ? result : worst_result
|
1309
|
+
end
|
1310
|
+
total_message = total_message_bad if worst_result != 0
|
1311
|
+
return [worst_result, total_message]
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
def nagios_check( backup_name = 'localhost' )
|
1315
|
+
@status_file = @status_file_pref + '-' + backup_name
|
1316
|
+
conf_key = backup_name.intern
|
1317
|
+
|
1318
|
+
if not @config.key?(conf_key)
|
1319
|
+
return [ 2, "backup '#{backup_name}' not configured" ]
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
no_mirror_warn = false
|
1323
|
+
warn_t = 26 # implicitni hodnoty pro warning a error (v hodinach)
|
1324
|
+
error_t = 30
|
1325
|
+
if @config[conf_key].key?(:monitor)
|
1326
|
+
warn_t = @config[conf_key][:monitor][:warn] || warn_t
|
1327
|
+
error_t = @config[conf_key][:monitor][:error] || error_t
|
1328
|
+
no_mirror_warn = @config[conf_key][:monitor][:no_mirror_warn] || false
|
1329
|
+
end
|
1330
|
+
|
1331
|
+
stats = {}
|
1332
|
+
message = ''
|
1333
|
+
status = 0
|
1334
|
+
|
1335
|
+
if File.readable?(@status_file)
|
1336
|
+
stats = parse_stats
|
1337
|
+
else
|
1338
|
+
return [ 2, "backup status file #{@status_file} not found" ]
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
if stats.key?('backup end')
|
1342
|
+
message = stats['lnbackup statistics for'] + ': '
|
1343
|
+
|
1344
|
+
delta = ((Time.now() - Time.parse( stats['backup end'] ))/3600).to_i
|
1345
|
+
if delta > error_t
|
1346
|
+
status = 2
|
1347
|
+
message << "backup age: #{delta}h > #{error_t} ->ERR"
|
1348
|
+
elsif delta > warn_t
|
1349
|
+
status = 1
|
1350
|
+
message << "backup age: #{delta}h > #{warn_t} ->WARN"
|
1351
|
+
else
|
1352
|
+
if stats.key?('mirror end')
|
1353
|
+
message << (Time.parse( stats['mirror end'] ).strftime( '%H:%M:%S %d/%m/%y' ) rescue 'parse error')
|
1354
|
+
message << ' : ' << size2str(stats['backup size'].to_i) << '/' << size2str(stats['total size']) << 'B '
|
1355
|
+
message << '(bootable)'
|
1356
|
+
else
|
1357
|
+
message << stats['backup end']
|
1358
|
+
message << ' : ' << size2str(stats['backup size'].to_i) << '/' << size2str(stats['total size']) << 'B '
|
1359
|
+
if @config[backup_name.intern][:mirror]
|
1360
|
+
if no_mirror_warn
|
1361
|
+
message << '(not bootable ->warning canceled by config)'
|
1362
|
+
else
|
1363
|
+
message << '(not bootable ->WARN)'
|
1364
|
+
status = 1 if status < 1
|
1365
|
+
end
|
1366
|
+
end
|
1367
|
+
end
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
else
|
1371
|
+
message = backup_name.to_s + ' : '
|
1372
|
+
if stats.key?("error code")
|
1373
|
+
status = (s = stats['error code'].to_i) > status ? s : status
|
1374
|
+
message << stats['message']
|
1375
|
+
else
|
1376
|
+
message << "Unknown error"
|
1377
|
+
stats = 2
|
1378
|
+
end
|
1379
|
+
end
|
1380
|
+
|
1381
|
+
return [ status<2 ? status : 2, message ]
|
1382
|
+
rescue
|
1383
|
+
raise
|
1384
|
+
return [ 2, "can't parse status file #{@status_file}" ]
|
1385
|
+
end
|
1386
|
+
|
1387
|
+
def do_mirror_only(name)
|
1388
|
+
if (res = config_init(name)) != 0
|
1389
|
+
return res
|
1390
|
+
end
|
1391
|
+
|
1392
|
+
@status_file = '/dev/null'
|
1393
|
+
@dest = find_backups[-1]
|
1394
|
+
create_mirror
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
#### vytvoreni mirroru
|
1398
|
+
# pousti se po run_backup, takze muze pocitat s pripravenym prostredim...
|
1399
|
+
def create_mirror
|
1400
|
+
# pokud nema vybrana konfigurace v konfiguraci povoleno mirrorovani,
|
1401
|
+
# vratime nil
|
1402
|
+
return nil unless @cur_config[:mirror] == true
|
1403
|
+
|
1404
|
+
# pokud nemame device, nebo mount_point, nebo jsou prazdne, tak to rozhodne zabalime
|
1405
|
+
return nil if conf_val(:device).to_s.empty? or conf_val(:mount_point).to_s.empty?
|
1406
|
+
|
1407
|
+
# taky to zabalime, jestlize mame zadano crypto (neumime bootovat z sifrovane zalohy)
|
1408
|
+
return nil if @config.key?(:crypto)
|
1409
|
+
|
1410
|
+
mnt = conf_val(:mount_point)
|
1411
|
+
part = conf_val(:device)
|
1412
|
+
disk = conf_val(:disk)
|
1413
|
+
|
1414
|
+
lock_file_name = File.join(mnt,'LNBACKUP_RUNNING')
|
1415
|
+
return LNBACKUP_RUNNING if detect_running(lock_file_name)
|
1416
|
+
lock_file = File.open( lock_file_name, 'w' )
|
1417
|
+
lock_file.puts($$)
|
1418
|
+
lock_file.flush
|
1419
|
+
|
1420
|
+
@log.info { "creating bootable mirror #{@backup_name} at #{part} mounted as #{mnt}" }
|
1421
|
+
|
1422
|
+
# smazat stary mirror
|
1423
|
+
command = [ 'find', "#{mnt}", '-xdev',
|
1424
|
+
'-maxdepth', '1', '-mindepth', '1',
|
1425
|
+
'!', '-name', 'backup',
|
1426
|
+
'!', '-name', 'LNBACKUP_RUNNING',
|
1427
|
+
'!', '-name', 'lost+found',
|
1428
|
+
'-exec', 'rm', '-fr', '{}', ';' ]
|
1429
|
+
@log.debug { 'running: ' + command.join(' ') }
|
1430
|
+
system( *command ) unless @test_mode
|
1431
|
+
|
1432
|
+
@exclude_list = []
|
1433
|
+
@skip_excludes = true
|
1434
|
+
|
1435
|
+
@dont_delete = [@dest]
|
1436
|
+
latest_mirror = @dest
|
1437
|
+
latest_mirror_len = latest_mirror.size
|
1438
|
+
target_dir = conf_val(:mount_point)
|
1439
|
+
|
1440
|
+
# vytvorit novy mirror
|
1441
|
+
dirs_to_restore = []
|
1442
|
+
last_depth = 0
|
1443
|
+
Find.find3(latest_mirror + '/') do |src,depth|
|
1444
|
+
if @delay_denom
|
1445
|
+
if @delay and (rand(@delay_denom) == 0)
|
1446
|
+
sleep(@delay)
|
1447
|
+
end
|
1448
|
+
elsif @delay
|
1449
|
+
sleep(@delay)
|
1450
|
+
end
|
1451
|
+
dirs_to_restore = restore_dir_attributes(dirs_to_restore, depth+1) if last_depth>depth
|
1452
|
+
last_depth = depth
|
1453
|
+
|
1454
|
+
src = File.expand_path('./' + src, '/')
|
1455
|
+
tmp_dst = './' + src[latest_mirror_len..-1]
|
1456
|
+
dest = File.expand_path(tmp_dst, target_dir)
|
1457
|
+
|
1458
|
+
next if src==latest_mirror
|
1459
|
+
|
1460
|
+
begin
|
1461
|
+
if File.symlink?(src) or File.blockdev?(src) or File.chardev?(src) or File.socket?(src) or File.pipe?(src)
|
1462
|
+
# symlink, blockdev, chardev, socket, pipe zalohujeme pomoci 'cp -a'
|
1463
|
+
do_cp_a(src, dest)
|
1464
|
+
elsif File.directory?(src)
|
1465
|
+
# adresar
|
1466
|
+
# preskocime koren, protoze uz je vytvoren
|
1467
|
+
next if src == latest_mirror
|
1468
|
+
do_cp_dir(src, dest)
|
1469
|
+
# ulozime si ho do seznamu pro pozdejsi nastaveni vlastnosti
|
1470
|
+
dirs_to_restore << [ dest, File.stat(src), get_access_acl(src), get_default_acl(src), depth ] ### TODO: CHYBA?
|
1471
|
+
else # normalni soubor --> hard link
|
1472
|
+
do_hardlink(src, src, dest)
|
1473
|
+
end
|
1474
|
+
end
|
1475
|
+
end
|
1476
|
+
restore_dir_attributes(dirs_to_restore)
|
1477
|
+
|
1478
|
+
# vypnout lnbackup na zazalohovane kopii
|
1479
|
+
# mv "$MNT"/etc/cron.daily/lnbackup{,.disabled}
|
1480
|
+
# ucinit system bootovatelnym, pokud mame zadan i disk
|
1481
|
+
ret = true
|
1482
|
+
if disk
|
1483
|
+
if not @test_mode
|
1484
|
+
ret = create_fstab( mnt, part, disk )
|
1485
|
+
|
1486
|
+
if ret and not @cur_config[:skip_lilo]
|
1487
|
+
if FileTest.executable?(LILO_PATH)
|
1488
|
+
ret = create_lilo_conf( mnt, part, disk )
|
1489
|
+
else
|
1490
|
+
@log.warn { "LILO binary '#{LILO_PATH}' not found, skipping LILO" }
|
1491
|
+
end
|
1492
|
+
else
|
1493
|
+
@log.info { "skipping LILO as requested in config" }
|
1494
|
+
end
|
1495
|
+
end
|
1496
|
+
else
|
1497
|
+
@log.error { "'disk' not defined, skipping LILO and fstab ..." }
|
1498
|
+
ret = false
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
status = [""]
|
1502
|
+
if ret
|
1503
|
+
@log.info { "Mirror '#{@backup_name}' finished." }
|
1504
|
+
status << "mirror end: #{Time.now}"
|
1505
|
+
else
|
1506
|
+
@log.error { "Mirror '#{@backup_name}' failed." }
|
1507
|
+
status << "mirror failed: #{Time.now}"
|
1508
|
+
end
|
1509
|
+
|
1510
|
+
print_status_array(status,'a')
|
1511
|
+
return BACKUP_OK
|
1512
|
+
ensure
|
1513
|
+
if lock_file
|
1514
|
+
begin
|
1515
|
+
lock_file.close
|
1516
|
+
ensure
|
1517
|
+
File.delete( lock_file_name )
|
1518
|
+
end
|
1519
|
+
end
|
1520
|
+
end
|
1521
|
+
|
1522
|
+
#### generovani souboru
|
1523
|
+
# TODO: pokud je zaloha udelana pres label, generovat fstab na label
|
1524
|
+
# i swap lze nalezt pres label v /dev/disk/by-uuid/
|
1525
|
+
#
|
1526
|
+
# priklad /etc/fstab
|
1527
|
+
# UUID=ad71a12d-9cdf-460e-beab-969533237a36 none swap defaults 0 0
|
1528
|
+
# UUID=b76083c3-2871-4719-a670-26848043686d / ext3 defaults 0 0
|
1529
|
+
|
1530
|
+
def create_fstab( mnt, part, disk )
|
1531
|
+
swap_line = `/sbin/fdisk -l #{disk} | grep 'Linux swap'`.chomp
|
1532
|
+
swap_n = ( ( swap_line =~ /^#{disk}(\d+).*$/ ) ? $1 : nil ).to_s
|
1533
|
+
part_n = ( ( part =~ /^#{disk}(\d+)$/ ) ? $1 : nil ).to_s
|
1534
|
+
fstab = "#{mnt}/etc/fstab"
|
1535
|
+
|
1536
|
+
# check fstab
|
1537
|
+
if part_n == ''
|
1538
|
+
@log.error { "Cannot find correct part_n='#{part_n}'!" }
|
1539
|
+
@log.error { "The file '#{fstab}' was not generated." }
|
1540
|
+
@log.error { "Backup not bootable!" }
|
1541
|
+
return false
|
1542
|
+
end
|
1543
|
+
if swap_n == ''
|
1544
|
+
@log.warn { "Cannot find correct swap_n='#{swap_n}" }
|
1545
|
+
@log.warn { "The file '#{fstab}' was generated WITHOUT SWAP partition." }
|
1546
|
+
@log.warn { "Backuped system may have problems !" }
|
1547
|
+
end
|
1548
|
+
|
1549
|
+
ext3_options = 'noatime,defaults,errors=remount-ro'
|
1550
|
+
ext3_options << ',acl,user_xattr' if $HAVE_ACL and not @no_acl
|
1551
|
+
|
1552
|
+
# create fstab
|
1553
|
+
File.unlink(fstab)
|
1554
|
+
File.open(fstab,'w') do |f| # TODO: generate fstab based of filesystem LABELs (if possible, swap?)
|
1555
|
+
# otherwise we don't boot system from SATA/SCSI disks
|
1556
|
+
f.puts '#<file system> <mount point> <type> <options> <dump> <pass>'
|
1557
|
+
f.puts "/dev/hda#{part_n} / ext3 #{ext3_options} 0 1"
|
1558
|
+
f.puts "/dev/hda#{swap_n} none swap sw 0 0" if swap_n != ''
|
1559
|
+
f.puts "proc /proc proc defaults 0 0"
|
1560
|
+
end
|
1561
|
+
|
1562
|
+
return true
|
1563
|
+
end
|
1564
|
+
|
1565
|
+
def create_lilo_conf( mnt, part, disk )
|
1566
|
+
# run lilo
|
1567
|
+
sys_lilo_cfg = "/etc/lilo.conf" # TODO relativni k necemu?
|
1568
|
+
lilo_cfg = File.join( mnt, sys_lilo_cfg )
|
1569
|
+
|
1570
|
+
|
1571
|
+
File.unlink( lilo_cfg )
|
1572
|
+
File.open( lilo_cfg, 'w' ) do |f|
|
1573
|
+
f.puts "disk=#{disk}\nbios=0x80"
|
1574
|
+
content = File.open( sys_lilo_cfg ).read(nil)
|
1575
|
+
f.print content.gsub(/^((raid-extra-boot|disk|bios)[[:space:]]*=)/, '#\1')
|
1576
|
+
end
|
1577
|
+
|
1578
|
+
lilo_ret, lilo_out, lilo_err = system_catch_stdin_stderr( LILO_PATH, '-r', mnt, '-b', disk )
|
1579
|
+
if lilo_ret != 0 # check lilo
|
1580
|
+
@log.error { "error (#{lilo_ret}) in: '/sbin/lilo -r #{mnt} -b #{disk}' --> system not bootable" }
|
1581
|
+
@log.error { "\tstdout: #{lilo_out}" }
|
1582
|
+
@log.error { "\tstderr: #{lilo_err}" }
|
1583
|
+
return false
|
1584
|
+
else
|
1585
|
+
return true
|
1586
|
+
end
|
1587
|
+
end
|
1588
|
+
|
1589
|
+
#### kontrola disku a spousteni fsck ####
|
1590
|
+
def check_fsck
|
1591
|
+
part = nil
|
1592
|
+
crypto = @config[:crypto]
|
1593
|
+
password = @config[:password]
|
1594
|
+
loop_dev = nil
|
1595
|
+
|
1596
|
+
begin
|
1597
|
+
part = conf_val(:device)
|
1598
|
+
if (crypto)
|
1599
|
+
loop_dev = find_loop
|
1600
|
+
system_catch_stdin_stderr_with_input( password+"\n", '/sbin/losetup',
|
1601
|
+
'-p', '0', '-e', crypto, loop_dev, part )
|
1602
|
+
part = loop_dev
|
1603
|
+
end
|
1604
|
+
|
1605
|
+
if ! part.to_s.empty?
|
1606
|
+
@log.debug { "running: /sbin/tune2fs -l #{part}" }
|
1607
|
+
fs_params = {}
|
1608
|
+
|
1609
|
+
ret, out, err = system_catch_stdin_stderr('/sbin/tune2fs', '-l', part)
|
1610
|
+
if ret == 0
|
1611
|
+
out.split("\n").each do |l|
|
1612
|
+
k,v = l.split(/\s*:\s*/)
|
1613
|
+
fs_params[k] = v
|
1614
|
+
end
|
1615
|
+
else
|
1616
|
+
@log.error { "tune2fs failed with exit code: '#{ret}'" }
|
1617
|
+
@log.error { "\tstdout: #{out}" }
|
1618
|
+
@log.error { "\tstderr: #{err}" }
|
1619
|
+
return false
|
1620
|
+
end
|
1621
|
+
|
1622
|
+
if (mount_count = fs_params['Mount count'].to_i)+5 >=
|
1623
|
+
(max_mount_count = fs_params['Maximum mount count'].to_i)
|
1624
|
+
@log.info { "Disk #{part} has reached mount_count: #{mount_count} (max: #{max_mount_count}), running fsck" }
|
1625
|
+
if not @test_mode
|
1626
|
+
ret, out, err = system_catch_stdin_stderr( '/sbin/fsck.ext3', '-y', part )
|
1627
|
+
if ret == 0
|
1628
|
+
@log.info { "fsck found no errors" }
|
1629
|
+
return reset_mount_count( part )
|
1630
|
+
elsif ret == 1
|
1631
|
+
@log.info { "fsck corrected some errors" }
|
1632
|
+
@log.debug { "\tstdout: #{out}" }
|
1633
|
+
@log.debug { "\tstderr: #{err}" }
|
1634
|
+
return reset_mount_count( part )
|
1635
|
+
else
|
1636
|
+
@log.error { "fsck failed with exit code: '#{ret}'" }
|
1637
|
+
@log.error { "\tstdout: #{out}" }
|
1638
|
+
@log.error { "\tstderr: #{err}" }
|
1639
|
+
return false
|
1640
|
+
end
|
1641
|
+
end
|
1642
|
+
else
|
1643
|
+
@log.debug { "Disk #{part}: mount_count: #{mount_count} (max: #{max_mount_count}), not running fsck" }
|
1644
|
+
end
|
1645
|
+
@part_uuid = fs_params['Filesystem UUID']
|
1646
|
+
@part_label = fs_params['Filesystem volume name']
|
1647
|
+
@log.info { "Disk: #{part}, UUID: #{@part_uuid}, Label: #{@part_label}" }
|
1648
|
+
end
|
1649
|
+
ensure
|
1650
|
+
if crypto and loop_dev
|
1651
|
+
system_catch_stdin_stderr( '/sbin/losetup', '-d', loop_dev )
|
1652
|
+
end
|
1653
|
+
end
|
1654
|
+
|
1655
|
+
return true
|
1656
|
+
end
|
1657
|
+
|
1658
|
+
#### vypsani stavu zalohy ####
|
1659
|
+
def backup_status( backup_name, status_file_pref = nil )
|
1660
|
+
@status_file_pref = status_file_pref if status_file_pref
|
1661
|
+
res = nil
|
1662
|
+
return res if (res = config_init(backup_name)) != 0
|
1663
|
+
|
1664
|
+
if File.readable?(@status_file)
|
1665
|
+
puts File.open(@status_file).read(nil)
|
1666
|
+
puts
|
1667
|
+
end
|
1668
|
+
mount_backup
|
1669
|
+
backups = find_backups( backup_name )
|
1670
|
+
backups.each do |backup|
|
1671
|
+
puts backup
|
1672
|
+
end
|
1673
|
+
umount_backup(true)
|
1674
|
+
end
|
1675
|
+
|
1676
|
+
#### mount/umount ####
|
1677
|
+
# :none - not mounted, :ro - mounted ro, :rw - mounted rw
|
1678
|
+
def check_mounted
|
1679
|
+
File.open('/proc/mounts').read(nil).each_line do |line|
|
1680
|
+
what, where, type, opts, dump, pass = line.split(/\s+/)
|
1681
|
+
return [ opts.split(',').index('rw') ? :rw : :ro, what ] if where == conf_val(:mount_point)
|
1682
|
+
end
|
1683
|
+
return [ :none, nil ]
|
1684
|
+
end
|
1685
|
+
|
1686
|
+
def mount_backup(rw = true)
|
1687
|
+
device = conf_val(:device)
|
1688
|
+
mount_point = conf_val(:mount_point)
|
1689
|
+
fstype = conf_val(:fs_type)
|
1690
|
+
crypto = @config[:crypto]
|
1691
|
+
password = @config[:password]
|
1692
|
+
remount = false
|
1693
|
+
|
1694
|
+
if ! device.to_s.empty? and ! mount_point.to_s.empty?
|
1695
|
+
|
1696
|
+
mount_status, mount_dev = check_mounted
|
1697
|
+
@log.debug { "mount status before mount: device: #{mount_dev}, status: #{mount_status}" }
|
1698
|
+
|
1699
|
+
# pokud je na nasem mountpointu namountovano cokoliv jineho, nez co ma byt, koncime
|
1700
|
+
if (mount_status != :none) and (mount_dev != device)
|
1701
|
+
@log.fatal { "wrong device mounted as #{mount_point}: have #{mount_dev}, need #{device}"}
|
1702
|
+
return false
|
1703
|
+
end
|
1704
|
+
|
1705
|
+
if mount_status != :none
|
1706
|
+
if (mount_status == :ro) and rw
|
1707
|
+
# mame namontovano, ale jen ro, musime remountovat
|
1708
|
+
remount = true
|
1709
|
+
else
|
1710
|
+
# mame namontovano rw, ale neudelali jsme si to sami, preskocime mounting
|
1711
|
+
@log.warn { "disk not remounted, using previously mounted disk!" }
|
1712
|
+
return true
|
1713
|
+
end
|
1714
|
+
end
|
1715
|
+
|
1716
|
+
# try mount
|
1717
|
+
@log.debug { "mounting #{device} --> #{mount_point} " +
|
1718
|
+
(rw ? (remount ? 'remount,RW' : 'RW') : 'RO') +
|
1719
|
+
(crypto ? '[crypto]':'') }
|
1720
|
+
|
1721
|
+
cmd = [ 'mount', device, mount_point ]
|
1722
|
+
if rw
|
1723
|
+
if remount
|
1724
|
+
cmd << '-o' << 'remount,rw'
|
1725
|
+
end
|
1726
|
+
cmd << '-o' << 'noatime'
|
1727
|
+
else
|
1728
|
+
cmd << '-o' << 'ro'
|
1729
|
+
end
|
1730
|
+
cmd << '-o' << 'acl,user_xattr' if $HAVE_ACL and not @no_acl
|
1731
|
+
|
1732
|
+
cmd << '-t' << fstype unless fstype.to_s.empty?
|
1733
|
+
|
1734
|
+
ret, out, err =
|
1735
|
+
crypto ? system_catch_stdin_stderr_with_input(
|
1736
|
+
*( [password+"\n"] + cmd + ["-oencryption=#{crypto}", '-p', '0'] ) ) :
|
1737
|
+
system_catch_stdin_stderr( *cmd )
|
1738
|
+
if ret != 0
|
1739
|
+
@log.fatal { "mount #{device} --> #{mount_point} failed" }
|
1740
|
+
@log.warn { "\tstdout: #{out}" }
|
1741
|
+
@log.warn { "\tstderr: #{err}" }
|
1742
|
+
return false
|
1743
|
+
end
|
1744
|
+
end
|
1745
|
+
return true
|
1746
|
+
end
|
1747
|
+
|
1748
|
+
# unmount backup
|
1749
|
+
# return true on success (not mounted or succesfull unmount)
|
1750
|
+
def umount_backup(try_ro=false)
|
1751
|
+
device = conf_val(:device)
|
1752
|
+
mount_point = conf_val(:mount_point)
|
1753
|
+
|
1754
|
+
if ! device.to_s.empty? and ! mount_point.to_s.empty?
|
1755
|
+
|
1756
|
+
mount_status, mount_dev = check_mounted
|
1757
|
+
if mount_status != :none
|
1758
|
+
@log.debug { "mount status before umount: device: #{mount_dev}, status: #{mount_status}" }
|
1759
|
+
@log.debug { "unmounting #{mount_point}" }
|
1760
|
+
|
1761
|
+
ret, out, err = system_catch_stdin_stderr( '/bin/umount', mount_point )
|
1762
|
+
if ret != 0
|
1763
|
+
@log.warn { "umount #{mount_point} failed" }
|
1764
|
+
@log.warn { "\tstdout: #{out}" }
|
1765
|
+
@log.warn { "\tstderr: #{err}" }
|
1766
|
+
|
1767
|
+
if not try_ro
|
1768
|
+
return false
|
1769
|
+
else
|
1770
|
+
# jeste se pokusime o remount,ro
|
1771
|
+
ret, out, err = system_catch_stdin_stderr( '/bin/mount', '-o', 'remount,rw', mount_point )
|
1772
|
+
if ret != 0
|
1773
|
+
@log.warn { "mount -o remount,ro #{mount_point} failed" }
|
1774
|
+
@log.warn { "\tstdout: #{out}" }
|
1775
|
+
@log.warn { "\tstderr: #{err}" }
|
1776
|
+
@log.warn { "backup remained mounted!" }
|
1777
|
+
end
|
1778
|
+
end
|
1779
|
+
end
|
1780
|
+
end
|
1781
|
+
end
|
1782
|
+
return true
|
1783
|
+
end
|
1784
|
+
|
1785
|
+
def mirror_only( backup_name )
|
1786
|
+
return MOUNT_FAILED unless mount_backup
|
1787
|
+
|
1788
|
+
mirror_res = do_mirror_only( backup_name )
|
1789
|
+
res = mirror_res unless mirror_res == nil
|
1790
|
+
|
1791
|
+
umount_backup
|
1792
|
+
return 0
|
1793
|
+
end
|
1794
|
+
|
1795
|
+
def backup_pcs( pcs_status_f, no_delete )
|
1796
|
+
@log.info { "starting PCs backup" }
|
1797
|
+
|
1798
|
+
if (ret = umount_fsck_mount) != 0
|
1799
|
+
return ret
|
1800
|
+
end
|
1801
|
+
|
1802
|
+
pc_status = Marshal.restore( File.open( pcs_status_f ).read( nil ) ) rescue {}
|
1803
|
+
|
1804
|
+
@pcb[:backup_hosts].each do |host|
|
1805
|
+
@log.info { "mounting #{host}" }
|
1806
|
+
mount_to = "#{@pcb[:mounts_root]}/#{host}"
|
1807
|
+
system_catch_stdin_stderr('umount', mount_to )
|
1808
|
+
FileUtils.mkpath( mount_to )
|
1809
|
+
|
1810
|
+
pc_status[host] = Hash.new unless pc_status.key?(host)
|
1811
|
+
pc_status[host][:tried] = Time.now
|
1812
|
+
pc_status[host][:status] = -1
|
1813
|
+
|
1814
|
+
cmd = [ "/bin/mount", "-t", "smbfs", "-o",
|
1815
|
+
"username=#{@pcb[:backup_user]},"+
|
1816
|
+
"password=#{@pcb[:backup_password]},"+
|
1817
|
+
"workgroup=#{@pcb[:backup_workgroup]}",
|
1818
|
+
"//#{host}/#{@pcb[:backup_share]}", mount_to ]
|
1819
|
+
@log.debug { "running '" + cmd.join("' '") + "'" }
|
1820
|
+
ret, out, err = system_catch_stdin_stderr( *cmd )
|
1821
|
+
|
1822
|
+
if ret == 0
|
1823
|
+
begin
|
1824
|
+
@log.debug { "host #{host} mount passed" }
|
1825
|
+
|
1826
|
+
# run backup
|
1827
|
+
bk2 = LnBackup.new(
|
1828
|
+
:log_level => @log.level,
|
1829
|
+
:log_file => "#{@pcb[:log_dir]}/lnbackup-#{host}.log",
|
1830
|
+
:test_mode => @test_mode,
|
1831
|
+
:config_dir => @config_dir,
|
1832
|
+
:status_file_pref => "#{@pcb[:status_dir]}/lnbackup-#{host}.status",
|
1833
|
+
:source_prefix => mount_to,
|
1834
|
+
:target_dir_name => "#{@pcb[:backup_config]}/#{host}",
|
1835
|
+
:no_delete => no_delete
|
1836
|
+
)
|
1837
|
+
|
1838
|
+
bk2.config_init( @pcb[:backup_config] ) # TODO: error handling ?!
|
1839
|
+
res = bk2.run_backup
|
1840
|
+
|
1841
|
+
pc_status[host][:status] = res
|
1842
|
+
pc_status[host][:stats] = bk2.stats
|
1843
|
+
if res == BACKUP_OK
|
1844
|
+
pc_status[host][:success] = Time.now
|
1845
|
+
end
|
1846
|
+
rescue => e
|
1847
|
+
@log.fatal { "host #{host} raised exception #{e.class}:'#{e.message}'" }
|
1848
|
+
@log.fatal { e.backtrace.join("\n") }
|
1849
|
+
@log.fatal { "skipping to next host" }
|
1850
|
+
ensure
|
1851
|
+
system_catch_stdin_stderr("/bin/umount", mount_to )
|
1852
|
+
end
|
1853
|
+
@log.debug { "host #{host} finished" }
|
1854
|
+
else
|
1855
|
+
@log.error { "#{host} failed: \n\tout:#{out}\n\terr:#{err}" }
|
1856
|
+
pc_status[host][:status] = SMBMOUNT_FAILED
|
1857
|
+
end
|
1858
|
+
end
|
1859
|
+
|
1860
|
+
begin
|
1861
|
+
File.open( pcs_status_f, 'w' ) do |f|
|
1862
|
+
f.print Marshal.dump( pc_status )
|
1863
|
+
end
|
1864
|
+
rescue => e
|
1865
|
+
@log.error { "cannot write pc_status: #{pcs_status_f}: #{e.message}" }
|
1866
|
+
end
|
1867
|
+
|
1868
|
+
umount_backup(true)
|
1869
|
+
@log.info { "finished PCs backup" }
|
1870
|
+
end
|
1871
|
+
|
1872
|
+
def backup_pcs_status( pcs_status_f )
|
1873
|
+
pc_status = Hash.new
|
1874
|
+
begin
|
1875
|
+
pc_status = Marshal.restore( File.open( pcs_status_f ).read( nil ) )
|
1876
|
+
rescue
|
1877
|
+
end
|
1878
|
+
|
1879
|
+
print "Content-type: text/html\r\n\r\n"
|
1880
|
+
puts "<html><table>"
|
1881
|
+
|
1882
|
+
@pcb[:backup_hosts].each do |host|
|
1883
|
+
puts "<tr><td>#{host}<td>"
|
1884
|
+
|
1885
|
+
if not pc_status.key?(host)
|
1886
|
+
puts "\t\thas no backup"
|
1887
|
+
else
|
1888
|
+
st = pc_status[host]
|
1889
|
+
if st.key?(:status) and (st[:status] == BACKUP_OK)
|
1890
|
+
puts "\t\t(OK) " + st[:success].to_s + ' ' + size2str( st[:stats][:size].to_i ) << 'B '
|
1891
|
+
else
|
1892
|
+
if st.key?(:success)
|
1893
|
+
puts "\t\t(WARN) Last success: " + st[:success].to_s
|
1894
|
+
else
|
1895
|
+
puts "\t\t(ERROR) Last tried: " + st[:tried].to_s
|
1896
|
+
end
|
1897
|
+
end
|
1898
|
+
end
|
1899
|
+
end
|
1900
|
+
|
1901
|
+
puts "</table></html>"
|
1902
|
+
end
|
1903
|
+
|
1904
|
+
def backup_pcs_test
|
1905
|
+
@log.info { "starting PCs backup test" }
|
1906
|
+
|
1907
|
+
@pcb[:backup_hosts].each do |host|
|
1908
|
+
@log.debug { "trying #{host} ... " }
|
1909
|
+
#echo smbclient -c "''" -W $BACKUP_WORKGROUP //$HOST/$BACKUP_SHARE -U $BACKUP_USER%$BACKUP_PASSWORD
|
1910
|
+
cmd = [ '/usr/bin/smbclient', '-c', '', '-W', @pcb[:backup_workgroup],
|
1911
|
+
"//#{host}/#{@pcb[:backup_share]}", '-U',
|
1912
|
+
"#{@pcb[:backup_user]}%#{@pcb[:backup_password]}" ]
|
1913
|
+
@log.debug { "running '" + cmd.join("' '") + "'" }
|
1914
|
+
|
1915
|
+
ret, out, err = system_catch_stdin_stderr( *cmd )
|
1916
|
+
if ret == 0
|
1917
|
+
@log.info { "#{host} passed" }
|
1918
|
+
else
|
1919
|
+
@log.error { "#{host} failed: ret:#{ret}\n\tout:#{out}\n\terr:#{err}" }
|
1920
|
+
end
|
1921
|
+
end
|
1922
|
+
|
1923
|
+
@log.info { "finished PCs backup test" }
|
1924
|
+
end
|
1925
|
+
|
1926
|
+
def crypto_init(size)
|
1927
|
+
device = conf_val(:device)
|
1928
|
+
crypto = @config[:crypto]
|
1929
|
+
password = @config[:password]
|
1930
|
+
|
1931
|
+
if crypto and password and device
|
1932
|
+
mk_loop( device, password, size, crypto )
|
1933
|
+
else
|
1934
|
+
@log.fatal { "crypto_init: must specify device, crypto and password in config file" }
|
1935
|
+
end
|
1936
|
+
end
|
1937
|
+
end
|
1938
|
+
end
|