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