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