rubyyabt 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rubyyabt-backup.rb +65 -0
- data/bin/rubyyabt-restore.rb +56 -0
- data/classes/Backup.rb +100 -0
- data/classes/Chunk.rb +101 -0
- data/classes/GPG.rb +137 -0
- data/classes/ProxyFile.rb +43 -0
- data/classes/ProxyHTTP.rb +224 -0
- data/classes/SMGFile.rb +292 -0
- data/classes/Source.rb +93 -0
- data/classes/Target.rb +130 -0
- data/classes/cui.rb +148 -0
- data/classes/proxy_http_cache_data.rb +16 -0
- data/classes/proxy_http_cache_hash.rb +72 -0
- data/doc/dev/options.txt +69 -0
- metadata +78 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$stdout.sync = true
|
4
|
+
$stderr.sync = true
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
# Defaults for the command line options
|
9
|
+
options = {
|
10
|
+
:program_name => 'rubyyabt',
|
11
|
+
:version => [0, 0, 1],
|
12
|
+
:verbose => false,
|
13
|
+
:debug => false,
|
14
|
+
:excl_incl => [],
|
15
|
+
:username => "",
|
16
|
+
:password => "",
|
17
|
+
:chunk_size => 2 ** 20, # 1 Megabyte chunk size by default
|
18
|
+
:gpg_bin => '/usr/bin/gpg',
|
19
|
+
:ssl_cert_dir => '/etc/ssl/certs',
|
20
|
+
:mode => :backup,
|
21
|
+
:empty_cache => false,
|
22
|
+
:cache_max_age => 90 * (24*60*60), # By default allow 30 days cache time
|
23
|
+
:http_timeout => 5 * 60 # By default allow a http timeout of 5 minutes
|
24
|
+
}
|
25
|
+
|
26
|
+
# Parse the command line...
|
27
|
+
OptionParser.new do |opts|
|
28
|
+
opts.banner = "Usage: rubyyabt-backup.rb [options] --source <source> --target <target>"
|
29
|
+
opts.on("-s", "--source SOURCE", "Specify the source path") { |v| options[:source] = v }
|
30
|
+
opts.on("-e", "--exclincl FILE", "Load excludes/includes from FILE") { |v| options[:excl_incl] = File.readlines(v) }
|
31
|
+
opts.on("-t", "--target TARGET", "Specify the target path") { |v| options[:target] = v }
|
32
|
+
opts.on("-u", "--user USER", "Username for target") { |v| options[:username] = v }
|
33
|
+
opts.on("-p", "--pass PASS", "Password for target") { |v| options[:password] = v }
|
34
|
+
opts.on("-P", "--passfile FILE", "Read password for target from FILE") { |v| options[:password] = File.read(v) }
|
35
|
+
opts.on("-G", "--gpg-bin FILE", "use FILE as gpg binary") { |v| options[:gpg_bin] = v }
|
36
|
+
opts.on("-g", "--gpg-key KEY", "Use KEY as GPG key for encryption and signing") { |v| options[:gpg_key] = v }
|
37
|
+
opts.on("-h", "--gpg-pass PASS", "Password for GPG key") { |v| options[:gpg_pass] = v }
|
38
|
+
opts.on("-H", "--gpg-passfile FILE", "Read password for GPG key from FILE") { |v| options[:gpg_pass] = File.read(v) }
|
39
|
+
opts.on("-c", "--cache-max-age DAYS", "Allow maximum of DAYS time for a positive cache hit. False hits are never cached.") { |v| options[:cache_max_age] = v.to_i *(24*60*60) }
|
40
|
+
opts.on("", "--http-timeout SECS", "Require all http requests to complete within SECS seconds.") { |v| options[:http_timeout] = v.to_i }
|
41
|
+
opts.on("-C", "--empty-cache", "Start with an empty cache instead of restoring the cache from saved data") { options[:empty_cache] = true }
|
42
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") { |v| options[:verbose] = v }
|
43
|
+
opts.on("-d", "--[no-]debug", "Show debug messages") { |v| options[:debug] = v }
|
44
|
+
opts.on_tail("-?", "--help", "Show this message") {
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
}
|
48
|
+
end.parse!
|
49
|
+
|
50
|
+
abort('--source must be specified') if not options.include?(:source)
|
51
|
+
abort('--target must be specified') if not options.include?(:target)
|
52
|
+
abort('--gpg-key must be specified') if not options.include?(:gpg_key)
|
53
|
+
abort('--gpg-pass must be specified') if not options.include?(:gpg_pass)
|
54
|
+
|
55
|
+
# Now load the main program
|
56
|
+
#noinspection RubyResolve
|
57
|
+
require 'classes/Backup'
|
58
|
+
|
59
|
+
Thread.abort_on_exception = true
|
60
|
+
$myDEBUG = options[:debug]
|
61
|
+
$myVERBOSE = options[:verbose]
|
62
|
+
|
63
|
+
backup = Backup.new(options)
|
64
|
+
|
65
|
+
exit 1 if backup.backup! > 0
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$stdout.sync = true
|
3
|
+
$stderr.sync = true
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
# Defaults for the command line options
|
8
|
+
options = {
|
9
|
+
:program_name => 'rubyyabt',
|
10
|
+
:version => [0, 0, 1],
|
11
|
+
:verbose => false,
|
12
|
+
:debug => false,
|
13
|
+
:excl_incl => [],
|
14
|
+
:username => "",
|
15
|
+
:password => "",
|
16
|
+
:chunk_size => 2 ** 20, # 1 Megabyte chunk size by default
|
17
|
+
:gpg_bin => '/usr/bin/gpg',
|
18
|
+
:ssl_cert_dir => '/etc/ssl/certs',
|
19
|
+
:mode => :restore
|
20
|
+
}
|
21
|
+
# Parse the command line...
|
22
|
+
OptionParser.new do |opts|
|
23
|
+
opts.banner = "Usage: rubyyabt-restore.rb [options] --source <LOCAL> --target <REMOTE> --backup <BACKUPNAME>"
|
24
|
+
opts.on("-s", "--source LOCAL", "Specify the local path (restore will write to this directory)") { |v| options[:source] = v }
|
25
|
+
opts.on("-e", "--excl_incl FILE", "Load excludes/includes from FILE") { |v| options[:excl_incl] = File.readlines(v) }
|
26
|
+
opts.on("-t", "--target REMOTE", "Specifies the remote path that contains the backed up data") { |v| options[:target] = v }
|
27
|
+
opts.on("-b", "--backup BACKUPNAME", "Specifies which backup should be restored (no .gpg extension)") { |v| options[:backup_name] = v }
|
28
|
+
opts.on("-u", "--user USER", "Username for target") { |v| options[:username] = v }
|
29
|
+
opts.on("-p", "--pass PASS", "Password for target") { |v| options[:password] = v }
|
30
|
+
opts.on("-P", "--passfile FILE", "Read password for target from FILE") { |v| options[:password] = File.read(v) }
|
31
|
+
opts.on("-g", "--gpg-key KEY", "Use KEY as GPG key for encryption and signing") { |v| options[:gpg_key] = v }
|
32
|
+
opts.on("-h", "--gpg-pass PASS", "Password for GPG key") { |v| options[:gpg_pass] = v }
|
33
|
+
opts.on("-H", "--gpg-passfile FILE", "Read password for GPG key from FILE") { |v| options[:gpg_pass] = File.read(v) }
|
34
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") { |v| options[:verbose] = v }
|
35
|
+
opts.on("-d", "--[no-]debug", "Show debug messages") { |v| options[:debug] = v }
|
36
|
+
opts.on_tail("-?", "--help", "Show this message") {
|
37
|
+
puts opts
|
38
|
+
exit
|
39
|
+
}
|
40
|
+
end.parse!
|
41
|
+
|
42
|
+
abort('--source must be specified') if not options.include?(:source)
|
43
|
+
abort('--target must be specified') if not options.include?(:target)
|
44
|
+
abort('--gpg-key must be specified') if not options.include?(:gpg_key)
|
45
|
+
abort('--gpg-pass must be specified') if not options.include?(:gpg_pass)
|
46
|
+
|
47
|
+
# Now load the main program
|
48
|
+
#noinspection RubyResolve
|
49
|
+
require 'classes/Backup'
|
50
|
+
|
51
|
+
Thread.abort_on_exception = true
|
52
|
+
$myDEBUG = options[:debug]
|
53
|
+
$myVERBOSE = options[:verbose]
|
54
|
+
|
55
|
+
backup = Backup.new(options)
|
56
|
+
backup.restore!
|
data/classes/Backup.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#noinspection RubyResolve
|
4
|
+
require 'classes/SMGFile'
|
5
|
+
#noinspection RubyResolve
|
6
|
+
require 'classes/Source'
|
7
|
+
#noinspection RubyResolve
|
8
|
+
require 'classes/Target'
|
9
|
+
#noinspection RubyResolve
|
10
|
+
require 'classes/cui'
|
11
|
+
|
12
|
+
$stderr.sync = true
|
13
|
+
$stdout.sync = true
|
14
|
+
|
15
|
+
$myDEBUG = false unless $myDEBUG
|
16
|
+
|
17
|
+
# TODO: Implement a trap for SIGINT to catch CTRL+c
|
18
|
+
|
19
|
+
class Backup
|
20
|
+
def initialize(options)
|
21
|
+
$options = options
|
22
|
+
@cui = Cui.instance()
|
23
|
+
@target = Target.instance()
|
24
|
+
@source = Source.instance()
|
25
|
+
@metadata = Array.new
|
26
|
+
# Find a good name for the backup. If one has been given through options[:backup_name] then use it,
|
27
|
+
# else create one by using the current date and time.
|
28
|
+
@name = $options[:backup_name] if options.include?(:backup_name)
|
29
|
+
if (options[:mode] == :restore) and not (options.include?(:backup_name)) then
|
30
|
+
raise("No backup to restore given.")
|
31
|
+
end
|
32
|
+
@time = Time.now
|
33
|
+
@time.utc
|
34
|
+
@name = @time.strftime("%Y%m%dT%H%M%S") unless @name
|
35
|
+
@cui.message("Initialized backup #{@name}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def backup!()
|
39
|
+
$errors = 0 if not $errors
|
40
|
+
@cui.message("Scanning for files...")
|
41
|
+
@cui.start
|
42
|
+
@cui.message("Found #{@source.files.count.to_s} files and directories")
|
43
|
+
backup_meta = Array.new
|
44
|
+
backup_meta << '[Backup]'
|
45
|
+
cache_timer = Time.now
|
46
|
+
@source.files.each { | f |
|
47
|
+
if (Time.now - cache_timer) > 900 then
|
48
|
+
@cui.message("Uploading cache data...")
|
49
|
+
@target.upload_cache
|
50
|
+
cache_timer = Time.now
|
51
|
+
end
|
52
|
+
begin
|
53
|
+
file = SMGFile.new(f)
|
54
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: created new SMGFile object from #{f}") if $myDEBUG
|
55
|
+
file.backup!
|
56
|
+
file_meta = Array.new
|
57
|
+
file_meta << 'file=' + file.meta_sha256 + '.' + file.meta_md5
|
58
|
+
file_meta << 'size=' + file.size.to_s
|
59
|
+
file_meta << 'mtime=' + file.mtime
|
60
|
+
file_meta << 'name=' + f
|
61
|
+
file_meta = file_meta.join(";")
|
62
|
+
backup_meta << file_meta
|
63
|
+
rescue Exception
|
64
|
+
@cui.error("Error: #{$!.to_s}. Skipping this object.")
|
65
|
+
$errors += 1
|
66
|
+
end
|
67
|
+
}
|
68
|
+
@cui.message("Waiting for last chunk to finish uploading...")
|
69
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: Joining chunk threads") if $myDEBUG
|
70
|
+
chunk = Chunk.new
|
71
|
+
chunk.join
|
72
|
+
@cui.stop
|
73
|
+
@cui.update
|
74
|
+
@cui.message("Finished backing up data. Now uploading metadata for backup #{@name}.")
|
75
|
+
backup_meta = backup_meta.join("\n")
|
76
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: Writing backup info") if $myDEBUG
|
77
|
+
@target.write(:backup, @name, backup_meta)
|
78
|
+
@cui.message("Uploading cache data...")
|
79
|
+
@target.upload_cache
|
80
|
+
@cui.error("Errors: A total of #{$errors} errors have been encountered.") if $errors > 0
|
81
|
+
return $errors
|
82
|
+
end
|
83
|
+
|
84
|
+
def restore!()
|
85
|
+
backup_meta = @target.read(:backup, @name)
|
86
|
+
backup_meta.lines { | line |
|
87
|
+
line.chomp!
|
88
|
+
if !(line.eql?('[Backup]')) then
|
89
|
+
sha256 = line[5..68]
|
90
|
+
md5 = line[70..101]
|
91
|
+
size = /^[^;]*;size=([0-9]*);/.match(line).captures[0].to_i
|
92
|
+
mtime = Time.rfc2822(/^[^;]*;[^;]*;mtime=([^;]*);/.match(line).captures[0])
|
93
|
+
name = /^[^;]*;[^;]*;[^;]*;name=(.*)$/.match(line).captures[0]
|
94
|
+
@cui.message("Restoring file: size=#{size} mtime=#{mtime} name=#{name}")
|
95
|
+
f = SMGFile.new(nil, md5, sha256)
|
96
|
+
f.restore!
|
97
|
+
end
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
data/classes/Chunk.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'digest/md5'
|
4
|
+
#noinspection RubyResolve
|
5
|
+
require 'digest/sha2'
|
6
|
+
#noinspection RubyResolve
|
7
|
+
require 'thread'
|
8
|
+
#noinspection RubyResolve
|
9
|
+
require 'classes/Target'
|
10
|
+
#noinspection RubyResolve
|
11
|
+
require 'classes/cui'
|
12
|
+
|
13
|
+
$myDEBUG = false unless $myDEBUG
|
14
|
+
|
15
|
+
class Chunk
|
16
|
+
attr_reader :data, :length
|
17
|
+
@@upload_mutex = Mutex.new
|
18
|
+
@@upload_thread = nil
|
19
|
+
|
20
|
+
def initialize()
|
21
|
+
@md5 = nil
|
22
|
+
@md5thread = nil
|
23
|
+
@sha256 = nil
|
24
|
+
@sha256thread = nil
|
25
|
+
@data = nil
|
26
|
+
@cui = Cui.instance
|
27
|
+
end
|
28
|
+
|
29
|
+
def join()
|
30
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: Joining chunk thread") if $myDEBUG
|
31
|
+
@@upload_thread.join if @@upload_thread
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_data(data)
|
35
|
+
@cui.error("DEBUG[#{Thread.current.inspect}: Entered chunk set_data") if $myDEBUG
|
36
|
+
@data = data
|
37
|
+
@cui.error("DEBUG[#{Thread.current.inspect}: Set data") if $myDEBUG
|
38
|
+
@length = @data.length
|
39
|
+
@cui.error("DEBUG[#{Thread.current.inspect}: Calculated length") if $myDEBUG
|
40
|
+
@md5 = nil
|
41
|
+
@md5 = Digest::MD5.hexdigest(@data)
|
42
|
+
@sha256 = nil
|
43
|
+
@sha256 = Digest::SHA2.new(256).hexdigest(@data)
|
44
|
+
end
|
45
|
+
|
46
|
+
def md5()
|
47
|
+
return @md5 if @md5
|
48
|
+
@md5 = Digest::MD5.hexdigest(@data)
|
49
|
+
end
|
50
|
+
|
51
|
+
def md5=(md5)
|
52
|
+
@data = nil
|
53
|
+
@md5 = md5
|
54
|
+
end
|
55
|
+
|
56
|
+
def sha256()
|
57
|
+
return @sha256 if @sha256
|
58
|
+
@sha256 = Digest::SHA2.new(256).hexdigest(@data)
|
59
|
+
end
|
60
|
+
|
61
|
+
def sha256=(sha256)
|
62
|
+
@data = nil
|
63
|
+
@sha256 = sha256
|
64
|
+
end
|
65
|
+
|
66
|
+
def backup!()
|
67
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: Locking chunk upload mutex in thread #{Thread.current.inspect}") if $myDEBUG
|
68
|
+
@@upload_mutex.synchronize {
|
69
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: Got lock of chunk upload mutex in thread #{Thread.current.inspect}") if $myDEBUG
|
70
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: Trying to join earlier upload threads if any (@@uploadThread: #{@@upload_thread.inspect})") if $myDEBUG
|
71
|
+
@@upload_thread.join if @@upload_thread
|
72
|
+
@@upload_thread = Thread.new {
|
73
|
+
target = Target.instance # Get a target object, then ...
|
74
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: Checking if target chunk exists (#{sha256}, #{md5})") if $myDEBUG
|
75
|
+
if not target.exists?(:chunk, "#{sha256}.#{md5}") then
|
76
|
+
@cui.error("Uploading chunk #{sha256}.#{md5}...") if $myDEBUG
|
77
|
+
target.write(:chunk, "#{sha256}.#{md5}", @data)
|
78
|
+
end
|
79
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: Upload chunk thread finished (thread id: #{Thread.current.inspect})") if $myDEBUG
|
80
|
+
}
|
81
|
+
@cui.error("DEBUG[#{Thread.current.inspect}]: New upload thread: @@upload_thread = #{@@upload_thread.inspect}") if $myDEBUG
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def restore!()
|
86
|
+
@cui.message("Downloading chunk #{sha256}.#{md5}...")
|
87
|
+
target = Target.instance
|
88
|
+
@data = target.read(:chunk, "#{sha256}.#{md5}")
|
89
|
+
@length = @data.length
|
90
|
+
expected_md5 = @md5
|
91
|
+
expected_sha256 = @sha256
|
92
|
+
@md5 = nil
|
93
|
+
@sha256 = nil
|
94
|
+
if (expected_md5 == md5) and (expected_sha256 == sha256) then
|
95
|
+
@cui.message("chunk verified")
|
96
|
+
else
|
97
|
+
@cui.message("checksum error in chunk #{sha256}.#{md5}")
|
98
|
+
raise 'checksum error'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/classes/GPG.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
#noinspection RubyResolve
|
5
|
+
require 'classes/cui'
|
6
|
+
|
7
|
+
$options = nil unless $options
|
8
|
+
|
9
|
+
class GPG
|
10
|
+
def initialize()
|
11
|
+
@cui = Cui.instance
|
12
|
+
end
|
13
|
+
|
14
|
+
def encrypt(data)
|
15
|
+
# Setup the pipes
|
16
|
+
pass = IO::pipe # Pipe for providing the password
|
17
|
+
pi = IO::pipe # Data will be written to this pipe (stdin for gpg)
|
18
|
+
po = IO::pipe # Encrypted data can be read from this pipe (stdout for gpg)
|
19
|
+
|
20
|
+
gpg_proc = Kernel.fork do
|
21
|
+
# Cleanup unused end of the pipes
|
22
|
+
pass[1].close
|
23
|
+
pi[1].close
|
24
|
+
po[0].close
|
25
|
+
|
26
|
+
# Remap the STDIN and STDOUT pipes
|
27
|
+
STDIN.reopen(pi[0])
|
28
|
+
pi[0].close
|
29
|
+
STDOUT.reopen(po[1])
|
30
|
+
po[1].close
|
31
|
+
|
32
|
+
# Execute gpg
|
33
|
+
Kernel.exec($options[:gpg_bin], '--batch', '--encrypt', '--sign', '--passphrase-fd', pass[0].fileno.to_s, '--recipient', $options[:gpg_key], '--local-user', $options[:gpg_key])
|
34
|
+
raise 'Error running GPG'
|
35
|
+
end
|
36
|
+
|
37
|
+
# Cleanup unused end of the pipes for ruby
|
38
|
+
pass[0].close
|
39
|
+
pi[0].close
|
40
|
+
po[1].close
|
41
|
+
|
42
|
+
# Start new threads to send the data to GPG. This hopefully avoids any blocking.
|
43
|
+
thread_pass = Thread.new do
|
44
|
+
pass[1].write($options[:gpg_pass])
|
45
|
+
pass[1].close
|
46
|
+
end
|
47
|
+
thread_pi = Thread.new do
|
48
|
+
pi[1].write(data)
|
49
|
+
pi[1].close
|
50
|
+
end
|
51
|
+
|
52
|
+
# Run a new thread to read data from GPG.
|
53
|
+
encrypted_data = String.new # Initialize the variable for reading from GPG
|
54
|
+
thread_po = Thread.new do
|
55
|
+
# Read from GPG until the pipe is closed
|
56
|
+
#while (new_encrypted_data = po[0].read)
|
57
|
+
#encrypted_data += new_encrypted_data
|
58
|
+
while (!po[0].eof?)
|
59
|
+
encrypted_data += po[0].read
|
60
|
+
end
|
61
|
+
po[0].close
|
62
|
+
end
|
63
|
+
# Now cleanup the process from GPG and the threads
|
64
|
+
Process.wait(gpg_proc)
|
65
|
+
thread_pi.join
|
66
|
+
#noinspection RubyUnusedLocalVariable
|
67
|
+
data = nil # Free up the variable and hopefully some memory while we're waiting for the other threads
|
68
|
+
thread_pass.join
|
69
|
+
thread_po.join
|
70
|
+
# Return the data, if there's no data, GPG has probably thrown an error.
|
71
|
+
return encrypted_data if encrypted_data.length > 0
|
72
|
+
@cui.error("Failed to run gpg to encrypt data. Command run: " + [$options[:gpg_bin], '--batch', '--encrypt', '--sign', '--passphrase-fd', pass[0].fileno.to_s, '--recipient', $options[:gpg_key], '--local-user', $options[:gpg_key]].inspect)
|
73
|
+
raise 'GPG error'
|
74
|
+
end
|
75
|
+
|
76
|
+
def decrypt(encrypted_data)
|
77
|
+
# Setup the pipes
|
78
|
+
pass = IO::pipe # Pipe for providing the password
|
79
|
+
pi = IO::pipe # Data will be written to this pipe (stdin for gpg)
|
80
|
+
po = IO::pipe # Encrypted data can be read from this pipe (stdout for gpg)
|
81
|
+
|
82
|
+
gpg_proc = Kernel.fork do
|
83
|
+
# Cleanup unused end of the pipes
|
84
|
+
pass[1].close
|
85
|
+
pi[1].close
|
86
|
+
po[0].close
|
87
|
+
|
88
|
+
# Remap the STDIN and STDOUT pipes
|
89
|
+
STDIN.reopen(pi[0])
|
90
|
+
pi[0].close
|
91
|
+
STDOUT.reopen(po[1])
|
92
|
+
po[1].close
|
93
|
+
|
94
|
+
# Execute gpg
|
95
|
+
Kernel.exec($options[:gpg_bin], '--batch', '--decrypt', '--passphrase-fd', pass[0].fileno.to_s)
|
96
|
+
raise 'Error running GPG'
|
97
|
+
end
|
98
|
+
|
99
|
+
# Cleanup unused end of the pipes for ruby
|
100
|
+
pass[0].close
|
101
|
+
pi[0].close
|
102
|
+
po[1].close
|
103
|
+
|
104
|
+
# Start new threads to send the data to GPG. This hopefully avoids any blocking.
|
105
|
+
thread_pass = Thread.new do
|
106
|
+
pass[1].write($options[:gpg_pass])
|
107
|
+
pass[1].close
|
108
|
+
end
|
109
|
+
thread_pi = Thread.new do
|
110
|
+
pi[1].write(encrypted_data)
|
111
|
+
pi[1].close
|
112
|
+
end
|
113
|
+
|
114
|
+
# Run a new thread to read data from GPG.
|
115
|
+
data = String.new # Initialize the variable for reading from GPG
|
116
|
+
thread_po = Thread.new do
|
117
|
+
# Read from GPG until the pipe is closed
|
118
|
+
#while (new_data = po[0].read)
|
119
|
+
#data += new_data
|
120
|
+
while (!po[0].eof?)
|
121
|
+
data += po[0].read
|
122
|
+
end
|
123
|
+
po[0].close
|
124
|
+
end
|
125
|
+
# Now cleanup the process from GPG and the threads
|
126
|
+
Process.wait(gpg_proc)
|
127
|
+
thread_pi.join
|
128
|
+
#noinspection RubyUnusedLocalVariable
|
129
|
+
encrypted_data = nil # Free up the variable and hopefully some memory while we're waiting for the other threads
|
130
|
+
thread_pass.join
|
131
|
+
thread_po.join
|
132
|
+
# Return the data, if there's no data, GPG has probably thrown an error.
|
133
|
+
return data if data.length > 0
|
134
|
+
@cui.error("Failed to run gpg to encrypt data. Command run: " + [$options[:gpg_bin], '--batch', '--decrypt', '--passphrase-fd', pass[0].fileno.to_s].inspect)
|
135
|
+
raise 'GPG error'
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
class ProxyFile
|
4
|
+
def initialize()
|
5
|
+
@types = {
|
6
|
+
:chunk => 'chunks',
|
7
|
+
:file => 'files',
|
8
|
+
:backup => 'backups',
|
9
|
+
:root => ''
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def caching?()
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
|
17
|
+
def read(target_dir, type, file)
|
18
|
+
target_file = File.expand_path(target_dir.to_s + '/' + @types[type] + '/' + file)
|
19
|
+
open(target_file, "rb") {|io| io.read }
|
20
|
+
end
|
21
|
+
|
22
|
+
def write(target_dir, type, file, data)
|
23
|
+
target file = File.expand_path(target_dir.to_s + '/' + @types[type] + '/' + file)
|
24
|
+
open(target file, "wb") {|io| io.write(data) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def exists?(target_dir, type, file)
|
28
|
+
target_file = File.expand_path(target_dir.to_s + '/' + @types[type] + '/' + file)
|
29
|
+
if File.exists?(target_file) then
|
30
|
+
if File.directory?(target_file) then
|
31
|
+
false
|
32
|
+
else
|
33
|
+
true
|
34
|
+
end
|
35
|
+
else
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def list(target_dir, type)
|
41
|
+
Dir.entries(target_dir + '/' + @types[type])
|
42
|
+
end
|
43
|
+
end
|