Interpreter Files
All contemporary UNIX systems support interpreter files. These files are text files that begin with a line of the form
#! pathname [ optional-argument ]
The space between the exclamation point and the pathname is optional. The most common of these interpreter files begin with the line
#!/bin/sh
The pathname is normally an absolute pathname,since no special operations are performed on it (i.e.,PATH is not used).
Be aware that systems place a size limit on the first line of an interpreter file. This limit includes the #!,the pathname,the optional argument,the terminating newline,and any spaces.
On FreeBSD 8.0,this limit is 4,097 bytes. On Linux 3.2.0,the limit is 128 bytes. Mac OS X 10.6.8 supports a limit of 513 bytes,whereas Solaris 10 places the limit at 1,024 bytes.
#include "apue.h"
#include <sys/wait.h>
int main(int argc,char *argv[])
{
pid_t pid;
if((pid=fork())<0)
err_sys("fork error");
else if(pid==0)
{
if(execl("/home/sar/bin/testinterp","testinterp","myarg1","MY ARG2",(char *)0)<0)
err_sys("execl error");
}
if(waitpid(pid,NULL,0)<0)
err_sys("waitpid error");
return 0;
}
A program that execs an interpreter file
A common use for the optional argument following the interpreter pathname is to specify the -f option for programs that support this option. For example,an awk(1) program can be executed as
awk -f myfile
which tells awk to read the awk program from the file myfile.
Systems derived from UNIX System V often include two versions of the awk language. On these systems,awk is often called ‘‘old awk’’ and corresponds to the original version distributed with Version 7. In contrast,nawk (new awk) contains numerous enhancements and corresponds to the language.
This newer version provides access to the command-line arguments,which we need for the example that follows. Solaris 10 provides both versions. The awk program is one of the utilities included by POSIX in its 1003.2 standard,which is now part of the base POSIX.1 specification in the Single UNIX Specification.
The version of awk in Mac OS X 10.6.8 is based on the Bell Laboratories version,which has been placed in the public domain. FreeBSD 8.0 and some Linux distributions ship with GNU awk,called gawk,which is linked to the name awk. gawk conforms to the POSIX standard,but also includes other extensions.
Using the -f option with an interpreter file lets us write
#!/bin/awk -f
(awk program follows in the interpreter file)
#!/usr/bin/awk -f
# Note: on Solaris,use nawk instead
BEGIN {
for (i = 0; i < ARGC; i++)
printf "ARGV[%d] = %s\n",i,ARGV[i]
exit
}
An awk program as an interpreter file
When /bin/awk is executed,its command-line arguments are
/bin/awk -f /usr/local/bin/awkexample file1 FILENAME2 f3
The pathname of the interpreter file (/usr/local/bin/awkexample) is passed to the interpreter.
Interpreter files are useful for the following reasons.
1.They hide that certain programs are scripts in some other language. For example,to execute the program in Figure 8.21,we just say
awkexample optional-arguments
instead of needing to know that the program is really an awk script that we would otherwise have to execute as
awk -f awkexample optional-arguments
2.Interpreter scripts provide an efficiency gain. Consider the prevIoUs example again. We could still hide that the program is an awk script,by wrapping it in a shell script:
awk ’BEGIN {
for (i = 0; i < ARGC; i++)
printf "ARGV[%d] = %s\n",ARGV[i]
exit
}’ $*
3.Interpreter scripts let us write shell scripts using shells other than /bin/sh. When it finds an executable file that isn’t a machine executable,execlp has to choose a shell to invoke,and it always uses /bin/sh. Using an interpreter script,however,we can simply write
#!/bin/csh
(C shell script follows in the interpreter file)
Again,we could wrap all of this in a /bin/sh script (that invokes the C shell),as we described earlier,but more overhead is required.
system Function
It is much easier,to say
system("date > file");
ISO C defines the system function,but its operation is strongly system dependent. POSIX.1 includes the system interface,expanding on the ISO C definition to dscribe its behavior in a POSIX environment.
#include <stdlib.h>
int system(const char *cmdstring);
Because system is implemented by calling fork,exec,and waitpid,there are three types of return values.
1.If either the fork fails or waitpid returns an error other than EINTR,system returns −1 with errno set to indicate the error.
2.If the exec fails,implying that the shell can’t be executed,the return value is as if the shell had executed exit(127).
3.Otherwise,all three functions—fork,and waitpid—succeed,and the return value from system is the termination status of the shell,in the format specified for waitpid.
Some older implementations of system returned an error (EINTR) if waitpid was interrupted by a caught signal. Because there is no strategy that an application can use to recover from this type of error (the process ID of the child is hidden from the caller),POSIX later added the requirement that system not return an error in this case.
#include <errnor.h>
#include <unistd.h>
#include <sys/wait.h>
int system(const char *cmdstring)
{
pid_t pid;
int status;
if(cmdstring==NULL)
{
return 1;
}
if((pid=fork())<0)
{
status=-1;
}
else if(pid==0)
{
execl("/bin/sh","sh","-c",cmdstring,(char *)0);
_exit(127);
}
else
{
while(waitpid(pid,&status,0))
{
if(errno!=EINTR)
{
status=-1;
break;
}
}
}
return status;
}
The system function,without signal handling
The advantage in using system,instead of using fork and exec directly,is that system does all the required error handling and all the required signal handling.
#include "apue.h"
#include <sys/wait.h>
int main(void)
{
int status;
if((status=system("date"))<0)
err_sys("system() error");
pr_exit(status);
if((status=system("nosuchcommand"))<0)
err_sys("system() error");
pr_exit(status);
if((status=system("who; exit 44"))<0)
err_sys("system() error");
pr_exit(status);
return 0;
}
Calling the system function
#include "apue.h"
int main(int argc,char*argv[])
{
int status;
if(argc<2)
err_quit("command-line argument required");
if((status=system(argv[1]))<0)
err_sys("system() error");
pr_exit(status);
return 0;
}
Execute the command-line argument using system
#include "apue.h"
int main(void)
{
printf("real uid=%d,effective uid=%d\n",getuid(),geteuid());
return 0;
}
The superuser permissions that we gave the tsys program are retained across the fork and exec that are done by system.
Some implementations have closed this security hole by changing /bin/sh to reset the effective user ID to the real user ID when they don’t match. On these systems,the prevIoUs example doesn’t work as shown. Instead,the same effective user ID will be printed regardless of the status of the set-user-ID bit on the program calling system.