rolo 1.0.2

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.
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: []