Patchers,

I wrote an "install" program in C.  It's supposed to replace the
config/install-sh script, limited to the functionality we need, i.e.
what is in Makefiles in the Pg main source tree.  The main objective of
this exercise is to reduce "make install" execution time; a part of that
is being able to install multiple files with one command.

Portability testing right now is limited to my machine, which is Linux
with glibc 2.3.2 (Debian Sid).

With this in place, "make install" in src/include takes 17 seconds on
this machine, where the script version takes more than a minute.  I
think this is a useful improvement.


Right now I'm missing a Makefile rule for it.  It needs the pg_progname
symbol from src/port, and the includes from $(srcdir)/src/include and
$(builddir)/src/include.  From the config directory, this works:

$(CC) $(CFLAGS) -I$(top_srcdir)/src/include -I$(top_builddir)/src/include 
../src/port/path.o ../src/port/exec.o $< -o $@

Also, I don't know how to force it to build before executing "install",
short of putting it as a dependency in every makefile (which sounds
silly to me).

I attach the source code, and a src/include/Makefile modification to
use the multi-file install feature.

-- 
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"Hay quien adquiere la mala costumbre de ser infeliz" (M. A. Evans)
/*
 * install-bin.c
 *
 * A faster, portable install-sh replacement.
 * (c) 2004 Alvaro Herrera, <[EMAIL PROTECTED]>
 *
 * 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);
}
Index: src/include/Makefile
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/include/Makefile,v
retrieving revision 1.17
diff -c -r1.17 Makefile
*** src/include/Makefile        9 Nov 2004 06:23:50 -0000       1.17
--- src/include/Makefile        9 Nov 2004 15:51:51 -0000
***************
*** 36,48 ****
  # These headers are needed for server-side development
        $(INSTALL_DATA) pg_config.h    $(DESTDIR)$(includedir_server)
        $(INSTALL_DATA) pg_config_os.h $(DESTDIR)$(includedir_server)
!       for file in $(srcdir)/*.h; do \
!         $(INSTALL_DATA) $$file $(DESTDIR)$(includedir_server)/`basename 
$$file` || exit; \
!       done
        for dir in $(SUBDIRS); do \
!         for file in $(srcdir)/$$dir/*.h; do \
!           $(INSTALL_DATA) $$file 
$(DESTDIR)$(includedir_server)/$$dir/`basename $$file` || exit; \
!         done \
        done
  
  installdirs:
--- 36,44 ----
  # These headers are needed for server-side development
        $(INSTALL_DATA) pg_config.h    $(DESTDIR)$(includedir_server)
        $(INSTALL_DATA) pg_config_os.h $(DESTDIR)$(includedir_server)
!       $(INSTALL_DATA) $(wildcard $(srcdir)/*.h) $(DESTDIR)$(includedir_server)
        for dir in $(SUBDIRS); do \
!         $(INSTALL_DATA) $(srcdir)/$$dir/*.h 
$(DESTDIR)$(includedir_server)/$$dir; \
        done
  
  installdirs:
---------------------------(end of broadcast)---------------------------
TIP 4: Don't 'kill -9' the postmaster

Reply via email to