# Copyright (C) 2011 Canonical Ltd. # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. # # Author: Scott Moser # Author: Juerg Haefliger # # This file is part of cloud-init. See LICENSE file for license information. """Resizefs: cloud-config module which resizes the filesystem""" import errno import os import stat from textwrap import dedent from cloudinit.config.schema import ( get_schema_doc, validate_cloudconfig_schema) from cloudinit.settings import PER_ALWAYS from cloudinit import subp from cloudinit import util NOBLOCK = "noblock" frequency = PER_ALWAYS distros = ['all'] schema = { 'id': 'cc_resizefs', 'name': 'Resizefs', 'title': 'Resize filesystem', 'description': dedent("""\ Resize a filesystem to use all avaliable space on partition. This module is useful along with ``cc_growpart`` and will ensure that if the root partition has been resized the root filesystem will be resized along with it. By default, ``cc_resizefs`` will resize the root partition and will block the boot process while the resize command is running. Optionally, the resize operation can be performed in the background while cloud-init continues running modules. This can be enabled by setting ``resize_rootfs`` to ``true``. This module can be disabled altogether by setting ``resize_rootfs`` to ``false``."""), 'distros': distros, 'examples': [ 'resize_rootfs: false # disable root filesystem resize operation'], 'frequency': PER_ALWAYS, 'type': 'object', 'properties': { 'resize_rootfs': { 'enum': [True, False, NOBLOCK], 'description': dedent("""\ Whether to resize the root partition. Default: 'true'""") } } } __doc__ = get_schema_doc(schema) # Supplement python help() def _resize_btrfs(mount_point, devpth): # If "/" is ro resize will fail. However it should be allowed since resize # makes everything bigger and subvolumes that are not ro will benefit. # Use a subvolume that is not ro to trick the resize operation to do the # "right" thing. The use of ".snapshot" is specific to "snapper" a generic # solution would be walk the subvolumes and find a rw mounted subvolume. if (not util.mount_is_read_write(mount_point) and os.path.isdir("%s/.snapshots" % mount_point)): return ('btrfs', 'filesystem', 'resize', 'max', '%s/.snapshots' % mount_point) else: return ('btrfs', 'filesystem', 'resize', 'max', mount_point) def _resize_ext(mount_point, devpth): return ('resize2fs', devpth) def _resize_xfs(mount_point, devpth): return ('xfs_growfs', mount_point) def _resize_ufs(mount_point, devpth): return ('growfs', '-y', mount_point) def _resize_zfs(mount_point, devpth): return ('zpool', 'online', '-e', mount_point, devpth) def _resize_hammer2(mount_point, devpth): return ('hammer2', 'growfs', mount_point) def _can_skip_resize_ufs(mount_point, devpth): # possible errors cases on the code-path to growfs -N following: # https://github.com/freebsd/freebsd/blob/HEAD/sbin/growfs/growfs.c # This is the "good" error: skip_start = "growfs: requested size" skip_contain = "is not larger than the current filesystem size" # growfs exits with 1 for almost all cases up to this one. # This means we can't just use rcs=[0, 1] as subp parameter: try: subp.subp(['growfs', '-N', devpth]) except subp.ProcessExecutionError as e: if e.stderr.startswith(skip_start) and skip_contain in e.stderr: # This FS is already at the desired size return True else: raise e return False # Do not use a dictionary as these commands should be able to be used # for multiple filesystem types if possible, e.g. one command for # ext2, ext3 and ext4. RESIZE_FS_PREFIXES_CMDS = [ ('btrfs', _resize_btrfs), ('ext', _resize_ext), ('xfs', _resize_xfs), ('ufs', _resize_ufs), ('zfs', _resize_zfs), ('hammer2', _resize_hammer2), ] RESIZE_FS_PRECHECK_CMDS = { 'ufs': _can_skip_resize_ufs } def can_skip_resize(fs_type, resize_what, devpth): fstype_lc = fs_type.lower() for i, func in RESIZE_FS_PRECHECK_CMDS.items(): if fstype_lc.startswith(i): return func(resize_what, devpth) return False def maybe_get_writable_device_path(devpath, info, log): """Return updated devpath if the devpath is a writable block device. @param devpath: Requested path to the root device we want to resize. @param info: String representing information about the requested device. @param log: Logger to which logs will be added upon error. @returns devpath or updated devpath per kernel commandline if the device path is a writable block device, returns None otherwise. """ container = util.is_container() # Ensure the path is a block device. if (devpath == "/dev/root" and not os.path.exists(devpath) and not container): devpath = util.rootdev_from_cmdline(util.get_cmdline()) if devpath is None: log.warning("Unable to find device '/dev/root'") return None log.debug("Converted /dev/root to '%s' per kernel cmdline", devpath) if devpath == 'overlayroot': log.debug("Not attempting to resize devpath '%s': %s", devpath, info) return None # FreeBSD zpool can also just use gpt/