/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include "pkglib.h"
#include "pkglibmsgs.h"
#include "pkglocale.h"
/* global environment inherited by this process */
extern char **environ;
/* dstream.c */
extern int ds_curpartcnt;
extern int ds_close(int pkgendflg);
/*
* global internal (private) variables
*/
/* received signal count - bumped with hooked signals are caught */
static int sig_received = 0;
/*
* Name: sig_trap
* Description: hooked up to signal counts number of signals received
* Arguments: a_signo - [RO, *RO] - (int)
* Integer representing the signal received; see signal(3c)
* Returns: <void>
*/
static void
sig_trap(int a_signo)
{
sig_received++;
}
/*
* Name: pkgexecv
* Description: Asynchronously execute a package command in a separate process
* and return results - the subprocess MUST arm it's own SIGINT
* and SIGHUP signals and must return a standard package command
* exit code (see returns below)
* Only another package command (such as pkginstall, pkgremove,
* etc.) may be called via this interface. No files are closed
* because open files are passed across to certain commands using
* either implicit agreements between the two (yuk!) or by using
* the '-p' option which passes a string of digits, some of which
* represent open file descriptors passed through this interface!
* Arguments: filein - [RO, *RO] - (char *)
* Pointer to string representing the name of the file to
* use for the package commands's stdin
* == (char *)NULL or == "" - the current stdin
* is used for the new package command process
* fileout - [RO, *RO] - (char *)
* Pointer to string representing the name of the file to
* use for the package commands's stdout and stderr
* == (char *)NULL or == "" - the current stdout/stderr
* is used for the new package command process
* uname - [RO, *RO] - (char *)
* Pointer to string representing the user name to execute
* the package command as - the user name is looked up
* using the ncgrpw:cpwnam() interface
* == (char *)NULL or == "" - the user name of the current
* process is used for the new package command process
* gname - [RO, *RO] - (char *)
* Pointer to string representing the group name to execute
* the package command as - the group name is looked up
* using the ncgrpw:cgrnam() interface
* == (char *)NULL or == "" - the group name of the current
* process is used for the new package command process
* arg - [RO, *RO] - (char **)
* Pointer to array of character pointers representing the
* arguments to pass to the package command - the array is
* terminated with a pointer to (char *)NULL
* Returns: int
* == 99 - exec() of package command failed
* == -1 - fork failed or other fatal error during
* execution of the package command
* otherwise - exit code from package command:
* 0 - successful
* 1 - package operation failed (fatal error)
* 2 - non-fatal error (warning)
* 3 - operation interrupted (including SIGINT/SIGHUP)
* 4 - admin settings prevented operation
* 5 - administration required and -n was specified
* IN addition:
* 10 is added to the return code if reboot after the
* installation of all packages is required
* 20 is added to the return code if immediate reboot
* after installation of this package is required
*/
int
pkgexecv(char *filein, char *fileout, char *uname, char *gname, char *arg[])
{
int exit_no;
int n;
int status;
pid_t pid;
pid_t waitstat;
struct group *grp;
struct passwd *pwp;
struct sigaction nact;
struct sigaction oact;
void (*funcSighup)();
void (*funcSigint)();
/* flush standard i/o before creating new process */
(void) fflush(stdout);
(void) fflush(stderr);
/*
* hold SIGINT/SIGHUP signals and reset signal received counter;
* after the vfork() the parent and child need to setup their respective
* interrupt handling and release the hold on the signals
*/
(void) sighold(SIGINT);
(void) sighold(SIGHUP);
sig_received = 0;
/*
* create new process to execute command in;
* vfork() is being used to avoid duplicating the parents
* memory space - this means that the child process may
* not modify any of the parents memory including the
* standard i/o descriptors - all the child can do is
* adjust interrupts and open files as a prelude to a
* call to exec().
*/
pid = vfork();
if (pid < 0) {
/*
* *************************************************************
* fork failed!
* *************************************************************
*/
progerr(pkg_gt(ERR_FORK_FAILED), errno, strerror(errno));
/* release hold on signals */
(void) sigrelse(SIGHUP);
(void) sigrelse(SIGINT);
return (-1);
}
if (pid > 0) {
/*
* *************************************************************
* This is the forking (parent) process
* *************************************************************
*/
/* close datastream if any portion read */
if (ds_curpartcnt >= 0) {
if (ds_close(0) != 0) {
/* kill child process */
(void) sigsend(P_PID, pid, SIGKILL);
/* release hold on signals */
(void) sigrelse(SIGHUP);
(void) sigrelse(SIGINT);
return (-1);
}
}
/*
* setup signal handlers for SIGINT and SIGHUP and release hold
*/
/* hook SIGINT to sig_trap() */
nact.sa_handler = sig_trap;
nact.sa_flags = SA_RESTART;
(void) sigemptyset(&nact.sa_mask);
if (sigaction(SIGINT, &nact, &oact) < 0) {
funcSigint = SIG_DFL;
} else {
funcSigint = oact.sa_handler;
}
/* hook SIGHUP to sig_trap() */
nact.sa_handler = sig_trap;
nact.sa_flags = SA_RESTART;
(void) sigemptyset(&nact.sa_mask);
if (sigaction(SIGHUP, &nact, &oact) < 0) {
funcSighup = SIG_DFL;
} else {
funcSighup = oact.sa_handler;
}
/* release hold on signals */
(void) sigrelse(SIGHUP);
(void) sigrelse(SIGINT);
/*
* wait for the process to exit, reap child exit status
*/
for (;;) {
status = 0;
waitstat = waitpid(pid, (int *)&status, 0);
if (waitstat < 0) {
/* waitpid returned error */
if (errno == EAGAIN) {
/* try again */
continue;
}
if (errno == EINTR) {
continue;
}
/* error from waitpid: bail */
break;
} else if (waitstat == pid) {
/* child exit status available */
break;
}
}
/*
* reset signal handlers
*/
/* reset SIGINT */
nact.sa_handler = funcSigint;
nact.sa_flags = SA_RESTART;
(void) sigemptyset(&nact.sa_mask);
(void) sigaction(SIGINT, &nact, (struct sigaction *)NULL);
/* reset SIGHUP */
nact.sa_handler = funcSighup;
nact.sa_flags = SA_RESTART;
(void) sigemptyset(&nact.sa_mask);
(void) sigaction(SIGHUP, &nact, (struct sigaction *)NULL);
/* error if child process does not match */
if (waitstat != pid) {
progerr(pkg_gt(ERR_WAIT_FAILED), pid, waitstat, status,
errno, strerror(errno));
return (-1);
}
/*
* determine final exit code:
* - if signal received, then return interrupted (3)
* - if child exit status is available, return exit child status
* - otherwise return error (-1)
*/
if (sig_received != 0) {
exit_no = 3; /* interrupted */
} else if (WIFEXITED(status)) {
exit_no = WEXITSTATUS(status);
} else {
exit_no = -1; /* exec() or other process error */
}
return (exit_no);
}
/*
* *********************************************************************
* This is the forked (child) process
* *********************************************************************
*/
/* reset all signals to default */
for (n = 0; n < NSIG; n++) {
(void) sigset(n, SIG_DFL);
}
/* release hold on signals held by parent before fork() */
(void) sigrelse(SIGHUP);
(void) sigrelse(SIGINT);
/*
* The caller wants to have stdin connected to filein.
*/
if (filein && *filein) {
/*
* If input is supposed to be connected to /dev/tty
*/
if (strncmp(filein, "/dev/tty", 8) == 0) {
/*
* If stdin is connected to a tty device.
*/
if (isatty(STDIN_FILENO)) {
/*
* Reopen it to /dev/tty.
*/
n = open(filein, O_RDONLY);
if (n >= 0) {
(void) dup2(n, STDIN_FILENO);
}
}
} else {
/*
* If we did not want to be connected to /dev/tty, we
* connect input to the requested file no questions.
*/
n = open(filein, O_RDONLY);
if (n >= 0) {
(void) dup2(n, STDIN_FILENO);
}
}
}
/*
* The caller wants to have stdout and stderr connected to fileout.
* If "fileout" is "/dev/tty" then reconnect stdout to "/dev/tty"
* only if /dev/tty is not already associated with "a tty".
*/
if (fileout && *fileout) {
/*
* If output is supposed to be connected to /dev/tty
*/
if (strncmp(fileout, "/dev/tty", 8) == 0) {
/*
* If stdout is connected to a tty device.
*/
if (isatty(STDOUT_FILENO)) {
/*
* Reopen it to /dev/tty if /dev/tty available.
*/
n = open(fileout, O_WRONLY);
if (n >= 0) {
/*
* /dev/tty is available - close the
* current standard output stream, and
* reopen it on /dev/tty
*/
(void) dup2(n, STDOUT_FILENO);
}
}
/*
* not connected to tty device - probably redirect to
* file - preserve existing output device
*/
} else {
/*
* If we did not want to be connected to /dev/tty, we
* connect output to the requested file no questions.
*/
/* LINTED O_CREAT without O_EXCL specified in call to */
n = open(fileout, O_WRONLY|O_CREAT|O_APPEND, 0666);
if (n >= 0) {
(void) dup2(n, STDOUT_FILENO);
}
}
/*
* Dup stderr from stdout.
*/
(void) dup2(STDOUT_FILENO, STDERR_FILENO);
}
/*
* do NOT close all file descriptors except stdio
* file descriptors are passed in to some subcommands
* (see dstream:ds_getinfo() and dstream:ds_putinfo())
*/
/* set group/user i.d. if requested */
if (gname && *gname && (grp = cgrnam(gname)) != NULL) {
if (setgid(grp->gr_gid) == -1) {
progerr(pkg_gt(ERR_SETGID), grp->gr_gid);
}
}
if (uname && *uname && (pwp = cpwnam(uname)) != NULL) {
if (setuid(pwp->pw_uid) == -1) {
progerr(pkg_gt(ERR_SETUID), pwp->pw_uid);
}
}
/* execute target executable */
(void) execve(arg[0], arg, environ);
progerr(pkg_gt(ERR_EX_FAIL), arg[0], errno);
_exit(99);
/*NOTREACHED*/
}