rexec 1.1.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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