Beispiel #1
0
static void
lookup_complex(const atf_tc_t *tc, const char *mountpath)
{
	char pb[MAXPATHLEN];
	struct stat sb1, sb2;

	USES_DIRS;

	if (FSTYPE_UDF(tc))
		atf_tc_expect_fail("PR kern/49033");

	sprintf(pb, "%s/dir", mountpath);
	if (rump_sys_mkdir(pb, 0777) == -1)
		atf_tc_fail_errno("mkdir");
	if (rump_sys_stat(pb, &sb1) == -1)
		atf_tc_fail_errno("stat 1");

	sprintf(pb, "%s/./dir/../././dir/.", mountpath);
	if (rump_sys_stat(pb, &sb2) == -1)
		atf_tc_fail_errno("stat 2");

	if (memcmp(&sb1, &sb2, sizeof(sb1)) != 0) {
		printf("what\tsb1\t\tsb2\n");

#define FIELD(FN)	\
		printf(#FN "\t%lld\t%lld\n", \
		(long long)sb1.FN, (long long)sb2.FN)
#define TIME(FN)	\
		printf(#FN "\t%lld.%ld\t%lld.%ld\n", \
		(long long)sb1.FN.tv_sec, sb1.FN.tv_nsec, \
		(long long)sb2.FN.tv_sec, sb2.FN.tv_nsec)

		FIELD(st_dev);
		FIELD(st_mode);
		FIELD(st_ino);
		FIELD(st_nlink);
		FIELD(st_uid);
		FIELD(st_gid);
		FIELD(st_rdev);
		TIME(st_atim);
		TIME(st_mtim);
		TIME(st_ctim);
		TIME(st_birthtim);
		FIELD(st_size);
		FIELD(st_blocks);
		FIELD(st_flags);
		FIELD(st_gen);

#undef FIELD
#undef TIME

		atf_tc_fail("stat results differ, see ouput for more details");
	}
	if (FSTYPE_UDF(tc))
		atf_tc_fail("random failure of PR kern/49033 "
			    "did not happen this time");
}
Beispiel #2
0
static void
attrs(const atf_tc_t *tc, const char *mp)
{
	struct stat sb, sb2;
	struct timeval tv[2];
	int fd;

	FSTEST_ENTER();
	RL(fd = rump_sys_open(TESTFILE, O_RDWR | O_CREAT, 0755));
	RL(rump_sys_close(fd));
	RL(rump_sys_stat(TESTFILE, &sb));
	if (!(FSTYPE_MSDOS(tc) || FSTYPE_SYSVBFS(tc))) {
		RL(rump_sys_chown(TESTFILE, 1, 2));
		sb.st_uid = 1;
		sb.st_gid = 2;
		RL(rump_sys_chmod(TESTFILE, 0123));
		sb.st_mode = (sb.st_mode & ~ACCESSPERMS) | 0123;
	}

	tv[0].tv_sec = 1000000000; /* need something >1980 for msdosfs */
	tv[0].tv_usec = 1;
	tv[1].tv_sec = 1000000002; /* need even seconds for msdosfs */
	tv[1].tv_usec = 3;
	RL(rump_sys_utimes(TESTFILE, tv));
	RL(rump_sys_utimes(TESTFILE, tv)); /* XXX: utimes & birthtime */
	sb.st_atimespec.tv_sec = 1000000000;
	sb.st_atimespec.tv_nsec = 1000;
	sb.st_mtimespec.tv_sec = 1000000002;
	sb.st_mtimespec.tv_nsec = 3000;

	RL(rump_sys_stat(TESTFILE, &sb2));
#define CHECK(a) ATF_REQUIRE_EQ(sb.a, sb2.a)
	if (FSTYPE_ZFS(tc))
		atf_tc_expect_fail("PR kern/47656: Test known to be broken");
	if (!(FSTYPE_MSDOS(tc) || FSTYPE_SYSVBFS(tc))) {
		CHECK(st_uid);
		CHECK(st_gid);
		CHECK(st_mode);
	}
	if (!FSTYPE_MSDOS(tc)) {
		/* msdosfs has only access date, not time */
		CHECK(st_atimespec.tv_sec);
	}
	CHECK(st_mtimespec.tv_sec);
	if (!(FSTYPE_EXT2FS(tc) || FSTYPE_MSDOS(tc) ||
	      FSTYPE_SYSVBFS(tc) || FSTYPE_V7FS(tc))) {
		CHECK(st_atimespec.tv_nsec);
		CHECK(st_mtimespec.tv_nsec);
	}
#undef  CHECK

	FSTEST_EXIT();
}
Beispiel #3
0
static int
move_to_dir(const char *from, const char *to, int flags)
{
	const char *p;
	int rv;
	char path[PATH_MAX + 1];
	struct stat file_stat;
	size_t len;

	len = strlen(to);
	rv = strlcpy(path, to, PATH_MAX + 1);
	if (rv != (int)len) {
		warn("%s", to);
		return -1;
	}

	rv = rump_sys_stat(from, &file_stat);
	if (rv == -1) {
		warn("%s", from);
		return -1;
	}

	p = strrchr(from, '/');
	if (p == NULL)
		p = from;
	else
		++p;

	rv = strlcat(path, p, PATH_MAX + 1);
	if (rv == -1) {
		warn("%s/%s", path, p);
		return -1;
	}

	if (flags & FSU_MV_VERBOSE)
		printf("%s -> %s\n", from, path);

	if (flags & FSU_MV_INTERACTIVE) {
		rv = rump_sys_stat(path, &file_stat);
		if (rv != -1 && !is_user_ok(to))
			return 0;
	}

	rv = rump_sys_rename(from, path);
	if (rv == -1) {
		warn("%s", from);
		return -1;
	}
	return 0;
}
Beispiel #4
0
static void
lookup_simple(const atf_tc_t *tc, const char *mountpath)
{
	char pb[MAXPATHLEN], final[MAXPATHLEN];
	struct stat sb1, sb2;

	strcpy(final, mountpath);
	sprintf(pb, "%s/../%s", mountpath, basename(final));
	if (rump_sys_stat(pb, &sb1) == -1)
		atf_tc_fail_errno("stat 1");

	sprintf(pb, "%s/./../%s", mountpath, basename(final));
	if (rump_sys_stat(pb, &sb2) == -1)
		atf_tc_fail_errno("stat 2");

	ATF_REQUIRE(memcmp(&sb1, &sb2, sizeof(sb1)) == 0);
}
Beispiel #5
0
static void
lookup_complex(const atf_tc_t *tc, const char *mountpath)
{
	char pb[MAXPATHLEN];
	struct stat sb1, sb2;

	USES_DIRS;

	sprintf(pb, "%s/dir", mountpath);
	if (rump_sys_mkdir(pb, 0777) == -1)
		atf_tc_fail_errno("mkdir");
	if (rump_sys_stat(pb, &sb1) == -1)
		atf_tc_fail_errno("stat 1");

	sprintf(pb, "%s/./dir/../././dir/.", mountpath);
	if (rump_sys_stat(pb, &sb2) == -1)
		atf_tc_fail_errno("stat 2");

	ATF_REQUIRE(memcmp(&sb1, &sb2, sizeof(sb1)) == 0);
}
Beispiel #6
0
static void
dir_simple(const atf_tc_t *tc, const char *mountpath)
{
	char pb[MAXPATHLEN];
	struct stat sb;

	USES_DIRS;

	/* check we can create directories */
	sprintf(pb, "%s/dir", mountpath);
	if (rump_sys_mkdir(pb, 0777) == -1)
		atf_tc_fail_errno("mkdir");
	if (rump_sys_stat(pb, &sb) == -1)
		atf_tc_fail_errno("stat new directory");

	/* check we can remove then and that it makes them unreachable */
	if (rump_sys_rmdir(pb) == -1)
		atf_tc_fail_errno("rmdir");
	if (rump_sys_stat(pb, &sb) != -1 || errno != ENOENT)
		atf_tc_fail("ENOENT expected from stat");
}
Beispiel #7
0
static int
fsu_mv(const char *from, const char *to, int flags)
{
	int rv;
	struct stat file_stat;

	rv = rump_sys_stat(to, &file_stat);
	if (rv == -1 || !S_ISDIR(file_stat.st_mode))
		return move_to_file(from, to, flags);

	return move_to_dir(from, to, flags);
}
Beispiel #8
0
static time_t
lock_it(void)
{
	struct stat st;

	if (rump_sys_stat(LOCKFILE, &st) != 0)
		st.st_mtime = 0;

	int f = rump_sys_open(LOCKFILE, O_WRONLY|O_CREAT|O_TRUNC, 0666);
	if (f == -1) return 0;
	rump_sys_close(f);

	return st.st_mtime;
}
Beispiel #9
0
static void
checkfile(const char *path, struct stat *refp)
{
	char buf[MAXPATHLEN];
	struct stat sb;
	static int n = 1;

	md(buf, path, "file");
	if (rump_sys_stat(buf, &sb) == -1)
		atf_tc_fail_errno("cannot stat file %d (%s)", n, buf);
	if (memcmp(&sb, refp, sizeof(sb)) != 0)
		atf_tc_fail("stat mismatch %d", n);
	n++;
}
static void
flags(const atf_tc_t *tc, const char *mp)
{
	const char *name = "file.test";
	int fd, fflags;
	struct stat st;

	FSTEST_ENTER();

	if ((fd = rump_sys_open(name, O_RDWR|O_CREAT, 0666)) == -1)
		atf_tc_fail_errno("open");
	if (rump_sys_close(fd) == -1)
		atf_tc_fail_errno("close");

	if (rump_sys_stat(name, &st) == -1)
		atf_tc_fail_errno("stat");
	if (FSTYPE_ZFS(tc))
		atf_tc_expect_fail("PR kern/47656: Test known to be broken");
	if (rump_sys_chflags(name, st.st_flags) == -1) {
		if (errno == EOPNOTSUPP)
			atf_tc_skip("file flags not supported by file system");
		atf_tc_fail_errno("chflags");
	}

	fflags = st.st_flags | UF_IMMUTABLE;

	rump_pub_lwproc_rfork(RUMP_RFCFDG);
	if (rump_sys_setuid(1) == -1)
		atf_tc_fail_errno("setuid");
	fflags |= UF_IMMUTABLE;
	if (rump_sys_chflags(name, fflags) != -1 || errno != EPERM)
		atf_tc_fail_errno("chflags");
	rump_pub_lwproc_releaselwp();

	if (rump_sys_chflags(name, fflags) == -1)
		atf_tc_fail_errno("chflags");

	fflags &= ~UF_IMMUTABLE;
	if (rump_sys_chflags(name, fflags) == -1)
		atf_tc_fail_errno("chflags");

	if (rump_sys_unlink(name) == -1)
		atf_tc_fail_errno("unlink");

	FSTEST_EXIT();
}
Beispiel #11
0
static int
move_to_file(const char *from, const char *to, int flags)
{
	int rv;
	struct stat file_stat;

	if (flags & FSU_MV_VERBOSE)
		printf("%s -> %s\n", from, to);

	if (flags & FSU_MV_INTERACTIVE) {
		rv = rump_sys_stat(to, &file_stat);
		if (rv != -1 && !is_user_ok(to))
			return 0;
	}
	rv = rump_sys_rename(from, to);
	if (rv == -1) {
		warn("%s or %s", from, to);
		return -1;
	}
	return 0;
}
Beispiel #12
0
static void
rename_reg_nodir(const atf_tc_t *tc, const char *mp)
{
	bool haslinks;
	struct stat sb;
	ino_t f1ino, f2ino;

	if (FSTYPE_RUMPFS(tc))
		atf_tc_skip("rename not supported by file system");

	if (rump_sys_chdir(mp) == -1)
		atf_tc_fail_errno("chdir mountpoint");

	if (FSTYPE_MSDOS(tc) || FSTYPE_SYSVBFS(tc))
		haslinks = false;
	else
		haslinks = true;

	if (rump_sys_mknod("file1", S_IFREG | 0777, -1) == -1)
		atf_tc_fail_errno("create file");
	if (rump_sys_mknod("file2", S_IFREG | 0777, -1) == -1)
		atf_tc_fail_errno("create file");

	if (rump_sys_stat("file1", &sb) == -1)
		atf_tc_fail_errno("stat");
	f1ino = sb.st_ino;

	if (haslinks) {
		if (rump_sys_link("file1", "file_link") == -1)
			atf_tc_fail_errno("link");
		if (rump_sys_stat("file_link", &sb) == -1)
			atf_tc_fail_errno("stat");
		ATF_REQUIRE_EQ(sb.st_ino, f1ino);
		ATF_REQUIRE_EQ(sb.st_nlink, 2);
	}

	if (rump_sys_stat("file2", &sb) == -1)
		atf_tc_fail_errno("stat");
	f2ino = sb.st_ino;

	if (rump_sys_rename("file1", "file3") == -1)
		atf_tc_fail_errno("rename 1");
	if (rump_sys_stat("file3", &sb) == -1)
		atf_tc_fail_errno("stat 1");
	if (haslinks) {
		ATF_REQUIRE_EQ(sb.st_ino, f1ino);
	}
	if (rump_sys_stat("file1", &sb) != -1 || errno != ENOENT)
		atf_tc_fail_errno("source 1");

	if (rump_sys_rename("file3", "file2") == -1)
		atf_tc_fail_errno("rename 2");
	if (rump_sys_stat("file2", &sb) == -1)
		atf_tc_fail_errno("stat 2");
	if (haslinks) {
		ATF_REQUIRE_EQ(sb.st_ino, f1ino);
	}

	if (rump_sys_stat("file3", &sb) != -1 || errno != ENOENT)
		atf_tc_fail_errno("source 2");

	if (haslinks) {
		if (rump_sys_rename("file2", "file_link") == -1)
			atf_tc_fail_errno("rename hardlink");
		if (rump_sys_stat("file2", &sb) != -1 || errno != ENOENT)
			atf_tc_fail_errno("source 3");
		if (rump_sys_stat("file_link", &sb) == -1)
			atf_tc_fail_errno("stat 2");
		ATF_REQUIRE_EQ(sb.st_ino, f1ino);
		ATF_REQUIRE_EQ(sb.st_nlink, 1);
	}

	rump_sys_chdir("/");
}
Beispiel #13
0
static void
rename_dir(const atf_tc_t *tc, const char *mp)
{
	char pb1[MAXPATHLEN], pb2[MAXPATHLEN], pb3[MAXPATHLEN];
	struct stat ref, sb;

	if (FSTYPE_RUMPFS(tc))
		atf_tc_skip("rename not supported by file system");

	USES_DIRS;

	md(pb1, mp, "dir1");
	if (rump_sys_mkdir(pb1, 0777) == -1)
		atf_tc_fail_errno("mkdir 1");

	md(pb2, mp, "dir2");
	if (rump_sys_mkdir(pb2, 0777) == -1)
		atf_tc_fail_errno("mkdir 2");
	md(pb2, mp, "dir2/subdir");
	if (rump_sys_mkdir(pb2, 0777) == -1)
		atf_tc_fail_errno("mkdir 3");

	md(pb3, mp, "dir1/file");
	if (rump_sys_mknod(pb3, S_IFREG | 0777, -1) == -1)
		atf_tc_fail_errno("create file");
	if (rump_sys_stat(pb3, &ref) == -1)
		atf_tc_fail_errno("stat of file");

	/*
	 * First try ops which should succeed.
	 */

	/* rename within directory */
	md(pb3, mp, "dir3");
	if (rump_sys_rename(pb1, pb3) == -1)
		atf_tc_fail_errno("rename 1");
	checkfile(pb3, &ref);

	/* rename directory onto itself (two ways, should fail) */
	md(pb1, mp, "dir3/.");
	if (rump_sys_rename(pb1, pb3) != -1 || errno != EINVAL)
		atf_tc_fail_errno("rename 2");
	if (FSTYPE_ZFS(tc))
		atf_tc_expect_fail("PR kern/47656: Test known to be broken");
	if (rump_sys_rename(pb3, pb1) != -1 || errno != EISDIR)
		atf_tc_fail_errno("rename 3");

	checkfile(pb3, &ref);

	/* rename father of directory into directory */
	md(pb1, mp, "dir2/dir");
	md(pb2, mp, "dir2");
	if (rump_sys_rename(pb2, pb1) != -1 || errno != EINVAL)
		atf_tc_fail_errno("rename 4");

	/* same for grandfather */
	md(pb1, mp, "dir2/subdir/dir2");
	if (rump_sys_rename(pb2, pb1) != -1 || errno != EINVAL)
		atf_tc_fail("rename 5");

	checkfile(pb3, &ref);

	/* rename directory over a non-empty directory */
	if (rump_sys_rename(pb2, pb3) != -1 || errno != ENOTEMPTY)
		atf_tc_fail("rename 6");

	/* cross-directory rename */
	md(pb1, mp, "dir3");
	md(pb2, mp, "dir2/somedir");
	if (rump_sys_rename(pb1, pb2) == -1)
		atf_tc_fail_errno("rename 7");
	checkfile(pb2, &ref);

	/* move to parent directory */
	md(pb1, mp, "dir2/somedir/../../dir3");
	if (rump_sys_rename(pb2, pb1) == -1)
		atf_tc_fail_errno("rename 8");
	md(pb1, mp, "dir2/../dir3");
	checkfile(pb1, &ref);

	/* atomic cross-directory rename */
	md(pb3, mp, "dir2/subdir");
	if (rump_sys_rename(pb1, pb3) == -1)
		atf_tc_fail_errno("rename 9");
	checkfile(pb3, &ref);

	/* rename directory over an empty directory */
	md(pb1, mp, "parent");
	md(pb2, mp, "parent/dir1");
	md(pb3, mp, "parent/dir2");
	RL(rump_sys_mkdir(pb1, 0777));
	RL(rump_sys_mkdir(pb2, 0777));
	RL(rump_sys_mkdir(pb3, 0777));
	RL(rump_sys_rename(pb2, pb3));

	RL(rump_sys_stat(pb1, &sb));
	if (! FSTYPE_MSDOS(tc))
		ATF_CHECK_EQ(sb.st_nlink, 3);
	RL(rump_sys_rmdir(pb3));
	RL(rump_sys_rmdir(pb1));
}
Beispiel #14
0
int
copy_file(FTSENT *entp, int dne)
{
	static unsigned char buf[MAXBSIZE];
	struct stat to_stat, *fs;
	int ch, checkch, rv, rcount, rval, tolnk, wcount, fdin, fdout;
	off_t off;

	fs = entp->fts_statp;
	tolnk = ((Rflag && !(Lflag || Hflag)) || Pflag);

	/*
	 * If the file exists and we're interactive, verify with the user.
	 * If the file DNE, set the mode to be the from file, minus setuid
	 * bits, modified by the umask; arguably wrong, but it makes copying
	 * executables work right and it's been that way forever.  (The
	 * other choice is 666 or'ed with the execute bits on the from file
	 * modified by the umask.)
	 */
	if (!dne) {
		if (iflag) {
			(void)fprintf(stderr, "overwrite %s? ", to.p_path);
			checkch = ch = getchar();
			while (ch != '\n' && ch != EOF)
				ch = getchar();
			if (checkch != 'y' && checkch != 'Y')
				return (0);
		}
		rump_sys_unlink(to.p_path);
	}

	rv = rump_sys_open(to.p_path, fs->st_mode & ~(S_ISUID | S_ISGID));
	if (rv == -1 && (fflag || tolnk)) {
		/*
		 * attempt to remove existing destination file name and
		 * create a new file
		 */
		rump_sys_unlink(to.p_path);
		rv = rump_sys_open(to.p_path,
				 fs->st_mode & ~(S_ISUID | S_ISGID));
		if (rv == -1) {
			warn("%s", to.p_path);
			return (1);
		}
	}
	fdout = rv;
	fdin = rump_sys_open(entp->fts_path, O_RDONLY);

	rval = 0;
	/*
	 * There's no reason to do anything other than close the file
	 * now if it's empty, so let's not bother.
	 */
	off = 0;
	if (fs->st_size > 0) {
		while ((rcount = rump_sys_read(fdin, buf, MAXBSIZE)) > 0) {
			wcount = rump_sys_write(fdout, buf, (size_t)rcount);
			if (rcount != wcount || wcount == -1) {
				warn("%s", to.p_path);
				rval = 1;
				break;
			}
			off += rcount;
		}
		if (rcount < 0) {
			warn("%s", entp->fts_path);
			rval = 1;
		}
	}

	if (rval == 1)
		return (1);

	if (pflag && setfile(fs, 0))
		rval = 1;
	/*
	 * If the source was setuid or setgid, lose the bits unless the
	 * copy is owned by the same user and group.
	 */
#define	RETAINBITS \
	(S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
	if (!pflag && dne
	    && fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) {
		if (rump_sys_stat(to.p_path, &to_stat)) {
			warn("%s", to.p_path);
			rval = 1;
		} else if (fs->st_gid == to_stat.st_gid &&
			   rump_sys_chmod(to.p_path,
				      fs->st_mode & RETAINBITS & ~myumask)) {
			warn("%s", to.p_path);
			rval = 1;
		}
	}

	/* set the mod/access times now after close of the fd */
	if (pflag && set_utimes(to.p_path, fs)) {
	    rval = 1;
	}
	return (rval);
}
Beispiel #15
0
int
main(int argc, char *argv[])
{
	struct stat st;
	int ch, rc, errs, am_readlink;
	int lsF, fmtchar, usestat, fn, nonl, quiet;
	const char *statfmt, *options, *synopsis;

	am_readlink = 0;
	lsF = 0;
	fmtchar = '\0';
	usestat = 0;
	nonl = 0;
	quiet = 0;
	linkfail = 0;
	statfmt = NULL;
	timefmt = NULL;

	setprogname(argv[0]);

	if (strcmp(getprogname(), "readlink") == 0) {
		am_readlink = 1;
		options = "fnqsv";
		synopsis = "[-fnqsv] [file ...]";
		statfmt = "%Y";
		fmtchar = 'f';
		quiet = 1;
	} else {
		options = "f:FlLnqrst:x";
		synopsis = "[-FlLnqrsx] [-f format] [-t timefmt] [file ...]";
	}

	if (fsu_mount(&argc, &argv, MOUNT_READONLY) != 0)
		usage(synopsis);

	while ((ch = getopt(argc, argv, options)) != -1)
		switch (ch) {
		case 'F':
			lsF = 1;
			break;
		case 'L':
			usestat = 1;
			break;
		case 'n':
			nonl = 1;
			break;
		case 'q':
			quiet = 1;
			break;
		case 'f':
			if (am_readlink) {
				statfmt = "%R";
				break;
			}
			statfmt = optarg;
			/* FALLTHROUGH */
		case 'l':
		case 'r':
		case 's':
			if (am_readlink) {
				quiet = 1;
				break;
			}
			/*FALLTHROUGH*/
		case 'x':
			if (fmtchar != 0)
				errx(1, "can't use format '%c' with '%c'",
				    fmtchar, ch);
			fmtchar = ch;
			break;
		case 't':
			timefmt = optarg;
			break;
		case 'v':
			quiet = 0;
			break;
		default:
			usage(synopsis);
		}

	argc -= optind;
	argv += optind;
	fn = 1;

	if (fmtchar == '\0') {
		if (lsF)
			fmtchar = 'l';
		else {
			fmtchar = 'f';
			statfmt = DEF_FORMAT;
		}
	}

	if (lsF && fmtchar != 'l')
		errx(1, "can't use format '%c' with -F", fmtchar);

	switch (fmtchar) {
	case 'f':
		/* statfmt already set */
		break;
	case 'l':
		statfmt = lsF ? LSF_FORMAT : LS_FORMAT;
		break;
	case 'r':
		statfmt = RAW_FORMAT;
		break;
	case 's':
		statfmt = SHELL_FORMAT;
		if (timefmt == NULL)
			timefmt = "%s";
		break;
	case 'x':
		statfmt = LINUX_FORMAT;
		if (timefmt == NULL)
			timefmt = "%c";
		break;
	default:
		usage(synopsis);
		/*NOTREACHED*/
	}

	if (timefmt == NULL)
		timefmt = TIME_FORMAT;

	errs = 0;
	do {
		if (argc == 0)
			rc = rump_sys_fstat(STDIN_FILENO, &st);
		else if (usestat) {
			/*
			 * Try stat() and if it fails, fall back to
			 * lstat() just in case we're examining a
			 * broken symlink.
			 */
			if ((rc = rump_sys_stat(argv[0], &st)) == -1 &&
			    errno == ENOENT &&
			    (rc = rump_sys_lstat(argv[0], &st)) == -1)
				errno = ENOENT;
		}
		else
			rc = rump_sys_lstat(argv[0], &st);

		if (rc == -1) {
			errs = 1;
			linkfail = 1;
			if (!quiet)
				warn("%s: %s",
				    argc == 0 ? "(stdin)" : argv[0],
				    usestat ? "stat" : "lstat");
		}
		else
			output(&st, argv[0], statfmt, fn, nonl, quiet);

		argv++;
		argc--;
		fn++;
	} while (argc > 0);

	return (am_readlink ? linkfail : errs);
}