1/* provide a replacement openat function
2   Copyright (C) 2004-2010 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.  */
37int
38rpl_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.  */
135int
136openat (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
166int
167openat_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.  */
243bool
244openat_needs_fchdir (void)
245{
246  bool needs_fchdir = true;
247  int fd = open ("/", O_RDONLY);
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 */
266