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(enabled=False)
52 def remove_user_accounts(self, print_header=True):
53 """Remove all user accounts with id greater than 1000"""
56 self.out.output("Removing all user accounts with id greater than "
59 if 'USERS' not in self.meta:
62 # Remove users from /etc/passwd
65 metadata_users = self.meta['USERS'].split()
66 for line in self.g.cat('/etc/passwd').splitlines():
67 fields = line.split(':')
68 if int(fields[2]) > 1000:
69 removed_users[fields[0]] = fields
70 # remove it from the USERS metadata too
71 if fields[0] in metadata_users:
72 metadata_users.remove(fields[0])
74 passwd.append(':'.join(fields))
76 self.meta['USERS'] = " ".join(metadata_users)
78 # Delete the USERS metadata if empty
79 if not len(self.meta['USERS']):
80 del self.meta['USERS']
82 self.g.write('/etc/passwd', '\n'.join(passwd) + '\n')
84 # Remove the corresponding /etc/shadow entries
86 for line in self.g.cat('/etc/shadow').splitlines():
87 fields = line.split(':')
88 if fields[0] not in removed_users:
89 shadow.append(':'.join(fields))
91 self.g.write('/etc/shadow', "\n".join(shadow) + '\n')
93 # Remove the corresponding /etc/group entries
95 for line in self.g.cat('/etc/group').splitlines():
96 fields = line.split(':')
97 # Remove groups tha have the same name as the removed users
98 if fields[0] not in removed_users:
99 group.append(':'.join(fields))
101 self.g.write('/etc/group', '\n'.join(group) + '\n')
103 # Remove home directories
104 for home in [field[5] for field in removed_users.values()]:
105 if self.g.is_dir(home) and home.startswith('/home/'):
109 def cleanup_passwords(self, print_header=True):
110 """Remove all passwords and lock all user accounts"""
113 self.out.output("Cleaning up passwords & locking all user "
118 for line in self.g.cat('/etc/shadow').splitlines():
119 fields = line.split(':')
120 if fields[1] not in ('*', '!'):
123 shadow.append(":".join(fields))
125 self.g.write('/etc/shadow', "\n".join(shadow) + '\n')
128 def fix_acpid(self, print_header=True):
129 """Replace acpid powerdown action scripts to immediately shutdown the
130 system without checking if a GUI is running.
134 self.out.output('Fixing acpid powerdown action')
136 powerbtn_action = '#!/bin/sh\n\nPATH=/sbin:/bin:/usr/bin\n' \
137 'shutdown -h now "Power button pressed"\n'
139 events_dir = '/etc/acpi/events'
140 if not self.g.is_dir(events_dir):
141 self.out.warn("No acpid event directory found")
144 event_exp = re.compile('event=(.+)', re.I)
145 action_exp = re.compile('action=(.+)', re.I)
146 for events_file in self.g.readdir(events_dir):
147 if events_file['ftyp'] != 'r':
150 fullpath = "%s/%s" % (events_dir, events_file['name'])
153 for line in self.g.cat(fullpath).splitlines():
154 match = event_exp.match(line)
156 event = match.group(1)
158 match = action_exp.match(line)
160 action = match.group(1)
163 if event.strip() in ("button[ /]power", "button/power.*"):
165 if not self.g.is_file(action):
166 self.out.warn("Acpid action file: %s does not exist" %
169 self.g.copy_file_to_file(action,
170 "%s.orig.snf-image-creator-%d" %
171 (action, time.time()))
172 self.g.write(action, powerbtn_action)
175 self.out.warn("Acpid event file %s does not contain and "
178 elif event.strip() == ".*":
179 self.out.warn("Found action `.*'. Don't know how to handle "
180 "this. Please edit `%s' image file manually to "
181 "make the system immediatelly shutdown when an "
182 "power button acpi event occures." %
183 action.strip().split()[0])
186 self.out.warn("No acpi power button event found!")
189 def remove_persistent_net_rules(self, print_header=True):
190 """Remove udev rules that will keep network interface names persistent
191 after hardware changes and reboots. Those rules will be created again
192 the next time the image runs.
196 self.out.output('Removing persistent network interface names')
198 rule_file = '/etc/udev/rules.d/70-persistent-net.rules'
199 if self.g.is_file(rule_file):
203 def remove_swap_entry(self, print_header=True):
204 """Remove swap entry from /etc/fstab. If swap is the last partition
205 then the partition will be removed when shrinking is performed. If the
206 swap partition is not the last partition in the disk or if you are not
207 going to shrink the image you should probably disable this.
211 self.out.output('Removing swap entry from fstab')
214 fstab = self.g.cat('/etc/fstab')
215 for line in fstab.splitlines():
217 entry = line.split('#')[0].strip().split()
218 if len(entry) == 6 and entry[2] == 'swap':
221 new_fstab += "%s\n" % line
223 self.g.write('/etc/fstab', new_fstab)
226 def use_persistent_block_device_names(self, print_header=True):
227 """Scan fstab & grub configuration files and replace all non-persistent
228 device references with UUIDs.
232 self.out.output("Replacing fstab & grub non-persistent device "
235 # convert all devices in fstab to persistent
236 persistent_root = self._persistent_fstab()
238 # convert all devices in grub1 to persistent
239 self._persistent_grub1(persistent_root)
241 def _persistent_grub1(self, new_root):
242 """Replaces non-persistent device name occurencies with persistent
243 ones in GRUB1 configuration files.
245 if self.g.is_file('/boot/grub/menu.lst'):
246 grub1 = '/boot/grub/menu.lst'
247 elif self.g.is_file('/etc/grub.conf'):
248 grub1 = '/etc/grub.conf'
252 self.g.aug_init('/', 0)
254 roots = self.g.aug_match('/files%s/title[*]/kernel/root' % grub1)
256 dev = self.g.aug_get(root)
257 if not self._is_persistent(dev):
258 # This is not always correct. Grub may contain root entries
259 # for other systems, but we only support 1 OS per hard
260 # disk, so this shouldn't harm.
261 self.g.aug_set(root, new_root)
266 def _persistent_fstab(self):
267 """Replaces non-persistent device name occurencies in /etc/fstab with
270 mpoints = self.g.mountpoints()
271 if len(mpoints) == 0:
272 pass # TODO: error handling
274 device_dict = dict([[mpoint, dev] for dev, mpoint in mpoints])
278 fstab = self.g.cat('/etc/fstab')
279 for line in fstab.splitlines():
281 line, dev, mpoint = self._convert_fstab_line(line, device_dict)
282 new_fstab += "%s\n" % line
287 self.g.write('/etc/fstab', new_fstab)
289 pass # TODO: error handling
293 def _convert_fstab_line(self, line, devices):
294 """Replace non-persistent device names in an fstab line to their UUID
298 line = line.split('#')[0].strip()
304 self.out.warn("Detected abnormal entry in fstab")
310 if not self._is_persistent(dev):
311 if mpoint in devices:
312 dev = "UUID=%s" % self._get_uuid(devices[mpoint])
315 # comment out the entry
316 entry[0] = "#%s" % dev
317 return " ".join(entry), dev, mpoint
319 return orig, dev, mpoint
321 def _do_collect_metadata(self):
322 """Collect metadata about the OS"""
323 super(Linux, self)._do_collect_metadata()
324 self.meta["USERS"] = " ".join(self._get_passworded_users())
326 # Delete the USERS metadata if empty
327 if not len(self.meta['USERS']):
328 self.out.warn("No passworded users found!")
329 del self.meta['USERS']
331 def _get_passworded_users(self):
332 """Returns a list of non-locked user accounts"""
334 regexp = re.compile('(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}')
336 for line in self.g.cat('/etc/shadow').splitlines():
337 match = regexp.match(line)
341 user, passwd = match.groups()
342 if len(passwd) > 0 and passwd[0] == '!':
343 self.out.warn("Ignoring locked %s account." % user)
349 def _is_persistent(self, dev):
350 """Checks if a device name is persistent."""
351 return not self._persistent.match(dev)
353 def _get_uuid(self, dev):
354 """Returns the UUID corresponding to a device"""
355 if dev in self._uuid:
356 return self._uuid[dev]
358 uuid = self.g.vfs_uuid(dev)
360 self._uuid[dev] = uuid
363 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :