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