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