[comp.unix.programmer] Detecting exec

shj@login.dkuug.dk (Stig Jacobsen) (03/01/91)

When my application desires to spawn off a background process,
I use something roughly like this:

int spawn(char *path, ...)
{

   if (fork() == 0)
      execlp(path, NULL);

}

This is fine if the exec..() call goes well. However, if the exec()
call fails, the error is not reported back to the parent. I get
a SIGCLD of course, but what I'd really like is that my spawn()
function just returns an error, if the exec() call fails. So far
the best solution that I've come up with is,

int spawn(char *path, ...)
{

   if (access(path, R_OK|X_OK) != 0)
      return -1;
   if (fork() == 0)
      execlp(path, NULL);

}

But this is not an elegant solution, nor is it entirely safe:
If someone unlinks 'path' between the access() and the exec()
call (not _that_ unlikely if the system is heavily loaded),
I won't detect the error anyways!

So - what does everybody else do? Am I overseeing something
totally obvious?

--
Stig Jacobsen
shj@login.dkuug.dk / sysadm

rosenqui@crc.skl.dnd.ca (Eric Rosenquist) (03/02/91)

In article <shj.667793966@dkuugin> shj@login.dkuug.dk (Stig Jacobsen) writes:
>When my application desires to spawn off a background process,
>I use something roughly like this:
>
>int spawn(char *path, ...)
>{
>
>   if (fork() == 0)
>      execlp(path, NULL);
>
>}
>
>This is fine if the exec..() call goes well. However, if the exec()
>call fails, the error is not reported back to the parent. I get
>a SIGCLD of course, but what I'd really like is that my spawn()
>function just returns an error, if the exec() call fails.   ...
> ...

It's too late once the fork() has completed.  What you need to do is
have the child exit in a particular way that your SIGCLD handler looks
for if the exec() fails.  In your code snippet the child keeps
executing!

	if (fork() == 0) {
		execlp(path, NULL);
		/* if you get here, the exec() failed */
		exit(SOME_STATUS_CODE);
		/* or */
		abort() or kill(...) etc.
	}

Eric @ SKL
-------------
Eric.Rosenquist@crc.skl.dnd.ca
Software Kinetics Limited
65 Iber Road, Stittsville, Ontario
Canada - K2S 1E7  Phone (613) 831-0888

mike@bria (03/02/91)

In an article, login.dkuug.dk!shj (Stig Jacobsen) writes:
|When my application desires to spawn off a background process,
|I use something roughly like this:

[ example deleted ]

|This is fine if the exec..() call goes well. However, if the exec()
|call fails, the error is not reported back to the parent. I get
|a SIGCLD of course, but what I'd really like is that my spawn()
|function just returns an error, if the exec() call fails. So far
|the best solution that I've come up with is,
|
|int spawn(char *path, ...)
|{
|
|   if (access(path, R_OK|X_OK) != 0)
|      return -1;
|   if (fork() == 0)
|      execlp(path, NULL);
|
|}
|
|But this is not an elegant solution, nor is it entirely safe:
|If someone unlinks 'path' between the access() and the exec()
|call (not _that_ unlikely if the system is heavily loaded),
|I won't detect the error anyways!
|
|So - what does everybody else do? Am I overseeing something
|totally obvious?

Some time ago, I wrote a spawn function (but then I got religion, and
only use fork/exec explicitly :-)

The only caveat is if the spawned program returns an exit status of 255,
it will look like the spawn failed (this was never a big issue for me,
so I left it).  Here goes:

#include <stdio.h>

main(argc,argv)
int argc;
char *argv[];
{
	printf("spawn returned %d\n",spawn(argv[1],&argv[1]));
	return(0);
}

spawn(path,argv)
char *path, *argv[];
{
int ret, xit;

	if ( (ret = fork()) > 0 ) {
		if ( wait(&xit) == -1 )
			return(-1);
		}
	else if ( ret == 0 ) {
		if ( execvp(path,argv) == -1 )
			exit(-1);
		}
	else
		return(-1);

	return(xit >> 8);
}
-- 
Michael Stefanik, MGI Inc., Los Angeles| Opinions stated are not even my own.
Title of the week: Systems Engineer    | UUCP: ...!uunet!bria!mike
-------------------------------------------------------------------------------
Remember folks: If you can't flame MS-DOS, then what _can_ you flame?

denap@alta.sw.stratus.com (Tom DeNapoli) (03/04/91)

>>>>> On 1 Mar 91 20:59:44 GMT, rosenqui@crc.skl.dnd.ca (Eric Rosenquist) said:

[original post deleted] 

