1/* provide a replacement openat function
2   Copyright (C) 2004, 2005, 2006, 2007 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
26#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
27#include "fcntl--.h"
28#include "lstat.h"
29#include "openat-priv.h"
30#include "save-cwd.h"
31
32/* Replacement for Solaris' openat function.
33   <http://www.google.com/search?q=openat+site:docs.sun.com>
34   First, try to simulate it via open ("/proc/self/fd/FD/FILE").
35   Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
36   If either the save_cwd or the restore_cwd fails (relatively unlikely),
37   then give a diagnostic and exit nonzero.
38   Otherwise, upon failure, set errno and return -1, as openat does.
39   Upon successful completion, return a file descriptor.  */
40int
41openat (int fd, char const *file, int flags, ...)
42{
43  mode_t mode = 0;
44
45  if (flags & O_CREAT)
46    {
47      va_list arg;
48      va_start (arg, flags);
49
50      /* If mode_t is narrower than int, use the promoted type (int),
51         not mode_t.  Use sizeof to guess whether mode_t is narrower;
52         we don't know of any practical counterexamples.  */
53      mode = (sizeof (mode_t) < sizeof (int)
54	      ? va_arg (arg, int)
55	      : va_arg (arg, mode_t));
56
57      va_end (arg);
58    }
59
60  return openat_permissive (fd, file, flags, mode, NULL);
61}
62
63/* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
64   nonnull, set *CWD_ERRNO to an errno value if unable to save
65   or restore the initial working directory.  This is needed only
66   the first time remove.c's remove_dir opens a command-line
67   directory argument.
68
69   If a previous attempt to restore the current working directory
70   failed, then we must not even try to access a `.'-relative name.
71   It is the caller's responsibility not to call this function
72   in that case.  */
73
74int
75openat_permissive (int fd, char const *file, int flags, mode_t mode,
76		   int *cwd_errno)
77{
78  struct saved_cwd saved_cwd;
79  int saved_errno;
80  int err;
81  bool save_ok;
82
83  if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
84    return open (file, flags, mode);
85
86  {
87    char buf[OPENAT_BUFFER_SIZE];
88    char *proc_file = openat_proc_name (buf, fd, file);
89    if (proc_file)
90      {
91	int open_result = open (proc_file, flags, mode);
92	int open_errno = errno;
93	if (proc_file != buf)
94	  free (proc_file);
95	/* If the syscall succeeds, or if it fails with an unexpected
96	   errno value, then return right away.  Otherwise, fall through
97	   and resort to using save_cwd/restore_cwd.  */
98	if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
99	  {
100	    errno = open_errno;
101	    return open_result;
102	  }
103      }
104  }
105
106  save_ok = (save_cwd (&saved_cwd) == 0);
107  if (! save_ok)
108    {
109      if (! cwd_errno)
110	openat_save_fail (errno);
111      *cwd_errno = errno;
112    }
113
114  err = fchdir (fd);
115  saved_errno = errno;
116
117  if (! err)
118    {
119      err = open (file, flags, mode);
120      saved_errno = errno;
121      if (save_ok && restore_cwd (&saved_cwd) != 0)
122	{
123	  if (! cwd_errno)
124	    openat_restore_fail (errno);
125	  *cwd_errno = errno;
126	}
127    }
128
129  free_cwd (&saved_cwd);
130  errno = saved_errno;
131  return err;
132}
133
134/* Return true if our openat implementation must resort to
135   using save_cwd and restore_cwd.  */
136bool
137openat_needs_fchdir (void)
138{
139  bool needs_fchdir = true;
140  int fd = open ("/", O_RDONLY);
141
142  if (0 <= fd)
143    {
144      char buf[OPENAT_BUFFER_SIZE];
145      char *proc_file = openat_proc_name (buf, fd, ".");
146      if (proc_file)
147	{
148	  needs_fchdir = false;
149	  if (proc_file != buf)
150	    free (proc_file);
151	}
152      close (fd);
153    }
154
155  return needs_fchdir;
156}
157
158#if !HAVE_FDOPENDIR
159
160/* Replacement for Solaris' function by the same name.
161   <http://www.google.com/search?q=fdopendir+site:docs.sun.com>
162   First, try to simulate it via opendir ("/proc/self/fd/FD").  Failing
163   that, simulate it by doing save_cwd/fchdir/opendir(".")/restore_cwd.
164   If either the save_cwd or the restore_cwd fails (relatively unlikely),
165   then give a diagnostic and exit nonzero.
166   Otherwise, this function works just like Solaris' fdopendir.
167
168   W A R N I N G:
169   Unlike the other fd-related functions here, this one
170   effectively consumes its FD parameter.  The caller should not
171   close or otherwise manipulate FD if this function returns successfully.  */
172DIR *
173fdopendir (int fd)
174{
175  struct saved_cwd saved_cwd;
176  int saved_errno;
177  DIR *dir;
178
179  char buf[OPENAT_BUFFER_SIZE];
180  char *proc_file = openat_proc_name (buf, fd, ".");
181  if (proc_file)
182    {
183      dir = opendir (proc_file);
184      saved_errno = errno;
185    }
186  else
187    {
188      dir = NULL;
189      saved_errno = EOPNOTSUPP;
190    }
191
192  /* If the syscall fails with an expected errno value, resort to
193     save_cwd/restore_cwd.  */
194  if (! dir && EXPECTED_ERRNO (saved_errno))
195    {
196      if (save_cwd (&saved_cwd) != 0)
197	openat_save_fail (errno);
198
199      if (fchdir (fd) != 0)
200	{
201	  dir = NULL;
202	  saved_errno = errno;
203	}
204      else
205	{
206	  dir = opendir (".");
207	  saved_errno = errno;
208
209	  if (restore_cwd (&saved_cwd) != 0)
210	    openat_restore_fail (errno);
211	}
212
213      free_cwd (&saved_cwd);
214    }
215
216  if (dir)
217    close (fd);
218  if (proc_file != buf)
219    free (proc_file);
220  errno = saved_errno;
221  return dir;
222}
223
224#endif
225
226/* Replacement for Solaris' function by the same name.
227   <http://www.google.com/search?q=fstatat+site:docs.sun.com>
228   First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
229   Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
230   If either the save_cwd or the restore_cwd fails (relatively unlikely),
231   then give a diagnostic and exit nonzero.
232   Otherwise, this function works just like Solaris' fstatat.  */
233
234#define AT_FUNC_NAME fstatat
235#define AT_FUNC_F1 lstat
236#define AT_FUNC_F2 stat
237#define AT_FUNC_USE_F1_COND flag == AT_SYMLINK_NOFOLLOW
238#define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
239#define AT_FUNC_POST_FILE_ARGS        , st
240#include "at-func.c"
241#undef AT_FUNC_NAME
242#undef AT_FUNC_F1
243#undef AT_FUNC_F2
244#undef AT_FUNC_USE_F1_COND
245#undef AT_FUNC_POST_FILE_PARAM_DECLS
246#undef AT_FUNC_POST_FILE_ARGS
247
248/* Replacement for Solaris' function by the same name.
249   <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
250   First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
251   Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
252   If either the save_cwd or the restore_cwd fails (relatively unlikely),
253   then give a diagnostic and exit nonzero.
254   Otherwise, this function works just like Solaris' unlinkat.  */
255
256#define AT_FUNC_NAME unlinkat
257#define AT_FUNC_F1 rmdir
258#define AT_FUNC_F2 unlink
259#define AT_FUNC_USE_F1_COND flag == AT_REMOVEDIR
260#define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
261#define AT_FUNC_POST_FILE_ARGS        /* empty */
262#include "at-func.c"
263#undef AT_FUNC_NAME
264#undef AT_FUNC_F1
265#undef AT_FUNC_F2
266#undef AT_FUNC_USE_F1_COND
267#undef AT_FUNC_POST_FILE_PARAM_DECLS
268#undef AT_FUNC_POST_FILE_ARGS
269