From: Andreas Dilger <andreas.dilger@intel.com>
Subject: [PATCH] LU-10801 utils: fix lfs_migrate argument parsing
Date: March 9, 2018 at 22:57:57 MST
To: <andreas.dilger@intel.com>

The argument parsing is broken sometimes.

Signed-off-by: Andreas Dilger <andreas.dilger@intel.com>
Change-Id: I79361c13940b2047182b80ec7ec245f3ddfcba83
---
lustre/doc/lfs_migrate.1   |  85 +++++++++++-----------
lustre/scripts/lfs_migrate | 176 +++++++++++++++------------------------------
lustre/utils/lfs.c         |  26 +++----
3 files changed, 116 insertions(+), 171 deletions(-)

diff --git a/lustre/doc/lfs_migrate.1 b/lustre/doc/lfs_migrate.1
index 4390740..12a519a 100644
--- a/lustre/doc/lfs_migrate.1
+++ b/lustre/doc/lfs_migrate.1
@@ -1,29 +1,32 @@
 .TH lfs_migrate 1 "Dec 19, 2017" Lustre "utilities"
 .SH NAME
 .B lfs_migrate
-\- simple tool to migrate files between Lustre OSTs
+\- migrate files between Lustre OSTs
 .SH SYNOPSIS
 .B lfs_migrate
 .RB [ --dry-run ]
-.RB [ -D ]
-.RB [ -h ]
+.RB [ --help|-h ]
 .RB [ --no-rsync | --rsync ]
-.RB [ -q ]
-.RB [ -R ]
-.RB [ -s ]
-.RB [ -v ]
-.RB [ -y ]
+.RB [ --quiet|-q ]
+.RB [ --restripe|-R ]
+.RB [ --skip|-s ]
+.RB [ --verbose|-v ]
+.RB [ --yes|-y ]
 .RB [ -0 ]
 .RI [ FILE | DIR ]...
 .br
 .SH DESCRIPTION
 .B lfs_migrate
-is a simple tool to assist migration of files between Lustre OSTs.  It
-is simply copying each specified file to a new file, verifying the file
-contents have not changed, and then renaming the new file back to the
-original filename.  This allows balancing space usage between OSTs, moving
-files off OSTs that are starting to show hardware problems (though are still
-functional), or OSTs that will be removed from the filesystem.
+is a tool to assist migration of files between Lustre OSTs, possibly also
+restriping the files as it goes.  It copies each specified file to a new
+file, verifies the file contents have not changed, and then replaces the
+original file with the new file (either atomically via
+.BR lfs-migrate (1)
+on Lustre 2.5 and later, or
+.BR mv (1)
+on older versions of Lustre.  This allows balancing space usage between
+OSTs, moving files off OSTs that are starting to show hardware problems
+but are still functional, or OSTs that will be removed from the filesystem.
 .PP
 Files to be migrated can be specified as command-line arguments.  If a
 directory is specified on the command-line then all files within that
@@ -31,16 +34,22 @@ directory are migrated.  If no files are specified on the command-line,
 then a list of files is read from the standard input, making
 .B lfs_migrate
 suitable for use with
-.BR lfs (1) " find"
-to locate files on specific OSTs and/or matching other file attributes.
+.BR lfs-find (1)
+to locate files on specific OSTs and/or matching other file attributes,
+or any other tools that generate a list of files.
 .PP
-Any options and arguments not explicitly recognized by the script are passed
-through to the
+Any options and arguments not explicitly recognized by
+.B lfs_migrate
+are passed through to the underlying
 .B lfs migrate
 command, see
-.BR lfs-migrate (1).
-To maintain backward compatibility, the \fI-n \fRoption is used by the script
-for a dry-run, and is not passed to
+.BR lfs-migrate (1)
+and
+.BR lfs-setstripe (1)
+for a complete list of options.
+.PP
+To maintain backward compatibility, the \fI-n \fRoption is used by the
+script to indicate a dry-run (no modifications made), and is not passed to
 .B lfs migrate
 as the non-block option.  To specify non-block, use the long option
 .IR --non-block .
@@ -59,10 +68,7 @@ or OST index of a new file).
 .B \\--dry-run
 Only print the names of files to be migrated.
 .TP
