* openat.c (openat): Use ?:, not if, to work around GCC bug 4210
[gnulib.git] / lib / openat.c
1 /* provide a replacement openat function
2    Copyright (C) 2004, 2005 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 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include "openat.h"
25
26 #include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
27 #include "fcntl--.h"
28 #include "openat-priv.h"
29 #include "save-cwd.h"
30 #include "unistd--.h"
31
32 #include <stdarg.h>
33 #include <stddef.h>
34 #include <errno.h>
35
36 /* Replacement for Solaris' openat function.
37    <http://www.google.com/search?q=openat+site:docs.sun.com>
38    Simulate it by doing save_cwd/fchdir/open/restore_cwd.
39    If either the save_cwd or the restore_cwd fails (relatively unlikely,
40    and usually indicative of a problem that deserves close attention),
41    then give a diagnostic and exit nonzero.
42    Otherwise, upon failure, set errno and return -1, as openat does.
43    Upon successful completion, return a file descriptor.  */
44 int
45 openat (int fd, char const *file, int flags, ...)
46 {
47   mode_t mode = 0;
48
49   if (flags & O_CREAT)
50     {
51       va_list arg;
52       va_start (arg, flags);
53
54       /* If mode_t is narrower than int, use the promoted type (int),
55          not mode_t.  Use sizeof to guess whether mode_t is nerrower;
56          we don't know of any practical counterexamples.  */
57       mode = (sizeof (mode_t) < sizeof (int)
58               ? va_arg (arg, int)
59               : va_arg (arg, mode_t));
60
61       va_end (arg);
62     }
63
64   return openat_permissive (fd, file, flags, mode, NULL);
65 }
66
67 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
68    nonnull, set *CWD_ERRNO to an errno value if unable to save
69    or restore the initial working directory.  This is needed only
70    the first time remove.c's remove_dir opens a command-line
71    directory argument.
72
73    If a previous attempt to restore the current working directory
74    failed, then we must not even try to access a `.'-relative name.
75    It is the caller's responsibility not to call this function
76    in that case.  */
77
78 int
79 openat_permissive (int fd, char const *file, int flags, mode_t mode,
80                    int *cwd_errno)
81 {
82   struct saved_cwd saved_cwd;
83   int saved_errno;
84   int err;
85   bool save_ok;
86
87   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
88     return open (file, flags, mode);
89
90   {
91     char *proc_file;
92     BUILD_PROC_NAME (proc_file, fd, file);
93     err = open (proc_file, flags, mode);
94     /* If the syscall succeeds, or if it fails with an unexpected
95        errno value, then return right away.  Otherwise, fall through
96        and resort to using save_cwd/restore_cwd.  */
97     if (0 <= err || ! EXPECTED_ERRNO (errno))
98       return err;
99   }
100
101   save_ok = (save_cwd (&saved_cwd) == 0);
102   if (! save_ok)
103     {
104       if (! cwd_errno)
105         openat_save_fail (errno);
106       *cwd_errno = errno;
107     }
108
109   err = fchdir (fd);
110   saved_errno = errno;
111
112   if (! err)
113     {
114       err = open (file, flags, mode);
115       saved_errno = errno;
116       if (save_ok && restore_cwd (&saved_cwd) != 0)
117         {
118           if (! cwd_errno)
119             openat_restore_fail (errno);
120           *cwd_errno = errno;
121         }
122     }
123
124   free_cwd (&saved_cwd);
125   errno = saved_errno;
126   return err;
127 }
128
129 #if !HAVE_FDOPENDIR
130
131 /* Replacement for Solaris' function by the same name.
132    <http://www.google.com/search?q=fdopendir+site:docs.sun.com>
133    Simulate it by doing save_cwd/fchdir/opendir(".")/restore_cwd.
134    If either the save_cwd or the restore_cwd fails (relatively unlikely,
135    and usually indicative of a problem that deserves close attention),
136    then give a diagnostic and exit nonzero.
137    Otherwise, this function works just like Solaris' fdopendir.
138
139    W A R N I N G:
140    Unlike the other fd-related functions here, this one
141    effectively consumes its FD parameter.  The caller should not
142    close or otherwise manipulate FD if this function returns successfully.  */
143 DIR *
144 fdopendir (int fd)
145 {
146   struct saved_cwd saved_cwd;
147   int saved_errno;
148   DIR *dir;
149
150   char *proc_file;
151   BUILD_PROC_NAME (proc_file, fd, ".");
152   dir = opendir (proc_file);
153   saved_errno = errno;
154
155   /* If the syscall fails with an expected errno value, resort to
156      save_cwd/restore_cwd.  */
157   if (! dir && EXPECTED_ERRNO (saved_errno))
158     {
159       if (save_cwd (&saved_cwd) != 0)
160         openat_save_fail (errno);
161
162       if (fchdir (fd) != 0)
163         {
164           dir = NULL;
165           saved_errno = errno;
166         }
167       else
168         {
169           dir = opendir (".");
170           saved_errno = errno;
171
172           if (restore_cwd (&saved_cwd) != 0)
173             openat_restore_fail (errno);
174         }
175
176       free_cwd (&saved_cwd);
177     }
178
179   if (dir)
180     close (fd);
181   errno = saved_errno;
182   return dir;
183 }
184
185 #endif
186
187 /* Replacement for Solaris' function by the same name.
188    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
189    Simulate it by doing save_cwd/fchdir/(stat|lstat)/restore_cwd.
190    If either the save_cwd or the restore_cwd fails (relatively unlikely,
191    and usually indicative of a problem that deserves close attention),
192    then give a diagnostic and exit nonzero.
193    Otherwise, this function works just like Solaris' fstatat.  */
194 int
195 fstatat (int fd, char const *file, struct stat *st, int flag)
196 {
197   struct saved_cwd saved_cwd;
198   int saved_errno;
199   int err;
200
201   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
202     return (flag == AT_SYMLINK_NOFOLLOW
203             ? lstat (file, st)
204             : stat (file, st));
205
206   {
207     char *proc_file;
208     BUILD_PROC_NAME (proc_file, fd, file);
209     err = (flag == AT_SYMLINK_NOFOLLOW
210            ? lstat (proc_file, st)
211            : stat (proc_file, st));
212     /* If the syscall succeeds, or if it fails with an unexpected
213        errno value, then return right away.  Otherwise, fall through
214        and resort to using save_cwd/restore_cwd.  */
215     if (0 <= err || ! EXPECTED_ERRNO (errno))
216       return err;
217   }
218
219   if (save_cwd (&saved_cwd) != 0)
220     openat_save_fail (errno);
221
222   err = fchdir (fd);
223   saved_errno = errno;
224
225   if (! err)
226     {
227       err = (flag == AT_SYMLINK_NOFOLLOW
228              ? lstat (file, st)
229              : stat (file, st));
230       saved_errno = errno;
231
232       if (restore_cwd (&saved_cwd) != 0)
233         openat_restore_fail (errno);
234     }
235
236   free_cwd (&saved_cwd);
237   errno = saved_errno;
238   return err;
239 }
240
241 /* Replacement for Solaris' function by the same name.
242    <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
243    Simulate it by doing save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
244    If either the save_cwd or the restore_cwd fails (relatively unlikely,
245    and usually indicative of a problem that deserves close attention),
246    then give a diagnostic and exit nonzero.
247    Otherwise, this function works just like Solaris' unlinkat.  */
248 int
249 unlinkat (int fd, char const *file, int flag)
250 {
251   struct saved_cwd saved_cwd;
252   int saved_errno;
253   int err;
254
255   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
256     return (flag == AT_REMOVEDIR ? rmdir (file) : unlink (file));
257
258   {
259     char *proc_file;
260     BUILD_PROC_NAME (proc_file, fd, file);
261     err = (flag == AT_REMOVEDIR ? rmdir (proc_file) : unlink (proc_file));
262     /* If the syscall succeeds, or if it fails with an unexpected
263        errno value, then return right away.  Otherwise, fall through
264        and resort to using save_cwd/restore_cwd.  */
265     if (0 <= err || ! EXPECTED_ERRNO (errno))
266       return err;
267   }
268
269   if (save_cwd (&saved_cwd) != 0)
270     openat_save_fail (errno);
271
272   err = fchdir (fd);
273   saved_errno = errno;
274
275   if (! err)
276     {
277       err = (flag == AT_REMOVEDIR ? rmdir (file) : unlink (file));
278       saved_errno = errno;
279
280       if (restore_cwd (&saved_cwd) != 0)
281         openat_restore_fail (errno);
282     }
283
284   free_cwd (&saved_cwd);
285   errno = saved_errno;
286   return err;
287 }