Flesh out the status handling a bit more, actually check all multi-user runlevels
[aids.git] / lib / aids.rb
1 module AIDS
2         RUNLEVEL_SINGLE          = :'S'
3         RUNLEVEL_MULTI_TO_SINGLE = :'1'
4         RUNLEVEL_MULTI           = :'2'
5         RUNLEVEL_MULTI_ALL       = [:'2',:'3',:'4',:'5']
6         RUNLEVEL_HALT            = :'0'
7         RUNLEVEL_REBOOT          = :'6'
8         RUNLEVEL_ALL             = [:'0',:'1',:'2',:'3',:'4',:'5',:'6',:'S']
9
10         class Service
11                 attr_reader :name
12
13                 def initialize(name)
14                         if name.nil? or name.empty? or not name.is_a?(String)
15                                 raise AIDS::Infection.new("Service name must be a non-empty string, got #{name.inspect}.")
16                         end
17                         name = $1 if name =~ %r{^/etc/init\.d/(.+)$}
18                         if name =~ /[^[:alnum:]\-.]/
19                                 raise AIDS::Infection.new("Invalid init script name: #{name}.")
20                         end
21                         unless File.exist?("/etc/init.d/#{name}")
22                                 raise AIDS::Infection.new("Unknown service: #{name}.")
23                         end
24                         @name = name
25                 end
26
27                 def enable!
28                         start_on_runlevels!(RUNLEVEL_MULTI_ALL)
29                 end
30
31                 def disable!
32                         stop_on_runlevels!(RUNLEVEL_MULTI_ALL)
33                 end
34
35                 def enabled?
36                         started_on_runlevel?(RUNLEVEL_MULTI)
37                 end
38
39                 def status
40                         status = {}
41                         RUNLEVEL_ALL.each do |r|
42                                 if started_on_runlevel?(r)
43                                         status[r] = :start
44                                 elsif stopped_on_runlevel?(r)
45                                         status[r] = :stop
46                                 else
47                                         status[r] = :none
48                                 end
49                         end
50                         status
51                 end
52
53                 private
54
55                 def started_on_runlevel?(runlevel)
56                         AIDS.validate_runlevel(runlevel)
57                         not Dir.glob("/etc/rc#{runlevel}.d/S[0-9][0-9]#{@name}").empty?
58                 end
59
60                 def stopped_on_runlevel?(runlevel)
61                         AIDS.validate_runlevel(runlevel)
62                         not Dir.glob("/etc/rc#{runlevel}.d/K[0-9][0-9]#{@name}").empty?
63                 end
64
65                 def start_on_runlevels!(runlevels)
66                         [runlevels].flatten.each do |r|
67                                 AIDS.validate_runlevel(r)
68                                 next if started_on_runlevel?(r)
69                                 updatercd(:enable, r)
70                         end
71                 end
72
73                 def stop_on_runlevels!(runlevels)
74                         [runlevels].flatten.each do |r|
75                                 AIDS.validate_runlevel(r)
76                                 next if stopped_on_runlevel?(r)
77                                 updatercd(:disable, r)
78                         end
79                 end
80
81                 def set_default_runlevels!
82                         updatercd(:remove)
83                         updatercd(:defaults)
84                 end
85
86                 def updatercd(action, runlevel=nil)
87                         unless [:enable, :disable, :remove, :defaults].include?(action)
88                                 raise AIDS::Infection.new("Invalid action for updatercd: #{action}.")
89                         end
90                         AIDS.validate_runlevel(runlevel) if runlevel
91                         # update-rc.d will baulk at being told to do anything with
92                         # these runlevels.
93                         if [:'0',:'1',:'6'].include?(runlevel)
94                                 raise AIDS::Infection.new("Unable to comply: update-rc.d is balls.")
95                         end
96                         pid = Process.fork do
97                                 $stdout.close
98                                 $stderr.close
99                                 Kernel.exec(
100                                         '/usr/sbin/update-rc.d',
101                                         @name,
102                                         action.to_s,
103                                         runlevel.to_s
104                                 )
105                         end
106                         Process.wait(pid)
107                         if (rc = $?.exitstatus) != 0
108                                 raise AIDS::Infection.new("update-rc.d returned #{rc}")
109                         end
110                 end
111         end
112
113         class Infection < Exception
114         end
115
116         def self.get_all_services
117                 Dir.glob("/etc/init.d/*").map do |f|
118                         next unless File.executable?(f)
119                         Service.new(f)
120                 end.compact
121         end
122
123         private
124
125         def self.validate_runlevel(runlevel)
126                 unless RUNLEVEL_ALL.include?(runlevel)
127                         raise AIDS::Infection.new("Invalid runlevel: #{runlevel}.")
128                 end
129         end
130 end