ftp.nice.ch/pub/next/unix/mail/zend.1.0.s.tar.gz#/zend-1.0/zendutil.c

This is zendutil.c in view mode; [Download] [Up]

static char rcsid[] = "$Id: zendutil.c,v 1.12 1993/02/02 21:00:00 gerben Exp $";

/*
    Zend --- a mail transport program for large files and directories
    Copyright (C) 1992  Gerben C. Th. Wierda

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    Gerben Wierda
    Goudriaanstraat 1
    1222 SG  Hilversum
    The Netherlands

    gerben@rna.indiv.nluug.nl
*/

/*
 *
 * zendutil.c -- various routines for supporting zend.
 *
 */

#include "zend.h"

/*
 * read_config() -- open HOMEDIR/config and parse the file.
 */

#if _ANSI_DEFUN_
extern int read_config( void)
#else
extern int read_config()
#endif
{
    char buf[PATHBUFSIZ];
    FILE *configStream;

    strcpy( buf, HOMEDIR);
    strcat( buf, "/config");

    if ((configStream = fopen( buf, "r")) == NIL)
    {
	return -1;
    }
    else
    {
	while (parsestreamtoglob( global, configStream) != PARSEEOF)
	    ;
	fclose( configStream);
    }
    if (get_global_string( global, SYSTEMNAMEPROMPT) == NIL)
    {
	return -2;
    }
    return 0;
}

/*
 * Find a Variable in the array sentinel-terminated array `globs', by name.
 * Return a pointer to that variable, and NULL if not found.
 */
static Variable *
#if _ANSI_DEFUN_
get_var( Variable vars[], const char *prompt)
#else
get_var( vars, prompt)
Variable vars[];
char *prompt;
#endif
{
    register Variable *varptr;

    for (varptr = vars;  varptr->type != NONE;  varptr++)
    {
	if (strcmp( varptr->prompt, prompt) == 0)
	{
	    return varptr;
	}
    }
    return NULL;
}

#if _ANSI_DEFUN_
extern int set_global( Variable *globs, const char *prompt, ...)
#else
extern int set_global( globs, prompt, va_alist)
Variable *globs;
char *prompt;
va_dcl
#endif
{
    register va_list argpt;
    register Variable *varptr = get_var( globs, prompt);

    if (varptr == NULL)
    {
	return -1;
    }
    va_begin( argpt, prompt);

    switch( varptr->type)
    {
    case STRING:
	if (varptr->value.string)
	{
	    free(varptr->value.string);
	}
	if (varptr->value.string = va_arg( argpt, char *))
	{
	    varptr->value.string =
		strcpy( safecalloc( strlen( varptr->value.string) + 1,
				    sizeof( char)),
			varptr->value.string);
	}
	break;
    case ULONG:
	varptr->value.ulong = va_arg( argpt, Ulong);
	break;
    case USHORT:
	varptr->value.ushort = (Ushort)va_arg( argpt, unsigned int);
	break;
    case BOOLEAN:
	varptr->value.bool = va_arg( argpt, Boolean);
	break;
    default:
	;
    }
    va_end( argpt);
    return (int)(varptr - globs);
}

#if _ANSI_DEFUN_
extern Ulong get_global_ulong( Variable *globs, const char *prompt)
#else
extern Ulong get_global_ulong( globs, prompt)
Variable *globs;
char *prompt;
#endif
{
    register Variable *varptr = get_var(globs, prompt);

    if (varptr == NULL || varptr->type != ULONG)
    {
	zerr( "get_global_ulong with wrong prompt %s\n",
	      prompt);
	abort();
    }
    return varptr->value.ulong;
}

#if _ANSI_DEFUN_
extern Ushort get_global_ushort( Variable *globs, const char *prompt)
#else
extern Ushort get_global_ushort( globs, prompt)
Variable *globs;
char *prompt;
#endif
{
    register Variable *varptr = get_var(globs, prompt);

    if (varptr == NULL || varptr->type != USHORT)
    {
	zerr( "get_global_ushort with wrong prompt %s\n",
	      prompt);
	abort();
    }
    return varptr->value.ushort;
}

#if _ANSI_DEFUN_
extern StringPointer get_global_string( Variable *globs, const char *prompt)
#else
extern StringPointer get_global_string( globs, prompt)
Variable *globs;
char *prompt;
#endif
{
    register Variable *varptr = get_var(globs, prompt);

    if (varptr == NULL || varptr->type != STRING)
    {
	zerr( "get_global_string with wrong prompt %s\n",
	      prompt);
	abort();
    }
    return varptr->value.string;
}

