~sircmpwn/ctools

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[PATCH] Implement nohup

Details
Message ID
<20191011075217.20477-1-chris@vittal.dev>
DKIM signature
missing
Download raw message
Patch: +212 -0
---
 doc/meson.build  |   1 +
 doc/nohup.1.scd  |  37 +++++++++++++
 meson.build      |   1 +
 src/nohup.c      | 134 +++++++++++++++++++++++++++++++++++++++++++++++
 test/meson.build |   1 +
 test/nohup       |  38 ++++++++++++++
 6 files changed, 212 insertions(+)
 create mode 100644 doc/nohup.1.scd
 create mode 100644 src/nohup.c
 create mode 100755 test/nohup

diff --git a/doc/meson.build b/doc/meson.build
index befc6df..e348e9c 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -19,6 +19,7 @@ man_files = [
	'link.1',
	'logname.1',
	'nice.1',
	'nohup.1',
	'rmdir.1',
	'sleep.1',
	'true.1',
diff --git a/doc/nohup.1.scd b/doc/nohup.1.scd
new file mode 100644
index 0000000..b357eb7
--- /dev/null
+++ b/doc/nohup.1.scd
@@ -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, unless
*nohup.out* cannot be created. In that case, _$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.
diff --git a/meson.build b/meson.build
index 15fa23d..04ec48e 100644
--- a/meson.build
+++ b/meson.build
@@ -25,6 +25,7 @@ oneshots = [
	'head',
	'logname',
	'nice', # Included in base but only effective under XSI
	'nohup',
	'rmdir',
	'true',
	'tty',
diff --git a/src/nohup.c b/src/nohup.c
new file mode 100644
index 0000000..ff4ccfe
--- /dev/null
+++ b/src/nohup.c
@@ -0,0 +1,134 @@
#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;
}
diff --git a/test/meson.build b/test/meson.build
index 822f7bb..f3c0d3b 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -15,6 +15,7 @@ test_files = [
	'head',
	'logname',
	'nice',
	'nohup',
	'rmdir',
	'sleep',
	'true',
diff --git a/test/nohup b/test/nohup
new file mode 100755
index 0000000..6f0ae44
--- /dev/null
+++ b/test/nohup
@@ -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
-- 
2.23.0
Review patch Export thread (mbox)