# HG changeset patch # User Paul Eggert # Date 1158550278 0 # Node ID f3ddfeb5dff90cd416e957d813ce2249a9f18291 # Parent 247b51a26471589004cbf3575f0c5ad74ef9c2bb Initial revision. diff --git a/lib/savewd.c b/lib/savewd.c new file mode 100644 --- /dev/null +++ b/lib/savewd.c @@ -0,0 +1,305 @@ +/* Save and restore the working directory, possibly using a child process. + + Copyright (C) 2006 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Eggert. */ + +#include + +#include "savewd.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "exit.h" +#include "dirname.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_RDONLY); + if (0 <= fd) + { + wd->state = FD_STATE; + wd->val.fd = fd; + break; + } + if (errno != EACCES) + { + 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 & (O_NOFOLLOW ? SAVEWD_CHDIR_NOFOLLOW : 0))) + { + fd = open (dir, + (O_RDONLY | 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 status; + while (waitpid (child, &status, 0) < 0) + assert (errno == EINTR); + wd->val.child = -1; + if (! WIFEXITED (status)) + raise (WTERMSIG (status)); + return WEXITSTATUS (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 inline 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; +} diff --git a/lib/savewd.h b/lib/savewd.h new file mode 100644 --- /dev/null +++ b/lib/savewd.h @@ -0,0 +1,149 @@ +/* Save and restore the working directory, possibly using a subprocess. + + Copyright (C) 2006 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Eggert. */ + +#ifndef SAVEWD_H +# define SAVEWD_H 1 + +#include +#include + +/* A saved working directory. The member names and constants defined + by this structure are private to the savewd module. */ +struct savewd +{ + /* The state of this object. */ + enum + { + /* This object has been created but does not yet represent + the working directory. */ + INITIAL_STATE, + + /* val.fd is the original working directory's file descriptor. + It is still the working directory. */ + FD_STATE, + + /* Like FD_STATE, but the working directory has changed, so + restoring it will require a fchdir. */ + FD_POST_CHDIR_STATE, + + /* Fork and let the subprocess do the work. val.child is 0 in a + child, negative in a childless parent, and the child process + ID in a parent with a child. */ + FORKING_STATE, + + /* A serious problem argues against further efforts. val.errnum + contains the error number (e.g., EIO). */ + ERROR_STATE, + + /* savewd_finish has been called, so the application no longer + cares whether the working directory is saved, and there is no + more work to do. */ + FINAL_STATE + } state; + + /* The object's value. */ + union + { + int fd; + int errnum; + pid_t child; + } val; +}; + +/* Initialize a saved working directory object. */ +static inline void +savewd_init (struct savewd *wd) +{ + wd->state = INITIAL_STATE; +} + + +/* Options for savewd_chdir. */ +enum + { + /* Do not follow symbolic links, if supported. */ + SAVEWD_CHDIR_NOFOLLOW = 1, + + /* The directory should be readable, so fail if it happens to be + discovered that the directory is not readable. (Unreadable + directories are not necessarily diagnosed, though.) */ + SAVEWD_CHDIR_READABLE = 2, + + /* Do not chdir if the directory is readable; simply succeed + without invoking chdir if the directory was opened. */ + SAVEWD_CHDIR_SKIP_READABLE = 4 + }; + +/* Change the directory, and if successful, record into *WD the fact + that the process chdired into DIR. A process using this module + should use savewd_chdir rather than chdir or fchdir. Obey the + options specified in OPTIONS. + + If OPEN_RESULT is not null, store into OPEN_RESULT[0] a file + descriptor that accesses DIR if a file descriptor is successfully + obtained. Store -1 otherwise, setting OPEN_RESULT[1] to the error + number. Store through OPEN_RESULT regardless of whether the chdir + is successful. However, when -2 is returned, the contents of + OPEN_RESULT are indeterminate since the file descriptor is closed + in the parent. + + Return -2 if a subprocess was spun off to do the real work, -1 + (setting errno) if unsuccessful, 0 if successful. */ +int savewd_chdir (struct savewd *wd, char const *dir, int options, + int open_result[2]); + +/* Restore the working directory from *WD. STATUS indicates the exit + status corresponding to the work done since the last save; this is + used when the caller is in a subprocess. Return 0 if successful, + -1 (setting errno) on our failure, a positive subprocess exit + status if the working directory was restored in the parent but the + subprocess failed. */ +int savewd_restore (struct savewd *wd, int status); + +/* Return WD's error number, or 0 if WD is not in an error state. */ +static inline int +savewd_errno (struct savewd const *wd) +{ + return (wd->state == ERROR_STATE ? wd->val.errnum : 0); +} + +/* Deallocate any resources associated with WD. A program that chdirs + should restore before finishing. */ +void savewd_finish (struct savewd *wd); + +/* Process N_FILES file names, FILE[0] through FILE[N_FILES - 1]. + For each file name F, call ACT (F, WD, OPTIONS); ACT should invoke + savewd_chdir as needed, and should return an exit status. WD + represents thw working directory; it may be in an error state when + ACT is called. + + Save and restore the working directory as needed by the file name + vector; assume that ACT does not require access to any relative + file names other than its first argument, and that it is OK if the + working directory is changed when this function returns. Some + actions may be applied in a subprocess. + + Return the maximum exit status that any call to ACT returned, or + EXIT_SUCCESS (i.e., 0) if no calls were made. */ +int savewd_process_files (int n_files, char **file, + int (*act) (char *, struct savewd *, void *), + void *options); + +#endif diff --git a/m4/savewd.m4 b/m4/savewd.m4 new file mode 100644 --- /dev/null +++ b/m4/savewd.m4 @@ -0,0 +1,9 @@ +# Save and restore the working directory, possibly using a child process. + +dnl Copyright (C) 2004 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_SAVEWD], + [AC_REQUIRE([AC_C_INLINE])]) diff --git a/modules/savewd b/modules/savewd new file mode 100644 --- /dev/null +++ b/modules/savewd @@ -0,0 +1,29 @@ +Description: +Save and restore the working directory, possibly using a child process. + +Files: +lib/savewd.h +lib/savewd.c +m4/savewd.m4 + +Depends-on: +dirname +exit +fcntl-safer +stdbool +xalloc + +configure.ac: +gl_SAVEWD + +Makefile.am: +lib_SOURCES += savewd.h savewd.c + +Include: +"savewd.h" + +License: +GPL + +Maintainer: +Paul Eggert, Jim Meyering