#if _ANSI_DEFUN_
extern Boolean get_global_boolean( Variable *globs, const char *prompt)
#else
extern Boolean get_global_boolean( globs, prompt)
Variable *globs;
char *prompt;
#endif
{
    register Variable *varptr = get_var(globs, prompt);

    if (varptr == NULL || varptr->type != BOOLEAN)
    {
	zerr( "get_global_boolean with wrong prompt %s\n",
	      prompt);
	abort();
    }
    return varptr->value.bool;
}

/*
 * parsebuftoglob() -- parse the buffer for a prompt from the global set
 * and get the accopanying value.
 */

#if _ANSI_DEFUN_
extern int parsebuftoglob( Variable *globs, const char *buf)
#else
extern int parsebuftoglob( globs, buf)
Variable *globs;
char *buf;
#endif
{
    register Variable *varptr;

    for (varptr = globs;  varptr->type != NONE;  varptr++)
    {
	if (strncmp( varptr->prompt, buf, strlen( varptr->prompt)) == 0)
	{
	    buf += (strlen( varptr->prompt));
	    switch (varptr->type)
	    {
		Ulong tmpulong;

	    case STRING:
		if (strlen( buf) == 0)
		{
		    varptr->value.string = NIL;
		}
		else
		{
		    varptr->value.string =
			(StringPointer)safecalloc( strlen(buf)+1, sizeof( char));
		    strcpy( varptr->value.string, buf);
		}
		break;
	    case USHORT:
		sscanf( buf, "%lu", &tmpulong);
		varptr->value.ushort = (Ushort)tmpulong;
		break;
	    case ULONG:
		sscanf( buf, "%lu", &tmpulong);
		varptr->value.ulong = tmpulong;
		break;
	    case BOOLEAN:
		if (strncmp( buf, BOOLTRUESTRING, strlen( BOOLTRUESTRING)) == 0)
		{
		    varptr->value.bool = TRUE;
		}
		else if (strncmp( buf, BOOLFALSESTRING, strlen( BOOLFALSESTRING)) == 0)
		{
		    varptr->value.bool = FALSE;
		}
		else
		{
		    zerr( "ERROR, illegal value for global boolean, set to %s\n",
			  BOOLFALSESTRING);
		    varptr->value.bool = FALSE;
		}
		break;
	    default:
		/* Only for compiler warning. */
		;
	    }
	    return (int)(varptr - globs);
	}
    }
    return PARSEUNKNOWN;
}

/*
 * writeglobtobuf() -- Write the prompt and the value to buf.
 */

#if _ANSI_DEFUN_
extern int writeglobtobuf( Variable *globs, const char *prompt, char *buf)
#else
extern int writeglobtobuf( globs, prompt, buf)
Variable *globs;
char *prompt;
char *buf;
#endif
{
    register Variable *varptr = get_var( globs, prompt);

    if (varptr == NULL)
    {
	return -1;
    }
    switch (varptr->type)
    {
	char *boolstr;

    case STRING:
	sprintf( buf, "%s%s", varptr->prompt,
		 varptr->value.string == NIL ? "" : varptr->value.string);
	break;
    case USHORT:
	sprintf( buf, "%s%u", varptr->prompt, (unsigned)varptr->value.ushort);
	break;
    case ULONG:
	sprintf( buf, "%s%lu", varptr->prompt, varptr->value.ulong);
	break;
    case BOOLEAN:
	boolstr = "";
	switch (varptr->value.bool)
	{
	case FALSE:
	    boolstr = BOOLFALSESTRING;
	    break;
	case TRUE:
	    boolstr = BOOLTRUESTRING;
	    break;
	}
	sprintf( buf, "%s%s", varptr->prompt, boolstr);
	break;
    default:
	/* Programmer error */
	;
    }
    return (int)(varptr - globs);
}


/*
 * parsestreamtoglob() -- read a line from the stream and run parsebuftoglob()
 * reads until some variable is found or EOF.
 */

#if _ANSI_DEFUN_
extern int parsestreamtoglob( Variable *globs, FILE *stream)
#else
extern int parsestreamtoglob( globs, stream)
Variable *globs;
FILE *stream;
#endif
{
    char buf[PATHBUFSIZ];

    while (fgets( buf, PATHBUFSIZ, stream))
    {
	if (strncmp( buf, ZENDDELIMITER, strlen( ZENDDELIMITER)) == 0)
	{
	    return PARSEDELIMITER;
	}
	if (buf[strlen( buf)-1] == '\n')
	{
	    buf[strlen( buf)-1] = NUL;
	}
	return parsebuftoglob( globs, buf);
    }
    return PARSEEOF;
}

