LCOV - code coverage report
Current view: top level - lib - savewd.c (source / functions) Hit Total Coverage
Test: coreutils.info Lines: 80 131 61.1 %
Date: 2018-01-30 Functions: 6 6 100.0 %

          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             : }

Generated by: LCOV version 1.10