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