Fix a bug introduced in 4e58b51b
[snf-image-creator] / image_creator / os_type / linux.py
1 # Copyright 2012 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 from image_creator.os_type.unix import Unix, sysprep
35
36 import re
37 import time
38
39
40 class Linux(Unix):
41     def __init__(self, rootdev, ghandler, output):
42         super(Linux, self).__init__(rootdev, ghandler, output)
43         self._uuid = dict()
44         self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*')
45
46     def is_persistent(self, dev):
47         return not self._persistent.match(dev)
48
49     def get_uuid(self, dev):
50         if dev in self._uuid:
51             return self._uuid[dev]
52
53         for attr in self.g.blkid(dev):
54             if attr[0] == 'UUID':
55                 self._uuid[dev] = attr[1]
56                 return attr[1]
57
58     @sysprep()
59     def fix_acpid(self, print_header=True):
60         """Replace acpid powerdown action scripts to immediately shutdown the
61         system without checking if a GUI is running.
62         """
63
64         if print_header:
65             self.out.output('Fixing acpid powerdown action')
66
67         powerbtn_action = '#!/bin/sh\n\nPATH=/sbin:/bin:/usr/bin\n' \
68                                 'shutdown -h now \"Power button pressed\"\n'
69
70         events_dir = '/etc/acpi/events'
71         if not self.g.is_dir(events_dir):
72             self.out.warn("No acpid event directory found")
73             return
74
75         event_exp = re.compile('event=(.+)', re.I)
76         action_exp = re.compile('action=(.+)', re.I)
77         for f in self.g.readdir(events_dir):
78             if f['ftyp'] != 'r':
79                 continue
80
81             fullpath = "%s/%s" % (events_dir, f['name'])
82             event = ""
83             action = ""
84             for line in self.g.cat(fullpath).splitlines():
85                 m = event_exp.match(line)
86                 if m:
87                     event = m.group(1)
88                     continue
89                 m = action_exp.match(line)
90                 if m:
91                     action = m.group(1)
92                     continue
93
94             if event.strip() == "button[ /]power":
95                 if action:
96                     if not self.g.is_file(action):
97                         self.out.warn("Acpid action file: %s does not exist" %
98                                                                         action)
99                         return
100                     self.g.copy_file_to_file(action, \
101                       "%s.orig.snf-image-creator-%d" % (action, time.time()))
102                     self.g.write(action, powerbtn_action)
103                     return
104                 else:
105                     self.out.warn(
106                             "Acpid event file %s does not contain and action")
107                     return
108             elif event.strip() == ".*":
109                 self.out.warn(
110                     "Found action `.*'. Don't know how to handle this. " \
111                     "Please edit \%s' image file manually to make the " \
112                     "system immediatelly shutdown when an power button acpi " \
113                     "event occures" % action)
114                 return
115
116     @sysprep()
117     def persistent_net_rules(self, print_header=True):
118         """Remove udev rules that will keep network interface names persistent
119         after hardware changes and reboots. Those rules will be created again
120         the next time the image runs.
121         """
122
123         if print_header:
124             self.out.output('Removing persistent network interface names')
125
126         rule_file = '/etc/udev/rules.d/70-persistent-net.rules'
127         if self.g.is_file(rule_file):
128             self.g.rm(rule_file)
129
130     @sysprep()
131     def remove_swap_entry(self, print_header=True):
132         """Remove swap entry from /etc/fstab. If swap is the last partition
133         then the partition will be removed when shrinking is performed. If the
134         swap partition is not the last partition in the disk or if you are not
135         going to shrink the image you should probably disable this.
136         """
137
138         if print_header:
139             self.out.output('Removing swap entry from fstab')
140
141         new_fstab = ""
142         fstab = self.g.cat('/etc/fstab')
143         for line in fstab.splitlines():
144
145             entry = line.split('#')[0].strip().split()
146             if len(entry) == 6 and entry[2] == 'swap':
147                 continue
148
149             new_fstab += "%s\n" % line
150
151         self.g.write('/etc/fstab', new_fstab)
152
153     @sysprep()
154     def persistent_devs(self, print_header=True):
155         """Scan fstab & grub configuration files and replace all non-persistent
156         device appearences with UUIDs.
157         """
158
159         if print_header:
160             self.out.output(
161                     'Replacing fstab & grub non-persistent device appearences')
162
163         # convert all devices in fstab to persistent
164         persistent_root = self._persistent_fstab()
165
166         # convert all devices in grub1 to persistent
167         self._persistent_grub1(persistent_root)
168
169     def _persistent_grub1(self, new_root):
170         if self.g.is_file('/boot/grub/menu.lst'):
171             grub1 = '/boot/grub/menu.lst'
172         elif self.g.is_file('/etc/grub.conf'):
173             grub1 = '/etc/grub.conf'
174         else:
175             return
176
177         self.g.aug_init('/', 0)
178         try:
179             roots = self.g.aug_match('/files%s/title[*]/kernel/root' % grub1)
180             for root in roots:
181                 dev = self.g.aug_get(root)
182                 if not self.is_persistent(dev):
183                     # This is not always correct. Grub may contain root entries
184                     # for other systems, but we only support 1 OS per hard
185                     # disk, so this shouldn't harm.
186                     self.g.aug_set(root, new_root)
187         finally:
188             self.g.aug_save()
189             self.g.aug_close()
190
191     def _persistent_fstab(self):
192         mpoints = self.g.mountpoints()
193         if len(mpoints) == 0:
194             pass  # TODO: error handling
195
196         device_dict = dict([[mpoint, dev] for dev, mpoint in mpoints])
197
198         root_dev = None
199         new_fstab = ""
200         fstab = self.g.cat('/etc/fstab')
201         for line in fstab.splitlines():
202
203             line, dev, mpoint = self._convert_fstab_line(line, device_dict)
204             new_fstab += "%s\n" % line
205
206             if mpoint == '/':
207                 root_dev = dev
208
209         self.g.write('/etc/fstab', new_fstab)
210         if root_dev is None:
211             pass  # TODO: error handling
212
213         return root_dev
214
215     def _convert_fstab_line(self, line, devices):
216         orig = line
217         line = line.split('#')[0].strip()
218         if len(line) == 0:
219             return orig, "", ""
220
221         entry = line.split()
222         if len(entry) != 6:
223             self.out.warn("Detected abnormal entry in fstab")
224             return orig, "", ""
225
226         dev = entry[0]
227         mpoint = entry[1]
228
229         if not self.is_persistent(dev):
230             if mpoint in devices:
231                 dev = "UUID=%s" % self.get_uuid(devices[mpoint])
232                 entry[0] = dev
233             else:
234                 # comment out the entry
235                 entry[0] = "#%s" % dev
236             return " ".join(entry), dev, mpoint
237
238         return orig, dev, mpoint
239
240 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :