From f29e7ac50446fa5ef0b638c50d2aaacadc4aa3a4 Mon Sep 17 00:00:00 2001 From: Alasdair Kergon Date: Mon, 17 Dec 2007 12:31:50 +0000 Subject: [PATCH] replace fsadm.c with fsadm.sh --- WHATS_NEW | 1 + scripts/fsadm.sh | 343 +++++++++++++++++++++++++++++++++++++++ tools/fsadm/Makefile.in | 31 ---- tools/fsadm/fsadm.c | 347 ---------------------------------------- 4 files changed, 344 insertions(+), 378 deletions(-) create mode 100644 scripts/fsadm.sh delete mode 100644 tools/fsadm/Makefile.in delete mode 100644 tools/fsadm/fsadm.c diff --git a/WHATS_NEW b/WHATS_NEW index d5eed1356..79f8210ab 100644 --- a/WHATS_NEW +++ b/WHATS_NEW @@ -1,5 +1,6 @@ Version 2.02.30 - =================================== + Replace tools/fsadm with scripts/fsadm.sh. Append fields to report/pvsegs_cols_verbose. Permit LV segment fields with PV segment reports. Add seg_start_pe and seg_pe_ranges to reports. diff --git a/scripts/fsadm.sh b/scripts/fsadm.sh new file mode 100644 index 000000000..1b673d8b2 --- /dev/null +++ b/scripts/fsadm.sh @@ -0,0 +1,343 @@ +#!/bin/sh +# +# Copyright (C) 2007 Red Hat, Inc. All rights reserved. +# +# This file is part of LVM2. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions +# of the GNU General Public License v.2. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Author: Zdenek Kabelac +# +# Script for resizing devices (usable for LVM resize) +# +# Needed utilities: +# mount, umount, grep, readlink, blockdev, blkid, fsck, xfs_check +# +# ext2/ext3: resize2fs, tune2fs +# reiserfs: resize_reiserfs, reiserfstune +# xfs: xfs_growfs, xfs_info +# + +TOOL=fsadm + +PATH=/sbin:/usr/sbin:/bin:/usr/sbin:$PATH + +# utilities +TUNE_EXT=tune2fs +RESIZE_EXT=resize2fs +TUNE_REISER=reiserfstune +RESIZE_REISER=resize_reiserfs +TUNE_XFS=xfs_info +RESIZE_XFS=xfs_growfs + +MOUNT=mount +UMOUNT=umount +MKDIR=mkdir +RM=rm +BLOCKDEV=blockdev +BLKID=blkid +GREP=grep +READLINK=readlink +FSCK=fsck +XFS_CHECK=xfs_check + +YES= +DRY=0 +VERB=0 +FORCE= +EXTOFF=0 +FSTYPE=unknown +VOLUME=unknown +TEMPDIR="${TMPDIR:-/tmp}/${TOOL}_${RANDOM}$$/m" +BLOCKSIZE= +BLOCKCOUNT= +MOUNTPOINT= +MOUNTED= +REMOUNT= + +IFS_OLD=$IFS + +tool_usage() { + echo "${TOOL}: Utility to resize underlying filesystem" + echo "Usage:" + echo " ${TOOL} [options] check|resize device [size]" + echo " -h | --help show this help" + echo " -v | --verbose be verbose" + echo " -f | --force forces to proceed" + echo " -e | --ext-offline unmount filesystem before Ext2/3 resize" + echo " -n | --dry-run print commands rather than running them" + echo " -y | --yes answer \"yes\" to automatically proceed" + echo " check run fsck" + echo " resize resize given device to new size" + echo " size in filesystem blocks" + echo " add B to specify Bytes (i.e.: 1000000B)" + echo " add K to specify KiloBytes (1024B)" + echo " add M to specify MegaBytes (1024KB)" + echo " add G to specify GigaBytes (1024MB)" + echo " add T to specify TeraBytes (1024GB)" + echo " (if unspecified full device is used)" + exit +} + +verbose() { + test "$VERB" -eq 1 && echo "$TOOL: $@" || true +} + +error() { + echo "$TOOL: $@" >&2 + cleanup 1 +} + +dry() { + verbose "Executing $@" + test "$DRY" -ne 0 && return 0 + $@ +} + +cleanup() { + trap '' 2 + # reset MOUNTPOINT - avoid recursion + test "$MOUNTPOINT" = "$TEMPDIR" && MOUNTPOINT="" temp_umount + if [ -n "$REMOUNT" ]; then + verbose "Remounting unmounted filesystem back" + dry $MOUNT "$VOLUME" "$MOUNTED" + fi + IFS=$IFS_OLD + trap 2 + exit $1 +} + +# convert parameters from Mega/Kilo/Bytes/Blocks +# and print number of bytes +decode_size() { + case "$1" in + *[tT]) NEWSIZE=$(( ${1%[tT]} * 1099511627776 )) ;; + *[gG]) NEWSIZE=$(( ${1%[gG]} * 1073741824 )) ;; + *[mM]) NEWSIZE=$(( ${1%[mM]} * 1048576 )) ;; + *[kK]) NEWSIZE=$(( ${1%[kK]} * 1024 )) ;; + *[bB]) NEWSIZE=${1%[bB]} ;; + *) NEWSIZE=$(( $1 * $2 )) ;; + esac + #NEWBLOCKCOUNT=$(round_block_size $NEWSIZE $2) + NEWBLOCKCOUNT=$(( $NEWSIZE / $2 )) +} + +# detect filesystem on the given device +# dereference device name if it is symbolic link +detect_fs() { + VOLUME=$($READLINK -e -n "$1") + # use /dev/null as cache file to be sure about the result + FSTYPE=$($BLKID -c /dev/null -o value -s TYPE "$VOLUME" || error "Cannot get FSTYPE of \"$VOLUME\"") + verbose "\"$FSTYPE\" filesystem found on \"$VOLUME\"" +} + +# check if the given device is already mounted and where +detect_mounted() { + MOUNTED=$($MOUNT | $GREP "$VOLUME") + MOUNTED=${MOUNTED##* on } + MOUNTED=${MOUNTED% type *} # allow type in the mount name + test -n "$MOUNTED" +} + +# get the full size of device in bytes +detect_device_size() { + DEVSIZE=$($BLOCKDEV --getsize64 "$VOLUME") || error "Cannot read device \"$VOLUME\"" +} + +# round up $1 / $2 +# could be needed to gaurantee 'at least given size' +# but it makes many troubles +round_up_block_size() { + echo $(( ($1 + $2 - 1) / $2 )) +} + +temp_mount() { + dry $MKDIR -p -m 0000 "$TEMPDIR" || error "Failed to create $TEMPDIR" + dry $MOUNT "$VOLUME" "$TEMPDIR" || error "Failed to mount $TEMPDIR" +} + +temp_umount() { + dry $UMOUNT "$TEMPDIR" && dry $RM -r "${TEMPDIR%%m}" || error "Failed to umount $TEMPDIR" +} + +yes_no() { + echo -n "$@? [Y|n] " + if [ -n "$YES" ]; then + ANS="y"; echo -n $ANS + else + read -n 1 ANS + fi + test -n "$ANS" && echo + case "$ANS" in + "y" | "Y" | "" ) return 0 ;; + esac + return 1 +} + +try_umount() { + yes_no "Do you want to unmount \"$MOUNTED\"" && dry $UMOUNT "$MOUNTED" && return 0 + error "Cannot proceed test with mounted filesystem \"$MOUNTED\"" +} + +validate_parsing() { + test -n "$BLOCKSIZE" -a -n "$BLOCKCOUNT" || error "Cannot parse $1 output" +} +#################################### +# Resize ext2/ext3 filesystem +# - unmounted or mounted for upsize +# - unmounted for downsize +#################################### +resize_ext() { + verbose "Parsing $TUNE_EXT -l \"$VOLUME\"" + for i in $($TUNE_EXT -l "$VOLUME"); do + case "$i" in + "Block size"*) BLOCKSIZE=${i##* } ;; + "Block count"*) BLOCKCOUNT=${i##* } ;; + esac + done + validate_parsing $TUNE_EXT + decode_size $1 $BLOCKSIZE + FSFORCE=$FORCE + + if [ $NEWBLOCKCOUNT -lt $BLOCKCOUNT -o $EXTOFF -eq 1 ]; then + detect_mounted && verbose "$RESIZE_EXT needs unmounted filesystem" && try_umount + REMOUNT=$MOUNTED + # CHECKME: after umount resize2fs requires fsck or -f flag. + FSFORCE="-f" + fi + + verbose "Resizing \"$VOLUME\" $BLOCKCOUNT -> $NEWBLOCKCOUNT blocks ($NEWSIZE bytes, bs:$BLOCKSIZE)" + dry $RESIZE_EXT $FSFORCE "$VOLUME" $NEWBLOCKCOUNT +} + +############################# +# Resize reiserfs filesystem +# - unmounted for upsize +# - unmounted for downsize +############################# +resize_reiser() { + detect_mounted + if [ -n "$MOUNTED" ]; then + verbose "ReiserFS resizes only unmounted filesystem" + try_umount + REMOUNT=$MOUNTED + fi + verbose "Parsing $TUNE_REISER \"$VOLUME\"" + for i in $($TUNE_REISER "$VOLUME"); do + case "$i" in + "Blocksize"*) BLOCKSIZE=${i##*: } ;; + "Count of blocks"*) BLOCKCOUNT=${i##*: } ;; + esac + done + validate_parsing $TUNE_REISER + decode_size $1 $BLOCKSIZE + verbose "Resizing \"$VOLUME\" $BLOCKCOUNT -> $NEWBLOCKCOUNT blocks ($NEWSIZE bytes, bs: $NEWBLOCKCOUNT)" + if [ -n "$YES" ]; then + dry echo y | $RESIZE_REISER -s $NEWSIZE "$VOLUME" + else + dry $RESIZE_REISER -s $NEWSIZE "$VOLUME" + fi +} + +######################## +# Resize XFS filesystem +# - mounted for upsize +# - can not downsize +######################## +resize_xfs() { + detect_mounted + MOUNTPOINT=$MOUNTED + if [ -z "$MOUNTED" ]; then + MOUNTPOINT=$TEMPDIR + temp_mount || error "Cannot mount Xfs filesystem" + fi + verbose "Parsing $TUNE_XFS \"$MOUNTPOINT\"" + for i in $($TUNE_XFS "$MOUNTPOINT"); do + case "$i" in + "data"*) BLOCKSIZE=${i##*bsize=} ; BLOCKCOUNT=${i##*blocks=} ;; + esac + done + BLOCKSIZE=${BLOCKSIZE%%[^0-9]*} + BLOCKCOUNT=${BLOCKCOUNT%%[^0-9]*} + validate_parsing $TUNE_XFS + decode_size $1 $BLOCKSIZE + if [ $NEWBLOCKCOUNT -gt $BLOCKCOUNT ]; then + verbose "Resizing Xfs mounted on \"$MOUNTPOINT\" to fill device \"$VOLUME\"" + dry $RESIZE_XFS $MOUNTPOINT + elif [ $NEWBLOCKCOUNT -eq $BLOCKCOUNT ]; then + verbose "Xfs filesystem already has the right size" + else + error "Xfs filesystem shrinking is unsupported" + fi +} + +#################### +# Resize filesystem +#################### +resize() { + detect_fs "$1" + detect_device_size + verbose "Device \"$VOLUME\" has $DEVSIZE bytes" + # if the size parameter is missing use device size + NEWSIZE=$2 + test -z $NEWSIZE && NEWSIZE=${DEVSIZE}b + trap cleanup 2 + #IFS=$'\n' # don't use bash-ism ?? + IFS="$(printf \"\\n\")" # needed for parsing output + case "$FSTYPE" in + "ext3"|"ext2") resize_ext $NEWSIZE ;; + "reiserfs") resize_reiser $NEWSIZE ;; + "xfs") resize_xfs $NEWSIZE ;; + *) error "Filesystem \"$FSTYPE\" on device \"$VOLUME\" is not supported by this tool" ;; + esac || error "Resize $FSTYPE failed" + cleanup +} + +################### +# Check filesystem +################### +check() { + detect_fs "$1" + case "$FSTYPE" in + "xfs") dry $XFS_CHECK "$VOLUME" ;; + *) dry $FSCK $YES "$VOLUME" ;; + esac +} + +############################# +# start point of this script +# - parsing parameters +############################# +if [ "$1" = "" ] ; then + tool_usage +fi + +while [ "$1" != "" ] +do + case "$1" in + "-h"|"--help") tool_usage ;; + "-v"|"--verbose") VERB=1 ;; + "-n"|"--dry-run") DRY=1 ;; + "-f"|"--force") FORCE="-f" ;; + "-e"|"--ext-offline") EXTOFF=1 ;; + "-y"|"--yes") YES="-y" ;; + "check") shift; CHECK=$1 ;; + "resize") shift; RESIZE=$1; shift; NEWSIZE=$1 ;; + *) error "Wrong argument \"$1\". (see: $TOOL --help)" + esac + shift +done + +if [ -n "$CHECK" ]; then + check "$CHECK" +elif [ -n "$RESIZE" ]; then + resize "$RESIZE" "$NEWSIZE" +else + error "Missing command. (see: $TOOL --help)" +fi diff --git a/tools/fsadm/Makefile.in b/tools/fsadm/Makefile.in deleted file mode 100644 index c72ae08cb..000000000 --- a/tools/fsadm/Makefile.in +++ /dev/null @@ -1,31 +0,0 @@ -# -# Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. -# Copyright (C) 2004 Red Hat, Inc. All rights reserved. -# -# This file is part of LVM2. -# -# This copyrighted material is made available to anyone wishing to use, -# modify, copy, or redistribute it subject to the terms and conditions -# of the GNU General Public License v.2. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -srcdir = @srcdir@ -top_srcdir = @top_srcdir@ -VPATH = @srcdir@ - -SOURCES = fsadm.c - -TARGETS = fsadm - -include $(top_srcdir)/make.tmpl - -fsadm: $(OBJECTS) - $(CC) -o $@ $(CFLAGS) $(OBJECTS) -rdynamic - -install: fsadm - $(INSTALL) -D $(OWNER) $(GROUP) -m 555 $(STRIP) fsadm \ - $(sbindir)/fsadm - diff --git a/tools/fsadm/fsadm.c b/tools/fsadm/fsadm.c deleted file mode 100644 index 619228ac7..000000000 --- a/tools/fsadm/fsadm.c +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright (C) 2004 Red Hat GmbH. All rights reserved. - * Copyright (C) 2007 Red Hat, Inc. All rights reserved. - * - * This file is part of LVM2. - * - * This copyrighted material is made available to anyone wishing to use, - * modify, copy, or redistribute it subject to the terms and conditions - * of the GNU General Public License v.2. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -/* - * FIXME: pass smart resizer arguments through from lvresize - * (eg, for xfs_growfs) - */ - -/* FIXME All funcs to return 0 on success or an error from errno.h on failure */ - -#define _GNU_SOURCE -#define _FILE_OFFSET_BITS 64 - -#define MAX_ARGS 8 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "util.h" -#include "last-path-component.h" - -#define log_error(str, x...) fprintf(stderr, "%s(%u): " str "\n", __FILE__, __LINE__, x) - -/* Filesystem related information */ -struct fsinfo { - struct fstab *fsent; - struct statfs statfs; - uint64_t new_size; - const char *cmd; -}; - -static void _usage(const char *cmd) -{ - log_error("Usage: %s [check | resize ]", - last_path_component(cmd)); -} - -/* FIXME Make this more robust - /proc, multiple mounts, TMPDIR + security etc. */ -/* FIXME Ensure filesystem is not mounted anywhere before running fsck/resize */ -/* Gather filesystem information (VFS type, special file and block size) */ -static int _get_fsinfo(const char *file, struct fsinfo *fsinfo) -{ - char *dir, template[] = "/tmp/fscmd_XXXXXX"; - struct stat info; - int ret = 0; - - if (stat(file, &info)) { - log_error("%s: stat failed: %s", file, strerror(errno)); - return errno; - } - - /* FIXME: are we limited to /etc/fstab entries ? */ - if (!(fsinfo->fsent = info.st_rdev ? getfsspec(file) : getfsfile(file))) { - log_error("%s: getfsspec/getfsfile failed: " - "Missing from /etc/fstab?", file); - return EINVAL; - } - - /* FIXME: any other way to retrieve fs blocksize avoiding mounting ? */ - if (!(dir = (mkdtemp(template)))) { - log_error("%s: mkdtemp failed: %s", template, strerror(errno)); - return errno; - } - - if (mount(fsinfo->fsent->fs_spec, dir, fsinfo->fsent->fs_vfstype, - MS_RDONLY, NULL)) { - log_error("%s: mount %s failed: %s", fsinfo->fsent->fs_spec, - dir, strerror(errno)); - ret = errno; - goto out; - } - - if (statfs(dir, &fsinfo->statfs)) { - log_error("%s: statfs failed: %s", dir, strerror(errno)); - ret = errno; - goto out1; - } - - out1: - if (umount(dir)) - log_error("%s: umount failed: %s", dir, strerror(errno)); - - out: - if (rmdir(dir)) - log_error("%s: rmdir failed: %s", dir, strerror(errno)); - - return ret; -} - -enum { - BLOCKS, - KILOBYTES -}; - -#define LEN 32 /* Length of temporary string */ - -/* Create size string in units of blocks or kilobytes - * (size expected in kilobytes) - */ -static char *_size_str(struct fsinfo *fsinfo, int unit) -{ - uint64_t new_size = fsinfo->new_size; - static char s[LEN]; - - /* Avoid switch() as long as there's only 2 cases */ - snprintf(s, LEN, "%" PRIu64, - unit == BLOCKS ? new_size / (fsinfo->statfs.f_bsize >> 10) : - new_size); - - return s; -} - -/* Allocate memory and store string updating string pointer */ -static int _store(char **arg, char **str) -{ - size_t len = 0; - char *s = *str; - - while (*s && *s != '%' && *(s++) != ' ') - len++; - - if ((*arg = (char *) malloc(len + 1))) { - strncpy(*arg, *str, len); - (*arg)[len] = '\0'; - *str = s - 1; - return 0; - } - - return 1; -} - -/* Construct a new argument vector for the real external command */ -static int _new_argv(char **new_argv, struct fsinfo *fsinfo) -{ - int i = 0, b = 0, d = 0, k = 0, m = 0; - char *s1; - char *s = (char *) fsinfo->cmd; - - if (!s || !strlen(s)) - return 1; - - do { - switch (*s) { - case ' ': - break; - - case '%': - s++; - - switch (tolower(*s)) { - case 'b': - s1 = _size_str(fsinfo, BLOCKS); - if (b++ + k || _store(&new_argv[i++], &s1)) - goto error; - break; - - case 'k': - s1 = _size_str(fsinfo, KILOBYTES); - if (b + k++ || _store(&new_argv[i++], &s1)) - goto error; - break; - - case 'd': - s1 = fsinfo->fsent->fs_spec; - if (d++ + m || _store(&new_argv[i++], &s1)) - goto error; - break; - - case 'm': - s1 = fsinfo->fsent->fs_file; - if (d + m++ || _store(&new_argv[i++], &s1)) - goto error; - break; - - default: - goto error; - } - - break; - - default: - if (_store(&new_argv[i++], &s)) - goto error; - } - } while (*++s); - - new_argv[i] = NULL; - - return 0; - - error: - new_argv[i] = NULL; - log_error("Failed constructing arguments for %s", s); - - return EINVAL; -} - -/* - * Get filesystem command arguments derived from a command definition string - * - * Command definition syntax: 'cmd [-option]{0,} [(option)argument]{0,}' - * - * (option)argument can be: '%{bdkm}' - * - * Command definition is parsed into argument strings of - * an argument vector with: - * - * %b replaced by the size in filesystem blocks - * %k replaced by the size in kilobytes - * %d replaced by the name of the device special of the LV - * %m replaced by the mountpoint of the filesystem - * - */ -static int _get_cmd(char *command, struct fsinfo *fsinfo) -{ - const char *vfstype = fsinfo->fsent->fs_vfstype; - struct fscmd { - const char *vfstype; - const char *fsck; - const char *fsresize; - } fscmds[] = { - { "ext2", "fsck -fy %d", "ext2resize %d %b"}, - {"ext3", "fsck -fy %d", "ext2resize %d %b"}, - {"reiserfs", "", "resize_reiserfs -s%k %d"}, - {"xfs", "", "xfs_growfs -D %b %m"}, /* simple xfs grow */ - {NULL, NULL, NULL}, - }, *p = &fscmds[0]; - - for (; p->vfstype; p++) { - if (!strcmp(vfstype, p->vfstype)) { - if (!strcmp(command, "resize")) - fsinfo->cmd = p->fsresize; - else if (!strcmp(command, "check")) - fsinfo->cmd = p->fsck; - else { - log_error("Unrecognised command: %s", command); - return EINVAL; - } - - break; - } - } - - if (!fsinfo->cmd) { - log_error("%s: Unrecognised filesystem type", vfstype); - return EINVAL; - } - - return 0; -} - -/* Collapse multiple slashes */ -static char *_collapse_slashes(char *path) -{ - char *s = path; - - /* Slight overhead but short ;) */ - while ((s = strchr(s, '/')) && *(s + 1)) - *(s + 1) == '/' ? memmove(s, s + 1, strlen(s)) : s++; - - return path; -} - -/* Free the argument array */ -static void _free_argv(char **new_argv) -{ - int i; - - for (i = 0; new_argv[i]; i++) - free(new_argv[i]); -} - -/* - * check/resize a filesystem - */ -int main(int argc, char **argv) -{ - int ret = 0; - struct fsinfo fsinfo; - char *new_argv[MAX_ARGS]; - char *command, *path; - - if (argc < 3) - goto error; - - command = argv[1]; - path = _collapse_slashes(argv[2]); - - if (!strcmp(command, "resize")) { - if (argc != 4) - goto error; - /* FIXME sanity checks */ - fsinfo.new_size = strtoul(argv[3], NULL, 10); - } else if (argc != 3) - goto error; - - /* Retrieve filesystem information (block size...) */ - if ((ret = _get_fsinfo(path, &fsinfo))) { - log_error("Can't get filesystem information from %s", path); - return ret; - } - - /* Get filesystem command info */ - if ((ret = _get_cmd(command, &fsinfo))) { - log_error("Can't get filesystem command for %s", command); - return ret; - } - - if (_new_argv(new_argv, &fsinfo)) - return EINVAL; - - if (!new_argv[0]) - return 0; - - execvp(new_argv[0], new_argv); - ret = errno; - log_error("%s: execvp %s failed", new_argv[0], strerror(errno)); - _free_argv(new_argv); - - return ret; - - error: - _usage(argv[0]); - return EINVAL; -}