Change copyright notice from GPLv2+ to GPLv3+.
[gnulib.git] / lib / savewd.c
1 /* Save and restore the working directory, possibly using a child process.
2
3    Copyright (C) 2006, 2007 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 #ifndef ESTALE
38 # define ESTALE -1
39 #endif
40
41 /* Save the working directory into *WD, if it hasn't been saved
42    already.  Return true if a child has been forked to do the real
43    work.  */
44 static bool
45 savewd_save (struct savewd *wd)
46 {
47   switch (wd->state)
48     {
49     case INITIAL_STATE:
50       /* Save the working directory, or prepare to fall back if possible.  */
51       {
52         int fd = open_safer (".", O_RDONLY);
53         if (0 <= fd)
54           {
55             wd->state = FD_STATE;
56             wd->val.fd = fd;
57             break;
58           }
59         if (errno != EACCES && errno != ESTALE)
60           {
61             wd->state = ERROR_STATE;
62             wd->val.errnum = errno;
63             break;
64           }
65       }
66       wd->state = FORKING_STATE;
67       wd->val.child = -1;
68       /* Fall through.  */
69     case FORKING_STATE:
70       if (wd->val.child < 0)
71         {
72           /* "Save" the initial working directory by forking a new
73              subprocess that will attempt all the work from the chdir
74              until until the next savewd_restore.  */
75           wd->val.child = fork ();
76           if (wd->val.child != 0)
77             {
78               if (0 < wd->val.child)
79                 return true;
80               wd->state = ERROR_STATE;
81               wd->val.errnum = errno;
82             }
83         }
84       break;
85
86     case FD_STATE:
87     case FD_POST_CHDIR_STATE:
88     case ERROR_STATE:
89     case FINAL_STATE:
90       break;
91
92     default:
93       assert (false);
94     }
95
96   return false;
97 }
98
99 int
100 savewd_chdir (struct savewd *wd, char const *dir, int options,
101               int open_result[2])
102 {
103   int fd = -1;
104   int result = 0;
105
106   /* Open the directory if requested, or if avoiding a race condition
107      is requested and possible.  */
108   if (open_result
109       || (options & (HAVE_WORKING_O_NOFOLLOW ? SAVEWD_CHDIR_NOFOLLOW : 0)))
110     {
111       fd = open (dir,
112                  (O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
113                   | (options & SAVEWD_CHDIR_NOFOLLOW ? O_NOFOLLOW : 0)));
114
115       if (open_result)
116         {
117           open_result[0] = fd;
118           open_result[1] = errno;
119         }
120
121       if (fd < 0 && (errno != EACCES || (options & SAVEWD_CHDIR_READABLE)))
122         result = -1;
123     }
124
125   if (result == 0 && ! (0 <= fd && options & SAVEWD_CHDIR_SKIP_READABLE))
126     {
127       if (savewd_save (wd))
128         {
129           open_result = NULL;
130           result = -2;
131         }
132       else
133         {
134           result = (fd < 0 ? chdir (dir) : fchdir (fd));
135
136           if (result == 0)
137             switch (wd->state)
138               {
139               case FD_STATE:
140                 wd->state = FD_POST_CHDIR_STATE;
141                 break;
142
143               case ERROR_STATE:
144               case FD_POST_CHDIR_STATE:
145               case FINAL_STATE:
146                 break;
147
148               case FORKING_STATE:
149                 assert (wd->val.child == 0);
150                 break;
151
152               default:
153                 assert (false);
154               }
155         }
156     }
157
158   if (0 <= fd && ! open_result)
159     {
160       int e = errno;
161       close (fd);
162       errno = e;
163     }
164
165   return result;
166 }
167
168 int
169 savewd_restore (struct savewd *wd, int status)
170 {
171   switch (wd->state)
172     {
173     case INITIAL_STATE:
174     case FD_STATE:
175       /* The working directory is the desired directory, so there's no
176          work to do.  */
177       break;
178
179     case FD_POST_CHDIR_STATE:
180       /* Restore the working directory using fchdir.  */
181       if (fchdir (wd->val.fd) == 0)
182         {
183           wd->state = FD_STATE;
184           break;
185         }
186       else
187         {
188           int chdir_errno = errno;
189           close (wd->val.fd);
190           wd->state = ERROR_STATE;
191           wd->val.errnum = chdir_errno;
192         }
193       /* Fall through.  */
194     case ERROR_STATE:
195       /* Report an error if asked to restore the working directory.  */
196       errno = wd->val.errnum;
197       return -1;
198
199     case FORKING_STATE:
200       /* "Restore" the working directory by waiting for the subprocess
201          to finish.  */
202       {
203         pid_t child = wd->val.child;
204         if (child == 0)
205           _exit (status);
206         if (0 < child)
207           {
208             int child_status;
209             while (waitpid (child, &child_status, 0) < 0)
210               assert (errno == EINTR);
211             wd->val.child = -1;
212             if (! WIFEXITED (child_status))
213               raise (WTERMSIG (child_status));
214             return WEXITSTATUS (child_status);
215           }
216       }
217       break;
218
219     default:
220       assert (false);
221     }
222
223   return 0;
224 }
225
226 void
227 savewd_finish (struct savewd *wd)
228 {
229   switch (wd->state)
230     {
231     case INITIAL_STATE:
232     case ERROR_STATE:
233       break;
234
235     case FD_STATE:
236     case FD_POST_CHDIR_STATE:
237       close (wd->val.fd);
238       break;
239
240     case FORKING_STATE:
241       assert (wd->val.child < 0);
242       break;
243
244     default:
245       assert (false);
246     }
247
248   wd->state = FINAL_STATE;
249 }
250
251 /* Return true if the actual work is currently being done by a
252    subprocess.
253
254    A true return means that the caller and the subprocess should
255    resynchronize later with savewd_restore, using only their own
256    memory to decide when to resynchronize; they should not consult the
257    file system to decide, because that might lead to race conditions.
258    This is why savewd_chdir is broken out into another function;
259    savewd_chdir's callers _can_ inspect the file system to decide
260    whether to call savewd_chdir.  */
261 static inline bool
262 savewd_delegating (struct savewd const *wd)
263 {
264   return wd->state == FORKING_STATE && 0 < wd->val.child;
265 }
266
267 int
268 savewd_process_files (int n_files, char **file,
269                       int (*act) (char *, struct savewd *, void *),
270                       void *options)
271 {
272   int i = 0;
273   int last_relative;
274   int exit_status = EXIT_SUCCESS;
275   struct savewd wd;
276   savewd_init (&wd);
277
278   for (last_relative = n_files - 1; 0 <= last_relative; last_relative--)
279     if (! IS_ABSOLUTE_FILE_NAME (file[last_relative]))
280       break;
281
282   for (; i < last_relative; i++)
283     {
284       if (! savewd_delegating (&wd))
285         {
286           int s = act (file[i], &wd, options);
287           if (exit_status < s)
288             exit_status = s;
289         }
290
291       if (! IS_ABSOLUTE_FILE_NAME (file[i + 1]))
292         {
293           int r = savewd_restore (&wd, exit_status);
294           if (exit_status < r)
295             exit_status = r;
296         }
297     }
298
299   savewd_finish (&wd);
300
301   for (; i < n_files; i++)
302     {
303       int s = act (file[i], &wd, options);
304       if (exit_status < s)
305         exit_status = s;
306     }
307
308   return exit_status;
309 }