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