Line data Source code
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 18 : savewd_save (struct savewd *wd)
46 : {
47 18 : switch (wd->state)
48 : {
49 11 : case INITIAL_STATE:
50 : /* Save the working directory, or prepare to fall back if possible. */
51 : {
52 11 : int fd = open_safer (".", O_RDONLY);
53 11 : if (0 <= fd)
54 : {
55 11 : wd->state = FD_STATE;
56 11 : wd->val.fd = fd;
57 11 : break;
58 : }
59 0 : if (errno != EACCES && errno != ESTALE)
60 : {
61 0 : wd->state = ERROR_STATE;
62 0 : wd->val.errnum = errno;
63 0 : break;
64 : }
65 : }
66 0 : wd->state = FORKING_STATE;
67 0 : wd->val.child = -1;
68 : /* Fall through. */
69 0 : case FORKING_STATE:
70 0 : 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 0 : wd->val.child = fork ();
76 0 : if (wd->val.child != 0)
77 : {
78 0 : if (0 < wd->val.child)
79 0 : return true;
80 0 : wd->state = ERROR_STATE;
81 0 : wd->val.errnum = errno;
82 : }
83 : }
84 0 : break;
85 :
86 7 : case FD_STATE:
87 : case FD_POST_CHDIR_STATE:
88 : case ERROR_STATE:
89 : case FINAL_STATE:
90 7 : break;
91 :
92 0 : default:
93 0 : assert (false);
94 : }
95 :
96 18 : return false;
97 : }
98 :
99 : int
100 57 : savewd_chdir (struct savewd *wd, char const *dir, int options,
101 : int open_result[2])
102 : {
103 57 : int fd = -1;
104 57 : int result = 0;
105 :
106 : /* Open the directory if requested, or if avoiding a race condition
107 : is requested and possible. */
108 57 : if (open_result
109 18 : || (options & (HAVE_WORKING_O_NOFOLLOW ? SAVEWD_CHDIR_NOFOLLOW : 0)))
110 : {
111 40 : fd = open (dir,
112 : (O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
113 40 : | (options & SAVEWD_CHDIR_NOFOLLOW ? O_NOFOLLOW : 0)));
114 :
115 40 : if (open_result)
116 : {
117 39 : open_result[0] = fd;
118 39 : open_result[1] = errno;
119 : }
120 :
121 40 : if (fd < 0 && (errno != EACCES || (options & SAVEWD_CHDIR_READABLE)))
122 23 : result = -1;
123 : }
124 :
125 57 : if (result == 0 && ! (0 <= fd && options & SAVEWD_CHDIR_SKIP_READABLE))
126 : {
127 18 : if (savewd_save (wd))
128 : {
129 0 : open_result = NULL;
130 0 : result = -2;
131 : }
132 : else
133 : {
134 18 : result = (fd < 0 ? chdir (dir) : fchdir (fd));
135 :
136 18 : if (result == 0)
137 5 : switch (wd->state)
138 : {
139 3 : case FD_STATE:
140 3 : wd->state = FD_POST_CHDIR_STATE;
141 3 : break;
142 :
143 2 : case ERROR_STATE:
144 : case FD_POST_CHDIR_STATE:
145 : case FINAL_STATE:
146 2 : break;
147 :
148 0 : case FORKING_STATE:
149 0 : assert (wd->val.child == 0);
150 0 : break;
151 :
152 0 : default:
153 0 : assert (false);
154 : }
155 52 : }
156 : }
157 :
158 57 : if (0 <= fd && ! open_result)
159 : {
160 1 : int e = errno;
161 1 : close (fd);
162 1 : errno = e;
163 : }
164 :
165 57 : return result;
166 : }
167 :
168 : int
169 35 : savewd_restore (struct savewd *wd, int status)
170 : {
171 35 : switch (wd->state)
172 : {
173 32 : case INITIAL_STATE:
174 : case FD_STATE:
175 : /* The working directory is the desired directory, so there's no
176 : work to do. */
177 32 : break;
178 :
179 3 : case FD_POST_CHDIR_STATE:
180 : /* Restore the working directory using fchdir. */
181 3 : if (fchdir (wd->val.fd) == 0)
182 : {
183 3 : wd->state = FD_STATE;
184 3 : break;
185 : }
186 : else
187 : {
188 0 : int chdir_errno = errno;
189 0 : close (wd->val.fd);
190 0 : wd->state = ERROR_STATE;
191 0 : wd->val.errnum = chdir_errno;
192 : }
193 : /* Fall through. */
194 0 : case ERROR_STATE:
195 : /* Report an error if asked to restore the working directory. */
196 0 : errno = wd->val.errnum;
197 0 : return -1;
198 :
199 0 : case FORKING_STATE:
200 : /* "Restore" the working directory by waiting for the subprocess
201 : to finish. */
202 : {
203 0 : pid_t child = wd->val.child;
204 0 : if (child == 0)
205 0 : _exit (status);
206 0 : if (0 < child)
207 : {
208 : int child_status;
209 0 : while (waitpid (child, &child_status, 0) < 0)
210 0 : assert (errno == EINTR);
211 0 : wd->val.child = -1;
212 0 : if (! WIFEXITED (child_status))
213 0 : raise (WTERMSIG (child_status));
214 0 : return WEXITSTATUS (child_status);
215 : }
216 : }
217 0 : break;
218 :
219 0 : default:
220 0 : assert (false);
221 : }
222 :
223 35 : return 0;
224 : }
225 :
226 : void
227 95 : savewd_finish (struct savewd *wd)
228 : {
229 95 : switch (wd->state)
230 : {
231 84 : case INITIAL_STATE:
232 : case ERROR_STATE:
233 84 : break;
234 :
235 11 : case FD_STATE:
236 : case FD_POST_CHDIR_STATE:
237 11 : close (wd->val.fd);
238 11 : break;
239 :
240 0 : case FORKING_STATE:
241 0 : assert (wd->val.child < 0);
242 0 : break;
243 :
244 0 : default:
245 0 : assert (false);
246 : }
247 :
248 95 : wd->state = FINAL_STATE;
249 95 : }
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 34 : savewd_delegating (struct savewd const *wd)
263 : {
264 34 : return wd->state == FORKING_STATE && 0 < wd->val.child;
265 : }
266 :
267 : int
268 92 : savewd_process_files (int n_files, char **file,
269 : int (*act) (char *, struct savewd *, void *),
270 : void *options)
271 : {
272 92 : int i = 0;
273 : int last_relative;
274 92 : int exit_status = EXIT_SUCCESS;
275 : struct savewd wd;
276 92 : savewd_init (&wd);
277 :
278 130 : for (last_relative = n_files - 1; 0 <= last_relative; last_relative--)
279 102 : if (! IS_ABSOLUTE_FILE_NAME (file[last_relative]))
280 64 : break;
281 :
282 126 : for (; i < last_relative; i++)
283 : {
284 34 : if (! savewd_delegating (&wd))
285 : {
286 34 : int s = act (file[i], &wd, options);
287 34 : if (exit_status < s)
288 23 : exit_status = s;
289 : }
290 :
291 34 : if (! IS_ABSOLUTE_FILE_NAME (file[i + 1]))
292 : {
293 33 : int r = savewd_restore (&wd, exit_status);
294 33 : if (exit_status < r)
295 0 : exit_status = r;
296 : }
297 : }
298 :
299 92 : savewd_finish (&wd);
300 :
301 194 : for (; i < n_files; i++)
302 : {
303 102 : int s = act (file[i], &wd, options);
304 102 : if (exit_status < s)
305 59 : exit_status = s;
306 : }
307 :
308 92 : return exit_status;
309 : }
|