/* tcx.c, Version 1.0.2, 25/3/1993 by Stewart Forster */

/************************************************************************/
/*   Copyright (C) 1993 Stewart Forster					*/
/*  This program is free software; you can redistribute it and/or modify*/
/*  it under the terms of the GNU General Public License as published by*/
/*  the Free Software Foundation; either version 2, or (at your option) */
/*  any later version.							*/
/*									*/
/*  This program is distributed in the hope that it will be useful,	*/
/*  but WITHOUT ANY WARRANTY; without even the implied warranty of	*/
/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the	*/
/*  GNU General Public License for more details.			*/
/*									*/
/*  You should have received a copy of the GNU General Public License	*/
/*  along with this program; if not, write to the Free Software		*/
/*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.		*/
/************************************************************************/


#include	"config.h"

extern	int	errno;

int	main(int, char *[]);
int	is_tcx(int);
int	doencode(int, int);

#ifdef ULTRIX
int	is_file_local(char *);
#endif

int
main(int argc, char *argv[])
{
struct	stat	dostat;
char	*s;
int	perms;
int	infd, outfd;
char	tofile[MAXPATHLEN];
char	header[MAXHEADERSIZE];
struct	flock	lck;
unsigned char c;
#ifdef ULTRIX
int	islocal;
#endif

	/* Check to make sure we have an argument */

	if(argc < 2)
	{
		(void)fprintf(stderr, "Usage: %s filename\n", argv[0]);
		exit(-1);
	}

	/* Try to lstat first argument. If cannot, quit */

	if(lstat(argv[1], &dostat) < 0)
	{
		perror(argv[1]);
		exit(-1);
	}

	/* Make sure it's a regular file. If not, quit! */

	if(!(dostat.st_mode & S_IFREG)||((dostat.st_mode & S_IFMT) == S_IFLNK))
	{
		(void)fprintf(stderr, "Error: %s is not a regular file\n", argv[1]);
		exit(-1);
	}

	/* Check permissions on file, must not be setuid or setgid */
	/* Then check to see if it's an executable */

	if(dostat.st_mode & (S_ISUID | S_ISGID))
	{
		(void)fprintf(stderr, "Error: Cannot compress setuid or setgid programs.\n");
		exit(-1);
	}

	if(! (dostat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
	{
		(void)fprintf(stderr, "File does have any execute bits set. Aborting.\n");
		exit(-1);
	}

#ifdef ULTRIX
	/* Test to see if file we are compressing is local or not */

	if((islocal = is_file_local(argv[1])) < 0)
	{
		perror("statfs");
		exit(-1);
	}
#endif

	/* Now open file we are compressing for reading. Quit if can't. */

	if((infd = open(argv[1], O_RDONLY)) < 0)
	{
		perror(argv[1]);
		exit(-1);
	}

	/* Check to make sure file is not already tcx'ed */

	if(is_tcx(infd))
	{
		(void)fprintf(stderr, "%s is already in tcx format!\n", argv[1]);
		exit(0);
	}

	if(lseek(infd, 0, SEEK_SET) < 0)
	{
		perror("lseek");
		exit(-1);
	}

	/* Open generation file, and try to mimic permissions */
	/* If cannot, warn user and quit */

	if(strrchr(argv[1], '/') == NULL)
		(void)sprintf(tofile, ".tcx.%s", argv[1]);
	else
	{
		(void)strcpy(tofile, argv[1]);
		s = strrchr(tofile, '/');
		*s = '\0';
		(void)strcat(tofile, "/.tcx.");
		s = strrchr(argv[1], '/');
		s++;
		(void)strcat(tofile, s);
	}

	lck.l_type = F_WRLCK; lck.l_whence = 0; lck.l_start = 0; lck.l_len = 0;

	perms = (dostat.st_mode & 0777);
	if(perms & S_IXUSR) perms |= S_IRUSR;
	if(perms & S_IXGRP) perms |= S_IRGRP;
	if(perms & S_IXOTH) perms |= S_IROTH;
	perms |= S_IWUSR;

	/* Attempt to create scratch file */
	/* Ultrix barfs on F_SETLK if file is on an NFS mount. */

	if((outfd = open(tofile, O_EXCL | O_CREAT | O_WRONLY, perms)) < 0)
	{
#ifdef ULTRIX
		if(islocal == 0 || errno != EEXIST)
#else
		if(errno != EEXIST)
#endif
		{
			perror(tofile);
			exit(-1);
		}

		/* Attempt to open and lock file that's there.  If we can't */
		/* lock the file, someone else must be packing it, so quit. */
		/* If we can, it must be bogus and left lying around after a*/
		/* crash, interrupt or something. Delete file and try again.*/

		if((outfd = open(tofile, O_WRONLY)) < 0)
		{
			perror(tofile);
			exit(-1);
		}

		if(fcntl(outfd, F_SETLK, &lck) < 0)
			exit(-1);

		(void)unlink(tofile);	/* Unlink. Don't care if fails yet */
		(void)close(outfd);
		if((outfd = open(tofile, O_EXCL | O_CREAT | O_WRONLY, perms)) < 0)
		{
			perror(tofile);
			exit(-1);
		}
	}

	/* Attempt to lock tofile.  If can't then assume someone else */
	/* is in the process of packing this file, so quit. */
	/* Only try to lock on ULTRIX if file is local.  If not, risk */
	/* the race condition, we have no choice!. */

#ifdef ULTRIX
	if(islocal == 1)
#endif
		if(fcntl(outfd, F_SETLK, &lck) < 0)
			exit(-1);

	/* Do a chmod (Just to be sure - in case of user umask affecting open) */

	if(chmod(tofile, perms) < 0)
	{
		(void)fprintf(stderr, "Cannot set proper permissions on scratch file %s\n", tofile);
		(void)close(infd);
		(void)close(outfd);
		if(unlink(tofile) < 0)
			(void)fprintf(stderr, "Warning: Unable to delete scratch file\n");
		exit(-1);
	}

	if(chown(tofile, dostat.st_uid, dostat.st_gid) < 0)
	{
		(void)fprintf(stderr, "Cannot set proper ownership on scratch file\n");
		(void)close(infd);
		(void)close(outfd);
		if(unlink(tofile) < 0)
			(void)fprintf(stderr, "Warning: Unable to delete scratch file\n");
		exit(-1);
	}

	/* Spit out header and start encoding executable */

	(void)sprintf(header, "#!%s\n", PATHUNTCX);
	if(write(outfd, header, strlen(header)) < 0) { (void)perror("write"); exit(-1); }

	c = 0;	if((write(outfd, &c, 1)) < 0) { (void)perror("write"); exit(-1); }
	c = 76;	if((write(outfd, &c, 1)) < 0) { (void)perror("write"); exit(-1); }
	c = 193; if((write(outfd, &c, 1)) < 0) { (void)perror("write"); exit(-1); }
	c = 13;	if((write(outfd, &c, 1)) < 0) { (void)perror("write"); exit(-1); }
	c = 138; if((write(outfd, &c, 1)) < 0) { (void)perror("write"); exit(-1); }

	if(doencode(infd, outfd) != 0)
	{
		(void)fprintf(stderr, "Compression failed\n");
		if(unlink(tofile) < 0)
			(void)fprintf(stderr, "Warning: Unable to delete scratch file\n");
		exit(-1);
	}

	(void)close(infd);

	if((infd = open(argv[1], O_WRONLY)) <= 0)
	{
		perror(argv[1]);
		if(unlink(tofile) < 0)
			(void)fprintf(stderr, "Warning: Unable to delete scratch file\n");
		exit(-1);
	}

#ifdef ULTRIX
	if(islocal == 1)
#endif
		if(fcntl(infd, F_SETLK, &lck) < 0)
		{
			if(unlink(tofile) < 0)
				(void)fprintf(stderr, "Warning: Unable to delete scratch file\n");
			exit(-1);
		}

	/* Rename() compressed version to original */

	if(rename(tofile, argv[1]) < 0)
	{
		perror(argv[1]);
		if(unlink(tofile) < 0)
			(void)fprintf(stderr, "Warning: Unable to delete scratch file\n");
		exit(-1);
	}

	/* Close files and hence locks */

	(void)close(infd);
	(void)close(outfd);

	/* All done! Bye, bye. */

	return(0);
}


int
is_tcx(int fd)
{
int     i;
unsigned char   c;

        for(i = 0; i < MAXHEADERSIZE; i++)
                if(read(fd, &c, 1) < 1 || c == 0)
                        break;
        if((i >= MAXHEADERSIZE) || read(fd, &c, 1) < 1  || c != 76 || read(fd, &c, 1) < 1  || c != 193
            || read(fd, &c, 1) < 1  || c != 13 || read(fd, &c, 1) < 1  || c != 138 )
                return 0;
        return 1;
} /* is_tcx */


int
doencode(int infd, int outfd)
{
int	pid;
union	wait	status;

	pid = fork();
	if(pid < 0) return -1;
	if(pid == 0)
	{
		if(dup2(infd, 0) < 0)	exit(-1);	/* Attach infd to stdin */
		(void)close(infd);
		if(dup2(outfd, 1) < 0)	exit(-1);	/* Attach outfd to stdout */
		(void)close(outfd);
#ifdef PACKEROPTS
		(void)execl(PATHPACKER, "packer", PACKEROPTS, (char *)0);
#else
		(void)execl(PATHPACKER, "packer", (char *)0);
#endif
		exit(-1);
	}
	else
		pid = wait(&status);
	return WEXITSTATUS(status);
} /* doencode */


#ifdef ULTRIX
int
is_file_local(char *path)
{
struct  fs_data fsbuf;

        if(statfs(path, &fsbuf) < 1)     /* Returns 0 on "NOT MOUNTED" */
                return -1;

        /* NFS Version 2 returns -1 or 0 for both gfree, and gtot */
        /* to a client, so return false on this condition. */

        if((fsbuf.fd_req.gfree <= 0) && (fsbuf.fd_req.gtot <= 0))
                return 0;
	return 1;
} /* is_file_local */
#endif