-.B \\-D
-Do not use direct I/O to copy file contents.
-.TP
-.B \\-h
+.B \\--help|-h
 Display help information.
 .TP
 .B \\--no-rsync
@@ -70,7 +76,7 @@ Do not fall back to using rsync if
 .BR lfs (1) " migrate" " fails."
 Cannot be used at the same time as \fI--rsync\fR.
 .TP
-.B \\-q
+.B \\--quiet|-q
 Run quietly (don't print filenames or status).
 .TP
 .B \\--rsync
@@ -78,21 +84,21 @@ Force rsync to be used instead of
 .BR lfs (1) " migrate" .
 May not be used at the same time as \fI--no-rsync\fR.
 .TP
-.B \\-R
+.B \\--restripe|-R
 Restripe file using default directory striping instead of keeping striping.
 This option may not be specified at the same time as the -c or -S options
 (these options are passed through to
 .BR "lfs migrate" ,
 and are therefore not listed here).
 .TP
-.B \\-s
+.B \\--skip|-s
 Skip file data comparison after migrate.  Default is to compare migrated file
 against original to verify correctness.
 .TP
-.B \\-v
+.B \\--verbose|-v
 Show verbose debug messages.
 .TP
-.B \\-y
+.B \\--yes|-y
 Answer 'y' to usage warning without prompting (for scripts, use with caution).
 .TP
 .B \\-0
@@ -108,11 +114,14 @@ To migrate files within the
 .I /testfs
 filesystem on OST0004 (perhaps because it is much more full than other OSTs),
 larger than 4GB (because it is more efficient to just migrate large files),
-and older than two days (to avoid files that are in use, though this is NOT
-a guarantee the files are not being modified, that is workload specific):
+and older than two days (to avoid files that are in use, though this is NOT a
+guarantee the files are not being modified, that is workload specific) after
+disabling file creation on testfs-OST0004 (this is needed on all MDS nodes):
 .IP
-lfs find /testfs -obd test-OST0004 -size +4G -mtime +2d |
-    lfs_migrate -y
+mds# lctl set_param osp.testfs-OST0004*.max_create_count=0
+client# lfs find /testfs -obd testfs-OST0004 -size +4G -mtime +2d |
+            lfs_migrate -y
+mds# lctl set_param osp.testfs-OST0004*.max_create_count=20000
 .SH NOTES
 In versions prior to 2.5,
 .B lfs_migrate
@@ -121,17 +130,11 @@ is
 closely integrated with the MDS, and cannot determine whether a file
 is currently open and/or in-use by other applications or nodes.  That makes
 it
-.B
-UNSAFE
+.B UNSAFE
 for use on files that might be modified by other applications, since the
 migrated file is only a copy of the current file. This will result in the
 old file becoming an open-unlinked file, and any modifications to that file
 will be lost.
-.SH KNOWN BUGS
-Eventually, this functionality will be integrated into
-.BR lfs (1)
-itself and will integrate with the MDS layout locking to make it safe
-in the presence of opened files and ongoing file IO.
 .SH AVAILABILITY
 .B lfs_migrate
 is part of the 
 diff --git a/lustre/scripts/lfs_migrate b/lustre/scripts/lfs_migrate
 index 306326a..65d2d4c 100755
--- a/lustre/scripts/lfs_migrate
+++ b/lustre/scripts/lfs_migrate
@@ -13,7 +13,7 @@
 # to be 100% safe the administrator needs to ensure this is safe.

 RSYNC=${RSYNC:-rsync}
-LFS_MIGRATE_RSYNC_MODE=${LFS_MIGRATE_RSYNC_MODE:-false}
+OPT_RSYNC=${LFS_MIGRATE_RSYNC_MODE:-false}
 ECHO=echo
 LFS=${LFS:-lfs}
 RSYNC_WITH_HLINKS=false
@@ -21,6 +21,7 @@ LFS_MIGRATE_TMP=${TMPDIR:-/tmp}
 MIGRATED_SET="$(mktemp ${LFS_MIGRATE_TMP}/lfs_migrate.links.XXXXXX)"
 NEWNAME=""
 REMOVE_FID='s/^\[[0-9a-fx:]*\] //'
+PROG=$(basename $0)

 add_to_set() {
 	local old_fid="$1"
@@ -44,22 +45,24 @@ old_fid_in_set() {

 usage() {
     cat -- <<USAGE 1>&2
-usage: lfs_migrate [--dry-run] [-D] [-h] [--no-rsync|--rsync] [-q] [-R]
-		  [-s] [-v] [-y] [-0] [FILE|DIR...]
-	--dry-run only print the names of files to be migrated
-	-D do not use direct I/O to copy file contents
-	-h show this usage message
+usage: lfs_migrate [--dry-run] [--help|-h] [--no-rsync|--rsync] [--quiet|-q]
+		  [--restripe|-R] [--skip|-s] [--verbose|-v] [--yes|-y] [-0]
+		  [FILE|DIR...]
+	--dry-run  only print the names of files to be migrated
+	-h         show this usage message
 	--no-rsync do not fall back to rsync mode even if lfs migrate fails
-	-q run quietly (don't print filenames or status)
-	--rsync force rsync mode instead of using lfs migrate
-	-R restripe file using default directory striping
-	-s skip file data comparison after migrate
-	-v show verbose debug messages
-	-y answer 'y' to usage question
-	-0 input file names on stdin are separated by a null character
-
-The -c <stripe_count> and -S <stripe_size> options may not be specified at
-the same time as the -R option.
+	-q         run quietly (don't print filenames or status)
+	--rsync    force rsync mode instead of using lfs migrate
+	-R         restripe file using default directory striping
+	-s         skip file data comparison after migrate
+	-v         show verbose debug messages
+	-y         answer 'y' to usage question
+	-0         input file names on stdin are separated by a null character
+
+If the --restripe|-R option is used, other "lfs setstripe" layout options
+such as -E, -c, -S, --copy, and --yaml may not be specified at the same time.
+Only the --block, --non-block, --non-direct, and --verbose non-layout
+setstripe options may be used in that case.

 The --rsync and --no-rsync options may not be specified at the same time.

@@ -84,124 +87,60 @@ cleanup() {

 trap cleanup EXIT

+OPT_BLOCK=false
 OPT_CHECK=true
 OPT_DEBUG=false
-OPT_NO_RSYNC=false
 OPT_DRYRUN=false
-OPT_YES=false
+OPT_NO_RSYNC=false
+OPT_NO_DIRECT=false
 OPT_RESTRIPE=false
 OPT_NULL=false
 OPT_PASSTHROUGH=()
+OPT_FILE=()
+OPT_LAYOUT=()
+OPT_YES=false
 STRIPE_COUNT=""
 STRIPE_SIZE=""
 POOL=""
-LFS_OPT_DIRECTIO=""

 # Examine any long options and arguments.  getopts does not support long
 # options, so they must be stripped out and classified as either options
 # for the script, or passed through to "lfs migrate".
-LONG_OPT=false
-SHORT_OPT=false
-OPTS=()
-
-for f in $(seq 1 $#); do
-	arg=${!f}
-	if [ "${arg:0:2}" = "--" ]; then
-		SHORT_OPT=false
-		if [ "$arg" = "--block" ]; then
-			BLOCK="$arg"
-			OPT_YES=true
-		elif [ "$arg" = "--non-block" ]; then
-			BLOCK="$arg"
-		elif [ "$arg" = "--dry-run" ]; then
-			OPT_DRYRUN=true
-			OPT_YES=true
-		elif [ "$arg" = "--rsync" ]; then
-			LFS_MIGRATE_RSYNC_MODE=true
-		elif [ "$arg" = "--no-rsync" ]; then
-			OPT_NO_RSYNC=true
-			OPT_YES=true
-		else
-			LONG_OPT=true
-			OPT_PASSTHROUGH+=("$arg")
-			PREV="$arg"
-		fi
-	elif [ "${arg:0:1}" = "-" ]; then
-		LONG_OPT=false
-		if [ "$arg" == "-b" ]; then
-			BLOCK="$arg"
-		else
-			SHORT_OPT=true
-			OPTS+=("$arg")
-			PREV="$arg"
-		fi
-	elif $LONG_OPT; then
-		LONG_OPT=false
-		# This will prevent long options from having file name
-		# arguments, but allows long options with no arguments to work.
-		if [ -f "$arg" -o -d "$arg" ]; then
-			OPTS+=("$arg")
-		else
-			[ "$PREV" = "--stripe-count" ] &&
-				STRIPE_COUNT="$arg"
-			[ "$PREV" = "--stripe-size" ] &&
-				STRIPE_SIZE="$arg"
-			[ "$PREV" = "--pool" ] &&
-				POOL="$arg"
-			OPT_PASSTHROUGH+=("$arg")
-		fi
-	elif $SHORT_OPT; then
-		[ "$PREV" = "-c" ] &&
-			STRIPE_COUNT="$arg"
-		[ "$PREV" = "-S" ] &&
-			STRIPE_SIZE="$arg"
-		[ "$PREV" = "-p" ] &&
-			POOL="$arg"
-		SHORT_OPT=false
-		OPTS+=("$arg")
-	else
-		OPTS+=("$arg")
-	fi
-done

-# Reset the argument list to include only the short options and file names
-set -- "${OPTS[@]}"
-
-while getopts ":DhlnqRsvy0" opt $*; do
-    case $opt in
-	D) LFS_OPT_DIRECTIO="-D";;
-	h) usage;;
-	l) ;; # maintained for backward compatibility
-	n) OPT_DRYRUN=true
-	  OPT_YES=true
-	  echo "$(basename $0): -n deprecated, use --dry-run instead" 1>&2
-	  echo "$(basename $0): to specify non-block, use --non-block instead" 1>&2;;
-	q) ECHO=:;;
-	R) OPT_RESTRIPE=true;;
-	s) OPT_CHECK=false;;
-	v) OPT_DEBUG=true; ECHO=echo; OPT_PASSTHROUGH+=("-v");;
-	y) OPT_YES=true;;
-	0) OPT_NULL=true;;
-	*) # Pass through any unrecognized options to 'lfs migrate'
-	  OPT_PASSTHROUGH+=("-$OPTARG")
-	  if [[ ${!OPTIND:0:1} != "-" && ! -f "${!OPTIND}" &&
-		! -d "${!OPTIND}" ]]; then
-		OPT_PASSTHROUGH+=("${!OPTIND}")
-		((OPTIND++))
-	  fi;;
-    esac
+while [ -n "$*" ]; do
+	arg="$1"
+	case "$arg" in
+	-h|--help) usage;;
+	-l|--link) ;; # maintained backward compatibility for now
+	-n|--dry-run) OPT_DRYRUN=true; OPT_YES=true
+	   echo "$PROG: -n deprecated, use --dry-run or --non-block" 1>&2;;
+	-q|--quiet) ECHO=:;;
+	-R|--restripe) OPT_RESTRIPE=true;;
+	-s|--skip) OPT_CHECK=false;;
+	-v|--verbose) OPT_DEBUG=true; ECHO=echo; OPT_PASSTHROUGH+=("$arg");;
+	-y|--yes) OPT_YES=true;;
+	-0) OPT_NULL=true;;
+	-b|--block|--non-block|--non-direct|--no-verify)
+	   # Always pass non-layout options to 'lfs migrate'
+	   OPT_PASSTHROUGH+=("$arg");;
+	--copy|--yaml|--file)
+	   # these options have files as arguments, pass both through
+	   OPT_LAYOUT+=("$arg $2"); shift;;
+	*)  # Pass other non-file layout options to 'lfs migrate'
+	   [ -e "$arg" ] && OPT_FILE+=("$arg") || OPT_LAYOUT+=("$arg");;
+	esac
+	shift
 done
