[lib/ChangeLog]
[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 buf[OPENAT_BUFFER_SIZE];
89     char *proc_file = openat_proc_name (buf, fd, file);
90     if (proc_file)
91       {
92         int open_result = open (proc_file, flags, mode);
93         int open_errno = errno;
94         if (proc_file != buf)
95           free (proc_file);
96         /* If the syscall succeeds, or if it fails with an unexpected
97            errno value, then return right away.  Otherwise, fall through
98            and resort to using save_cwd/restore_cwd.  */
99         if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
100           {
101             errno = open_errno;
102             return open_result;
103           }
104       }
105   }
106
107   save_ok = (save_cwd (&saved_cwd) == 0);
108   if (! save_ok)
109     {
110       if (! cwd_errno)
111         openat_save_fail (errno);
112       *cwd_errno = errno;
113     }
114
115   err = fchdir (fd);
116   saved_errno = errno;
117
118   if (! err)
119     {
120       err = open (file, flags, mode);
121       saved_errno = errno;
122       if (save_ok && restore_cwd (&saved_cwd) != 0)
123         {
124           if (! cwd_errno)
125             openat_restore_fail (errno);
126           *cwd_errno = errno;
127         }
128     }
129
130   free_cwd (&saved_cwd);
131   errno = saved_errno;
132   return err;
133 }
134
135 /* Return true if our openat implementation must resort to
136    using save_cwd and restore_cwd.  */
137 bool
138 openat_needs_fchdir (void)
139 {
140   bool needs_fchdir = true;
141   int fd = open ("/", O_RDONLY);
142
143   if (0 <= fd)
144     {
145       char buf[OPENAT_BUFFER_SIZE];
146       char *proc_file = openat_proc_name (buf, fd, ".");
147       if (proc_file)
148         {
149           needs_fchdir = false;
150           if (proc_file != buf)
151             free (proc_file);
152         }
153       close (fd);
154     }
155
156   return needs_fchdir;
157 }
158
159 #if !HAVE_FDOPENDIR
160
161 /* Replacement for Solaris' function by the same name.
162    <http://www.google.com/search?q=fdopendir+site:docs.sun.com>
163    First, try to simulate it via opendir ("/proc/self/fd/FD").  Failing
164    that, simulate it by doing save_cwd/fchdir/opendir(".")/restore_cwd.
165    If either the save_cwd or the restore_cwd fails (relatively unlikely),
166    then give a diagnostic and exit nonzero.
167    Otherwise, this function works just like Solaris' fdopendir.
168
169    W A R N I N G:
170    Unlike the other fd-related functions here, this one
171    effectively consumes its FD parameter.  The caller should not
172    close or otherwise manipulate FD if this function returns successfully.  */
173 DIR *
174 fdopendir (int fd)
175 {
176   struct saved_cwd saved_cwd;
177   int saved_errno;
178   DIR *dir;
179
180   char buf[OPENAT_BUFFER_SIZE];
181   char *proc_file = openat_proc_name (buf, fd, ".");
182   if (proc_file)
183     {
184       dir = opendir (proc_file);
185       saved_errno = errno;
186     }
187   else
188     {
189       dir = NULL;
190       saved_errno = EOPNOTSUPP;
191     }
192
193   /* If the syscall fails with an expected errno value, resort to
194      save_cwd/restore_cwd.  */
195   if (! dir && EXPECTED_ERRNO (saved_errno))
196     {
197       if (save_cwd (&saved_cwd) != 0)
198         openat_save_fail (errno);
199
200       if (fchdir (fd) != 0)
201         {
202           dir = NULL;
203           saved_errno = errno;
204         }
205       else
206         {
207           dir = opendir (".");
208           saved_errno = errno;
209
210           if (restore_cwd (&saved_cwd) != 0)
211             openat_restore_fail (errno);
212         }
213
214       free_cwd (&saved_cwd);
215     }
216
217   if (dir)
218     close (fd);
219   if (proc_file != buf)
220     free (proc_file);
221   errno = saved_errno;
222   return dir;
223 }
224
225 #endif
226
227 /* Replacement for Solaris' function by the same name.
228    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
229    First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
230    Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
231    If either the save_cwd or the restore_cwd fails (relatively unlikely),
232    then give a diagnostic and exit nonzero.
233    Otherwise, this function works just like Solaris' fstatat.  */
234
235 #define AT_FUNC_NAME fstatat
236 #define AT_FUNC_F1 lstat
237 #define AT_FUNC_F2 stat
238 #define AT_FUNC_USE_F1_COND flag == AT_SYMLINK_NOFOLLOW
239 #define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
240 #define AT_FUNC_POST_FILE_ARGS        , st
241 #include "at-func.c"
242 #undef AT_FUNC_NAME
243 #undef AT_FUNC_F1
244 #undef AT_FUNC_F2
245 #undef AT_FUNC_USE_F1_COND
246 #undef AT_FUNC_POST_FILE_PARAM_DECLS
247 #undef AT_FUNC_POST_FILE_ARGS
248
249 /* Replacement for Solaris' function by the same name.
250    <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
251    First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
252    Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
253    If either the save_cwd or the restore_cwd fails (relatively unlikely),
254    then give a diagnostic and exit nonzero.
255    Otherwise, this function works just like Solaris' unlinkat.  */
256
257 #define AT_FUNC_NAME unlinkat
258 #define AT_FUNC_F1 rmdir
259 #define AT_FUNC_F2 unlink
260 #define AT_FUNC_USE_F1_COND flag == AT_REMOVEDIR
261 #define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
262 #define AT_FUNC_POST_FILE_ARGS        /* empty */
263 #include "at-func.c"
264 #undef AT_FUNC_NAME
265 #undef AT_FUNC_F1
266 #undef AT_FUNC_F2
267 #undef AT_FUNC_USE_F1_COND
268 #undef AT_FUNC_POST_FILE_PARAM_DECLS
269 #undef AT_FUNC_POST_FILE_ARGS
270
271 /* Replacement for Solaris' function by the same name.
272    Invoke chown or lchown on file, FILE, using OWNER and GROUP, in the
273    directory open on descriptor FD.  If FLAG is AT_SYMLINK_NOFOLLOW, then
274    use lchown, otherwise, use chown.  If possible, do it without changing
275    the working directory.  Otherwise, resort to using save_cwd/fchdir,
276    then mkdir/restore_cwd.  If either the save_cwd or the restore_cwd
277    fails, then give a diagnostic and exit nonzero.  */
278
279 #define AT_FUNC_NAME fchownat
280 #define AT_FUNC_F1 lchown
281 #define AT_FUNC_F2 chown
282 #define AT_FUNC_USE_F1_COND flag == AT_SYMLINK_NOFOLLOW
283 #define AT_FUNC_POST_FILE_PARAM_DECLS , uid_t owner, gid_t group, int flag
284 #define AT_FUNC_POST_FILE_ARGS        , owner, group
285 #include "at-func.c"