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