openat: make template easier to use
[gnulib.git] / lib / openat.c
1 /* provide a replacement openat function
2    Copyright (C) 2004-2009 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 <sys/stat.h>
26
27 #include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
28 #include "openat-priv.h"
29 #include "save-cwd.h"
30
31 /* We can't use "fcntl--.h", so that openat_safer does not interfere.  */
32 #if GNULIB_FCNTL_SAFER
33 # include "fcntl-safer.h"
34 # undef open
35 # define open open_safer
36 #endif
37
38 /* Replacement for Solaris' openat function.
39    <http://www.google.com/search?q=openat+site:docs.sun.com>
40    First, try to simulate it via open ("/proc/self/fd/FD/FILE").
41    Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
42    If either the save_cwd or the restore_cwd fails (relatively unlikely),
43    then give a diagnostic and exit nonzero.
44    Otherwise, upon failure, set errno and return -1, as openat does.
45    Upon successful completion, return a file descriptor.  */
46 int
47 openat (int fd, char const *file, int flags, ...)
48 {
49   mode_t mode = 0;
50
51   if (flags & O_CREAT)
52     {
53       va_list arg;
54       va_start (arg, flags);
55
56       /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
57          creates crashing code when 'mode_t' is smaller than 'int'.  */
58       mode = va_arg (arg, PROMOTED_MODE_T);
59
60       va_end (arg);
61     }
62
63   return openat_permissive (fd, file, flags, mode, NULL);
64 }
65
66 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
67    nonnull, set *CWD_ERRNO to an errno value if unable to save
68    or restore the initial working directory.  This is needed only
69    the first time remove.c's remove_dir opens a command-line
70    directory argument.
71
72    If a previous attempt to restore the current working directory
73    failed, then we must not even try to access a `.'-relative name.
74    It is the caller's responsibility not to call this function
75    in that case.  */
76
77 int
78 openat_permissive (int fd, char const *file, int flags, mode_t mode,
79                    int *cwd_errno)
80 {
81   struct saved_cwd saved_cwd;
82   int saved_errno;
83   int err;
84   bool save_ok;
85
86   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
87     return open (file, flags, mode);
88
89   {
90     char buf[OPENAT_BUFFER_SIZE];
91     char *proc_file = openat_proc_name (buf, fd, file);
92     if (proc_file)
93       {
94         int open_result = open (proc_file, flags, mode);
95         int open_errno = errno;
96         if (proc_file != buf)
97           free (proc_file);
98         /* If the syscall succeeds, or if it fails with an unexpected
99            errno value, then return right away.  Otherwise, fall through
100            and resort to using save_cwd/restore_cwd.  */
101         if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
102           {
103             errno = open_errno;
104             return open_result;
105           }
106       }
107   }
108
109   save_ok = (save_cwd (&saved_cwd) == 0);
110   if (! save_ok)
111     {
112       if (! cwd_errno)
113         openat_save_fail (errno);
114       *cwd_errno = errno;
115     }
116
117   err = fchdir (fd);
118   saved_errno = errno;
119
120   if (! err)
121     {
122       err = open (file, flags, mode);
123       saved_errno = errno;
124       if (save_ok && restore_cwd (&saved_cwd) != 0)
125         {
126           if (! cwd_errno)
127             openat_restore_fail (errno);
128           *cwd_errno = errno;
129         }
130     }
131
132   free_cwd (&saved_cwd);
133   errno = saved_errno;
134   return err;
135 }
136
137 /* Return true if our openat implementation must resort to
138    using save_cwd and restore_cwd.  */
139 bool
140 openat_needs_fchdir (void)
141 {
142   bool needs_fchdir = true;
143   int fd = open ("/", O_RDONLY);
144
145   if (0 <= fd)
146     {
147       char buf[OPENAT_BUFFER_SIZE];
148       char *proc_file = openat_proc_name (buf, fd, ".");
149       if (proc_file)
150         {
151           needs_fchdir = false;
152           if (proc_file != buf)
153             free (proc_file);
154         }
155       close (fd);
156     }
157
158   return needs_fchdir;
159 }
160
161 /* Replacement for Solaris' function by the same name.
162    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
163    First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
164    Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
165    If either the save_cwd or the restore_cwd fails (relatively unlikely),
166    then give a diagnostic and exit nonzero.
167    Otherwise, this function works just like Solaris' fstatat.  */
168
169 #define AT_FUNC_NAME fstatat
170 #define AT_FUNC_F1 lstat
171 #define AT_FUNC_F2 stat
172 #define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
173 #define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
174 #define AT_FUNC_POST_FILE_ARGS        , st
175 #include "at-func.c"
176 #undef AT_FUNC_NAME
177 #undef AT_FUNC_F1
178 #undef AT_FUNC_F2
179 #undef AT_FUNC_USE_F1_COND
180 #undef AT_FUNC_POST_FILE_PARAM_DECLS
181 #undef AT_FUNC_POST_FILE_ARGS
182
183 /* Replacement for Solaris' function by the same name.
184    <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
185    First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
186    Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
187    If either the save_cwd or the restore_cwd fails (relatively unlikely),
188    then give a diagnostic and exit nonzero.
189    Otherwise, this function works just like Solaris' unlinkat.  */
190
191 #define AT_FUNC_NAME unlinkat
192 #define AT_FUNC_F1 rmdir
193 #define AT_FUNC_F2 unlink
194 #define AT_FUNC_USE_F1_COND AT_REMOVEDIR
195 #define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
196 #define AT_FUNC_POST_FILE_ARGS        /* empty */
197 #include "at-func.c"
198 #undef AT_FUNC_NAME
199 #undef AT_FUNC_F1
200 #undef AT_FUNC_F2
201 #undef AT_FUNC_USE_F1_COND
202 #undef AT_FUNC_POST_FILE_PARAM_DECLS
203 #undef AT_FUNC_POST_FILE_ARGS