-shift $((OPTIND - 1))

-if $OPT_RESTRIPE && [[ "$STRIPE_COUNT" || "$STRIPE_SIZE" ]]; then
-	echo "$(basename $0): Options -c <stripe_count> and -S <stripe_size> "\
-	"may not be specified at the same time as the -R option." 1>&2
+
+if $OPT_RESTRIPE && [ -n $OPT_LAYOUT ]; then
+	echo "$PROG: Options $OPT_LAYOUT cannot be used with the -R option" 1>&2
 	exit 1
 fi

-if $LFS_MIGRATE_RSYNC_MODE && $OPT_NO_RSYNC; then
-	echo "$(basename $0): Options --rsync and --no-rsync may not be "\
-	"specified at the same time." 1>&2
+if $OPT_RSYNC && $OPT_NO_RSYNC; then
+	echo "$PROG: Options --rsync and --no-rsync may not be" \
+		"specified at the same time." 1>&2
 	exit 1
 fi
 
@@ -236,8 +175,6 @@ lfs_migrate() {
 		local hlinks=()
 		local stripe_size="$STRIPE_SIZE"
 		local stripe_count="$STRIPE_COUNT"
-		local parent_count=""
-		local parent_size=""
 
 		$ECHO -n "$OLDNAME: "
 
@@ -329,6 +266,9 @@ lfs_migrate() {
 		fi
 
 		if $OPT_DEBUG; then
+			local parent_count
+			local parent_size
+
 			if $OPT_RESTRIPE; then
 				parent_count=$($LFS getstripe -c \
 					      $(dirname "$OLDNAME") 2> \
@@ -378,7 +318,7 @@ lfs_migrate() {
 		# first try to migrate via Lustre tools, then fall back to rsync
 		if ! $LFS_MIGRATE_RSYNC_MODE; then
 			if $LFS migrate "${OPT_PASSTHROUGH[@]}" ${BLOCK} \
-			  $LFS_OPT_DIRECTIO ${stripe_count} ${stripe_size} \
+			  ${stripe_count} ${stripe_size} \
 			   "$OLDNAME" &> /dev/null; then
 				$ECHO "done migrate"
 				for link in ${hlinks[*]}; do
diff --git a/lustre/utils/lfs.c b/lustre/utils/lfs.c
index 1219665..d0337f5 100644
--- a/lustre/utils/lfs.c
+++ b/lustre/utils/lfs.c
@@ -2507,6 +2507,7 @@ static int lfs_setstripe_internal(int argc, char **argv,
 	char *template = NULL;
 
 	struct option long_opts[] = {
+/* find { .val = '0',	.name = "null",		.has_arg = no_argument }, */
 /* find	{ .val = 'A',	.name = "atime",	.has_arg = required_argument }*/
  	/* --block is only valid in migrate mode */
 	{ .val = 'b',	.name = "block",	.has_arg = no_argument },
@@ -2550,6 +2545,7 @@ static int lfs_setstripe_internal(int argc, char **argv,
 /* find	{ .val = 'F',	.name = "fid",		.has_arg = no_argument }, */
 /* find	{ .val = 'g',	.name = "gid",		.has_arg = no_argument }, */
 /* find	{ .val = 'G',	.name = "group",	.has_arg = required_argument }*/
+/* find	{ .val = 'h',	.name = "help",		.has_arg = no_argument }, */
 /* dirstripe { .val = 'H', .name = "mdt-hash",  .has_arg = required_argument }*/
 	{ .val = 'i',	.name = "stripe-index",	.has_arg = required_argument},
 	{ .val = 'i',	.name = "stripe_index",	.has_arg = required_argument},
-- 
1.8.0