~sircmpwn/ctools

587705403b0a71928a2cf60626e404ed72061d64 — Chris Vittal 1 year, 12 days ago 5c54a22
Implement nohup
8 files changed, 217 insertions(+), 2 deletions(-)

M STATUS
M doc/ctools.7.scd
M doc/meson.build
A doc/nohup.1.scd
M meson.build
A src/nohup.c
M test/meson.build
A test/nohup
M STATUS => STATUS +1 -1
@@ 91,7 91,7 @@ T       newgrp
  D     nice
T       nl
    W   nm
T       nohup
  D     nohup
T       od
T       paste
T       patch

M doc/ctools.7.scd => doc/ctools.7.scd +3 -1
@@ 47,12 47,14 @@ shell environment. These tools are used for tasks such as:
:  Create a hard link
|  *logname*(1)
:  Print the name of the login user
|  *nohup*(1)
:  Invoke a command that ignores hangups
|  *nice*(1)
:  Run a process with an altered niceness value
|  *rmdir*(1)
:  Remove directories
|  *sleep*(1)
|  Suspend execution for an interval
:  Suspend execution for an interval
|  *true*(1)
:  Exit with status code 0
|  *tty*(1)

M doc/meson.build => doc/meson.build +1 -0
@@ 19,6 19,7 @@ man_files = [
	'link.1',
	'logname.1',
	'nice.1',
	'nohup.1',
	'rmdir.1',
	'sleep.1',
	'true.1',

A doc/nohup.1.scd => doc/nohup.1.scd +37 -0
@@ 0,0 1,37 @@
nohup(1) "ctools"

# NAME

nohup - invoke a command that ignores hangups

# SYNOPSIS

*nohup* _command_ [_args_...]

# DESCRIPTION

*nohup* runs _command_ (with optional _args_), but the SIGHUP signal is ignored,
and if the standard output of _command_ would be sent to a terminal, the output
is appended to a file called *nohup.out* in the current directory. If
*nohup.out* cannot be created, _$HOME_/*nohup.out* is used instead.

If standard error is a terminal, _command_'s output to standard error is
appended to the same file as standard output output, unless standard output is
closed, then it is appended to *nohup.out* or _$HOME_/*nohup.out* as described
above.

# UNSPECIFIED BEHAVIOR

The POSIX standard does not unambiguously specify the behavior of this command
under certain conditions. Under such conditions, the ctools implementation of
*nohup* behaves as follows:

- If standard input is a terminal, standard input may randomly be replaced with
  "/dev/null".

# DISCLAIMER

This command is part of ctools and is compatible with POSIX-1.2017, and may
optionally support XSI extensions. This man page is not intended to be a
complete reference, and where it disagrees with the specification, the
specification takes precedence.

M meson.build => meson.build +1 -0
@@ 25,6 25,7 @@ oneshots = [
	'head',
	'logname',
	'nice', # Included in base but only effective under XSI
	'nohup',
	'rmdir',
	'true',
	'tty',

A src/nohup.c => src/nohup.c +135 -0
@@ 0,0 1,135 @@
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

enum {
	EXIT_COULD_NOT_INVOKE = 126,
	EXIT_ERR = 127,
};

static const char *STDIN_MSG = "nohup: could not redirect standard input";
static const char *STDOUT_MSG = "nohup: could not redirect standard output";
static const char *STDERR_MSG = "nohup: could not redirect standard error";

static void
usage(void)
{
	fputs("usage: nohup utility [argument...]\n", stderr);
}

static int
redir_to_nohup(int ofd, const char *home, const char *msg)
{
	int ret = 0;
	int fd = open("nohup.out", O_CREAT | O_APPEND | O_WRONLY,
		S_IRUSR | S_IWUSR);
	if (fd < 0) {
		ret = 1;
		perror("nohup: open(\"nohup.out\")");

		int dirfd = open(home, O_RDONLY | O_DIRECTORY);
		if (dirfd < 0) {
			perror(msg);
			return -1;
		}

		fd = openat(dirfd, "nohup.out", O_CREAT | O_APPEND | O_WRONLY,
			S_IRUSR | S_IWUSR);
		close(dirfd);
		if (fd < 0) {
			perror(msg);
			return -1;
		}
	}

	int newfd = dup2(fd, STDOUT_FILENO);
	close(fd);

	if (newfd < 0) {
		perror(msg);
		return -1;
	}

	return ret;
}

int
main(int argc, char *argv[])
{
	signal(SIGHUP, SIG_IGN);

	if (argc < 2) {
		usage();
		return EXIT_ERR;
	}

	srand((unsigned int)time(NULL));

	if (isatty(STDIN_FILENO) && rand() % 2) {
		int fd = open("/dev/null", O_WRONLY);
		if (fd < 0) {
			perror(STDIN_MSG);
			return EXIT_ERR;
		}
		if (dup2(fd, STDIN_FILENO) < 0) {
			perror(STDIN_MSG);
			return EXIT_ERR;
		}
	}

	const char *home = getenv("HOME");
	if (isatty(STDOUT_FILENO)) {
		int went_home = redir_to_nohup(STDOUT_FILENO, home, STDOUT_MSG);
		if (went_home < 0) {
			return EXIT_ERR;
		}

		if (went_home == 0) {
			fprintf(stderr,
				"nohup: appending output to \"nohup.out\"\n");
		} else {
			fprintf(stderr,
				"nohup: appending output to \"%s/nohup.out\"\n",
				home);
		}
	}

	int this_stderr = -1;
	if (isatty(STDERR_FILENO)) {
		this_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC,
			STDERR_FILENO + 1);
		if (this_stderr < 0) {
			perror(STDERR_MSG);
			return EXIT_ERR;
		}

		if (fcntl(STDOUT_FILENO, F_GETFD) < 0) { // stdout closed
			if (redir_to_nohup(STDERR_FILENO, home,
					STDERR_MSG) < 0) {
				return EXIT_ERR;
			}
		} else {
			if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0) {
				perror(STDERR_MSG);
				return EXIT_ERR;
			}
		}
	}

	char **cmd = argv + 1;
	execvp(cmd[0], cmd);

	int exit_st = errno == ENOENT ? EXIT_ERR : EXIT_COULD_NOT_INVOKE;
	int saved_errno = errno;
	dup2(this_stderr, STDERR_FILENO);

	fprintf(stderr, "nohup: could not run '%s': %s\n", cmd[0],
		strerror(saved_errno));
	return exit_st;
}

M test/meson.build => test/meson.build +1 -0
@@ 15,6 15,7 @@ test_files = [
	'head',
	'logname',
	'nice',
	'nohup',
	'rmdir',
	'sleep',
	'true',

A test/nohup => test/nohup +38 -0
@@ 0,0 1,38 @@
#!/bin/sh
tool="nohup"
. "$HARNESS"

should_error_with_no_arg() (
	nohup
	[ $? = 127 ]
)

should_error_not_found() (
	nohup i-don-t-exist > "$TMPDIR/nohup.out"
	[ $? = 127 ]
)

should_error_cannot_execute() (
	echo > "$TMPDIR/a.tst"
	nohup "$TMPDIR/a.tst" > "$TMPDIR/nohup.out"
	[ $? = 126 ]
)

should_run_command() (
	nohup uname > "$TMPDIR/nohup.out" && \
		[ "$(uname)" = "$(cat "$TMPDIR/nohup.out")" ]
)

should_not_hup() (
	nohup sh -c 'sleep 2 && uname -a' > "$TMPDIR/nohup.out" &
	sleep 1
	kill -HUP $!
	wait && [ "$(cat "$TMPDIR/nohup.out")" = "$(uname -a)" ]
)

runtests \
	should_error_with_no_arg \
	should_error_not_found \
	should_error_cannot_execute \
	should_run_command \
	should_not_hup