allbery@ncoast.UUCP (06/22/87)
[I won't make a habit of posting my own sources here, but there've been people asking for this. ++bsa] The following program is designed to be used with the "alias shell" feature of many "add-on" C shells (I don't know if it's supported under BSD4.x; would someone please send me mail about it?) in order to force the issue of how a shell script should be run. BSD-based kernels are capable of "executing" scripts which begin with the magic incantation "#! /full/path/name [single-arg]" by running the program specified by /full/path/name with two arguments: the (optional) single-arg in the #! incantation and the name of the script. If the first line of the script is not in the proper format, the exec fails and csh attempts to execute the script by examining the first character. If the first character is # then the script is executed by csh, otherwise by sh. This can be a royal pain in the *ss. The bexec program uses the "alias shell" feature to intercept csh's interpre- tation of scripts. If the kernel exec fails, csh checks to see if there is an alias for "shell"; if so, it executes the program specified by that alias by prepending the alias's contents to the command line. The program named by the alias must be a full path name, and the usual history substitutions are not performed. The checking for # is only done is there is no alias for "shell". Bexec thus takes over when the kernel cannot execute a script. It supports three kinds of execution: (1) It handles #! if the kernel does not. Some of the semantics are different; in particular, it does not handle setuid. This is probably a win, as setuid shell scripts are a major security hole. (2) If the first line is not a valid #! line, the first block of the script is scanned. If it contains non-ASCII characters, bexec attempts to pass the script to RM/COBOL; this makes executing RM/COBOL programs a bit easier. (RM/COBOL options can be specified in the command line, due to the way "runcobol" parses options.) (3) If all else fails, sh is executed on the script. The upshot of all this, with respect to the basic problem of getting the right shell to run the right script, is that csh scripts will be forced to start with "#! /bin/csh", otherwise sh runs the script; this is in most cases the desired behavior. Bexec is designed to be small and fast, since it is wedged into the "execute" command sequence. It does not use stdio, and it does exactly one file read of one disk block. (The default block size is 1024.) Compilation: There is no makefile. To compile it, select some options: -DSH='"path"' The pathname of the shell to execute; default is /bin/sh. -DSYSBLK=num The size of a disk block; default is 1024. -DBSD Disables #! option, since BSD does it in the kernel. -DRMC='"path"' If defined, enables RM/COBOL program execution; its value is the pathname of the RM/COBOL "runcobol" executable. In general, this option makes no sense on BSD systems because RM/COBOL isn't available for them. Then compile it: "cc -O -o bexec bexec.c <options>", where <options> is zero or more of the -D options above. Installation: Copy bexec into a bin directory. It need not be on people's paths. Then type in to csh: "alias shell /path/to/bexec". The full pathname of bexec must be specified. Place this command in your .cshrc for permanent use. Testing: Try it on some shell scripts, including ones with # comments at the beginning. Then try it on some non-shell scripts, like the following: --- #! /usr/bin/awk -f BEGIN { print "AWK running"; exit } --- --- #! /bin/date --- If you have any RM/COBOL programs, "chmod +x" one of them and try running it as a shell command. (As written, bexec does a chdir to the directory contain- ing the RM/COBOL program. At the price of compatibility with RM/COBOL 1.x and some loss of speed (and increase in size), it could instead put the directory in the RUNPATH environment variable.) ---------------------------- NOT A SHAR FILE!!!!! ---------------------------- ---------------------------------- cut here ---------------------------------- /* * bexec: execute a non-binary file for csh, with #! recognition * (usage: alias shell /usr/local/bin/bexec) */ #ifndef SH #define SH "/bin/sh" #endif #ifndef SYSBLK #define SYSBLK 1024 /* the fastest disk read size */ #endif #ifdef BSD #define strrchr rindex #else char *calloc(), *basename(), *strrchr(); #endif #ifdef RMC char *dirname(), *strrchr(); #endif main(argc, argv, envp) char **argv, **envp; { register int cmd, oc; char kcmd[SYSBLK]; register char *cp, *ap; register char **newargv; if ((cmd = open(argv[1], 0)) < 0) _exit(1); oc = read(cmd, kcmd, sizeof kcmd); close(cmd); if (kcmd[0] != '#' || kcmd[1] != '!') { #ifdef RMC for (cp = kcmd; cp < &kcmd[oc]; cp++) if (*cp == 0 || *cp > '\177') { if (chdir(dirname(argv[1])) < 0) { extern int errno; write(1, dirname(argv[1]), strlen(dirname(argv[1]))); _exit(errno); } argv[0] = "runcobol"; execve(RMC, argv, envp); _exit(-115); } #endif execve(SH, argv, envp); _exit(100); } #ifndef BSD for (cp = kcmd + 2; *cp == ' '; cp++) ; for (ap = cp; *ap != ' ' && *ap != '\n'; ap++) ; if (*ap == ' ') *ap++ = '\0'; for (cmd = 0; ap[cmd] != '\n'; cmd++) ; ap[cmd] = '\0'; newargv = (char **) calloc((unsigned) argc + 2, sizeof (char *)); oc = 0; newargv[oc++] = basename(cp); argv++; if (*ap != '\0') newargv[oc++] = ap; for (; *argv != 0; argv++) newargv[oc++] = *argv; newargv[oc] = 0; execve(cp, newargv, envp); #endif _exit(100); } #ifndef BSD char *basename(path) char *path; { register char *cp; if ((cp = strrchr(path, '/')) == (char *) 0) return path; return cp + 1; } #endif #ifdef RMC char *dirname(path) char *path; { static char dir[1024]; register char *cp; strcpy(dir, path); if ((cp = strrchr(dir, '/')) == (char *) 0) return "."; *cp = '\0'; if (dir[0] == '\0') return "/"; return dir; } #endif