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