rosenqui> It's too late once the fork() has completed.  What you need to do is
rosenqui> have the child exit in a particular way that your SIGCLD handler looks
rosenqui> for if the exec() fails.  In your code snippet the child keeps
rosenqui> executing!

rosenqui> 	if (fork() == 0) {
rosenqui> 		execlp(path, NULL);
rosenqui> 		/* if you get here, the exec() failed */
rosenqui> 		exit(SOME_STATUS_CODE);
rosenqui> 		/* or */
rosenqui> 		abort() or kill(...) etc.
rosenqui> 	}

along the same lines... prior to exiting the child could use a
write back to the parent through a pipe with more details of the
failure. 

rosenqui> Eric @ SKL
rosenqui> -------------
rosenqui> Eric.Rosenquist@crc.skl.dnd.ca
rosenqui> Software Kinetics Limited
rosenqui> 65 Iber Road, Stittsville, Ontario
rosenqui> Canada - K2S 1E7  Phone (613) 831-0888
--
  Tom DeNapoli              | Stratus Computer, Inc.
  denap@alta.sw.stratus.com | 55 Fairbanks Blvd M23EN3
  uunet!lectroid!alta!denap | Marlboro, MA 01752

maart@nat.vu.nl (Maarten Litmaath) (03/05/91)

In article <1991Mar1.205944.13198@crc.skl.dnd.ca>,
	rosenqui@crc.skl.dnd.ca (Eric Rosenquist) writes:
>In article <shj.667793966@dkuugin> shj@login.dkuug.dk (Stig Jacobsen) writes:
>>When my application desires to spawn off a background process,
>>I use something roughly like this:
>>
>>int spawn(char *path, ...)
>>{
>>
>>   if (fork() == 0)
>>      execlp(path, NULL);
>>
>>}
>>
>>This is fine if the exec..() call goes well. However, if the exec()
>>call fails, the error is not reported back to the parent. I get
>>a SIGCLD of course, but what I'd really like is that my spawn()
>>function just returns an error, if the exec() call fails.   ...
>> ...
>
>It's too late once the fork() has completed.  [...]

Wrong.  The following example shows how it can be done.
(I used execvp() instead of execlp().)

------------------------------------------------------------
/*
 * Compile with `-DNO_FCNTL' if your UNIX variant doesn't have fcntl(2).
 * Compile with `-DNO_STRERROR' if it doesn't have strerror(3).
 */
#include	<stdio.h>
#ifdef	NO_FCNTL
#include	<sys/ioctl.h>
#else
#include	<fcntl.h>
#endif	/* NO_FCNTL */

main(argc, argv)
int	argc;
char	**argv;
{
	int	err, spawn();
	char	*strerror();

	if (argc == 1) {
		fprintf(stderr, "Usage: %s command args\n", argv[0]);
		exit(1);
	}

	err = spawn(&argv[1]);

	switch (err) {
	case -1:
		perror("pipe");
		break;
	case -2:
		perror("fork");
		break;
	default:
		printf("The execvp() in the child %s.\n", err == 0 ?
			"succeeded" : "failed");
		if (err != 0)
			printf("The reason was: %s.\n", strerror(err));
	}
}


int	spawn(argv)
char	**argv;
{
	extern	int	errno;
	int	pp[2], n, err;

	if (pipe(pp) < 0)
		return -1;

	switch (fork()) {
	case -1:
		return -2;
	case 0:
		close(pp[0]);

		/* set close-on-exec flag */
#ifdef	NO_FCNTL
		ioctl(pp[1], FIOCLEX, (int *) 0);
#else
		fcntl(pp[1], F_SETFD, 1);
#endif	/* NO_FCNTL */

		execvp(*argv, argv);

		/* send a message indicating the failure */
		write(pp[1], (char *) &errno, sizeof errno);
		_exit(1);
	}

	close(pp[1]);
	n = read(pp[0], (char *) &err, sizeof err);
	close(pp[0]);

	return n == 0 ? 0 : err;
}


#ifdef	NO_STRERROR
char	*strerror(n)
int	n;
{
	extern	int	errno, sys_nerr;
	extern	char	*sys_errlist[];
	static	char	buf[32];

	if ((unsigned) n < sys_nerr)
		return sys_errlist[n];
	
	sprintf(buf, "Unknown error %d", n);
	return buf;
}
#endif	/* NO_STRERROR */

shj@login.dkuug.dk (Stig Jacobsen) (03/06/91)

maart@nat.vu.nl (Maarten Litmaath) writes:

>		/* set close-on-exec flag */
>#ifdef	NO_FCNTL
>		ioctl(pp[1], FIOCLEX, (int *) 0);
>#else
>		fcntl(pp[1], F_SETFD, 1);
>#endif	/* NO_FCNTL */
...
>	n = read(pp[0], (char *) &err, sizeof err);
>	close(pp[0]);

