lsync 2.3.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/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
|