root/maint/gnulib/lib/savewd.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. savewd_save
  2. savewd_chdir
  3. savewd_restore
  4. savewd_finish
  5. savewd_delegating
  6. savewd_process_files

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

/* [previous][next][first][last][top][bottom][index][help] */