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 @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.image.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.image.g.readdir(events_dir):
136 if events_file['ftyp'] != 'r':
139 fullpath = "%s/%s" % (events_dir, events_file['name'])
142 for line in self.image.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.image.g.is_file(action):
155 self.out.warn("Acpid action file: %s does not exist" %
158 self.image.g.copy_file_to_file(
159 action, "%s.orig.snf-image-creator-%d" %
160 (action, time.time()))
161 self.image.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.image.g.is_file(rule_file):
186 self.image.g.rm(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.image.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.image.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.image.g.is_file('/boot/grub/menu.lst'):
225 grub1 = '/boot/grub/menu.lst'
226 elif self.image.g.is_file('/etc/grub.conf'):
227 grub1 = '/etc/grub.conf'
231 self.image.g.aug_init('/', 0)
233 roots = self.image.g.aug_match(
234 '/files%s/title[*]/kernel/root' % grub1)
236 dev = self.image.g.aug_get(root)
237 if not self._is_persistent(dev):
238 # This is not always correct. Grub may contain root entries
239 # for other systems, but we only support 1 OS per hard
240 # disk, so this shouldn't harm.
241 self.image.g.aug_set(root, new_root)
243 self.image.g.aug_save()
244 self.image.g.aug_close()
246 def _persistent_fstab(self):
247 """Replaces non-persistent device name occurencies in /etc/fstab with
250 mpoints = self.image.g.mountpoints()
251 if len(mpoints) == 0:
252 pass # TODO: error handling
254 device_dict = dict([[mpoint, dev] for dev, mpoint in mpoints])
258 fstab = self.image.g.cat('/etc/fstab')
259 for line in fstab.splitlines():
261 line, dev, mpoint = self._convert_fstab_line(line, device_dict)
262 new_fstab += "%s\n" % line
267 self.image.g.write('/etc/fstab', new_fstab)
269 pass # TODO: error handling
273 def _convert_fstab_line(self, line, devices):
274 """Replace non-persistent device names in an fstab line to their UUID
278 line = line.split('#')[0].strip()
284 self.out.warn("Detected abnormal entry in fstab")
290 if not self._is_persistent(dev):
291 if mpoint in devices:
292 dev = "UUID=%s" % self._get_uuid(devices[mpoint])
295 # comment out the entry
296 entry[0] = "#%s" % dev
297 return " ".join(entry), dev, mpoint
299 return orig, dev, mpoint
301 def _do_collect_metadata(self):
302 """Collect metadata about the OS"""
303 super(Linux, self)._do_collect_metadata()
304 self.meta["USERS"] = " ".join(self._get_passworded_users())
306 # Delete the USERS metadata if empty
307 if not len(self.meta['USERS']):
308 self.out.warn("No passworded users found!")
309 del self.meta['USERS']
311 def _get_passworded_users(self):
312 """Returns a list of non-locked user accounts"""
314 regexp = re.compile(r'(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}')
316 for line in self.image.g.cat('/etc/shadow').splitlines():
317 match = regexp.match(line)
321 user, passwd = match.groups()
322 if len(passwd) > 0 and passwd[0] == '!':
323 self.out.warn("Ignoring locked %s account." % user)
329 def _is_persistent(self, dev):
330 """Checks if a device name is persistent."""
331 return not self._persistent.match(dev)
333 def _get_uuid(self, dev):
334 """Returns the UUID corresponding to a device"""
335 if dev in self._uuid:
336 return self._uuid[dev]
338 uuid = self.image.g.vfs_uuid(dev)
340 self._uuid[dev] = uuid
343 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :