view lib/savewd.c @ 17381:915d2ad64b47

qacl: new module, broken out from the acl module This is for GNU Emacs, which wants the acl functions but does not want 'error' invoked when they fail. * lib/acl-internal.h: Do not include error.h, quote.h. (ENOSYS, ENOTSUP): Remove; no longer needed. (ACL_NOT_WELL_SUPPORTED): Remove; replaced by acl_errno_valid. * lib/acl.h: Include <stdbool.h>. (acl_errno_valid): New function. * lib/copy-acl.c, lib/set-acl.c: Include errno,h, not acl-internal.h. * lib/copy-acl.c (qcopy_acl): Move to lib/qcopy-acl.c. * lib/set-acl.c: Rename from lib/set-mode-acl.c. (chmod_or_fchmod, qset_acl): Move to lib/qset-acl.c. (ACL_INTERNAL_INLINE): Remove; no longer needed. * lib/file-has-acl.c (file_has_acl): * lib/qcopy-acl.c (qcopy_acl): * lib/qset-acl.c (qset_acl): Use acl_errno_valid instead of ACL_NOT_WELL_SUPPORTED. * modules/acl (Files): Move lib/acl.h, lib/acl-internal.h, lib/acl_entries.c, lib/set-mode-acl.c (renamed to lib/set-acl.c), lib/file-has-acl.c, m4/acl.m4 to qacl module. Add lib/set-acl.c. (Depends-on): Move extern-inline, fstat, sys_stat to qacl module. Add qacl. (configure.ac): Move gl_FUNC_ACL to qacl module. (lib_SOURCES): Remove file-has-acl.c (moved to qacl module). Rename set-mode-acl.c to set-acl.c. * lib/acl-errno-valid.c: New file. * lib/qcopy-acl.c: New file, moved from the old lib/copy-acl.c; the copy_acl function remains in copy-acl.c. * lib/qcopy-acl.c, lib/qset-acl.c: Do not include gettext.h. (_): Remove; not needed. * lib/qset-acl.c: New file, moved from the old lib/set-mode-acl.c; the set_acl function remains in set-acl.c (renamed from set-mode-acl.c). * modules/qacl: New file, moved from the old modules/acl. (Files, lib_SOURCES): Add acl-errno-valid.c, qcopy-acl.c, qset-acl.c. Remove set-mode-acl.c, copy-acl.c. (Depends-on): Remove error, gettext-h, quote. Add stdbool.
author Paul Eggert <eggert@cs.ucla.edu>
date Sat, 27 Apr 2013 17:39:07 -0700
parents e542fd46ad6f
children 344018b6e5d7
line wrap: on
line source

/* Save and restore the working directory, possibly using a child process.

   Copyright (C) 2006-2007, 2009-2013 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* Written by Paul Eggert.  */

#include <config.h>

#define SAVEWD_INLINE _GL_EXTERN_INLINE

#include "savewd.h"

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "dosname.h"
#include "fcntl-safer.h"

/* Save the working directory into *WD, if it hasn't been saved
   already.  Return true if a child has been forked to do the real
   work.  */
static bool
savewd_save (struct savewd *wd)
{
  switch (wd->state)
    {
    case INITIAL_STATE:
      /* Save the working directory, or prepare to fall back if possible.  */
      {
        int fd = open_safer (".", O_SEARCH);
        if (0 <= fd)
          {
            wd->state = FD_STATE;
            wd->val.fd = fd;
            break;
          }
        if (errno != EACCES && errno != ESTALE)
          {
            wd->state = ERROR_STATE;
            wd->val.errnum = errno;
            break;
          }
      }
      wd->state = FORKING_STATE;
      wd->val.child = -1;
      /* Fall through.  */
    case FORKING_STATE:
      if (wd->val.child < 0)
        {
          /* "Save" the initial working directory by forking a new
             subprocess that will attempt all the work from the chdir
             until until the next savewd_restore.  */
          wd->val.child = fork ();
          if (wd->val.child != 0)
            {
              if (0 < wd->val.child)
                return true;
              wd->state = ERROR_STATE;
              wd->val.errnum = errno;
            }
        }
      break;

    case FD_STATE:
    case FD_POST_CHDIR_STATE:
    case ERROR_STATE:
    case FINAL_STATE:
      break;

    default:
      assert (false);
    }

  return false;
}

