Home | History | Annotate | Line # | Download | only in import
      1 /* provide a replacement openat function
      2    Copyright (C) 2004-2022 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 */
    313