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