fdopendir: split into its own module
[pspp] / 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 <sys/stat.h>
26
27 #include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
28 #include "fcntl--.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.  */
40 int
41 openat (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       /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
51          creates crashing code when 'mode_t' is smaller than 'int'.  */
52       mode = va_arg (arg, PROMOTED_MODE_T);
53
54       va_end (arg);
55     }
56
57   return openat_permissive (fd, file, flags, mode, NULL);
58 }
59
60 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
61    nonnull, set *CWD_ERRNO to an errno value if unable to save
62    or restore the initial working directory.  This is needed only
63    the first time remove.c's remove_dir opens a command-line
64    directory argument.
65
66    If a previous attempt to restore the current working directory
67    failed, then we must not even try to access a `.'-relative name.
68    It is the caller's responsibility not to call this function
69    in that case.  */
70
71 int
72 openat_permissive (int fd, char const *file, int flags, mode_t mode,
73                    int *cwd_errno)
74 {
75   struct saved_cwd saved_cwd;
76   int saved_errno;
77   int err;
78   bool save_ok;
79
80   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
81     return open (file, flags, mode);
82
83   {
84     char buf[OPENAT_BUFFER_SIZE];
85     char *proc_file = openat_proc_name (buf, fd, file);
86     if (proc_file)
87       {
88         int open_result = open (proc_file, flags, mode);
89         int open_errno = errno;
90         if (proc_file != buf)
91           free (proc_file);
92         /* If the syscall succeeds, or if it fails with an unexpected
93            errno value, then return right away.  Otherwise, fall through
94            and resort to using save_cwd/restore_cwd.  */
95         if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
96           {
97             errno = open_errno;
98             return open_result;
99           }
100       }
101   }
102
103   save_ok = (save_cwd (&saved_cwd) == 0);
104   if (! save_ok)
105     {
106       if (! cwd_errno)
107         openat_save_fail (errno);
108       *cwd_errno = errno;
109     }
110
111   err = fchdir (fd);
112   saved_errno = errno;
113
114   if (! err)
115     {
116       err = open (file, flags, mode);
117       saved_errno = errno;
118       if (save_ok && restore_cwd (&saved_cwd) != 0)
119         {
120           if (! cwd_errno)
121             openat_restore_fail (errno);
122           *cwd_errno = errno;
123         }
124     }
125
126   free_cwd (&saved_cwd);
127   errno = saved_errno;
128   return err;
129 }
130
131 /* Return true if our openat implementation must resort to
132    using save_cwd and restore_cwd.  */
133 bool
134 openat_needs_fchdir (void)
135 {
136   bool needs_fchdir = true;
137   int fd = open ("/", O_RDONLY);
138
139   if (0 <= fd)
140     {
141       char buf[OPENAT_BUFFER_SIZE];
142       char *proc_file = openat_proc_name (buf, fd, ".");
143       if (proc_file)
144         {
145           needs_fchdir = false;
146           if (proc_file != buf)
147             free (proc_file);
148         }
149       close (fd);
150     }
151
152   return needs_fchdir;
153 }
154
155 /* Replacement for Solaris' function by the same name.
156    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
157    First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
158    Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
159    If either the save_cwd or the restore_cwd fails (relatively unlikely),
160    then give a diagnostic and exit nonzero.
161    Otherwise, this function works just like Solaris' fstatat.  */
162
163 #define AT_FUNC_NAME fstatat
164 #define AT_FUNC_F1 lstat
165 #define AT_FUNC_F2 stat
166 #define AT_FUNC_USE_F1_COND flag == AT_SYMLINK_NOFOLLOW
167 #define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
168 #define AT_FUNC_POST_FILE_ARGS        , st
169 #include "at-func.c"
170 #undef AT_FUNC_NAME
171 #undef AT_FUNC_F1
172 #undef AT_FUNC_F2
173 #undef AT_FUNC_USE_F1_COND
174 #undef AT_FUNC_POST_FILE_PARAM_DECLS
175 #undef AT_FUNC_POST_FILE_ARGS
176
177 /* Replacement for Solaris' function by the same name.
178    <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
179    First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
180    Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
181    If either the save_cwd or the restore_cwd fails (relatively unlikely),
182    then give a diagnostic and exit nonzero.
183    Otherwise, this function works just like Solaris' unlinkat.  */
184
185 #define AT_FUNC_NAME unlinkat
186 #define AT_FUNC_F1 rmdir
187 #define AT_FUNC_F2 unlink
188 #define AT_FUNC_USE_F1_COND flag == AT_REMOVEDIR
189 #define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
190 #define AT_FUNC_POST_FILE_ARGS        /* empty */
191 #include "at-func.c"
192 #undef AT_FUNC_NAME
193 #undef AT_FUNC_F1
194 #undef AT_FUNC_F2
195 #undef AT_FUNC_USE_F1_COND
196 #undef AT_FUNC_POST_FILE_PARAM_DECLS
197 #undef AT_FUNC_POST_FILE_ARGS