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