svdir 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 (56) hide show
  1. data/CHANGELOG +29 -0
  2. data/LICENSE +19 -0
  3. data/README +67 -0
  4. data/Rakefile +66 -0
  5. data/example/svctl +117 -0
  6. data/lib/sys/sv/statusbytes.rb +53 -0
  7. data/lib/sys/sv/svdir.rb +224 -0
  8. data/lib/sys/sv/util.rb +36 -0
  9. data/test/fixtures/PrefabSvDir.rb +22 -0
  10. data/test/fixtures/TempSvDir.rb +62 -0
  11. data/test/services/corrupt-normd/down +0 -0
  12. data/test/services/corrupt-normd/supervise/ok +0 -0
  13. data/test/services/corrupt-normd/supervise/status +0 -0
  14. data/test/services/corrupt-normu/supervise/ok +0 -0
  15. data/test/services/corrupt-normu/supervise/status +0 -0
  16. data/test/services/corrupt-zero-normd/down +0 -0
  17. data/test/services/corrupt-zero-normd/supervise/ok +0 -0
  18. data/test/services/corrupt-zero-normd/supervise/status +0 -0
  19. data/test/services/corrupt-zero-normu/supervise/ok +0 -0
  20. data/test/services/corrupt-zero-normu/supervise/status +0 -0
  21. data/test/services/down-0-normd-nowant/down +0 -0
  22. data/test/services/down-0-normd-nowant/supervise/ok +0 -0
  23. data/test/services/down-0-normd-nowant/supervise/status +0 -0
  24. data/test/services/down-0-normu-nowant/supervise/ok +0 -0
  25. data/test/services/down-0-normu-nowant/supervise/status +0 -0
  26. data/test/services/up-12526-normu-wantd/supervise/ok +0 -0
  27. data/test/services/up-12526-normu-wantd/supervise/status +0 -0
  28. data/test/services/up-12581-normd-wantd-paused/down +0 -0
  29. data/test/services/up-12581-normd-wantd-paused/supervise/ok +0 -0
  30. data/test/services/up-12581-normd-wantd-paused/supervise/status +0 -0
  31. data/test/services/up-12581-normu-wantd-paused/supervise/ok +0 -0
  32. data/test/services/up-12581-normu-wantd-paused/supervise/status +0 -0
  33. data/test/services/up-12816-normd-nowant/down +0 -0
  34. data/test/services/up-12816-normd-nowant/supervise/ok +0 -0
  35. data/test/services/up-12816-normd-nowant/supervise/status +0 -0
  36. data/test/services/up-12816-normu-nowant/supervise/ok +0 -0
  37. data/test/services/up-12816-normu-nowant/supervise/status +0 -0
  38. data/test/services/up-12868-normd-wantu-paused/down +0 -0
  39. data/test/services/up-12868-normd-wantu-paused/supervise/ok +0 -0
  40. data/test/services/up-12868-normd-wantu-paused/supervise/status +0 -0
  41. data/test/services/up-12868-normd-wantu/down +0 -0
  42. data/test/services/up-12868-normd-wantu/supervise/ok +0 -0
  43. data/test/services/up-12868-normd-wantu/supervise/status +0 -0
  44. data/test/services/up-12868-normu-wantu-paused/supervise/ok +0 -0
  45. data/test/services/up-12868-normu-wantu-paused/supervise/status +0 -0
  46. data/test/services/up-12868-normu-wantu/supervise/ok +0 -0
  47. data/test/services/up-12868-normu-wantu/supervise/status +0 -0
  48. data/test/services/up-16464-normd-wantd/down +0 -0
  49. data/test/services/up-16464-normd-wantd/supervise/ok +0 -0
  50. data/test/services/up-16464-normd-wantd/supervise/status +0 -0
  51. data/test/testbase.rb +30 -0
  52. data/test/ts_corrupt.rb +133 -0
  53. data/test/ts_nosupervisor.rb +81 -0
  54. data/test/ts_signal.rb +128 -0
  55. data/test/ts_svstat.rb +548 -0
  56. metadata +117 -0
