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