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