TODO: add an item for a chmod optimization
[platform/upstream/coreutils.git] / src / nohup.c
1 /* nohup -- run a command immune to hangups, with output to a non-tty
2    Copyright (C) 2003, 2004, 2005, 2007-2008 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17 /* Written by Jim Meyering  */
18
19 #include <config.h>
20 #include <getopt.h>
21 #include <stdio.h>
22 #include <sys/types.h>
23 #include <signal.h>
24
25 #include "system.h"
26
27 #include "cloexec.h"
28 #include "error.h"
29 #include "filenamecat.h"
30 #include "fd-reopen.h"
31 #include "long-options.h"
32 #include "quote.h"
33 #include "unistd--.h"
34
35 #define PROGRAM_NAME "nohup"
36
37 #define AUTHORS proper_name ("Jim Meyering")
38
39 /* Exit statuses.  */
40 enum
41   {
42     /* `nohup' itself failed.  */
43     NOHUP_FAILURE = 127
44   };
45
46 void
47 usage (int status)
48 {
49   if (status != EXIT_SUCCESS)
50     fprintf (stderr, _("Try `%s --help' for more information.\n"),
51              program_name);
52   else
53     {
54       printf (_("\
55 Usage: %s COMMAND [ARG]...\n\
56   or:  %s OPTION\n\
57 "),
58               program_name, program_name);
59
60       fputs (_("\
61 Run COMMAND, ignoring hangup signals.\n\
62 \n\
63 "), stdout);
64       fputs (HELP_OPTION_DESCRIPTION, stdout);
65       fputs (VERSION_OPTION_DESCRIPTION, stdout);
66       printf (_("\n\
67 If standard input is a terminal, redirect it from /dev/null.\n\
68 If standard output is a terminal, append output to `nohup.out' if possible,\n\
69 `$HOME/nohup.out' otherwise.\n\
70 If standard error is a terminal, redirect it to standard output.\n\
71 To save output to FILE, use `%s COMMAND > FILE'.\n"),
72               program_name);
73       printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
74       emit_bug_reporting_address ();
75     }
76   exit (status);
77 }
78
79 int
80 main (int argc, char **argv)
81 {
82   int out_fd = STDOUT_FILENO;
83   int saved_stderr_fd = STDERR_FILENO;
84   bool ignoring_input;
85   bool redirecting_stdout;
86   bool stdout_is_closed;
87   bool redirecting_stderr;
88
89   initialize_main (&argc, &argv);
90   set_program_name (argv[0]);
91   setlocale (LC_ALL, "");
92   bindtextdomain (PACKAGE, LOCALEDIR);
93   textdomain (PACKAGE);
94
95   initialize_exit_failure (NOHUP_FAILURE);
96   atexit (close_stdout);
97
98   parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version,
99                       usage, AUTHORS, (char const *) NULL);
100   if (getopt_long (argc, argv, "+", NULL, NULL) != -1)
101     usage (NOHUP_FAILURE);
102
103   if (argc <= optind)
104     {
105       error (0, 0, _("missing operand"));
106       usage (NOHUP_FAILURE);
107     }
108
109   ignoring_input = isatty (STDIN_FILENO);
110   redirecting_stdout = isatty (STDOUT_FILENO);
111   stdout_is_closed = (!redirecting_stdout && errno == EBADF);
112   redirecting_stderr = isatty (STDERR_FILENO);
113
114   /* If standard input is a tty, replace it with /dev/null if possible.
115      Note that it is deliberately opened for *writing*,
116      to ensure any read evokes an error.  */
117   if (ignoring_input)
118     {
119       fd_reopen (STDIN_FILENO, "/dev/null", O_WRONLY, 0);
120       if (!redirecting_stdout && !redirecting_stderr)
121         error (0, 0, _("ignoring input"));
122     }
123
124   /* If standard output is a tty, redirect it (appending) to a file.
125      First try nohup.out, then $HOME/nohup.out.  If standard error is
126      a tty and standard output is closed, open nohup.out or
127      $HOME/nohup.out without redirecting anything.  */
128   if (redirecting_stdout || (redirecting_stderr && stdout_is_closed))
129     {
130       char *in_home = NULL;
131       char const *file = "nohup.out";
132       int flags = O_CREAT | O_WRONLY | O_APPEND;
133       mode_t mode = S_IRUSR | S_IWUSR;
134       mode_t umask_value = umask (~mode);
135       out_fd = (redirecting_stdout
136                 ? fd_reopen (STDOUT_FILENO, file, flags, mode)
137                 : open (file, flags, mode));
138
139       if (out_fd < 0)
140         {
141           int saved_errno = errno;
142           char const *home = getenv ("HOME");
143           if (home)
144             {
145               in_home = file_name_concat (home, file, NULL);
146               out_fd = (redirecting_stdout
147                         ? fd_reopen (STDOUT_FILENO, in_home, flags, mode)
148                         : open (in_home, flags, mode));
149             }
150           if (out_fd < 0)
151             {
152               int saved_errno2 = errno;
153               error (0, saved_errno, _("failed to open %s"), quote (file));
154               if (in_home)
155                 error (0, saved_errno2, _("failed to open %s"),
156                        quote (in_home));
157               exit (NOHUP_FAILURE);
158             }
159           file = in_home;
160         }
161
162       umask (umask_value);
163       error (0, 0,
164              _(ignoring_input
165                ? "ignoring input and appending output to %s"
166                : "appending output to %s"),
167              quote (file));
168       free (in_home);
169     }
170
171   /* If standard error is a tty, redirect it.  */
172   if (redirecting_stderr)
173     {
174       /* Save a copy of stderr before redirecting, so we can use the original
175          if execve fails.  It's no big deal if this dup fails.  It might
176          not change anything, and at worst, it'll lead to suppression of
177          the post-failed-execve diagnostic.  */
178       saved_stderr_fd = dup (STDERR_FILENO);
179
180       if (0 <= saved_stderr_fd
181           && set_cloexec_flag (saved_stderr_fd, true) != 0)
182         error (NOHUP_FAILURE, errno,
183                _("failed to set the copy of stderr to close on exec"));
184
185       if (!redirecting_stdout)
186         error (0, 0,
187                _(ignoring_input
188                  ? "ignoring input and redirecting stderr to stdout"
189                  : "redirecting stderr to stdout"));
190
191       if (dup2 (out_fd, STDERR_FILENO) < 0)
192         error (NOHUP_FAILURE, errno, _("failed to redirect standard error"));
193
194       if (stdout_is_closed)
195         close (out_fd);
196     }
197
198   signal (SIGHUP, SIG_IGN);
199
200   {
201     int exit_status;
202     int saved_errno;
203     char **cmd = argv + optind;
204
205     execvp (*cmd, cmd);
206     exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
207     saved_errno = errno;
208
209     /* The execve failed.  Output a diagnostic to stderr only if:
210        - stderr was initially redirected to a non-tty, or
211        - stderr was initially directed to a tty, and we
212          can dup2 it to point back to that same tty.
213        In other words, output the diagnostic if possible, but only if
214        it will go to the original stderr.  */
215     if (dup2 (saved_stderr_fd, STDERR_FILENO) == STDERR_FILENO)
216       error (0, saved_errno, _("cannot run command %s"), quote (*cmd));
217
218     exit (exit_status);
219   }
220 }