exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / fopen.c
blobd3b57a987a07106bc223f3c30c8e01024c27d441
1 /* Open a stream to a file.
2 Copyright (C) 2007-2024 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
9 This file 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 Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Bruno Haible <bruno@clisp.org>, 2007. */
19 /* If the user's config.h happens to include <stdio.h>, let it include only
20 the system's <stdio.h> here, so that orig_fopen doesn't recurse to
21 rpl_fopen. */
22 #define _GL_ALREADY_INCLUDING_STDIO_H
23 #include <config.h>
25 /* Get the original definition of fopen. It might be defined as a macro. */
26 #include <stdio.h>
27 #undef _GL_ALREADY_INCLUDING_STDIO_H
29 static FILE *
30 orig_fopen (const char *filename, const char *mode)
32 return fopen (filename, mode);
35 /* Specification. */
36 #ifdef __osf__
37 /* Write "stdio.h" here, not <stdio.h>, otherwise OSF/1 5.1 DTK cc eliminates
38 this include because of the preliminary #include <stdio.h> above. */
39 # include "stdio.h"
40 #else
41 # include <stdio.h>
42 #endif
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <sys/types.h>
49 #include <sys/stat.h>
51 FILE *
52 rpl_fopen (const char *filename, const char *mode)
54 int open_direction;
55 int open_flags;
56 #if GNULIB_FOPEN_GNU
57 bool open_flags_gnu;
58 # define BUF_SIZE 80
59 char fdopen_mode_buf[BUF_SIZE + 1];
60 #endif
62 #if defined _WIN32 && ! defined __CYGWIN__
63 if (strcmp (filename, "/dev/null") == 0)
64 filename = "NUL";
65 #endif
67 /* Parse the mode. */
68 open_direction = 0;
69 open_flags = 0;
70 #if GNULIB_FOPEN_GNU
71 open_flags_gnu = false;
72 #endif
74 const char *p = mode;
75 #if GNULIB_FOPEN_GNU
76 char *q = fdopen_mode_buf;
77 #endif
79 for (; *p != '\0'; p++)
81 switch (*p)
83 case 'r':
84 open_direction = O_RDONLY;
85 #if GNULIB_FOPEN_GNU
86 if (q < fdopen_mode_buf + BUF_SIZE)
87 *q++ = *p;
88 #endif
89 continue;
90 case 'w':
91 open_direction = O_WRONLY;
92 open_flags |= O_CREAT | O_TRUNC;
93 #if GNULIB_FOPEN_GNU
94 if (q < fdopen_mode_buf + BUF_SIZE)
95 *q++ = *p;
96 #endif
97 continue;
98 case 'a':
99 open_direction = O_WRONLY;
100 open_flags |= O_CREAT | O_APPEND;
101 #if GNULIB_FOPEN_GNU
102 if (q < fdopen_mode_buf + BUF_SIZE)
103 *q++ = *p;
104 #endif
105 continue;
106 case 'b':
107 /* While it is non-standard, O_BINARY is guaranteed by
108 gnulib <fcntl.h>. We can also assume that orig_fopen
109 supports the 'b' flag. */
110 open_flags |= O_BINARY;
111 #if GNULIB_FOPEN_GNU
112 if (q < fdopen_mode_buf + BUF_SIZE)
113 *q++ = *p;
114 #endif
115 continue;
116 case '+':
117 open_direction = O_RDWR;
118 #if GNULIB_FOPEN_GNU
119 if (q < fdopen_mode_buf + BUF_SIZE)
120 *q++ = *p;
121 #endif
122 continue;
123 #if GNULIB_FOPEN_GNU
124 case 'x':
125 open_flags |= O_EXCL;
126 open_flags_gnu = true;
127 continue;
128 case 'e':
129 open_flags |= O_CLOEXEC;
130 open_flags_gnu = true;
131 continue;
132 #endif
133 default:
134 break;
136 #if GNULIB_FOPEN_GNU
137 /* The rest of the mode string can be a platform-dependent extension.
138 Copy it unmodified. */
140 size_t len = strlen (p);
141 if (len > fdopen_mode_buf + BUF_SIZE - q)
142 len = fdopen_mode_buf + BUF_SIZE - q;
143 memcpy (q, p, len);
144 q += len;
146 #endif
147 break;
149 #if GNULIB_FOPEN_GNU
150 *q = '\0';
151 #endif
154 #if FOPEN_TRAILING_SLASH_BUG
155 /* Fail if the mode requires write access and the filename ends in a slash,
156 as POSIX says such a filename must name a directory
157 <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
158 "A pathname that contains at least one non-<slash> character and that
159 ends with one or more trailing <slash> characters shall not be resolved
160 successfully unless the last pathname component before the trailing
161 <slash> characters names an existing directory"
162 If the named file already exists as a directory, then if a mode that
163 requires write access is specified, fopen() must fail because POSIX
164 <https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html>
165 says that it fails with errno = EISDIR in this case.
166 If the named file does not exist or does not name a directory, then
167 fopen() must fail since the file does not contain a '.' directory. */
169 size_t len = strlen (filename);
170 if (len > 0 && filename[len - 1] == '/')
172 int fd;
173 struct stat statbuf;
174 FILE *fp;
176 if (open_direction != O_RDONLY)
178 errno = EISDIR;
179 return NULL;
182 fd = open (filename, open_direction | open_flags,
183 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
184 if (fd < 0)
185 return NULL;
187 if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
189 close (fd);
190 errno = ENOTDIR;
191 return NULL;
194 # if GNULIB_FOPEN_GNU
195 fp = fdopen (fd, fdopen_mode_buf);
196 # else
197 fp = fdopen (fd, mode);
198 # endif
199 if (fp == NULL)
201 int saved_errno = errno;
202 close (fd);
203 errno = saved_errno;
205 return fp;
208 #endif
210 #if GNULIB_FOPEN_GNU
211 if (open_flags_gnu)
213 int fd;
214 FILE *fp;
216 fd = open (filename, open_direction | open_flags,
217 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
218 if (fd < 0)
219 return NULL;
221 fp = fdopen (fd, fdopen_mode_buf);
222 if (fp == NULL)
224 int saved_errno = errno;
225 close (fd);
226 errno = saved_errno;
228 return fp;
230 #endif
232 /* open_direction is sometimes used, sometimes unused.
233 Silence gcc's warning about this situation. */
234 (void) open_direction;
236 return orig_fopen (filename, mode);