Bump version to 0.2.8
[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     """OS class for Linux"""
42     def __init__(self, rootdev, ghandler, output):
43         super(Linux, self).__init__(rootdev, ghandler, output)
44         self._uuid = dict()
45         self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*')
46
47     def is_persistent(self, dev):
48         return not self._persistent.match(dev)
49
50     def get_uuid(self, dev):
51         if dev in self._uuid:
52             return self._uuid[dev]
53
54         uuid = self.g.vfs_uuid(dev)
55         assert len(uuid)
56         self._uuid[dev] = uuid
57         return uuid
58
59     @sysprep()
60     def fix_acpid(self, print_header=True):
61         """Replace acpid powerdown action scripts to immediately shutdown the
62         system without checking if a GUI is running.
63         """
64
65         if print_header:
66             self.out.output('Fixing acpid powerdown action')
67
68         powerbtn_action = '#!/bin/sh\n\nPATH=/sbin:/bin:/usr/bin\n' \
69                           'shutdown -h now "Power button pressed"\n'
70
71         events_dir = '/etc/acpi/events'
72         if not self.g.is_dir(events_dir):
73             self.out.warn("No acpid event directory found")
74             return
75
76         event_exp = re.compile('event=(.+)', re.I)
77         action_exp = re.compile('action=(.+)', re.I)
78         for f in self.g.readdir(events_dir):
79             if f['ftyp'] != 'r':
80                 continue
81
82             fullpath = "%s/%s" % (events_dir, f['name'])
83             event = ""
84             action = ""
85             for line in self.g.cat(fullpath).splitlines():
86                 m = event_exp.match(line)
87                 if m:
88                     event = m.group(1)
89                     continue
90                 m = action_exp.match(line)
91                 if m:
92                     action = m.group(1)
93                     continue
94
95             if event.strip() in ("button[ /]power", "button/power.*"):
96                 if action:
97                     if not self.g.is_file(action):
98                         self.out.warn("Acpid action file: %s does not exist" %
99                                       action)
100                         return
101                     self.g.copy_file_to_file(action,
102                                              "%s.orig.snf-image-creator-%d" %
103                                              (action, time.time()))
104                     self.g.write(action, powerbtn_action)
105                     return
106                 else:
107                     self.out.warn("Acpid event file %s does not contain and "
108                                   "action")
109                     return
110             elif event.strip() == ".*":
111                 self.out.warn("Found action `.*'. Don't know how to handle "
112                               "this. Please edit `%s' image file manually to "
113                               "make the system immediatelly shutdown when an "
114                               "power button acpi event occures." %
115                               action.strip().split()[0])
116                 return
117
118         self.out.warn("No acpi power button event found!")
119
120     @sysprep()
121     def remove_persistent_net_rules(self, print_header=True):
122         """Remove udev rules that will keep network interface names persistent
123         after hardware changes and reboots. Those rules will be created again
124         the next time the image runs.
125         """
126
127         if print_header:
128             self.out.output('Removing persistent network interface names')
129
130         rule_file = '/etc/udev/rules.d/70-persistent-net.rules'
131         if self.g.is_file(rule_file):
132             self.g.rm(rule_file)
133
134     @sysprep()
135     def remove_swap_entry(self, print_header=True):
136         """Remove swap entry from /etc/fstab. If swap is the last partition
137         then the partition will be removed when shrinking is performed. If the
138         swap partition is not the last partition in the disk or if you are not
139         going to shrink the image you should probably disable this.
140         """
141
142         if print_header:
143             self.out.output('Removing swap entry from fstab')
144
145         new_fstab = ""
146         fstab = self.g.cat('/etc/fstab')
147         for line in fstab.splitlines():
148
149             entry = line.split('#')[0].strip().split()
150             if len(entry) == 6 and entry[2] == 'swap':
151                 continue
152
153             new_fstab += "%s\n" % line
154
155         self.g.write('/etc/fstab', new_fstab)
156
157     @sysprep()
158     def use_persistent_block_device_names(self, print_header=True):
159         """Scan fstab & grub configuration files and replace all non-persistent
160         device references with UUIDs.
161         """
162
163         if print_header:
164             self.out.output("Replacing fstab & grub non-persistent device "
165                             "references")
166
167         # convert all devices in fstab to persistent
168         persistent_root = self._persistent_fstab()
169
170         # convert all devices in grub1 to persistent
171         self._persistent_grub1(persistent_root)
172
173     def _persistent_grub1(self, new_root):
174         if self.g.is_file('/boot/grub/menu.lst'):
175             grub1 = '/boot/grub/menu.lst'
176         elif self.g.is_file('/etc/grub.conf'):
177             grub1 = '/etc/grub.conf'
178         else:
179             return
180
181         self.g.aug_init('/', 0)
182         try:
183             roots = self.g.aug_match('/files%s/title[*]/kernel/root' % grub1)
184             for root in roots:
185                 dev = self.g.aug_get(root)
186                 if not self.is_persistent(dev):
187                     # This is not always correct. Grub may contain root entries
188                     # for other systems, but we only support 1 OS per hard
189                     # disk, so this shouldn't harm.
190                     self.g.aug_set(root, new_root)
191         finally:
192             self.g.aug_save()
193             self.g.aug_close()
194
195     def _persistent_fstab(self):
196         mpoints = self.g.mountpoints()
197         if len(mpoints) == 0:
198             pass  # TODO: error handling
199
200         device_dict = dict([[mpoint, dev] for dev, mpoint in mpoints])
201
202         root_dev = None
203         new_fstab = ""
204         fstab = self.g.cat('/etc/fstab')
205         for line in fstab.splitlines():
206
207             line, dev, mpoint = self._convert_fstab_line(line, device_dict)
208             new_fstab += "%s\n" % line
209
210             if mpoint == '/':
211                 root_dev = dev
212
213         self.g.write('/etc/fstab', new_fstab)
214         if root_dev is None:
215             pass  # TODO: error handling
216
217         return root_dev
218
219     def _convert_fstab_line(self, line, devices):
220         orig = line
221         line = line.split('#')[0].strip()
222         if len(line) == 0:
223             return orig, "", ""
224
225         entry = line.split()
226         if len(entry) != 6:
227             self.out.warn("Detected abnormal entry in fstab")
228             return orig, "", ""
229
230         dev = entry[0]
231         mpoint = entry[1]
232
233         if not self.is_persistent(dev):
234             if mpoint in devices:
235                 dev = "UUID=%s" % self.get_uuid(devices[mpoint])
236                 entry[0] = dev
237             else:
238                 # comment out the entry
239                 entry[0] = "#%s" % dev
240             return " ".join(entry), dev, mpoint
241
242         return orig, dev, mpoint
243
244 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :