Commit | Line | Data |
---|---|---|
6ec2e0f5 | 1 | /* provide a replacement openat function |
7a6dbc2f | 2 | Copyright (C) 2004-2018 Free Software Foundation, Inc. |
6ec2e0f5 SDJ |
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 | |
7a6dbc2f | 15 | along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
6ec2e0f5 SDJ |
16 | |
17 | /* written by Jim Meyering */ | |
18 | ||
19 | /* If the user's config.h happens to include <fcntl.h>, let it include only | |
20 | the system's <fcntl.h> here, so that orig_openat doesn't recurse to | |
21 | rpl_openat. */ | |
22 | #define __need_system_fcntl_h | |
23 | #include <config.h> | |
24 | ||
25 | /* Get the original definition of open. It might be defined as a macro. */ | |
26 | #include <fcntl.h> | |
27 | #include <sys/types.h> | |
28 | #undef __need_system_fcntl_h | |
29 | ||
30 | #if HAVE_OPENAT | |
31 | static int | |
32 | orig_openat (int fd, char const *filename, int flags, mode_t mode) | |
33 | { | |
34 | return openat (fd, filename, flags, mode); | |
35 | } | |
36 | #endif | |
37 | ||
38 | /* Write "fcntl.h" here, not <fcntl.h>, otherwise OSF/1 5.1 DTK cc eliminates | |
39 | this include because of the preliminary #include <fcntl.h> above. */ | |
40 | #include "fcntl.h" | |
41 | ||
42 | #include "openat.h" | |
43 | ||
7a6dbc2f SDJ |
44 | #include "cloexec.h" |
45 | ||
6ec2e0f5 SDJ |
46 | #include <stdarg.h> |
47 | #include <stdbool.h> | |
48 | #include <stddef.h> | |
49 | #include <string.h> | |
50 | #include <sys/stat.h> | |
51 | #include <errno.h> | |
52 | ||
53 | #if HAVE_OPENAT | |
54 | ||
7a6dbc2f SDJ |
55 | /* Like openat, but support O_CLOEXEC and work around Solaris 9 bugs |
56 | with trailing slash. */ | |
6ec2e0f5 SDJ |
57 | int |
58 | rpl_openat (int dfd, char const *filename, int flags, ...) | |
59 | { | |
7a6dbc2f SDJ |
60 | /* 0 = unknown, 1 = yes, -1 = no. */ |
61 | #if GNULIB_defined_O_CLOEXEC | |
62 | int have_cloexec = -1; | |
63 | #else | |
64 | static int have_cloexec; | |
65 | #endif | |
66 | ||
6ec2e0f5 SDJ |
67 | mode_t mode; |
68 | int fd; | |
69 | ||
70 | mode = 0; | |
71 | if (flags & O_CREAT) | |
72 | { | |
73 | va_list arg; | |
74 | va_start (arg, flags); | |
75 | ||
76 | /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4 | |
77 | creates crashing code when 'mode_t' is smaller than 'int'. */ | |
78 | mode = va_arg (arg, PROMOTED_MODE_T); | |
79 | ||
80 | va_end (arg); | |
81 | } | |
82 | ||
83 | # if OPEN_TRAILING_SLASH_BUG | |
84 | /* If the filename ends in a slash and one of O_CREAT, O_WRONLY, O_RDWR | |
85 | is specified, then fail. | |
86 | Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html> | |
87 | says that | |
88 | "A pathname that contains at least one non-slash character and that | |
89 | ends with one or more trailing slashes shall be resolved as if a | |
90 | single dot character ( '.' ) were appended to the pathname." | |
91 | and | |
92 | "The special filename dot shall refer to the directory specified by | |
93 | its predecessor." | |
94 | If the named file already exists as a directory, then | |
95 | - if O_CREAT is specified, open() must fail because of the semantics | |
96 | of O_CREAT, | |
97 | - if O_WRONLY or O_RDWR is specified, open() must fail because POSIX | |
98 | <http://www.opengroup.org/susv3/functions/open.html> says that it | |
99 | fails with errno = EISDIR in this case. | |
100 | If the named file does not exist or does not name a directory, then | |
101 | - if O_CREAT is specified, open() must fail since open() cannot create | |
102 | directories, | |
103 | - if O_WRONLY or O_RDWR is specified, open() must fail because the | |
104 | file does not contain a '.' directory. */ | |
105 | if (flags & (O_CREAT | O_WRONLY | O_RDWR)) | |
106 | { | |
107 | size_t len = strlen (filename); | |
108 | if (len > 0 && filename[len - 1] == '/') | |
109 | { | |
110 | errno = EISDIR; | |
111 | return -1; | |
112 | } | |
113 | } | |
114 | # endif | |
115 | ||
7a6dbc2f SDJ |
116 | fd = orig_openat (dfd, filename, |
117 | flags & ~(have_cloexec <= 0 ? O_CLOEXEC : 0), mode); | |
118 | ||
119 | if (flags & O_CLOEXEC) | |
120 | { | |
121 | if (! have_cloexec) | |
122 | { | |
123 | if (0 <= fd) | |
124 | have_cloexec = 1; | |
125 | else if (errno == EINVAL) | |
126 | { | |
127 | fd = orig_openat (dfd, filename, flags & ~O_CLOEXEC, mode); | |
128 | have_cloexec = -1; | |
129 | } | |
130 | } | |
131 | if (have_cloexec < 0 && 0 <= fd) | |
132 | set_cloexec_flag (fd, true); | |
133 | } | |
134 | ||
6ec2e0f5 SDJ |
135 | |
136 | # if OPEN_TRAILING_SLASH_BUG | |
137 | /* If the filename ends in a slash and fd does not refer to a directory, | |
138 | then fail. | |
139 | Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html> | |
140 | says that | |
141 | "A pathname that contains at least one non-slash character and that | |
142 | ends with one or more trailing slashes shall be resolved as if a | |
143 | single dot character ( '.' ) were appended to the pathname." | |
144 | and | |
145 | "The special filename dot shall refer to the directory specified by | |
146 | its predecessor." | |
147 | If the named file without the slash is not a directory, open() must fail | |
148 | with ENOTDIR. */ | |
149 | if (fd >= 0) | |
150 | { | |
151 | /* We know len is positive, since open did not fail with ENOENT. */ | |
152 | size_t len = strlen (filename); | |
153 | if (filename[len - 1] == '/') | |
154 | { | |
155 | struct stat statbuf; | |
156 | ||
157 | if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode)) | |
158 | { | |
159 | close (fd); | |
160 | errno = ENOTDIR; | |
161 | return -1; | |
162 | } | |
163 | } | |
164 | } | |
165 | # endif | |
166 | ||
167 | return fd; | |
168 | } | |
169 | ||
170 | #else /* !HAVE_OPENAT */ | |
171 | ||
172 | # include "dosname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */ | |
173 | # include "openat-priv.h" | |
174 | # include "save-cwd.h" | |
175 | ||
176 | /* Replacement for Solaris' openat function. | |
7a6dbc2f | 177 | <https://www.google.com/search?q=openat+site:docs.oracle.com> |
6ec2e0f5 SDJ |
178 | First, try to simulate it via open ("/proc/self/fd/FD/FILE"). |
179 | Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd. | |
180 | If either the save_cwd or the restore_cwd fails (relatively unlikely), | |
181 | then give a diagnostic and exit nonzero. | |
182 | Otherwise, upon failure, set errno and return -1, as openat does. | |
183 | Upon successful completion, return a file descriptor. */ | |
184 | int | |
185 | openat (int fd, char const *file, int flags, ...) | |
186 | { | |
187 | mode_t mode = 0; | |
188 | ||
189 | if (flags & O_CREAT) | |
190 | { | |
191 | va_list arg; | |
192 | va_start (arg, flags); | |
193 | ||
194 | /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4 | |
195 | creates crashing code when 'mode_t' is smaller than 'int'. */ | |
196 | mode = va_arg (arg, PROMOTED_MODE_T); | |
197 | ||
198 | va_end (arg); | |
199 | } | |
200 | ||
201 | return openat_permissive (fd, file, flags, mode, NULL); | |
202 | } | |
203 | ||
204 | /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is | |
205 | nonnull, set *CWD_ERRNO to an errno value if unable to save | |
206 | or restore the initial working directory. This is needed only | |
207 | the first time remove.c's remove_dir opens a command-line | |
208 | directory argument. | |
209 | ||
210 | If a previous attempt to restore the current working directory | |
211 | failed, then we must not even try to access a '.'-relative name. | |
212 | It is the caller's responsibility not to call this function | |
213 | in that case. */ | |
214 | ||
215 | int | |
216 | openat_permissive (int fd, char const *file, int flags, mode_t mode, | |
217 | int *cwd_errno) | |
218 | { | |
219 | struct saved_cwd saved_cwd; | |
220 | int saved_errno; | |
221 | int err; | |
222 | bool save_ok; | |
223 | ||
224 | if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file)) | |
225 | return open (file, flags, mode); | |
226 | ||
227 | { | |
228 | char buf[OPENAT_BUFFER_SIZE]; | |
229 | char *proc_file = openat_proc_name (buf, fd, file); | |
230 | if (proc_file) | |
231 | { | |
232 | int open_result = open (proc_file, flags, mode); | |
233 | int open_errno = errno; | |
234 | if (proc_file != buf) | |
235 | free (proc_file); | |
236 | /* If the syscall succeeds, or if it fails with an unexpected | |
237 | errno value, then return right away. Otherwise, fall through | |
238 | and resort to using save_cwd/restore_cwd. */ | |
239 | if (0 <= open_result || ! EXPECTED_ERRNO (open_errno)) | |
240 | { | |
241 | errno = open_errno; | |
242 | return open_result; | |
243 | } | |
244 | } | |
245 | } | |
246 | ||
247 | save_ok = (save_cwd (&saved_cwd) == 0); | |
248 | if (! save_ok) | |
249 | { | |
250 | if (! cwd_errno) | |
251 | openat_save_fail (errno); | |
252 | *cwd_errno = errno; | |
253 | } | |
254 | if (0 <= fd && fd == saved_cwd.desc) | |
255 | { | |
256 | /* If saving the working directory collides with the user's | |
257 | requested fd, then the user's fd must have been closed to | |
258 | begin with. */ | |
259 | free_cwd (&saved_cwd); | |
260 | errno = EBADF; | |
261 | return -1; | |
262 | } | |
263 | ||
264 | err = fchdir (fd); | |
265 | saved_errno = errno; | |
266 | ||
267 | if (! err) | |
268 | { | |
269 | err = open (file, flags, mode); | |
270 | saved_errno = errno; | |
271 | if (save_ok && restore_cwd (&saved_cwd) != 0) | |
272 | { | |
273 | if (! cwd_errno) | |
274 | { | |
275 | /* Don't write a message to just-created fd 2. */ | |
276 | saved_errno = errno; | |
277 | if (err == STDERR_FILENO) | |
278 | close (err); | |
279 | openat_restore_fail (saved_errno); | |
280 | } | |
281 | *cwd_errno = errno; | |
282 | } | |
283 | } | |
284 | ||
285 | free_cwd (&saved_cwd); | |
286 | errno = saved_errno; | |
287 | return err; | |
288 | } | |
289 | ||
290 | /* Return true if our openat implementation must resort to | |
291 | using save_cwd and restore_cwd. */ | |
292 | bool | |
293 | openat_needs_fchdir (void) | |
294 | { | |
295 | bool needs_fchdir = true; | |
296 | int fd = open ("/", O_SEARCH); | |
297 | ||
298 | if (0 <= fd) | |
299 | { | |
300 | char buf[OPENAT_BUFFER_SIZE]; | |
301 | char *proc_file = openat_proc_name (buf, fd, "."); | |
302 | if (proc_file) | |
303 | { | |
304 | needs_fchdir = false; | |
305 | if (proc_file != buf) | |
306 | free (proc_file); | |
307 | } | |
308 | close (fd); | |
309 | } | |
310 | ||
311 | return needs_fchdir; | |
312 | } | |
313 | ||
314 | #endif /* !HAVE_OPENAT */ |