/* Test of opening a file descriptor.
Copyright (C) 2007-2026 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
/* Written by Bruno Haible , 2007. */
/* Tell GCC not to warn about the specific edge cases tested here. */
#if _GL_GNUC_PREREQ (13, 0)
# pragma GCC diagnostic ignored "-Wanalyzer-fd-leak"
#endif
/* Make test_open always inline if we're using Fortify, which defines
__always_inline to do that. Do nothing otherwise. This works
around a glibc bug whereby 'open' cannot be used as a function
pointer when _FORTIFY_SOURCE is positive. */
#if __GLIBC__ && defined __always_inline
# define ALWAYS_INLINE __always_inline
#else
# define ALWAYS_INLINE
#endif
/* This file is designed to test open(n,buf[,mode]),
openat(dfd,n,buf[,mode]), and openat2(dfd,n,how,size).
FUNC is the function to test; for openat and openat2 it is a wrapper.
Assumes that BASE and ASSERT are already defined, and that
appropriate headers are already included. If PRINT, warn before
skipping symlink tests with status 77. */
static ALWAYS_INLINE int
test_open (int (*func) (char const *, int, ...), bool print)
{
#if HAVE_DECL_ALARM
/* Declare failure if test takes too long, by using default abort
caused by SIGALRM. */
int alarm_value = 5;
signal (SIGALRM, SIG_DFL);
alarm (alarm_value);
#endif
int fd;
/* Remove anything from prior partial run. */
unlink (BASE "fifo");
unlink (BASE "file");
unlink (BASE "e.exe");
unlink (BASE "link");
/* Cannot create directory. */
errno = 0;
ASSERT (func ("nonexist.ent/", O_CREAT | O_RDONLY, 0600) == -1);
ASSERT (errno == ENOTDIR || errno == EISDIR || errno == ENOENT
|| errno == EINVAL);
/* Create a regular file. */
fd = func (BASE "file", O_CREAT | O_RDONLY, 0600);
ASSERT (0 <= fd);
ASSERT (close (fd) == 0);
/* Create an executable regular file. */
fd = func (BASE "e.exe", O_CREAT | O_RDONLY, 0700);
ASSERT (0 <= fd);
ASSERT (close (fd) == 0);
/* Trailing slash handling. */
errno = 0;
ASSERT (func (BASE "file/", O_RDONLY) == -1);
ASSERT (errno == ENOTDIR || errno == EISDIR || errno == EINVAL);
/* Cannot open regular file with O_DIRECTORY. */
errno = 0;
ASSERT (func (BASE "file", O_RDONLY | O_DIRECTORY) == -1);
ASSERT (errno == ENOTDIR);
/* Cannot open /dev/null with trailing slash or O_DIRECTORY. */
errno = 0;
ASSERT (func ("/dev/null/", O_RDONLY) == -1);
#if defined _WIN32 && !defined __CYGWIN__
ASSERT (errno == ENOENT);
#else
ASSERT (errno == ENOTDIR || errno == EISDIR || errno == EINVAL);
#endif
errno = 0;
ASSERT (func ("/dev/null", O_RDONLY | O_DIRECTORY) == -1);
ASSERT (errno == ENOTDIR);
/* Cannot open /dev/tty with trailing slash or O_DIRECTORY,
though errno may differ as there may not be a controlling tty. */
ASSERT (func ("/dev/tty/", O_RDONLY) == -1);
ASSERT (func ("/dev/tty", O_RDONLY | O_DIRECTORY) == -1);
/* Cannot open fifo with trailing slash or O_DIRECTORY. */
if (mkfifo (BASE "fifo", 0666) == 0)
{
errno = 0;
ASSERT (func (BASE "fifo/", O_RDONLY) == -1);
ASSERT (errno == ENOTDIR || errno == EISDIR || errno == EINVAL);
errno = 0;
ASSERT (func (BASE "fifo", O_RDONLY | O_DIRECTORY) == -1);
ASSERT (errno == ENOTDIR);
ASSERT (unlink (BASE "fifo") == 0);
}
/* Directories cannot be opened for writing. */
errno = 0;
ASSERT (func (".", O_WRONLY) == -1);
ASSERT (errno == EISDIR || errno == EACCES);
/* /dev/null must exist, and be writable. */
fd = func ("/dev/null", O_RDONLY);
ASSERT (0 <= fd);
{
char c;
ASSERT (read (fd, &c, 1) == 0);
}
ASSERT (close (fd) == 0);
fd = func ("/dev/null", O_WRONLY);
ASSERT (0 <= fd);
ASSERT (write (fd, "c", 1) == 1);
ASSERT (close (fd) == 0);
/* Although O_NONBLOCK on regular files can be ignored, it must not
cause a failure. */
fd = func (BASE "file", O_NONBLOCK | O_RDONLY);
ASSERT (0 <= fd);
ASSERT (close (fd) == 0);
/* O_CLOEXEC must be honoured. */
if (O_CLOEXEC)
{
/* Since the O_CLOEXEC handling goes through a special code path at its
first invocation, test it twice. */
for (int i = 0; i < 2; i++)
{
int flags;
fd = func (BASE "file", O_CLOEXEC | O_RDONLY);
ASSERT (0 <= fd);
flags = fcntl (fd, F_GETFD);
ASSERT (flags >= 0);
ASSERT ((flags & FD_CLOEXEC) != 0);
ASSERT (close (fd) == 0);
}
}
/* Symlink handling, where supported. */
if (symlink (BASE "file", BASE "link") != 0)
{
ASSERT (unlink (BASE "file") == 0);
if (print)
fputs ("skipping test: symlinks not supported on this file system\n",
stderr);
return 77;
}
errno = 0;
ASSERT (func (BASE "link/", O_RDONLY) == -1);
ASSERT (errno == ENOTDIR);
fd = func (BASE "link", O_RDONLY);
ASSERT (0 <= fd);
ASSERT (close (fd) == 0);
/* Cleanup. */
ASSERT (unlink (BASE "file") == 0);
ASSERT (unlink (BASE "e.exe") == 0);
ASSERT (unlink (BASE "link") == 0);
return 0;
}