# 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 import subp, util from cloudinit.config.schema import ( MetaSchema, get_meta_doc, validate_cloudconfig_schema, ) from cloudinit.settings import PER_ALWAYS NOBLOCK = "noblock" frequency = PER_ALWAYS distros = ["all"] meta: MetaSchema = { "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, } schema = { "type": "object", "properties": { "resize_rootfs": { "enum": [True, False, NOBLOCK], "description": dedent( """\ Whether to resize the root partition. Default: 'true'""" ), } }, } __doc__ = get_meta_doc(meta, 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/