root/usr/src/cmd/nohup/nohup.c
/*
 * 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   */

#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, 0);
        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, 0);
                        (void) pr_close(g_proc, tmpfd);
                }

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

        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,
                    0);
                (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(3C) 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 */