1/* Save and restore the working directory, possibly using a child process. 2 3 Copyright (C) 2006-2007, 2009-2010 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. */ 40static bool 41savewd_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 95int 96savewd_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 164int 165savewd_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 222void 223savewd_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. */ 257static inline bool 258savewd_delegating (struct savewd const *wd) 259{ 260 return wd->state == FORKING_STATE && 0 < wd->val.child; 261} 262 263int 264savewd_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} 306