Bump to m4 1.4.19
[platform/upstream/m4.git] / lib / openat.c
1 /* provide a replacement openat function
2    Copyright (C) 2004-2021 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 <stdlib.h>
50 #include <string.h>
51 #include <sys/stat.h>
52 #include <errno.h>
53
54 #if HAVE_OPENAT
55
56 /* Like openat, but support O_CLOEXEC and work around Solaris 9 bugs
57    with trailing slash.  */
58 int
59 rpl_openat (int dfd, char const *filename, int flags, ...)
60 {
61   /* 0 = unknown, 1 = yes, -1 = no.  */
62 #if GNULIB_defined_O_CLOEXEC
63   int have_cloexec = -1;
64 #else
65   static int have_cloexec;
66 #endif
67
68   mode_t mode;
69   int fd;
70
71   mode = 0;
72   if (flags & O_CREAT)
73     {
74       va_list arg;
75       va_start (arg, flags);
76
77       /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
78          creates crashing code when 'mode_t' is smaller than 'int'.  */
79       mode = va_arg (arg, PROMOTED_MODE_T);
80
81       va_end (arg);
82     }
83
84 # if OPEN_TRAILING_SLASH_BUG
85   /* Fail if one of O_CREAT, O_WRONLY, O_RDWR is specified and the filename
86      ends in a slash, as POSIX says such a filename must name a directory
87      <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
88        "A pathname that contains at least one non-<slash> character and that
89         ends with one or more trailing <slash> characters shall not be resolved
90         successfully unless the last pathname component before the trailing
91         <slash> characters names an existing directory"
92      If the named file already exists as a directory, then
93        - if O_CREAT is specified, open() must fail because of the semantics
94          of O_CREAT,
95        - if O_WRONLY or O_RDWR is specified, open() must fail because POSIX
96          <https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html>
97          says that it fails with errno = EISDIR in this case.
98      If the named file does not exist or does not name a directory, then
99        - if O_CREAT is specified, open() must fail since open() cannot create
100          directories,
101        - if O_WRONLY or O_RDWR is specified, open() must fail because the
102          file does not contain a '.' directory.  */
103   if ((flags & O_CREAT)
104       || (flags & O_ACCMODE) == O_RDWR
105       || (flags & O_ACCMODE) == O_WRONLY)
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 says such a filename must name a directory
140      <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
141        "A pathname that contains at least one non-<slash> character and that
142         ends with one or more trailing <slash> characters shall not be resolved
143         successfully unless the last pathname component before the trailing
144         <slash> characters names an existing directory"
145      If the named file without the slash is not a directory, open() must fail
146      with ENOTDIR.  */
147   if (fd >= 0)
148     {
149       /* We know len is positive, since open did not fail with ENOENT.  */
150       size_t len = strlen (filename);
151       if (filename[len - 1] == '/')
152         {
153           struct stat statbuf;
154
155           if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
156             {
157               close (fd);
158               errno = ENOTDIR;
159               return -1;
160             }
161         }
162     }
163 # endif
164
165   return fd;
166 }
167
168 #else /* !HAVE_OPENAT */
169
170 # include "filename.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
171 # include "openat-priv.h"
172 # include "save-cwd.h"
173
174 /* Replacement for Solaris' openat function.
175    <https://www.google.com/search?q=openat+site:docs.oracle.com>
176    First, try to simulate it via open ("/proc/self/fd/FD/FILE").
177    Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
178    If either the save_cwd or the restore_cwd fails (relatively unlikely),
179    then give a diagnostic and exit nonzero.
180    Otherwise, upon failure, set errno and return -1, as openat does.
181    Upon successful completion, return a file descriptor.  */
182 int
183 openat (int fd, char const *file, int flags, ...)
184 {
185   mode_t mode = 0;
186
187   if (flags & O_CREAT)
188     {
189       va_list arg;
190       va_start (arg, flags);
191
192       /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
193          creates crashing code when 'mode_t' is smaller than 'int'.  */
194       mode = va_arg (arg, PROMOTED_MODE_T);
195
196       va_end (arg);
197     }
198
199   return openat_permissive (fd, file, flags, mode, NULL);
200 }
201
202 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
203    nonnull, set *CWD_ERRNO to an errno value if unable to save
204    or restore the initial working directory.  This is needed only
205    the first time remove.c's remove_dir opens a command-line
206    directory argument.
207
208    If a previous attempt to restore the current working directory
209    failed, then we must not even try to access a '.'-relative name.
210    It is the caller's responsibility not to call this function
211    in that case.  */
212
213 int
214 openat_permissive (int fd, char const *file, int flags, mode_t mode,
215                    int *cwd_errno)
216 {
217   struct saved_cwd saved_cwd;
218   int saved_errno;
219   int err;
220   bool save_ok;
221
222   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
223     return open (file, flags, mode);
224
225   {
226     char buf[OPENAT_BUFFER_SIZE];
227     char *proc_file = openat_proc_name (buf, fd, file);
228     if (proc_file)
229       {
230         int open_result = open (proc_file, flags, mode);
231         int open_errno = errno;
232         if (proc_file != buf)
233           free (proc_file);
234         /* If the syscall succeeds, or if it fails with an unexpected
235            errno value, then return right away.  Otherwise, fall through
236            and resort to using save_cwd/restore_cwd.  */
237         if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
238           {
239             errno = open_errno;
240             return open_result;
241           }
242       }
243   }
244
245   save_ok = (save_cwd (&saved_cwd) == 0);
246   if (! save_ok)
247     {
248       if (! cwd_errno)
249         openat_save_fail (errno);
250       *cwd_errno = errno;
251     }
252   if (0 <= fd && fd == saved_cwd.desc)
253     {
254       /* If saving the working directory collides with the user's
255          requested fd, then the user's fd must have been closed to
256          begin with.  */
257       free_cwd (&saved_cwd);
258       errno = EBADF;
259       return -1;
260     }
261
262   err = fchdir (fd);
263   saved_errno = errno;
264
265   if (! err)
266     {
267       err = open (file, flags, mode);
268       saved_errno = errno;
269       if (save_ok && restore_cwd (&saved_cwd) != 0)
270         {
271           if (! cwd_errno)
272             {
273               /* Don't write a message to just-created fd 2.  */
274               saved_errno = errno;
275               if (err == STDERR_FILENO)
276                 close (err);
277               openat_restore_fail (saved_errno);
278             }
279           *cwd_errno = errno;
280         }
281     }
282
283   free_cwd (&saved_cwd);
284   errno = saved_errno;
285   return err;
286 }
287
288 /* Return true if our openat implementation must resort to
289    using save_cwd and restore_cwd.  */
290 bool
291 openat_needs_fchdir (void)
292 {
293   bool needs_fchdir = true;
294   int fd = open ("/", O_SEARCH | O_CLOEXEC);
295
296   if (0 <= fd)
297     {
298       char buf[OPENAT_BUFFER_SIZE];
299       char *proc_file = openat_proc_name (buf, fd, ".");
300       if (proc_file)
301         {
302           needs_fchdir = false;
303           if (proc_file != buf)
304             free (proc_file);
305         }
306       close (fd);
307     }
308
309   return needs_fchdir;
310 }
311
312 #endif /* !HAVE_OPENAT */