Bump version to 0.2.2
[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         uuid = self.g.vfs_uuid(dev)
54         assert len(uuid)
55         self._uuid[dev] = uuid
56         return uuid
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() in ("button[ /]power", "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" %
102                                              (action, time.time()))
103                     self.g.write(action, powerbtn_action)
104                     return
105                 else:
106                     self.out.warn("Acpid event file %s does not contain and "
107                                   "action")
108                     return
109             elif event.strip() == ".*":
110                 self.out.warn("Found action `.*'. Don't know how to handle "
111                               "this. Please edit `%s' image file manually to "
112                               "make the system immediatelly shutdown when an "
113                               "power button acpi event occures." %
114                               action.strip().split()[0])
115                 return
116
117         self.out.warn("No acpi power button event found!")
118
119     @sysprep()
120     def remove_persistent_net_rules(self, print_header=True):
121         """Remove udev rules that will keep network interface names persistent
122         after hardware changes and reboots. Those rules will be created again
123         the next time the image runs.
124         """
125
126         if print_header:
127             self.out.output('Removing persistent network interface names')
128
129         rule_file = '/etc/udev/rules.d/70-persistent-net.rules'
130         if self.g.is_file(rule_file):
131             self.g.rm(rule_file)
132
133     @sysprep()
134     def remove_swap_entry(self, print_header=True):
135         """Remove swap entry from /etc/fstab. If swap is the last partition
136         then the partition will be removed when shrinking is performed. If the
137         swap partition is not the last partition in the disk or if you are not
138         going to shrink the image you should probably disable this.
139         """
140
141         if print_header:
142             self.out.output('Removing swap entry from fstab')
143
144         new_fstab = ""
145         fstab = self.g.cat('/etc/fstab')
146         for line in fstab.splitlines():
147
148             entry = line.split('#')[0].strip().split()
149             if len(entry) == 6 and entry[2] == 'swap':
150                 continue
151
152             new_fstab += "%s\n" % line
153
154         self.g.write('/etc/fstab', new_fstab)
155
156     @sysprep()
157     def use_persistent_block_device_names(self, print_header=True):
158         """Scan fstab & grub configuration files and replace all non-persistent
159         device references with UUIDs.
160         """
161
162         if print_header:
163             self.out.output("Replacing fstab & grub non-persistent device "
164                             "references")
165
166         # convert all devices in fstab to persistent
167         persistent_root = self._persistent_fstab()
168
169         # convert all devices in grub1 to persistent
170         self._persistent_grub1(persistent_root)
171
172     def _persistent_grub1(self, new_root):
173         if self.g.is_file('/boot/grub/menu.lst'):
174             grub1 = '/boot/grub/menu.lst'
175         elif self.g.is_file('/etc/grub.conf'):
176             grub1 = '/etc/grub.conf'
177         else:
178             return
179
180         self.g.aug_init('/', 0)
181         try:
182             roots = self.g.aug_match('/files%s/title[*]/kernel/root' % grub1)
183             for root in roots:
184                 dev = self.g.aug_get(root)
185                 if not self.is_persistent(dev):
186                     # This is not always correct. Grub may contain root entries
187                     # for other systems, but we only support 1 OS per hard
188                     # disk, so this shouldn't harm.
189                     self.g.aug_set(root, new_root)
190         finally:
191             self.g.aug_save()
192             self.g.aug_close()
193
194     def _persistent_fstab(self):
195         mpoints = self.g.mountpoints()
196         if len(mpoints) == 0:
197             pass  # TODO: error handling
198
199         device_dict = dict([[mpoint, dev] for dev, mpoint in mpoints])
200
201         root_dev = None
202         new_fstab = ""
203         fstab = self.g.cat('/etc/fstab')
204         for line in fstab.splitlines():
205
206             line, dev, mpoint = self._convert_fstab_line(line, device_dict)
207             new_fstab += "%s\n" % line
208
209             if mpoint == '/':
210                 root_dev = dev
211
212         self.g.write('/etc/fstab', new_fstab)
213         if root_dev is None:
214             pass  # TODO: error handling
215
216         return root_dev
217
218     def _convert_fstab_line(self, line, devices):
219         orig = line
220         line = line.split('#')[0].strip()
221         if len(line) == 0:
222             return orig, "", ""
223
224         entry = line.split()
225         if len(entry) != 6:
226             self.out.warn("Detected abnormal entry in fstab")
227             return orig, "", ""
228
229         dev = entry[0]
230         mpoint = entry[1]
231
232         if not self.is_persistent(dev):
233             if mpoint in devices:
234                 dev = "UUID=%s" % self.get_uuid(devices[mpoint])
235                 entry[0] = dev
236             else:
237                 # comment out the entry
238                 entry[0] = "#%s" % dev
239             return " ".join(entry), dev, mpoint
240
241         return orig, dev, mpoint
242
243 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :