/*
 * install-bin.c
 *
 * A faster, portable install-sh replacement.
 * (c) 2004 Alvaro Herrera, <alvherre@dcc.uchile.cl>
 *
 * This code is released under the terms of the PostgreSQL License.
 */
#include "postgres_fe.h"
#include "getopt_long.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>


#define _(x) gettext((x))

static void usage(void);
static int parseMode(const char *mode);
static void installFile(const char *sourceFile, const char *targetFile, int mode);
static void copyFile(const char *source, const char *targetdir, const char *targetfile);
static char *getDirName(const char *path);
static char *getBaseName(const char *path);
void cleanup_at_exit(void);

const char *progname;
char *unlink_at_exit;

int
main(int argc, char *argv[])
{
	static struct option longopts[] = {
		{"strip", optional_argument, NULL, 's'},
		{"mode", required_argument, NULL, 'm'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	};

	int			optindex;
	int			c;
	int			strip = false;
	char	   *stripprog = NULL;
	char	   *mode = NULL;
	int			chmod;

	int i;

	atexit(cleanup_at_exit);
	progname = get_progname(argv[0]);
	
	while ((c = getopt_long(argc, argv, "s::m:h", longopts, &optindex)) != -1)
	{
		switch (c)
		{
			case 'm':
				mode = optarg;
				break;
			case 's':
				strip = true;
				printf("optarg es %s\n", optarg);
				if (optarg != NULL)
					stripprog = optarg;
				else if (getenv("STRIPPROG"))
					stripprog = getenv("STRIPPROG");
				else
					stripprog = "strip";
				break;
			case 'h':
				usage();
				exit(0);
			default:
				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
				exit(1);
		}
	}

	chmod = parseMode(mode);

	/* 
	 * If we have more than two arguments remaining, the last one
	 * better be a directory.
	 *
	 * FIXME -- this assumes that getopt() reordered its arguments,
	 * as documented in the glibc manpage.
	 * Not sure how portable that assumption is; probably not a lot.
	 */
	if (argc - optind >= 3)
	{
		struct stat statbuf;

		stat(argv[argc - 1], &statbuf);

		if (!S_ISDIR(statbuf.st_mode))
		{
			fprintf(stderr, _("%s: \"%s\" is not a directory\n"),
					progname, argv[argc - 1]);
			exit(1);
		}
	}

	/*
	 * Ok, do it.
	 *
	 * FIXME -- this assumes that getopt() reordered its arguments,
	 * as documented in the glibc manpage.
	 * Not sure how portable that assumption is; probably not a lot.
	 */
	for (i = optind; i < argc - 1; i++)
		installFile(argv[i], argv[argc - 1], chmod);
	exit(0);
}

/*
 * Installs a file.
 *
 * If the target is a directory, then the target file will be a file
 * named identically as the source file, in the target directory.
 */
static void
installFile(const char *sourceFile, const char *target, int mode)
{
	struct stat		statbuf;
	char		   *targetFile,
				   *targetDir,
				   *fullTarget;
	
	if (stat(target, &statbuf) == 0 && S_ISDIR(statbuf.st_mode))
	{
		targetDir = strdup(target);
		if (targetDir == NULL)
		{
			fprintf(stderr, _("%s: out of memory\n"), progname);
			exit(1);
		}
		targetFile = getBaseName(sourceFile);
	}
	else
	{
		targetDir = getDirName(target);
		targetFile = getBaseName(target);
	}

	/* 
	 * If source doesn't exist or is not a regular file, error out
	 */
	if (stat(sourceFile, &statbuf) != 0 || !S_ISREG(statbuf.st_mode))
	{
		fprintf(stderr, _("%s: \"%s\" is not a regular file\n"),
				progname, sourceFile);
		exit(1);
	}

	copyFile(sourceFile, targetDir, targetFile);

	fullTarget = (char *) malloc(strlen(targetFile) + 1 + strlen(targetDir) + 1);
	if (fullTarget == NULL)
	{
		fprintf(stderr, _("%s: out of memory\n"), progname);
		exit(1);
	}
	sprintf(fullTarget, "%s/%s", targetDir, targetFile);

	free(targetFile);
	free(targetDir);

	if (chmod(fullTarget, mode) != 0)
	{
		fprintf(stderr, _("%s: could not chmod file \"%s\": %s\n"),
				progname, fullTarget, strerror(errno));
		exit(1);
	}
	free(fullTarget);
}

/*
 * Copy a file somewhere else.
 */
static void
copyFile(const char *source, const char *targetdir, const char *targetfile)
{
	char   *tmptarget;
	char   *fullTarget;
	int		sourcefd,
			targetfd;
	int		nread,
			nwritten;
	void   *buf;
	
#define BUFSIZE 8192
	buf = malloc(BUFSIZE);
	if (buf == NULL)
	{
		fprintf(stderr, _("%s: out of memory\n"), progname);
		exit(1);
	}

	tmptarget = (char *) malloc(strlen(targetdir) + 1 + 18 + 1);
	if (tmptarget == NULL)
	{
		fprintf(stderr, _("%s: out of memory\n"), progname);
		exit(1);
	}
	sprintf(tmptarget, "%s/install-tmp.XXXXXX", targetdir);

	if ((targetfd = mkstemp(tmptarget)) == -1)
	{
		fprintf(stderr, _("%s: could not create temp file \"%s\": %s\n"),
				progname, tmptarget, strerror(errno));
		exit(1);
	}
	unlink_at_exit = tmptarget;

	if ((sourcefd = open(source, 0)) == -1)
	{
		fprintf(stderr, _("%s: could not open source file \"%s\": %s\n"),
				progname, source, strerror(errno));
		exit(1);
	}

	while ((nread = read(sourcefd, buf, BUFSIZE)) > 0)
	{
		nwritten = write(targetfd, buf, nread);
		if (nwritten != nread)
		{
			fprintf(stderr, _("%s: could not write %d bytes to \"%s\": %s\n"),
					progname, nread, tmptarget, strerror(errno));
			exit(1);
		}
	}
	if (close(sourcefd) != 0)
	{
		fprintf(stderr, _("%s: could not close source file \"%s\": %s\n"),
				progname, source, strerror(errno));
		exit(1);
	}
	if (close(targetfd) != 0)
	{
		fprintf(stderr, _("%s: could not close target file \"%s\": %s\n"),
				progname, tmptarget, strerror(errno));
		exit(1);
	}

	fullTarget = (char *) malloc(strlen(targetdir) + 1 + strlen(targetfile) + 1);
	if (fullTarget == NULL)
	{
		fprintf(stderr, _("%s: out of memory\n"), progname);
		exit(1);
	}
	sprintf(fullTarget, "%s/%s", targetdir, targetfile);

	/* Ignore ENOENT errors */
	if (unlink(fullTarget) != 0)
	{
		if (errno != ENOENT)
		{
			fprintf(stderr, _("%s: could not unlink target file \"%s\": %s"),
					progname, fullTarget, strerror(errno));
			exit(1);
		}
	}
	if (link(tmptarget, fullTarget) != 0)
	{
		fprintf(stderr, _("%s: could not create target file \"%s\" from \"%s\": %s\n"),
				progname, fullTarget, tmptarget, strerror(errno));
		exit(1);

	}
	if (unlink(tmptarget) != 0)
	{
		fprintf(stderr, _("%s: could not unlink temporary file \"%s\": %s\n"),
				progname, tmptarget, strerror(errno));
		exit(1);
	}
	unlink_at_exit = NULL;
	
	free(buf);
}

static void
usage(void)
{
	printf(_("%s installs files into the filesystem.\n\n"), progname);
	printf(_("Usage:\n"
			 "  %s [OPTION] [SOURCE] ... [DIR]\n"
			 "  %s [OPTION] [SOURCE] [TARGET]\n"
			 "Options:\n"
			 "  --strip [STRIPPROG]    strip installed files, using STRIPPROG,\n"
			 "                         or the program in the STRIPPROG environment variable,\n"
			 "                         or /usr/bin/strip\n"
			 "  --mode MODE            chmod files to MODE (defaults to 755)\n"),
			progname, progname);
	printf(_("\nIf multiple files are specified, the last argument\n"
			 "must be a directory where the files will be copied to.\n"));
	exit(0);
}

static int
parseMode(const char *mode)
{
	int	nummode;
	int retval = 0;

	/* Defaults to 755 */
	if (mode == NULL)
		return S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;

	if (strlen(mode) != 3)
	{
		fprintf(stderr, _("%s: invalid mode %s\n"), progname, mode);
		exit(1);
	}
	nummode = atoi(mode);

	if ((nummode / 100) & (1 << 2))
		retval |= S_IRUSR;
	if ((nummode / 100) & (1 << 1))
		retval |= S_IWUSR;
	if ((nummode / 100) & (1 << 0))
		retval |= S_IXUSR;

	nummode %= 100;

	if ((nummode / 10) & (1 << 2))
		retval |= S_IRGRP;
	if ((nummode / 10) & (1 << 1))
		retval |= S_IWGRP;
	if ((nummode / 10) & (1 << 0))
		retval |= S_IXGRP;

	nummode %= 10;

	if (nummode & 1 << 2)
		retval |= S_IROTH;
	if (nummode & 1 << 1)
		retval |= S_IWOTH;
	if (nummode & 1 << 0)
		retval |= S_IXOTH;

	return retval;
}

/*
 * Returns the "dir" component of path, that is, everything up to
 * the last slash character.  If there is no slash character,
 * returns "." (current directory).
 */
static char *
getDirName(const char *path)
{
	char *slash,
		 *dirname,
		 *retval;

	dirname = strdup(path);
	if (dirname == NULL)
	{
		fprintf(stderr, _("%s: out of memory\n"), progname);
		exit(1);
	}
	slash = strrchr(dirname, '/');
	if (slash == NULL)
	{
		retval = strdup(".");
	}
	else
	{
		slash[0] = '\0';
		retval = strdup(dirname);
	}
	if (retval == NULL)
	{
		fprintf(stderr, _("%s: out of memory\n"), progname);
		exit(1);
	}
	free(dirname);
	return retval;
}

/*
 * Returns the "basename" of the argument, that is, everything
 * from the last slash character.  If there is no slash in the
 * argument, returns the whole path.
 */
static char *
getBaseName(const char *path)
{
	char *slash,
		 *filename;

	slash = strrchr(path, '/');
	if (slash == NULL)
		filename = strdup(path);
	else
		filename = strdup(slash + 1);
	if (filename == NULL)
	{
		fprintf(stderr, _("%s: out of memory\n"), progname);
		exit(1);
	}

	return filename;
}

void
cleanup_at_exit(void)
{
	if (unlink_at_exit != NULL)
		unlink(unlink_at_exit);
}
