openat: Conditionalize dependencies.
[gnulib.git] / lib / openat.c
1 /* provide a replacement openat function
2    Copyright (C) 2004-2011 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 /* If the user's config.h happens to include <fcntl.h>, let it include only
20    the system's <fcntl.h> here, so that orig_openat doesn't recurse to
21    rpl_openat.  */
22 #define __need_system_fcntl_h
23 #include <config.h>
24
25 /* Get the original definition of open.  It might be defined as a macro.  */
26 #include <fcntl.h>
27 #include <sys/types.h>
28 #undef __need_system_fcntl_h
29
30 #if HAVE_OPENAT
31 static inline int
32 orig_openat (int fd, char const *filename, int flags, mode_t mode)
33 {
34   return openat (fd, filename, flags, mode);
35 }
36 #endif
37
38 /* Write "fcntl.h" here, not <fcntl.h>, otherwise OSF/1 5.1 DTK cc eliminates
39    this include because of the preliminary #include <fcntl.h> above.  */
40 #include "fcntl.h"
41
42 #include "openat.h"
43
44 #include <stdarg.h>
45 #include <stdbool.h>
46 #include <stddef.h>
47 #include <string.h>
48 #include <sys/stat.h>
49
50 #if HAVE_OPENAT
51
52 /* Like openat, but work around Solaris 9 bugs with trailing slash.  */
53 int
54 rpl_openat (int dfd, char const *filename, int flags, ...)
55 {
56   mode_t mode;
57   int fd;
58
59   mode = 0;
60   if (flags & O_CREAT)
61     {
62       va_list arg;
63       va_start (arg, flags);
64
65       /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
66          creates crashing code when 'mode_t' is smaller than 'int'.  */
67       mode = va_arg (arg, PROMOTED_MODE_T);
68
69       va_end (arg);
70     }
71
72 # if OPEN_TRAILING_SLASH_BUG
73   /* If the filename ends in a slash and one of O_CREAT, O_WRONLY, O_RDWR
74      is specified, then fail.
75      Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
76      says that
77        "A pathname that contains at least one non-slash character and that
78         ends with one or more trailing slashes shall be resolved as if a
79         single dot character ( '.' ) were appended to the pathname."
80      and
81        "The special filename dot shall refer to the directory specified by
82         its predecessor."
83      If the named file already exists as a directory, then
84        - if O_CREAT is specified, open() must fail because of the semantics
85          of O_CREAT,
86        - if O_WRONLY or O_RDWR is specified, open() must fail because POSIX
87          <http://www.opengroup.org/susv3/functions/open.html> says that it
88          fails with errno = EISDIR in this case.
89      If the named file does not exist or does not name a directory, then
90        - if O_CREAT is specified, open() must fail since open() cannot create
91          directories,
92        - if O_WRONLY or O_RDWR is specified, open() must fail because the
93          file does not contain a '.' directory.  */
94   if (flags & (O_CREAT | O_WRONLY | O_RDWR))
95     {
96       size_t len = strlen (filename);
97       if (len > 0 && filename[len - 1] == '/')
98         {
99           errno = EISDIR;
100           return -1;
101         }
102     }
103 # endif
104
105   fd = orig_openat (dfd, filename, flags, mode);
106
107 # if OPEN_TRAILING_SLASH_BUG
108   /* If the filename ends in a slash and fd does not refer to a directory,
109      then fail.
110      Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
111      says that
112        "A pathname that contains at least one non-slash character and that
113         ends with one or more trailing slashes shall be resolved as if a
114         single dot character ( '.' ) were appended to the pathname."
115      and
116        "The special filename dot shall refer to the directory specified by
117         its predecessor."
118      If the named file without the slash is not a directory, open() must fail
119      with ENOTDIR.  */
120   if (fd >= 0)
121     {
122       /* We know len is positive, since open did not fail with ENOENT.  */
123       size_t len = strlen (filename);
124       if (filename[len - 1] == '/')
125         {
126           struct stat statbuf;
127
128           if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
129             {
130               close (fd);
131               errno = ENOTDIR;
132               return -1;
133             }
134         }
135     }
136 # endif
137
138   return fd;
139 }
140
141 #else /* !HAVE_OPENAT */
142
143 # include "dosname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
144 # include "openat-priv.h"
145 # include "save-cwd.h"
146
147 /* Replacement for Solaris' openat function.
148    <http://www.google.com/search?q=openat+site:docs.sun.com>
149    First, try to simulate it via open ("/proc/self/fd/FD/FILE").
150    Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
151    If either the save_cwd or the restore_cwd fails (relatively unlikely),
152    then give a diagnostic and exit nonzero.
153    Otherwise, upon failure, set errno and return -1, as openat does.
154    Upon successful completion, return a file descriptor.  */
155 int
156 openat (int fd, char const *file, int flags, ...)
157 {
158   mode_t mode = 0;
159
160   if (flags & O_CREAT)
161     {
162       va_list arg;
163       va_start (arg, flags);
164
165       /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
166          creates crashing code when 'mode_t' is smaller than 'int'.  */
167       mode = va_arg (arg, PROMOTED_MODE_T);
168
169       va_end (arg);
170     }
171
172   return openat_permissive (fd, file, flags, mode, NULL);
173 }
174
175 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
176    nonnull, set *CWD_ERRNO to an errno value if unable to save
177    or restore the initial working directory.  This is needed only
178    the first time remove.c's remove_dir opens a command-line
179    directory argument.
180
181    If a previous attempt to restore the current working directory
182    failed, then we must not even try to access a `.'-relative name.
183    It is the caller's responsibility not to call this function
184    in that case.  */
185
186 int
187 openat_permissive (int fd, char const *file, int flags, mode_t mode,
188                    int *cwd_errno)
189 {
190   struct saved_cwd saved_cwd;
191   int saved_errno;
192   int err;
193   bool save_ok;
194
195   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
196     return open (file, flags, mode);
197
198   {
199     char buf[OPENAT_BUFFER_SIZE];
200     char *proc_file = openat_proc_name (buf, fd, file);
201     if (proc_file)
202       {
203         int open_result = open (proc_file, flags, mode);
204         int open_errno = errno;
205         if (proc_file != buf)
206           free (proc_file);
207         /* If the syscall succeeds, or if it fails with an unexpected
208            errno value, then return right away.  Otherwise, fall through
209            and resort to using save_cwd/restore_cwd.  */
210         if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
211           {
212             errno = open_errno;
213             return open_result;
214           }
215       }
216   }
217
218   save_ok = (save_cwd (&saved_cwd) == 0);
219   if (! save_ok)
220     {
221       if (! cwd_errno)
222         openat_save_fail (errno);
223       *cwd_errno = errno;
224     }
225   if (0 <= fd && fd == saved_cwd.desc)
226     {
227       /* If saving the working directory collides with the user's
228          requested fd, then the user's fd must have been closed to
229          begin with.  */
230       free_cwd (&saved_cwd);
231       errno = EBADF;
232       return -1;
233     }
234
235   err = fchdir (fd);
236   saved_errno = errno;
237
238   if (! err)
239     {
240       err = open (file, flags, mode);
241       saved_errno = errno;
242       if (save_ok && restore_cwd (&saved_cwd) != 0)
243         {
244           if (! cwd_errno)
245             {
246               /* Don't write a message to just-created fd 2.  */
247               saved_errno = errno;
248               if (err == STDERR_FILENO)
249                 close (err);
250               openat_restore_fail (saved_errno);
251             }
252           *cwd_errno = errno;
253         }
254     }
255
256   free_cwd (&saved_cwd);
257   errno = saved_errno;
258   return err;
259 }
260
261 /* Return true if our openat implementation must resort to
262    using save_cwd and restore_cwd.  */
263 bool
264 openat_needs_fchdir (void)
265 {
266   bool needs_fchdir = true;
267   int fd = open ("/", O_SEARCH);
268
269   if (0 <= fd)
270     {
271       char buf[OPENAT_BUFFER_SIZE];
272       char *proc_file = openat_proc_name (buf, fd, ".");
273       if (proc_file)
274         {
275           needs_fchdir = false;
276           if (proc_file != buf)
277             free (proc_file);
278         }
279       close (fd);
280     }
281
282   return needs_fchdir;
283 }
284
285 #endif /* !HAVE_OPENAT */