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