Copyright notices are important
[aids.git] / lib / aids.rb
1 # Copyright (C) 2012  Steven McDonald <steven@steven-mcdonald.id.au>
2 #
3 # This program is free software. It comes without any warranty, to the
4 # extent permitted by applicable law. You can redistribute it and/or
5 # modify it under the terms of the Do What The Fuck You Want To Public
6 # License, Version 2, as published by Sam Hocevar. See the file COPYING
7 # in the root of this program's source distribution for details, or:
8 #
9 #   http://sam.zoy.org/wtfpl/COPYING
10
11 module AIDS
12         RUNLEVEL_SINGLE          = :'S'
13         RUNLEVEL_MULTI_TO_SINGLE = :'1'
14         RUNLEVEL_MULTI           = :'2'
15         RUNLEVEL_MULTI_ALL       = [:'2',:'3',:'4',:'5']
16         RUNLEVEL_HALT            = :'0'
17         RUNLEVEL_REBOOT          = :'6'
18         RUNLEVEL_ALL             = [:'0',:'1',:'2',:'3',:'4',:'5',:'6',:'S']
19
20         class Service
21                 attr_reader :name
22
23                 def initialize(name)
24                         if name.nil? or name.empty? or not name.is_a?(String)
25                                 raise AIDS::Infection.new("Service name must be a non-empty string, got #{name.inspect}.")
26                         end
27                         name = $1 if name =~ %r{^/etc/init\.d/(.+)$}
28                         if name =~ /[^[:alnum:]\-.]/
29                                 raise AIDS::Infection.new("Invalid init script name: #{name}.")
30                         end
31                         unless File.exist?("/etc/init.d/#{name}")
32                                 raise AIDS::Infection.new("Unknown service: #{name}.")
33                         end
34                         @name = name
35                 end
36
37                 def enable!
38                         start_on_runlevels!(RUNLEVEL_MULTI_ALL)
39                 end
40
41                 def disable!
42                         stop_on_runlevels!(RUNLEVEL_MULTI_ALL)
43                 end
44
45                 def enabled?
46                         started_on_runlevel?(RUNLEVEL_MULTI)
47                 end
48
49                 def status
50                         status = {}
51                         RUNLEVEL_ALL.each do |r|
52                                 if started_on_runlevel?(r)
53                                         status[r] = :start
54                                 elsif stopped_on_runlevel?(r)
55                                         status[r] = :stop
56                                 else
57                                         status[r] = :none
58                                 end
59                         end
60                         status
61                 end
62
63                 private
64
65                 def started_on_runlevel?(runlevel)
66                         AIDS.validate_runlevel(runlevel)
67                         not Dir.glob("/etc/rc#{runlevel}.d/S[0-9][0-9]#{@name}").empty?
68                 end
69
70                 def stopped_on_runlevel?(runlevel)
71                         AIDS.validate_runlevel(runlevel)
72                         not Dir.glob("/etc/rc#{runlevel}.d/K[0-9][0-9]#{@name}").empty?
73                 end
74
75                 def start_on_runlevels!(runlevels)
76                         [runlevels].flatten.each do |r|
77                                 AIDS.validate_runlevel(r)
78                                 next if started_on_runlevel?(r)
79                                 updatercd(:enable, r)
80                         end
81                 end
82
83                 def stop_on_runlevels!(runlevels)
84                         [runlevels].flatten.each do |r|
85                                 AIDS.validate_runlevel(r)
86                                 next if stopped_on_runlevel?(r)
87                                 updatercd(:disable, r)
88                         end
89                 end
90
91                 def set_default_runlevels!
92                         updatercd(:remove)
93                         updatercd(:defaults)
94                 end
95
96                 def updatercd(action, runlevel=nil)
97                         unless [:enable, :disable, :remove, :defaults].include?(action)
98                                 raise AIDS::Infection.new("Invalid action for updatercd: #{action}.")
99                         end
100                         AIDS.validate_runlevel(runlevel) if runlevel
101                         # update-rc.d will baulk at being told to do anything with
102                         # these runlevels.
103                         if [:'0',:'1',:'6'].include?(runlevel)
104                                 raise AIDS::Infection.new("Unable to comply: update-rc.d is balls.")
105                         end
106                         pid = Process.fork do
107                                 $stdout.close
108                                 $stderr.close
109                                 Kernel.exec(
110                                         '/usr/sbin/update-rc.d',
111                                         @name,
112                                         action.to_s,
113                                         runlevel.to_s
114                                 )
115                         end
116                         Process.wait(pid)
117                         if (rc = $?.exitstatus) != 0
118                                 raise AIDS::Infection.new("update-rc.d returned #{rc}")
119                         end
120                 end
121         end
122
123         class Infection < Exception
124         end
125
126         def self.get_all_services
127                 Dir.glob("/etc/init.d/*").map do |f|
128                         next unless File.executable?(f)
129                         Service.new(f)
130                 end.compact
131         end
132
133         private
134
135         def self.validate_runlevel(runlevel)
136                 unless RUNLEVEL_ALL.include?(runlevel)
137                         raise AIDS::Infection.new("Invalid runlevel: #{runlevel}.")
138                 end
139         end
140 end