int
savewd_chdir (struct savewd *wd, char const *dir, int options,
              int open_result[2])
{
  int fd = -1;
  int result = 0;

  /* Open the directory if requested, or if avoiding a race condition
     is requested and possible.  */
  if (open_result
      || (options & (HAVE_WORKING_O_NOFOLLOW ? SAVEWD_CHDIR_NOFOLLOW : 0)))
    {
      fd = open (dir,
                 (O_SEARCH | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
                  | (options & SAVEWD_CHDIR_NOFOLLOW ? O_NOFOLLOW : 0)));

      if (open_result)
        {
          open_result[0] = fd;
          open_result[1] = errno;
        }

      if (fd < 0 && (errno != EACCES || (options & SAVEWD_CHDIR_READABLE)))
        result = -1;
    }

  if (result == 0 && ! (0 <= fd && options & SAVEWD_CHDIR_SKIP_READABLE))
    {
      if (savewd_save (wd))
        {
          open_result = NULL;
          result = -2;
        }
      else
        {
          result = (fd < 0 ? chdir (dir) : fchdir (fd));

          if (result == 0)
            switch (wd->state)
              {
              case FD_STATE:
                wd->state = FD_POST_CHDIR_STATE;
                break;

              case ERROR_STATE:
              case FD_POST_CHDIR_STATE:
              case FINAL_STATE:
                break;

              case FORKING_STATE:
                assert (wd->val.child == 0);
                break;

              default:
                assert (false);
              }
        }
    }

  if (0 <= fd && ! open_result)
    {
      int e = errno;
      close (fd);
      errno = e;
    }

  return result;
}

int
savewd_restore (struct savewd *wd, int status)
{
  switch (wd->state)
    {
    case INITIAL_STATE:
    case FD_STATE:
      /* The working directory is the desired directory, so there's no
         work to do.  */
      break;

    case FD_POST_CHDIR_STATE:
      /* Restore the working directory using fchdir.  */
      if (fchdir (wd->val.fd) == 0)
        {
          wd->state = FD_STATE;
          break;
        }
      else
        {
          int chdir_errno = errno;
          close (wd->val.fd);
          wd->state = ERROR_STATE;
          wd->val.errnum = chdir_errno;
        }
      /* Fall through.  */
    case ERROR_STATE:
      /* Report an error if asked to restore the working directory.  */
      errno = wd->val.errnum;
      return -1;

    case FORKING_STATE:
      /* "Restore" the working directory by waiting for the subprocess
         to finish.  */
      {
        pid_t child = wd->val.child;
        if (child == 0)
          _exit (status);
        if (0 < child)
          {
            int child_status;
            while (waitpid (child, &child_status, 0) < 0)
              assert (errno == EINTR);
            wd->val.child = -1;
            if (! WIFEXITED (child_status))
              raise (WTERMSIG (child_status));
            return WEXITSTATUS (child_status);
          }
      }
      break;

    default:
      assert (false);
    }

  return 0;
}

void
savewd_finish (struct savewd *wd)
{
  switch (wd->state)
    {
    case INITIAL_STATE:
    case ERROR_STATE:
      break;

    case FD_STATE:
    case FD_POST_CHDIR_STATE:
      close (wd->val.fd);
      break;

    case FORKING_STATE:
      assert (wd->val.child < 0);
      break;

    default:
      assert (false);
    }

  wd->state = FINAL_STATE;
}

/* Return true if the actual work is currently being done by a
   subprocess.

   A true return means that the caller and the subprocess should
   resynchronize later with savewd_restore, using only their own
   memory to decide when to resynchronize; they should not consult the
   file system to decide, because that might lead to race conditions.
   This is why savewd_chdir is broken out into another function;
   savewd_chdir's callers _can_ inspect the file system to decide
   whether to call savewd_chdir.  */
static bool
savewd_delegating (struct savewd const *wd)
{
  return wd->state == FORKING_STATE && 0 < wd->val.child;
}

int
savewd_process_files (int n_files, char **file,
                      int (*act) (char *, struct savewd *, void *),
                      void *options)
{
  int i = 0;
  int last_relative;
  int exit_status = EXIT_SUCCESS;
  struct savewd wd;
  savewd_init (&wd);

  for (last_relative = n_files - 1; 0 <= last_relative; last_relative--)
    if (! IS_ABSOLUTE_FILE_NAME (file[last_relative]))
      break;

  for (; i < last_relative; i++)
    {
      if (! savewd_delegating (&wd))
        {
          int s = act (file[i], &wd, options);
          if (exit_status < s)
            exit_status = s;
        }

      if (! IS_ABSOLUTE_FILE_NAME (file[i + 1]))
        {
          int r = savewd_restore (&wd, exit_status);
          if (exit_status < r)
            exit_status = r;
        }
    }

  savewd_finish (&wd);

  for (; i < n_files; i++)
    {
      int s = act (file[i], &wd, options);
      if (exit_status < s)
        exit_status = s;
    }

  return exit_status;
}