rolo 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.md +111 -0
  2. data/bin/rolo +144 -0
  3. 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: []