@@ -0,0 +1,29 @@
1
+ 2011-02-28 mjp version 0.2
2
+ [doc] Documentation touch-up, move from Qualys SVN to github.
3
+
4
+ 2011-02-17 mjp
5
+ [api] Remove #epoch() method, #pid() now returns nil if service is not
6
+ running (formerly returned zero).
7
+ [int] Remove Forwardable delegation, refactor SvDir and StatusBytes
8
+ accordingly. A skosh more efficient, but also puts method documentation
9
+ in the class that needs it.
10
+ [tst] Update tests for API changes, delegation refactoring and corrupt
11
+ status file handling.
12
+ [doc] update LICENSE, copyright notice, README
13
+ [pkg] remove Echoe dependency, generate gem with vanilla rake tasks
14
+ (Rakefile)
15
+
16
+ 2008-10-13 mjp
17
+ [api] make that ENXIO/EWHATEVER on non-running supervisor. Raise
18
+ EPROTO specifically on truncated 'status' file.
19
+ [tst] Fixtures::TempSvDir makes bona fide FIFOs, correctly makes
20
+ incremented tempdirs. ts_control renamed ts_signal, also test non-
21
+ running supervisor (ts_nosupervisor).
22
+ [doc] note Errno:: conventions, use "supervisor" consistently.
23
+
24
+ 2008-10-10 mjp
25
+ [bug] qualify 'ok' FIFO path (cpforbes).
26
+ [api] raise EPROTO for non-running supervisor.
27
+
28
+ 2008-10-06 mjp
29
+ Initial check-in.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Qualys, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,67 @@
1
+ Copyright (c) 2010, 2011 Qualys, Inc.
2
+
3
+ Copyright (c) 2008 - 2010 Nemean Networks, LLC.
4
+
5
+ = Overview
6
+
7
+ +svdir+ is a Ruby interface to the "service directory" style of robust daemon
8
+ process supervision introducted in Dan Bernstein's +daemontools+ software and
9
+ compatibly extended in Gerit Pape's +runit+ package.
10
+
11
+ It exposes a programmatic interface to reliably starting, stopping, signalling
12
+ and interrogating services all implemented directly -- no need to shell out to
13
+ separate utilities. See the documentation for Sys::Sv::SvDir for complete
14
+ information.
15
+
16
+ More information on +daemontools+ is available at
17
+ http://cr.yp.to/daemontools.html. More information on +runit+ is available at
18
+ http://smarden.org/runit/.
19
+
20
+ == Example
21
+
22
+ The <tt>example/</tt> subdirectory in the source distribution contains a
23
+ demonstration program which a system adminstrator might use to control daemons.
24
+
25
+ Typical programmatic use of this software might look like this:
26
+
27
+ #! /usr/bin/env ruby
28
+
29
+ require 'sys/sv/svdir'
30
+ include Sys::Sv
31
+
32
+ # shut down all daemons running under /service
33
+ Dir["/service"].each do |svpath|
34
+ s = SvDir.new(svpath)
35
+
36
+ # force the .../log to shut down, ignoring services without loggers
37
+ s.log.signal(:exit) rescue nil
38
+
39
+ pid = s.pid
40
+ if pid != 0
41
+ s.signal(:exit)
42
+ puts "Told #{s.path} (pid #{pid}) to exit"
43
+ end
44
+ end
45
+
46
+ = Installation
47
+
48
+ Look for the gem on http://rubygems.org.
49
+
50
+ `rake package` will build a .gem under pkg/, and `rake rdoc` will
51
+ generate module documentation.
52
+
53
+ `rake test` and `rake rcov` will give a good idea of where the code is.
54
+
55
+ = To Do
56
+
57
+ * Further testing
58
+ - log()
59
+ - TAI64 testing
60
+
61
+ * Possible extensions
62
+ - <tt>SvDir.new(d) &block</tt> - persist StatusBytes object for block?
63
+ - <tt>normally_down!</tt> and <tt>normally_up!</tt>
64
+
65
+ = Author
66
+
67
+ Mike Pomraning ("mpomraning" at "qualys" dot "com")
@@ -0,0 +1,66 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/testtask'
5
+
6
+ FILES = FileList['Rakefile', 'CHANGELOG', 'LICENSE', 'README',
7
+ 'lib/**/*.rb', 'test/**/*', 'example/*'].to_a
8
+ TESTS = FileList['test/ts_*.rb']
9
+
10
+ spec = Gem::Specification.new do |s|
11
+ s.platform = Gem::Platform::RUBY
12
+ s.author = "Mike Pomraning"
13
+ s.summary = "An interface to service directories ala supervise/runsv"
14
+ s.name = 'svdir'
15
+ s.version = '0.2'
16
+ s.require_path = 'lib'
17
+ s.description = <<eodesc
18
+ The svdir package controls service directories, a scheme for reliably
19
+ controlling daemon processes as implemented in Dan Bernstein's daemontools
20
+ software ("supervise") or Gerit Pape's runit software ("runsv").
21
+ eodesc
22
+ s.files = FILES
23
+ s.test_files = TESTS
24
+ s.has_rdoc = true
25
+ end
26
+
27
+ Rake::GemPackageTask.new(spec) do |pkg|
28
+ pkg.package_dir = 'pkg'
29
+ pkg.need_tar = true
30
+ end
31
+
32
+ desc "Generate rdoc"
33
+ Rake::RDocTask.new("rdoc") do |rdoc|
34
+ rdoc.rdoc_dir = 'doc/rdoc'
35
+ rdoc.title = "svdir"
36
+ # Show source inline with line numbers
37
+ rdoc.options << "--inline-source" << "--line-numbers"
38
+ # Make the readme file the start page for the generated html
39
+ rdoc.options << '--main' << 'README'
40
+ rdoc.rdoc_files.include('lib/**/*.rb',
41
+ 'CHANGELOG',
42
+ 'README',
43
+ 'LICENSE')
44
+ end
45
+
46
+ desc "Run included tests"
47
+ Rake::TestTask.new do |t|
48
+ t.libs << "test"
49
+ t.test_files = TESTS
50
+ t.libs << "lib"
51
+ end
52
+
53
+ desc "Run test coverage (rcov)"
54
+ begin
55
+ require 'rcov/rcovtask'
56
+ Rcov::RcovTask.new do |t|
57
+ t.test_files = TESTS
58
+ t.libs << "test" << "lib"
59
+ t.rcov_opts << '--exclude /gems/,/Library/,/usr/,spec,lib/tasks'
60
+ end
61
+ rescue LoadError
62
+ task :rcov do
63
+ puts "Error - you seem to be missing 'rcov'"
64
+ exit 1
65
+ end
66
+ end
@@ -0,0 +1,117 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'sys/sv/svdir'
4
+ include Sys::Sv
5
+
6
+ USAGE = <<'__eousage'
7
+ Usage: svctl [cmd] service_dir [service_dir ...]
8
+ svctl [-h|--help]
9
+
10
+ If 'cmd' is 'status', print a summary of the current state of each
11
+ given service directory and its subordinate log directory, if any.
12
+
13
+ Issue 'cmd' to the given service directories in turn. Behavior
14
+ varies with the command:
15
+
16
+ status ...... summarize status of service and log service, if any
17
+ up .......... start and, as needed, restart the service
18
+ down ........ TERM+CONT a running service, do not restart
19
+ exit ........ "down" a service, TERM its log service, then exit
20
+ once ........ start but do not restart the service
21
+
22
+ Other values of 'cmd' instruct the supervisory process to send a
23
+ UNIX signal to its service, if running:
24
+
25
+ pause, STOP, continue, CONT, hangup, HUP, alarm, ALRM, interrupt, INT,
26
+ terminate, TERM, kill, KILL, user1, USR1, user2, USR2
27
+
28
+ Note that the 'user'/'USR' commands are extensions to the original
29
+ process supervision implementation, and not supported by all supervisors.
30
+ __eousage
31
+
32
+ def usage_exit(errmsg = nil)
33
+ outio, rc = [$stdout, 0]
34
+
35
+ if errmsg
36
+ outio = $stderr
37
+ rc = 1
38
+ outio.puts errmsg
39
+ end
40
+
41
+ outio.puts USAGE
42
+
43
+ exit(rc)
44
+ end
45
+
46
+ def formatted_status(sv, name = nil)
47
+ # sv(1)-inspired status
48
+ #
49
+ # For each SvDir argument, show:
50
+ # current state: running, paused ("running" but SIGSTOP'd), or down
51
+ # service name: supplied by caller
52
+ # elapsed time: time since begun running or since stopped
53
+ # pid: if applicable
54
+ # typical state: normally up/down? (only if different than current)
55
+ # want up/down?: if applicable
56
+ #
57
+ name = File.basename(sv.path) unless name
58
+
59
+ runstate, elapsed, pid_str, unusual_state = [nil, nil, nil, nil]
60
+
61
+ if sv.up?
62
+ runstate = sv.paused? ? "paused" : "run"
63
+ elapsed = sv.uptime
64
+ pid_str = "(pid #{sv.pid})"
65
+ unusual_state = "normally down" if sv.normally_down? # Hmm!
66
+ else
67
+ runstate = "down"
68
+ elapsed = sv.downtime
69
+ unusual_state = "normally up" if sv.normally_up? # Hmm!
70
+ end
71
+
72
+ elapsed = elapsed.round.to_s + 's'
73
+ want_state = sv.want_up? ? "want up" :
74
+ sv.want_down? ? "want down" :
75
+ ""
76
+
77
+ ["#{runstate}: #{name}:", pid_str, elapsed, unusual_state].compact.join(" ")
78
+ rescue SystemCallError => e
79
+ "err: #{name}: No supervisor detected (#{e})"
80
+ end
81
+
82
+ def do_signal(sig, *dirs)
83
+ dirs.each do |dir|
84
+ begin
85
+ SvDir.new(dir).signal(sig)
86
+ rescue SystemCallError => e
87
+ $stderr.puts "error signalling #{dir}: #{e.message}"
88
+ end
89
+ end
90
+ end
91
+
92
+ def main
93
+ # A quick demonstration of Sys::Sv::SvDir ...
94
+ cmd = ARGV.shift
95
+
96
+ case cmd
97
+ when '--help', '-h', nil
98
+ usage_exit
99
+ when 'status'
100
+ ARGV.each do |dir|
101
+ sv = SvDir.new(dir)
102
+ sv_stat = formatted_status(sv, dir)
103
+ if log = sv.log
104
+ sv_stat += '; ' + formatted_status(log, 'log')
105
+ end
106
+ puts sv_stat
107
+ end
108
+ else
109
+ begin
110
+ do_signal(cmd, *ARGV)
111
+ rescue Exception => e
112
+ usage_exit("error: #{e.message}")
113
+ end
114
+ end
115
+ end
116
+
117
+ main if __FILE__ == $0
@@ -0,0 +1,53 @@
1
+ #
2
+ # Author:: Mike Pomraning
3
+ # Copyright:: Copyright (c) 2011 Qualys, Inc.
4
+ # License:: MIT (see the file LICENSE)
5
+ #
6
+
7
+ module Sys # :nodoc:
8
+ module Sv # :nodoc:
9
+
10
+ # The StatusBytes class interprets state files maintained by a SvDir's
11
+ # _monitor_ process. It should normally not be instantiated directly.
12
+ class StatusBytes # :nodoc:
13
+ BUFLEN = 18 # TODO - grok runit extended info (20 bytes)
14
+ TAI_EPOCH = 4611686018427387914 # time_t 0 on the TAI scale
15
+
16
+ attr_reader :pid, :pauseflag, :wantflag
17
+
18
+ def initialize(bytes) # :nodoc:
19
+ if bytes.size < BUFLEN
20
+ raise ::Errno::EPROTO.new("corrupt status buffer")
21
+ end
22
+
23
+ @bytes = bytes
24
+ @pid, @pauseflag, @wantflag = @bytes.unpack('x12 V c a')
25
+ @epoch = nil # computed if needed
26
+ end
27
+
28
+ # Number of seconds since service was most recently started or
29
+ # stopped.
30
+ def elapsed
31
+ ::Time.now.to_f - epoch()
32
+ end
33
+
34
+ # Returns the number of seconds since the UNIX epoch since the
35
+ # service was most recently started or stopped.
36
+ def epoch
37
+ return @epoch if @epoch
38
+
39
+ # assemble UNIX-scale seconds from TAI64N label
40
+ hi32, lo32, nano = @bytes.unpack('N N N')
41
+ @epoch = (hi32 << 32) + lo32 - TAI_EPOCH
42
+ if @epoch <= 0
43
+ @epoch = 0.0
44
+ else
45
+ @epoch += nano/10e9
46
+ end
47
+
48
+ @epoch
49
+ end
50
+ end
51
+
52
+ end #-- module Sv
53
+ end #-- module Sys
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Author:: Mike Pomraning
4
+ # Copyright:: Copyright (c) 2011 Qualys, Inc.
5
+ # License:: MIT (see the file LICENSE)
6
+ #
7
+
8
+ require 'sys/sv/statusbytes' # class StatusBytes
9
+ require 'sys/sv/util' # Util::open_write(), open_read()
10
+
11
+ module Sys # :nodoc
12
+ module Sv # :nodoc:
13
+
14
+ # The SvDir class encapsulates service directories, a scheme for
15
+ # reliably controlling daemon processes (services) introduced in Dan
16
+ # Bernstein's +daemontools+ software.
17
+ #
18
+ # Each service is monitored by a _supervisor_, which is responsible
19
+ # for starting, stopping, restarting and generally controlling the
20
+ # service. Examples of such supervisors include +supervise+ from
21
+ # the +daemontools+ package and +runsv+ from Gerit Pape's compatible
22
+ # +runit+ software.
23
+ #
24
+ # Most SvDir methods will raise <tt>Errno::</tt> exceptions (each
25
+ # a subclass of +SystemCallException+) if the underlying filesystem
26
+ # representation of the service directory is not as expected. For
27
+ # example, +EACCESS+ or +ENOENT+ may be raised if the _supervisor_'s
28
+ # status directory or state files are missing or unreadable. Additionally,
29
+ # +ENXIO+ is raised if the _supervisor_ itself isn't running.
30
+ #
31
+
32
+ class SvDir
33
+
34
+ VERSION = '0.2'
35
+
36
+ attr_reader :path
37
+
38
+ # ...SvDir::Commands = { :alarm => 'a', :ALRM => 'a', :exit => 'x' ... }
39
+ begin
40
+ h = {}
41
+ {
42
+ [:up ] => 'u',
43
+ [:down ] => 'd',
44
+ [:once ] => 'o',
45
+ [:pause, :STOP ] => 'p',
46
+ [:continue, :CONT ] => 'c',
47
+ [:hangup, :HUP ] => 'h',
48
+ [:alarm, :ALRM ] => 'a',
49
+ [:interrupt, :INT ] => 'i',
50
+ [:terminate, :TERM ] => 't',
51
+ [:kill, :KILL ] => 'k',
52
+ [:exit ] => 'x',
53
+ [:user1, :USR1 ] => '1', # runit and some patches to daemontools
54
+ [:user2, :USR2 ] => '2',
55
+ }.each do | cmds, byte |
56
+ cmds.each { |c| h[c] = byte }
57
+ end
58
+ const_set(:Commands, h)
59
+ end
60
+
61
+ # Create a new SvDir corresponding to the service directory +path+.
62
+ def initialize(path)
63
+ @path = path
64
+ end
65
+
66
+ # Send a signal to the service via its _supervisor_.
67
+ #
68
+ # [<tt>:up</tt>] start the service if not running, restarting as necessary.
69
+ # [<tt>:down</tt>] stop the service, issuing a TERM followed by a CONT. Do not restart it if it stops.
70
+ # [<tt>:once</tt>] start the service if not running, but do not restart it if it stops.
71
+ # [<tt>:pause</tt> or <tt>:STOP</tt>] issue a STOP signal. See also #paused?.
72
+ # [<tt>:continue</tt> or <tt>:CONT</tt>] issue a CONT signal. See also #paused?.
73
+ # [<tt>:hangup</tt> or <tt>:HUP</tt>] issue a HUP signal.
74
+ # [<tt>:alarm</tt> or <tt>:ALRM</tt>] issue an ALRM signal.
75
+ # [<tt>:interrupt</tt> or <tt>INT</tt>] issue an INT signal.
76
+ # [<tt>:terminate</tt> or <tt>TERM</tt>] issue a TERM signal.
77
+ # [<tt>:kill</tt> or <tt>KILL</tt>] issue a KILL signal.
78
+ # [<tt>:exit</tt>] tell the _supervisor_ to exit as soon as the service stops.
79
+ # [<tt>:user1</tt> or <tt>:USR1</tt>] issue a USR1 signal. <i>Not supported by all supervisors</i>
80
+ # [<tt>:user2</tt> or <tt>:USR2</tt>] issue a USR2 signal. <i>Not supported by all supervisors</i>
81
+ def signal(cmd)
82
+ unless byte = Commands[cmd.to_sym]
83
+ raise ArgumentError.new("unsupported SvDir signal `#{cmd}'")
84
+ end
85
+ Util::open_write(svfn('control')) do |f|
86
+ f.syswrite(byte)
87
+ end
88
+ end
89
+
90
+ # Return a SvDir object representing this service's attendant +log+
91
+ # service, otherwise +nil+.
92
+ #
93
+ # Typical SvDir daemons contain a "nested" SvDir responsible for
94
+ # logging the stdout of the base service. E.g.:
95
+ #
96
+ # /path/to/services/webserver # <--- base service
97
+ # /path/to/services/webserver/log # <--- logger
98
+ #
99
+ # The +log+ service is supervised and controllable just like the base
100
+ # service. (Also, the pipe connecting the base service's stdout to
101
+ # the +log+ service's stdin is maintained by a common parent, so that
102
+ # restarting either service won't lose data in the pipe.)
103
+ #
104
+ # Example:
105
+ #
106
+ # my_serv = Sys::Sv::SvDir.new("path/to/my_serv")
107
+ # my_serv.signal(:down)
108
+ # if logger = my_serv.log
109
+ # logger.signal(:down)
110
+ # end
111
+ def log
112
+ fn = File.join(@path, 'log')
113
+ return self.class.new(fn) if File.exists? fn
114
+ end
115
+
116
+ # Returns +true+ if the service directory's _supervisor_ is running.
117
+ #
118
+ # To determine whether the service itself is running, see #up?, #down?
119
+ # and #paused?
120
+ def svok?
121
+ Util::open_write(svfn('ok')) { true }
122
+ rescue Errno::ENXIO, Errno::ENOENT
123
+ false # No pipe reader, or no pipe!
124
+ end
125
+
126
+ # Returns +true+ if the service is typically running, i.e., if the
127
+ # service directory lacks a <tt>./down</tt> file.
128
+ #
129
+ # Note that this method functions whether or not the service is
130
+ # running, and whether or not a supervisor is running.
131
+ #
132
+ # See also the #want_up? method documentation.
133
+ def normally_up?
134
+ ! normally_down?
135
+ end
136
+
137
+ # Returns +true+ if a _supervisor_ will not start the service without
138
+ # explicit instruction to do so.
139
+ #
140
+ # Note that this method functions whether or not the service is
141
+ # running, and whether or not a supervisor is running.
142
+ #
143
+ # See also the #want_down? method documentation.
144
+ def normally_down?
145
+ File.exists? File.join(@path, 'down')
146
+ end
147
+
148
+ # Returns the number of seconds the service has been down,
149
+ # as a float, or +nil+ if the service is in fact running.
150
+ def downtime
151
+ return elapsed() if down?
152
+ end
153
+
154
+ # Returns the number of seconds the service has been running,
155
+ # as a float, or +nil+ if the service is not running.
156
+ def uptime
157
+ return elapsed() if up?
158
+ end
159
+
160
+ # Return the pid of the service running under this SvDir, or
161
+ # +nil+ if no service is running.
162
+ def pid
163
+ p = statusbytes.pid
164
+ return p == 0 ? nil : p
165
+ end
166
+
167
+ # +true+ if the service is not running.
168
+ def down?
169
+ pid.nil?
170
+ end
171
+
172
+ # +true+ if the service is running, even if paused.
173
+ def up?
174
+ !down?
175
+ end
176
+
177
+ # Returns +true+ if the service is paused, that is, has received
178
+ # a SIGSTOP.
179
+ def paused?
180
+ statusbytes.pauseflag != 0
181
+ end
182
+
183
+ # Returns +true+ if the service's supervisor has been instructed to
184
+ # bring the service down.
185
+ def want_down?
186
+ statusbytes.wantflag == 'd'
187
+ end
188
+
189
+ # Returns +true+ if the service's supervisor has been instructed to
190
+ # bring the service up.
191
+ def want_up?
192
+ statusbytes.wantflag == 'u'
193
+ end
194
+
195
+ private
196
+
197
+ def elapsed
198
+ statusbytes.elapsed
199
+ end
200
+
201
+ def svfn(basename)
202
+ File.join(@path, 'supervise', basename)
203
+ end
204
+
205
+ # Instantiate our one-time, delegate helper class
206
+ def statusbytes
207
+ # We _want_ to pass Errno back to caller, which is why we don't
208
+ # simply call #svok?() here.
209
+ Util::open_write(svfn('ok')) {true}
210
+
211
+ buf = Util::open_read(svfn('status')) do |f|
212
+ begin
213
+ f.sysread( StatusBytes::BUFLEN )
214
+ rescue ::EOFError
215
+ ""
216
+ end
217
+ end
218
+ StatusBytes.new(buf)
219
+ end
220
+
221
+ end
222
+
223
+ end #-- module Sv
224
+ end #-- module Sys