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