/*
 * writeglobtostream() -- run writeglobtobuf() and write buf to stream
 */

#if _ANSI_DEFUN_
extern int writeglobtostream( Variable *globs, const char *prompt, FILE *stream)
#else
extern int writeglobtostream( globs, prompt, stream)
Variable *globs;
char *prompt;
FILE *stream;
#endif
{
    char buf[PATHBUFSIZ];
    int varno;

    if ((varno = writeglobtobuf( globs, prompt, buf)) != -1)
    {
	strcat( buf, "\n");
	fputs( buf, stream);
#if DEBUG
	if (dump && stderr != stream)
	{
	    zerr( "%s", buf);
	}
#endif
    }
    return varno;
}

extern Boolean
#if _ANSI_DEFUN_
write_globs_to_jobfile( Variable *globs, Ulong jobId)
#else
write_globs_to_jobfile( globs, jobId)
Variable *globs;
Ulong jobId;
#endif
{
    char buf[PATHBUFSIZ];
    FILE *tmpStream;
    int try;
    int oldumask;

    sprintf( buf, "%s/C.%lu", HOMEDIR, jobId);
    add_file_to_cleanup( buf, FALSE);
    if (one_at_a_time( jobId, RDWR_JOBFILE) != 0)
    {
	return TRUE;
    }
    oldumask = umask( ~(S_IRUSR|S_IWUSR));
    tmpStream = fopen( buf, "w");
    umask( oldumask);
    if (tmpStream == NIL)
    {
	zerr( "Unable to create job config %s\n", buf);
	end_one_at_a_time( jobId, RDWR_JOBFILE);
	return FALSE;
    }
    for (try=0; try<nrOfGlobalVariables; try++)
    {
	writeglobtostream( global, global[try].prompt, tmpStream);
    }
    if (fclose( tmpStream) != 0)
    {
	zerr( "Unable to save job config %s\n", buf);
	end_one_at_a_time( jobId, RDWR_JOBFILE);
	return FALSE;
    }
    end_one_at_a_time( jobId, RDWR_JOBFILE);
    return TRUE;
}

#if _ANSI_DEFUN_
extern int read_jobfile_to_globs( Variable *globs, Ulong jobId)
#else
extern int read_jobfile_to_globs( globs, jobId)
Variable *globs;
Ulong jobId;
#endif
{
    char buf[PATHBUFSIZ];
    FILE *tmpStream;

    sprintf( buf, "%s/C.%lu", HOMEDIR, jobId);
    if (one_at_a_time( jobId, RDWR_JOBFILE) != 0)
    {
	return -1;
    }
    if ((tmpStream =  fopen( buf, "r")) == NIL)
    {
	end_one_at_a_time( jobId, RDWR_JOBFILE);
	return -1;
    }
    while( parsestreamtoglob( globs, tmpStream) != PARSEEOF)
	;
    fclose( tmpStream);
    end_one_at_a_time( jobId, RDWR_JOBFILE);
    return 0;
}

#if _ANSI_DEFUN_
extern int one_at_a_time( Ulong jobId, char type)
#else
extern int one_at_a_time( jobId, type)
Ulong jobId;
char type;
#endif
{
    char buf[PATHBUFSIZ];
    char buf2[PATHBUFSIZ];
    int fd;

    sprintf( buf, "%s/D.%lu/X.%05u%c", HOMEDIR, jobId, process_id, type);
    add_file_to_cleanup( buf, FALSE);
    if ((fd = creat( buf, 0)) == -1)
    {
	remove_file_to_cleanup( buf);
	zerr( "Failed lock (%lu,%c)\n", jobId, type);
	return -1;
    }
    close( fd);
    if (jobId == RESERVEDID)
    {
	sprintf( buf2, "%s/LCK.%c", HOMEDIR, type);
    }
    else
    {
	sprintf( buf2, "%s/D.%lu/LCK.%c", HOMEDIR, jobId, type);
    }
    while (link( buf, buf2) != 0)
    {
	sleep( 2);
    }
    add_file_to_cleanup( buf2, FALSE);
#if DEBUG
    zerr( "Got lock (%lu,%c)\n", jobId, type);
#endif
    unlink( buf);
    remove_file_to_cleanup( buf);
    return 0;
}

