1/* provide a replacement openat function 2 Copyright (C) 2004, 2005, 2006, 2007 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 26#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */ 27#include "fcntl--.h" 28#include "lstat.h" 29#include "openat-priv.h" 30#include "save-cwd.h" 31 32/* Replacement for Solaris' openat function. 33 <http://www.google.com/search?q=openat+site:docs.sun.com> 34 First, try to simulate it via open ("/proc/self/fd/FD/FILE"). 35 Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd. 36 If either the save_cwd or the restore_cwd fails (relatively unlikely), 37 then give a diagnostic and exit nonzero. 38 Otherwise, upon failure, set errno and return -1, as openat does. 39 Upon successful completion, return a file descriptor. */ 40int 41openat (int fd, char const *file, int flags, ...) 42{ 43 mode_t mode = 0; 44 45 if (flags & O_CREAT) 46 { 47 va_list arg; 48 va_start (arg, flags); 49 50 /* If mode_t is narrower than int, use the promoted type (int), 51 not mode_t. Use sizeof to guess whether mode_t is narrower; 52 we don't know of any practical counterexamples. */ 53 mode = (sizeof (mode_t) < sizeof (int) 54 ? va_arg (arg, int) 55 : va_arg (arg, mode_t)); 56 57 va_end (arg); 58 } 59 60 return openat_permissive (fd, file, flags, mode, NULL); 61} 62 63/* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is 64 nonnull, set *CWD_ERRNO to an errno value if unable to save 65 or restore the initial working directory. This is needed only 66 the first time remove.c's remove_dir opens a command-line 67 directory argument. 68 69 If a previous attempt to restore the current working directory 70 failed, then we must not even try to access a `.'-relative name. 71 It is the caller's responsibility not to call this function 72 in that case. */ 73 74int 75openat_permissive (int fd, char const *file, int flags, mode_t mode, 76 int *cwd_errno) 77{ 78 struct saved_cwd saved_cwd; 79 int saved_errno; 80 int err; 81 bool save_ok; 82 83 if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file)) 84 return open (file, flags, mode); 85 86 { 87 char buf[OPENAT_BUFFER_SIZE]; 88 char *proc_file = openat_proc_name (buf, fd, file); 89 if (proc_file) 90 { 91 int open_result = open (proc_file, flags, mode); 92 int open_errno = errno; 93 if (proc_file != buf) 94 free (proc_file); 95 /* If the syscall succeeds, or if it fails with an unexpected 96 errno value, then return right away. Otherwise, fall through 97 and resort to using save_cwd/restore_cwd. */ 98 if (0 <= open_result || ! EXPECTED_ERRNO (open_errno)) 99 { 100 errno = open_errno; 101 return open_result; 102 } 103 } 104 } 105 106 save_ok = (save_cwd (&saved_cwd) == 0); 107 if (! save_ok) 108 { 109 if (! cwd_errno) 110 openat_save_fail (errno); 111 *cwd_errno = errno; 112 } 113 114 err = fchdir (fd); 115 saved_errno = errno; 116 117 if (! err) 118 { 119 err = open (file, flags, mode); 120 saved_errno = errno; 121 if (save_ok && restore_cwd (&saved_cwd) != 0) 122 { 123 if (! cwd_errno) 124 openat_restore_fail (errno); 125 *cwd_errno = errno; 126 } 127 } 128 129 free_cwd (&saved_cwd); 130 errno = saved_errno; 131 return err; 132} 133 134/* Return true if our openat implementation must resort to 135 using save_cwd and restore_cwd. */ 136bool 137openat_needs_fchdir (void) 138{ 139 bool needs_fchdir = true; 140 int fd = open ("/", O_RDONLY); 141 142 if (0 <= fd) 143 { 144 char buf[OPENAT_BUFFER_SIZE]; 145 char *proc_file = openat_proc_name (buf, fd, "."); 146 if (proc_file) 147 { 148 needs_fchdir = false; 149 if (proc_file != buf) 150 free (proc_file); 151 } 152 close (fd); 153 } 154 155 return needs_fchdir; 156} 157 158#if !HAVE_FDOPENDIR 159 160/* Replacement for Solaris' function by the same name. 161 <http://www.google.com/search?q=fdopendir+site:docs.sun.com> 162 First, try to simulate it via opendir ("/proc/self/fd/FD"). Failing 163 that, simulate it by doing save_cwd/fchdir/opendir(".")/restore_cwd. 164 If either the save_cwd or the restore_cwd fails (relatively unlikely), 165 then give a diagnostic and exit nonzero. 166 Otherwise, this function works just like Solaris' fdopendir. 167 168 W A R N I N G: 169 Unlike the other fd-related functions here, this one 170 effectively consumes its FD parameter. The caller should not 171 close or otherwise manipulate FD if this function returns successfully. */ 172DIR * 173fdopendir (int fd) 174{ 175 struct saved_cwd saved_cwd; 176 int saved_errno; 177 DIR *dir; 178 179 char buf[OPENAT_BUFFER_SIZE]; 180 char *proc_file = openat_proc_name (buf, fd, "."); 181 if (proc_file) 182 { 183 dir = opendir (proc_file); 184 saved_errno = errno; 185 } 186 else 187 { 188 dir = NULL; 189 saved_errno = EOPNOTSUPP; 190 } 191 192 /* If the syscall fails with an expected errno value, resort to 193 save_cwd/restore_cwd. */ 194 if (! dir && EXPECTED_ERRNO (saved_errno)) 195 { 196 if (save_cwd (&saved_cwd) != 0) 197 openat_save_fail (errno); 198 199 if (fchdir (fd) != 0) 200 { 201 dir = NULL; 202 saved_errno = errno; 203 } 204 else 205 { 206 dir = opendir ("."); 207 saved_errno = errno; 208 209 if (restore_cwd (&saved_cwd) != 0) 210 openat_restore_fail (errno); 211 } 212 213 free_cwd (&saved_cwd); 214 } 215 216 if (dir) 217 close (fd); 218 if (proc_file != buf) 219 free (proc_file); 220 errno = saved_errno; 221 return dir; 222} 223 224#endif 225 226/* Replacement for Solaris' function by the same name. 227 <http://www.google.com/search?q=fstatat+site:docs.sun.com> 228 First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE"). 229 Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd. 230 If either the save_cwd or the restore_cwd fails (relatively unlikely), 231 then give a diagnostic and exit nonzero. 232 Otherwise, this function works just like Solaris' fstatat. */ 233 234#define AT_FUNC_NAME fstatat 235#define AT_FUNC_F1 lstat 236#define AT_FUNC_F2 stat 237#define AT_FUNC_USE_F1_COND flag == AT_SYMLINK_NOFOLLOW 238#define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag 239#define AT_FUNC_POST_FILE_ARGS , st 240#include "at-func.c" 241#undef AT_FUNC_NAME 242#undef AT_FUNC_F1 243#undef AT_FUNC_F2 244#undef AT_FUNC_USE_F1_COND 245#undef AT_FUNC_POST_FILE_PARAM_DECLS 246#undef AT_FUNC_POST_FILE_ARGS 247 248/* Replacement for Solaris' function by the same name. 249 <http://www.google.com/search?q=unlinkat+site:docs.sun.com> 250 First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE"). 251 Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd. 252 If either the save_cwd or the restore_cwd fails (relatively unlikely), 253 then give a diagnostic and exit nonzero. 254 Otherwise, this function works just like Solaris' unlinkat. */ 255 256#define AT_FUNC_NAME unlinkat 257#define AT_FUNC_F1 rmdir 258#define AT_FUNC_F2 unlink 259#define AT_FUNC_USE_F1_COND flag == AT_REMOVEDIR 260#define AT_FUNC_POST_FILE_PARAM_DECLS , int flag 261#define AT_FUNC_POST_FILE_ARGS /* empty */ 262#include "at-func.c" 263#undef AT_FUNC_NAME 264#undef AT_FUNC_F1 265#undef AT_FUNC_F2 266#undef AT_FUNC_USE_F1_COND 267#undef AT_FUNC_POST_FILE_PARAM_DECLS 268#undef AT_FUNC_POST_FILE_ARGS 269