util-linux/include/closestream.h

111 lines
2.1 KiB
C
Raw Normal View History

#ifndef UTIL_LINUX_CLOSESTREAM_H
#define UTIL_LINUX_CLOSESTREAM_H
#include <stdio.h>
#ifdef HAVE_STDIO_EXT_H
#include <stdio_ext.h>
#endif
#include <unistd.h>
#include "c.h"
#include "nls.h"
#ifndef CLOSE_EXIT_CODE
# define CLOSE_EXIT_CODE EXIT_FAILURE
#endif
static inline int
close_stream(FILE * stream)
{
#ifdef HAVE___FPENDING
const int some_pending = (__fpending(stream) != 0);
#endif
const int prev_fail = (ferror(stream) != 0);
const int fclose_fail = (fclose(stream) != 0);
if (prev_fail || (fclose_fail && (
#ifdef HAVE___FPENDING
some_pending ||
#endif
errno != EBADF))) {
if (!fclose_fail && !(errno == EPIPE))
errno = 0;
return EOF;
}
return 0;
}
include/closestream: fix assignment to read-only standard streams In order to avoid closing standard streams multiple times, commit 52aa1a661 (include/closestream: avoid close more than once, 2019-06-13) introduced code to set the standard output and error streams to `NULL`. As musl libc defines standard streams as constant pointers, the change causes compiler errors on systems with that libc. According to ISO C89, being able to assign to the standard text streams is not a requirement for any C implementation, see footnote 238 in chapter §7.19.5.6: The primary use of the freopen function is to change the file associated with a standard text stream (stderr, stdin, or stdout), as those identifiers need not be modifiable lvalues to which the value returned by the fopen function may be assigned. This commit implements a new function `flush_standard_stream` that tries to reliably flush standard streams without actually closing them. By not calling fclose(3P), we can neatly avoid the issue of accessing standard streams in an unspecified state and thus remove the infringing `NULL` assignments. Properly flushing standard streams without fclose(3P) proves to be more intricate than one may expect, though, as some filesystems like NFS may defer flushing until they see a close(3P) of the underlying descriptor. One may call fsync(3P) to remedy that, but this may incur a heavy performance penalty in some scenarios. To work around the issue and still get proper errors, we duplicate the stream's file descriptor and close that one instead, which is sufficient to cause a flush. Note that both `close_stdout` and `close_stdout_atexit` are misnamed after this change, as we do not actually close the streams now. In order to avoid unnecessary code churn, we still retain their current names. Signed-off-by: Patrick Steinhardt <ps@pks.im>
2019-08-22 04:40:15 -05:00
static inline int
flush_standard_stream(FILE *stream)
{
int fd;
errno = 0;
if (ferror(stream) != 0 || fflush(stream) != 0)
goto error;
/*
* Calling fflush is not sufficient on some filesystems
* like e.g. NFS, which may defer the actual flush until
* close. Calling fsync would help solve this, but would
* probably result in a performance hit. Thus, we work
* around this issue by calling close on a dup'd file
* descriptor from the stream.
*/
if ((fd = fileno(stream)) < 0 || (fd = dup(fd)) < 0 || close(fd) != 0)
goto error;
return 0;
error:
return (errno == EBADF) ? 0 : EOF;
}
/* Meant to be used atexit(close_stdout); */
static inline void
close_stdout(void)
{
include/closestream: fix assignment to read-only standard streams In order to avoid closing standard streams multiple times, commit 52aa1a661 (include/closestream: avoid close more than once, 2019-06-13) introduced code to set the standard output and error streams to `NULL`. As musl libc defines standard streams as constant pointers, the change causes compiler errors on systems with that libc. According to ISO C89, being able to assign to the standard text streams is not a requirement for any C implementation, see footnote 238 in chapter §7.19.5.6: The primary use of the freopen function is to change the file associated with a standard text stream (stderr, stdin, or stdout), as those identifiers need not be modifiable lvalues to which the value returned by the fopen function may be assigned. This commit implements a new function `flush_standard_stream` that tries to reliably flush standard streams without actually closing them. By not calling fclose(3P), we can neatly avoid the issue of accessing standard streams in an unspecified state and thus remove the infringing `NULL` assignments. Properly flushing standard streams without fclose(3P) proves to be more intricate than one may expect, though, as some filesystems like NFS may defer flushing until they see a close(3P) of the underlying descriptor. One may call fsync(3P) to remedy that, but this may incur a heavy performance penalty in some scenarios. To work around the issue and still get proper errors, we duplicate the stream's file descriptor and close that one instead, which is sufficient to cause a flush. Note that both `close_stdout` and `close_stdout_atexit` are misnamed after this change, as we do not actually close the streams now. In order to avoid unnecessary code churn, we still retain their current names. Signed-off-by: Patrick Steinhardt <ps@pks.im>
2019-08-22 04:40:15 -05:00
if (flush_standard_stream(stdout) != 0 && !(errno == EPIPE)) {
if (errno)
warn(_("write error"));
else
warnx(_("write error"));
_exit(CLOSE_EXIT_CODE);
}
include/closestream: fix assignment to read-only standard streams In order to avoid closing standard streams multiple times, commit 52aa1a661 (include/closestream: avoid close more than once, 2019-06-13) introduced code to set the standard output and error streams to `NULL`. As musl libc defines standard streams as constant pointers, the change causes compiler errors on systems with that libc. According to ISO C89, being able to assign to the standard text streams is not a requirement for any C implementation, see footnote 238 in chapter §7.19.5.6: The primary use of the freopen function is to change the file associated with a standard text stream (stderr, stdin, or stdout), as those identifiers need not be modifiable lvalues to which the value returned by the fopen function may be assigned. This commit implements a new function `flush_standard_stream` that tries to reliably flush standard streams without actually closing them. By not calling fclose(3P), we can neatly avoid the issue of accessing standard streams in an unspecified state and thus remove the infringing `NULL` assignments. Properly flushing standard streams without fclose(3P) proves to be more intricate than one may expect, though, as some filesystems like NFS may defer flushing until they see a close(3P) of the underlying descriptor. One may call fsync(3P) to remedy that, but this may incur a heavy performance penalty in some scenarios. To work around the issue and still get proper errors, we duplicate the stream's file descriptor and close that one instead, which is sufficient to cause a flush. Note that both `close_stdout` and `close_stdout_atexit` are misnamed after this change, as we do not actually close the streams now. In order to avoid unnecessary code churn, we still retain their current names. Signed-off-by: Patrick Steinhardt <ps@pks.im>
2019-08-22 04:40:15 -05:00
if (flush_standard_stream(stderr) != 0)
_exit(CLOSE_EXIT_CODE);
}
static inline void
close_stdout_atexit(void)
{
/*
* Note that close stdout at exit disables ASAN to report memory leaks
*/
#if !HAS_FEATURE_ADDRESS_SANITIZER
atexit(close_stdout);
#endif
}
#ifndef HAVE_FSYNC
static inline int
fsync(int fd __attribute__((__unused__)))
{
return 0;
}
#endif
static inline int
close_fd(int fd)
{
const int fsync_fail = (fsync(fd) != 0);
const int close_fail = (close(fd) != 0);
if (fsync_fail || close_fail)
return EOF;
return 0;
}
#endif /* UTIL_LINUX_CLOSESTREAM_H */