diff mbox

xfs_io: fix exitcode handling (was Re: generic/399 and xfs_io pwrite command)

Message ID 20171206002638.GB5858@dastard (mailing list archive)
State New, archived
Headers show

Commit Message

Dave Chinner Dec. 6, 2017, 12:26 a.m. UTC
On Wed, Dec 06, 2017 at 08:18:50AM +1100, Dave Chinner wrote:
> On Tue, Dec 05, 2017 at 04:23:43PM +0200, Ari Sundholm wrote:
> > On 12/05/2017 12:28 AM, Dave Chinner wrote:
> > >On Thu, Nov 30, 2017 at 04:21:47PM +0200, Ari Sundholm wrote:
> > >>Hi!
> > >>
> > >>While debugging an issue, we found out that generic/399 seems to
> > >>rely on a behavior that is specific to ext4 in the following section
> > >>of code:
> > >>----------------8<--------snip--------8<------------------------------
> > >>#
> > >># Write files of 1 MB of all the same byte until we hit ENOSPC.
> > >>Note that we
> > >># must not create sparse files, since the contents of sparse files are not
> > >># stored on-disk.  Also, we create multiple files rather than one big file
> > >># because we want to test for reuse of per-file keys.
> > >>#
> > >>total_file_size=0
> > >>i=1
> > >>while true; do
> > >>	file=$SCRATCH_MNT/encrypted_dir/file$i
> > >>	if ! $XFS_IO_PROG -f $file -c 'pwrite 0 1M' &> $tmp.out; then
> > >>		if ! grep -q 'No space left on device' $tmp.out; then
> > >>			echo "FAIL: unexpected pwrite failure"
> > >>			cat $tmp.out
> > >>		elif [ -e $file ]; then
> > >>			total_file_size=$((total_file_size + $(stat -c %s $file)))
> > >>		fi
> > >>		break
> > >>	fi
> > >>	total_file_size=$((total_file_size + $(stat -c %s $file)))
> > >>	i=$((i + 1))
> > >>	if [ $i -gt $fs_size_in_mb ]; then
> > >>		echo "FAIL: filesystem never filled up!"
> > >>		break
> > >>	fi
> > >>done
> > >>----------------8<--------snip--------8<------------------------------
> > >>
> > >>What happens with ext4 is that the xfs_io command gives a nonzero
> > >>exit value not when the pwrite command fails with ENOSPC but during
> > >>the *next* iteration when opening the file fails with ENOSPC. Turns
> > >>out the pwrite command failing does not cause xfs_io to give a
> > >>nonzero exit value.
> > >
> > >That implies ext4 is returning zero bytes written to the pwrite()
> > >call rather than ENOSPC. i.e.:
> > >
> > >                 bytes = do_pwrite(file->fd, off, cnt, buffersize,
> > >                                 pwritev2_flags);
> > >                 if (bytes == 0)
> > >                         break;
> > >                 if (bytes < 0) {
> > >                         perror("pwrite");
> > >                         return -1;
> > >                 }
> > >
> > >So if it's exiting with no error, then we can't have got an error
> > >from ext4 at ENOSPC. If that's the case, it probably should be
> > >considered an ext4 bug, not an issue with xfs_io...
> > >
> > 
> > No, according to what we've observed, that is not what happens. The
> > pwrite() call does fail and errno is ENOSPC after the call. The
> > immediate problem is that xfs_io does not reflect this failure in
> > its exit value and thus the check in generic/399 does not work in
> > this case. Only when open() fails during the next iteration does
> > xfs_io give a nonzero exit value and cause the check in the test
> > case to allow the test case to end successfully.
> 
> Ah, io/pwrite.c fails to set exitcode on failure iappropriately
> before returning. Looks like there is a bunch of xfs_io commands
> that fail to do this properly.
> 
> > What is specific to ext4 here is, as stated in my original message,
> > that open() fails.
> 
> Because there are no more inodes to allocate (i.e. inode table is
> full) and so attempts to create more fail with ENOSPC. The xfs_io
> open command set exitcode appropriately, and that's why it finally
> triggers a test abort.
> 
> Looks like xfs_io does need fixing to set exitcode appropriately on
> all failures...

Try the patch below.

Cheers,

Dave.

Comments

