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.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.g.write('/etc/passwd', '\n'.join(passwd) + '\n')
80 # Remove the corresponding /etc/shadow entries
82 for line in self.g.cat('/etc/shadow').splitlines():
83 fields = line.split(':')
84 if fields[0] not in removed_users:
85 shadow.append(':'.join(fields))
87 self.g.write('/etc/shadow', "\n".join(shadow) + '\n')
89 # Remove the corresponding /etc/group entries
91 for line in self.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.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.g.is_dir(home) and home.startswith('/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.g.cat('/etc/shadow').splitlines():
111 fields = line.split(':')
112 if fields[1] not in ('*', '!'):
115 shadow.append(":".join(fields))
117 self.g.write('/etc/shadow', "\n".join(shadow) + '\n')
119 @sysprep('Fixing acpid powerdown action')
121 """Replace acpid powerdown action scripts to immediately shutdown the
122 system without checking if a GUI is running.
125 powerbtn_action = '#!/bin/sh\n\nPATH=/sbin:/bin:/usr/bin\n' \
126 'shutdown -h now "Power button pressed"\n'
128 events_dir = '/etc/acpi/events'
129 if not self.g.is_dir(events_dir):
130 self.out.warn("No acpid event directory found")
133 event_exp = re.compile('event=(.+)', re.I)
134 action_exp = re.compile('action=(.+)', re.I)
135 for events_file in self.g.readdir(events_dir):
136 if events_file['ftyp'] != 'r':
139 fullpath = "%s/%s" % (events_dir, events_file['name'])
142 for line in self.g.cat(fullpath).splitlines():
143 match = event_exp.match(line)
145 event = match.group(1)
147 match = action_exp.match(line)
149 action = match.group(1)
152 if event.strip() in ("button[ /]power", "button/power.*"):
154 if not self.g.is_file(action):
155 self.out.warn("Acpid action file: %s does not exist" %
158 self.g.copy_file_to_file(action,
159 "%s.orig.snf-image-creator-%d" %
160 (action, time.time()))
161 self.g.write(action, powerbtn_action)
164 self.out.warn("Acpid event file %s does not contain and "
167 elif event.strip() == ".*":
168 self.out.warn("Found action `.*'. Don't know how to handle "
169 "this. Please edit `%s' image file manually to "
170 "make the system immediatelly shutdown when an "
171 "power button acpi event occures." %
172 action.strip().split()[0])
175 self.out.warn("No acpi power button event found!")
177 @sysprep('Removing persistent network interface names')
178 def remove_persistent_net_rules(self):
179 """Remove udev rules that will keep network interface names persistent
180 after hardware changes and reboots. Those rules will be created again
181 the next time the image runs.
184 rule_file = '/etc/udev/rules.d/70-persistent-net.rules'
185 if self.g.is_file(rule_file):
188 @sysprep('Removing swap entry from fstab')
189 def remove_swap_entry(self):
190 """Remove swap entry from /etc/fstab. If swap is the last partition
191 then the partition will be removed when shrinking is performed. If the
192 swap partition is not the last partition in the disk or if you are not
193 going to shrink the image you should probably disable this.
197 fstab = self.g.cat('/etc/fstab')
198 for line in fstab.splitlines():
200 entry = line.split('#')[0].strip().split()
201 if len(entry) == 6 and entry[2] == 'swap':
204 new_fstab += "%s\n" % line
206 self.g.write('/etc/fstab', new_fstab)
208 @sysprep('Replacing fstab & grub non-persistent device references')
209 def use_persistent_block_device_names(self):
210 """Scan fstab & grub configuration files and replace all non-persistent
211 device references with UUIDs.
214 # convert all devices in fstab to persistent
215 persistent_root = self._persistent_fstab()
217 # convert all devices in grub1 to persistent
218 self._persistent_grub1(persistent_root)
220 def _persistent_grub1(self, new_root):
221 """Replaces non-persistent device name occurencies with persistent
222 ones in GRUB1 configuration files.
224 if self.g.is_file('/boot/grub/menu.lst'):
225 grub1 = '/boot/grub/menu.lst'
226 elif self.g.is_file('/etc/grub.conf'):
227 grub1 = '/etc/grub.conf'
231 self.g.aug_init('/', 0)
233 roots = self.g.aug_match('/files%s/title[*]/kernel/root' % grub1)
235 dev = self.g.aug_get(root)
236 if not self._is_persistent(dev):
237 # This is not always correct. Grub may contain root entries
238 # for other systems, but we only support 1 OS per hard
239 # disk, so this shouldn't harm.
240 self.g.aug_set(root, new_root)
245 def _persistent_fstab(self):
246 """Replaces non-persistent device name occurencies in /etc/fstab with
249 mpoints = self.g.mountpoints()
250 if len(mpoints) == 0:
251 pass # TODO: error handling
253 device_dict = dict([[mpoint, dev] for dev, mpoint in mpoints])
257 fstab = self.g.cat('/etc/fstab')
258 for line in fstab.splitlines():
260 line, dev, mpoint = self._convert_fstab_line(line, device_dict)
261 new_fstab += "%s\n" % line
266 self.g.write('/etc/fstab', new_fstab)
268 pass # TODO: error handling
272 def _convert_fstab_line(self, line, devices):
273 """Replace non-persistent device names in an fstab line to their UUID
277 line = line.split('#')[0].strip()
283 self.out.warn("Detected abnormal entry in fstab")
289 if not self._is_persistent(dev):
290 if mpoint in devices:
291 dev = "UUID=%s" % self._get_uuid(devices[mpoint])
294 # comment out the entry
295 entry[0] = "#%s" % dev
296 return " ".join(entry), dev, mpoint
298 return orig, dev, mpoint
300 def _do_collect_metadata(self):
301 """Collect metadata about the OS"""
302 super(Linux, self)._do_collect_metadata()
303 self.meta["USERS"] = " ".join(self._get_passworded_users())
305 # Delete the USERS metadata if empty
306 if not len(self.meta['USERS']):
307 self.out.warn("No passworded users found!")
308 del self.meta['USERS']
310 def _get_passworded_users(self):
311 """Returns a list of non-locked user accounts"""
313 regexp = re.compile('(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}')
315 for line in self.g.cat('/etc/shadow').splitlines():
316 match = regexp.match(line)
320 user, passwd = match.groups()
321 if len(passwd) > 0 and passwd[0] == '!':
322 self.out.warn("Ignoring locked %s account." % user)
328 def _is_persistent(self, dev):
329 """Checks if a device name is persistent."""
330 return not self._persistent.match(dev)
332 def _get_uuid(self, dev):
333 """Returns the UUID corresponding to a device"""
334 if dev in self._uuid:
335 return self._uuid[dev]
337 uuid = self.g.vfs_uuid(dev)
339 self._uuid[dev] = uuid
342 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :