Commit | Line | Data |
---|---|---|
6ec2e0f5 | 1 | /* provide a replacement openat function |
9c9d63b1 | 2 | Copyright (C) 2004-2021 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. */ | |
698be2d8 CB |
103 | if ((flags & O_CREAT) |
104 | || (flags & O_ACCMODE) == O_RDWR | |
105 | || (flags & O_ACCMODE) == O_WRONLY) | |
6ec2e0f5 SDJ |
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 | ||
c0c3707f | 116 | fd = orig_openat (dfd, filename, |
698be2d8 | 117 | flags & ~(have_cloexec < 0 ? O_CLOEXEC : 0), mode); |
c0c3707f CB |
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. | |
c0c3707f CB |
139 | Rationale: POSIX says such a filename must name a directory |
140 | <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>: | |
141 | "A pathname that contains at least one non-<slash> character and that | |
142 | ends with one or more trailing <slash> characters shall not be resolved | |
143 | successfully unless the last pathname component before the trailing | |
144 | <slash> characters names an existing directory" | |
6ec2e0f5 SDJ |
145 | If the named file without the slash is not a directory, open() must fail |
146 | with ENOTDIR. */ | |
147 | if (fd >= 0) | |
148 | { | |
149 | /* We know len is positive, since open did not fail with ENOENT. */ | |
150 | size_t len = strlen (filename); | |
151 | if (filename[len - 1] == '/') | |
152 | { | |
153 | struct stat statbuf; | |
154 | ||
155 | if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode)) | |
156 | { | |
157 | close (fd); | |
158 | errno = ENOTDIR; | |
159 | return -1; | |
160 | } | |
161 | } | |
162 | } | |
163 | # endif | |
164 | ||
165 | return fd; | |
166 | } | |
167 | ||
168 | #else /* !HAVE_OPENAT */ | |
169 | ||
698be2d8 | 170 | # include "filename.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */ |
6ec2e0f5 SDJ |
171 | # include "openat-priv.h" |
172 | # include "save-cwd.h" | |
173 | ||
174 | /* Replacement for Solaris' openat function. | |
c0c3707f | 175 | <https://www.google.com/search?q=openat+site:docs.oracle.com> |
6ec2e0f5 SDJ |
176 | First, try to simulate it via open ("/proc/self/fd/FD/FILE"). |
177 | Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd. | |
178 | If either the save_cwd or the restore_cwd fails (relatively unlikely), | |
179 | then give a diagnostic and exit nonzero. | |
180 | Otherwise, upon failure, set errno and return -1, as openat does. | |
181 | Upon successful completion, return a file descriptor. */ | |
182 | int | |
183 | openat (int fd, char const *file, int flags, ...) | |
184 | { | |
185 | mode_t mode = 0; | |
186 | ||
187 | if (flags & O_CREAT) | |
188 | { | |
189 | va_list arg; | |
190 | va_start (arg, flags); | |
191 | ||
192 | /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4 | |
193 | creates crashing code when 'mode_t' is smaller than 'int'. */ | |
194 | mode = va_arg (arg, PROMOTED_MODE_T); | |
195 | ||
196 | va_end (arg); | |
197 | } | |
198 | ||
199 | return openat_permissive (fd, file, flags, mode, NULL); | |
200 | } | |
201 | ||
202 | /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is | |
203 | nonnull, set *CWD_ERRNO to an errno value if unable to save | |
204 | or restore the initial working directory. This is needed only | |
205 | the first time remove.c's remove_dir opens a command-line | |
206 | directory argument. | |
207 | ||
208 | If a previous attempt to restore the current working directory | |
209 | failed, then we must not even try to access a '.'-relative name. | |
210 | It is the caller's responsibility not to call this function | |
211 | in that case. */ | |
212 | ||
213 | int | |
214 | openat_permissive (int fd, char const *file, int flags, mode_t mode, | |
215 | int *cwd_errno) | |
216 | { | |
217 | struct saved_cwd saved_cwd; | |
218 | int saved_errno; | |
219 | int err; | |
220 | bool save_ok; | |
221 | ||
222 | if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file)) | |
223 | return open (file, flags, mode); | |
224 | ||
225 | { | |
226 | char buf[OPENAT_BUFFER_SIZE]; | |
227 | char *proc_file = openat_proc_name (buf, fd, file); | |
228 | if (proc_file) | |
229 | { | |
230 | int open_result = open (proc_file, flags, mode); | |
231 | int open_errno = errno; | |
232 | if (proc_file != buf) | |
233 | free (proc_file); | |
234 | /* If the syscall succeeds, or if it fails with an unexpected | |
235 | errno value, then return right away. Otherwise, fall through | |
236 | and resort to using save_cwd/restore_cwd. */ | |
237 | if (0 <= open_result || ! EXPECTED_ERRNO (open_errno)) | |
238 | { | |
239 | errno = open_errno; | |
240 | return open_result; | |
241 | } | |
242 | } | |
243 | } | |
244 | ||
245 | save_ok = (save_cwd (&saved_cwd) == 0); | |
246 | if (! save_ok) | |
247 | { | |
248 | if (! cwd_errno) | |
249 | openat_save_fail (errno); | |
250 | *cwd_errno = errno; | |
251 | } | |
252 | if (0 <= fd && fd == saved_cwd.desc) | |
253 | { | |
254 | /* If saving the working directory collides with the user's | |
255 | requested fd, then the user's fd must have been closed to | |
256 | begin with. */ | |
257 | free_cwd (&saved_cwd); | |
258 | errno = EBADF; | |
259 | return -1; | |
260 | } | |
261 | ||
262 | err = fchdir (fd); | |
263 | saved_errno = errno; | |
264 | ||
265 | if (! err) | |
266 | { | |
267 | err = open (file, flags, mode); | |
268 | saved_errno = errno; | |
269 | if (save_ok && restore_cwd (&saved_cwd) != 0) | |
270 | { | |
271 | if (! cwd_errno) | |
272 | { | |
273 | /* Don't write a message to just-created fd 2. */ | |
274 | saved_errno = errno; | |
275 | if (err == STDERR_FILENO) | |
276 | close (err); | |
277 | openat_restore_fail (saved_errno); | |
278 | } | |
279 | *cwd_errno = errno; | |
280 | } | |
281 | } | |
282 | ||
283 | free_cwd (&saved_cwd); | |
284 | errno = saved_errno; | |
285 | return err; | |
286 | } | |
287 | ||
288 | /* Return true if our openat implementation must resort to | |
289 | using save_cwd and restore_cwd. */ | |
290 | bool | |
291 | openat_needs_fchdir (void) | |
292 | { | |
293 | bool needs_fchdir = true; | |
698be2d8 | 294 | int fd = open ("/", O_SEARCH | O_CLOEXEC); |
6ec2e0f5 SDJ |
295 | |
296 | if (0 <= fd) | |
297 | { | |
298 | char buf[OPENAT_BUFFER_SIZE]; | |
299 | char *proc_file = openat_proc_name (buf, fd, "."); | |
300 | if (proc_file) | |
301 | { | |
302 | needs_fchdir = false; | |
303 | if (proc_file != buf) | |
304 | free (proc_file); | |
305 | } | |
306 | close (fd); | |
307 | } | |
308 | ||
309 | return needs_fchdir; | |
310 | } | |
311 | ||
312 | #endif /* !HAVE_OPENAT */ |