Correct image size computation
[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
35 from image_creator.util import warn
36
37 from clint.textui import puts, indent
38
39 import re
40 import time
41
42
43 class Linux(Unix):
44     def __init__(self, rootdev, ghandler):
45         super(Linux, self).__init__(rootdev, ghandler)
46         self._uuid = dict()
47         self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*')
48
49     def is_persistent(self, dev):
50         return not self._persistent.match(dev)
51
52     def get_uuid(self, dev):
53         if dev in self._uuid:
54             return self._uuid[dev]
55
56         for attr in self.g.blkid(dev):
57             if attr[0] == 'UUID':
58                 self._uuid[dev] = attr[1]
59                 return attr[1]
60
61     def sysprep_acpid(self, print_header=True):
62         """Replace acpid powerdown action scripts to automatically shutdown
63         the system without checking if a GUI is running.
64         """
65
66         if print_header:
67             print 'Fixing acpid powerdown action'
68
69         powerbtn_action = '#!/bin/sh\n\nPATH=/sbin:/bin:/usr/bin\n' \
70                                 'shutdown -h now \"Power button pressed\"'
71
72         events_dir = '/etc/acpi/events'
73         if not self.g.is_dir(events_dir):
74             warn("No acpid event directory found")
75             return
76
77         event_exp = re.compile('event=(.+)', re.I)
78         action_exp = re.compile('action=(.+)', re.I)
79         for f in self.g.readdir(events_dir):
80             if f['ftyp'] != 'r':
81                 continue
82
83             fullpath = "%s/%s" % (events_dir, f['name'])
84             event = ""
85             action = ""
86             for line in self.g.cat(fullpath).splitlines():
87                 m = event_exp.match(line)
88                 if m:
89                     event = m.group(1)
90                     continue
91                 m = action_exp.match(line)
92                 if m:
93                     action = m.group(1)
94                     continue
95
96             if event.strip() == "button[ /]power":
97                 if action:
98                     if not self.g.is_file(action):
99                         warn("Acpid action file: %s does not exist" % action)
100                         return
101                     self.g.copy_file_to_file(fullpath, \
102                       "%s.orig.snf-image-creator-%d" % (fullpath, time.time()))
103                     self.g.write(fullpath, powerbtn_action)
104                     return
105                 else:
106                     warn("Acpid event file %s does not contain and action")
107                     return
108             elif event.strip() == ".*":
109                 warn("Found action `.*'. Don't know how to handle this." \
110                     " Please edit \%s' image file manually to make the "
111                     "system immediatelly shutdown when an power button acpi " \
112                     "event occures")
113                 return
114
115     def sysprep_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             puts('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     def sysprep_persistent_devs(self, print_header=True):
129         """Scan fstab and grub configuration files and replace all
130         non-persistent device appearences with UUIDs.
131         """
132
133         if print_header:
134             puts('Replacing fstab & grub non-persistent device appearences')
135
136         # convert all devices in fstab to persistent
137         persistent_root = self._persistent_fstab()
138
139         # convert all devices in grub1 to persistent
140         self._persistent_grub1(persistent_root)
141
142     def _persistent_grub1(self, new_root):
143         if self.g.is_file('/boot/grub/menu.lst'):
144             grub1 = '/boot/grub/menu.lst'
145         elif self.g.is_file('/etc/grub.conf'):
146             grub1 = '/etc/grub.conf'
147         else:
148             return
149
150         self.g.aug_init('/', 0)
151         try:
152             roots = self.g.aug_match('/files%s/title[*]/kernel/root' % grub1)
153             for root in roots:
154                 dev = self.g.aug_get(root)
155                 if not self.is_persistent(dev):
156                     # This is not always correct. Grub may contain root entries
157                     # for other systems, but we only support 1 OS per hard
158                     # disk, so this shouldn't harm.
159                     self.g.aug_set(root, new_root)
160         finally:
161             self.g.aug_save()
162             self.g.aug_close()
163
164     def _persistent_fstab(self):
165         mpoints = self.g.mountpoints()
166         if len(mpoints) == 0:
167             pass  # TODO: error handling
168
169         device_dict = dict([[mpoint, dev] for dev, mpoint in mpoints])
170
171         root_dev = None
172         new_fstab = ""
173         fstab = self.g.cat('/etc/fstab')
174         for line in fstab.splitlines():
175
176             line, dev, mpoint = self._convert_fstab_line(line, device_dict)
177             new_fstab += "%s\n" % line
178
179             if mpoint == '/':
180                 root_dev = dev
181
182         self.g.write('/etc/fstab', new_fstab)
183         if root_dev is None:
184             pass  # TODO: error handling
185
186         return root_dev
187
188     def _convert_fstab_line(self, line, devices):
189         orig = line
190         line = line.split('#')[0].strip()
191         if len(line) == 0:
192             return orig, "", ""
193
194         entry = line.split()
195         if len(entry) != 6:
196             warn("Detected abnormal entry in fstab")
197             return orig, "", ""
198
199         dev = entry[0]
200         mpoint = entry[1]
201
202         if not self.is_persistent(dev):
203             if mpoint in devices:
204                 dev = "UUID=%s" % self.get_uuid(devices[mpoint])
205                 entry[0] = dev
206             else:
207                 # comment out the entry
208                 entry[0] = "#%s" % dev
209             return " ".join(entry), dev, mpoint
210
211         return orig, dev, mpoint
212
213 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :