OpenSolaris_b135/cmd/nohup/nohup.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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	*/

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <stdlib.h>
#include <nl_types.h>
#include <locale.h>
#include <signal.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <libproc.h>
#include <dirent.h>
#include <ctype.h>
#include <sys/time.h>

#define	NOHUP_PERM	(S_IRUSR | S_IWUSR)

#define	NOHUP_NOEXEC	126
#define	NOHUP_ERROR	127

#ifdef XPG4
#define	OPTSTR	""
#else
#define	OPTSTR	"pFag"

static int pnohup(int, char **);

static struct ps_prochandle *g_proc;
static int g_wrfd;
static int g_rdfd;

static int g_dirty;
static volatile int g_interrupt = 0;
#endif

static int opt_p = 0;
static int opt_g = 0;
static int opt_a = 0;
static int opt_F = 0;

static char *pname;

static char nout[PATH_MAX] = "nohup.out";

static int
open_file(void)
{
	char *home;
	int fd;
	int flags = O_CREAT | O_WRONLY | O_APPEND;

	if ((fd = open(nout, flags, NOHUP_PERM)) < 0) {
		if ((home = getenv("HOME")) == NULL)
			return (-1);

		if ((snprintf(nout, sizeof (nout),
		    "%s/nohup.out", home) >= sizeof (nout)) ||
		    (fd = open(nout, flags, NOHUP_PERM)) < 0) {
			return (-1);
		}

	}

	(void) fprintf(stderr, gettext("Sending output to %s\n"), nout);

	return (fd);
}

int
main(int argc, char **argv)
{
	int fd = -1;
	int opt;
	int err;

	if ((pname = strrchr(argv[0], '/')) == NULL)
		pname = argv[0];
	else
		argv[0] = ++pname;		/* for getopt */

	(void) setlocale(LC_ALL, "");

#ifndef TEXT_DOMAIN
#define	TEXT_DOMAIN "SYS_TEST"
#endif

	(void) textdomain(TEXT_DOMAIN);

	while ((opt = getopt(argc, argv, OPTSTR)) != EOF) {
		switch (opt) {
		case 'p':
			opt_p = 1;
			break;
		case 'F':
			opt_F = 1;
			break;
		case 'a':
			opt_a = 1;
			break;
		case 'g':
			opt_g = 1;
			break;
		default:
			goto usage;
		}
	}

	argc -= optind;
	argv += optind;

	if (argc == 0)
		goto usage;			/* need at least one argument */

#ifndef XPG4
	if (opt_p && opt_g)
		goto usage;

	if (opt_p || opt_g)
		return (pnohup(argc, argv));

	if (opt_a || opt_F)
		goto usage;			/* only valid with -p or -g */
#endif

	argv[argc] = NULL;

	(void) signal(SIGHUP, SIG_IGN);		/* POSIX.2 only SIGHUP */
#ifndef XPG4
	(void) signal(SIGQUIT, SIG_IGN);	/* Solaris compatibility */
#endif

	if (isatty(STDOUT_FILENO)) {
		if ((fd = open_file()) < 0)
			goto err;

		(void) dup2(fd, STDOUT_FILENO);
	}

	if (isatty(STDERR_FILENO)) {
		if (fd < 0 && (fd = open_file()) < 0)
			goto err;

		(void) dup2(fd, STDERR_FILENO);
	}

	if (fd >= 0)
		(void) close(fd);

	(void) execvp(argv[0], argv);
	err = errno;

	(void) freopen("/dev/tty", "w", stderr);
	(void) fprintf(stderr, gettext("nohup: %s: %s\n"), argv[0],
	    strerror(err));

	return (err == ENOENT ? NOHUP_ERROR : NOHUP_NOEXEC);

err:
	(void) fprintf(stderr, gettext("nohup: cannot open/create "
	    "nohup.out: %s\n"), strerror(errno));
	return (NOHUP_ERROR);

usage:
#ifdef XPG4
	(void) fprintf(stderr,
	    gettext("usage: nohup command [argument ...]\n"));
#else
	(void) fprintf(stderr, gettext("usage:\n"
	    "\tnohup command [argument ...]\n"
	    "\tnohup -p [-Fa] pid [pid ...]\n"
	    "\tnohup -g [-Fa] pgid [pgid ...]\n"));
#endif
	return (NOHUP_ERROR);
}

#ifndef XPG4

/*
 * File descriptor iteration interface.
 */
typedef int proc_fd_iter_f(void *, int);

static int
Pfd_iter(struct ps_prochandle *P, proc_fd_iter_f *cb, void *data)
{
	char file[64];
	dirent_t *dentp;
	DIR *dirp;
	int ret = 0;

	if (Pstate(P) == PS_DEAD)
		return (-1);

	(void) sprintf(file, "/proc/%d/fd", (int)Pstatus(P)->pr_pid);
	if ((dirp = opendir(file)) == NULL)
		return (-1);

	while ((dentp = readdir(dirp)) != NULL) {
		if (dentp->d_name[0] == '.')
			continue;

		if ((ret = cb(data, atoi(dentp->d_name))) != 0)
			break;
	}

	(void) closedir(dirp);

	return (ret);
}

/*ARGSUSED*/
static int
fd_cb(void *data, int fd)
{
	struct stat64 sbuf;
	int flags;
	int *fdp;
	int oflags;
	char *file;
	int tmpfd;

	/*
	 * See if this fd refers to the controlling tty.
	 */
	if (pr_fstat64(g_proc, fd, &sbuf) == -1 ||
	    sbuf.st_rdev != Ppsinfo(g_proc)->pr_ttydev)
		return (0);

	/*
	 * tty's opened for input are usually O_RDWR so that the program
	 * can change terminal settings. We assume that if there's a
	 * controlling tty in the STDIN_FILENO file descriptor that is
	 * effectively used only for input. If standard in gets dup'ed to
	 * other file descriptors, then we're out of luck unless the
	 * program is nice enough to fcntl it to be O_RDONLY. We close the
	 * file descriptor before we call open to handle the case that
	 * there are no available file descriptors left in the victim. If
	 * our call to pr_open fails, we try to reopen the controlling tty.
	 */
	flags = pr_fcntl(g_proc, fd, F_GETFL, NULL);
	if ((flags & O_ACCMODE) == O_RDONLY || fd == STDIN_FILENO) {
		fdp = &g_rdfd;
		oflags = O_RDONLY;
		file = "/dev/null";
	} else {
		fdp = &g_wrfd;
		oflags = O_RDWR | O_APPEND;
		file = &nout[0];
	}

	if (*fdp < 0) {
		(void) pr_close(g_proc, fd);

		tmpfd = pr_open(g_proc, file, oflags, 0);

		if (tmpfd < 0) {
			(void) fprintf(stderr,
			    gettext("nohup: process %d cannot open %s: %s\n"),
			    Pstatus(g_proc)->pr_pid, file, strerror(errno));

			goto err;
		}

		if (tmpfd != fd) {
			(void) pr_fcntl(g_proc, tmpfd, F_DUP2FD,
				(void *)(uintptr_t)fd);
			(void) pr_close(g_proc, tmpfd);
		}

		*fdp = fd;
	} else {
		(void) pr_fcntl(g_proc, *fdp, F_DUP2FD, (void *)(uintptr_t)fd);
	}

	return (0);

err:
	/*
	 * The victim couldn't open nohup.out so we'll have it try to reopen
	 * its terminal. If this fails, we are left with little recourse.
	 */
	tmpfd = pr_open(g_proc, "/dev/tty", O_RDWR, 0);

	if (tmpfd != fd && tmpfd >= 0) {
		(void) pr_fcntl(g_proc, tmpfd, F_DUP2FD, (void *)(uintptr_t)fd);
		(void) pr_close(g_proc, tmpfd);
	}

	return (1);
}

static int
lwp_restartable(short syscall)
{
	switch (syscall) {
	case SYS_read:
	case SYS_readv:
	case SYS_pread:
	case SYS_pread64:
	case SYS_write:
	case SYS_writev:
	case SYS_pwrite:
	case SYS_pwrite64:
	case SYS_ioctl:
	case SYS_fcntl:
	case SYS_getmsg:
	case SYS_getpmsg:
	case SYS_putmsg:
	case SYS_putpmsg:
	case SYS_recv:
	case SYS_recvmsg:
	case SYS_recvfrom:
	case SYS_send:
	case SYS_sendmsg:
	case SYS_sendto:
		return (1);
	}

	return (0);
}

/*ARGSUSED*/
static int
lwp_abort(void *data, const lwpstatus_t *lsp)
{
	struct ps_lwphandle *L;
	int err;

	/*
	 * Continue if this lwp isn't asleep in a restartable syscall.
	 */
	if (!(lsp->pr_flags & PR_ASLEEP) || !lwp_restartable(lsp->pr_syscall))
		return (0);

	L = Lgrab(g_proc, lsp->pr_lwpid, &err);
	(void) Lsetrun(L, 0, PRSABORT);
	Lfree(L);

	/*
	 * Indicate that we have aborted a syscall.
	 */
	g_dirty = 1;

	return (0);
}

/*ARGSUSED*/
static int
lwp_restart(void *data, const lwpstatus_t *lsp)
{
	struct ps_lwphandle *L;
	int err;

	/*
	 * If any lwp is still sleeping in a restartable syscall, it means
	 * the lwp is wedged and we've screwed up.
	 */
	if (lsp->pr_flags & PR_ASLEEP) {
		if (!lwp_restartable(lsp->pr_syscall))
			return (0);
		(void) fprintf(stderr, gettext("nohup: LWP %d failed "
		    "to abort syscall (%d) in process %d\n"),
		    lsp->pr_lwpid, lsp->pr_syscall, Pstatus(g_proc)->pr_pid);
		return (1);
	}

	if (lsp->pr_why == PR_SYSEXIT && lsp->pr_errno == EINTR) {
		L = Lgrab(g_proc, lsp->pr_lwpid, &err);
		(void) Lputareg(L, R_R0, ERESTART);
		Lsync(L);
		Lfree(L);
	}

	return (0);
}

static int
do_pnohup(struct ps_prochandle *P)
{
	int sig = 0;
	struct sigaction sa;
	const pstatus_t *psp;

	psp = Pstatus(P);

	/*
	 * Make sure there's a pending procfs stop directive.
	 */
	(void) Pdstop(P);

	if (Pcreate_agent(P) != 0) {
		(void) fprintf(stderr, gettext("nohup: cannot control "
		    "process %d\n"), psp->pr_pid);
		goto err_no_agent;
	}

	/*
	 * Set the disposition of SIGHUP and SIGQUIT to SIG_IGN. If either
	 * signal is handled by the victim, only adjust the disposition if
	 * the -a flag is set.
	 */
	if (!opt_a && pr_sigaction(P, SIGHUP, NULL, &sa) != 0) {
		(void) fprintf(stderr, gettext("nohup: cannot read "
		    "disposition of SIGHUP for %d\n"), psp->pr_pid);
		goto no_sigs;
	}

	if (!opt_a && sa.sa_handler != SIG_DFL && sa.sa_handler != SIG_IGN) {
		(void) fprintf(stderr, gettext("nohup: SIGHUP already handled "
		    "by %d; use -a to force process to ignore\n"), psp->pr_pid);
		goto no_sigs;
	}

	if (!opt_a && pr_sigaction(P, SIGQUIT, NULL, &sa) != 0) {
		(void) fprintf(stderr, gettext("nohup: cannot read "
		    "disposition of SIGQUIT for %d\n"), psp->pr_pid);
		goto no_sigs;
	}

	if (!opt_a && sa.sa_handler != SIG_DFL && sa.sa_handler != SIG_IGN) {
		(void) fprintf(stderr, gettext("nohup: SIGQUIT already handled "
		    "by %d; use -a to force process to ignore\n"), psp->pr_pid);
		goto no_sigs;
	}

	sa.sa_handler = SIG_IGN;

	if (pr_sigaction(P, SIGHUP, &sa, NULL) != 0) {
		(void) fprintf(stderr, gettext("nohup: cannot set "
		    "disposition of SIGHUP for %d\n"), psp->pr_pid);
		goto no_sigs;
	}

	if (pr_sigaction(P, SIGQUIT, &sa, NULL) != 0) {
		(void) fprintf(stderr, gettext("nohup: cannot set "
		    "disposition of SIGQUIT for %d\n"), psp->pr_pid);
		goto no_sigs;
	}

no_sigs:
	Pdestroy_agent(P);

	/*
	 * We need to close and reassign some file descriptors, but we
	 * need to be careful about how we do it. If we send in the agent
	 * to close some fd and there's an lwp asleep in the kernel due to
	 * a syscall using that fd, then we have a problem. The normal
	 * sequence of events is the close syscall wakes up any threads
	 * that have the fd in question active (see kthread.t_activefd)
	 * and then waits for those threads to wake up and release the
	 * file descriptors (they then continue to user-land to return
	 * EBADF from the syscall). However, recall that if the agent lwp
	 * is present in a process, no other lwps can run, so if the agent
	 * lwp itself is making the call to close(2) (or something else
	 * like dup2 that involves a call to closeandsetf()) then we're in
	 * pretty bad shape. The solution is to abort and restart any lwp
	 * asleep in a syscall on the off chance that it may be using one
	 * of the file descriptors that we want to manipulate.
	 */

	/*
	 * We may need to chase some lwps out of the kernel briefly, so we
	 * send SIGCONT to the process if it was previously stopped due to
	 * a job control signal, and save the current signal to repost it
	 * when we detatch from the victim. A process that is stopped due
	 * to job control will start running as soon as we send SIGCONT
	 * since there is no procfs stop command pending; we use Pdstop to
	 * post a procfs stop request (above).
	 */
	if ((psp->pr_lwp.pr_flags & PR_STOPPED) &&
	    psp->pr_lwp.pr_why == PR_JOBCONTROL) {
		sig = psp->pr_lwp.pr_what;
		(void) kill(psp->pr_pid, SIGCONT);
		(void) Pwait(P, 0);
	}

	(void) Psysexit(P, 0, 1);

	/*
	 * Abort each syscall; set g_dirty if any lwp was asleep.
	 */
	g_dirty = 0;
	g_proc = P;
	(void) Plwp_iter(P, lwp_abort, NULL);

	if (g_dirty) {
		/*
		 * Block until each lwp that was asleep in a syscall has
		 * wandered back up to user-land.
		 */
		(void) Pwait(P, 0);

		/*
		 * Make sure that each lwp has successfully aborted its
		 * syscall and that the syscall gets restarted when we
		 * detach later.
		 */
		if (Plwp_iter(P, lwp_restart, NULL) != 0)
			goto err_no_agent;
	}

	(void) Psysexit(P, 0, 0);

	if (Pcreate_agent(P) != 0) {
		(void) fprintf(stderr, gettext("nohup: cannot control "
		    "process %d\n"), psp->pr_pid);
		goto err_no_agent;
	}

	/*
	 * See if the victim has access to the nohup.out file we created.
	 * If the user does something that would invalidate the result
	 * of this call from here until the call to pr_open, the process
	 * may be left in an inconsistent state -- we assume that the user
	 * is not intentionally trying to shoot himself in the foot.
	 */
	if (pr_access(P, nout, R_OK | W_OK) != 0) {
		(void) fprintf(stderr, gettext("nohup: process %d can not "
		    "access %s: %s\n"), psp->pr_pid, nout, strerror(errno));
		goto err_agent;
	}

	/*
	 * Redirect output to the controlling tty to nohup.out and tty
	 * input to read from /dev/null.
	 */

	g_wrfd = -1;
	g_rdfd = -1;

	(void) Pfd_iter(P, fd_cb, NULL);

	Pdestroy_agent(P);
	if (sig != 0)
		(void) kill(psp->pr_pid, sig);

	return (0);

err_agent:
	Pdestroy_agent(P);
err_no_agent:
	if (sig != 0)
		(void) kill(psp->pr_pid, sig);
	return (-1);
}

/*ARGSUSED*/
static void
intr(int sig)
{
	g_interrupt = 1;
}

static int
pnohup(int argc, char **argv)
{
	struct ps_prochandle *P;
	int i, j;
	int flag = 0;
	int gcode;
	int nh_fd = -1;
	char *fname;
	char *home;
	int nerrs = 0;

	/*
	 * Catch signals from the terminal.
	 */
	if (sigset(SIGHUP, SIG_IGN) == SIG_DFL)
		(void) sigset(SIGHUP, intr);
	if (sigset(SIGINT, SIG_IGN) == SIG_DFL)
		(void) sigset(SIGINT, intr);
	if (sigset(SIGQUIT, SIG_IGN) == SIG_DFL)
		(void) sigset(SIGQUIT, intr);
	(void) sigset(SIGPIPE, intr);
	(void) sigset(SIGTERM, intr);

	if (opt_F)
		flag |= PGRAB_FORCE;

	/*
	 * Set nout to be the full path name of nohup.out and fname to be
	 * the simplified path name:
	 * nout = /cwd/nohup.out	fname = nohup.out
	 * nout = $HOME/nohup.out	fname = $HOME/nohup.out
	 */
	if (getcwd(nout, sizeof (nout) - strlen("/nohup.out") - 1) != NULL) {
		fname = &nout[strlen(nout)];
		(void) strcpy(fname, "/nohup.out");
		fname++;

		nh_fd = open(nout, O_WRONLY | O_CREAT, NOHUP_PERM);
	}

	if (nh_fd == -1 && (home = getenv("HOME")) != NULL) {
		if (snprintf(nout, sizeof (nout),
		    "%s/nohup.out", home) < sizeof (nout)) {
			nh_fd = open(nout, O_WRONLY | O_CREAT, NOHUP_PERM);
			fname = &nout[0];
		}
	}

	if (nh_fd == -1) {
		(void) fprintf(stderr, gettext("nohup: cannot open/create "
		    "nohup.out: %s\n"), strerror(errno));

		return (NOHUP_ERROR);
	}

	if (opt_g) {
		pid_t *pgids;
		int npgids;
		int success;

		/*
		 * Make nohup its own process group leader so that we
		 * don't accidently send SIGSTOP to this process.
		 */
		(void) setpgid(0, 0);

		/*
		 * If a list of process group ids is specified, we want to
		 * first SIGSTOP the whole process group so that we can be
		 * sure not to miss any processes that belong to the group
		 * (it's harder to hit a moving target). We then iterate
		 * over all the processes on the system looking for
		 * members of the given process group to apply the
		 * do_pnohup function to. If the process was stopped due
		 * to our SIGSTOP, we send the process SIGCONT; if the
		 * process was already stopped, we leave it alone.
		 */
		pgids = calloc(argc, sizeof (pid_t));
		pgids[0] = getpid();
		npgids = 1;

		for (i = 0; i < argc; i++) {
			dirent_t *dent;
			DIR *dirp;
			psinfo_t psinfo;
			const pstatus_t *psp;
			pid_t pgid;
			char *end;
			hrtime_t kill_time, stop_time;

			if (isdigit(*argv[i])) {
				pgid = strtol(argv[i], &end, 10);

				/*
				 * kill(2) with pid = 0 or -1 has a special
				 * meaning, so don't let pgid be 0 or 1.
				 */
				if (*end == '\0' && pgid > 1)
					goto pgid_ok;
			}

			(void) fprintf(stderr, gettext("nohup: "
			    "bad process group %s\n"), argv[i]);
			nerrs++;
			continue;

pgid_ok:
			/*
			 * We don't want to nohup a process group twice.
			 */
			for (j = 0; j < npgids; j++) {
				if (pgids[j] == pgid)
					break;
			}

			if (j != npgids)
				continue;

			pgids[npgids++] = pgid;

			/*
			 * Have the kernel stop all members of the process
			 * group; record the time we stopped the process
			 * group so that we can tell if a member stopped
			 * because of this call to kill(2) or if it was
			 * already stopped when we got here. If the user
			 * job control stops the victim between the call
			 * to gethrtime(2) and kill(2), we may send
			 * SIGCONT when we really shouldn't -- we assume
			 * that the user is not trying to shoot himself in
			 * the foot.
			 */
			kill_time = gethrtime();
			if (kill(-pgid, SIGSTOP) == -1) {
				(void) fprintf(stderr, gettext("nohup: cannot "
				    "stop process group %d: %s\n"), pgid,
				    errno != ESRCH ? strerror(errno) :
				    gettext("No such process group"));

				nerrs++;
				continue;
			}

			dirp = opendir("/proc");
			success = 0;
			while ((dent = readdir(dirp)) != NULL && !g_interrupt) {
				if (dent->d_name[0] == '.')
					continue;

				if (proc_arg_psinfo(dent->d_name,
				    PR_ARG_PIDS, &psinfo, &gcode) == -1)
					continue;

				if (psinfo.pr_pgid != pgid)
					continue;

				/*
				 * Ignore zombies.
				 */
				if (psinfo.pr_nlwp == 0)
					continue;

				if ((P = proc_arg_grab(dent->d_name,
				    PR_ARG_PIDS, flag, &gcode)) == NULL) {
					(void) fprintf(stderr, gettext("nohup: "
					    "cannot examine %s: %s\n"),
					    dent->d_name, Pgrab_error(gcode));

					(void) kill(psinfo.pr_pid, SIGCONT);
					continue;
				}

				/*
				 * This implicitly restarts any process that
				 * was stopped via job control any time after
				 * the call to kill(2). This is the desired
				 * behavior since nohup is busy trying to
				 * disassociate a process from its controlling
				 * terminal.
				 */
				psp = Pstatus(P);
				if (psp->pr_lwp.pr_why == PR_JOBCONTROL) {
					stop_time =
					    psp->pr_lwp.pr_tstamp.tv_sec;
					stop_time *= (hrtime_t)NANOSEC;
					stop_time +=
					    psp->pr_lwp.pr_tstamp.tv_nsec;
				} else {
					stop_time = 0;
				}

				if (do_pnohup(P) == 0)
					success = 1;

				/*
				 * If the process was stopped because of
				 * our call to kill(2) (i.e. if it stopped
				 * some time after kill_time) then restart
				 * the process.
				 */
				if (kill_time <= stop_time)
					(void) kill(psinfo.pr_pid, SIGCONT);

				Prelease(P, 0);
			}

			/*
			 * If we didn't successfully nohup any member of the
			 * process group.
			 */
			if (!success)
				nerrs++;

			(void) closedir(dirp);
		}
	} else {
		for (i = 0; i < argc && !g_interrupt; i++) {
			if ((P = proc_arg_grab(argv[i], PR_ARG_PIDS, flag,
			    &gcode)) == NULL) {
				(void) fprintf(stderr,
				    gettext("nohup: cannot examine %s: %s\n"),
				    argv[i], Pgrab_error(gcode));

				nerrs++;
				continue;
			}

			if (do_pnohup(P) != 0)
				nerrs++;

			Prelease(P, 0);
		}
	}

	(void) close(nh_fd);

	if (argc == nerrs)
		return (NOHUP_ERROR);

	(void) fprintf(stderr, gettext("Sending output to %s\n"), fname);

	return (0);
}

#endif /* !XPG4 */