#if _ANSI_DEFUN_
extern int end_one_at_a_time( Ulong jobId, char type)
#else
extern int end_one_at_a_time( jobId, type)
Ulong jobId;
char type;
#endif
{
    char buf[PATHBUFSIZ];

    if (jobId == RESERVEDID)
    {
	sprintf( buf, "%s/LCK.%c", HOMEDIR, type);
    }
    else
    {
	sprintf( buf, "%s/D.%lu/LCK.%c", HOMEDIR, jobId, type);
    }
    if (unlink( buf) != 0)
    {
	zerr( "Failed unlock (%lu,%c)\n", jobId, type);
	return -1;
    }
    remove_file_to_cleanup( buf);
#if DEBUG
    zerr( "Got unlock (%lu,%c)\n", jobId, type);
#endif
    return 0;
}

extern void
#if _ANSI_DEFUN_
cleanup_stale_locks( void)
#else
cleanup_stale_locks()
#endif
{
    char buf[PATHBUFSIZ];

    sprintf( buf, "cd %s && %s -f LCK.* */LCK.* */X.*", HOMEDIR, RMPROG);
    system_as_zend( buf);
}

/*
 * Create a directory for this transfer. The name of the directory defines
 * the jobID. If not found within RETRY_LIMIT tries, give up.
 */
extern Ulong
#if _ANSI_DEFUN_
new_job( void)
#else
new_job()
#endif
{
    char buf[PATHBUFSIZ];
    int try;
    Ulong jobId;
#if DEBUG
    zerr( "Trying to find job slot within retry limit (%d)\n", RETRY_LIMIT);
#endif
    srandom( (int)time( NIL));

    for (try=0; try<RETRY_LIMIT; try++)
    {
	if ((jobId = (Ulong)random()) == RESERVEDID)
	{
	    continue;
	}
	sprintf( buf, "%s/D.%lu", HOMEDIR, jobId);
	add_file_to_cleanup( buf, TRUE);
	if (mkdir( buf, S_IRWXU) == 0)
	{
#if DEBUG
	    zerr( "Found job slot %lu\n", jobId);
#endif
	    return jobId;
	}
	remove_file_to_cleanup( buf);
    }
    zerr( "No job slot found within retry limit (%d)!\n", RETRY_LIMIT);
    return RESERVEDID;
}

/*
 * Kill a job. (but only if it exists.)
 */
extern void
#if _ANSI_DEFUN_
kill_job( Ulong jobId)
#else
kill_job( jobId)
Ulong jobId;
#endif
{
    char buf[PATHBUFSIZ];
    struct stat statBuf;

    sprintf( buf, "%s/D.%lu", HOMEDIR, jobId);

#if 0
    if (access( buf, F_OK) == 0)
#endif
    if (stat( buf, &statBuf) == 0)
    {
	sprintf( buf, "cd %s && %s -rf C.%lu D.%lu F.%lu", HOMEDIR, RMPROG,
		 jobId, jobId, jobId);
	system_as_zend( buf);
    }
}

/*
 * See if `str' is listed as an element in a comma-separated `list'.
 */
extern Boolean
#if _ANSI_DEFUN_
str_listed( const char *str, const char *list)
#else
str_listed(str, list)
char *str, *list;
#endif
{
    const char *lp;

    if (lp = list)
    {
	int len = strlen(str);

	for (;  lp = strchr( lp, str[0]);  lp++)
	{
	    if ((lp == list || lp[-1] == ',') &&
		(strncmp( lp, str, len) == 0) &&
		(lp[len] == '\0' || lp[len] == ','))
	    {
		return TRUE;
	    }
	}
    }
    return FALSE;
}

