xref: /netbsd-src/external/gpl3/gdb/dist/gnulib/import/openat.c (revision 4b169a6ba595ae283ca507b26b15fdff40495b1c)
18dffb485Schristos /* provide a replacement openat function
2*4b169a6bSchristos    Copyright (C) 2004-2022 Free Software Foundation, Inc.
38dffb485Schristos 
48dffb485Schristos    This program is free software: you can redistribute it and/or modify
58dffb485Schristos    it under the terms of the GNU General Public License as published by
6*4b169a6bSchristos    the Free Software Foundation, either version 3 of the License, or
78dffb485Schristos    (at your option) any later version.
88dffb485Schristos 
98dffb485Schristos    This program is distributed in the hope that it will be useful,
108dffb485Schristos    but WITHOUT ANY WARRANTY; without even the implied warranty of
118dffb485Schristos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
128dffb485Schristos    GNU General Public License for more details.
138dffb485Schristos 
148dffb485Schristos    You should have received a copy of the GNU General Public License
158dffb485Schristos    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
168dffb485Schristos 
178dffb485Schristos /* written by Jim Meyering */
188dffb485Schristos 
198dffb485Schristos /* If the user's config.h happens to include <fcntl.h>, let it include only
208dffb485Schristos    the system's <fcntl.h> here, so that orig_openat doesn't recurse to
218dffb485Schristos    rpl_openat.  */
228dffb485Schristos #define __need_system_fcntl_h
238dffb485Schristos #include <config.h>
248dffb485Schristos 
258dffb485Schristos /* Get the original definition of open.  It might be defined as a macro.  */
268dffb485Schristos #include <fcntl.h>
278dffb485Schristos #include <sys/types.h>
288dffb485Schristos #undef __need_system_fcntl_h
298dffb485Schristos 
308dffb485Schristos #if HAVE_OPENAT
318dffb485Schristos static int
orig_openat(int fd,char const * filename,int flags,mode_t mode)328dffb485Schristos orig_openat (int fd, char const *filename, int flags, mode_t mode)
338dffb485Schristos {
348dffb485Schristos   return openat (fd, filename, flags, mode);
358dffb485Schristos }
368dffb485Schristos #endif
378dffb485Schristos 
388dffb485Schristos /* Write "fcntl.h" here, not <fcntl.h>, otherwise OSF/1 5.1 DTK cc eliminates
398dffb485Schristos    this include because of the preliminary #include <fcntl.h> above.  */
408dffb485Schristos #include "fcntl.h"
418dffb485Schristos 
428dffb485Schristos #include "openat.h"
438dffb485Schristos 
448dffb485Schristos #include "cloexec.h"
458dffb485Schristos 
468dffb485Schristos #include <stdarg.h>
478dffb485Schristos #include <stdbool.h>
488dffb485Schristos #include <stddef.h>
498dffb485Schristos #include <stdlib.h>
508dffb485Schristos #include <string.h>
518dffb485Schristos #include <sys/stat.h>
528dffb485Schristos #include <errno.h>
538dffb485Schristos 
548dffb485Schristos #if HAVE_OPENAT
558dffb485Schristos 
568dffb485Schristos /* Like openat, but support O_CLOEXEC and work around Solaris 9 bugs
578dffb485Schristos    with trailing slash.  */
588dffb485Schristos int
rpl_openat(int dfd,char const * filename,int flags,...)598dffb485Schristos rpl_openat (int dfd, char const *filename, int flags, ...)
608dffb485Schristos {
618dffb485Schristos   /* 0 = unknown, 1 = yes, -1 = no.  */
628dffb485Schristos #if GNULIB_defined_O_CLOEXEC
638dffb485Schristos   int have_cloexec = -1;
648dffb485Schristos #else
658dffb485Schristos   static int have_cloexec;
668dffb485Schristos #endif
678dffb485Schristos 
688dffb485Schristos   mode_t mode;
698dffb485Schristos   int fd;
708dffb485Schristos 
718dffb485Schristos   mode = 0;
728dffb485Schristos   if (flags & O_CREAT)
738dffb485Schristos     {
748dffb485Schristos       va_list arg;
758dffb485Schristos       va_start (arg, flags);
768dffb485Schristos 
778dffb485Schristos       /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
788dffb485Schristos          creates crashing code when 'mode_t' is smaller than 'int'.  */
798dffb485Schristos       mode = va_arg (arg, PROMOTED_MODE_T);
808dffb485Schristos 
818dffb485Schristos       va_end (arg);
828dffb485Schristos     }
838dffb485Schristos 
848dffb485Schristos # if OPEN_TRAILING_SLASH_BUG
858dffb485Schristos   /* Fail if one of O_CREAT, O_WRONLY, O_RDWR is specified and the filename
868dffb485Schristos      ends in a slash, as POSIX says such a filename must name a directory
878dffb485Schristos      <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
888dffb485Schristos        "A pathname that contains at least one non-<slash> character and that
898dffb485Schristos         ends with one or more trailing <slash> characters shall not be resolved
908dffb485Schristos         successfully unless the last pathname component before the trailing
918dffb485Schristos         <slash> characters names an existing directory"
928dffb485Schristos      If the named file already exists as a directory, then
938dffb485Schristos        - if O_CREAT is specified, open() must fail because of the semantics
948dffb485Schristos          of O_CREAT,
958dffb485Schristos        - if O_WRONLY or O_RDWR is specified, open() must fail because POSIX
968dffb485Schristos          <https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html>
978dffb485Schristos          says that it fails with errno = EISDIR in this case.
988dffb485Schristos      If the named file does not exist or does not name a directory, then
998dffb485Schristos        - if O_CREAT is specified, open() must fail since open() cannot create
1008dffb485Schristos          directories,
1018dffb485Schristos        - if O_WRONLY or O_RDWR is specified, open() must fail because the
1028dffb485Schristos          file does not contain a '.' directory.  */
1038dffb485Schristos   if ((flags & O_CREAT)
1048dffb485Schristos       || (flags & O_ACCMODE) == O_RDWR
1058dffb485Schristos       || (flags & O_ACCMODE) == O_WRONLY)
1068dffb485Schristos     {
1078dffb485Schristos       size_t len = strlen (filename);
1088dffb485Schristos       if (len > 0 && filename[len - 1] == '/')
1098dffb485Schristos         {
1108dffb485Schristos           errno = EISDIR;
1118dffb485Schristos           return -1;
1128dffb485Schristos         }
1138dffb485Schristos     }
1148dffb485Schristos # endif
1158dffb485Schristos 
1168dffb485Schristos   fd = orig_openat (dfd, filename,
1178dffb485Schristos                     flags & ~(have_cloexec < 0 ? O_CLOEXEC : 0), mode);
1188dffb485Schristos 
1198dffb485Schristos   if (flags & O_CLOEXEC)
1208dffb485Schristos     {
1218dffb485Schristos       if (! have_cloexec)
1228dffb485Schristos         {
1238dffb485Schristos           if (0 <= fd)
1248dffb485Schristos             have_cloexec = 1;
1258dffb485Schristos           else if (errno == EINVAL)
1268dffb485Schristos             {
1278dffb485Schristos               fd = orig_openat (dfd, filename, flags & ~O_CLOEXEC, mode);
1288dffb485Schristos               have_cloexec = -1;
1298dffb485Schristos             }
1308dffb485Schristos         }
1318dffb485Schristos       if (have_cloexec < 0 && 0 <= fd)
1328dffb485Schristos         set_cloexec_flag (fd, true);
1338dffb485Schristos     }
1348dffb485Schristos 
1358dffb485Schristos 
1368dffb485Schristos # if OPEN_TRAILING_SLASH_BUG
1378dffb485Schristos   /* If the filename ends in a slash and fd does not refer to a directory,
1388dffb485Schristos      then fail.
1398dffb485Schristos      Rationale: POSIX says such a filename must name a directory
1408dffb485Schristos      <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
1418dffb485Schristos        "A pathname that contains at least one non-<slash> character and that
1428dffb485Schristos         ends with one or more trailing <slash> characters shall not be resolved
1438dffb485Schristos         successfully unless the last pathname component before the trailing
1448dffb485Schristos         <slash> characters names an existing directory"
1458dffb485Schristos      If the named file without the slash is not a directory, open() must fail
1468dffb485Schristos      with ENOTDIR.  */
1478dffb485Schristos   if (fd >= 0)
1488dffb485Schristos     {
1498dffb485Schristos       /* We know len is positive, since open did not fail with ENOENT.  */
1508dffb485Schristos       size_t len = strlen (filename);
1518dffb485Schristos       if (filename[len - 1] == '/')
1528dffb485Schristos         {
1538dffb485Schristos           struct stat statbuf;
1548dffb485Schristos 
1558dffb485Schristos           if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
1568dffb485Schristos             {
1578dffb485Schristos               close (fd);
1588dffb485Schristos               errno = ENOTDIR;
1598dffb485Schristos               return -1;
1608dffb485Schristos             }
1618dffb485Schristos         }
1628dffb485Schristos     }
1638dffb485Schristos # endif
1648dffb485Schristos 
1658dffb485Schristos   return fd;
1668dffb485Schristos }
1678dffb485Schristos 
1688dffb485Schristos #else /* !HAVE_OPENAT */
1698dffb485Schristos 
1708dffb485Schristos # include "filename.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
1718dffb485Schristos # include "openat-priv.h"
1728dffb485Schristos # include "save-cwd.h"
1738dffb485Schristos 
1748dffb485Schristos /* Replacement for Solaris' openat function.
1758dffb485Schristos    <https://www.google.com/search?q=openat+site:docs.oracle.com>
1768dffb485Schristos    First, try to simulate it via open ("/proc/self/fd/FD/FILE").
1778dffb485Schristos    Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
1788dffb485Schristos    If either the save_cwd or the restore_cwd fails (relatively unlikely),
1798dffb485Schristos    then give a diagnostic and exit nonzero.
1808dffb485Schristos    Otherwise, upon failure, set errno and return -1, as openat does.
1818dffb485Schristos    Upon successful completion, return a file descriptor.  */
1828dffb485Schristos int
openat(int fd,char const * file,int flags,...)1838dffb485Schristos openat (int fd, char const *file, int flags, ...)
1848dffb485Schristos {
1858dffb485Schristos   mode_t mode = 0;
1868dffb485Schristos 
1878dffb485Schristos   if (flags & O_CREAT)
1888dffb485Schristos     {
1898dffb485Schristos       va_list arg;
1908dffb485Schristos       va_start (arg, flags);
1918dffb485Schristos 
1928dffb485Schristos       /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
1938dffb485Schristos          creates crashing code when 'mode_t' is smaller than 'int'.  */
1948dffb485Schristos       mode = va_arg (arg, PROMOTED_MODE_T);
1958dffb485Schristos 
1968dffb485Schristos       va_end (arg);
1978dffb485Schristos     }
1988dffb485Schristos 
1998dffb485Schristos   return openat_permissive (fd, file, flags, mode, NULL);
2008dffb485Schristos }
2018dffb485Schristos 
2028dffb485Schristos /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
2038dffb485Schristos    nonnull, set *CWD_ERRNO to an errno value if unable to save
2048dffb485Schristos    or restore the initial working directory.  This is needed only
2058dffb485Schristos    the first time remove.c's remove_dir opens a command-line
2068dffb485Schristos    directory argument.
2078dffb485Schristos 
2088dffb485Schristos    If a previous attempt to restore the current working directory
2098dffb485Schristos    failed, then we must not even try to access a '.'-relative name.
2108dffb485Schristos    It is the caller's responsibility not to call this function
2118dffb485Schristos    in that case.  */
2128dffb485Schristos 
2138dffb485Schristos int
openat_permissive(int fd,char const * file,int flags,mode_t mode,int * cwd_errno)2148dffb485Schristos openat_permissive (int fd, char const *file, int flags, mode_t mode,
2158dffb485Schristos                    int *cwd_errno)
2168dffb485Schristos {
2178dffb485Schristos   struct saved_cwd saved_cwd;
2188dffb485Schristos   int saved_errno;
2198dffb485Schristos   int err;
2208dffb485Schristos   bool save_ok;
2218dffb485Schristos 
2228dffb485Schristos   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
2238dffb485Schristos     return open (file, flags, mode);
2248dffb485Schristos 
2258dffb485Schristos   {
2268dffb485Schristos     char buf[OPENAT_BUFFER_SIZE];
2278dffb485Schristos     char *proc_file = openat_proc_name (buf, fd, file);
2288dffb485Schristos     if (proc_file)
2298dffb485Schristos       {
2308dffb485Schristos         int open_result = open (proc_file, flags, mode);
2318dffb485Schristos         int open_errno = errno;
2328dffb485Schristos         if (proc_file != buf)
2338dffb485Schristos           free (proc_file);
2348dffb485Schristos         /* If the syscall succeeds, or if it fails with an unexpected
2358dffb485Schristos            errno value, then return right away.  Otherwise, fall through
2368dffb485Schristos            and resort to using save_cwd/restore_cwd.  */
2378dffb485Schristos         if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
2388dffb485Schristos           {
2398dffb485Schristos             errno = open_errno;
2408dffb485Schristos             return open_result;
2418dffb485Schristos           }
2428dffb485Schristos       }
2438dffb485Schristos   }
2448dffb485Schristos 
2458dffb485Schristos   save_ok = (save_cwd (&saved_cwd) == 0);
2468dffb485Schristos   if (! save_ok)
2478dffb485Schristos     {
2488dffb485Schristos       if (! cwd_errno)
2498dffb485Schristos         openat_save_fail (errno);
2508dffb485Schristos       *cwd_errno = errno;
2518dffb485Schristos     }
2528dffb485Schristos   if (0 <= fd && fd == saved_cwd.desc)
2538dffb485Schristos     {
2548dffb485Schristos       /* If saving the working directory collides with the user's
2558dffb485Schristos          requested fd, then the user's fd must have been closed to
2568dffb485Schristos          begin with.  */
2578dffb485Schristos       free_cwd (&saved_cwd);
2588dffb485Schristos       errno = EBADF;
2598dffb485Schristos       return -1;
2608dffb485Schristos     }
2618dffb485Schristos 
2628dffb485Schristos   err = fchdir (fd);
2638dffb485Schristos   saved_errno = errno;
2648dffb485Schristos 
2658dffb485Schristos   if (! err)
2668dffb485Schristos     {
2678dffb485Schristos       err = open (file, flags, mode);
2688dffb485Schristos       saved_errno = errno;
2698dffb485Schristos       if (save_ok && restore_cwd (&saved_cwd) != 0)
2708dffb485Schristos         {
2718dffb485Schristos           if (! cwd_errno)
2728dffb485Schristos             {
2738dffb485Schristos               /* Don't write a message to just-created fd 2.  */
2748dffb485Schristos               saved_errno = errno;
2758dffb485Schristos               if (err == STDERR_FILENO)
2768dffb485Schristos                 close (err);
2778dffb485Schristos               openat_restore_fail (saved_errno);
2788dffb485Schristos             }
2798dffb485Schristos           *cwd_errno = errno;
2808dffb485Schristos         }
2818dffb485Schristos     }
2828dffb485Schristos 
2838dffb485Schristos   free_cwd (&saved_cwd);
2848dffb485Schristos   errno = saved_errno;
2858dffb485Schristos   return err;
2868dffb485Schristos }
2878dffb485Schristos 
2888dffb485Schristos /* Return true if our openat implementation must resort to
2898dffb485Schristos    using save_cwd and restore_cwd.  */
2908dffb485Schristos bool
openat_needs_fchdir(void)2918dffb485Schristos openat_needs_fchdir (void)
2928dffb485Schristos {
2938dffb485Schristos   bool needs_fchdir = true;
2948dffb485Schristos   int fd = open ("/", O_SEARCH | O_CLOEXEC);
2958dffb485Schristos 
2968dffb485Schristos   if (0 <= fd)
2978dffb485Schristos     {
2988dffb485Schristos       char buf[OPENAT_BUFFER_SIZE];
2998dffb485Schristos       char *proc_file = openat_proc_name (buf, fd, ".");
3008dffb485Schristos       if (proc_file)
3018dffb485Schristos         {
3028dffb485Schristos           needs_fchdir = false;
3038dffb485Schristos           if (proc_file != buf)
3048dffb485Schristos             free (proc_file);
3058dffb485Schristos         }
3068dffb485Schristos       close (fd);
3078dffb485Schristos     }
3088dffb485Schristos 
3098dffb485Schristos   return needs_fchdir;
3108dffb485Schristos }
3118dffb485Schristos 
3128dffb485Schristos #endif /* !HAVE_OPENAT */
313