Use spaces for indentation, not tabs.
[gnulib.git] / lib / savewd.c
1 /* Save and restore the working directory, possibly using a child process.
2
3    Copyright (C) 2006, 2007, 2009 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 /* Written by Paul Eggert.  */
19
20 #include <config.h>
21
22 #include "savewd.h"
23
24 #include <assert.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <signal.h>
28 #include <stdbool.h>
29 #include <stdlib.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 #include <unistd.h>
33
34 #include "dirname.h"
35 #include "fcntl-safer.h"
36
37 /* Save the working directory into *WD, if it hasn't been saved
38    already.  Return true if a child has been forked to do the real
39    work.  */
40 static bool
41 savewd_save (struct savewd *wd)
42 {
43   switch (wd->state)
44     {
45     case INITIAL_STATE:
46       /* Save the working directory, or prepare to fall back if possible.  */
47       {
48         int fd = open_safer (".", O_RDONLY);
49         if (0 <= fd)
50           {
51             wd->state = FD_STATE;
52             wd->val.fd = fd;
53             break;
54           }
55         if (errno != EACCES && errno != ESTALE)
56           {
57             wd->state = ERROR_STATE;
58             wd->val.errnum = errno;
59             break;
60           }
61       }
62       wd->state = FORKING_STATE;
63       wd->val.child = -1;
64       /* Fall through.  */
65     case FORKING_STATE:
66       if (wd->val.child < 0)
67         {
68           /* "Save" the initial working directory by forking a new
69              subprocess that will attempt all the work from the chdir
70              until until the next savewd_restore.  */
71           wd->val.child = fork ();
72           if (wd->val.child != 0)
73             {
74               if (0 < wd->val.child)
75                 return true;
76               wd->state = ERROR_STATE;
77               wd->val.errnum = errno;
78             }
79         }
80       break;
81
82     case FD_STATE:
83     case FD_POST_CHDIR_STATE:
84     case ERROR_STATE:
85     case FINAL_STATE:
86       break;
87
88     default:
89       assert (false);
90     }
91
92   return false;
93 }
94
95 int
96 savewd_chdir (struct savewd *wd, char const *dir, int options,
97               int open_result[2])
98 {
99   int fd = -1;
100   int result = 0;
101
102   /* Open the directory if requested, or if avoiding a race condition
103      is requested and possible.  */
104   if (open_result
105       || (options & (HAVE_WORKING_O_NOFOLLOW ? SAVEWD_CHDIR_NOFOLLOW : 0)))
106     {
107       fd = open (dir,
108                  (O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
109                   | (options & SAVEWD_CHDIR_NOFOLLOW ? O_NOFOLLOW : 0)));
110
111       if (open_result)
112         {
113           open_result[0] = fd;
114           open_result[1] = errno;
115         }
116
117       if (fd < 0 && (errno != EACCES || (options & SAVEWD_CHDIR_READABLE)))
118         result = -1;
119     }
120
121   if (result == 0 && ! (0 <= fd && options & SAVEWD_CHDIR_SKIP_READABLE))
122     {
123       if (savewd_save (wd))
124         {
125           open_result = NULL;
126           result = -2;
127         }
128       else
129         {
130           result = (fd < 0 ? chdir (dir) : fchdir (fd));
131
132           if (result == 0)
133             switch (wd->state)
134               {
135               case FD_STATE:
136                 wd->state = FD_POST_CHDIR_STATE;
137                 break;
138
139               case ERROR_STATE:
140               case FD_POST_CHDIR_STATE:
141               case FINAL_STATE:
142                 break;
143
144               case FORKING_STATE:
145                 assert (wd->val.child == 0);
146                 break;
147
148               default:
149                 assert (false);
150               }
151         }
152     }
153
154   if (0 <= fd && ! open_result)
155     {
156       int e = errno;
157       close (fd);
158       errno = e;
159     }
160
161   return result;
162 }
163
164 int
165 savewd_restore (struct savewd *wd, int status)
166 {
167   switch (wd->state)
168     {
169     case INITIAL_STATE:
170     case FD_STATE:
171       /* The working directory is the desired directory, so there's no
172          work to do.  */
173       break;
174
175     case FD_POST_CHDIR_STATE:
176       /* Restore the working directory using fchdir.  */
177       if (fchdir (wd->val.fd) == 0)
178         {
179           wd->state = FD_STATE;
180           break;
181         }
182       else
183         {
184           int chdir_errno = errno;
185           close (wd->val.fd);
186           wd->state = ERROR_STATE;
187           wd->val.errnum = chdir_errno;
188         }
189       /* Fall through.  */
190     case ERROR_STATE:
191       /* Report an error if asked to restore the working directory.  */
192       errno = wd->val.errnum;
193       return -1;
194
195     case FORKING_STATE:
196       /* "Restore" the working directory by waiting for the subprocess
197          to finish.  */
198       {
199         pid_t child = wd->val.child;
200         if (child == 0)
201           _exit (status);
202         if (0 < child)
203           {
204             int child_status;
205             while (waitpid (child, &child_status, 0) < 0)
206               assert (errno == EINTR);
207             wd->val.child = -1;
208             if (! WIFEXITED (child_status))
209               raise (WTERMSIG (child_status));
210             return WEXITSTATUS (child_status);
211           }
212       }
213       break;
214
215     default:
216       assert (false);
217     }
218
219   return 0;
220 }
221
222 void
223 savewd_finish (struct savewd *wd)
224 {
225   switch (wd->state)
226     {
227     case INITIAL_STATE:
228     case ERROR_STATE:
229       break;
230
231     case FD_STATE:
232     case FD_POST_CHDIR_STATE:
233       close (wd->val.fd);
234       break;
235
236     case FORKING_STATE:
237       assert (wd->val.child < 0);
238       break;
239
240     default:
241       assert (false);
242     }
243
244   wd->state = FINAL_STATE;
245 }
246
247 /* Return true if the actual work is currently being done by a
248    subprocess.
249
250    A true return means that the caller and the subprocess should
251    resynchronize later with savewd_restore, using only their own
252    memory to decide when to resynchronize; they should not consult the
253    file system to decide, because that might lead to race conditions.
254    This is why savewd_chdir is broken out into another function;
255    savewd_chdir's callers _can_ inspect the file system to decide
256    whether to call savewd_chdir.  */
257 static inline bool
258 savewd_delegating (struct savewd const *wd)
259 {
260   return wd->state == FORKING_STATE && 0 < wd->val.child;
261 }
262
263 int
264 savewd_process_files (int n_files, char **file,
265                       int (*act) (char *, struct savewd *, void *),
266                       void *options)
267 {
268   int i = 0;
269   int last_relative;
270   int exit_status = EXIT_SUCCESS;
271   struct savewd wd;
272   savewd_init (&wd);
273
274   for (last_relative = n_files - 1; 0 <= last_relative; last_relative--)
275     if (! IS_ABSOLUTE_FILE_NAME (file[last_relative]))
276       break;
277
278   for (; i < last_relative; i++)
279     {
280       if (! savewd_delegating (&wd))
281         {
282           int s = act (file[i], &wd, options);
283           if (exit_status < s)
284             exit_status = s;
285         }
286
287       if (! IS_ABSOLUTE_FILE_NAME (file[i + 1]))
288         {
289           int r = savewd_restore (&wd, exit_status);
290           if (exit_status < r)
291             exit_status = r;
292         }
293     }
294
295   savewd_finish (&wd);
296
297   for (; i < n_files; i++)
298     {
299       int s = act (file[i], &wd, options);
300       if (exit_status < s)
301         exit_status = s;
302     }
303
304   return exit_status;
305 }