lsync 2.3.1 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -39,6 +39,10 @@ module LSync
39
39
  def self.run_script(options = {}, &block)
40
40
  script = LSync::Script.new(options, &block)
41
41
 
42
+ script.on(:failure) do |error|
43
+ LSync::log_error(error, logger)
44
+ end
45
+
42
46
  script.run!
43
47
  end
44
48
 
@@ -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
- # system('rm', '-f', local_path)
48
-
49
- exit!(result.exitstatus)
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
@@ -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.inspect} on #{@server}"
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.inspect} on #{@server}"
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.inspect} on #{@server}"
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.debug "Executing #{command.inspect} on #{@server}"
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 ShellScriptError.new("Command #{command.inspect} failed: #{result.exitstatus}", result.exitstatus)
158
+ raise CommandFailure.new(command, result.exitstatus)
120
159
  end
121
160
 
122
161
  return task.output.read
@@ -44,9 +44,9 @@ module LSync
44
44
  end
45
45
 
46
46
  # Indicates that a backup action shell script has failed.
47
- class ShellScriptError < Error
48
- def initialize(script, return_code)
49
- super("Shell script #{script} failed", :return_code => return_code)
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
@@ -42,7 +42,7 @@ module LSync
42
42
  handled = true
43
43
 
44
44
  if scope
45
- scope.instance_eval &handler
45
+ scope.instance_exec *args, &handler
46
46
  else
47
47
  handler.call
48
48
  end
@@ -20,7 +20,6 @@
20
20
 
21
21
  require 'fileutils'
22
22
  require 'pathname'
23
- require 'lsync/run'
24
23
  require 'lsync/event_handler'
25
24
 
26
25
  module LSync
@@ -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
- protected
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
- public
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(directory)
66
- source = local_server.full_path(directory)
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(directory)
72
- destination = local_server.full_path(directory)
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.logger.info "In directory #{Dir.getwd}..."
81
- Dir.chdir(local_server.root) do
82
- if run_handler(controller, source, destination, arguments) == false
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 = nil
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
- Dir.chdir(local_server.root) do
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
 
@@ -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 name
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 out the config section for the current server
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
- # Find out if the master server is local...
77
- if master && master.local?
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)
@@ -58,7 +58,7 @@ module LSync
58
58
  @start_time ||= time
59
59
  diff = time - @start_time
60
60
 
61
- "[T+#{sprintf('%0.1f', diff).ljust(5)} #{severity.rjust(5)}] #{msg}\n"
61
+ "[T+#{sprintf('%0.1f', diff).ljust(6)} #{severity.rjust(5)}] #{msg}\n"
62
62
  end
63
63
  end
64
64
  end
@@ -22,7 +22,7 @@ module LSync
22
22
  module VERSION
23
23
  MAJOR = 2
24
24
  MINOR = 3
25
- TINY = 1
25
+ TINY = 2
26
26
 
27
27
  STRING = [MAJOR, MINOR, TINY].join('.')
28
28
  end
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: 1
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
8
  - 3
9
- - 1
10
- version: 2.3.1
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-08 00:00:00 Z
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: 7
44
+ hash: 5
45
45
  segments:
46
46
  - 1
47
47
  - 4
48
- - 0
49
- version: 1.4.0
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
@@ -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