=== modified file 'cloudinit/config/cc_resizefs.py' --- cloudinit/config/cc_resizefs.py 2012-11-20 06:05:36 +0000 +++ cloudinit/config/cc_resizefs.py 2013-01-13 20:12:12 +0000 @@ -27,9 +27,21 @@ frequency = PER_ALWAYS +def _resize_btrfs(mount_point, devpth, tmp_devpth, log): + return ('btrfs', 'filesystem', 'resize', 'max', mount_point) + +def _resize_ext(mount_point, devpth, tmp_devpth, log): + nodeify_path(tmp_devpth, resize_what, log) + return ('resize2fs', tmp_devpth) + +def _resize_xfs(mount_point, devpth, tmp_devpth, log): + nodeify_path(tmp_devpth, resize_what, log) + return ('xfs_growfs', tmp_devpth) + RESIZE_FS_PREFIXES_CMDS = [ - ('ext', 'resize2fs'), - ('xfs', 'xfs_growfs'), + ('btrfs', _resize_btrfs), + ('ext', _resize_ext), + ('xfs', _resize_xfs), ] NOBLOCK = "noblock" @@ -50,19 +62,85 @@ raise -def get_fs_type(st_dev, path, log): - try: - dev_entries = util.find_devs_with(tag='TYPE', oformat='value', - no_cache=True, path=path) - if not dev_entries: - return None - return dev_entries[0].strip() - except util.ProcessExecutionError: - util.logexc(log, ("Failed to get filesystem type" - " of maj=%s, min=%s for path %s"), - os.major(st_dev), os.minor(st_dev), path) - raise - +def get_fs_devpth_and_type(path, log): + # Use /proc/$$/mountinfo to find the device where path is mounted. + # This is done because with a btrfs filesystem using os.stat(path) + # does not return the ID of the device. + # + # Here, / has a device of 18 (decimal). + # + # $ stat / + # File: '/' + # Size: 234 Blocks: 0 IO Block: 4096 directory + # Device: 12h/18d Inode: 256 Links: 1 + # Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) + # Access: 2013-01-13 07:31:04.358011255 +0000 + # Modify: 2013-01-13 18:48:25.930011255 +0000 + # Change: 2013-01-13 18:48:25.930011255 +0000 + # Birth: - + # + # Find where / is mounted: + # + # $ mount | grep ' / ' + # /dev/vda1 on / type btrfs (rw,subvol=@,compress=lzo) + # + # And the device ID for /dev/vda1 is not 18: + # + # $ ls -l /dev/vda1 + # brw-rw---- 1 root disk 253, 1 Jan 13 08:29 /dev/vda1 + # + # So use /proc/$$/mountinfo to find the device underlying the + # input path. + fs_type = None + devpth = None + match_mount_elements = None + path_elements = [e for e in path.split('/') if e] + mountinfo_path = '/proc/%s/mountinfo' % os.getpid() + with open(mountinfo_path) as f: + for line in f.readlines(): + # Look for - and use the field two to the right. + parts = line.split() + + mount = parts[4] + mount_elements = [e for e in mount.split('/') if e] + + # Ignore mounts deeper than the path in question. + if len(mount_elements) > len(path_elements): + continue + + # Ignore mounts where the common path is not the same. + l = min(len(mount_elements), len(path_elements)) + if mount_elements[0:l] != path_elements[0:l]: + continue + + # Ignore mount points higher than an already seen mount + # point. + if (match_mount_elements is not None and + len(match_mount_elements) > len(mount_elements)): + continue + + # Find the '-' which terminates a list of optional columns + # to find the path to the device and the mount point. See + # man 5 proc for the format of this file. + try: + i = parts.index('-') + except ValueError: + log.debug("Did not find column named '-' in %s", + mountinfo_path) + return None + + # Get the path to the device. + try: + fs_type = parts[i+1] + devpth = parts[i+2] + except IndexError, e: + log.debug("Too few columns in %s after '-' column", + mountinfo_path) + return None + + match_mount_elements = mount_elements + + return (devpth, fs_type) def handle(name, cfg, _cloud, log, args): if len(args) != 0: @@ -82,7 +160,7 @@ resize_what = "/" with util.ExtendedTemporaryFile(prefix="cloudinit.resizefs.", dir=resize_root_d, delete=True) as tfh: - devpth = tfh.name + tmp_devpth = tfh.name # Delete the file so that mknod will work # but don't change the file handle to know that its @@ -91,12 +169,15 @@ # auto deletion tfh.unlink_now() - st_dev = nodeify_path(devpth, resize_what, log) - fs_type = get_fs_type(st_dev, devpth, log) - if not fs_type: + result = get_fs_devpth_and_type(resize_what, log) + if not result: log.warn("Could not determine filesystem type of %s", resize_what) return + (devpth, fs_type) = result + + st_dev = os.stat(devpth).st_rdev + resizer = None fstype_lc = fs_type.lower() for (pfix, root_cmd) in RESIZE_FS_PREFIXES_CMDS: @@ -109,8 +190,9 @@ fs_type, resize_what) return - log.debug("Resizing %s (%s) using %s", resize_what, fs_type, resizer) - resize_cmd = [resizer, devpth] + resize_cmd = resizer(resize_what, devpth, tmp_devpth, log) + log.debug("Resizing %s (%s) using %s", resize_what, fs_type, + ' '.join(resize_cmd)) if resize_root == NOBLOCK: # Fork to a child that will run