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