>	return n == 0 ? 0 : err;

Yeah! It was something like this that I wanted, where I get no
ugly messages splattering over the parents screen and where I get
an error return from the spawn call and not via some signal. A
big thanks to you and to everybody else who answered here and in
mail.
--
Stig Jacobsen
shj@login.dkuug.dk / sysadm

maart@nat.vu.nl (Maarten Litmaath) (03/08/91)

In article <shj.668195000@dkuugin>,
	shj@login.dkuug.dk (Stig Jacobsen) writes:
>[...]
>Yeah! It was something like this that I wanted, where I get no
>ugly messages splattering over the parents screen and where I get
>an error return from the spawn call and not via some signal. [...]

One thing I forgot to mention: don't forget to wait() for the child,
_even_ when the spawn() has failed!

If your UNIX variant has wait4(2), waitfor(2) or waitpid(2), you can
let spawn() itself take care of it in case the execvp() has failed.

Otherwise things must be handled by a routine that takes care of all
children, if any.

The interface might be changed to:

	int	spawn(argv, pidp)
	char	**argv
	int	*pidp;

..and spawn() would return the PID in `*pidp'.

Do _not_ use the simple wait(2) system call in spawn(), as that may
reap other children as well...  The other forms of wait() weren't
invented for nothing.

libes@cme.nist.gov (Don Libes) (03/08/91)

In article <1991Mar7.222033.4711@nat.vu.nl> maart@nat.vu.nl (Maarten Litmaath) writes:
>	shj@login.dkuug.dk (Stig Jacobsen) writes:
>>Yeah! It was something like this that I wanted, where I get no
>>ugly messages splattering over the parents screen and where I get
>>an error return from the spawn call and not via some signal. [...]

>The interface might be changed to:

>	int	spawn(argv, pidp)
>	char	**argv
>	int	*pidp;
>..and spawn() would return the PID in `*pidp'.

If you have 'expect' (as of 1/91), you can use the library that comes
with it from C or C++.  The following functions exist:

     int
     exp_spawnl(file, arg0 [, arg1, ..., argn] (char *)0);
     char *file;
     char *arg0, *arg1, ... *argn;

     int
     exp_spawnv(file,argv);
     char *file, *argv[ ];

     extern int exp_pid;

Both of these fork a new process so that stdin, stdout, and stderr can
be written and read by the current process.  A file descriptor is
returned which corresponds to the process's stdin, stdout, and stderr.
the new process.  exp_pid is set to the pid of the new process.

An additional interface exists, styled after popen:

     FILE *
     exp_popen(command);
     char *command;

In all of these, exec errors are returned by reading output of the new
process (as the original poster requested).  The library has a number
of other related I/O functions to control interactive processes from C.
See the man page for more info.

Don Libes          libes@cme.nist.gov      ...!uunet!cme-durer!libes

shj@login.dkuug.dk (Stig Jacobsen) (03/08/91)

maart@nat.vu.nl (Maarten Litmaath) writes:

>One thing I forgot to mention: don't forget to wait() for the child,
>_even_ when the spawn() has failed!

Since I didn't want to wait() for anything, I just performed
another fork() in the spawn() function and then let the first
child exit() imediately, so that the second child would get init
(process 1) as parent. This way I avoid a zombie process.

Rather wastefull, yes, but I couldn't find another way to
disassociate my child process from the parent.

>If your UNIX variant has wait4(2), waitfor(2) or waitpid(2), 

It does not; its a lowly System V r3.2.

--
Stig Jacobsen
shj@login.dkuug.dk / sysadm

maart@nat.vu.nl (Maarten Litmaath) (03/12/91)

In article <shj.668447405@dkuugin>,
	shj@login.dkuug.dk (Stig Jacobsen) writes:
>[...]
>Since I didn't want to wait() for anything, I just performed
>another fork() in the spawn() function and then let the first
>child exit() imediately, so that the second child would get init
>(process 1) as parent. This way I avoid a zombie process.
>
>Rather wastefull, yes, but I couldn't find another way to
>disassociate my child process from the parent.

This is quite a normal (portable!) way to dispose of child processes.

Right, I've composed a general spawn() example, included below.
Note that the calling syntax and semantics have changed.
Enjoy!

