rolo 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +111 -0
- data/bin/rolo +144 -0
- metadata +47 -0
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
## NAME
|
2
|
+
|
3
|
+
`rolo.rb` -- Prevents a program from running more than one copy at a time
|
4
|
+
|
5
|
+
## SYNOPSIS
|
6
|
+
|
7
|
+
<pre>
|
8
|
+
$0 [-v] [--test] [-a address] -p port command [arguments]
|
9
|
+
</pre>
|
10
|
+
|
11
|
+
## DESCRIPTION
|
12
|
+
|
13
|
+
`rolo.rb` prevents a program from running more than one copy at a time;
|
14
|
+
it is useful with cron to make sure that a job doesn't run before a
|
15
|
+
previous one has finished. `robo.rb` is a ruby version of Timothy
|
16
|
+
program `solo` with more options.
|
17
|
+
|
18
|
+
## OPTIONS
|
19
|
+
|
20
|
+
* `-v` (`--verbose`)
|
21
|
+
Print verbose message
|
22
|
+
* `-t` (`--test`)
|
23
|
+
Test of program is running. Don't execute command.
|
24
|
+
* `-a` (`--address`)
|
25
|
+
Address to check / listen on. By default, this address is
|
26
|
+
`127.x.y.1:<port>` where `x.y` is translated from process's user
|
27
|
+
identity number and this allows two different users on the system
|
28
|
+
can use the same port with `rolo.rb`
|
29
|
+
* `-p` (`--port`)
|
30
|
+
Port to check / on which rolo will listen
|
31
|
+
|
32
|
+
In `<command>` and `<arguments>`, you can use `%address`, `%port` which
|
33
|
+
are replaced by the socket address and port that the problem uses to
|
34
|
+
check for status of your command. This is very useful if your command
|
35
|
+
closes all file descriptors at the time it runs, but it has some ways
|
36
|
+
to listen on `%address:%port`. See EXAMPLE for details.
|
37
|
+
|
38
|
+
## HOW IT WORKS
|
39
|
+
|
40
|
+
Before starting your `<command>` (using `exec`), `rolo.rb` will open a
|
41
|
+
socket on a local address (or address specified by option `--address`.)
|
42
|
+
This socket will be closed after your command exits, and as long as
|
43
|
+
your command is running, we have a chance to check its status by
|
44
|
+
checking the status of this socket. If it is still open when `rolo.rb`
|
45
|
+
is invoked, `rolo.rb` exits without invoking a new instance of command.
|
46
|
+
|
47
|
+
However, if your `<command>` closes all file descriptors at the time it
|
48
|
+
is executed, `rolo.rb` will be sucked. (See `EXAMPLE` for details and for
|
49
|
+
a trick when using `rolo.rb` with `ssh`.) If that the cases, you may
|
50
|
+
use the option `--address` and `--port` to specify a socket that your
|
51
|
+
command binds on.
|
52
|
+
|
53
|
+
## EXAMPLE
|
54
|
+
|
55
|
+
To create tunnel to a remote server, you can use this ssh command
|
56
|
+
|
57
|
+
<pre>
|
58
|
+
ssh -fN remote -L localhost:1234:localhost:10000
|
59
|
+
</pre>
|
60
|
+
|
61
|
+
This allows you to connect to the local port 1234 on your mahince
|
62
|
+
as same as conneting to address `localhost:10000` on remote server.
|
63
|
+
The process `ssh` will go to background immediately after it authenticates
|
64
|
+
successfully with the remote.
|
65
|
+
|
66
|
+
To keep this tunnel persistent, you can add this to your crontab
|
67
|
+
|
68
|
+
<pre>
|
69
|
+
rolo.rb -p 4567 \
|
70
|
+
ssh remote -fNL localhost:1234:localhost:10000
|
71
|
+
</pre>
|
72
|
+
|
73
|
+
and allows this line to be executed once every 5 minutes. `rolo.rb`
|
74
|
+
will check if your ssh command is still running. If 'yes', it will
|
75
|
+
simply exit; if 'no', `rolo.rb` will start the ssh command.
|
76
|
+
|
77
|
+
However, if you use *OpenSSH 5.6p1* (or later), `ssh` will close all file
|
78
|
+
descriptors from the parent (except for `STDIN`, `STDOUT` and `STDERR`).
|
79
|
+
As the socket opened by `rolo.rb` is closed, `rolo.rb` will always
|
80
|
+
start new instance of the `ssh` tunnel. (Actually I had process `bomb`
|
81
|
+
on my system when I used the original program `solo` to launch my
|
82
|
+
tunnels.)
|
83
|
+
|
84
|
+
Fortunately, `ssh` has option to bind on the local address.
|
85
|
+
Using this option we can trick `rolo.rb` as below
|
86
|
+
<pre>
|
87
|
+
rolo.rb -p 4567 \
|
88
|
+
ssh remote -fN \
|
89
|
+
-L localhost:1234:localhost:10000 \
|
90
|
+
-L %address:%port:localhost:12345
|
91
|
+
</pre>
|
92
|
+
|
93
|
+
The last use of option `-L` will ask `ssh` to open a socket on
|
94
|
+
`%address:%port` (the real values will be provided by `rolo.rb`),
|
95
|
+
and it will be checked by `rolo.rb` in its next run. Please note that
|
96
|
+
we use a random port `12345` to prevent local connections to
|
97
|
+
`%address:%port` from being forwarded to remote.
|
98
|
+
|
99
|
+
Another way is to use option `--address`
|
100
|
+
|
101
|
+
<pre>
|
102
|
+
rolo.rb -p 1234 -a 127.0.0.1 \
|
103
|
+
ssh remote -fNL localhost:1234:localhost:10000
|
104
|
+
</pre>
|
105
|
+
|
106
|
+
And this is another way
|
107
|
+
|
108
|
+
<pre>
|
109
|
+
rolo.rb -p 1234 -a 127.0.0.1 \
|
110
|
+
ssh remote -fNL %address:%port:localhost:10000
|
111
|
+
</pre>
|
data/bin/rolo
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Purpose: Prevents a program from running more than one copy at a time
|
4
|
+
# Author : Anh K. Huynh
|
5
|
+
# License: GPL2
|
6
|
+
# Date : 2012 July 16th
|
7
|
+
# Source : http://github.com/icy/rolo
|
8
|
+
# Link : [1] Tim's solo: http://github.com/timkay/solo/
|
9
|
+
# [2] Fork-exec : http://en.wikipedia.org/wiki/Fork-exec
|
10
|
+
# [3] Unix fork : http://www-h.eng.cam.ac.uk/help/tpl/unix/fork.html
|
11
|
+
# [4] Fork-exec : http://jacktang.github.com/2009/01/04/ruby-fork-exec-socket-hang.html
|
12
|
+
# [5] ^F in Perl: http://perldoc.perl.org/perlvar.html#%24^F
|
13
|
+
# [6] RubyPaint : http://ruby.runpaint.org/io
|
14
|
+
# [7] Secure FD : http://udrepper.livejournal.com/20407.html
|
15
|
+
# [8] OpenSSH : http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-5.9p1.tar.gz
|
16
|
+
# [9] closefrom : http://www.unix.com/man-page/All/3c/closefrom/
|
17
|
+
#
|
18
|
+
# Note [5]:
|
19
|
+
# The maximum system file descriptor, ordinarily 2. System file descrip-
|
20
|
+
# tors are passed to exec()ed processes, while higher file descriptors
|
21
|
+
# are not. Also, during an open(), system file descriptors are preserved
|
22
|
+
# even if the open() fails (ordinary file descriptors are closed before
|
23
|
+
# the open() is attempted). The close-on-exec status of a file descrip-
|
24
|
+
# tor will be decided according to the value of $^F when the correspon-
|
25
|
+
# ding file, pipe, or socket was opened, not the time of the exec().
|
26
|
+
#
|
27
|
+
# Note [6]:
|
28
|
+
# On a Unix-based system a process created by Kernel.exec, Kernel.fork,
|
29
|
+
# or IO.popen inherits the file descriptors of its parent. Depending on
|
30
|
+
# the application, this may constitute an information leak in that the
|
31
|
+
# child is able to access data that he shouldn’t have access to. If
|
32
|
+
# given a true argument, IO#close_on_exec= ensures that its receiver
|
33
|
+
# is closed before the new process is created; otherwise, it does not.
|
34
|
+
# IO#close_on_exec? returns the status of this flag as either true or
|
35
|
+
# false. On systems that don’t support this feature, these methods
|
36
|
+
# raise NotImplementedError.
|
37
|
+
#
|
38
|
+
# Note [8]:
|
39
|
+
# In the source code of OpenSSH 5.9p1, ssh.c::268 we can see that all
|
40
|
+
# file descriptors from (STDERR_FILENO + 1) will be closed by the
|
41
|
+
# function (closefrom) (see [9].) This may cause problem!
|
42
|
+
#
|
43
|
+
# /*
|
44
|
+
# * Discard other fds that are hanging around. These can cause problem
|
45
|
+
# * with backgrounded ssh processes started by ControlPersist.
|
46
|
+
# */
|
47
|
+
# closefrom(STDERR_FILENO + 1);
|
48
|
+
#
|
49
|
+
# On my system (ArchLinux, 3.3.8), (closefrom) is not found. The method
|
50
|
+
# should be used from (openbsd-compact/bsd-closefrom.c).s
|
51
|
+
#
|
52
|
+
# This behavior is implemented since OpenSSH 5.6p1.
|
53
|
+
#
|
54
|
+
|
55
|
+
require 'socket'
|
56
|
+
|
57
|
+
class String
|
58
|
+
def die(ret = 1, dev = STDERR, prefix = ":: ")
|
59
|
+
dev.puts("#{prefix}#{self}")
|
60
|
+
exit(ret)
|
61
|
+
end
|
62
|
+
|
63
|
+
def verbose(v = false)
|
64
|
+
STDERR.puts(":: #{self}") if v
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Based on the original code taken from from [4]
|
69
|
+
def close_on_exec(att = true)
|
70
|
+
ObjectSpace.each_object(IO) do |io|
|
71
|
+
begin
|
72
|
+
if io.respond_to?(:close_on_exec?)
|
73
|
+
io.close_on_exec = attr
|
74
|
+
else
|
75
|
+
io.fcntl(Fcntl::F_SETFD, attr ? Fcntl::FD_CLOEXEC : 0)
|
76
|
+
end
|
77
|
+
rescue ::Exception => err
|
78
|
+
end unless [STDIN, STDOUT, STDERR].include?(io) or io.closed?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
OPTIONS = {:verbose => false, :port => 0}
|
83
|
+
|
84
|
+
"Syntax: rolo [--verbose] [--test] [--address <address>] --port <port_number> <command> [<arguments>]".die if ARGV.empty?
|
85
|
+
|
86
|
+
while true
|
87
|
+
f = ARGV.first
|
88
|
+
if %w{-p --port}.include?(f)
|
89
|
+
ARGV.shift
|
90
|
+
OPTIONS[:port] = ARGV.shift.to_s.to_i
|
91
|
+
"Port must be a positive number".die if OPTIONS[:port] == 0
|
92
|
+
elsif %w{-v --verbose}.include?(f)
|
93
|
+
OPTIONS[:verbose] = true
|
94
|
+
ARGV.shift
|
95
|
+
elsif %w{-a --address}.include?(f)
|
96
|
+
ARGV.shift
|
97
|
+
OPTIONS[:address] = ARGV.shift.to_s.strip
|
98
|
+
"Invalid address '#{OPTIONS[:address]}' was provided. Should be in format 'x.y.z.t'".die \
|
99
|
+
unless OPTIONS[:address].match(/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/)
|
100
|
+
elsif %w{-m --message}.include?(f)
|
101
|
+
ARGV.shift
|
102
|
+
OPTIONS[:message] = ARGV.shift
|
103
|
+
elsif %w{-t --test}.include?(f)
|
104
|
+
OPTIONS[:test] = true
|
105
|
+
ARGV.shift
|
106
|
+
elsif %w{--}.include?(f)
|
107
|
+
ARGV.shift
|
108
|
+
break
|
109
|
+
else
|
110
|
+
break
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
"Port must be a positive number".die if OPTIONS[:port] == 0
|
115
|
+
|
116
|
+
cmd = ARGV.shift.to_s
|
117
|
+
"You must provide a command".die if cmd.empty?
|
118
|
+
cmd = "#{cmd} #{ARGV.join(' ')}" unless ARGV.empty?
|
119
|
+
|
120
|
+
address = OPTIONS[:address] || [127, Process.uid, 1].pack("CnC").unpack("C4").join(".")
|
121
|
+
|
122
|
+
cmd = cmd.gsub("%port", OPTIONS[:port].to_s).gsub("%address", address)
|
123
|
+
|
124
|
+
("Will bind on %s:%d, command = '%s'" \
|
125
|
+
% [address, OPTIONS[:port], cmd]).verbose(OPTIONS[:verbose])
|
126
|
+
|
127
|
+
# Taken from example in the source code documetation
|
128
|
+
# Link: http://ruby-doc.org/stdlib-1.8.7/
|
129
|
+
# libdoc/socket/rdoc/Socket.html#method-i-bind
|
130
|
+
begin
|
131
|
+
include Socket::Constants
|
132
|
+
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
|
133
|
+
socket.bind(Socket.pack_sockaddr_in(OPTIONS[:port], address))
|
134
|
+
socket.close if OPTIONS[:address]
|
135
|
+
rescue Errno::EADDRINUSE
|
136
|
+
(OPTIONS[:message] || ":: Address is in use. Is your application running?").die(0, STDOUT, "")
|
137
|
+
rescue => e
|
138
|
+
e.to_s.die(1)
|
139
|
+
end
|
140
|
+
|
141
|
+
unless OPTIONS[:test]
|
142
|
+
close_on_exec(false)
|
143
|
+
exec cmd
|
144
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rolo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Anh K. Huynh
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-20 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Prevents a program from running more than one copy at a time
|
15
|
+
email: kyanh@viettug.org
|
16
|
+
executables:
|
17
|
+
- rolo
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.md
|
22
|
+
- bin/rolo
|
23
|
+
homepage: https://github.com/icy/rolo
|
24
|
+
licenses: []
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ! '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 1.8.16
|
44
|
+
signing_key:
|
45
|
+
specification_version: 3
|
46
|
+
summary: ! '`rolo` prevents a program from running more than one copy at a time'
|
47
|
+
test_files: []
|