Brian Foster Dec. 7, 2017, 2:05 p.m. UTC | #1
On Wed, Dec 06, 2017 at 11:26:38AM +1100, Dave Chinner wrote:
> On Wed, Dec 06, 2017 at 08:18:50AM +1100, Dave Chinner wrote:
> > On Tue, Dec 05, 2017 at 04:23:43PM +0200, Ari Sundholm wrote:
> > > On 12/05/2017 12:28 AM, Dave Chinner wrote:
> > > >On Thu, Nov 30, 2017 at 04:21:47PM +0200, Ari Sundholm wrote:
...
> 
> xfs_io: set exitcode on failure appropriately
> 
> From: Dave Chinner <dchinner@redhat.com>
> 
> Many operations don't set the exitcode when they fail, resulting
> in xfs_io exiting with a zero (no failure) exit code despite the
> command failing and returning an error. Fix this.
> 
> Signed-Off-By: Dave Chinner <dchinner@redhat.com>
> ---
...
> diff --git a/io/copy_file_range.c b/io/copy_file_range.c
> index d1dfc5a5e33a..9b737eff4c02 100644
> --- a/io/copy_file_range.c
> +++ b/io/copy_file_range.c
> @@ -92,6 +92,7 @@ copy_range_f(int argc, char **argv)
>  	int ret;
>  	int fd;
>  
> +	exitcode = 1;
>  	while ((opt = getopt(argc, argv, "s:d:l:")) != -1) {
>  		switch (opt) {
>  		case 's':
> @@ -132,6 +133,8 @@ copy_range_f(int argc, char **argv)
>  
>  	ret = copy_file_range(fd, &src, &dst, len);
>  	close(fd);
> +	if (ret >= 0)
> +		exitcode = 0;

I don't think it's appropriate to blindly overwrite the global exitcode
value like this. What about the case where multiple commands are chained
together? Should we expect an error code if any of the commands failed
or only the last?

For example:

  xfs_io -c "pwrite ..." <file>

... returns an error if the write fails, while:

  xfs_io -c "pwrite ..." -c "fadvise ..." <file>

... would never so long as the fadvise succeeds.

Brian

>  	return ret;
>  }
>  
> diff --git a/io/cowextsize.c b/io/cowextsize.c
> index c4cd6de24da5..d5872449cb60 100644
> --- a/io/cowextsize.c
> +++ b/io/cowextsize.c
> @@ -53,6 +53,7 @@ get_cowextsize(const char *path, int fd)
>  	if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) {
>  		printf("%s: XFS_IOC_FSGETXATTR %s: %s\n",
>  			progname, path, strerror(errno));
> +		exitcode = 1;
>  		return 0;
>  	}
>  	printf("[%u] %s\n", fsx.fsx_cowextsize, path);
> @@ -67,11 +68,13 @@ set_cowextsize(const char *path, int fd, long extsz)
>  
>  	if (fstat64(fd, &stat) < 0) {
>  		perror("fstat64");
> +		exitcode = 1;
>  		return 0;
>  	}
>  	if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) {
>  		printf("%s: XFS_IOC_FSGETXATTR %s: %s\n",
>  			progname, path, strerror(errno));
> +		exitcode = 1;
>  		return 0;
>  	}
>  
> @@ -86,6 +89,7 @@ set_cowextsize(const char *path, int fd, long extsz)
>  	if ((xfsctl(path, fd, FS_IOC_FSSETXATTR, &fsx)) < 0) {
>  		printf("%s: XFS_IOC_FSSETXATTR %s: %s\n",
>  			progname, path, strerror(errno));
> +		exitcode = 1;
>  		return 0;
>  	}
>  
> @@ -168,6 +172,7 @@ cowextsize_f(
>  		if (cowextsize < 0) {
>  			printf(_("non-numeric cowextsize argument -- %s\n"),
>  				argv[optind]);
> +			exitcode = 1;
>  			return 0;
>  		}
>  	} else {
> diff --git a/io/encrypt.c b/io/encrypt.c
> index 26ab97ce614b..3351e0ff8666 100644
> --- a/io/encrypt.c
> +++ b/io/encrypt.c
> @@ -192,6 +192,7 @@ set_encpolicy_f(int argc, char **argv)
>  	struct fscrypt_policy policy;
>  
>  	/* Initialize the policy structure with default values */
> +	exitcode = 1;
>  	memset(&policy, 0, sizeof(policy));
>  	policy.contents_encryption_mode = FS_ENCRYPTION_MODE_AES_256_XTS;
>  	policy.filenames_encryption_mode = FS_ENCRYPTION_MODE_AES_256_CTS;
> @@ -269,10 +270,10 @@ set_encpolicy_f(int argc, char **argv)
>  	if (ioctl(file->fd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) < 0) {
>  		fprintf(stderr, "%s: failed to set encryption policy: %s\n",
>  			file->name, strerror(errno));
> -		exitcode = 1;
>  		return 0;
>  	}
>  
> +	exitcode = 0;
>  	return 0;
>  }
>  
> diff --git a/io/fadvise.c b/io/fadvise.c
> index 46174f34680e..9cc91a5712e1 100644
> --- a/io/fadvise.c
> +++ b/io/fadvise.c
> @@ -54,6 +54,7 @@ fadvise_f(
>  	off64_t		offset = 0, length = 0;
>  	int		c, range = 0, advise = POSIX_FADV_NORMAL;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "dnrsw")) != EOF) {
>  		switch (c) {
>  		case 'd':	/* Don't need these pages */
> @@ -107,6 +108,7 @@ fadvise_f(
>  		perror("fadvise");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> diff --git a/io/fiemap.c b/io/fiemap.c
> index bdcfacdb2811..d97f53ae8d79 100644
> --- a/io/fiemap.c
> +++ b/io/fiemap.c
> @@ -238,6 +238,7 @@ fiemap_f(
>  	__u64		last_logical = 0;
>  	struct stat	st;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "aln:v")) != EOF) {
>  		switch (c) {
>  		case 'a':
> @@ -263,7 +264,6 @@ fiemap_f(
>  	if (!fiemap) {
>  		fprintf(stderr, _("%s: malloc of %d bytes failed.\n"),
>  			progname, map_size);
> -		exitcode = 1;
>  		return 0;
>  	}
>  
> @@ -282,7 +282,6 @@ fiemap_f(
>  			fprintf(stderr, "%s: ioctl(FS_IOC_FIEMAP) [\"%s\"]: "
>  				"%s\n", progname, file->name, strerror(errno));
>  			free(fiemap);
> -			exitcode = 1;
>  			return 0;
>  		}
>  
> @@ -332,7 +331,6 @@ fiemap_f(
>  		fprintf(stderr, "%s: fstat failed: %s\n", progname,
>  			strerror(errno));
>  		free(fiemap);
> -		exitcode = 1;
>  		return 0;
>  	}
>  
> @@ -341,6 +339,7 @@ fiemap_f(
>  			   BTOBBT(last_logical), BTOBBT(st.st_size));
>  
>  out:
> +	exitcode = 0;
>  	free(fiemap);
>  	return 0;
>  }
> diff --git a/io/file.c b/io/file.c
> index 349b19cdc420..2afb42e60620 100644
> --- a/io/file.c
> +++ b/io/file.c
> @@ -79,6 +79,7 @@ file_f(
>  	i = atoi(argv[1]);
>  	if (i < 0 || i >= filecount) {
>  		printf(_("value %d is out of range (0-%d)\n"), i, filecount-1);
> +		exitcode = 1;
>  	} else {
>  		file = &filetable[i];
>  		filelist_f();
> diff --git a/io/fsmap.c b/io/fsmap.c
> index 448fb5356466..4e3ae7ca2bab 100644
> --- a/io/fsmap.c
> +++ b/io/fsmap.c
> @@ -404,6 +404,7 @@ fsmap_f(
>  	bool			dumped_flags = false;
>  	int			dflag, lflag, rflag;
>  
> +	exitcode = 1;
>  	init_cvtnum(&fsblocksize, &fssectsize);
>  
>  	dflag = lflag = rflag = 0;
> @@ -466,7 +467,6 @@ fsmap_f(
>  			fprintf(stderr,
>  				_("%s: can't get geometry [\"%s\"]: %s\n"),
>  				progname, file->name, strerror(errno));
> -			exitcode = 1;
>  			return 0;
>  		}
>  	}
> @@ -476,7 +476,6 @@ fsmap_f(
>  	if (head == NULL) {
>  		fprintf(stderr, _("%s: malloc of %zu bytes failed.\n"),
>  			progname, fsmap_sizeof(map_size));
> -		exitcode = 1;
>  		return 0;
>  	}
>  
> @@ -509,7 +508,6 @@ fsmap_f(
>  				progname, head->fmh_iflags, file->name,
>  				strerror(errno));
>  			free(head);
> -			exitcode = 1;
>  			return 0;
>  		}
>  		if (head->fmh_entries > map_size + 2) {
> @@ -548,7 +546,6 @@ fsmap_f(
>  				progname, head->fmh_iflags, file->name,
>  				strerror(errno));
>  			free(head);
> -			exitcode = 1;
>  			return 0;
>  		}
>  
> @@ -571,6 +568,7 @@ fsmap_f(
>  	if (dumped_flags)
>  		dump_verbose_key();
>  
> +	exitcode = 0;
>  	free(head);
>  	return 0;
>  }
> diff --git a/io/fsync.c b/io/fsync.c
> index 9fe5e2f50c62..61061c401a04 100644
> --- a/io/fsync.c
> +++ b/io/fsync.c
> @@ -31,6 +31,7 @@ fsync_f(
>  {
>  	if (fsync(file->fd) < 0) {
>  		perror("fsync");
> +		exitcode = 1;
>  		return 0;
>  	}
>  	return 0;
> @@ -43,6 +44,7 @@ fdatasync_f(
>  {
>  	if (fdatasync(file->fd) < 0) {
>  		perror("fdatasync");
> +		exitcode = 1;
>  		return 0;
>  	}
>  	return 0;
> diff --git a/io/getrusage.c b/io/getrusage.c
> index cf1f2afd19a8..9fc51709a73e 100644
> --- a/io/getrusage.c
> +++ b/io/getrusage.c
> @@ -62,6 +62,7 @@ getrusage_f(
>  
>  	if (getrusage(RUSAGE_SELF, &rusage) < 0) {
>  		perror("getrusage");
> +		exitcode = 1;
>  		return 0;
>  	}
>  
> diff --git a/io/imap.c b/io/imap.c
> index f52238e0c450..410e1662b76c 100644
> --- a/io/imap.c
> +++ b/io/imap.c
> @@ -39,8 +39,10 @@ imap_f(int argc, char **argv)
>  		nent = atoi(argv[1]);
>  
>  	t = malloc(nent * sizeof(*t));
> -	if (!t)
> +	if (!t) {
> +		exitcode = 1;
>  		return 0;
> +	}
>  
>  	bulkreq.lastip  = &last;
>  	bulkreq.icount  = nent;
> diff --git a/io/inject.c b/io/inject.c
> index 964ebfe13bd6..db0795023a64 100644
> --- a/io/inject.c
> +++ b/io/inject.c
> @@ -156,6 +156,7 @@ inject_f(
>  			command = XFS_IOC_ERROR_CLEARALL;
>  		if ((xfsctl(file->name, file->fd, command, &error)) < 0) {
>  			perror("XFS_IOC_ERROR_INJECTION");
> +			exitcode = 1;
>  			continue;
>  		}
>  	}
> diff --git a/io/link.c b/io/link.c
> index 9b2e8a970942..55bb806024eb 100644
> --- a/io/link.c
> +++ b/io/link.c
> @@ -47,6 +47,7 @@ flink_f(
>  
>  	if (linkat(file->fd, "", AT_FDCWD, argv[1], AT_EMPTY_PATH) < 0) {
>  		perror("flink");
> +		exitcode = 1;
>  		return 0;
>  	}
>  	return 0;
> diff --git a/io/madvise.c b/io/madvise.c
> index 1d8b53cb516f..fbfe35cc1c13 100644
> --- a/io/madvise.c
> +++ b/io/madvise.c
> @@ -57,6 +57,7 @@ madvise_f(
>  	int		advise = MADV_NORMAL, c;
>  	size_t		blocksize, sectsize;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "drsw")) != EOF) {
>  		switch (c) {
>  		case 'd':	/* Don't need these pages */
> @@ -111,6 +112,7 @@ madvise_f(
>  		perror("madvise");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> diff --git a/io/mincore.c b/io/mincore.c
> index 9e0d3a620319..09a165737705 100644
> --- a/io/mincore.c
> +++ b/io/mincore.c
> @@ -37,6 +37,7 @@ mincore_f(
>  	unsigned char	*vec;
>  	int		i;
>  
> +	exitcode = 1;
>  	if (argc == 1) {
>  		offset = mapping->offset;
>  		length = mapping->length;
> @@ -106,6 +107,7 @@ mincore_f(
>  			(unsigned long)(current - previous));
>  
>  	free(vec);
> +	exitcode = 0;
>  	return 0;
>  }
>  
> diff --git a/io/mmap.c b/io/mmap.c
> index 7a8150e3d517..b0c1f764b8c4 100644
> --- a/io/mmap.c
> +++ b/io/mmap.c
> @@ -121,6 +121,7 @@ mapset_f(
>  	i = atoi(argv[1]);
>  	if (i < 0 || i >= mapcount) {
>  		printf("value %d is out of range (0-%d)\n", i, mapcount);
> +		exitcode = 1;
>  	} else {
>  		mapping = &maptable[i];
>  		maplist_f();
> @@ -163,6 +164,7 @@ mmap_f(
>  	size_t		blocksize, sectsize;
>  	int		c, prot = 0;
>  
> +	exitcode = 1;
>  	if (argc == 1) {
>  		if (mapping)
>  			return maplist_f();
> @@ -263,6 +265,7 @@ mmap_f(
>  	mapping->offset = offset;
>  	mapping->name = filename;
>  	mapping->prot = prot;
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -294,6 +297,7 @@ msync_f(
>  	int		c, flags = 0;
>  	size_t		blocksize, sectsize;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "ais")) != EOF) {
>  		switch (c) {
>  		case 'a':
> @@ -336,9 +340,12 @@ msync_f(
>  	if (!start)
>  		return 0;
>  
> -	if (msync(start, length, flags) < 0)
> +	if (msync(start, length, flags) < 0) {
>  		perror("msync");
> +		return 0;
> +	}
>  
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -380,6 +387,7 @@ mread_f(
>  	int		dump = 0, rflag = 0, c;
>  	size_t		blocksize, sectsize;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "frv")) != EOF) {
>  		switch (c) {
>  		case 'f':
> @@ -467,6 +475,7 @@ mread_f(
>  			}
>  		}
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -478,6 +487,7 @@ munmap_f(
>  	ssize_t		length;
>  	unsigned int	offset;
>  
> +	exitcode = 1;
>  	if (munmap(mapping->addr, mapping->length) < 0) {
>  		perror("munmap");
>  		return 0;
> @@ -503,6 +513,7 @@ munmap_f(
>  		mapping = maptable = NULL;
>  	}
>  	maplist_f();
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -538,6 +549,7 @@ mwrite_f(
>  	int		c;
>  	size_t		blocksize, sectsize;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "rS:")) != EOF) {
>  		switch (c) {
>  		case 'r':
> @@ -590,6 +602,7 @@ mwrite_f(
>  			((char *)mapping->addr)[tmp] = seed;
>  	}
>  
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -622,6 +635,7 @@ mremap_f(
>  	int		c;
>  	size_t		blocksize, sectsize;
>  
> +	exitcode = 1;
>  	init_cvtnum(&blocksize, &sectsize);
>  
>  	while ((c = getopt(argc, argv, "f:m")) != EOF) {
> @@ -655,13 +669,14 @@ mremap_f(
>  	else
>  		new_addr = mremap(mapping->addr, mapping->length,
>  		                  new_length, flags, new_addr);
> -	if (new_addr == MAP_FAILED)
> +	if (new_addr == MAP_FAILED) {
>  		perror("mremap");
> -	else {
> -		mapping->addr = new_addr;
> -		mapping->length = new_length;
> +		return 0;
>  	}
>  
> +	mapping->addr = new_addr;
> +	mapping->length = new_length;
> +	exitcode = 0;
>  	return 0;
>  }
>  #endif /* HAVE_MREMAP */
> diff --git a/io/open.c b/io/open.c
> index 1abcb2ff2a01..0523f68263ec 100644
> --- a/io/open.c
> +++ b/io/open.c
> @@ -209,6 +209,7 @@ open_f(
>  	struct xfs_fsop_geom	geometry = { 0 };
>  	struct fs_path	fsp;
>  
> +	exitcode = 1;
>  	if (argc == 1) {
>  		if (file)
>  			return stat_f(argc, argv);
> @@ -277,7 +278,10 @@ open_f(
>  	if (!platform_test_xfs_fd(fd))
>  		flags |= IO_FOREIGN;
>  
> -	addfile(argv[optind], fd, &geometry, flags, &fsp);
> +	if (addfile(argv[optind], fd, &geometry, flags, &fsp) != 0)
> +		return 0;
> +
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -289,6 +293,7 @@ close_f(
>  	size_t		length;
>  	unsigned int	offset;
>  
> +	exitcode = 1;
>  	if (close(file->fd) < 0) {
>  		perror("close");
>  		return 0;
> @@ -314,6 +319,7 @@ close_f(
>  		file = filetable = NULL;
>  	}
>  	filelist_f();
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -346,9 +352,12 @@ lsproj_callback(
>  	if ((fd = open(path, O_RDONLY)) == -1) {
>  		fprintf(stderr, _("%s: cannot open %s: %s\n"),
>  			progname, path, strerror(errno));
> +		exitcode = 1;
>  	} else {
>  		if (getprojid(path, fd, &projid) == 0)
>  			printf("[%u] %s\n", (unsigned int)projid, path);
> +		else
> +			exitcode = 1;
>  		close(fd);
>  	}
>  	return 0;
> @@ -384,9 +393,10 @@ lsproj_f(
>  	if (recurse_all || recurse_dir)
>  		nftw(file->name, lsproj_callback,
>  			100, FTW_PHYS | FTW_MOUNT | FTW_DEPTH);
> -	else if (getprojid(file->name, file->fd, &projid) < 0)
> +	else if (getprojid(file->name, file->fd, &projid) < 0) {
>  		perror("getprojid");
> -	else
> +		exitcode = 1;
> +	} else
>  		printf(_("projid = %u\n"), (unsigned int)projid);
>  	return 0;
>  }
> @@ -418,9 +428,12 @@ chproj_callback(
>  	if ((fd = open(path, O_RDONLY)) == -1) {
>  		fprintf(stderr, _("%s: cannot open %s: %s\n"),
>  			progname, path, strerror(errno));
> +		exitcode = 1;
>  	} else {
> -		if (setprojid(path, fd, prid) < 0)
> +		if (setprojid(path, fd, prid) < 0) {
> +			exitcode = 1;
>  			perror("setprojid");
> +		}
>  		close(fd);
>  	}
>  	return 0;
> @@ -455,14 +468,17 @@ chproj_f(
>  	prid = prid_from_string(argv[optind]);
>  	if (prid == -1) {
>  		printf(_("invalid project ID -- %s\n"), argv[optind]);
> +		exitcode = 1;
>  		return 0;
>  	}
>  
>  	if (recurse_all || recurse_dir)
>  		nftw(file->name, chproj_callback,
>  			100, FTW_PHYS | FTW_MOUNT | FTW_DEPTH);
> -	else if (setprojid(file->name, file->fd, prid) < 0)
> +	else if (setprojid(file->name, file->fd, prid) < 0) {
>  		perror("setprojid");
> +		exitcode = 1;
> +	}
>  	return 0;
>  }
>  
> @@ -486,6 +502,7 @@ get_extsize(const char *path, int fd)
>  	if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) {
>  		printf("%s: FS_IOC_FSGETXATTR %s: %s\n",
>  			progname, path, strerror(errno));
> +		exitcode = 1;
>  		return 0;
>  	}
>  	printf("[%u] %s\n", fsx.fsx_extsize, path);
> @@ -498,6 +515,7 @@ set_extsize(const char *path, int fd, long extsz)
>  	struct fsxattr	fsx;
>  	struct stat	stat;
>  
> +	exitcode = 1;
>  	if (fstat(fd, &stat) < 0) {
>  		perror("fstat");
>  		return 0;
> @@ -524,6 +542,7 @@ set_extsize(const char *path, int fd, long extsz)
>  		return 0;
>  	}
>  
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -542,6 +561,7 @@ get_extsize_callback(
>  	if ((fd = open(path, O_RDONLY)) == -1) {
>  		fprintf(stderr, _("%s: cannot open %s: %s\n"),
>  			progname, path, strerror(errno));
> +		exitcode = 1;
>  	} else {
>  		get_extsize(path, fd);
>  		close(fd);
> @@ -564,6 +584,7 @@ set_extsize_callback(
>  	if ((fd = open(path, O_RDONLY)) == -1) {
>  		fprintf(stderr, _("%s: cannot open %s: %s\n"),
>  			progname, path, strerror(errno));
> +		exitcode = 1;
>  	} else {
>  		set_extsize(path, fd, extsize);
>  		close(fd);
> @@ -601,6 +622,7 @@ extsize_f(
>  		if (extsize < 0) {
>  			printf(_("non-numeric extsize argument -- %s\n"),
>  				argv[optind]);
> +			exitcode = 1;
>  			return 0;
>  		}
>  	} else {
> diff --git a/io/parent.c b/io/parent.c
> index 1968516d2c00..4653ddf3789a 100644
> --- a/io/parent.c
> +++ b/io/parent.c
> @@ -387,6 +387,7 @@ parent_f(int argc, char **argv)
>  	if (!fs) {
>  		fprintf(stderr, _("file argument, \"%s\", is not in a mounted XFS filesystem\n"),
>  			file->name);
> +		exitcode = 1;
>  		return 1;
>  	}
>  	mntpt = fs->fs_dir;
> diff --git a/io/pread.c b/io/pread.c
> index 60650aa340f6..7e9425fe21e8 100644
> --- a/io/pread.c
> +++ b/io/pread.c
> @@ -390,6 +390,7 @@ pread_f(
>  	int		eof = 0, direction = IO_FORWARD;
>  	int		c;
>  
> +	exitcode = 1;
>  	Cflag = qflag = uflag = vflag = 0;
>  	init_cvtnum(&fsblocksize, &fssectsize);
>  	bsize = fsblocksize;
> @@ -488,6 +489,8 @@ pread_f(
>  	}
>  	if (c < 0)
>  		return 0;
> +
> +	exitcode = 0;
>  	if (qflag)
>  		return 0;
>  	gettimeofday(&t2, NULL);
> diff --git a/io/prealloc.c b/io/prealloc.c
> index 1a1c9ca37da2..2ddde9023760 100644
> --- a/io/prealloc.c
> +++ b/io/prealloc.c
> @@ -88,6 +88,7 @@ allocsp_f(
>  {
>  	xfs_flock64_t	segment;
>  
> +	exitcode = 1;
>  	if (!offset_length(argv[1], argv[2], &segment))
>  		return 0;
>  
> @@ -95,6 +96,7 @@ allocsp_f(
>  		perror("XFS_IOC_ALLOCSP64");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -105,6 +107,7 @@ freesp_f(
>  {
>  	xfs_flock64_t	segment;
>  
> +	exitcode = 1;
>  	if (!offset_length(argv[1], argv[2], &segment))
>  		return 0;
>  
> @@ -112,6 +115,7 @@ freesp_f(
>  		perror("XFS_IOC_FREESP64");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -122,6 +126,7 @@ resvsp_f(
>  {
>  	xfs_flock64_t	segment;
>  
> +	exitcode = 1;
>  	if (!offset_length(argv[1], argv[2], &segment))
>  		return 0;
>  
> @@ -129,6 +134,7 @@ resvsp_f(
>  		perror("XFS_IOC_RESVSP64");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -139,6 +145,7 @@ unresvsp_f(
>  {
>  	xfs_flock64_t	segment;
>  
> +	exitcode = 1;
>  	if (!offset_length(argv[1], argv[2], &segment))
>  		return 0;
>  
> @@ -146,6 +153,7 @@ unresvsp_f(
>  		perror("XFS_IOC_UNRESVSP64");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -156,6 +164,7 @@ zero_f(
>  {
>  	xfs_flock64_t	segment;
>  
> +	exitcode = 1;
>  	if (!offset_length(argv[1], argv[2], &segment))
>  		return 0;
>  
> @@ -163,6 +172,7 @@ zero_f(
>  		perror("XFS_IOC_ZERO_RANGE");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -198,6 +208,7 @@ fallocate_f(
>  	int		mode = 0;
>  	int		c;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "cikpu")) != EOF) {
>  		switch (c) {
>  		case 'c':
> @@ -230,6 +241,7 @@ fallocate_f(
>  		perror("fallocate");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -241,6 +253,7 @@ fpunch_f(
>  	xfs_flock64_t	segment;
>  	int		mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
>  
> +	exitcode = 1;
>  	if (!offset_length(argv[1], argv[2], &segment))
>  		return 0;
>  
> @@ -249,6 +262,7 @@ fpunch_f(
>  		perror("fallocate");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -260,6 +274,7 @@ fcollapse_f(
>  	xfs_flock64_t	segment;
>  	int		mode = FALLOC_FL_COLLAPSE_RANGE;
>  
> +	exitcode = 1;
>  	if (!offset_length(argv[1], argv[2], &segment))
>  		return 0;
>  
> @@ -268,6 +283,7 @@ fcollapse_f(
>  		perror("fallocate");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -279,6 +295,7 @@ finsert_f(
>  	xfs_flock64_t	segment;
>  	int		mode = FALLOC_FL_INSERT_RANGE;
>  
> +	exitcode = 1;
>  	if (!offset_length(argv[1], argv[2], &segment))
>  		return 0;
>  
> @@ -287,6 +304,7 @@ finsert_f(
>  		perror("fallocate");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -299,6 +317,7 @@ fzero_f(
>  	int		mode = FALLOC_FL_ZERO_RANGE;
>  	int		index = 1;
>  
> +	exitcode = 1;
>  	if (strncmp(argv[index], "-k", 3) == 0) {
>  		mode |= FALLOC_FL_KEEP_SIZE;
>  		index++;
> @@ -312,6 +331,7 @@ fzero_f(
>  		perror("fallocate");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> @@ -324,6 +344,7 @@ funshare_f(
>  	int		mode = FALLOC_FL_UNSHARE_RANGE;
>  	int		index = 1;
>  
> +	exitcode = 1;
>  	if (!offset_length(argv[index], argv[index + 1], &segment))
>  		return 0;
>  
> @@ -332,6 +353,7 @@ funshare_f(
>  		perror("fallocate");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  #endif	/* HAVE_FALLOCATE */
> diff --git a/io/pwrite.c b/io/pwrite.c
> index a89edfd0496f..ca262bc40b25 100644
> --- a/io/pwrite.c
> +++ b/io/pwrite.c
> @@ -295,6 +295,7 @@ pwrite_f(
>  	int		c, fd = -1;
>  	int		pwritev2_flags = 0;
>  
> +	exitcode = 1;
>  	Cflag = qflag = uflag = dflag = wflag = Wflag = 0;
>  	init_cvtnum(&fsblocksize, &fssectsize);
>  	bsize = fsblocksize;
> @@ -446,6 +447,8 @@ pwrite_f(
>  			goto done;
>  		}
>  	}
> +
> +	exitcode = 0;
>  	if (qflag)
>  		goto done;
>  	gettimeofday(&t2, NULL);
> diff --git a/io/readdir.c b/io/readdir.c
> index ca7a881d27e4..65fe51d2ca68 100644
> --- a/io/readdir.c
> +++ b/io/readdir.c
> @@ -149,6 +149,7 @@ readdir_f(
>  	DIR *dir;
>  	int dfd;
>  
> +	exitcode = 1;
>  	init_cvtnum(&fsblocksize, &fssectsize);
>  
>  	while ((c = getopt(argc, argv, "l:o:v")) != EOF) {
> @@ -169,12 +170,12 @@ readdir_f(
>  
>  	dfd = dup(file->fd);
>  	if (dfd < 0)
> -		return -1;
> +		return 0;
>  
>  	dir = fdopendir(dfd);
>  	if (!dir) {
>  		close(dfd);
> -		return -1;
> +		return 0;
>  	}
>  
>  	if (offset == -1) {
> @@ -199,6 +200,7 @@ readdir_f(
>  	printf(_("%s, %d ops, %s (%s/sec and %.4f ops/sec)\n"),
>  		s1, cnt, ts, s2, tdiv(cnt, t2));
>  
> +	exitcode = 0;
>  	return 0;
>  }
>  
> diff --git a/io/reflink.c b/io/reflink.c
> index f584e8f1fe43..86a20b2c629e 100644
> --- a/io/reflink.c
> +++ b/io/reflink.c
> @@ -75,6 +75,7 @@ dedupe_ioctl(
>  		error = ioctl(fd, XFS_IOC_FILE_EXTENT_SAME, args);
>  		if (error) {
>  			perror("XFS_IOC_FILE_EXTENT_SAME");
> +			exitcode = 1;
>  			goto done;
>  		}
>  		if (info->status < 0) {
> @@ -139,24 +140,29 @@ dedupe_f(
>  	soffset = cvtnum(fsblocksize, fssectsize, argv[optind]);
>  	if (soffset < 0) {
>  		printf(_("non-numeric src offset argument -- %s\n"), argv[optind]);
> +		exitcode = 1;
>  		return 0;
>  	}
>  	optind++;
>  	doffset = cvtnum(fsblocksize, fssectsize, argv[optind]);
>  	if (doffset < 0) {
>  		printf(_("non-numeric dest offset argument -- %s\n"), argv[optind]);
> +		exitcode = 1;
>  		return 0;
>  	}
>  	optind++;
>  	count = cvtnum(fsblocksize, fssectsize, argv[optind]);
>  	if (count < 0) {
>  		printf(_("non-positive length argument -- %s\n"), argv[optind]);
> +		exitcode = 1;
>  		return 0;
>  	}
>  
>  	fd = openfile(infile, NULL, IO_READONLY, 0, NULL);
> -	if (fd < 0)
> +	if (fd < 0) {
> +		exitcode = 1;
>  		return 0;
> +	}
>  
>  	gettimeofday(&t1, NULL);
>  	total = dedupe_ioctl(fd, soffset, doffset, count, &ops);
> @@ -237,6 +243,7 @@ reflink_f(
>  	struct timeval	t1, t2;
>  	int		c, ops = 0, fd = -1;
>  
> +	exitcode = 1;
>  	condensed = quiet_flag = 0;
>  	doffset = soffset = 0;
>  	init_cvtnum(&fsblocksize, &fssectsize);
> @@ -284,7 +291,11 @@ clone_all:
>  
>  	gettimeofday(&t1, NULL);
>  	total = reflink_ioctl(fd, soffset, doffset, count, &ops);
> -	if (ops == 0 || quiet_flag)
> +	if (ops == 0)
> +		goto done;
> +
> +	exitcode = 0;
> +	if (quiet_flag)
>  		goto done;
>  	gettimeofday(&t2, NULL);
>  	t2 = tsub(t2, t1);
> diff --git a/io/resblks.c b/io/resblks.c
> index 06903f5bb748..33da10711a0b 100644
> --- a/io/resblks.c
> +++ b/io/resblks.c
> @@ -32,6 +32,7 @@ resblks_f(
>  	xfs_fsop_resblks_t	res;
>  	long long		blks;
>  
> +	exitcode = 1;
>  	if (argc == 2) {
>  		blks = cvtnum(file->geom.blocksize, file->geom.sectsize, argv[1]);
>  		if (blks < 0) {
> @@ -51,6 +52,7 @@ resblks_f(
>  			(unsigned long long) res.resblks);
>  	printf(_("available reserved blocks = %llu\n"),
>  			(unsigned long long) res.resblks_avail);
> +	exitcode = 0;
>  	return 0;
>  }
>  
> diff --git a/io/seek.c b/io/seek.c
> index 871b47262f03..e43a8f05ea36 100644
> --- a/io/seek.c
> +++ b/io/seek.c
> @@ -111,6 +111,7 @@ seek_f(
>  	int		flag;
>  	int		startflag;
>  
> +	exitcode = 1;
>  	flag = startflag = 0;
>  	init_cvtnum(&fsblocksize, &fssectsize);
>  
> @@ -186,9 +187,11 @@ found_hole:
>  	for (c = 0; flag; c++) {
>  		if (offset == -1) {
>  			/* print error or eof if the only entry */
> -			if (errno != ENXIO || c == 0 )
> +			if (errno != ENXIO || c == 0 ) {
>  				seek_output(startflag, seekinfo[current].name,
>  					    start, offset);
> +				exitcode = 0;
> +			}
>  			return 0;	/* stop on error or EOF */
>  		}
>  
> @@ -210,6 +213,7 @@ found_hole:
>  		if (offset != -1 && offset <= start)
>  			goto bad_result;
>  	}
> +	exitcode = 0;
>  	return 0;
>  
>  bad_result:
> diff --git a/io/sendfile.c b/io/sendfile.c
> index 063fa7f48114..ce6b9f127e4e 100644
> --- a/io/sendfile.c
> +++ b/io/sendfile.c
> @@ -85,6 +85,7 @@ sendfile_f(
>  	int		Cflag, qflag;
>  	int		c, fd = -1;
>  
> +	exitcode = 1;
>  	Cflag = qflag = 0;
>  	init_cvtnum(&blocksize, &sectsize);
>  	while ((c = getopt(argc, argv, "Cf:i:q")) != EOF) {
> @@ -146,6 +147,8 @@ sendfile_f(
>  	c = send_buffer(offset, count, fd, &total);
>  	if (c < 0)
>  		goto done;
> +
> +	exitcode = 0;
>  	if (qflag)
>  		goto done;
>  	gettimeofday(&t2, NULL);
> diff --git a/io/shutdown.c b/io/shutdown.c
> index 022a0e9a07ae..6374c973b44d 100644
> --- a/io/shutdown.c
> +++ b/io/shutdown.c
> @@ -30,6 +30,7 @@ shutdown_f(
>  {
>  	int		c, flag = XFS_FSOP_GOING_FLAGS_NOLOGFLUSH;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "fv")) != -1) {
>  		switch (c) {
>  		case 'f':
> @@ -44,6 +45,7 @@ shutdown_f(
>  		perror("XFS_IOC_GOINGDOWN");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> diff --git a/io/stat.c b/io/stat.c
> index 41d421525791..2e5e90f7a172 100644
> --- a/io/stat.c
> +++ b/io/stat.c
> @@ -137,6 +137,7 @@ stat_f(
>  	struct stat	st;
>  	int		c, verbose = 0, raw = 0;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "rv")) != EOF) {
>  		switch (c) {
>  		case 'r':
> @@ -157,6 +158,7 @@ stat_f(
>  		perror("fstat");
>  		return 0;
>  	}
> +	exitcode = 0;
>  
>  	if (raw)
>  		return dump_raw_stat(&st);
> @@ -193,6 +195,7 @@ statfs_f(
>  	printf(_("fd.path = \"%s\"\n"), file->name);
>  	if (platform_fstatfs(file->fd, &st) < 0) {
>  		perror("fstatfs");
> +		exitcode = 1;
>  	} else {
>  		printf(_("statfs.f_bsize = %lld\n"), (long long) st.f_bsize);
>  		printf(_("statfs.f_blocks = %lld\n"), (long long) st.f_blocks);
> @@ -207,6 +210,7 @@ statfs_f(
>  		return 0;
>  	if ((xfsctl(file->name, file->fd, XFS_IOC_FSGEOMETRY_V1, &fsgeo)) < 0) {
>  		perror("XFS_IOC_FSGEOMETRY_V1");
> +		exitcode = 1;
>  	} else {
>  		printf(_("geom.bsize = %u\n"), fsgeo.blocksize);
>  		printf(_("geom.agcount = %u\n"), fsgeo.agcount);
> @@ -223,6 +227,7 @@ statfs_f(
>  	}
>  	if ((xfsctl(file->name, file->fd, XFS_IOC_FSCOUNTS, &fscounts)) < 0) {
>  		perror("XFS_IOC_FSCOUNTS");
> +		exitcode = 1;
>  	} else {
>  		printf(_("counts.freedata = %llu\n"),
>  			(unsigned long long) fscounts.freedata);
> @@ -315,6 +320,7 @@ statx_f(
>  	int		atflag = 0;
>  	unsigned int	mask = STATX_ALL;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "m:rvFD")) != EOF) {
>  		switch (c) {
>  		case 'm':
> @@ -358,6 +364,7 @@ statx_f(
>  		perror("statx");
>  		return 0;
>  	}
> +	exitcode = 0;
>  
>  	if (raw)
>  		return dump_raw_statx(&stx);
> diff --git a/io/sync_file_range.c b/io/sync_file_range.c
> index 7e4f3e6da397..2a9965b098d2 100644
> --- a/io/sync_file_range.c
> +++ b/io/sync_file_range.c
> @@ -46,6 +46,7 @@ sync_range_f(
>  	int		c, sync_mode = 0;
>  	size_t		blocksize, sectsize;
>  
> +	exitcode = 1;
>  	while ((c = getopt(argc, argv, "abw")) != EOF) {
>  		switch (c) {
>  		case 'a':
> @@ -87,6 +88,7 @@ sync_range_f(
>  		perror("sync_file_range");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> diff --git a/io/truncate.c b/io/truncate.c
> index 20bada82c4aa..d741e36860b6 100644
> --- a/io/truncate.c
> +++ b/io/truncate.c
> @@ -31,6 +31,7 @@ truncate_f(
>  	off64_t		offset;
>  	size_t		blocksize, sectsize;
>  
> +	exitcode = 1;
>  	init_cvtnum(&blocksize, &sectsize);
>  	offset = cvtnum(blocksize, sectsize, argv[1]);
>  	if (offset < 0) {
> @@ -42,6 +43,7 @@ truncate_f(
>  		perror("ftruncate");
>  		return 0;
>  	}
> +	exitcode = 0;
>  	return 0;
>  }
>  
> diff --git a/io/utimes.c b/io/utimes.c
> index faf9b8d55dbc..9fcb44c22bc0 100755
> --- a/io/utimes.c
> +++ b/io/utimes.c
> @@ -44,6 +44,7 @@ utimes_f(
>  	struct timespec t[2];
>  	int result;
>  
> +	exitcode = 1;
>  	/* Get the timestamps */
>  	result = timespec_from_string(argv[1], argv[2], &t[0]);
>  	if (result) {
> @@ -62,6 +63,7 @@ utimes_f(
>  		return 0;
>  	}
>  
> +	exitcode = 0;
>  	return 0;
>  }
>  
> --
> To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe fstests" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Dave Chinner Dec. 7, 2017, 11:46 p.m. UTC | #2
On Thu, Dec 07, 2017 at 09:05:20AM -0500, Brian Foster wrote:
> On Wed, Dec 06, 2017 at 11:26:38AM +1100, Dave Chinner wrote:
> > On Wed, Dec 06, 2017 at 08:18:50AM +1100, Dave Chinner wrote:
> > > On Tue, Dec 05, 2017 at 04:23:43PM +0200, Ari Sundholm wrote:
> > > > On 12/05/2017 12:28 AM, Dave Chinner wrote:
> > > > >On Thu, Nov 30, 2017 at 04:21:47PM +0200, Ari Sundholm wrote:
> ...
> > 
> > xfs_io: set exitcode on failure appropriately
> > 
> > From: Dave Chinner <dchinner@redhat.com>
> > 
> > Many operations don't set the exitcode when they fail, resulting
> > in xfs_io exiting with a zero (no failure) exit code despite the
> > command failing and returning an error. Fix this.
> > 
> > Signed-Off-By: Dave Chinner <dchinner@redhat.com>
> > ---
> ...
> > diff --git a/io/copy_file_range.c b/io/copy_file_range.c
> > index d1dfc5a5e33a..9b737eff4c02 100644
> > --- a/io/copy_file_range.c
> > +++ b/io/copy_file_range.c
> > @@ -92,6 +92,7 @@ copy_range_f(int argc, char **argv)
> >  	int ret;
> >  	int fd;
> >  
> > +	exitcode = 1;
> >  	while ((opt = getopt(argc, argv, "s:d:l:")) != -1) {
> >  		switch (opt) {
> >  		case 's':
> > @@ -132,6 +133,8 @@ copy_range_f(int argc, char **argv)
> >  
> >  	ret = copy_file_range(fd, &src, &dst, len);
> >  	close(fd);
> > +	if (ret >= 0)
> > +		exitcode = 0;
> 
> I don't think it's appropriate to blindly overwrite the global exitcode
> value like this. What about the case where multiple commands are chained
> together? Should we expect an error code if any of the commands failed
> or only the last?
> 
> For example:
> 
>   xfs_io -c "pwrite ..." <file>
> 
> ... returns an error if the write fails, while:
> 
>   xfs_io -c "pwrite ..." -c "fadvise ..." <file>
> 
> ... would never so long as the fadvise succeeds.

Yup, I mentioned that this would be a problem on IRC. The patch fixes
the interactive and the single command cases, but won't work for
chained commands as you rightly point out.

To fix it properly, it's going to take a lot more than 15 minutes of
time. We're going to have to change how errors are returned to and
propagated through the libxcmd infrastruture, how we handle "fatal
error, don't continue" conditions through the libxcmd command loop,
etc. If we want to stop at the first error, then we've got to go
change all the return values from xfs_io commands to return non-zero
on error. We still have to set the exitcode as per this patch,
because the non-zero return value simply says "stop parsing input"
and doesn't affect the exit code of the program.

Given that xfs_quota, xfs_io, xfs_db, and xfs_spaceman all use the
same libxcmd infrastructure, we've got to make the changes
consistently across all of those utilities. This will require an
audit of all the commands first to determine if there's anything
special in any of those utilities, then changing all the code, then
testing all the CLI parsing still works correctly.  xfs_quota, as
usual, will be the problem child that causes us lots of pain here.

I'm not planning on doing all this in the near future, so I did the
next best thing that would only affect xfs_io behaviour. i.e.
directly manipulate the exitcode as many of the existing xfs_io
commands already do.

Cheers,

Dave.
Ari Sundholm Dec. 14, 2017, 12:11 p.m. UTC | #3
On 12/06/2017 02:26 AM, Dave Chinner wrote:
> On Wed, Dec 06, 2017 at 08:18:50AM +1100, Dave Chinner wrote:
>> On Tue, Dec 05, 2017 at 04:23:43PM +0200, Ari Sundholm wrote:
>>> On 12/05/2017 12:28 AM, Dave Chinner wrote:
>>>> On Thu, Nov 30, 2017 at 04:21:47PM +0200, Ari Sundholm wrote:
>>>>> Hi!
>>>>>
>>>>> While debugging an issue, we found out that generic/399 seems to
>>>>> rely on a behavior that is specific to ext4 in the following section
>>>>> of code:
>>>>> ----------------8<--------snip--------8<------------------------------
>>>>> #
>>>>> # Write files of 1 MB of all the same byte until we hit ENOSPC.
>>>>> Note that we
>>>>> # must not create sparse files, since the contents of sparse files are not
>>>>> # stored on-disk.  Also, we create multiple files rather than one big file
>>>>> # because we want to test for reuse of per-file keys.
>>>>> #
>>>>> total_file_size=0
>>>>> i=1
>>>>> while true; do
>>>>> 	file=$SCRATCH_MNT/encrypted_dir/file$i
>>>>> 	if ! $XFS_IO_PROG -f $file -c 'pwrite 0 1M' &> $tmp.out; then
>>>>> 		if ! grep -q 'No space left on device' $tmp.out; then
>>>>> 			echo "FAIL: unexpected pwrite failure"
>>>>> 			cat $tmp.out
>>>>> 		elif [ -e $file ]; then
>>>>> 			total_file_size=$((total_file_size + $(stat -c %s $file)))
>>>>> 		fi
>>>>> 		break
>>>>> 	fi
>>>>> 	total_file_size=$((total_file_size + $(stat -c %s $file)))
>>>>> 	i=$((i + 1))
>>>>> 	if [ $i -gt $fs_size_in_mb ]; then
>>>>> 		echo "FAIL: filesystem never filled up!"
>>>>> 		break
>>>>> 	fi
>>>>> done
>>>>> ----------------8<--------snip--------8<------------------------------
>>>>>
>>>>> What happens with ext4 is that the xfs_io command gives a nonzero
>>>>> exit value not when the pwrite command fails with ENOSPC but during
>>>>> the *next* iteration when opening the file fails with ENOSPC. Turns
>>>>> out the pwrite command failing does not cause xfs_io to give a
>>>>> nonzero exit value.
>>>>
>>>> That implies ext4 is returning zero bytes written to the pwrite()
>>>> call rather than ENOSPC. i.e.:
>>>>
>>>>                  bytes = do_pwrite(file->fd, off, cnt, buffersize,
>>>>                                  pwritev2_flags);
>>>>                  if (bytes == 0)
>>>>                          break;
>>>>                  if (bytes < 0) {
>>>>                          perror("pwrite");
>>>>                          return -1;
>>>>                  }
>>>>
>>>> So if it's exiting with no error, then we can't have got an error
>>> >from ext4 at ENOSPC. If that's the case, it probably should be
>>>> considered an ext4 bug, not an issue with xfs_io...
>>>>
>>>
>>> No, according to what we've observed, that is not what happens. The
>>> pwrite() call does fail and errno is ENOSPC after the call. The
>>> immediate problem is that xfs_io does not reflect this failure in
>>> its exit value and thus the check in generic/399 does not work in
>>> this case. Only when open() fails during the next iteration does
>>> xfs_io give a nonzero exit value and cause the check in the test
>>> case to allow the test case to end successfully.
>>
>> Ah, io/pwrite.c fails to set exitcode on failure iappropriately
>> before returning. Looks like there is a bunch of xfs_io commands
>> that fail to do this properly.
>>
>>> What is specific to ext4 here is, as stated in my original message,
>>> that open() fails.
>>
>> Because there are no more inodes to allocate (i.e. inode table is
>> full) and so attempts to create more fail with ENOSPC. The xfs_io
>> open command set exitcode appropriately, and that's why it finally
>> triggers a test abort.
>>
>> Looks like xfs_io does need fixing to set exitcode appropriately on
>> all failures...
> 
> Try the patch below.
> 

Thanks, I'll try it. Sorry for the late response (was on vacation).

> Cheers,
> 
> Dave.
> 

--
To unsubscribe from this list: send the line "unsubscribe fstests" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ari Sundholm Dec. 15, 2017, 2:26 p.m. UTC | #4
On 12/14/2017 02:11 PM, Ari Sundholm wrote:
> On 12/06/2017 02:26 AM, Dave Chinner wrote:
>> On Wed, Dec 06, 2017 at 08:18:50AM +1100, Dave Chinner wrote:
>>> On Tue, Dec 05, 2017 at 04:23:43PM +0200, Ari Sundholm wrote:
>>>> On 12/05/2017 12:28 AM, Dave Chinner wrote:
>>>>> On Thu, Nov 30, 2017 at 04:21:47PM +0200, Ari Sundholm wrote:
>>>>>> Hi!
>>>>>>
>>>>>> While debugging an issue, we found out that generic/399 seems to
>>>>>> rely on a behavior that is specific to ext4 in the following section
>>>>>> of code:
>>>>>> ----------------8<--------snip--------8<------------------------------ 
>>>>>>
>>>>>> #
>>>>>> # Write files of 1 MB of all the same byte until we hit ENOSPC.
>>>>>> Note that we
>>>>>> # must not create sparse files, since the contents of sparse files 
>>>>>> are not
>>>>>> # stored on-disk.  Also, we create multiple files rather than one 
>>>>>> big file
>>>>>> # because we want to test for reuse of per-file keys.
>>>>>> #
>>>>>> total_file_size=0
>>>>>> i=1
>>>>>> while true; do
>>>>>>     file=$SCRATCH_MNT/encrypted_dir/file$i
>>>>>>     if ! $XFS_IO_PROG -f $file -c 'pwrite 0 1M' &> $tmp.out; then
>>>>>>         if ! grep -q 'No space left on device' $tmp.out; then
>>>>>>             echo "FAIL: unexpected pwrite failure"
>>>>>>             cat $tmp.out
>>>>>>         elif [ -e $file ]; then
>>>>>>             total_file_size=$((total_file_size + $(stat -c %s 
>>>>>> $file)))
>>>>>>         fi
>>>>>>         break
>>>>>>     fi
>>>>>>     total_file_size=$((total_file_size + $(stat -c %s $file)))
>>>>>>     i=$((i + 1))
>>>>>>     if [ $i -gt $fs_size_in_mb ]; then
>>>>>>         echo "FAIL: filesystem never filled up!"
>>>>>>         break
>>>>>>     fi
>>>>>> done
>>>>>> ----------------8<--------snip--------8<------------------------------ 
>>>>>>
>>>>>>
>>>>>> What happens with ext4 is that the xfs_io command gives a nonzero
>>>>>> exit value not when the pwrite command fails with ENOSPC but during
>>>>>> the *next* iteration when opening the file fails with ENOSPC. Turns
>>>>>> out the pwrite command failing does not cause xfs_io to give a
>>>>>> nonzero exit value.
>>>>>
>>>>> That implies ext4 is returning zero bytes written to the pwrite()
>>>>> call rather than ENOSPC. i.e.:
>>>>>
>>>>>                  bytes = do_pwrite(file->fd, off, cnt, buffersize,
>>>>>                                  pwritev2_flags);
>>>>>                  if (bytes == 0)
>>>>>                          break;
>>>>>                  if (bytes < 0) {
>>>>>                          perror("pwrite");
>>>>>                          return -1;
>>>>>                  }
>>>>>
>>>>> So if it's exiting with no error, then we can't have got an error
>>>> >from ext4 at ENOSPC. If that's the case, it probably should be
>>>>> considered an ext4 bug, not an issue with xfs_io...
>>>>>
>>>>
>>>> No, according to what we've observed, that is not what happens. The
>>>> pwrite() call does fail and errno is ENOSPC after the call. The
>>>> immediate problem is that xfs_io does not reflect this failure in
>>>> its exit value and thus the check in generic/399 does not work in
>>>> this case. Only when open() fails during the next iteration does
>>>> xfs_io give a nonzero exit value and cause the check in the test
>>>> case to allow the test case to end successfully.
>>>
>>> Ah, io/pwrite.c fails to set exitcode on failure iappropriately
>>> before returning. Looks like there is a bunch of xfs_io commands
>>> that fail to do this properly.
>>>
>>>> What is specific to ext4 here is, as stated in my original message,
>>>> that open() fails.
>>>
>>> Because there are no more inodes to allocate (i.e. inode table is
>>> full) and so attempts to create more fail with ENOSPC. The xfs_io
>>> open command set exitcode appropriately, and that's why it finally
>>> triggers a test abort.
>>>
>>> Looks like xfs_io does need fixing to set exitcode appropriately on
>>> all failures...
>>
>> Try the patch below.
>>
> 
> Thanks, I'll try it. Sorry for the late response (was on vacation).
> 

The patch does seem to fix the specific issue with this test case. Thank 
you.

However, it seems there is still some need for discussion and a decision 
on what kind of a change should be merged in xfsprogs, if anything, 
and/or whether the fstests test case itself could be changed (at least 
for now) to accommodate the current xfs_io behavior.

Best regards,
Ari Sundholm
ari@tuxera.com

>> Cheers,
>>
>> Dave.
>>
> 
> -- 
> To unsubscribe from this list: send the line "unsubscribe fstests" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe fstests" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eric Sandeen Dec. 24, 2017, 7:51 p.m. UTC | #5
On 12/7/17 6:05 AM, Brian Foster wrote:
> On Wed, Dec 06, 2017 at 11:26:38AM +1100, Dave Chinner wrote:

...

>> diff --git a/io/copy_file_range.c b/io/copy_file_range.c
>> index d1dfc5a5e33a..9b737eff4c02 100644
>> --- a/io/copy_file_range.c
>> +++ b/io/copy_file_range.c
>> @@ -92,6 +92,7 @@ copy_range_f(int argc, char **argv)
>>  	int ret;
>>  	int fd;
>>  
>> +	exitcode = 1;
>>  	while ((opt = getopt(argc, argv, "s:d:l:")) != -1) {
>>  		switch (opt) {
>>  		case 's':
>> @@ -132,6 +133,8 @@ copy_range_f(int argc, char **argv)
>>  
>>  	ret = copy_file_range(fd, &src, &dst, len);
>>  	close(fd);
>> +	if (ret >= 0)
>> +		exitcode = 0;
> I don't think it's appropriate to blindly overwrite the global exitcode
> value like this. What about the case where multiple commands are chained
> together? Should we expect an error code if any of the commands failed
> or only the last?
> 
> For example:
> 
>   xfs_io -c "pwrite ..." <file>
> 
> ... returns an error if the write fails, while:
> 
>   xfs_io -c "pwrite ..." -c "fadvise ..." <file>
> 
> ... would never so long as the fadvise succeeds.
> 
> Brian

<late to the party>

Seems we need to start by defining the expected behavior, which so far
has been pretty ad-hoc.

AFAICT this is the expected/desired behavior:

exitcode is the global xfs_io exit code and indicates that any error
has occurred during processing.  It should never get reset to 0.
Using something like do_error to print the failure & set the exitcode
would seem to make sense.

Chained commands continue until some ->cfunc (foo_f()) returns nonzero,
but a nonzero return does /not/ affect exitcode, it simply stops chained
command processing.

It's not clear to me when a cfunc-> /should/ return non-zero and stop
the processing.

-Eric
--
To unsubscribe from this list: send the line "unsubscribe fstests" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/io/attr.c b/io/attr.c
index 728560e1d1fb..86527b05461b 100644
--- a/io/attr.c
+++ b/io/attr.c
@@ -175,10 +175,11 @@  lsattr_callback(
 	if ((fd = open(path, O_RDONLY)) == -1)
 		fprintf(stderr, _("%s: cannot open %s: %s\n"),
 			progname, path, strerror(errno));
-	else if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0)
+	else if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) {
 		fprintf(stderr, _("%s: cannot get flags on %s: %s\n"),
 			progname, path, strerror(errno));
-	else
+		exitcode = 1;
+	} else
 		printxattr(fsx.fsx_xflags, 0, 1, path, 0, 1);
 
 	if (fd != -1)
@@ -225,6 +226,7 @@  lsattr_f(
 	} else if ((xfsctl(name, file->fd, FS_IOC_FSGETXATTR, &fsx)) < 0) {
 		fprintf(stderr, _("%s: cannot get flags on %s: %s\n"),
 			progname, name, strerror(errno));
+		exitcode = 1;
 	} else {
 		printxattr(fsx.fsx_xflags, vflag, !aflag, name, vflag, !aflag);
 		if (aflag) {
@@ -257,9 +259,11 @@  chattr_callback(
 	} else {
 		attr.fsx_xflags |= orflags;
 		attr.fsx_xflags &= ~andflags;
-		if (xfsctl(path, fd, FS_IOC_FSSETXATTR, &attr) < 0)
+		if (xfsctl(path, fd, FS_IOC_FSSETXATTR, &attr) < 0) {
 			fprintf(stderr, _("%s: cannot set flags on %s: %s\n"),
 				progname, path, strerror(errno));
+			exitcode = 1;
+		}
 	}
 
 	if (fd != -1)
@@ -295,6 +299,7 @@  chattr_f(
 				if (!p->flag) {
 					fprintf(stderr, _("%s: unknown flag\n"),
 						progname);
+					exitcode = 1;
 					return 0;
 				}
 			}
@@ -309,12 +314,14 @@  chattr_f(
 				if (!p->flag) {
 					fprintf(stderr, _("%s: unknown flag\n"),
 						progname);
+					exitcode = 1;
 					return 0;
 				}
 			}
 		} else {
 			fprintf(stderr, _("%s: bad chattr command, not +/-X\n"),
 				progname);
+			exitcode = 1;
 			return 0;
 		}
 	}
@@ -325,12 +332,15 @@  chattr_f(
 	} else if (xfsctl(name, file->fd, FS_IOC_FSGETXATTR, &attr) < 0) {
 		fprintf(stderr, _("%s: cannot get flags on %s: %s\n"),
 			progname, name, strerror(errno));
+		exitcode = 1;
 	} else {
 		attr.fsx_xflags |= orflags;
 		attr.fsx_xflags &= ~andflags;
-		if (xfsctl(name, file->fd, FS_IOC_FSSETXATTR, &attr) < 0)
+		if (xfsctl(name, file->fd, FS_IOC_FSSETXATTR, &attr) < 0) {
 			fprintf(stderr, _("%s: cannot set flags on %s: %s\n"),
 				progname, name, strerror(errno));
+			exitcode = 1;
+		}
 	}
 	return 0;
 }
diff --git a/io/copy_file_range.c b/io/copy_file_range.c
index d1dfc5a5e33a..9b737eff4c02 100644
--- a/io/copy_file_range.c
+++ b/io/copy_file_range.c
@@ -92,6 +92,7 @@  copy_range_f(int argc, char **argv)
 	int ret;
 	int fd;
 
+	exitcode = 1;
 	while ((opt = getopt(argc, argv, "s:d:l:")) != -1) {
 		switch (opt) {
 		case 's':
@@ -132,6 +133,8 @@  copy_range_f(int argc, char **argv)
 
 	ret = copy_file_range(fd, &src, &dst, len);
 	close(fd);
+	if (ret >= 0)
+		exitcode = 0;
 	return ret;
 }
 
diff --git a/io/cowextsize.c b/io/cowextsize.c
index c4cd6de24da5..d5872449cb60 100644
--- a/io/cowextsize.c
+++ b/io/cowextsize.c
@@ -53,6 +53,7 @@  get_cowextsize(const char *path, int fd)
 	if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) {
 		printf("%s: XFS_IOC_FSGETXATTR %s: %s\n",
 			progname, path, strerror(errno));
+		exitcode = 1;
 		return 0;
 	}
 	printf("[%u] %s\n", fsx.fsx_cowextsize, path);
@@ -67,11 +68,13 @@  set_cowextsize(const char *path, int fd, long extsz)
 
 	if (fstat64(fd, &stat) < 0) {
 		perror("fstat64");
+		exitcode = 1;
 		return 0;
 	}
 	if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) {
 		printf("%s: XFS_IOC_FSGETXATTR %s: %s\n",
 			progname, path, strerror(errno));
+		exitcode = 1;
 		return 0;
 	}
 
@@ -86,6 +89,7 @@  set_cowextsize(const char *path, int fd, long extsz)
 	if ((xfsctl(path, fd, FS_IOC_FSSETXATTR, &fsx)) < 0) {
 		printf("%s: XFS_IOC_FSSETXATTR %s: %s\n",
 			progname, path, strerror(errno));
+		exitcode = 1;
 		return 0;
 	}
 
@@ -168,6 +172,7 @@  cowextsize_f(
 		if (cowextsize < 0) {
 			printf(_("non-numeric cowextsize argument -- %s\n"),
 				argv[optind]);
+			exitcode = 1;
 			return 0;
 		}
 	} else {
diff --git a/io/encrypt.c b/io/encrypt.c
index 26ab97ce614b..3351e0ff8666 100644
--- a/io/encrypt.c
+++ b/io/encrypt.c
@@ -192,6 +192,7 @@  set_encpolicy_f(int argc, char **argv)
 	struct fscrypt_policy policy;
 
 	/* Initialize the policy structure with default values */
+	exitcode = 1;
 	memset(&policy, 0, sizeof(policy));
 	policy.contents_encryption_mode = FS_ENCRYPTION_MODE_AES_256_XTS;
 	policy.filenames_encryption_mode = FS_ENCRYPTION_MODE_AES_256_CTS;
@@ -269,10 +270,10 @@  set_encpolicy_f(int argc, char **argv)
 	if (ioctl(file->fd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) < 0) {
 		fprintf(stderr, "%s: failed to set encryption policy: %s\n",
 			file->name, strerror(errno));
-		exitcode = 1;
 		return 0;
 	}
 
+	exitcode = 0;
 	return 0;
 }
 
diff --git a/io/fadvise.c b/io/fadvise.c
index 46174f34680e..9cc91a5712e1 100644
--- a/io/fadvise.c
+++ b/io/fadvise.c
@@ -54,6 +54,7 @@  fadvise_f(
 	off64_t		offset = 0, length = 0;
 	int		c, range = 0, advise = POSIX_FADV_NORMAL;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "dnrsw")) != EOF) {
 		switch (c) {
 		case 'd':	/* Don't need these pages */
@@ -107,6 +108,7 @@  fadvise_f(
 		perror("fadvise");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
diff --git a/io/fiemap.c b/io/fiemap.c
index bdcfacdb2811..d97f53ae8d79 100644
--- a/io/fiemap.c
+++ b/io/fiemap.c
@@ -238,6 +238,7 @@  fiemap_f(
 	__u64		last_logical = 0;
 	struct stat	st;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "aln:v")) != EOF) {
 		switch (c) {
 		case 'a':
@@ -263,7 +264,6 @@  fiemap_f(
 	if (!fiemap) {
 		fprintf(stderr, _("%s: malloc of %d bytes failed.\n"),
 			progname, map_size);
-		exitcode = 1;
 		return 0;
 	}
 
@@ -282,7 +282,6 @@  fiemap_f(
 			fprintf(stderr, "%s: ioctl(FS_IOC_FIEMAP) [\"%s\"]: "
 				"%s\n", progname, file->name, strerror(errno));
 			free(fiemap);
-			exitcode = 1;
 			return 0;
 		}
 
@@ -332,7 +331,6 @@  fiemap_f(
 		fprintf(stderr, "%s: fstat failed: %s\n", progname,
 			strerror(errno));
 		free(fiemap);
-		exitcode = 1;
 		return 0;
 	}
 
@@ -341,6 +339,7 @@  fiemap_f(
 			   BTOBBT(last_logical), BTOBBT(st.st_size));
 
 out:
+	exitcode = 0;
 	free(fiemap);
 	return 0;
 }
diff --git a/io/file.c b/io/file.c
index 349b19cdc420..2afb42e60620 100644
--- a/io/file.c
+++ b/io/file.c
@@ -79,6 +79,7 @@  file_f(
 	i = atoi(argv[1]);
 	if (i < 0 || i >= filecount) {
 		printf(_("value %d is out of range (0-%d)\n"), i, filecount-1);
+		exitcode = 1;
 	} else {
 		file = &filetable[i];
 		filelist_f();
diff --git a/io/fsmap.c b/io/fsmap.c
index 448fb5356466..4e3ae7ca2bab 100644
--- a/io/fsmap.c
+++ b/io/fsmap.c
@@ -404,6 +404,7 @@  fsmap_f(
 	bool			dumped_flags = false;
 	int			dflag, lflag, rflag;
 
+	exitcode = 1;
 	init_cvtnum(&fsblocksize, &fssectsize);
 
 	dflag = lflag = rflag = 0;
@@ -466,7 +467,6 @@  fsmap_f(
 			fprintf(stderr,
 				_("%s: can't get geometry [\"%s\"]: %s\n"),
 				progname, file->name, strerror(errno));
-			exitcode = 1;
 			return 0;
 		}
 	}
@@ -476,7 +476,6 @@  fsmap_f(
 	if (head == NULL) {
 		fprintf(stderr, _("%s: malloc of %zu bytes failed.\n"),
 			progname, fsmap_sizeof(map_size));
-		exitcode = 1;
 		return 0;
 	}
 
@@ -509,7 +508,6 @@  fsmap_f(
 				progname, head->fmh_iflags, file->name,
 				strerror(errno));
 			free(head);
-			exitcode = 1;
 			return 0;
 		}
 		if (head->fmh_entries > map_size + 2) {
@@ -548,7 +546,6 @@  fsmap_f(
 				progname, head->fmh_iflags, file->name,
 				strerror(errno));
 			free(head);
-			exitcode = 1;
 			return 0;
 		}
 
@@ -571,6 +568,7 @@  fsmap_f(
 	if (dumped_flags)
 		dump_verbose_key();
 
+	exitcode = 0;
 	free(head);
 	return 0;
 }
diff --git a/io/fsync.c b/io/fsync.c
index 9fe5e2f50c62..61061c401a04 100644
--- a/io/fsync.c
+++ b/io/fsync.c
@@ -31,6 +31,7 @@  fsync_f(
 {
 	if (fsync(file->fd) < 0) {
 		perror("fsync");
+		exitcode = 1;
 		return 0;
 	}
 	return 0;
@@ -43,6 +44,7 @@  fdatasync_f(
 {
 	if (fdatasync(file->fd) < 0) {
 		perror("fdatasync");
+		exitcode = 1;
 		return 0;
 	}
 	return 0;
diff --git a/io/getrusage.c b/io/getrusage.c
index cf1f2afd19a8..9fc51709a73e 100644
--- a/io/getrusage.c
+++ b/io/getrusage.c
@@ -62,6 +62,7 @@  getrusage_f(
 
 	if (getrusage(RUSAGE_SELF, &rusage) < 0) {
 		perror("getrusage");
+		exitcode = 1;
 		return 0;
 	}
 
diff --git a/io/imap.c b/io/imap.c
index f52238e0c450..410e1662b76c 100644
--- a/io/imap.c
+++ b/io/imap.c
@@ -39,8 +39,10 @@  imap_f(int argc, char **argv)
 		nent = atoi(argv[1]);
 
 	t = malloc(nent * sizeof(*t));
-	if (!t)
+	if (!t) {
+		exitcode = 1;
 		return 0;
+	}
 
 	bulkreq.lastip  = &last;
 	bulkreq.icount  = nent;
diff --git a/io/inject.c b/io/inject.c
index 964ebfe13bd6..db0795023a64 100644
--- a/io/inject.c
+++ b/io/inject.c
@@ -156,6 +156,7 @@  inject_f(
 			command = XFS_IOC_ERROR_CLEARALL;
 		if ((xfsctl(file->name, file->fd, command, &error)) < 0) {
 			perror("XFS_IOC_ERROR_INJECTION");
+			exitcode = 1;
 			continue;
 		}
 	}
diff --git a/io/link.c b/io/link.c
index 9b2e8a970942..55bb806024eb 100644
--- a/io/link.c
+++ b/io/link.c
@@ -47,6 +47,7 @@  flink_f(
 
 	if (linkat(file->fd, "", AT_FDCWD, argv[1], AT_EMPTY_PATH) < 0) {
 		perror("flink");
+		exitcode = 1;
 		return 0;
 	}
 	return 0;
diff --git a/io/madvise.c b/io/madvise.c
index 1d8b53cb516f..fbfe35cc1c13 100644
--- a/io/madvise.c
+++ b/io/madvise.c
@@ -57,6 +57,7 @@  madvise_f(
 	int		advise = MADV_NORMAL, c;
 	size_t		blocksize, sectsize;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "drsw")) != EOF) {
 		switch (c) {
 		case 'd':	/* Don't need these pages */
@@ -111,6 +112,7 @@  madvise_f(
 		perror("madvise");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
diff --git a/io/mincore.c b/io/mincore.c
index 9e0d3a620319..09a165737705 100644
--- a/io/mincore.c
+++ b/io/mincore.c
@@ -37,6 +37,7 @@  mincore_f(
 	unsigned char	*vec;
 	int		i;
 
+	exitcode = 1;
 	if (argc == 1) {
 		offset = mapping->offset;
 		length = mapping->length;
@@ -106,6 +107,7 @@  mincore_f(
 			(unsigned long)(current - previous));
 
 	free(vec);
+	exitcode = 0;
 	return 0;
 }
 
diff --git a/io/mmap.c b/io/mmap.c
index 7a8150e3d517..b0c1f764b8c4 100644
--- a/io/mmap.c
+++ b/io/mmap.c
@@ -121,6 +121,7 @@  mapset_f(
 	i = atoi(argv[1]);
 	if (i < 0 || i >= mapcount) {
 		printf("value %d is out of range (0-%d)\n", i, mapcount);
+		exitcode = 1;
 	} else {
 		mapping = &maptable[i];
 		maplist_f();
@@ -163,6 +164,7 @@  mmap_f(
 	size_t		blocksize, sectsize;
 	int		c, prot = 0;
 
+	exitcode = 1;
 	if (argc == 1) {
 		if (mapping)
 			return maplist_f();
@@ -263,6 +265,7 @@  mmap_f(
 	mapping->offset = offset;
 	mapping->name = filename;
 	mapping->prot = prot;
+	exitcode = 0;
 	return 0;
 }
 
@@ -294,6 +297,7 @@  msync_f(
 	int		c, flags = 0;
 	size_t		blocksize, sectsize;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "ais")) != EOF) {
 		switch (c) {
 		case 'a':
@@ -336,9 +340,12 @@  msync_f(
 	if (!start)
 		return 0;
 
-	if (msync(start, length, flags) < 0)
+	if (msync(start, length, flags) < 0) {
 		perror("msync");
+		return 0;
+	}
 
+	exitcode = 0;
 	return 0;
 }
 
@@ -380,6 +387,7 @@  mread_f(
 	int		dump = 0, rflag = 0, c;
 	size_t		blocksize, sectsize;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "frv")) != EOF) {
 		switch (c) {
 		case 'f':
@@ -467,6 +475,7 @@  mread_f(
 			}
 		}
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -478,6 +487,7 @@  munmap_f(
 	ssize_t		length;
 	unsigned int	offset;
 
+	exitcode = 1;
 	if (munmap(mapping->addr, mapping->length) < 0) {
 		perror("munmap");
 		return 0;
@@ -503,6 +513,7 @@  munmap_f(
 		mapping = maptable = NULL;
 	}
 	maplist_f();
+	exitcode = 0;
 	return 0;
 }
 
@@ -538,6 +549,7 @@  mwrite_f(
 	int		c;
 	size_t		blocksize, sectsize;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "rS:")) != EOF) {
 		switch (c) {
 		case 'r':
@@ -590,6 +602,7 @@  mwrite_f(
 			((char *)mapping->addr)[tmp] = seed;
 	}
 
+	exitcode = 0;
 	return 0;
 }
 
@@ -622,6 +635,7 @@  mremap_f(
 	int		c;
 	size_t		blocksize, sectsize;
 
+	exitcode = 1;
 	init_cvtnum(&blocksize, &sectsize);
 
 	while ((c = getopt(argc, argv, "f:m")) != EOF) {
@@ -655,13 +669,14 @@  mremap_f(
 	else
 		new_addr = mremap(mapping->addr, mapping->length,
 		                  new_length, flags, new_addr);
-	if (new_addr == MAP_FAILED)
+	if (new_addr == MAP_FAILED) {
 		perror("mremap");
-	else {
-		mapping->addr = new_addr;
-		mapping->length = new_length;
+		return 0;
 	}
 
+	mapping->addr = new_addr;
+	mapping->length = new_length;
+	exitcode = 0;
 	return 0;
 }
 #endif /* HAVE_MREMAP */
diff --git a/io/open.c b/io/open.c
index 1abcb2ff2a01..0523f68263ec 100644
--- a/io/open.c
+++ b/io/open.c
@@ -209,6 +209,7 @@  open_f(
 	struct xfs_fsop_geom	geometry = { 0 };
 	struct fs_path	fsp;
 
+	exitcode = 1;
 	if (argc == 1) {
 		if (file)
 			return stat_f(argc, argv);
@@ -277,7 +278,10 @@  open_f(
 	if (!platform_test_xfs_fd(fd))
 		flags |= IO_FOREIGN;
 
-	addfile(argv[optind], fd, &geometry, flags, &fsp);
+	if (addfile(argv[optind], fd, &geometry, flags, &fsp) != 0)
+		return 0;
+
+	exitcode = 0;
 	return 0;
 }
 
@@ -289,6 +293,7 @@  close_f(
 	size_t		length;
 	unsigned int	offset;
 
+	exitcode = 1;
 	if (close(file->fd) < 0) {
 		perror("close");
 		return 0;
@@ -314,6 +319,7 @@  close_f(
 		file = filetable = NULL;
 	}
 	filelist_f();
+	exitcode = 0;
 	return 0;
 }
 
@@ -346,9 +352,12 @@  lsproj_callback(
 	if ((fd = open(path, O_RDONLY)) == -1) {
 		fprintf(stderr, _("%s: cannot open %s: %s\n"),
 			progname, path, strerror(errno));
+		exitcode = 1;
 	} else {
 		if (getprojid(path, fd, &projid) == 0)
 			printf("[%u] %s\n", (unsigned int)projid, path);
+		else
+			exitcode = 1;
 		close(fd);
 	}
 	return 0;
@@ -384,9 +393,10 @@  lsproj_f(
 	if (recurse_all || recurse_dir)
 		nftw(file->name, lsproj_callback,
 			100, FTW_PHYS | FTW_MOUNT | FTW_DEPTH);
-	else if (getprojid(file->name, file->fd, &projid) < 0)
+	else if (getprojid(file->name, file->fd, &projid) < 0) {
 		perror("getprojid");
-	else
+		exitcode = 1;
+	} else
 		printf(_("projid = %u\n"), (unsigned int)projid);
 	return 0;
 }
@@ -418,9 +428,12 @@  chproj_callback(
 	if ((fd = open(path, O_RDONLY)) == -1) {
 		fprintf(stderr, _("%s: cannot open %s: %s\n"),
 			progname, path, strerror(errno));
+		exitcode = 1;
 	} else {
-		if (setprojid(path, fd, prid) < 0)
+		if (setprojid(path, fd, prid) < 0) {
+			exitcode = 1;
 			perror("setprojid");
+		}
 		close(fd);
 	}
 	return 0;
@@ -455,14 +468,17 @@  chproj_f(
 	prid = prid_from_string(argv[optind]);
 	if (prid == -1) {
 		printf(_("invalid project ID -- %s\n"), argv[optind]);
+		exitcode = 1;
 		return 0;
 	}
 
 	if (recurse_all || recurse_dir)
 		nftw(file->name, chproj_callback,
 			100, FTW_PHYS | FTW_MOUNT | FTW_DEPTH);
-	else if (setprojid(file->name, file->fd, prid) < 0)
+	else if (setprojid(file->name, file->fd, prid) < 0) {
 		perror("setprojid");
+		exitcode = 1;
+	}
 	return 0;
 }
 
@@ -486,6 +502,7 @@  get_extsize(const char *path, int fd)
 	if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) {
 		printf("%s: FS_IOC_FSGETXATTR %s: %s\n",
 			progname, path, strerror(errno));
+		exitcode = 1;
 		return 0;
 	}
 	printf("[%u] %s\n", fsx.fsx_extsize, path);
@@ -498,6 +515,7 @@  set_extsize(const char *path, int fd, long extsz)
 	struct fsxattr	fsx;
 	struct stat	stat;
 
+	exitcode = 1;
 	if (fstat(fd, &stat) < 0) {
 		perror("fstat");
 		return 0;
@@ -524,6 +542,7 @@  set_extsize(const char *path, int fd, long extsz)
 		return 0;
 	}
 
+	exitcode = 0;
 	return 0;
 }
 
@@ -542,6 +561,7 @@  get_extsize_callback(
 	if ((fd = open(path, O_RDONLY)) == -1) {
 		fprintf(stderr, _("%s: cannot open %s: %s\n"),
 			progname, path, strerror(errno));
+		exitcode = 1;
 	} else {
 		get_extsize(path, fd);
 		close(fd);
@@ -564,6 +584,7 @@  set_extsize_callback(
 	if ((fd = open(path, O_RDONLY)) == -1) {
 		fprintf(stderr, _("%s: cannot open %s: %s\n"),
 			progname, path, strerror(errno));
+		exitcode = 1;
 	} else {
 		set_extsize(path, fd, extsize);
 		close(fd);
@@ -601,6 +622,7 @@  extsize_f(
 		if (extsize < 0) {
 			printf(_("non-numeric extsize argument -- %s\n"),
 				argv[optind]);
+			exitcode = 1;
 			return 0;
 		}
 	} else {
diff --git a/io/parent.c b/io/parent.c
index 1968516d2c00..4653ddf3789a 100644
--- a/io/parent.c
+++ b/io/parent.c
@@ -387,6 +387,7 @@  parent_f(int argc, char **argv)
 	if (!fs) {
 		fprintf(stderr, _("file argument, \"%s\", is not in a mounted XFS filesystem\n"),
 			file->name);
+		exitcode = 1;
 		return 1;
 	}
 	mntpt = fs->fs_dir;
diff --git a/io/pread.c b/io/pread.c
index 60650aa340f6..7e9425fe21e8 100644
--- a/io/pread.c
+++ b/io/pread.c
@@ -390,6 +390,7 @@  pread_f(
 	int		eof = 0, direction = IO_FORWARD;
 	int		c;
 
+	exitcode = 1;
 	Cflag = qflag = uflag = vflag = 0;
 	init_cvtnum(&fsblocksize, &fssectsize);
 	bsize = fsblocksize;
@@ -488,6 +489,8 @@  pread_f(
 	}
 	if (c < 0)
 		return 0;
+
+	exitcode = 0;
 	if (qflag)
 		return 0;
 	gettimeofday(&t2, NULL);
diff --git a/io/prealloc.c b/io/prealloc.c
index 1a1c9ca37da2..2ddde9023760 100644
--- a/io/prealloc.c
+++ b/io/prealloc.c
@@ -88,6 +88,7 @@  allocsp_f(
 {
 	xfs_flock64_t	segment;
 
+	exitcode = 1;
 	if (!offset_length(argv[1], argv[2], &segment))
 		return 0;
 
@@ -95,6 +96,7 @@  allocsp_f(
 		perror("XFS_IOC_ALLOCSP64");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -105,6 +107,7 @@  freesp_f(
 {
 	xfs_flock64_t	segment;
 
+	exitcode = 1;
 	if (!offset_length(argv[1], argv[2], &segment))
 		return 0;
 
@@ -112,6 +115,7 @@  freesp_f(
 		perror("XFS_IOC_FREESP64");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -122,6 +126,7 @@  resvsp_f(
 {
 	xfs_flock64_t	segment;
 
+	exitcode = 1;
 	if (!offset_length(argv[1], argv[2], &segment))
 		return 0;
 
@@ -129,6 +134,7 @@  resvsp_f(
 		perror("XFS_IOC_RESVSP64");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -139,6 +145,7 @@  unresvsp_f(
 {
 	xfs_flock64_t	segment;
 
+	exitcode = 1;
 	if (!offset_length(argv[1], argv[2], &segment))
 		return 0;
 
@@ -146,6 +153,7 @@  unresvsp_f(
 		perror("XFS_IOC_UNRESVSP64");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -156,6 +164,7 @@  zero_f(
 {
 	xfs_flock64_t	segment;
 
+	exitcode = 1;
 	if (!offset_length(argv[1], argv[2], &segment))
 		return 0;
 
@@ -163,6 +172,7 @@  zero_f(
 		perror("XFS_IOC_ZERO_RANGE");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -198,6 +208,7 @@  fallocate_f(
 	int		mode = 0;
 	int		c;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "cikpu")) != EOF) {
 		switch (c) {
 		case 'c':
@@ -230,6 +241,7 @@  fallocate_f(
 		perror("fallocate");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -241,6 +253,7 @@  fpunch_f(
 	xfs_flock64_t	segment;
 	int		mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
 
+	exitcode = 1;
 	if (!offset_length(argv[1], argv[2], &segment))
 		return 0;
 
@@ -249,6 +262,7 @@  fpunch_f(
 		perror("fallocate");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -260,6 +274,7 @@  fcollapse_f(
 	xfs_flock64_t	segment;
 	int		mode = FALLOC_FL_COLLAPSE_RANGE;
 
+	exitcode = 1;
 	if (!offset_length(argv[1], argv[2], &segment))
 		return 0;
 
@@ -268,6 +283,7 @@  fcollapse_f(
 		perror("fallocate");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -279,6 +295,7 @@  finsert_f(
 	xfs_flock64_t	segment;
 	int		mode = FALLOC_FL_INSERT_RANGE;
 
+	exitcode = 1;
 	if (!offset_length(argv[1], argv[2], &segment))
 		return 0;
 
@@ -287,6 +304,7 @@  finsert_f(
 		perror("fallocate");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -299,6 +317,7 @@  fzero_f(
 	int		mode = FALLOC_FL_ZERO_RANGE;
 	int		index = 1;
 
+	exitcode = 1;
 	if (strncmp(argv[index], "-k", 3) == 0) {
 		mode |= FALLOC_FL_KEEP_SIZE;
 		index++;
@@ -312,6 +331,7 @@  fzero_f(
 		perror("fallocate");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
@@ -324,6 +344,7 @@  funshare_f(
 	int		mode = FALLOC_FL_UNSHARE_RANGE;
 	int		index = 1;
 
+	exitcode = 1;
 	if (!offset_length(argv[index], argv[index + 1], &segment))
 		return 0;
 
@@ -332,6 +353,7 @@  funshare_f(
 		perror("fallocate");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 #endif	/* HAVE_FALLOCATE */
diff --git a/io/pwrite.c b/io/pwrite.c
index a89edfd0496f..ca262bc40b25 100644
--- a/io/pwrite.c
+++ b/io/pwrite.c
@@ -295,6 +295,7 @@  pwrite_f(
 	int		c, fd = -1;
 	int		pwritev2_flags = 0;
 
+	exitcode = 1;
 	Cflag = qflag = uflag = dflag = wflag = Wflag = 0;
 	init_cvtnum(&fsblocksize, &fssectsize);
 	bsize = fsblocksize;
@@ -446,6 +447,8 @@  pwrite_f(
 			goto done;
 		}
 	}
+
+	exitcode = 0;
 	if (qflag)
 		goto done;
 	gettimeofday(&t2, NULL);
diff --git a/io/readdir.c b/io/readdir.c
index ca7a881d27e4..65fe51d2ca68 100644
--- a/io/readdir.c
+++ b/io/readdir.c
@@ -149,6 +149,7 @@  readdir_f(
 	DIR *dir;
 	int dfd;
 
+	exitcode = 1;
 	init_cvtnum(&fsblocksize, &fssectsize);
 
 	while ((c = getopt(argc, argv, "l:o:v")) != EOF) {
@@ -169,12 +170,12 @@  readdir_f(
 
 	dfd = dup(file->fd);
 	if (dfd < 0)
-		return -1;
+		return 0;
 
 	dir = fdopendir(dfd);
 	if (!dir) {
 		close(dfd);
-		return -1;
+		return 0;
 	}
 
 	if (offset == -1) {
@@ -199,6 +200,7 @@  readdir_f(
 	printf(_("%s, %d ops, %s (%s/sec and %.4f ops/sec)\n"),
 		s1, cnt, ts, s2, tdiv(cnt, t2));
 
+	exitcode = 0;
 	return 0;
 }
 
diff --git a/io/reflink.c b/io/reflink.c
index f584e8f1fe43..86a20b2c629e 100644
--- a/io/reflink.c
+++ b/io/reflink.c
@@ -75,6 +75,7 @@  dedupe_ioctl(
 		error = ioctl(fd, XFS_IOC_FILE_EXTENT_SAME, args);
 		if (error) {
 			perror("XFS_IOC_FILE_EXTENT_SAME");
+			exitcode = 1;
 			goto done;
 		}
 		if (info->status < 0) {
@@ -139,24 +140,29 @@  dedupe_f(
 	soffset = cvtnum(fsblocksize, fssectsize, argv[optind]);
 	if (soffset < 0) {
 		printf(_("non-numeric src offset argument -- %s\n"), argv[optind]);
+		exitcode = 1;
 		return 0;
 	}
 	optind++;
 	doffset = cvtnum(fsblocksize, fssectsize, argv[optind]);
 	if (doffset < 0) {
 		printf(_("non-numeric dest offset argument -- %s\n"), argv[optind]);
+		exitcode = 1;
 		return 0;
 	}
 	optind++;
 	count = cvtnum(fsblocksize, fssectsize, argv[optind]);
 	if (count < 0) {
 		printf(_("non-positive length argument -- %s\n"), argv[optind]);
+		exitcode = 1;
 		return 0;
 	}
 
 	fd = openfile(infile, NULL, IO_READONLY, 0, NULL);
-	if (fd < 0)
+	if (fd < 0) {
+		exitcode = 1;
 		return 0;
+	}
 
 	gettimeofday(&t1, NULL);
 	total = dedupe_ioctl(fd, soffset, doffset, count, &ops);
@@ -237,6 +243,7 @@  reflink_f(
 	struct timeval	t1, t2;
 	int		c, ops = 0, fd = -1;
 
+	exitcode = 1;
 	condensed = quiet_flag = 0;
 	doffset = soffset = 0;
 	init_cvtnum(&fsblocksize, &fssectsize);
@@ -284,7 +291,11 @@  clone_all:
 
 	gettimeofday(&t1, NULL);
 	total = reflink_ioctl(fd, soffset, doffset, count, &ops);
-	if (ops == 0 || quiet_flag)
+	if (ops == 0)
+		goto done;
+
+	exitcode = 0;
+	if (quiet_flag)
 		goto done;
 	gettimeofday(&t2, NULL);
 	t2 = tsub(t2, t1);
diff --git a/io/resblks.c b/io/resblks.c
index 06903f5bb748..33da10711a0b 100644
--- a/io/resblks.c
+++ b/io/resblks.c
@@ -32,6 +32,7 @@  resblks_f(
 	xfs_fsop_resblks_t	res;
 	long long		blks;
 
+	exitcode = 1;
 	if (argc == 2) {
 		blks = cvtnum(file->geom.blocksize, file->geom.sectsize, argv[1]);
 		if (blks < 0) {
@@ -51,6 +52,7 @@  resblks_f(
 			(unsigned long long) res.resblks);
 	printf(_("available reserved blocks = %llu\n"),
 			(unsigned long long) res.resblks_avail);
+	exitcode = 0;
 	return 0;
 }
 
diff --git a/io/seek.c b/io/seek.c
index 871b47262f03..e43a8f05ea36 100644
--- a/io/seek.c
+++ b/io/seek.c
@@ -111,6 +111,7 @@  seek_f(
 	int		flag;
 	int		startflag;
 
+	exitcode = 1;
 	flag = startflag = 0;
 	init_cvtnum(&fsblocksize, &fssectsize);
 
@@ -186,9 +187,11 @@  found_hole:
 	for (c = 0; flag; c++) {
 		if (offset == -1) {
 			/* print error or eof if the only entry */
-			if (errno != ENXIO || c == 0 )
+			if (errno != ENXIO || c == 0 ) {
 				seek_output(startflag, seekinfo[current].name,
 					    start, offset);
+				exitcode = 0;
+			}
 			return 0;	/* stop on error or EOF */
 		}
 
@@ -210,6 +213,7 @@  found_hole:
 		if (offset != -1 && offset <= start)
 			goto bad_result;
 	}
+	exitcode = 0;
 	return 0;
 
 bad_result:
diff --git a/io/sendfile.c b/io/sendfile.c
index 063fa7f48114..ce6b9f127e4e 100644
--- a/io/sendfile.c
+++ b/io/sendfile.c
@@ -85,6 +85,7 @@  sendfile_f(
 	int		Cflag, qflag;
 	int		c, fd = -1;
 
+	exitcode = 1;
 	Cflag = qflag = 0;
 	init_cvtnum(&blocksize, &sectsize);
 	while ((c = getopt(argc, argv, "Cf:i:q")) != EOF) {
@@ -146,6 +147,8 @@  sendfile_f(
 	c = send_buffer(offset, count, fd, &total);
 	if (c < 0)
 		goto done;
+
+	exitcode = 0;
 	if (qflag)
 		goto done;
 	gettimeofday(&t2, NULL);
diff --git a/io/shutdown.c b/io/shutdown.c
index 022a0e9a07ae..6374c973b44d 100644
--- a/io/shutdown.c
+++ b/io/shutdown.c
@@ -30,6 +30,7 @@  shutdown_f(
 {
 	int		c, flag = XFS_FSOP_GOING_FLAGS_NOLOGFLUSH;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "fv")) != -1) {
 		switch (c) {
 		case 'f':
@@ -44,6 +45,7 @@  shutdown_f(
 		perror("XFS_IOC_GOINGDOWN");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
diff --git a/io/stat.c b/io/stat.c
index 41d421525791..2e5e90f7a172 100644
--- a/io/stat.c
+++ b/io/stat.c
@@ -137,6 +137,7 @@  stat_f(
 	struct stat	st;
 	int		c, verbose = 0, raw = 0;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "rv")) != EOF) {
 		switch (c) {
 		case 'r':
@@ -157,6 +158,7 @@  stat_f(
 		perror("fstat");
 		return 0;
 	}
+	exitcode = 0;
 
 	if (raw)
 		return dump_raw_stat(&st);
@@ -193,6 +195,7 @@  statfs_f(
 	printf(_("fd.path = \"%s\"\n"), file->name);
 	if (platform_fstatfs(file->fd, &st) < 0) {
 		perror("fstatfs");
+		exitcode = 1;
 	} else {
 		printf(_("statfs.f_bsize = %lld\n"), (long long) st.f_bsize);
 		printf(_("statfs.f_blocks = %lld\n"), (long long) st.f_blocks);
@@ -207,6 +210,7 @@  statfs_f(
 		return 0;
 	if ((xfsctl(file->name, file->fd, XFS_IOC_FSGEOMETRY_V1, &fsgeo)) < 0) {
 		perror("XFS_IOC_FSGEOMETRY_V1");
+		exitcode = 1;
 	} else {
 		printf(_("geom.bsize = %u\n"), fsgeo.blocksize);
 		printf(_("geom.agcount = %u\n"), fsgeo.agcount);
@@ -223,6 +227,7 @@  statfs_f(
 	}
 	if ((xfsctl(file->name, file->fd, XFS_IOC_FSCOUNTS, &fscounts)) < 0) {
 		perror("XFS_IOC_FSCOUNTS");
+		exitcode = 1;
 	} else {
 		printf(_("counts.freedata = %llu\n"),
 			(unsigned long long) fscounts.freedata);
@@ -315,6 +320,7 @@  statx_f(
 	int		atflag = 0;
 	unsigned int	mask = STATX_ALL;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "m:rvFD")) != EOF) {
 		switch (c) {
 		case 'm':
@@ -358,6 +364,7 @@  statx_f(
 		perror("statx");
 		return 0;
 	}
+	exitcode = 0;
 
 	if (raw)
 		return dump_raw_statx(&stx);
diff --git a/io/sync_file_range.c b/io/sync_file_range.c
index 7e4f3e6da397..2a9965b098d2 100644
--- a/io/sync_file_range.c
+++ b/io/sync_file_range.c
@@ -46,6 +46,7 @@  sync_range_f(
 	int		c, sync_mode = 0;
 	size_t		blocksize, sectsize;
 
+	exitcode = 1;
 	while ((c = getopt(argc, argv, "abw")) != EOF) {
 		switch (c) {
 		case 'a':
@@ -87,6 +88,7 @@  sync_range_f(
 		perror("sync_file_range");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
diff --git a/io/truncate.c b/io/truncate.c
index 20bada82c4aa..d741e36860b6 100644
--- a/io/truncate.c
+++ b/io/truncate.c
@@ -31,6 +31,7 @@  truncate_f(
 	off64_t		offset;
 	size_t		blocksize, sectsize;
 
+	exitcode = 1;
 	init_cvtnum(&blocksize, &sectsize);
 	offset = cvtnum(blocksize, sectsize, argv[1]);
 	if (offset < 0) {
@@ -42,6 +43,7 @@  truncate_f(
 		perror("ftruncate");
 		return 0;
 	}
+	exitcode = 0;
 	return 0;
 }
 
diff --git a/io/utimes.c b/io/utimes.c
index faf9b8d55dbc..9fcb44c22bc0 100755
--- a/io/utimes.c
+++ b/io/utimes.c
@@ -44,6 +44,7 @@  utimes_f(
 	struct timespec t[2];
 	int result;
 
+	exitcode = 1;
 	/* Get the timestamps */
 	result = timespec_from_string(argv[1], argv[2], &t[0]);
 	if (result) {
@@ -62,6 +63,7 @@  utimes_f(
 		return 0;
 	}
 
+	exitcode = 0;
 	return 0;
 }