* _fpending.c: Include <config.h> unconditionally, since we no
[gnulib.git] / lib / openat.c
1 /* provide a replacement openat function
2    Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17
18 /* written by Jim Meyering */
19
20 #include <config.h>
21
22 #include "openat.h"
23
24 #include <stdarg.h>
25 #include <stddef.h>
26
27 #include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
28 #include "fcntl--.h"
29 #include "lstat.h"
30 #include "openat-priv.h"
31 #include "save-cwd.h"
32
33 /* Replacement for Solaris' openat function.
34    <http://www.google.com/search?q=openat+site:docs.sun.com>
35    First, try to simulate it via open ("/proc/self/fd/FD/FILE").
36    Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
37    If either the save_cwd or the restore_cwd fails (relatively unlikely),
38    then give a diagnostic and exit nonzero.
39    Otherwise, upon failure, set errno and return -1, as openat does.
40    Upon successful completion, return a file descriptor.  */
41 int
42 openat (int fd, char const *file, int flags, ...)
43 {
44   mode_t mode = 0;
45
46   if (flags & O_CREAT)
47     {
48       va_list arg;
49       va_start (arg, flags);
50
51       /* If mode_t is narrower than int, use the promoted type (int),
52          not mode_t.  Use sizeof to guess whether mode_t is narrower;
53          we don't know of any practical counterexamples.  */
54       mode = (sizeof (mode_t) < sizeof (int)
55               ? va_arg (arg, int)
56               : va_arg (arg, mode_t));
57
58       va_end (arg);
59     }
60
61   return openat_permissive (fd, file, flags, mode, NULL);
62 }
63
64 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
65    nonnull, set *CWD_ERRNO to an errno value if unable to save
66    or restore the initial working directory.  This is needed only
67    the first time remove.c's remove_dir opens a command-line
68    directory argument.
69
70    If a previous attempt to restore the current working directory
71    failed, then we must not even try to access a `.'-relative name.
72    It is the caller's responsibility not to call this function
73    in that case.  */
74
75 int
76 openat_permissive (int fd, char const *file, int flags, mode_t mode,
77                    int *cwd_errno)
78 {
79   struct saved_cwd saved_cwd;
80   int saved_errno;
81   int err;
82   bool save_ok;
83
84   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
85     return open (file, flags, mode);
86
87   {
88     char *proc_file;
89     BUILD_PROC_NAME (proc_file, fd, file);
90     err = open (proc_file, flags, mode);
91     /* If the syscall succeeds, or if it fails with an unexpected
92        errno value, then return right away.  Otherwise, fall through
93        and resort to using save_cwd/restore_cwd.  */
94     if (0 <= err || ! EXPECTED_ERRNO (errno))
95       return err;
96   }
97
98   save_ok = (save_cwd (&saved_cwd) == 0);
99   if (! save_ok)
100     {
101       if (! cwd_errno)
102         openat_save_fail (errno);
103       *cwd_errno = errno;
104     }
105
106   err = fchdir (fd);
107   saved_errno = errno;
108
109   if (! err)
110     {
111       err = open (file, flags, mode);
112       saved_errno = errno;
113       if (save_ok && restore_cwd (&saved_cwd) != 0)
114         {
115           if (! cwd_errno)
116             openat_restore_fail (errno);
117           *cwd_errno = errno;
118         }
119     }
120
121   free_cwd (&saved_cwd);
122   errno = saved_errno;
123   return err;
124 }
125
126 /* Return true if our openat implementation must resort to
127    using save_cwd and restore_cwd.  */
128 bool
129 openat_needs_fchdir (void)
130 {
131   int fd2;
132   int fd = open ("/", O_RDONLY);
133   char *proc_file;
134
135   if (fd < 0)
136     return true;
137   BUILD_PROC_NAME (proc_file, fd, ".");
138   fd2 = open (proc_file, O_RDONLY);
139   close (fd);
140   if (0 <= fd2)
141     close (fd2);
142
143   return fd2 < 0;
144 }
145
146 #if !HAVE_FDOPENDIR
147
148 /* Replacement for Solaris' function by the same name.
149    <http://www.google.com/search?q=fdopendir+site:docs.sun.com>
150    First, try to simulate it via opendir ("/proc/self/fd/FD").  Failing
151    that, simulate it by doing save_cwd/fchdir/opendir(".")/restore_cwd.
152    If either the save_cwd or the restore_cwd fails (relatively unlikely),
153    then give a diagnostic and exit nonzero.
154    Otherwise, this function works just like Solaris' fdopendir.
155
156    W A R N I N G:
157    Unlike the other fd-related functions here, this one
158    effectively consumes its FD parameter.  The caller should not
159    close or otherwise manipulate FD if this function returns successfully.  */
160 DIR *
161 fdopendir (int fd)
162 {
163   struct saved_cwd saved_cwd;
164   int saved_errno;
165   DIR *dir;
166
167   char *proc_file;
168   BUILD_PROC_NAME (proc_file, fd, ".");
169   dir = opendir (proc_file);
170   saved_errno = errno;
171
172   /* If the syscall fails with an expected errno value, resort to
173      save_cwd/restore_cwd.  */
174   if (! dir && EXPECTED_ERRNO (saved_errno))
175     {
176       if (save_cwd (&saved_cwd) != 0)
177         openat_save_fail (errno);
178
179       if (fchdir (fd) != 0)
180         {
181           dir = NULL;
182           saved_errno = errno;
183         }
184       else
185         {
186           dir = opendir (".");
187           saved_errno = errno;
188
189           if (restore_cwd (&saved_cwd) != 0)
190             openat_restore_fail (errno);
191         }
192
193       free_cwd (&saved_cwd);
194     }
195
196   if (dir)
197     close (fd);
198   errno = saved_errno;
199   return dir;
200 }
201
202 #endif
203
204 /* Replacement for Solaris' function by the same name.
205    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
206    First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
207    Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
208    If either the save_cwd or the restore_cwd fails (relatively unlikely),
209    then give a diagnostic and exit nonzero.
210    Otherwise, this function works just like Solaris' fstatat.  */
211
212 #define AT_FUNC_NAME fstatat
213 #define AT_FUNC_F1 lstat
214 #define AT_FUNC_F2 stat
215 #define AT_FUNC_USE_F1_COND flag == AT_SYMLINK_NOFOLLOW
216 #define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
217 #define AT_FUNC_POST_FILE_ARGS        , st
218 #include "at-func.c"
219 #undef AT_FUNC_NAME
220 #undef AT_FUNC_F1
221 #undef AT_FUNC_F2
222 #undef AT_FUNC_USE_F1_COND
223 #undef AT_FUNC_POST_FILE_PARAM_DECLS
224 #undef AT_FUNC_POST_FILE_ARGS
225
226 /* Replacement for Solaris' function by the same name.
227    <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
228    First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
229    Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
230    If either the save_cwd or the restore_cwd fails (relatively unlikely),
231    then give a diagnostic and exit nonzero.
232    Otherwise, this function works just like Solaris' unlinkat.  */
233
234 #define AT_FUNC_NAME unlinkat
235 #define AT_FUNC_F1 rmdir
236 #define AT_FUNC_F2 unlink
237 #define AT_FUNC_USE_F1_COND flag == AT_REMOVEDIR
238 #define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
239 #define AT_FUNC_POST_FILE_ARGS        /* empty */
240 #include "at-func.c"
241 #undef AT_FUNC_NAME
242 #undef AT_FUNC_F1
243 #undef AT_FUNC_F2
244 #undef AT_FUNC_USE_F1_COND
245 #undef AT_FUNC_POST_FILE_PARAM_DECLS
246 #undef AT_FUNC_POST_FILE_ARGS
247
248 /* Replacement for Solaris' function by the same name.
249    Invoke chown or lchown on file, FILE, using OWNER and GROUP, in the
250    directory open on descriptor FD.  If FLAG is AT_SYMLINK_NOFOLLOW, then
251    use lchown, otherwise, use chown.  If possible, do it without changing
252    the working directory.  Otherwise, resort to using save_cwd/fchdir,
253    then mkdir/restore_cwd.  If either the save_cwd or the restore_cwd
254    fails, then give a diagnostic and exit nonzero.  */
255
256 #define AT_FUNC_NAME fchownat
257 #define AT_FUNC_F1 lchown
258 #define AT_FUNC_F2 chown
259 #define AT_FUNC_USE_F1_COND flag == AT_SYMLINK_NOFOLLOW
260 #define AT_FUNC_POST_FILE_PARAM_DECLS , uid_t owner, gid_t group, int flag
261 #define AT_FUNC_POST_FILE_ARGS        , owner, group
262 #include "at-func.c"