# Copyright 2014 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import re import shutil from oslo.config import cfg from fuel_agent import errors from fuel_agent.openstack.common import log as logging from fuel_agent.utils import utils LOG = logging.getLogger(__name__) gu_opts = [ cfg.IntOpt( 'grub_timeout', default=5, help='Timeout in secs for GRUB' ), ] CONF = cfg.CONF CONF.register_opts(gu_opts) def guess_grub2_conf(chroot=''): for filename in ('/boot/grub/grub.cfg', '/boot/grub2/grub.cfg'): if os.path.isdir(os.path.dirname(chroot + filename)): return filename def guess_grub2_default(chroot=''): for filename in ('/etc/default/grub', '/etc/sysconfig/grub'): if os.path.isfile(chroot + filename): return filename def guess_grub2_mkconfig(chroot=''): for grub_mkconfig in \ ('/sbin/grub-mkconfig', '/sbin/grub2-mkconfig', '/usr/sbin/grub-mkconfig', '/usr/sbin/grub2-mkconfig'): if os.path.isfile(chroot + grub_mkconfig): return grub_mkconfig def guess_grub_version(chroot=''): grub_install = guess_grub_install(chroot=chroot) LOG.debug('Trying to run %s --version' % grub_install) cmd = [grub_install, '--version'] if chroot: cmd[:0] = ['chroot', chroot] result = utils.execute(*cmd) version = 1 if result[0].find('0.97') > 0 else 2 LOG.debug('Looks like grub version is %s' % version) return version def guess_grub(chroot=''): for grub in ('/sbin/grub', '/usr/sbin/grub'): LOG.debug('Looking for grub: trying %s' % grub) if os.path.isfile(chroot + grub): LOG.debug('grub found: %s' % grub) return grub raise errors.GrubUtilsError('grub not found') def guess_grub_install(chroot=''): for grub_install in ('/sbin/grub-install', '/sbin/grub2-install', '/usr/sbin/grub-install', '/usr/sbin/grub2-install'): LOG.debug('Looking for grub-install: trying %s' % grub_install) if os.path.isfile(chroot + grub_install): LOG.debug('grub-install found: %s' % grub_install) return grub_install raise errors.GrubUtilsError('grub-install not found') def guess_grub1_datadir(chroot='', arch='x86_64'): LOG.debug('Looking for grub data directory') for d in os.listdir(chroot + '/usr/share/grub'): if arch in d: LOG.debug('Looks like grub data directory ' 'is /usr/share/grub/%s' % d) return '/usr/share/grub/' + d def guess_kernel(chroot='', regexp=None): """Tries to guess kernel by regexp :param chroot: Path to chroot :param regexp: (String) Regular expression (must have python syntax). Default is r'^vmlinuz.*' """ kernel = utils.guess_filename( path=os.path.join(chroot, 'boot'), regexp=(regexp or r'^vmlinuz.*')) if kernel: return kernel raise errors.GrubUtilsError('Error while trying to find kernel: ' 'regexp=%s' % regexp) def guess_initrd(chroot='', regexp=None): """Tries to guess initrd by regexp :param chroot: Path to chroot :param regexp: (String) Regular expression (must have python syntax). Default is r'^(initrd|initramfs).*' """ initrd = utils.guess_filename( path=os.path.join(chroot, 'boot'), regexp=(regexp or r'^(initrd|initramfs).*')) if initrd: return initrd raise errors.GrubUtilsError('Error while trying to find initrd: ' 'regexp=%s' % regexp) def grub1_install(install_devices, boot_device, chroot=''): match = re.search(r'(.+?)(p?)(\d*)$', boot_device) # Checking whether boot device is a partition # !!! It must be a partition not a whole disk. !!! if not match.group(3): raise errors.GrubUtilsError( 'Error while installing legacy grub: ' 'boot device must be a partition') boot_disk = match.group(1) boot_part = str(int(match.group(3)) - 1) grub1_stage1(chroot=chroot) for install_device in install_devices: grub1_mbr(install_device, boot_disk, boot_part, chroot=chroot) def grub1_mbr(install_device, boot_disk, boot_part, chroot=''): # The device on which we are going to install # stage1 needs to be mapped as hd0, otherwise system won't be able to boot. batch = 'device (hd0) {0}\n'.format(install_device) # That is much easier to use grub-install, but unfortunately # it is not able to install bootloader on huge disks. # Instead we set drive geometry manually to avoid grub register # overlapping. We set it so as to make grub # thinking that disk size is equal to 1G. # 130 cylinders * (16065 * 512 = 8225280 bytes) = 1G # We also assume that boot partition is in the beginning # of disk between 0 and 1G. batch += 'geometry (hd0) 130 255 63\n' if boot_disk != install_device: batch += 'device (hd1) {0}\n'.format(boot_disk) batch += 'geometry (hd1) 130 255 63\n' batch += 'root (hd1,{0})\n'.format(boot_part) else: batch += 'root (hd0,{0})\n'.format(boot_part) batch += 'setup (hd0)\n' batch += 'quit\n' with open(chroot + '/tmp/grub.batch', 'wb') as f: LOG.debug('Grub batch content: \n%s' % batch) f.write(batch) script = 'cat /tmp/grub.batch | {0} --no-floppy --batch'.format( guess_grub(chroot=chroot)) with open(chroot + '/tmp/grub.sh', 'wb') as f: LOG.debug('Grub script content: \n%s' % script) f.write(script) os.chmod(chroot + '/tmp/grub.sh', 0o755) cmd = ['/tmp/grub.sh'] if chroot: cmd[:0] = ['chroot', chroot] stdout, stderr = utils.execute(*cmd, run_as_root=True, check_exit_code=[0]) LOG.debug('Grub script stdout: \n%s' % stdout) LOG.debug('Grub script stderr: \n%s' % stderr) def grub1_stage1(chroot=''): LOG.debug('Installing grub stage1 files') for f in os.listdir(chroot + '/boot/grub'): if f in ('stage1', 'stage2') or 'stage1_5' in f: LOG.debug('Removing: %s' % chroot + os.path.join('/boot/grub', f)) os.remove(chroot + os.path.join('/boot/grub', f)) grub1_datadir = guess_grub1_datadir(chroot=chroot) for f in os.listdir(chroot + grub1_datadir): if f in ('stage1', 'stage2') or 'stage1_5' in f: LOG.debug('Copying %s from %s to /boot/grub' % (f, grub1_datadir)) shutil.copy(chroot + os.path.join(grub1_datadir, f), chroot + os.path.join('/boot/grub', f)) def grub1_cfg(kernel=None, initrd=None, kernel_params='', chroot='', grub_timeout=CONF.grub_timeout): if not kernel: kernel = guess_kernel(chroot=chroot) if not initrd: initrd = guess_initrd(chroot=chroot) config = """ default=0 timeout={grub_timeout} title Default ({kernel}) kernel /{kernel} {kernel_params} initrd /{initrd} """.format(kernel=kernel, initrd=initrd, kernel_params=kernel_params, grub_timeout=grub_timeout) with open(chroot + '/boot/grub/grub.conf', 'wb') as f: f.write(config) def grub2_install(install_devices, chroot=''): grub_install = guess_grub_install(chroot=chroot) for install_device in install_devices: cmd = [grub_install, install_device] if chroot: cmd[:0] = ['chroot', chroot] utils.execute(*cmd, run_as_root=True, check_exit_code=[0]) def grub2_cfg(kernel_params='', chroot='', grub_timeout=CONF.grub_timeout): grub_defaults = chroot + guess_grub2_default(chroot=chroot) rekerparams = re.compile(r'^.*GRUB_CMDLINE_LINUX=.*') retimeout = re.compile(r'^.*GRUB_HIDDEN_TIMEOUT=.*') new_content = '' with open(grub_defaults) as f: for line in f: line = rekerparams.sub( 'GRUB_CMDLINE_LINUX="{kernel_params}"'. format(kernel_params=kernel_params), line) line = retimeout.sub('GRUB_HIDDEN_TIMEOUT={grub_timeout}'. format(grub_timeout=grub_timeout), line) new_content += line # NOTE(agordeev): explicitly add record fail timeout, in order to # prevent user confirmation appearing if unexpected reboot occured. new_content += '\nGRUB_RECORDFAIL_TIMEOUT={grub_timeout}\n'.\ format(grub_timeout=grub_timeout) with open(grub_defaults, 'wb') as f: f.write(new_content) cmd = [guess_grub2_mkconfig(chroot), '-o', guess_grub2_conf(chroot)] if chroot: cmd[:0] = ['chroot', chroot] utils.execute(*cmd, run_as_root=True)