| /* Invoke freopen, but avoid some glitches. | |
| Copyright (C) 2009-2026 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 | |
| the Free Software Foundation, either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 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 <https://www.gnu.org/licenses/>. */ | |
| /* Written by Eric Blake. */ | |
| #include <config.h> | |
| #include "stdio-safer.h" | |
| #include "attribute.h" | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <unistd.h> | |
| /* GCC 13 misunderstands the dup2 trickery in this file. */ | |
| #if _GL_GNUC_PREREQ (13, 0) | |
| # pragma GCC diagnostic ignored "-Wanalyzer-fd-leak" | |
| #endif | |
| /* Guarantee that FD is open; all smaller FDs must already be open. | |
| Return true if successful. */ | |
| static bool | |
| protect_fd (int fd) | |
| { | |
| int value = open ("/dev/null", O_RDONLY); | |
| if (value != fd) | |
| { | |
| if (0 <= value) | |
| { | |
| close (value); | |
| errno = EBADF; /* Unexpected; this is as good as anything else. */ | |
| } | |
| return false; | |
| } | |
| return true; | |
| } | |
| /* Like freopen, but guarantee that reopening stdin, stdout, or stderr | |
| preserves the invariant that STDxxx_FILENO==fileno(stdxxx), and | |
| that no other stream will interfere with the standard streams. | |
| This is necessary because most freopen implementations will change | |
| the associated fd of a stream to the lowest available slot. */ | |
| FILE * | |
| freopen_safer (char const *name, char const *mode, FILE *f) | |
| { | |
| /* Unfortunately, we cannot use the fopen_safer approach of using | |
| fdopen (dup_safer (fileno (freopen (cmd, mode, f)))), because we | |
| need to return f itself. The implementation of freopen(NULL,m,f) | |
| is system-dependent, so the best we can do is guarantee that all | |
| lower-valued standard fds are open prior to the freopen call, | |
| even though this puts more pressure on open fds. */ | |
| bool protect_in = false; | |
| bool protect_out = false; | |
| bool protect_err = false; | |
| switch (fileno (f)) | |
| { | |
| default: /* -1 or not a standard stream. */ | |
| if (dup2 (STDERR_FILENO, STDERR_FILENO) != STDERR_FILENO) | |
| protect_err = true; | |
| FALLTHROUGH; | |
| case STDERR_FILENO: | |
| if (dup2 (STDOUT_FILENO, STDOUT_FILENO) != STDOUT_FILENO) | |
| protect_out = true; | |
| FALLTHROUGH; | |
| case STDOUT_FILENO: | |
| if (dup2 (STDIN_FILENO, STDIN_FILENO) != STDIN_FILENO) | |
| protect_in = true; | |
| FALLTHROUGH; | |
| case STDIN_FILENO: | |
| /* Nothing left to protect. */ | |
| break; | |
| } | |
| if (protect_in && !protect_fd (STDIN_FILENO)) | |
| f = NULL; | |
| else if (protect_out && !protect_fd (STDOUT_FILENO)) | |
| f = NULL; | |
| else if (protect_err && !protect_fd (STDERR_FILENO)) | |
| f = NULL; | |
| else | |
| f = freopen (name, mode, f); | |
| int saved_errno = errno; | |
| if (protect_err) | |
| close (STDERR_FILENO); | |
| if (protect_out) | |
| close (STDOUT_FILENO); | |
| if (protect_in) | |
| close (STDIN_FILENO); | |
| if (!f) | |
| errno = saved_errno; | |
| return f; | |
| } |