static void
#if _ANSI_DEFUN_
vzlog( const char *format, va_list argpt)
#else
vzlog( format, argpt)
char *format;
va_list argpt;
#endif
{
    char logfilename[PATHBUFSIZ];
    FILE *logStream;
    struct tm *t;
    struct stat statBuf;

    sprintf( logfilename, "%s/LOGFILE", HOMEDIR);

    /*
     * Log only if logfile already exists.
     */
#if 0
    if (access( logfilename, F_OK) != 0)
#endif
    if (stat( logfilename, &statBuf) != 0)
    {
	return;
    }
    /* {{one_at_a_time()?}} */
    if ((logStream = fopen( logfilename, "a")) == NULL)
    {
	return;
    }

    t = localtime( &timestamp);

    fprintf( logStream,
	     "%04d/%02d/%02d-%02d:%02d (%lu) %s %s %s %s %s/%s: ",
	     t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
	     t->tm_hour, t->tm_min,
	     (get_global_string( global, ROLEPROMPT) ?
	      get_global_ulong( global,
				(strcmp( get_global_string( global, ROLEPROMPT),
					 ROLE_SENDER) == 0) ?
				SENDINGJOBIDPROMPT : RECEIVINGJOBIDPROMPT) :
	      RESERVEDID),
	     get_global_string( global, ROLEPROMPT),
	     get_global_string( global, SENDINGUSERPROMPT),
	     get_global_string( remoteglobal, SYSTEMNAMEPROMPT),
	     get_global_string( global, RECEIVINGUSERPROMPT),
	     get_global_string( global, DIRPROMPT),
	     get_global_string( global, FILEPROMPT));
    vfprintf( logStream, format, argpt);
    fprintf( logStream, "\n");

    fclose( logStream);
}

extern void
#if _ANSI_DEFUN_
zlog( const char *format, ...)
#else
zlog( format, va_alist)
char *format;
va_dcl
#endif
{
    va_list argpt;

    va_begin( argpt, format);
    vzlog( format, argpt);
    va_end( argpt);
}

#if _ANSI_DEFUN_
extern void zerr( const char *s, ...)
#else
extern void zerr( s, va_alist)
char *s;
va_dcl
#endif
{
    va_list argpt;

    va_begin( argpt, s);
    if ( logErrors)
    {
	char formatbuf[PATHBUFSIZ];
	int len = strlen(s);

	/* Kludge to remove trailing newline (if any) */
	if (len > 0 && s[len-1] == '\n')
	{
	    --len;
	}
	sprintf( formatbuf, "error (%.*s)", len, s);
	vzlog( formatbuf, argpt);
    }
    else
    {
	fprintf( stderr, "Zend: ");
	vfprintf( stderr, s, argpt);
    }
    va_end( argpt);
}

/* support functions for signal handler. */

struct filelist {
    struct filelist	*next;
    Boolean		isDirectory;
    char		name[1];	/* Dynamically allocated. */
};

static struct filelist	*filesToCleanup;

extern void
#if _ANSI_DEFUN_
add_file_to_cleanup( const char *name, Boolean isDirectory)
#else
add_file_to_cleanup( name, isDirectory)
char *name;
Boolean isDirectory;
#endif
{
    struct filelist *np;

    for (np = filesToCleanup;  np;  np = np->next)
    {
	if (strcmp( name, np->name) == 0)
	{
	    /* this shouldn't happen. */
	    zerr( "BUG (add_file_to_cleanup) duplicate filename %s\n", name);
	    return;
	}
    }
    np = (struct filelist *)safecalloc( 1, strlen( name) + sizeof( *np));
    np->next = filesToCleanup;
    filesToCleanup = np;

    strcpy( np->name, name);
    np->isDirectory = isDirectory;
}

extern void
#if _ANSI_DEFUN_
remove_file_to_cleanup( const char *name)
#else
remove_file_to_cleanup( name)
char *name;
#endif
{
    struct filelist *np, **head = &filesToCleanup;

    for (head = &filesToCleanup;  np = (*head);  head = &np->next)
    {
	if (strcmp( name, np->name) == 0)
	{
	    (*head) = np->next;
	    free(np);
	    return;
	}
    }
    /* This shouldn't happen. */
    zerr( "BUG (remove_file_to_cleanup) missing filename %s\n", name);
}

extern void
#if _ANSI_DEFUN_
file_cleanup( void)
#else
file_cleanup()
#endif
{
    struct filelist *np;

    while (np = filesToCleanup)
    {
	if (np->isDirectory)
	{
	    rmdir( np->name);
	}
	else
	{
	    unlink( np->name);
	}
	filesToCleanup = np->next;
	free( np);
    }
}

/* Like system(), but user and group IDs of child process can be given.
   Also this doesn't ignore signals, so it can be interrupted. */

