exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / freopen-safer.c
blob3a7f5681149c71678076b1e6e65316d1b8a08714
1 /* Invoke freopen, but avoid some glitches.
3 Copyright (C) 2009-2024 Free Software Foundation, Inc.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 /* Written by Eric Blake. */
20 #include <config.h>
22 #include "stdio-safer.h"
24 #include "attribute.h"
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <unistd.h>
30 /* GCC 13 misunderstands the dup2 trickery in this file. */
31 #if 13 <= __GNUC__
32 # pragma GCC diagnostic ignored "-Wanalyzer-fd-leak"
33 #endif
35 /* Guarantee that FD is open; all smaller FDs must already be open.
36 Return true if successful. */
37 static bool
38 protect_fd (int fd)
40 int value = open ("/dev/null", O_RDONLY);
41 if (value != fd)
43 if (0 <= value)
45 close (value);
46 errno = EBADF; /* Unexpected; this is as good as anything else. */
48 return false;
50 return true;
53 /* Like freopen, but guarantee that reopening stdin, stdout, or stderr
54 preserves the invariant that STDxxx_FILENO==fileno(stdxxx), and
55 that no other stream will interfere with the standard streams.
56 This is necessary because most freopen implementations will change
57 the associated fd of a stream to the lowest available slot. */
59 FILE *
60 freopen_safer (char const *name, char const *mode, FILE *f)
62 /* Unfortunately, we cannot use the fopen_safer approach of using
63 fdopen (dup_safer (fileno (freopen (cmd, mode, f)))), because we
64 need to return f itself. The implementation of freopen(NULL,m,f)
65 is system-dependent, so the best we can do is guarantee that all
66 lower-valued standard fds are open prior to the freopen call,
67 even though this puts more pressure on open fds. */
68 bool protect_in = false;
69 bool protect_out = false;
70 bool protect_err = false;
71 int saved_errno;
73 switch (fileno (f))
75 default: /* -1 or not a standard stream. */
76 if (dup2 (STDERR_FILENO, STDERR_FILENO) != STDERR_FILENO)
77 protect_err = true;
78 FALLTHROUGH;
79 case STDERR_FILENO:
80 if (dup2 (STDOUT_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
81 protect_out = true;
82 FALLTHROUGH;
83 case STDOUT_FILENO:
84 if (dup2 (STDIN_FILENO, STDIN_FILENO) != STDIN_FILENO)
85 protect_in = true;
86 FALLTHROUGH;
87 case STDIN_FILENO:
88 /* Nothing left to protect. */
89 break;
91 if (protect_in && !protect_fd (STDIN_FILENO))
92 f = NULL;
93 else if (protect_out && !protect_fd (STDOUT_FILENO))
94 f = NULL;
95 else if (protect_err && !protect_fd (STDERR_FILENO))
96 f = NULL;
97 else
98 f = freopen (name, mode, f);
99 saved_errno = errno;
100 if (protect_err)
101 close (STDERR_FILENO);
102 if (protect_out)
103 close (STDOUT_FILENO);
104 if (protect_in)
105 close (STDIN_FILENO);
106 if (!f)
107 errno = saved_errno;
108 return f;