From pa.dec.com!decwrl!uunet!sparky!kent Thu Jul 25 08:59:02 PDT 1991
Article: 2539 of comp.sources.misc
Newsgroups: comp.sources.misc
Path: pa.dec.com!decwrl!uunet!sparky!kent
From: David J. MacKenzie <djm@eng.umd.edu>
Subject:  v21i039:  libiexec - `#!' support for non-BSD systems', Part01/01
Message-ID: <1991Jul25.021433.28514@sparky.IMD.Sterling.COM>
X-Md4-Signature: c693974ce2dc10d79909903c83724b03
Sender: kent@sparky.IMD.Sterling.COM (Kent Landfield)
Organization: Sterling Software, IMD
Date: Thu, 25 Jul 1991 02:14:33 GMT
Approved: kent@sparky.imd.sterling.com
Lines: 588

Submitted-by: David J. MacKenzie <djm@eng.umd.edu>
Posting-number: Volume 21, Issue 39
Archive-name: libiexec/part01
Environment: UNIX

BSD-derived Unixes have a very useful kernel hack that most System V
(R<4) Unixes don't, which makes it easy to run scripts for various
interpreters such as perl, ksh, csh, and awk.

In comp.unix.wizards, john@sco.COM (John R. MacMillan) argued recently
that there is no good reason why the "#!" interpretation must be done
in the kernel; it can easily be done in the exec library functions
instead.

So here is an attempt to retrofit "#!" support onto systems that don't
have it.  It is a library of replacement functions for execl, execvp,
et al. that have an "i" (for "interpreter support") prepended to the
names.  This means that you don't have to change your system libraries
in order to use them.  

David
----
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  README Makefile iexec.c ietest.c cshscript
# Wrapped by djm@bleen on Thu Jul 18 19:59:29 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(3404 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
Xlibiexec
X
XBSD-derived Unixes have a very useful kernel hack that most System V
X(R<4) Unixes don't, which makes it easy to run scripts for various
Xinterpreters such as perl, ksh, csh, and awk.
X
XIf an exec-family function finds that a file it has been told to run
Xdoes not have a valid magic number for a binary, it tries to run the
Xfile as a script.  To do this, it checks to see whether the file
Xstarts with the characters "#!".  If it does, the kernel takes the
Xword following the "#!" to be the pathname of the program to run
Xinstead, with the path of the script file given as an argument, and if
Xthere is another word in the "#!" line following the program pathname,
Xthat is also passed to the program as an argument, before the script
Xname.  As a special case, if the file is neither a binary nor a script
Xstarting with "#!", execlp and execvp try to run it as a /bin/sh script.
X
XIn comp.unix.wizards, john@sco.COM (John R. MacMillan) argued recently
Xthat there is no good reason why the "#!" interpretation must be done
Xin the kernel; it can easily be done in the exec library functions
Xinstead.
X
XSo here is an attempt to retrofit "#!" support onto systems that don't
Xhave it.  It is a library of replacement functions for execl, execvp,
Xet al. that have an "i" (for "interpreter support") prepended to the
Xnames.  This means that you don't have to change your system libraries
Xin order to use them.  To compile programs using them, add these to
Xthe flags you pass cc:
X
X-Dexecl=iexecl -Dexecle=iexecle -Dexeclp=iexeclp -Dexecv=iexecv
X-Dexecve=iexecve -Dexecvp=iexecvp
X
XOr, just pass the ones redefining the functions the program you are
Xcompiling actually uses.
X
XThen add libiexec.a to the list of files passed to the linking phase.
X
XA limitation of this implementation is that you can't add "#!" support
Xto programs you don't have the source code to, probably including sh
Xand csh (unless you have some kind of shared libraries, perhaps).  A
Xsolution for the shells is to get free shells such as bash and tcsh
X(version 6.00 or later) and install them as /bin/sh or /bin/csh
Xinstead.  bash itself supports "#!" on systems where the exec
Xfunctions don't, except that versions 1.08 and previous don't do it
Xfor programs you run with the "exec" command.  Because bash uses
Xexecve, "exec scriptname" will fail if the script does not start with
X"#!interpreter-path" even if you link it with this library (and even
Xif you run bash on a BSD system).  If you want to fix that problem, at
Xthe loss of some BSD compatibility in the functions, change the ", 0"
Xto ", 1" in iexecve in iexec.c.  bash doesn't have the getopts builtin
Xor some of ksh's esoteric features, but as of release 1.08 it's in
Xreasonably good shape otherwise.
X
XIf you want to make linking with these functions transparent, the
Xfollowing procedure will probably work, though I have not tried it.
XUsing some program that can edit binary files, such as GNU emacs,
Xchange every "execv" or "execl" in your libc.a to "oxecv" or "oxecl"
X("o" for "old").  Change iexec.c so that script_execve, iexecve and
Xiexecvp call oxecve instead of execve; then rename the functions
Xdefined in iexec.c so that they do not have the initial "i" in their
Xnames.  Compile iexec.c and use ar to add iexec.o to libc.a.
X
Xietest.c is a test program that is linked with libiexec.a and runs
Xcshscript to make sure the library works.
X
XDavid MacKenzie <djm@eng.umd.edu>
END_OF_FILE
if test 3404 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(505 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X# Makefile for interpreter exec library.
X# This file is in the public domain.
X
XSHELL = /bin/sh
XCC = gcc -O
XCFLAGS = -g # -DSTDLIB_MISSING -DLIMITS_MISSING -DUNISTD_MISSING
XLDFLAGS = -g
X
XSHAR_FILES = README Makefile iexec.c ietest.c cshscript
X
Xall: libiexec.a
X
Xlibiexec.a: iexec.o
X	ar rc $@ iexec.o
X	-ranlib $@
X
Xietest: ietest.c libiexec.a
X	$(CC) -o $@ $(CFLAGS) $(LDFLAGS) -Dexeclp=iexeclp ietest.c libiexec.a
X
Xshar.out: $(SHAR_FILES)
X	shar $(SHAR_FILES) > shar.out
X
Xclean:
X	rm -f *.[ao] ietest tags TAGS
END_OF_FILE
if test 505 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'iexec.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'iexec.c'\"
else
echo shar: Extracting \"'iexec.c'\" \(7862 characters\)
sed "s/^X//" >'iexec.c' <<'END_OF_FILE'
X/* iexec.c -- script interpreter wrappers for exec functions
X
X   These exec-family functions try to run a command interpreter
X   on scripts, like BSD-based kernels do.  Thus, if
X   dupi is in /usr/local/bin, /usr/local/bin is in the PATH,
X   and /usr/local/bin/dupi starts with "#!/bin/sh -x", then
Xiexecvp ("dupi", ["dupi", "/usr/lib", NULL]);
X   is equivalent to
Xexecve ("/bin/sh", ["/bin/sh", "-x", "/usr/local/bin/dupi", "/usr/lib", NULL], environ);
X
X  Written by David MacKenzie <djm@eng.umd.edu>
X  Version 1.6
X  This file is in the public domain. */
X
X#include <sys/types.h>
X#include <fcntl.h>
X#include <errno.h>
Xextern int errno;
X#include <string.h>
X#ifndef UNISTD_MISSING
X#include <unistd.h>
X#endif
X#ifndef X_OK
X#define X_OK 1
X#endif
X
X#ifndef LIMITS_MISSING
X#include <limits.h>
X#else
X#include <sys/param.h>
X#endif
X
X#ifndef STDLIB_MISSING
X#include <stdlib.h>
X#else
Xchar *getenv ();
Xchar *malloc ();
X#endif
X
X#ifdef __STDC__
X#include <stdarg.h>
X#else
X#include <varargs.h>
X#endif
X
Xextern char **environ;
X
X/* Longest pathname allowed by the system. */
X#ifndef PATH_MAX
X#ifdef _PC_PATH_MAX
X#define PATH_MAX pathconf ("/", _PC_PATH_MAX)
X#else
X#ifdef MAXPATHLEN
X#define PATH_MAX MAXPATHLEN
X#else
X#define PATH_MAX 1024
X#endif
X#endif
X#endif
X
X#ifndef ARG_MAX
X#ifdef _SC_ARG_MAX
X#define ARG_MAX sysconf (_SC_ARG_MAX)
X#else
X#ifdef NCARGS
X#define ARG_MAX NCARGS
X#else
X#define ARG_MAX 5120
X#endif
X#endif
X#endif
X
X/* Maximum number of chars to read from the first line of a script. */
X#define MAXLINELEN 256
X
X/* Search path to use if PATH is not set in environment. */
X#define DEFAULT_PATH ":/bin:/usr/bin"
X
X/* Make sure we get the real execve from the C library. */
X#ifdef execve
X#undef execve
X#endif
X
X/* Return a static string containing the full pathname of PROGRAM,
X   the first place it is found in the PATH environment variable.
X   Return 0 if it is not found. */
X
Xstatic char *
Xfull_pathname (program)
X     char *program;
X{
X  char *path;
X  char *start, *end;
X  static char *trycommand = 0;
X
X  if (trycommand == 0)
X    {
X      trycommand = (char *) malloc (PATH_MAX);
X      if (trycommand == 0)
X	return 0;
X    }
X  /* If an absolute pathname, no need to search the PATH. */
X  if (*program == '/'
X      || !strncmp (program, "./", 2) || !strncmp (program, "../", 3))
X    {
X      strcpy (trycommand, program);
X      return trycommand;
X    }
X
X  path = getenv ("PATH");
X  if (path == 0)
X    path = DEFAULT_PATH;
X  for (start = end = path; *end; start = end + 1)
X    {
X      register char *s, *d;
X
X      end = strchr (start, ':');
X      if (end == 0)
X	end = strchr (start, '\0');
X      d = trycommand;
X      if (end == start)
X	*d++ = '.';
X      else
X	{
X	  s = start;
X	  while ((*d++ = *s++) != *end)
X	    /* Do nothing. */ ;
X	  --d;
X	}
X      *d++ = '/';
X      strcpy (d, program);
X      if (access (trycommand, X_OK) == 0)
X	return trycommand;
X    }
X  return 0;
X}
X
X/* If FILE is a script, set `command' to the name of the program
X   that should run it.  If FILE specifies an argument for the program,
X   put it as a static value in `cmdarg'; otherwise set `cmdarg' to 0.
X   If SH_DEFAULT is nonzero, try to run files that do not start with "#!"
X   as /bin/sh scripts.
X   Return 1 if successful, 0 if not. */
X
Xstatic char *command, *cmdarg;
X
Xstatic int
Xread_interpreter (file, sh_default)
X     char *file;
X     int sh_default;
X{
X  static char line[MAXLINELEN + 1];
X  register char *cp;
X  int fd, st;
X
X  fd = open (file, O_RDONLY, 0);
X  if (fd == -1)
X    return 0;
X  st = read (fd, line, MAXLINELEN);
X  if (close (fd) == -1)
X    return 0;
X  if (st == -1)
X    return 0;
X  if (st < 5 || line[0] != '#' || line[1] != '!')
X    {
X      if (sh_default)
X	{
X	  strcpy (line, "/bin/sh");
X	  command = line;
X	  cmdarg = 0;
X	  return 1;
X	}
X      else
X	return 0;
X    }
X
X  line[MAXLINELEN] = '\0';
X  cp = strchr (line, '\n');
X  if (cp == 0)
X    return 0;
X  *cp = '\0';
X
X  /* Find start of program name. */
X  for (cp = line + 2; *cp == ' ' || *cp == '\t'; ++cp)
X    /* Do nothing. */ ;
X  command = cp;
X  /* Find end of program name. */
X  for (; *cp != '\0' && *cp != ' ' && *cp != '\t'; ++cp)
X    /* Do nothing. */ ;
X
X  /* Find start of argument. */
X  cmdarg = 0;
X  if (*cp != '\0')
X    {
X      *cp++ = '\0';
X      for (; *cp == ' ' || *cp == '\t'; ++cp)
X	/* Do nothing. */ ;
X      if (*cp != '\0')
X	{
X	  cmdarg = cp;
X	  /* Find end of argument.  In BSD the argument goes to the end of
X	     the line, but stopping at the first whitespace is more useful. */
X	  for (; *cp != '\0' && *cp != ' ' && *cp != '\t'; ++cp)
X	    /* Do nothing. */ ;
X	  *cp = '\0';
X	}
X    }
X
X  return 1;
X}
X
X/* From null-terminated argument list ARGV, create a new argument list
X   containing `command' and, if non-0, `cmdarg', and SCRIPT_PATH.
X   Then try to run `command' with the new argument list and ENVP.
X   If it fails, return -1. */
X
Xstatic int
Xscript_execve (script_path, argv, envp)
X     char *script_path;
X     char **argv;
X     char **envp;
X{
X  register char **new_argv;
X  register char **ap;
X  register int argc = 0;
X
X  new_argv = (char **) malloc ((sizeof (char *) * (ARG_MAX + 4)));
X  if (new_argv == 0)
X    return -1;
X
X  new_argv[argc++] = command;
X  if (cmdarg)
X    new_argv[argc++] = cmdarg;
X  new_argv[argc++] = script_path;
X  /* Copy the rest of the arguments. */
X  for (ap = argv + 1; *ap; ++ap)
X    new_argv[argc++] = *ap;
X  new_argv[argc++] = 0;
X
X  execve (command, new_argv, envp);
X  free (new_argv);
X  return -1;
X}
X
X/* VARARGS */
Xint
X#ifdef __STDC__
Xiexecl (char *path, ...)
X#else
Xiexecl (path, va_alist)
X     char *path;
X     va_dcl
X#endif
X{
X  va_list args;
X  register char **argv;
X  register int argc = 0;
X
X  argv = (char **) malloc ((sizeof (char *) * (ARG_MAX + 1)));
X  if (argv == 0)
X    {
X      errno = ENOMEM;
X      return -1;
X    }
X#ifdef __STDC__
X  va_start (args, path);
X#else
X  va_start (args);
X#endif
X  while (argv[argc++] = va_arg (args, char *))
X    /* Do nothing. */ ;
X  va_end (args);
X  iexecve (path, argv, environ);
X  free (argv);
X  return -1;
X}
X
X/* VARARGS */
Xint
X#ifdef __STDC__
Xiexecle (char *path, ...)
X#else
Xiexecle (path, va_alist)
X     char *path;
X     va_dcl
X#endif
X{
X  va_list args;
X  register char **argv;
X  register int argc = 0;
X  char **envp;
X
X  argv = (char **) malloc ((sizeof (char *) * (ARG_MAX + 1)));
X  if (argv == 0)
X    {
X      errno = ENOMEM;
X      return -1;
X    }
X#ifdef __STDC__
X  va_start (args, path);
X#else
X  va_start (args);
X#endif
X  while (argv[argc++] = va_arg (args, char *))
X    /* Do nothing. */ ;
X  envp = va_arg (args, char **);
X  va_end (args);
X  iexecve (path, argv, envp);
X  free (argv);
X  return -1;
X}
X
X/* VARARGS */
Xint
X#ifdef __STDC__
Xiexeclp (char *path, ...)
X#else
Xiexeclp (path, va_alist)
X     char *path;
X     va_dcl
X#endif
X{
X  va_list args;
X  register char **argv;
X  register int argc = 0;
X
X  argv = (char **) malloc ((sizeof (char *) * (ARG_MAX + 1)));
X  if (argv == 0)
X    {
X      errno = ENOMEM;
X      return -1;
X    }
X#ifdef __STDC__
X  va_start (args, path);
X#else
X  va_start (args);
X#endif
X  while (argv[argc++] = va_arg (args, char *))
X    /* Do nothing. */ ;
X  va_end (args);
X  iexecvp (path, argv);
X  free (argv);
X  return -1;
X}
X
Xint
Xiexecv (path, argv)
X     char *path;
X     char **argv;
X{
X  return iexecve (path, argv, environ);
X}
X
Xint
Xiexecve (path, argv, envp)
X     char *path;
X     char **argv;
X     char **envp;
X{
X  if (execve (path, argv, envp) == -1 && errno == ENOEXEC)
X    {
X      if (read_interpreter (path, 0))
X	script_execve (path, argv, envp);
X      errno = ENOEXEC;
X    }
X  return -1;
X}
X
Xint
Xiexecvp (path, argv)
X     char *path;
X     char **argv;
X{
X  /* Don't use execvp; it tries to run scripts in its own way. */
X  path = full_pathname (path);
X  if (path == 0)
X    {
X      errno = ENOENT;
X      return -1;
X    }
X  if (execve (path, argv, environ) == -1 && errno == ENOEXEC)
X    {
X      if (read_interpreter (path, 1))
X	script_execve (path, argv, environ);
X      errno = ENOEXEC;
X    }
X  return -1;
X}
END_OF_FILE
if test 7862 -ne `wc -c <'iexec.c'`; then
    echo shar: \"'iexec.c'\" unpacked with wrong size!
