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