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