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