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