1 # -*- coding: utf-8 -*-
3 # Copyright 2012 GRNET S.A. All rights reserved.
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
9 # 1. Redistributions of source code must retain the above
10 # copyright notice, this list of conditions and the following
13 # 2. Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials
16 # provided with the distribution.
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
36 """This module hosts OS-specific code for Linux"""
38 from image_creator.os_type.unix import Unix, sysprep
45 """OS class for Linux"""
46 def __init__(self, image, **kargs):
47 super(Linux, self).__init__(image, **kargs)
49 self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*')
51 @sysprep('Removing user accounts with id greater that 1000', enabled=False)
52 def remove_user_accounts(self):
53 """Remove all user accounts with id greater than 1000"""
55 if 'USERS' not in self.meta:
58 # Remove users from /etc/passwd
61 metadata_users = self.meta['USERS'].split()
62 for line in self.image.g.cat('/etc/passwd').splitlines():
63 fields = line.split(':')
64 if int(fields[2]) > 1000:
65 removed_users[fields[0]] = fields
66 # remove it from the USERS metadata too
67 if fields[0] in metadata_users:
68 metadata_users.remove(fields[0])
70 passwd.append(':'.join(fields))
72 self.meta['USERS'] = " ".join(metadata_users)
74 # Delete the USERS metadata if empty
75 if not len(self.meta['USERS']):
76 del self.meta['USERS']
78 self.image.g.write('/etc/passwd', '\n'.join(passwd) + '\n')
80 # Remove the corresponding /etc/shadow entries
82 for line in self.image.g.cat('/etc/shadow').splitlines():
83 fields = line.split(':')
84 if fields[0] not in removed_users:
85 shadow.append(':'.join(fields))
87 self.image.g.write('/etc/shadow', "\n".join(shadow) + '\n')
89 # Remove the corresponding /etc/group entries
91 for line in self.image.g.cat('/etc/group').splitlines():
92 fields = line.split(':')
93 # Remove groups tha have the same name as the removed users
94 if fields[0] not in removed_users:
95 group.append(':'.join(fields))
97 self.image.g.write('/etc/group', '\n'.join(group) + '\n')
99 # Remove home directories
100 for home in [field[5] for field in removed_users.values()]:
101 if self.image.g.is_dir(home) and home.startswith('/home/'):
102 self.image.g.rm_rf(home)
104 @sysprep('Cleaning up password & locking all user accounts')
105 def cleanup_passwords(self):
106 """Remove all passwords and lock all user accounts"""
110 for line in self.image.g.cat('/etc/shadow').splitlines():
111 fields = line.split(':')
112 if fields[1] not in ('*', '!'):
115 shadow.append(":".join(fields))
117 self.image.g.write('/etc/shadow', "\n".join(shadow) + '\n')
119 # Remove backup file for /etc/shadow
120 self.image.g.rm_rf('/etc/shadow-')
122 @sysprep('Fixing acpid powerdown action')
124 """Replace acpid powerdown action scripts to immediately shutdown the
125 system without checking if a GUI is running.
128 powerbtn_action = '#!/bin/sh\n\nPATH=/sbin:/bin:/usr/bin\n' \
129 'shutdown -h now "Power button pressed"\n'
131 events_dir = '/etc/acpi/events'
132 if not self.image.g.is_dir(events_dir):
133 self.out.warn("No acpid event directory found")
136 event_exp = re.compile('event=(.+)', re.I)
137 action_exp = re.compile('action=(.+)', re.I)
138 for events_file in self.image.g.readdir(events_dir):
139 if events_file['ftyp'] != 'r':
142 fullpath = "%s/%s" % (events_dir, events_file['name'])
145 for line in self.image.g.cat(fullpath).splitlines():
146 match = event_exp.match(line)
148 event = match.group(1)
150 match = action_exp.match(line)
152 action = match.group(1)
155 if event.strip() in ("button[ /]power", "button/power.*"):
157 if not self.image.g.is_file(action):
158 self.out.warn("Acpid action file: %s does not exist" %
161 self.image.g.copy_file_to_file(
162 action, "%s.orig.snf-image-creator-%d" %
163 (action, time.time()))
164 self.image.g.write(action, powerbtn_action)
167 self.out.warn("Acpid event file %s does not contain and "
170 elif event.strip() == ".*":
171 self.out.warn("Found action `.*'. Don't know how to handle "
172 "this. Please edit `%s' image file manually to "
173 "make the system immediatelly shutdown when an "
174 "power button acpi event occures." %
175 action.strip().split()[0])
178 self.out.warn("No acpi power button event found!")
180 @sysprep('Removing persistent network interface names')
181 def remove_persistent_net_rules(self):
182 """Remove udev rules that will keep network interface names persistent
183 after hardware changes and reboots. Those rules will be created again
184 the next time the image runs.
187 rule_file = '/etc/udev/rules.d/70-persistent-net.rules'
188 if self.image.g.is_file(rule_file):
189 self.image.g.rm(rule_file)
191 @sysprep('Removing swap entry from fstab')
192 def remove_swap_entry(self):
193 """Remove swap entry from /etc/fstab. If swap is the last partition
194 then the partition will be removed when shrinking is performed. If the
195 swap partition is not the last partition in the disk or if you are not
196 going to shrink the image you should probably disable this.
200 fstab = self.image.g.cat('/etc/fstab')
201 for line in fstab.splitlines():
203 entry = line.split('#')[0].strip().split()
204 if len(entry) == 6 and entry[2] == 'swap':
207 new_fstab += "%s\n" % line
209 self.image.g.write('/etc/fstab', new_fstab)
211 @sysprep('Replacing fstab & grub non-persistent device references')
212 def use_persistent_block_device_names(self):
213 """Scan fstab & grub configuration files and replace all non-persistent
214 device references with UUIDs.
217 # convert all devices in fstab to persistent
218 persistent_root = self._persistent_fstab()
220 # convert all devices in grub1 to persistent
221 self._persistent_grub1(persistent_root)
223 def _persistent_grub1(self, new_root):
224 """Replaces non-persistent device name occurencies with persistent
225 ones in GRUB1 configuration files.
227 if self.image.g.is_file('/boot/grub/menu.lst'):
228 grub1 = '/boot/grub/menu.lst'
229 elif self.image.g.is_file('/etc/grub.conf'):
230 grub1 = '/etc/grub.conf'
234 self.image.g.aug_init('/', 0)
236 roots = self.image.g.aug_match(
237 '/files%s/title[*]/kernel/root' % grub1)
239 dev = self.image.g.aug_get(root)
240 if not self._is_persistent(dev):
241 # This is not always correct. Grub may contain root entries
242 # for other systems, but we only support 1 OS per hard
243 # disk, so this shouldn't harm.
244 self.image.g.aug_set(root, new_root)
246 self.image.g.aug_save()
247 self.image.g.aug_close()
249 def _persistent_fstab(self):
250 """Replaces non-persistent device name occurencies in /etc/fstab with
253 mpoints = self.image.g.mountpoints()
254 if len(mpoints) == 0:
255 pass # TODO: error handling
257 device_dict = dict([[mpoint, dev] for dev, mpoint in mpoints])
261 fstab = self.image.g.cat('/etc/fstab')
262 for line in fstab.splitlines():
264 line, dev, mpoint = self._convert_fstab_line(line, device_dict)
265 new_fstab += "%s\n" % line
270 self.image.g.write('/etc/fstab', new_fstab)
272 pass # TODO: error handling
276 def _convert_fstab_line(self, line, devices):
277 """Replace non-persistent device names in an fstab line to their UUID
281 line = line.split('#')[0].strip()
287 self.out.warn("Detected abnormal entry in fstab")
293 if not self._is_persistent(dev):
294 if mpoint in devices:
295 dev = "UUID=%s" % self._get_uuid(devices[mpoint])
298 # comment out the entry
299 entry[0] = "#%s" % dev
300 return " ".join(entry), dev, mpoint
302 return orig, dev, mpoint
304 def _do_inspect(self):
305 """Run various diagnostics to check if media is supported"""
308 'Checking if the media contains logical volumes (LVM)...', False)
310 has_lvm = True if len(self.image.g.lvs()) else False
314 self.image.set_unsupported('The media contains logical volumes')
316 self.out.success('no')
318 def _do_collect_metadata(self):
319 """Collect metadata about the OS"""
320 super(Linux, self)._do_collect_metadata()
321 self.meta["USERS"] = " ".join(self._get_passworded_users())
323 # Delete the USERS metadata if empty
324 if not len(self.meta['USERS']):
325 self.out.warn("No passworded users found!")
326 del self.meta['USERS']
328 def _get_passworded_users(self):
329 """Returns a list of non-locked user accounts"""
331 regexp = re.compile(r'(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}')
333 for line in self.image.g.cat('/etc/shadow').splitlines():
334 match = regexp.match(line)
338 user, passwd = match.groups()
339 if len(passwd) > 0 and passwd[0] == '!':
340 self.out.warn("Ignoring locked %s account." % user)
346 def _is_persistent(self, dev):
347 """Checks if a device name is persistent."""
348 return not self._persistent.match(dev)
350 def _get_uuid(self, dev):
351 """Returns the UUID corresponding to a device"""
352 if dev in self._uuid:
353 return self._uuid[dev]
355 uuid = self.image.g.vfs_uuid(dev)
357 self._uuid[dev] = uuid
360 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :