// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2000-2003,2005 Silicon Graphics, Inc.
 * All Rights Reserved.
 */

#include "libxfs.h"
#include "libfrog/fsgeom.h"

int rtcp(char *, char *, int);
int xfsrtextsize(char *path);

static int pflag;
char *progname;

static void
usage(void)
{
	fprintf(stderr, _("%s [-e extsize] [-p] [-V] source target\n"), progname);
	exit(2);
}

int
main(int argc, char **argv)
{
	int	c, i, r, errflg = 0;
	struct stat	s2;
	int		extsize = - 1;

	progname = basename(argv[0]);
	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);

	while ((c = getopt(argc, argv, "pe:V")) != EOF) {
		switch (c) {
		case 'e':
			extsize = atoi(optarg);
			break;
		case 'p':
			pflag = 1;
			break;
		case 'V':
			printf(_("%s version %s\n"), progname, VERSION);
			exit(0);
		default:
			errflg++;
		}
	}

	/*
	 * Check for sufficient arguments or a usage error.
	 */
	argc -= optind;
	argv  = &argv[optind];

	if (argc < 2) {
		fprintf(stderr, _("%s: must specify files to copy\n"),
			progname);
		errflg++;
	}

	if (errflg)
		usage();

	/*
	 * If there is more than a source and target,
	 * the last argument (the target) must be a directory
	 * which really exists.
	 */
	if (argc > 2) {
		if (stat(argv[argc-1], &s2) < 0) {
			fprintf(stderr, _("%s: stat of %s failed\n"),
				progname, argv[argc-1]);
			exit(2);
		}

		if (!S_ISDIR(s2.st_mode)) {
			fprintf(stderr,
				_("%s: final argument is not directory\n"),
				progname);
			usage();
		}
	}

	/*
	 * Perform a multiple argument rtcp by
	 * multiple invocations of rtcp().
	 */
	r = 0;
	for (i = 0; i < argc-1; i++)
		r += rtcp(argv[i], argv[argc-1], extsize);

	/*
	 * Show errors by nonzero exit code.
	 */
	exit(r?2:0);
}

int
rtcp( char *source, char *target, int fextsize)
{
	int		fromfd, tofd, readct, writect, iosz, reopen;
	int		remove = 0, rtextsize;
	char		*sp, *fbuf, *ptr;
	char		tbuf[ PATH_MAX ];
	struct stat	s1, s2;
	struct fsxattr	fsxattr;
	struct dioattr	dioattr;

	/*
	 * While source or target have trailing /, remove them
	 * unless only "/".
	 */
	sp = source + strlen(source);
	if (sp) {
		while (*--sp == '/' && sp > source)
			*sp = '\0';
	}
	sp = target + strlen(target);
	if (sp) {
		while (*--sp == '/' && sp > target)
			*sp = '\0';
	}

	if ( stat(source, &s1) ) {
		fprintf(stderr, _("%s: failed stat on %s: %s\n"),
			progname, source, strerror(errno));
		return( -1);
	}

	/*
	 * check for a realtime partition
	 */
	snprintf(tbuf, sizeof(tbuf), "%s", target);
	if ( stat(target, &s2) ) {
		if (!S_ISDIR(s2.st_mode)) {
			/* take out target file name */
			if ((ptr = strrchr(tbuf, '/')) != NULL)
				*ptr = '\0';
			else
				snprintf(tbuf, sizeof(tbuf), ".");
		}
	}

	if ( (rtextsize = xfsrtextsize( tbuf ))  <= 0 ) {
		fprintf(stderr,
			_("%s: %s filesystem has no realtime partition\n"),
			progname, tbuf);
		return( -1 );
	}

	/*
	 * check if target is a directory
	 */
	snprintf(tbuf, sizeof(tbuf), "%s", target);
	if ( !stat(target, &s2) ) {
		if (S_ISDIR(s2.st_mode)) {
			snprintf(tbuf, sizeof(tbuf), "%s/%s", target,
				basename(source));
		}
	}

	if ( stat(tbuf, &s2) ) {
		/*
		 * create the file if it does not exist
		 */
		if ( (tofd = open(tbuf, O_RDWR|O_CREAT|O_DIRECT, 0666)) < 0 ) {
			fprintf(stderr, _("%s: open of %s failed: %s\n"),
				progname, tbuf, strerror(errno));
			return( -1 );
		}
		remove = 1;

		/*
		 * mark the file as a realtime file
		 */
		fsxattr.fsx_xflags = FS_XFLAG_REALTIME;
		if (fextsize != -1 )
			fsxattr.fsx_extsize = fextsize;
		else
			fsxattr.fsx_extsize = 0;

		if ( xfsctl(tbuf, tofd, FS_IOC_FSSETXATTR, &fsxattr) ) {
			fprintf(stderr,
				_("%s: set attributes on %s failed: %s\n"),
				progname, tbuf, strerror(errno));
			close( tofd );
			unlink( tbuf );
			return( -1 );
		}
	} else {
		/*
		 * open existing file
		 */
		if ( (tofd = open(tbuf, O_RDWR|O_DIRECT)) < 0 ) {
			fprintf(stderr, _("%s: open of %s failed: %s\n"),
				progname, tbuf, strerror(errno));
			return( -1 );
		}

		if ( xfsctl(tbuf, tofd, FS_IOC_FSGETXATTR, &fsxattr) ) {
			fprintf(stderr,
				_("%s: get attributes of %s failed: %s\n"),
				progname, tbuf, strerror(errno));
			close( tofd );
			return( -1 );
		}

		/*
		 * check if the existing file is already a realtime file
		 */
		if ( !(fsxattr.fsx_xflags & FS_XFLAG_REALTIME) ) {
			fprintf(stderr, _("%s: %s is not a realtime file.\n"),
				progname, tbuf);
			close( tofd );
			return( -1 );
		}

		/*
		 * check for matching extent size
		 */
		if ( (fextsize != -1) && (fsxattr.fsx_extsize != fextsize) ) {
			fprintf(stderr, _("%s: %s file extent size is %d, "
					"instead of %d.\n"),
				progname, tbuf, fsxattr.fsx_extsize, fextsize);
			close( tofd );
			return( -1 );
		}
	}

	/*
	 * open the source file
	 */
	reopen = 0;
	if ( (fromfd = open(source, O_RDONLY|O_DIRECT)) < 0 ) {
		fprintf(stderr, _("%s: open of %s source failed: %s\n"),
			progname, source, strerror(errno));
		close( tofd );
		if (remove)
			unlink( tbuf );
		return( -1 );
	}

	fsxattr.fsx_xflags = 0;
	fsxattr.fsx_extsize = 0;
	if ( xfsctl(source, fromfd, FS_IOC_FSGETXATTR, &fsxattr) ) {
		reopen = 1;
	} else {
		if (! (fsxattr.fsx_xflags & FS_XFLAG_REALTIME) ){
			fprintf(stderr, _("%s: %s is not a realtime file.\n"),
				progname, source);
			reopen = 1;
		}
	}

	if (reopen) {
		close( fromfd );
		if ( (fromfd = open(source, O_RDONLY )) < 0 ) {
			fprintf(stderr, _("%s: open of %s source failed: %s\n"),
				progname, source, strerror(errno));
			close( tofd );
			if (remove)
				unlink( tbuf );
			return( -1 );
		}
	}

	/*
	 * get direct I/O parameters
	 */
	if ( xfsctl(tbuf, tofd, XFS_IOC_DIOINFO, &dioattr) ) {
		fprintf(stderr,
			_("%s: couldn't get direct I/O information: %s\n"),
			progname, strerror(errno));
		close( fromfd );
		close( tofd );
		if ( remove )
			unlink( tbuf );
		return( -1 );
	}

	if ( rtextsize % dioattr.d_miniosz ) {
		fprintf(stderr, _("%s: extent size %d not a multiple of %d.\n"),
			progname, rtextsize, dioattr.d_miniosz);
		close( fromfd );
		close( tofd );
		if ( remove )
			unlink( tbuf );
		return( -1 );
	}

	/*
	 * Check that the source file size is a multiple of the
	 * file system block size.
	 */
	if ( s1.st_size % dioattr.d_miniosz ) {
		printf(_("The size of %s is not a multiple of %d.\n"),
			source, dioattr.d_miniosz);
		if ( pflag ) {
			printf(_("%s will be padded to %lld bytes.\n"),
				tbuf, (long long)
				(((s1.st_size / dioattr.d_miniosz) + 1)  *
					dioattr.d_miniosz) );

		} else {
			printf(_("Use the -p option to pad %s to a "
				"size which is a multiple of %d bytes.\n"),
				tbuf, dioattr.d_miniosz);
			close( fromfd );
			close( tofd );
			if ( remove )
				unlink( tbuf );
			return( -1 );
		}
	}

	iosz =  dioattr.d_miniosz;
	fbuf = memalign( dioattr.d_mem, iosz);
	memset(fbuf, 0, iosz);

	/*
	 * read the entire source file
	 */
	while ( ( readct = read( fromfd, fbuf, iosz) ) != 0 ) {
		/*
		 * if there is a read error - break
		 */
		if (readct < 0 ) {
			break;
		}

		/*
		 * if there is a short read, pad to a block boundary
		 */
		if ( readct != iosz ) {
			if ( (readct % dioattr.d_miniosz)  != 0 )  {
				readct = ( (readct/dioattr.d_miniosz) + 1 ) *
					 dioattr.d_miniosz;
			}
		}

		/*
		 * write to target file
		 */
		writect = write( tofd, fbuf, readct);

		if ( writect != readct ) {
			fprintf(stderr, _("%s: write error: %s\n"),
				progname, strerror(errno));
			close(fromfd);
			close(tofd);
			free( fbuf );
			return( -1 );
		}

		memset( fbuf, 0, iosz);
	}

	close(fromfd);
	close(tofd);
	free( fbuf );
	return( 0 );
}

/*
 * Determine the realtime extent size of the XFS file system
 */
int
xfsrtextsize( char *path)
{
	struct xfs_fsop_geom	geo;
	int			fd, rval, rtextsize;

	fd = open( path, O_RDONLY );
	if ( fd < 0 ) {
		fprintf(stderr, _("%s: could not open %s: %s\n"),
			progname, path, strerror(errno));
		return -1;
	}
	rval = -xfrog_geometry(fd, &geo);
	close(fd);
	if (rval)
		return -1;

	rtextsize = geo.rtextsize * geo.blocksize;

	return rtextsize;
}