--------------------cut here--------------------
/*
 * spawn.c
 *
 * Usage:
 *
 *	char	*command, *argv[];
 *	int	status, *pidp, *errp, (*preamble)();
 *
 *	status = spawn(command, argv, pidp, errp, preamble);
 *
 * Spawn `command' with arguments `argv' (argv[0] is the name);
 * the `argv' array ends with a NULL pointer.
 *
 * If `preamble' is not a NULL pointer, the addressed function will be
 * called in the child (before the execvp(), of course) with arguments
 * `command' and `argv'.  This function may set some signals to be ignored,
 * reset the process group, redirect input, etc.  Only if it returns 0 the
 * call to execvp() will be made; else spawn() will return -3 and if `errp'
 * is not a NULL pointer the return value of `preamble' will be copied into
 * `*errp'.
 *
 * The process ID of the child is copied into `*pidp', unless `pidp' is a
 * NULL pointer.
 *
 * The return value of spawn() itself is 0 on success, -1 if it could not
 * create the pipe that it needs, -2 if it could not fork(), -3 if `preamble'
 * was nonnull and did not return 0, and -4 if the execvp() failed.
 *
 * In the last case the errno value indicating why the execvp() failed
 * is copied into `*errp', unless `errp' is a NULL pointer.
 *
 * Compile with `-DNO_FCNTL' if your UNIX variant doesn't have fcntl(2).
 * Compile with `-DNO_STRERROR' if it doesn't have strerror(3).
 *
 * Author: Maarten Litmaath @ Dept. of Physics, Vrije University Amsterdam
 * Email: maart@nat.vu.nl
 * Date: 11 Mar 91
 */

#include	<stdio.h>

#ifdef	NO_FCNTL
#include	<sys/ioctl.h>
#else
#include	<fcntl.h>
#endif	/* NO_FCNTL */


main(argc, argv)
int	argc;
char	**argv;
{
	int	status, spawn(), pid, error, preamble();
	char	*strerror();

	if (argc == 1) {
		fprintf(stderr, "Usage: %s command args\n", argv[0]);
		exit(1);
	}

	/* start background job */

	status = spawn(argv[1], &argv[1], &pid, &error, preamble);

	switch (status) {
	case 0:
		printf("The execvp() in the child succeeded.\n");
		printf("The child has process ID %d.\n", pid);
		break;
	case -1:
		perror("pipe");
		break;
	case -2:
		perror("fork");
		break;
	case -3:
		printf("The preamble function returned %d.\n", error);
		break;
	case -4:
		printf("The execvp() in the child failed.\n");
		printf("The reason was: %s.\n", strerror(error));
		break;
	default:
		printf("Values of %d will give rise to dom!\n", status);
		/* pseudo V6 */
		break;
	}

	return status;
}


int	spawn(cmd, argv, pidp, errp, preamble)
char	*cmd, **argv;
int	*pidp, *errp, (*preamble)();
{
	extern	int	errno;
	int	pp[2], n, pid;
	struct	error {
		int	status, errno;
	} err;

	if (pipe(pp) < 0)
		return -1;

	switch (pid = fork()) {
	case -1:
		close(pp[0]);
		close(pp[1]);
		return -2;
	case 0:
		close(pp[0]);

		/* set close-on-exec flag */
#ifdef	NO_FCNTL
		ioctl(pp[1], FIOCLEX, (int *) 0);
#else
		fcntl(pp[1], F_SETFD, 1);
#endif	/* NO_FCNTL */

		if (preamble && (err.errno = (*preamble)(cmd, argv)) != 0)
			err.status = -3;
		else {
			execvp(cmd, argv);
			err.errno = errno;
			err.status = -4;
		}

		/* send a message indicating the failure */

		write(pp[1], (char *) &err, sizeof err);
		_exit(1);
	}

	if (pidp)
		*pidp = pid;

	close(pp[1]);
	n = read(pp[0], (char *) &err, sizeof err);
	close(pp[0]);

	if (n != 0 && errp)
		*errp = err.errno;

	return n == 0 ? 0 : err.status;
}


#include	<signal.h>

static	int	Signals[] = {
	SIGHUP,		/* ignore SIGHUP (on logout or hangup) */
	SIGINT,		/* ignore interrupts */
	SIGQUIT,	/* ignore quits */
#ifdef	SIGTSTP
	SIGTSTP,	/* ignore keyboard stop signals */
#endif	/* SIGTSTP */
	0
};


/* ARGSUSED */

int	preamble(cmd, argv)
char	*cmd, **argv;
{
	int	*sigp;

	for (sigp = Signals; *sigp; sigp++)
		signal(*sigp, SIG_IGN);

	return 0;
}


#ifdef	NO_STRERROR
char	*strerror(n)
int	n;
{
	extern	int	errno, sys_nerr;
	extern	char	*sys_errlist[];
	static	char	buf[32];

	if ((unsigned) n < sys_nerr)
		return sys_errlist[n];

	sprintf(buf, "Unknown error %d", n);
	return buf;
}
#endif	/* NO_STRERROR */