fi
# end of 'iexec.c'
fi
if test -f 'ietest.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'ietest.c'\"
else
echo shar: Extracting \"'ietest.c'\" \(253 characters\)
sed "s/^X//" >'ietest.c' <<'END_OF_FILE'
X#include <stdio.h>
Xvoid
Xmain (argc, argv)
X     int argc;
X     char **argv;
X{
X  extern int errno;
X  execlp ("cshscript", "cshscript", "/etc/motd", "/usr/lib/more.help", (char *) 0);
X  printf ("%s: exec failed, errno=%d\n", argv[0], errno);
X  exit (1);
X}
END_OF_FILE
if test 253 -ne `wc -c <'ietest.c'`; then
    echo shar: \"'ietest.c'\" unpacked with wrong size!
fi
# end of 'ietest.c'
fi
if test -f 'cshscript' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cshscript'\"
else
echo shar: Extracting \"'cshscript'\" \(109 characters\)
sed "s/^X//" >'cshscript' <<'END_OF_FILE'
X#!  /bin/csh -f
Xif ($?prompt) then
Xecho prompt is set
Xelse
Xecho prompt is not set
Xendif
Xecho args are: $argv
END_OF_FILE
if test 109 -ne `wc -c <'cshscript'`; then
    echo shar: \"'cshscript'\" unpacked with wrong size!
fi
chmod +x 'cshscript'
# end of 'cshscript'
fi
echo shar: End of shell archive.
exit 0

exit 0 # Just in case...


