lsync 2.3.1 → 2.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/lsync.rb +4 -0
- data/lib/lsync/client.rb +20 -9
- data/lib/lsync/controller.rb +46 -7
- data/lib/lsync/error.rb +3 -3
- data/lib/lsync/event_handler.rb +1 -1
- data/lib/lsync/method.rb +0 -1
- data/lib/lsync/methods/rsync.rb +68 -53
- data/lib/lsync/script.rb +23 -12
- data/lib/lsync/tee_logger.rb +1 -1
- data/lib/lsync/version.rb +1 -1
- metadata +7 -8
- data/lib/lsync/run.rb +0 -71
data/lib/lsync.rb
CHANGED
data/lib/lsync/client.rb
CHANGED
@@ -28,29 +28,40 @@ $connection.run do |object|
|
|
28
28
|
Dir.chdir(object[1])
|
29
29
|
when :script
|
30
30
|
# [:script, :command, :data]
|
31
|
-
|
31
|
+
$connection.exceptions = false
|
32
|
+
|
33
|
+
status = 255
|
34
|
+
|
32
35
|
command = object[1]
|
36
|
+
command.collect! { |a| a.to_s }
|
37
|
+
|
33
38
|
script_name = File.basename(command[0])
|
34
|
-
|
39
|
+
|
35
40
|
local_path = `mktemp -t #{script_name.gsub(/[^a-z]/i, '')}.XXXX`.chomp
|
36
|
-
|
41
|
+
|
37
42
|
File.open(local_path, 'w') { |fp| fp.write(object[2]) }
|
38
43
|
system('chmod', '+x', local_path)
|
39
|
-
|
44
|
+
|
40
45
|
pid = fork do
|
41
46
|
command[0] = local_path
|
42
47
|
exec *command
|
43
48
|
end
|
44
|
-
|
49
|
+
|
45
50
|
# Clean up the script after execution:
|
46
51
|
pid, result = Process.wait2(pid)
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
status = result.exitstatus
|
53
|
+
system('rm', '-f', local_path)
|
54
|
+
|
55
|
+
exit!(status)
|
50
56
|
when :exec
|
51
57
|
# [:exec, :command]
|
58
|
+
$connection.exceptions = false
|
59
|
+
|
52
60
|
command = object[1]
|
53
|
-
|
61
|
+
|
54
62
|
exec *command
|
63
|
+
|
64
|
+
# If the above command failed, the exit status is 255
|
65
|
+
exit!(255)
|
55
66
|
end
|
56
67
|
end
|
data/lib/lsync/controller.rb
CHANGED
@@ -18,7 +18,38 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require 'rexec/task'
|
22
|
+
|
21
23
|
module LSync
|
24
|
+
|
25
|
+
def self.log_task(task, logger)
|
26
|
+
pipes = [task.output, task.error]
|
27
|
+
|
28
|
+
while pipes.size > 0
|
29
|
+
result = IO.select(pipes)
|
30
|
+
|
31
|
+
result[0].each do |pipe|
|
32
|
+
if pipe.closed? || pipe.eof?
|
33
|
+
pipes.delete(pipe)
|
34
|
+
next
|
35
|
+
end
|
36
|
+
|
37
|
+
if pipe == task.output
|
38
|
+
logger.info pipe.readline.chomp
|
39
|
+
elsif pipe == task.error
|
40
|
+
logger.error pipe.readline.chomp
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.log_error(error, logger)
|
47
|
+
logger.error "Error #{error.class.name}: #{error}"
|
48
|
+
error.backtrace.each do |where|
|
49
|
+
logger.error "\t#{where}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
22
53
|
class BasicController
|
23
54
|
def initialize(script, logger)
|
24
55
|
@script = script
|
@@ -60,6 +91,10 @@ module LSync
|
|
60
91
|
connection, task = @server.shell.connect(@server)
|
61
92
|
connection.send_object([:chdir, root])
|
62
93
|
|
94
|
+
# Convert all arguments to strings for execution.
|
95
|
+
# For some reason, the parent process hangs if you don't have this.. need to investigate further.
|
96
|
+
command = command.collect{|arg| arg.to_s}
|
97
|
+
|
63
98
|
if options[:script]
|
64
99
|
data = command[0]
|
65
100
|
|
@@ -73,16 +108,16 @@ module LSync
|
|
73
108
|
command[0] = "script"
|
74
109
|
end
|
75
110
|
|
76
|
-
@logger.info "Running script #{command.
|
111
|
+
@logger.info "Running script #{command.to_cmd} on #{@server}"
|
77
112
|
|
78
113
|
connection.send_object([:script, command, data])
|
79
114
|
elsif options[:remote]
|
80
|
-
@logger.info "Running script #{command.
|
115
|
+
@logger.info "Running script #{command.to_cmd} on #{@server}"
|
81
116
|
|
82
117
|
data = File.read(command[0])
|
83
118
|
connection.send_object([:script, command, data])
|
84
119
|
else
|
85
|
-
@logger.info "Running command #{command.
|
120
|
+
@logger.info "Running command #{command.to_cmd} on #{@server}"
|
86
121
|
connection.send_object([:exec, command])
|
87
122
|
end
|
88
123
|
|
@@ -93,8 +128,12 @@ module LSync
|
|
93
128
|
end
|
94
129
|
ensure
|
95
130
|
if task
|
96
|
-
task.stop
|
97
|
-
task.wait
|
131
|
+
# task.stop
|
132
|
+
result = task.wait
|
133
|
+
|
134
|
+
unless result.exitstatus == 0
|
135
|
+
raise CommandFailure.new(command, result.exitstatus)
|
136
|
+
end
|
98
137
|
end
|
99
138
|
end
|
100
139
|
end
|
@@ -105,7 +144,7 @@ module LSync
|
|
105
144
|
command = @server.shell.connection_command(@server) + ["--"] + command
|
106
145
|
end
|
107
146
|
|
108
|
-
@logger.
|
147
|
+
@logger.info "Executing #{command.to_cmd} on #{@server}"
|
109
148
|
RExec::Task.open(command, options, &block)
|
110
149
|
end
|
111
150
|
|
@@ -116,7 +155,7 @@ module LSync
|
|
116
155
|
result = task.wait
|
117
156
|
|
118
157
|
unless result.exitstatus == 0
|
119
|
-
raise
|
158
|
+
raise CommandFailure.new(command, result.exitstatus)
|
120
159
|
end
|
121
160
|
|
122
161
|
return task.output.read
|
data/lib/lsync/error.rb
CHANGED
@@ -44,9 +44,9 @@ module LSync
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# Indicates that a backup action shell script has failed.
|
47
|
-
class
|
48
|
-
def initialize(
|
49
|
-
super("
|
47
|
+
class CommandFailure < Error
|
48
|
+
def initialize(command, status)
|
49
|
+
super("Command #{command.inspect} failed with exit status #{status}", :command => command, :status => status)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
data/lib/lsync/event_handler.rb
CHANGED
data/lib/lsync/method.rb
CHANGED
data/lib/lsync/methods/rsync.rb
CHANGED
@@ -23,8 +23,42 @@ require 'lsync/method'
|
|
23
23
|
module LSync
|
24
24
|
module Methods
|
25
25
|
|
26
|
+
# RSync Exit Codes as of 2011:
|
27
|
+
|
28
|
+
# 0 Success
|
29
|
+
# 1 Syntax or usage error
|
30
|
+
# 2 Protocol incompatibility
|
31
|
+
# 3 Errors selecting input/output files, dirs
|
32
|
+
# 4 Requested action not supported: an attempt was made to manipulate 64-bit files on a platform
|
33
|
+
# that cannot support them; or an option was specified that is supported by the client and not by the server.
|
34
|
+
# 5 Error starting client-server protocol
|
35
|
+
# 6 Daemon unable to append to log-file
|
36
|
+
# 10 Error in socket I/O
|
37
|
+
# 11 Error in file I/O
|
38
|
+
# 12 Error in rsync protocol data stream
|
39
|
+
# 13 Errors with program diagnostics
|
40
|
+
# 14 Error in IPC code
|
41
|
+
# 20 Received SIGUSR1 or SIGINT
|
42
|
+
# 21 Some error returned by waitpid()
|
43
|
+
# 22 Error allocating core memory buffers
|
44
|
+
# 23 Partial transfer due to error
|
45
|
+
# 24 Partial transfer due to vanished source files
|
46
|
+
# 25 The --max-delete limit stopped deletions
|
47
|
+
# 30 Timeout in data send/receive
|
48
|
+
# 35 Timeout waiting for daemon connection
|
49
|
+
|
26
50
|
class RSync < Method
|
27
|
-
|
51
|
+
def initialize(direction, options = {})
|
52
|
+
super(options)
|
53
|
+
|
54
|
+
@direction = direction
|
55
|
+
@command = options[:command] || "rsync"
|
56
|
+
|
57
|
+
@options = options
|
58
|
+
@connection = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
28
62
|
|
29
63
|
def connect_arguments (local_server, remote_server)
|
30
64
|
# RSync -e option simply appends the hostname. There is no way to control this behaviour.
|
@@ -39,56 +73,60 @@ module LSync
|
|
39
73
|
return ['-e', command.to_cmd]
|
40
74
|
end
|
41
75
|
|
42
|
-
|
43
|
-
|
44
|
-
def initialize(direction, options = {})
|
45
|
-
super(options)
|
46
|
-
|
47
|
-
@direction = direction
|
48
|
-
@command = options[:command] || "rsync"
|
49
|
-
|
50
|
-
@options = options
|
51
|
-
@connection = nil
|
52
|
-
end
|
53
|
-
|
54
|
-
def run(controller)
|
55
|
-
directory = controller.directory
|
56
|
-
arguments = (@options[:arguments] || ["--archive"]) + (directory.options[:arguments] || [])
|
57
|
-
|
76
|
+
def configuration(controller, source_directory, destination_directory)
|
58
77
|
local_server = nil
|
59
78
|
remote_server = nil
|
79
|
+
source = nil
|
80
|
+
destination = nil
|
60
81
|
|
61
82
|
if @direction == :push
|
62
83
|
local_server = controller.master
|
63
84
|
remote_server = controller.target
|
64
85
|
|
65
|
-
destination = remote_server.connection_string(
|
66
|
-
source = local_server.full_path(
|
86
|
+
destination = remote_server.connection_string(destination_directory)
|
87
|
+
source = local_server.full_path(source_directory)
|
67
88
|
else
|
68
89
|
local_server = controller.target
|
69
90
|
remote_server = controller.master
|
70
91
|
|
71
|
-
source = remote_server.connection_string(
|
72
|
-
destination = local_server.full_path(
|
92
|
+
source = remote_server.connection_string(source_directory)
|
93
|
+
destination = local_server.full_path(destination_directory)
|
73
94
|
end
|
95
|
+
|
96
|
+
return local_server, remote_server, source, destination
|
97
|
+
end
|
98
|
+
|
99
|
+
public
|
100
|
+
|
101
|
+
def run(controller)
|
102
|
+
directory = controller.directory
|
103
|
+
arguments = (@options[:arguments] || ["--archive"]) + (directory.options[:arguments] || [])
|
104
|
+
|
105
|
+
local_server, remote_server, source, destination = configuration(controller, controller.directory, controller.directory)
|
74
106
|
|
75
107
|
arguments += connect_arguments(local_server, remote_server)
|
76
108
|
|
77
109
|
# Create the destination backup directory
|
78
110
|
controller.target.exec!(["mkdir", "-p", controller.target.full_path(directory.path)])
|
79
111
|
|
80
|
-
controller
|
81
|
-
|
82
|
-
|
112
|
+
run_handler(controller, local_server, source, destination, arguments)
|
113
|
+
end
|
114
|
+
|
115
|
+
def run_handler(controller, local_server, source, destination, arguments)
|
116
|
+
command = [@command] + arguments + [source, destination]
|
117
|
+
|
118
|
+
local_server.exec(command) do |task|
|
119
|
+
LSync::log_task(task, controller.logger)
|
120
|
+
|
121
|
+
result = task.wait
|
122
|
+
|
123
|
+
# Exit status 24 means that some files were deleted between indexing the data and copying it.
|
124
|
+
unless result.exitstatus == 0 || result.exitstatus == 24
|
83
125
|
raise BackupMethodError.new("Backup from #{source} to #{destination} failed.", :method => self)
|
84
126
|
end
|
85
127
|
end
|
86
128
|
end
|
87
129
|
|
88
|
-
def run_handler(controller, source, destination, arguments)
|
89
|
-
run_command(controller, [@command] + arguments + [source, destination])
|
90
|
-
end
|
91
|
-
|
92
130
|
def should_run?(controller)
|
93
131
|
if @direction == :push
|
94
132
|
return controller.current == controller.master
|
@@ -98,10 +136,6 @@ module LSync
|
|
98
136
|
return false
|
99
137
|
end
|
100
138
|
end
|
101
|
-
|
102
|
-
def run_command(controller, command)
|
103
|
-
return LSync.run_command("/", command, controller.logger) == 0
|
104
|
-
end
|
105
139
|
end
|
106
140
|
|
107
141
|
class RSyncSnapshot < RSync
|
@@ -118,33 +152,14 @@ module LSync
|
|
118
152
|
|
119
153
|
destination_directory = File.join(inprogress_path, directory.path)
|
120
154
|
|
121
|
-
local_server =
|
122
|
-
remote_server = nil
|
123
|
-
|
124
|
-
if @direction == :push
|
125
|
-
local_server = controller.master
|
126
|
-
remote_server = controller.target
|
127
|
-
|
128
|
-
destination = remote_server.connection_string(destination_directory)
|
129
|
-
source = local_server.full_path(directory)
|
130
|
-
else
|
131
|
-
local_server = controller.target
|
132
|
-
remote_server = controller.master
|
133
|
-
|
134
|
-
destination = local_server.full_path(destination_directory)
|
135
|
-
source = remote_server.connection_string(directory)
|
136
|
-
end
|
155
|
+
local_server, remote_server, source, destination = configuration(controller, controller.directory, destination_directory)
|
137
156
|
|
138
157
|
arguments += connect_arguments(local_server, remote_server)
|
139
158
|
|
140
159
|
# Create the destination backup directory
|
141
160
|
controller.target.exec!(["mkdir", "-p", controller.target.full_path(destination_directory)])
|
142
161
|
|
143
|
-
|
144
|
-
if run_handler(controller, source, destination, arguments) == false
|
145
|
-
raise BackupMethodError.new("Backup from #{source} to #{destination} failed.", :method => self)
|
146
|
-
end
|
147
|
-
end
|
162
|
+
run_handler(controller, local_server, source, destination, arguments)
|
148
163
|
end
|
149
164
|
end
|
150
165
|
|
data/lib/lsync/script.rb
CHANGED
@@ -52,35 +52,51 @@ module LSync
|
|
52
52
|
end
|
53
53
|
|
54
54
|
# Given a name, find out which server config matches it
|
55
|
-
def find_named_server
|
55
|
+
def find_named_server(name)
|
56
56
|
if @servers.key? name
|
57
57
|
return @servers[name]
|
58
58
|
else
|
59
59
|
hostname = Socket.gethostbyname(name)[0] rescue name
|
60
60
|
return @servers.values.find { |s| s.host == hostname }
|
61
61
|
end
|
62
|
+
|
63
|
+
# No server was found for this name
|
64
|
+
return nil
|
62
65
|
end
|
63
66
|
|
64
67
|
alias :[] :find_named_server
|
65
68
|
|
66
69
|
# Find the master server based on the name #master= specified
|
67
70
|
def find_master_server
|
68
|
-
find_named_server(@master)
|
71
|
+
server = find_named_server(@master)
|
72
|
+
|
73
|
+
# At this point we must know the current server or we can't continue
|
74
|
+
if server == nil
|
75
|
+
raise ScriptError.new("Could not determine master server!", :script => self, :name => @master)
|
76
|
+
end
|
77
|
+
|
78
|
+
return server
|
69
79
|
end
|
70
80
|
|
71
|
-
# Find
|
81
|
+
# Find the server that matches the current machine
|
72
82
|
def find_current_server
|
73
83
|
master = find_master_server
|
74
84
|
server = nil
|
75
85
|
|
76
|
-
#
|
77
|
-
if master
|
86
|
+
# There might be the case that the the local machine is both the master server and the backup server..
|
87
|
+
# thus we check first if the master server is the local machine:
|
88
|
+
if master.local?
|
78
89
|
server = master
|
79
90
|
else
|
80
91
|
# Find a server config that specifies the local host
|
81
92
|
server = @servers.values.find { |s| s.local? }
|
82
93
|
end
|
83
94
|
|
95
|
+
# At this point we must know the current server or we can't continue
|
96
|
+
if server == nil
|
97
|
+
raise ScriptError.new("Could not determine current server!", :script => self)
|
98
|
+
end
|
99
|
+
|
84
100
|
return server
|
85
101
|
end
|
86
102
|
|
@@ -144,11 +160,6 @@ module LSync
|
|
144
160
|
master = find_master_server
|
145
161
|
current = find_current_server
|
146
162
|
|
147
|
-
# At this point we must know the current server or we can't continue
|
148
|
-
if current == nil
|
149
|
-
raise ScriptError.new("Could not determine current server!", :script => self, :master => @master)
|
150
|
-
end
|
151
|
-
|
152
163
|
if master.local?
|
153
164
|
logger.info "We are the master server..."
|
154
165
|
else
|
@@ -158,8 +169,8 @@ module LSync
|
|
158
169
|
|
159
170
|
master_controller = ServerController.new(self, logger, master)
|
160
171
|
|
161
|
-
self.try do
|
162
|
-
method.try do
|
172
|
+
self.try(master_controller) do
|
173
|
+
method.try(master_controller) do
|
163
174
|
logger.info "Running backups for server #{current}..."
|
164
175
|
|
165
176
|
run_backups!(master, current, logger, options)
|
data/lib/lsync/tee_logger.rb
CHANGED
data/lib/lsync/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lsync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 7
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 2.3.
|
9
|
+
- 2
|
10
|
+
version: 2.3.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Samuel Williams
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-08-
|
18
|
+
date: 2011-08-09 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: fingerprint
|
@@ -41,12 +41,12 @@ dependencies:
|
|
41
41
|
requirements:
|
42
42
|
- - ">="
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
hash:
|
44
|
+
hash: 5
|
45
45
|
segments:
|
46
46
|
- 1
|
47
47
|
- 4
|
48
|
-
-
|
49
|
-
version: 1.4.
|
48
|
+
- 1
|
49
|
+
version: 1.4.1
|
50
50
|
type: :runtime
|
51
51
|
version_requirements: *id002
|
52
52
|
description:
|
@@ -71,7 +71,6 @@ files:
|
|
71
71
|
- lib/lsync/event_timer.rb
|
72
72
|
- lib/lsync/method.rb
|
73
73
|
- lib/lsync/methods/rsync.rb
|
74
|
-
- lib/lsync/run.rb
|
75
74
|
- lib/lsync/script.rb
|
76
75
|
- lib/lsync/server.rb
|
77
76
|
- lib/lsync/shell.rb
|
data/lib/lsync/run.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
# Copyright (c) 2007, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
20
|
-
|
21
|
-
require 'rexec/task'
|
22
|
-
|
23
|
-
module LSync
|
24
|
-
|
25
|
-
def self.log_task(task, logger)
|
26
|
-
pipes = [task.output, task.error]
|
27
|
-
|
28
|
-
while pipes.size > 0
|
29
|
-
result = IO.select(pipes)
|
30
|
-
|
31
|
-
result[0].each do |pipe|
|
32
|
-
if pipe.closed? || pipe.eof?
|
33
|
-
pipes.delete(pipe)
|
34
|
-
next
|
35
|
-
end
|
36
|
-
|
37
|
-
if pipe == task.output
|
38
|
-
logger.info pipe.readline.chomp
|
39
|
-
elsif pipe == task.error
|
40
|
-
logger.error pipe.readline.chomp
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# Run a specific command and output the results to the given logger.
|
47
|
-
def self.run_command(root, command, logger)
|
48
|
-
Dir.chdir(root) do
|
49
|
-
logger.info "Running #{command.inspect} in #{Dir.getwd.inspect}"
|
50
|
-
|
51
|
-
process_result = RExec::Task.open(command) do |task|
|
52
|
-
log_task(task, logger)
|
53
|
-
end
|
54
|
-
|
55
|
-
return process_result
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.run_remote_command(root, connection_command, command, logger)
|
60
|
-
logger.info "Running remote command #{command.inspect} in #{root}"
|
61
|
-
|
62
|
-
process_result = RExec::Task.open(connection_command) do |connection|
|
63
|
-
connection.puts(["cd", root].to_cmd)
|
64
|
-
connection.puts((["exec"] + command).to_cmd)
|
65
|
-
|
66
|
-
LSync::log_task(connection, logger)
|
67
|
-
end
|
68
|
-
|
69
|
-
return process_result
|
70
|
-
end
|
71
|
-
end
|