diff --git a/Userland/Utilities/nohup.cpp b/Userland/Utilities/nohup.cpp new file mode 100644 index 0000000000..fda664965e --- /dev/null +++ b/Userland/Utilities/nohup.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace { + +void dup_out_file(int fd_to_redirect) +{ + int fd = -1; + DeprecatedString path = "nohup.out"; + auto options = O_CREAT | O_WRONLY | O_APPEND; + auto mode = S_IRUSR | S_IWUSR; + auto fd_or_error = Core::System::open(path, options, mode); + + if (fd_or_error.is_error()) { + auto* home_env = getenv("HOME"); + if (!home_env) { + warnln("nohup: unable to open for appending as $HOME is not set"); + exit(127); + } + + path = LexicalPath::join(LexicalPath::canonicalized_path(home_env), path).string(); + fd_or_error = Core::System::open(path, options, mode); + if (fd_or_error.is_error()) { + warnln("nohup: unable to open {} for appending: {}", path, strerror(fd_or_error.error().code())); + exit(127); + } + } + + fd = fd_or_error.value(); + + auto maybe_error = Core::System::dup2(fd, fd_to_redirect); + if (maybe_error.is_error()) { + warnln("nohup: redirection failed: {}", strerror(maybe_error.error().code())); + exit(127); + } + + if (fd_to_redirect != STDERR_FILENO) + outln(stderr, "appending output to {}", path); +} + +} + +ErrorOr serenity_main(Main::Arguments arguments) +{ + TRY(Core::System::pledge("stdio wpath cpath rpath exec sigaction")); + + StringView utility; + Vector args; + + Core::ArgsParser args_parser; + args_parser.set_stop_on_first_non_option(true); + args_parser.set_general_help("Invoke a utility that will ignore SIGHUPs"); + args_parser.add_positional_argument(utility, "Utility to be invoked", "utility"); + args_parser.add_positional_argument(args, "Arguments to pass to utility", "args", Core::ArgsParser::Required::No); + args_parser.parse(arguments); + + auto stdout_is_a_tty = false; + auto tty_or_error = Core::System::isatty(STDOUT_FILENO); + if (tty_or_error.is_error()) { + auto error = tty_or_error.release_error(); + if (error.code() != EBADF) { + warnln("nohup: error while performing tty check on stdout: {}", strerror(error.code())); + return 127; + } + } else { + stdout_is_a_tty = tty_or_error.value(); + } + + if (stdout_is_a_tty) + dup_out_file(STDOUT_FILENO); + + auto stderr_is_a_tty = false; + tty_or_error = Core::System::isatty(STDERR_FILENO); + if (tty_or_error.is_error()) { + auto error = tty_or_error.release_error(); + if (error.code() != EBADF) { + warnln("nohup: error while performing tty check on stderr: {}", strerror(error.code())); + return 127; + } + } else { + stderr_is_a_tty = tty_or_error.value(); + } + + if (stderr_is_a_tty) { + auto dup_or_error = Core::System::dup2(STDOUT_FILENO, STDERR_FILENO); + if (dup_or_error.is_error()) { + auto error = dup_or_error.release_error(); + if (error.code() != EBADF) { + warnln("nohup: error redirecting stderr to stdout: {}", strerror(error.code())); + return 127; + } + // NOTE: Standard output must be closed, so "...the same output shall + // instead be appended to the end of the nohup.out file..." + dup_out_file(STDERR_FILENO); + } + } + + TRY(Core::System::signal(SIGHUP, SIG_IGN)); + + TRY(args.try_prepend(utility)); + auto exec_or_error = Core::System::exec(utility, args.span(), Core::System::SearchInPath::Yes); + if (exec_or_error.is_error()) { + auto error = exec_or_error.release_error(); + warnln("nohup: error while calling exec: {}", strerror(error.code())); + return error.code() == ENOENT ? 127 : 126; + } + + return 0; +}