extern int
#if _ANSI_DEFUN_
system_as( const char *command, uid_t uid, gid_t gid)
#else
system_as( command, uid, gid)
const char *command;
uid_t uid;
gid_t gid;
#endif
{
    pid_t child_pid;
    WAIT_T status;

    if ((child_pid = fork()) == 0)
    {
	/* child. */
	setuid( uid);
	setgid( gid);

	execl( "/bin/sh", "sh(zend)", "-c", command, NULL);
	exit( 127);
    }
    if (child_pid < 0)
    {
#if DEBUG
	zerr( "Unable to fork()\n");
#endif
	return -1;
    }

    /* parent */
    if (waitpid( child_pid, &status, 0) < 0)
    {
#if DEBUG
	zerr( "system_as: waitpid( %d) failed. (%s)\n", 
	      child_pid, strerror( errno));
#endif
	return -1;
    }
    return *(int *)&status;	/* {cast since WAIT_T may be `union wait'} */
}

/* Like popen(), but user and group IDs of child process can be given.
   Also this doesn't ignore signals, so it can be interrupted.
   {NOTE: Also note that popen_as() should be paired with pclose_as();
    the aliases for popen/pclose in zend.h help to enforce this.} */

static struct pinfo_listitem
{
    FILE		    *stream;
    pid_t		    child_pid;
    struct pinfo_listitem   *next;
} popen_info_listhead;

extern FILE *
#if _ANSI_DEFUN_
popen_as( const char *command, const char *mode, uid_t uid, gid_t gid)
#else
popen_as( command, mode, uid, gid)
const char *command;
const char *mode;
uid_t uid;
gid_t gid;
#endif
{
    int pipe_fd[2];
    int writing;
    struct pinfo_listitem *pinfo = &popen_info_listhead;

    assert(mode && (*mode == 'r' || *mode == 'w'));
    writing = (*mode == 'w');

    while (pinfo->stream != NULL && pinfo->next != NULL)
    {
#if DEBUG
	zerr( "popen_as: nested call.\n");
#endif
	pinfo = pinfo->next;
    }
    if (pinfo->stream != NULL)
    {
	pinfo->next = (struct pinfo_listitem *)
			calloc( 1, sizeof( struct pinfo_listitem));
	if (pinfo->next == NULL)
	{
#if DEBUG
	    zerr( "popen_as: pipe() failed. (Out of memory)\n");
#endif
	    return NULL;
	}
	pinfo = pinfo->next;
    }

    if (pipe( pipe_fd) != 0)
    {
#if DEBUG
	zerr( "popen_as: pipe() failed. (%s)\n", strerror( errno));
#endif
	return NULL;
    }
    if ((pinfo->stream = fdopen( pipe_fd[writing], mode)) == NULL)
    {
#if DEBUG
	zerr( "popen_as: fdopen() failed. (%s)\n", strerror( errno));
#endif
	close(pipe_fd[0]);
	close(pipe_fd[1]);
	return NULL;
    }
    if ((pinfo->child_pid = fork()) == 0)
    {
	/* child. */
	setuid( uid);
	setgid( gid);

	/* Connect pipe. (in child, read and write reverse roles) */
	writing = !writing;
	if (dup2( pipe_fd[writing], writing) != writing)
	{
	    exit( 127);
	}
	close( pipe_fd[0]);
	close( pipe_fd[1]);

	execl( "/bin/sh", "sh(zend)", "-c", command, NULL);
	exit( 127);
    }

    /* parent */
    close( pipe_fd[!writing]);

    if (pinfo->child_pid < 0)
    {
#if DEBUG
	zerr( "popen_as: fork() failed. (%s)\n", strerror( errno));
#endif
	fclose(pinfo->stream);
	pinfo->stream = NULL;
    }

    return pinfo->stream;
}

extern int
#if _ANSI_DEFUN_
pclose_as( FILE *stream)
#else
pclose_as( stream)
FILE *stream;
#endif
{
    WAIT_T status;
    struct pinfo_listitem *pinfo = &popen_info_listhead;

    while (stream != pinfo->stream && pinfo->next != NULL)
    {
	pinfo = pinfo->next;
    }
    if (stream != pinfo->stream)
    {
	errno = EINVAL;
	return -1;
    }
    /* Close the pipe BEFORE wait()ing for the child to finish.
       (so the child, if still running, sees an end-of-file,
	or gets a broken pipe) */
    fclose( stream);
    pinfo->stream = NULL;

    if (waitpid( pinfo->child_pid, &status, 0) < 0)
    {
#if DEBUG
	zerr( "pclose_as: waitpid( %d) failed. (%s)\n", 
	      pinfo->child_pid, strerror( errno));
#endif
	return -1;		/* {{really?}} */
    }
    
    return *(int *)&status;	/* {cast since WAIT_T may be `union wait'} */
}

/*
 * Return basename of `filename'.
 */
