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