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