openat: allow return of fd 0
[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 /* Replacement for Solaris' openat function.
32    <http://www.google.com/search?q=openat+site:docs.sun.com>
33    First, try to simulate it via open ("/proc/self/fd/FD/FILE").
34    Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
35    If either the save_cwd or the restore_cwd fails (relatively unlikely),
36    then give a diagnostic and exit nonzero.
37    Otherwise, upon failure, set errno and return -1, as openat does.
38    Upon successful completion, return a file descriptor.  */
39 int
40 openat (int fd, char const *file, int flags, ...)
41 {
42   mode_t mode = 0;
43
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   return openat_permissive (fd, file, flags, mode, NULL);
57 }
58
59 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
60    nonnull, set *CWD_ERRNO to an errno value if unable to save
61    or restore the initial working directory.  This is needed only
62    the first time remove.c's remove_dir opens a command-line
63    directory argument.
64
65    If a previous attempt to restore the current working directory
66    failed, then we must not even try to access a `.'-relative name.
67    It is the caller's responsibility not to call this function
68    in that case.  */
69
70 int
71 openat_permissive (int fd, char const *file, int flags, mode_t mode,
72                    int *cwd_errno)
73 {
74   struct saved_cwd saved_cwd;
75   int saved_errno;
76   int err;
77   bool save_ok;
78
79   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
80     return open (file, flags, mode);
81
82   {
83     char buf[OPENAT_BUFFER_SIZE];
84     char *proc_file = openat_proc_name (buf, fd, file);
85     if (proc_file)
86       {
87         int open_result = open (proc_file, flags, mode);
88         int open_errno = errno;
89         if (proc_file != buf)
90           free (proc_file);
91         /* If the syscall succeeds, or if it fails with an unexpected
92            errno value, then return right away.  Otherwise, fall through
93            and resort to using save_cwd/restore_cwd.  */
94         if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
95           {
96             errno = open_errno;
97             return open_result;
98           }
99       }
100   }
101
102   save_ok = (save_cwd (&saved_cwd) == 0);
103   if (! save_ok)
104     {
105       if (! cwd_errno)
106         openat_save_fail (errno);
107       *cwd_errno = errno;
108     }
109
110   err = fchdir (fd);
111   saved_errno = errno;
112
113   if (! err)
114     {
115       err = open (file, flags, mode);
116       saved_errno = errno;
117       if (save_ok && restore_cwd (&saved_cwd) != 0)
118         {
119           if (! cwd_errno)
120             {
121               /* Don't write a message to just-created fd 2.  */
122               saved_errno = errno;
123               if (err == STDERR_FILENO)
124                 close (err);
125               openat_restore_fail (saved_errno);
126             }
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.  */
138 bool
139 openat_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 /* Replacement for Solaris' function by the same name.
161    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
162    First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
163    Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/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' fstatat.  */
167
168 #define AT_FUNC_NAME fstatat
169 #define AT_FUNC_F1 lstat
170 #define AT_FUNC_F2 stat
171 #define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
172 #define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
173 #define AT_FUNC_POST_FILE_ARGS        , st
174 #include "at-func.c"
175 #undef AT_FUNC_NAME
176 #undef AT_FUNC_F1
177 #undef AT_FUNC_F2
178 #undef AT_FUNC_USE_F1_COND
179 #undef AT_FUNC_POST_FILE_PARAM_DECLS
180 #undef AT_FUNC_POST_FILE_ARGS
181
182 /* Replacement for Solaris' function by the same name.
183    <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
184    First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
185    Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
186    If either the save_cwd or the restore_cwd fails (relatively unlikely),
187    then give a diagnostic and exit nonzero.
188    Otherwise, this function works just like Solaris' unlinkat.  */
189
190 #define AT_FUNC_NAME unlinkat
191 #define AT_FUNC_F1 rmdir
192 #define AT_FUNC_F2 unlink
193 #define AT_FUNC_USE_F1_COND AT_REMOVEDIR
194 #define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
195 #define AT_FUNC_POST_FILE_ARGS        /* empty */
196 #include "at-func.c"
197 #undef AT_FUNC_NAME
198 #undef AT_FUNC_F1
199 #undef AT_FUNC_F2
200 #undef AT_FUNC_USE_F1_COND
201 #undef AT_FUNC_POST_FILE_PARAM_DECLS
202 #undef AT_FUNC_POST_FILE_ARGS