extern char *
#if _ANSI_DEFUN_
basename( const char *filename)
#else
basename( filename)
char *filename;
#endif
{
    char *sep;

    return (sep = strrchr(filename, '/')) ? (sep + 1) : (char *) filename;
}

/*
 * Quote `str' for use in a shell command; return result in a dynamically
 * allocated string.
 */
extern char *
#if _ANSI_DEFUN_
quote_for_sh( const char *str)
#else
quote_for_sh( str)
char *str;
#endif
{
    const char *s;
    char *buf, *d;
    int	nquotes = 0;

    /* count number of quote (') characters in string. */
    for (s = str;  *s != '\0'; )
    {
	if (*s++ == '\'')
	{
	    nquotes++;
	}
    }

    /* allocate space for result. */
    d = buf = safecalloc( 1 + (size_t)(s - str) + nquotes * 3 + 1 + 1,
			  sizeof( char));

    *d++ = '\'';
    for (s = str;  (*d++ = *s) != '\0'; )
    {
	if (*s++ == '\'')
	{
	    *d++ = '\\', *d++ = '\'', *d++ = '\'';
	}
    }
    d[-1] = '\'';
    d[0] = '\0';

    return buf;
}

extern void_*
#if _ANSI_DEFUN_
safecalloc( size_t n, size_t size)
#else
safecalloc( n, size)
size_t n;
size_t size;
#endif
{
    void_*p;

    if ((p = calloc( n, size)) == NULL)
    {
	zerr("Out of memory!");
	abort();
    }
    return p;	
}

#if !HAVE_STRERROR
# ifdef strerror
#   if _ANSI_DEFUN_
extern char *strerror( int errnum)
#   else
extern char *strerror( errnum)
int errnum;
#   endif
{
#   ifndef sys_nerr
    extern int sys_nerr;
#   endif
#   ifndef sys_errlist
    extern char *sys_errlist[];
#   endif

    if ((unsigned) errnum < sys_nerr)
    {
	return sys_errlist[errnum];
    }
    else
    {
	static char buf[20];

	sprintf( buf, "Error %d", errnum);
	return( buf);
    }
}
# endif /* strerror */
#endif /* HAVE_STRERROR */

#if !__POSIX__

/* Emulation of Posix.1 waitpid().

    Semantics:
    pid > 0	- wait for single child process with process id `pid';
    pid == 0	- wait for any child process in the caller's process group;
    pid == -1	- wait for any child process;
    pid < -1	- wait for any child process in process group `-pid'.

    Currently process group waits (i.e. pid == 0 or pid < -1) are not supported.
    Also WNOHANG and WUNTRACED options are supported only on systems that
    have at least the wait3() call.
 */
 
#if !HAVE_WAIT4
static pid_t	wait_lookup __(( pid_t _(pid), WAIT_T *_(stat_loc) ));
static int	wait_store  __(( pid_t _(pid), const WAIT_T *_(stat_loc) ));
#endif

extern pid_t
#if _ANSI_DEFUN_
waitpid( pid_t pid, WAIT_T *stat_loc, int options)
#else
waitpid( pid, stat_loc, options)
pid_t	pid;
WAIT_T	*stat_loc;
int	options;
#endif
{
#if HAVE_WAIT4

    if (pid <= 0)
    {
	if (pid != -1)
	{
	    errno = EINVAL;
	    return -1;
	}
	pid = 0;	/* wait4() expects pid=0 for indiscriminate wait. */ 
    }
    return wait4( pid, stat_loc, options, NULL);

#else /* !HAVE_WAIT4 */

    pid_t   child_pid;

    if (pid <= 0 && pid != -1)
    {
	errno = EINVAL;
	return -1;
    }
    if ((child_pid = wait_lookup( pid, stat_loc)) < 0)
    {
#   if !HAVE_WAIT3
	if (options & WNOHANG)
	{
	    errno = EINVAL;
	    return -1;
	}
	while ((child_pid = wait( stat_loc)) > 0)
#   else /* HAVE_WAIT3 */
	while ((child_pid = wait3( stat_loc, options, NULL)) > 0)
#   endif /* HAVE_WAIT3 */
	{
	    if (pid == -1 || pid == child_pid)
	    {
		break;
	    }
	    if (wait_store( pid, stat_loc) != 0)
	    {
		return -1;
	    }
	}
    }
    return child_pid;

#endif /* !HAVE_WAIT4 */
}

#if !HAVE_WAIT4

