/* Open a stream to a file.
- Copyright (C) 2007-2016 Free Software Foundation, Inc.
+ Copyright (C) 2007-2021 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
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 <http://www.gnu.org/licenses/>. */
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* Written by Bruno Haible <bruno@clisp.org>, 2007. */
/* If the user's config.h happens to include <stdio.h>, let it include only
the system's <stdio.h> here, so that orig_fopen doesn't recurse to
rpl_fopen. */
-#define __need_FILE
+#define _GL_ALREADY_INCLUDING_STDIO_H
#include <config.h>
/* Get the original definition of fopen. It might be defined as a macro. */
#include <stdio.h>
-#undef __need_FILE
+#undef _GL_ALREADY_INCLUDING_STDIO_H
static FILE *
orig_fopen (const char *filename, const char *mode)
#include <errno.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
FILE *
rpl_fopen (const char *filename, const char *mode)
{
-#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ int open_direction;
+ int open_flags;
+#if GNULIB_FOPEN_GNU
+ bool open_flags_gnu;
+# define BUF_SIZE 80
+ char fdopen_mode_buf[BUF_SIZE + 1];
+#endif
+
+#if defined _WIN32 && ! defined __CYGWIN__
if (strcmp (filename, "/dev/null") == 0)
filename = "NUL";
#endif
+ /* Parse the mode. */
+ open_direction = 0;
+ open_flags = 0;
+#if GNULIB_FOPEN_GNU
+ open_flags_gnu = false;
+#endif
+ {
+ const char *p = mode;
+#if GNULIB_FOPEN_GNU
+ char *q = fdopen_mode_buf;
+#endif
+
+ for (; *p != '\0'; p++)
+ {
+ switch (*p)
+ {
+ case 'r':
+ open_direction = O_RDONLY;
+#if GNULIB_FOPEN_GNU
+ if (q < fdopen_mode_buf + BUF_SIZE)
+ *q++ = *p;
+#endif
+ continue;
+ case 'w':
+ open_direction = O_WRONLY;
+ open_flags |= O_CREAT | O_TRUNC;
+#if GNULIB_FOPEN_GNU
+ if (q < fdopen_mode_buf + BUF_SIZE)
+ *q++ = *p;
+#endif
+ continue;
+ case 'a':
+ open_direction = O_WRONLY;
+ open_flags |= O_CREAT | O_APPEND;
+#if GNULIB_FOPEN_GNU
+ if (q < fdopen_mode_buf + BUF_SIZE)
+ *q++ = *p;
+#endif
+ continue;
+ case 'b':
+ /* While it is non-standard, O_BINARY is guaranteed by
+ gnulib <fcntl.h>. We can also assume that orig_fopen
+ supports the 'b' flag. */
+ open_flags |= O_BINARY;
+#if GNULIB_FOPEN_GNU
+ if (q < fdopen_mode_buf + BUF_SIZE)
+ *q++ = *p;
+#endif
+ continue;
+ case '+':
+ open_direction = O_RDWR;
+#if GNULIB_FOPEN_GNU
+ if (q < fdopen_mode_buf + BUF_SIZE)
+ *q++ = *p;
+#endif
+ continue;
+#if GNULIB_FOPEN_GNU
+ case 'x':
+ open_flags |= O_EXCL;
+ open_flags_gnu = true;
+ continue;
+ case 'e':
+ open_flags |= O_CLOEXEC;
+ open_flags_gnu = true;
+ continue;
+#endif
+ default:
+ break;
+ }
+#if GNULIB_FOPEN_GNU
+ /* The rest of the mode string can be a platform-dependent extension.
+ Copy it unmodified. */
+ {
+ size_t len = strlen (p);
+ if (len > fdopen_mode_buf + BUF_SIZE - q)
+ len = fdopen_mode_buf + BUF_SIZE - q;
+ memcpy (q, p, len);
+ q += len;
+ }
+#endif
+ break;
+ }
+#if GNULIB_FOPEN_GNU
+ *q = '\0';
+#endif
+ }
+
#if FOPEN_TRAILING_SLASH_BUG
- /* If the filename ends in a slash and a mode that requires write access is
- specified, then fail.
- Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
- says that
- "A pathname that contains at least one non-slash character and that
- ends with one or more trailing slashes shall be resolved as if a
- single dot character ( '.' ) were appended to the pathname."
- and
- "The special filename dot shall refer to the directory specified by
- its predecessor."
+ /* Fail if the mode requires write access and the filename ends in a slash,
+ as POSIX says such a filename must name a directory
+ <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
+ "A pathname that contains at least one non-<slash> character and that
+ ends with one or more trailing <slash> characters shall not be resolved
+ successfully unless the last pathname component before the trailing
+ <slash> characters names an existing directory"
If the named file already exists as a directory, then if a mode that
requires write access is specified, fopen() must fail because POSIX
- <http://www.opengroup.org/susv3/functions/fopen.html> says that it
- fails with errno = EISDIR in this case.
+ <https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html>
+ says that it fails with errno = EISDIR in this case.
If the named file does not exist or does not name a directory, then
fopen() must fail since the file does not contain a '.' directory. */
{
struct stat statbuf;
FILE *fp;
- if (mode[0] == 'w' || mode[0] == 'a')
+ if (open_direction != O_RDONLY)
{
errno = EISDIR;
return NULL;
}
- fd = open (filename, O_RDONLY);
+ fd = open (filename, open_direction | open_flags,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (fd < 0)
return NULL;
return NULL;
}
+# if GNULIB_FOPEN_GNU
+ fp = fdopen (fd, fdopen_mode_buf);
+# else
fp = fdopen (fd, mode);
+# endif
if (fp == NULL)
{
int saved_errno = errno;
return fp;
}
}
-# endif
+#endif
+
+#if GNULIB_FOPEN_GNU
+ if (open_flags_gnu)
+ {
+ int fd;
+ FILE *fp;
+
+ fd = open (filename, open_direction | open_flags,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (fd < 0)
+ return NULL;
+
+ fp = fdopen (fd, fdopen_mode_buf);
+ if (fp == NULL)
+ {
+ int saved_errno = errno;
+ close (fd);
+ errno = saved_errno;
+ }
+ return fp;
+ }
+#endif
return orig_fopen (filename, mode);
}