rexec 1.1.10

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.
@@ -0,0 +1,14 @@
1
+ = RExec
2
+
3
+ Author:: Samuel Williams (http://www.oriontransfer.co.nz/)
4
+ Copyright:: Copyright (C) 2009, 2010 Samuel Williams
5
+ License:: GPLv3
6
+
7
+ RExec stands for Ruby Execute or Remote Execute (depending on how you use it). It provides a number of different things to assist with running Ruby code:
8
+
9
+ * A framework to send Ruby code to a remote server for execution
10
+ * A framework for writing command line daemons (i.e. <tt>start</tt>, <tt>restart</tt>, <tt>stop</tt>, <tt>status</tt>)
11
+ * A comprehensive <tt>Task</tt> class for launching tasks, managing input and output, exit status, etc
12
+ * Basic privilege management code for changing the processes owner
13
+ * A bunch of helpers for various different things (such as reading a file backwards)
14
+ * <tt>daemon-exec</tt> executable for running regular shell tasks in the background
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'rubygems'
19
+
20
+ require 'rexec'
21
+ require 'optparse'
22
+
23
+ OPTIONS = {
24
+ :out => "/tmp/daemon-exec.log",
25
+ :err => "/tmp/daemon-exec-error.log",
26
+ :in => "/dev/null",
27
+ :print_pid => false,
28
+ :root => "/",
29
+ :verbose => false,
30
+ :relocate => true,
31
+ :read_stdin => false,
32
+ }
33
+
34
+ ARGV.options do |o|
35
+ script_name = File.basename($0)
36
+
37
+ o.set_summary_indent("\t")
38
+ o.banner = "Usage: #{script_name} [-I stdin] [-O stdout] [-E stderr] [script/stdin]"
39
+ o.define_head "Copyright (c) 2010 Samuel Williams <http://www.oriontransfer.co.nz/>."
40
+
41
+ o.on("-d [dir]", String, "Daemons working path, default /") do |dir|
42
+ OPTIONS[:root] = dir
43
+ end
44
+
45
+ o.on("-s", "Don't attempt to relocate arguments to absolute paths") do
46
+ OPTIONS[:relocate] = false
47
+ end
48
+
49
+ o.define "File / Pipe Options:"
50
+
51
+ o.on("-I [path]", String, "File for STDIN, defaults to #{OPTIONS[:in]}; Note: Use -I - to send data from current STDIN") do |path|
52
+ OPTIONS[:in] = path
53
+ end
54
+
55
+ o.on("-O [path]", String, "File for STDOUT, defaults to #{OPTIONS[:out]}") do |path|
56
+ OPTIONS[:in] = path
57
+ end
58
+
59
+ o.on("-E [path]", String, "File for STDERR, defaults to #{OPTIONS[:err]}") do |path|
60
+ OPTIONS[:err] = path
61
+ end
62
+
63
+ o.define "Misc Options:"
64
+
65
+ o.on("-p", "Print out the PID of the forked process") do
66
+ OPTIONS[:print_pid] = true
67
+ end
68
+
69
+ o.on("-V", "Print verbose information about what is going on") do
70
+ OPTIONS[:verbose] = true
71
+ end
72
+
73
+ o.on("-h", "Show this help/version information and exit") do
74
+ puts o
75
+ exit 0
76
+ end
77
+ end.parse!
78
+
79
+ if OPTIONS[:relocate]
80
+ ARGV.collect! do |value|
81
+ if File.exist?(value)
82
+ File.expand_path(value)
83
+ else
84
+ value
85
+ end
86
+ end
87
+
88
+ [:in, :out, :err].each do |path|
89
+ OPTIONS[path] = File.expand_path(OPTIONS[path]) unless OPTIONS[path] == "-"
90
+ end
91
+ end
92
+
93
+ if OPTIONS[:verbose]
94
+ puts "Running #{ARGV.inspect}"
95
+ end
96
+
97
+ task_options = {
98
+ :daemonize => true,
99
+ :out => File.open(OPTIONS[:out], "a"),
100
+ :err => File.open(OPTIONS[:err], "a")
101
+ }
102
+
103
+ if OPTIONS[:in] == '-'
104
+ task_options[:passthrough] = [:in]
105
+ end
106
+
107
+ daemon = lambda do
108
+ Dir.chdir(OPTIONS[:root])
109
+ system("env", *ARGV)
110
+ end
111
+
112
+ task = RExec::Task.open(daemon, task_options)
113
+
114
+ if OPTIONS[:print_pid]
115
+ puts task.pid
116
+ end
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2007 Samuel Williams. Released under the GNU GPLv3.
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ # = Summary =
17
+ # This gem provides a very simple connection based API for communicating
18
+ # with remote instances of ruby. These can either be local, or remote, such
19
+ # as over SSH.
20
+ #
21
+ # The API is very simple and deals with sending and receiving objects using
22
+ # Marshal. One of the primary goals was to impose as little structure as
23
+ # possible on the end user of this library, while still maintaining a level
24
+ # of convenience.
25
+ #
26
+ # Author:: Samuel Williams (samuel AT oriontransfer DOT org)
27
+ # Copyright:: Copyright (c) 2009 Samuel Williams.
28
+ # License:: Released under the GNU GPLv3.
29
+
30
+ require 'rexec/version'
31
+ require 'rexec/connection'
32
+ require 'rexec/server'
33
+ require 'rexec/priviledges'
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2007 Samuel Williams. Released under the GNU GPLv3.
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ # This code is executed in a remote ruby process.
17
+
18
+ $stdout.sync = true
19
+ $stderr.sync = true
20
+
21
+ # We don't connect to $stderr here as this is a client. Clients write to regular $stderr.
22
+ $connection = RExec::Connection.new($stdin, $stdout)
23
+
@@ -0,0 +1,153 @@
1
+ # Copyright (c) 2007 Samuel Williams. Released under the GNU GPLv3.
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ # This class is as small and independant as possible as it will get sent to clients for execution.
17
+
18
+ require 'thread'
19
+
20
+ module RExec
21
+
22
+ # This class represents an abstract connection to another ruby process. The interface does not impose
23
+ # any structure on the way this communication link works, except for the fact you can send and receive
24
+ # objects. You can implement whatever kind of idiom you need for communication on top of this library.
25
+ #
26
+ # Depending on how you set things up, this can connect to a local ruby process, or a remote ruby process
27
+ # via SSH (for example).
28
+ class Connection
29
+ public
30
+
31
+ def self.build(process, options, &block)
32
+ cin = process.input
33
+ cout = process.output
34
+ cerr = process.error
35
+
36
+ # We require both cin and cout to be connected in order for connection to work
37
+ raise InvalidConnectionError.new("Input (#{cin}) or Output (#{cout}) is not connected!") unless cin and cout
38
+
39
+ yield cin
40
+
41
+ cin.puts("\004")
42
+
43
+ return self.new(cout, cin, cerr)
44
+ end
45
+
46
+ # Create a new connection. You need to supply a pipe for reading input, a pipe for sending output,
47
+ # and optionally a pipe for errors to be read from.
48
+ def initialize(input, output, error = nil)
49
+ @input = input
50
+ @output = output
51
+ @running = true
52
+
53
+ @error = error
54
+
55
+ @receive_mutex = Mutex.new
56
+ @send_mutex = Mutex.new
57
+ end
58
+
59
+ # The pipe used for reading data
60
+ def input
61
+ @input
62
+ end
63
+
64
+ # The pipe used for writing data
65
+ def output
66
+ @output
67
+ end
68
+
69
+ # The pipe used for receiving errors. On the client side this pipe is writable, on the server
70
+ # side this pipe is readable. You should avoid using it on the client side and simply use $stderr.
71
+ def error
72
+ @error
73
+ end
74
+
75
+ # Stop the connection, and close the output pipe.
76
+ def stop
77
+ @running = false
78
+ @output.close
79
+ end
80
+
81
+ # Return whether or not the connection is running.
82
+ def running?
83
+ @running
84
+ end
85
+
86
+ # This is a very simple runloop. It provides an object when it is received.
87
+ def run(&block)
88
+ while @running
89
+ pipes = IO.select([@input])
90
+
91
+ if pipes[0].size > 0
92
+ object = receive_object
93
+
94
+ if object == nil
95
+ @running = false
96
+ return
97
+ end
98
+
99
+ begin
100
+ yield object
101
+ rescue Exception => ex
102
+ send_object(ex)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ # Dump any text which has been written to $stderr in the child process.
109
+ def dump_errors(to = $stderr)
110
+ if @error and !@error.closed?
111
+ while true
112
+ result = IO.select([@error], [], [], 0)
113
+
114
+ break if result == nil
115
+
116
+ to.puts @error.readline.chomp
117
+ end
118
+ end
119
+ end
120
+
121
+ # Receive an object from the connection. This function is thread-safe. This function may block.
122
+ def receive_object
123
+ object = nil
124
+
125
+ @receive_mutex.synchronize do
126
+ begin
127
+ object = Marshal.load(@input)
128
+ rescue EOFError
129
+ object = nil
130
+ @running = false
131
+ end
132
+ end
133
+
134
+ if object and object.kind_of?(Exception)
135
+ raise object
136
+ end
137
+
138
+ return object
139
+ end
140
+
141
+ # Send object(s). This function is thread-safe.
142
+ def send_object(*objects)
143
+ @send_mutex.synchronize do
144
+ objects.each do |o|
145
+ data = Marshal.dump(o)
146
+ @output.write(data)
147
+ end
148
+
149
+ @output.flush
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,27 @@
1
+ # Copyright (c) 2007, 2009 Samuel Williams. Released under the GNU GPLv3.
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'rexec/daemon/base'
17
+
18
+ module RExec
19
+ module Daemon
20
+
21
+ # Would this kind of API be useful?
22
+ #def run_daemon(options = {}, &block)
23
+ #
24
+ #end
25
+
26
+ end
27
+ end
@@ -0,0 +1,151 @@
1
+ # Copyright (c) 2007, 2009 Samuel Williams. Released under the GNU GPLv3.
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'fileutils'
17
+ require 'rexec/daemon/controller'
18
+ require 'rexec/reverse_io'
19
+
20
+ module RExec
21
+ module Daemon
22
+ # This class is the base daemon class. If you are writing a daemon, you should inherit from this class.
23
+ class Base
24
+ @@var_directory = nil
25
+ @@log_directory = nil
26
+ @@pid_directory = nil
27
+
28
+ # Return the name of the daemon
29
+ def self.daemon_name
30
+ return name.gsub(/[^a-zA-Z0-9]+/, '-')
31
+ end
32
+
33
+ # Base directory for daemon log files / run files
34
+ def self.var_directory
35
+ @@var_directory || File.join("", "var")
36
+ end
37
+
38
+ # The directory the daemon will run in (Dir.chdir)
39
+ def self.working_directory
40
+ var_directory
41
+ end
42
+
43
+ # Return the directory to store log files in
44
+ def self.log_directory
45
+ @@log_directory || File.join(var_directory, "log", daemon_name)
46
+ end
47
+
48
+ # Standard log file for errors
49
+ def self.err_fn
50
+ File.join(log_directory, "stderr.log")
51
+ end
52
+
53
+ # Standard log file for normal output
54
+ def self.log_fn
55
+ File.join(log_directory, "stdout.log")
56
+ end
57
+
58
+ # Standard location of pid file
59
+ def self.pid_directory
60
+ @@pid_directory || File.join(var_directory, "run", daemon_name)
61
+ end
62
+
63
+ # Standard pid file
64
+ def self.pid_fn
65
+ File.join(pid_directory, "#{daemon_name}.pid")
66
+ end
67
+
68
+ # Mark the error log
69
+ def self.mark_err_log
70
+ fp = File.open(err_fn, "a")
71
+ fp.puts "=== Error Log Opened @ #{Time.now.to_s} ==="
72
+ fp.close
73
+ end
74
+
75
+ # Prints some information relating to daemon startup problems
76
+ def self.tail_err_log(outp)
77
+ lines = []
78
+
79
+ File.open(err_fn, "r") do |fp|
80
+ fp.seek_end
81
+
82
+ fp.reverse_each_line do |line|
83
+ lines << line
84
+ break if line.match("=== Error Log") || line.match("=== Daemon Exception Backtrace")
85
+ end
86
+ end
87
+
88
+ lines.reverse_each do |line|
89
+ outp.puts line
90
+ end
91
+ end
92
+
93
+ # Check the last few lines of the log file to find out if
94
+ # the daemon crashed.
95
+ def self.crashed?
96
+ File.open(err_fn, "r") do |fp|
97
+ fp.seek_end
98
+
99
+ count = 2
100
+ fp.reverse_each_line do |line|
101
+ return true if line.match("=== Daemon Crashed")
102
+
103
+ count -= 1
104
+
105
+ break if count == 0
106
+ end
107
+ end
108
+
109
+ return false
110
+ end
111
+
112
+ # Corresponds to controller method of the same name
113
+ def self.daemonize
114
+ Controller.daemonize(self)
115
+ end
116
+
117
+ # Corresponds to controller method of the same name
118
+ def self.start
119
+ Controller.start(self)
120
+ end
121
+
122
+ # Corresponds to controller method of the same name
123
+ def self.stop
124
+ Controller.stop(self)
125
+ end
126
+
127
+ # Corresponds to controller method of the same name
128
+ def self.status
129
+ Controller.status(self)
130
+ end
131
+
132
+ # The main function to setup any environment required by the daemon
133
+ def self.prefork
134
+ @@var_directory = File.expand_path(@@var_directory) if @@var_directory
135
+ @@log_directory = File.expand_path(@@log_directory) if @@log_directory
136
+ @@pid_directory = File.expand_path(@@pid_directory) if @@pid_directory
137
+
138
+ FileUtils.mkdir_p(log_directory)
139
+ FileUtils.mkdir_p(pid_directory)
140
+ end
141
+
142
+ # The main function to start the daemon
143
+ def self.run
144
+ end
145
+
146
+ # The main function to stop the daemon
147
+ def self.shutdown
148
+ end
149
+ end
150
+ end
151
+ end