]> git.cworth.org Git - tar/blob - lib/openat.c
Imported Upstream version 1.21
[tar] / lib / openat.c
1 /* provide a replacement openat function
2    Copyright (C) 2004, 2005, 2006, 2007, 2008 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       /* If mode_t is narrower than int, use the promoted type (int),
51          not mode_t.  Use sizeof to guess whether mode_t is narrower;
52          we don't know of any practical counterexamples.  */
53       mode = (sizeof (mode_t) < sizeof (int)
54               ? va_arg (arg, int)
55               : va_arg (arg, mode_t));
56
57       va_end (arg);
58     }
59
60   return openat_permissive (fd, file, flags, mode, NULL);
61 }
62
63 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
64    nonnull, set *CWD_ERRNO to an errno value if unable to save
65    or restore the initial working directory.  This is needed only
66    the first time remove.c's remove_dir opens a command-line
67    directory argument.
68
69    If a previous attempt to restore the current working directory
70    failed, then we must not even try to access a `.'-relative name.
71    It is the caller's responsibility not to call this function
72    in that case.  */
73
74 int
75 openat_permissive (int fd, char const *file, int flags, mode_t mode,
76                    int *cwd_errno)
77 {
78   struct saved_cwd saved_cwd;
79   int saved_errno;
80   int err;
81   bool save_ok;
82
83   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
84     return open (file, flags, mode);
85
86   {
87     char buf[OPENAT_BUFFER_SIZE];
88     char *proc_file = openat_proc_name (buf, fd, file);
89     if (proc_file)
90       {
91         int open_result = open (proc_file, flags, mode);
92         int open_errno = errno;
93         if (proc_file != buf)
94           free (proc_file);
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 <= open_result || ! EXPECTED_ERRNO (open_errno))
99           {
100             errno = open_errno;
101             return open_result;
102           }
103       }
104   }
105
106   save_ok = (save_cwd (&saved_cwd) == 0);
107   if (! save_ok)
108     {
109       if (! cwd_errno)
110         openat_save_fail (errno);
111       *cwd_errno = errno;
112     }
113
114   err = fchdir (fd);
115   saved_errno = errno;
116
117   if (! err)
118     {
119       err = open (file, flags, mode);
120       saved_errno = errno;
121       if (save_ok && restore_cwd (&saved_cwd) != 0)
122         {
123           if (! cwd_errno)
124             openat_restore_fail (errno);
125           *cwd_errno = errno;
126         }
127     }
128
129   free_cwd (&saved_cwd);
130   errno = saved_errno;
131   return err;
132 }
133
134 /* Return true if our openat implementation must resort to
135    using save_cwd and restore_cwd.  */
136 bool
137 openat_needs_fchdir (void)
138 {
139   bool needs_fchdir = true;
140   int fd = open ("/", O_RDONLY);
141
142   if (0 <= fd)
143     {
144       char buf[OPENAT_BUFFER_SIZE];
145       char *proc_file = openat_proc_name (buf, fd, ".");
146       if (proc_file)
147         {
148           needs_fchdir = false;
149           if (proc_file != buf)
150             free (proc_file);
151         }
152       close (fd);
153     }
154
155   return needs_fchdir;
156 }
157
158 #if !HAVE_FDOPENDIR
159
160 /* Replacement for Solaris' function by the same name.
161    <http://www.google.com/search?q=fdopendir+site:docs.sun.com>
162    First, try to simulate it via opendir ("/proc/self/fd/FD").  Failing
163    that, simulate it by doing save_cwd/fchdir/opendir(".")/restore_cwd.
164    If either the save_cwd or the restore_cwd fails (relatively unlikely),
165    then give a diagnostic and exit nonzero.
166    Otherwise, this function works just like Solaris' fdopendir.
167
168    W A R N I N G:
169    Unlike the other fd-related functions here, this one
170    effectively consumes its FD parameter.  The caller should not
171    close or otherwise manipulate FD if this function returns successfully.  */
172 DIR *
173 fdopendir (int fd)
174 {
175   struct saved_cwd saved_cwd;
176   int saved_errno;
177   DIR *dir;
178
179   char buf[OPENAT_BUFFER_SIZE];
180   char *proc_file = openat_proc_name (buf, fd, ".");
181   if (proc_file)
182     {
183       dir = opendir (proc_file);
184       saved_errno = errno;
185     }
186   else
187     {
188       dir = NULL;
189       saved_errno = EOPNOTSUPP;
190     }
191
192   /* If the syscall fails with an expected errno value, resort to
193      save_cwd/restore_cwd.  */
194   if (! dir && EXPECTED_ERRNO (saved_errno))
195     {
196       if (save_cwd (&saved_cwd) != 0)
197         openat_save_fail (errno);
198
199       if (fchdir (fd) != 0)
200         {
201           dir = NULL;
202           saved_errno = errno;
203         }
204       else
205         {
206           dir = opendir (".");
207           saved_errno = errno;
208
209           if (restore_cwd (&saved_cwd) != 0)
210             openat_restore_fail (errno);
211         }
212
213       free_cwd (&saved_cwd);
214     }
215
216   if (dir)
217     close (fd);
218   if (proc_file != buf)
219     free (proc_file);
220   errno = saved_errno;
221   return dir;
222 }
223
224 #endif
225
226 /* Replacement for Solaris' function by the same name.
227    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
228    First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
229    Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
230    If either the save_cwd or the restore_cwd fails (relatively unlikely),
231    then give a diagnostic and exit nonzero.
232    Otherwise, this function works just like Solaris' fstatat.  */
233
234 #define AT_FUNC_NAME fstatat
235 #define AT_FUNC_F1 lstat
236 #define AT_FUNC_F2 stat
237 #define AT_FUNC_USE_F1_COND flag == AT_SYMLINK_NOFOLLOW
238 #define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
239 #define AT_FUNC_POST_FILE_ARGS        , st
240 #include "at-func.c"
241 #undef AT_FUNC_NAME
242 #undef AT_FUNC_F1
243 #undef AT_FUNC_F2
244 #undef AT_FUNC_USE_F1_COND
245 #undef AT_FUNC_POST_FILE_PARAM_DECLS
246 #undef AT_FUNC_POST_FILE_ARGS
247
248 /* Replacement for Solaris' function by the same name.
249    <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
250    First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
251    Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
252    If either the save_cwd or the restore_cwd fails (relatively unlikely),
253    then give a diagnostic and exit nonzero.
254    Otherwise, this function works just like Solaris' unlinkat.  */
255
256 #define AT_FUNC_NAME unlinkat
257 #define AT_FUNC_F1 rmdir
258 #define AT_FUNC_F2 unlink
259 #define AT_FUNC_USE_F1_COND flag == AT_REMOVEDIR
260 #define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
261 #define AT_FUNC_POST_FILE_ARGS        /* empty */
262 #include "at-func.c"
263 #undef AT_FUNC_NAME
264 #undef AT_FUNC_F1
265 #undef AT_FUNC_F2
266 #undef AT_FUNC_USE_F1_COND
267 #undef AT_FUNC_POST_FILE_PARAM_DECLS
268 #undef AT_FUNC_POST_FILE_ARGS