Imported Upstream version 4.5.10
[platform/upstream/findutils.git] / gnulib / 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 #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 "dosname.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       /* We know len is positive, since open did not fail with ENOENT.  */
107       size_t len = strlen (filename);
108       if (filename[len - 1] == '/')
109         {
110           struct stat statbuf;
111
112           if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
113             {
114               close (fd);
115               errno = ENOTDIR;
116               return -1;
117             }
118         }
119     }
120 # endif
121
122   return fd;
123 }
124
125 #else /* !HAVE_OPENAT */
126
127 /* Replacement for Solaris' openat function.
128    <http://www.google.com/search?q=openat+site:docs.sun.com>
129    First, try to simulate it via open ("/proc/self/fd/FD/FILE").
130    Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
131    If either the save_cwd or the restore_cwd fails (relatively unlikely),
132    then give a diagnostic and exit nonzero.
133    Otherwise, upon failure, set errno and return -1, as openat does.
134    Upon successful completion, return a file descriptor.  */
135 int
136 openat (int fd, char const *file, int flags, ...)
137 {
138   mode_t mode = 0;
139
140   if (flags & O_CREAT)
141     {
142       va_list arg;
143       va_start (arg, flags);
144
145       /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
146          creates crashing code when 'mode_t' is smaller than 'int'.  */
147       mode = va_arg (arg, PROMOTED_MODE_T);
148
149       va_end (arg);
150     }
151
152   return openat_permissive (fd, file, flags, mode, NULL);
153 }
154
155 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
156    nonnull, set *CWD_ERRNO to an errno value if unable to save
157    or restore the initial working directory.  This is needed only
158    the first time remove.c's remove_dir opens a command-line
159    directory argument.
160
161    If a previous attempt to restore the current working directory
162    failed, then we must not even try to access a `.'-relative name.
163    It is the caller's responsibility not to call this function
164    in that case.  */
165
166 int
167 openat_permissive (int fd, char const *file, int flags, mode_t mode,
168                    int *cwd_errno)
169 {
170   struct saved_cwd saved_cwd;
171   int saved_errno;
172   int err;
173   bool save_ok;
174
175   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
176     return open (file, flags, mode);
177
178   {
179     char buf[OPENAT_BUFFER_SIZE];
180     char *proc_file = openat_proc_name (buf, fd, file);
181     if (proc_file)
182       {
183         int open_result = open (proc_file, flags, mode);
184         int open_errno = errno;
185         if (proc_file != buf)
186           free (proc_file);
187         /* If the syscall succeeds, or if it fails with an unexpected
188            errno value, then return right away.  Otherwise, fall through
189            and resort to using save_cwd/restore_cwd.  */
190         if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
191           {
192             errno = open_errno;
193             return open_result;
194           }
195       }
196   }
197
198   save_ok = (save_cwd (&saved_cwd) == 0);
199   if (! save_ok)
200     {
201       if (! cwd_errno)
202         openat_save_fail (errno);
203       *cwd_errno = errno;
204     }
205   if (0 <= fd && fd == saved_cwd.desc)
206     {
207       /* If saving the working directory collides with the user's
208          requested fd, then the user's fd must have been closed to
209          begin with.  */
210       free_cwd (&saved_cwd);
211       errno = EBADF;
212       return -1;
213     }
214
215   err = fchdir (fd);
216   saved_errno = errno;
217
218   if (! err)
219     {
220       err = open (file, flags, mode);
221       saved_errno = errno;
222       if (save_ok && restore_cwd (&saved_cwd) != 0)
223         {
224           if (! cwd_errno)
225             {
226               /* Don't write a message to just-created fd 2.  */
227               saved_errno = errno;
228               if (err == STDERR_FILENO)
229                 close (err);
230               openat_restore_fail (saved_errno);
231             }
232           *cwd_errno = errno;
233         }
234     }
235
236   free_cwd (&saved_cwd);
237   errno = saved_errno;
238   return err;
239 }
240
241 /* Return true if our openat implementation must resort to
242    using save_cwd and restore_cwd.  */
243 bool
244 openat_needs_fchdir (void)
245 {
246   bool needs_fchdir = true;
247   int fd = open ("/", O_SEARCH);
248
249   if (0 <= fd)
250     {
251       char buf[OPENAT_BUFFER_SIZE];
252       char *proc_file = openat_proc_name (buf, fd, ".");
253       if (proc_file)
254         {
255           needs_fchdir = false;
256           if (proc_file != buf)
257             free (proc_file);
258         }
259       close (fd);
260     }
261
262   return needs_fchdir;
263 }
264
265 #endif /* !HAVE_OPENAT */