1 /* Open a stream to a file.
2 Copyright (C) 2007-2023 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 /* Written by Bruno Haible <bruno@clisp.org>, 2007. */
18
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>
24
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
28
29 static FILE *
30 orig_fopen (const char *filename, const char *mode)
31 {
32 return fopen (filename, mode);
33 }
34
35 /* Specification. */
36 /* Write "stdio.h" here, not <stdio.h>, otherwise OSF/1 5.1 DTK cc eliminates
37 this include because of the preliminary #include <stdio.h> above. */
38 #include "stdio.h"
39
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46
47 FILE *
48 rpl_fopen (const char *filename, const char *mode)
49 {
50 int open_direction;
51 int open_flags;
52 #if GNULIB_FOPEN_GNU
53 bool open_flags_gnu;
54 # define BUF_SIZE 80
55 char fdopen_mode_buf[BUF_SIZE + 1];
56 #endif
57
58 #if defined _WIN32 && ! defined __CYGWIN__
59 if (strcmp (filename, "/dev/null") == 0)
60 filename = "NUL";
61 #endif
62
63 /* Parse the mode. */
64 open_direction = 0;
65 open_flags = 0;
66 #if GNULIB_FOPEN_GNU
67 open_flags_gnu = false;
68 #endif
69 {
70 const char *p = mode;
71 #if GNULIB_FOPEN_GNU
72 char *q = fdopen_mode_buf;
73 #endif
74
75 for (; *p != '\0'; p++)
76 {
77 switch (*p)
78 {
79 case 'r':
80 open_direction = O_RDONLY;
81 #if GNULIB_FOPEN_GNU
82 if (q < fdopen_mode_buf + BUF_SIZE)
83 *q++ = *p;
84 #endif
85 continue;
86 case 'w':
87 open_direction = O_WRONLY;
88 open_flags |= O_CREAT | O_TRUNC;
89 #if GNULIB_FOPEN_GNU
90 if (q < fdopen_mode_buf + BUF_SIZE)
91 *q++ = *p;
92 #endif
93 continue;
94 case 'a':
95 open_direction = O_WRONLY;
96 open_flags |= O_CREAT | O_APPEND;
97 #if GNULIB_FOPEN_GNU
98 if (q < fdopen_mode_buf + BUF_SIZE)
99 *q++ = *p;
100 #endif
101 continue;
102 case 'b':
103 /* While it is non-standard, O_BINARY is guaranteed by
104 gnulib <fcntl.h>. We can also assume that orig_fopen
105 supports the 'b' flag. */
106 open_flags |= O_BINARY;
107 #if GNULIB_FOPEN_GNU
108 if (q < fdopen_mode_buf + BUF_SIZE)
109 *q++ = *p;
110 #endif
111 continue;
112 case '+':
113 open_direction = O_RDWR;
114 #if GNULIB_FOPEN_GNU
115 if (q < fdopen_mode_buf + BUF_SIZE)
116 *q++ = *p;
117 #endif
118 continue;
119 #if GNULIB_FOPEN_GNU
120 case 'x':
121 open_flags |= O_EXCL;
122 open_flags_gnu = true;
123 continue;
124 case 'e':
125 open_flags |= O_CLOEXEC;
126 open_flags_gnu = true;
127 continue;
128 #endif
129 default:
130 break;
131 }
132 #if GNULIB_FOPEN_GNU
133 /* The rest of the mode string can be a platform-dependent extension.
134 Copy it unmodified. */
135 {
136 size_t len = strlen (p);
137 if (len > fdopen_mode_buf + BUF_SIZE - q)
138 len = fdopen_mode_buf + BUF_SIZE - q;
139 memcpy (q, p, len);
140 q += len;
141 }
142 #endif
143 break;
144 }
145 #if GNULIB_FOPEN_GNU
146 *q = '\0';
147 #endif
148 }
149
150 #if FOPEN_TRAILING_SLASH_BUG
151 /* Fail if the mode requires write access and the filename ends in a slash,
152 as POSIX says such a filename must name a directory
153 <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
154 "A pathname that contains at least one non-<slash> character and that
155 ends with one or more trailing <slash> characters shall not be resolved
156 successfully unless the last pathname component before the trailing
157 <slash> characters names an existing directory"
158 If the named file already exists as a directory, then if a mode that
159 requires write access is specified, fopen() must fail because POSIX
160 <https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html>
161 says that it fails with errno = EISDIR in this case.
162 If the named file does not exist or does not name a directory, then
163 fopen() must fail since the file does not contain a '.' directory. */
164 {
165 size_t len = strlen (filename);
166 if (len > 0 && filename[len - 1] == '/')
167 {
168 int fd;
169 struct stat statbuf;
170 FILE *fp;
171
172 if (open_direction != O_RDONLY)
173 {
174 errno = EISDIR;
175 return NULL;
176 }
177
178 fd = open (filename, open_direction | open_flags,
179 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
180 if (fd < 0)
181 return NULL;
182
183 if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
184 {
185 close (fd);
186 errno = ENOTDIR;
187 return NULL;
188 }
189
190 # if GNULIB_FOPEN_GNU
191 fp = fdopen (fd, fdopen_mode_buf);
192 # else
193 fp = fdopen (fd, mode);
194 # endif
195 if (fp == NULL)
196 {
197 int saved_errno = errno;
198 close (fd);
199 errno = saved_errno;
200 }
201 return fp;
202 }
203 }
204 #endif
205
206 #if GNULIB_FOPEN_GNU
207 if (open_flags_gnu)
208 {
209 int fd;
210 FILE *fp;
211
212 fd = open (filename, open_direction | open_flags,
213 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
214 if (fd < 0)
215 return NULL;
216
217 fp = fdopen (fd, fdopen_mode_buf);
218 if (fp == NULL)
219 {
220 int saved_errno = errno;
221 close (fd);
222 errno = saved_errno;
223 }
224 return fp;
225 }
226 #endif
227
228 /* open_direction is sometimes used, sometimes unused.
229 Silence gcc's warning about this situation. */
230 (void) open_direction;
231
232 return orig_fopen (filename, mode);
233 }