rubyyabt 0.0.5
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/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
|