/* Store status of previously-encountered waited-for children
   in an array sorted on process id (so we can do a binary search.) */

static struct wait_list
{
    struct wait_info
    {
	pid_t	wait_pid;
	WAIT_T	wait_status;
    }
    *wait_base;
    int	wait_size;
    int	wait_used;
}
waitlist;

static int
#if _ANSI_DEFUN_
wait_search( pid_t pid)
#else
wait_search( pid)
pid_t	pid;
#endif
{
    /* Binary search on `pid'. */
    int	top = 0, end = waitlist.wait_used;

    while (top < end)
    {
	int mid = (top + end) >> 1;
	struct wait_info *wp = &waitlist.wait_base[mid];

	if	(pid < wp->wait_pid)
	{
	    end = mid;
	}
	else if (pid > wp->wait_pid)
	{
	    top = mid + 1;
	}
	else /* (pid == wp->wait_pid) -- match! */
	{
	    return mid;
	}
    }
    /* No match: return (-(the location where `pid' should be inserted) - 1) */

    return (-top - 1); 
}

static pid_t
#if _ANSI_DEFUN_
wait_lookup( pid_t pid, WAIT_T *stat_loc)
#else
wait_lookup( pid, stat_loc)
pid_t	pid;
WAIT_T	*stat_loc;
#endif
{
    struct wait_info *wp = waitlist.wait_base;
    int where;

    if ((where = (pid == -1) ? waitlist.wait_used - 1 : wait_search( pid)) < 0)
    {
	return -1;
    }
    pid = wp[where].wait_pid;
    (*stat_loc) = wp[where].wait_status;

    /* Remove entry from array. */
    while (++where < waitlist.wait_used)
    {
	wp[where - 1] = wp[where];
    }
    --waitlist.wait_used;

    return pid;
}

static pid_t
#if _ANSI_DEFUN_
wait_store( pid_t pid, const WAIT_T *stat_loc)
#else
wait_store( pid, stat_loc)
pid_t	pid;
WAIT_T	*stat_loc;
#endif
{
    struct wait_info *wp = waitlist.wait_base;
    int	i, where;

    if ((where = wait_search( pid)) < 0)	/* add a new entry. */
    {
	where = -where - 1;

	/* Expand the array if necessary. */
	if (waitlist.wait_used == waitlist.wait_size)
	{
	    if (!(wp = malloc( (waitlist.wait_size + 16) * sizeof( *wp))))
	    {
		return -1;
	    }
	    /* Deliberately no realloc() since that would invalidate the
	       entries we already have when it fails... */

	    if (waitlist.wait_base != NULL)
	    {
		i = waitlist.wait_size;	/* PRE: > 0 */

		do
		{
		    --i;
		    wp[i] = waitlist.wait_base[i];
		}
		while (i);

		free( waitlist.wait_base);
	    }
	    waitlist.wait_base = wp;
	    waitlist.wait_size += 16;
	}

	/* Shift tail upward to make room for new entry. */
	for (i = waitlist.wait_used;  i > where;  --i)
	{
	    wp[i] = wp[i - 1];
	}
	++waitlist.wait_used;
    }
    wp[where].wait_pid = pid;
    wp[where].wait_status = (*stat_loc);

    return 0;
}
#endif /* !HAVE_WAIT4 */

#endif /* __POSIX__ */

/*======================================================================
 * $Log: zendutil.c,v $
 * Revision 1.12  1993/02/02  21:00:00  gerben
 * *** empty log message ***
 *
 * Revision 1.12  1993/02/02  15:41:09  tom
 * allow multiple active popen()ed streams since this was needed for
 * check_timeout(); in conneection with this, add emulation of waitpid() for
 * non-Posix systems.
 *
 * Revision 1.11  1993/01/28  18:54:26  tom
 * add popen_as(), pclose_as() for secure piping; allow setuid non-root
 * installation; add a real safecalloc(); fix typo in non-ANSI C code.
 *
 * Revision 1.10  1993/01/10  20:20:25  tom
 * add basename(), quote_for_shell(); make vzlog() more robust.
 *
 * Revision 1.9  1992/12/22  15:11:58  tom
 * revision of HAVE_STRERROR.
 *
 * Revision 1.8  1992/12/18  23:11:13  tom
 * fix some non-ANSI C discrepancies.
 *
 * Revision 1.7  1992/12/10  05:27:34  tom
 * add support for file-cleanup on signal; stale-lock cleanup; plug security
 * holes; add support for safe program execution.
 *
 *======================================================================*/

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.