Skip to content

Commit 78c9261

Browse files
Dyre TjeldvollDyre Tjeldvoll
authored andcommitted
Bug#26447825: MYSQLD COMPLETELY SILENT EVEN IN FAILURE IF LOG-ERROR IS
NOT WRITABLE Problem: C-library function freopen() is not atomic. It closes the stream given as argument before trying (and thereby checking if it is possible) to open the file which the stream is to be associated with. So in the event that the open() fails, the user is left without a working stream where the error message for the failed open can go. Likewise, any buffered messages not yet written to the stream are also lost. Solution: Stop using freopen() and re-implement my_freopen() using fopen(), fileno() and dup2(). That way any error from open() (or fileno()) is caught before doing anything which affects the stream. According to the man-page, dup2() is atomic.
1 parent dee592d commit 78c9261

File tree

4 files changed

+193
-3
lines changed

4 files changed

+193
-3
lines changed

mysys/my_fopen.c

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
1+
/* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License as published by
@@ -143,7 +143,68 @@ static FILE *my_win_freopen(const char *path, const char *mode, FILE *stream)
143143

144144
return stream;
145145
}
146+
#else
147+
148+
/**
149+
Replacement for freopen() which will not close the stream until the
150+
new file has been successfully opened. This gives the ability log
151+
the reason for reopen's failure to the stream, and also to flush any
152+
buffered messages already written to the stream, before terminating
153+
the process.
154+
155+
After a new stream to path has been opened, its file descriptor is
156+
obtained, and dup2() is used to associate the original stream with
157+
the new file. The newly opened stream and associated file descriptor
158+
is then closed with fclose().
159+
160+
@param path file which the stream should be opened to
161+
@param mode FILE mode to use when opening new file
162+
@param stream stream to reopen
163+
164+
@retval FILE stream being passed in if successful,
165+
@retval nullptr otherwise
166+
*/
167+
static FILE *my_safe_freopen(const char *path, const char *mode, FILE *stream)
168+
{
169+
int streamfd= -1;
170+
FILE *pathstream= NULL;
171+
int pathfd= -1;
172+
int ds= -1;
173+
174+
DBUG_ASSERT(path != NULL && stream != NULL);
175+
streamfd= fileno(stream);
176+
if (streamfd == -1)
177+
{
178+
/* We have not done anything to the stream, but if we cannot
179+
get its fd, it is probably in a bad state anyway... */
180+
return NULL;
181+
}
182+
pathstream= fopen(path, mode);
183+
if (pathstream == NULL)
184+
{
185+
/* Failed to open file for some reason. stream should still be
186+
usable. */
187+
return NULL;
188+
}
146189

190+
pathfd= fileno(pathstream);
191+
if (pathfd == -1)
192+
{
193+
fclose(pathstream);
194+
return NULL;
195+
}
196+
do
197+
{
198+
ds= fflush(stream);
199+
if (ds == 0)
200+
{
201+
ds= dup2(pathfd, streamfd);
202+
}
203+
}
204+
while (ds == -1 && errno == EINTR);
205+
fclose(pathstream);
206+
return (ds == -1 ? NULL : stream);
207+
}
147208
#endif
148209

149210

@@ -168,7 +229,7 @@ FILE *my_freopen(const char *path, const char *mode, FILE *stream)
168229
#if defined(_WIN32)
169230
result= my_win_freopen(path, mode, stream);
170231
#else
171-
result= freopen(path, mode, stream);
232+
result= my_safe_freopen(path, mode, stream);
172233
#endif
173234

174235
return result;

sql/log.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
1+
/* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License as published by
@@ -1997,7 +1997,13 @@ bool open_error_log(const char *filename)
19971997
while (retries-- && errors);
19981998

19991999
if (errors)
2000+
{
2001+
char errbuf[MYSYS_STRERROR_SIZE];
2002+
sql_print_error("Could not open file '%s' for error logging: %s",
2003+
filename, my_strerror(errbuf, sizeof(errbuf), errno));
2004+
flush_error_log_messages();
20002005
return true;
2006+
}
20012007

20022008
/* The error stream must be unbuffered. */
20032009
setbuf(stderr, NULL);

unittest/gunit/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ SET(TESTS
290290
mysys_lf
291291
mysys_my_atomic
292292
mysys_my_b_vprintf
293+
mysys_my_freopen
293294
mysys_my_loadpath
294295
mysys_my_malloc
295296
mysys_my_pwrite

unittest/gunit/mysys_my_freopen-t.cc

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/* Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
2+
3+
This program is free software; you can redistribute it and/or modify
4+
it under the terms of the GNU General Public License as published by
5+
the Free Software Foundation; version 2 of the License.
6+
7+
This program is distributed in the hope that it will be useful,
8+
but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
GNU General Public License for more details.
11+
12+
You should have received a copy of the GNU General Public License
13+
along with this program; if not, write to the Free Software
14+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
15+
16+
#include "my_config.h"
17+
18+
#include <errno.h>
19+
#include <sys/types.h>
20+
#include <sys/stat.h>
21+
#include <fcntl.h>
22+
23+
#include <gmock/gmock.h>
24+
#include <gtest/gtest.h>
25+
#include <stddef.h>
26+
#ifdef HAVE_UNISTD_H
27+
#include <unistd.h>
28+
#endif
29+
30+
#include "my_sys.h"
31+
32+
#ifndef _WIN32
33+
34+
namespace mysys_my_freopen_unittest {
35+
FILE *null_file= NULL;
36+
37+
class MysysMyFreopenTest : public ::testing::Test
38+
{
39+
public:
40+
FILE *stream;
41+
char name[32];
42+
MysysMyFreopenTest() :
43+
stream(NULL)
44+
{
45+
strncpy(name, "MyFreopen_XXXXXX", 32);
46+
}
47+
virtual void SetUp()
48+
{
49+
int fd= mkstemp(name);
50+
stream= fdopen(fd, "a");
51+
}
52+
virtual void TearDown()
53+
{
54+
if (stream != NULL)
55+
{
56+
fclose(stream);
57+
unlink(name);
58+
}
59+
}
60+
};
61+
62+
63+
// Test case demonstates that freopen is not atomic
64+
TEST_F(MysysMyFreopenTest, FreopenFailure)
65+
{
66+
EXPECT_EQ(null_file, freopen("/", "a", stream));
67+
const char *txt= "This text should end up in old stream file";
68+
EXPECT_EQ(EOF, fputs(txt, stream));
69+
EXPECT_NE(0, ferror(stream));
70+
EXPECT_EQ(0, fflush(stream));
71+
72+
char buf[64]= "nada";
73+
FILE *instream= fopen(name, "r");
74+
EXPECT_NE(null_file, instream);
75+
EXPECT_EQ(static_cast<char*>(NULL), fgets(buf, 64, instream));
76+
EXPECT_EQ(0, ferror(instream));
77+
EXPECT_NE(0, feof(instream));
78+
EXPECT_STREQ("nada", buf);
79+
EXPECT_EQ(0, fclose(instream));
80+
}
81+
82+
// Positive test case for the new version of my_freopen
83+
TEST_F(MysysMyFreopenTest, MyFreopenOK)
84+
{
85+
char fname[32] = "MyFreopenOK_XXXXXX";
86+
int fd= mkstemp(fname);
87+
88+
EXPECT_EQ(stream, my_freopen(fname, "a", stream));
89+
const char *txt= "This text should end up in fname";
90+
int txtlen= strlen(txt);
91+
EXPECT_EQ(32, txtlen);
92+
EXPECT_LT(0, fputs(txt, stream));
93+
EXPECT_EQ(0, fflush(stream));
94+
95+
char buf[64];
96+
FILE *instream= fdopen(fd, "r");
97+
EXPECT_NE(null_file, instream);
98+
EXPECT_EQ(buf, fgets(buf, 64, instream));
99+
EXPECT_STREQ(txt, buf);
100+
EXPECT_EQ(0, fclose(instream));
101+
EXPECT_EQ(0, unlink(fname));
102+
}
103+
104+
105+
// Negative test case for my_reopen. Shows that even if my_reopen
106+
// fails, it is still possible to write to the stream.
107+
TEST_F(MysysMyFreopenTest, MyFreopenFailure)
108+
{
109+
EXPECT_EQ(null_file, my_freopen("/", "a", stream));
110+
const char *txt= "This text should end up in old stream file";
111+
EXPECT_LT(0, fputs(txt, stream));
112+
EXPECT_EQ(0, fflush(stream));
113+
114+
char buf[64];
115+
FILE *instream= fopen(name, "r");
116+
EXPECT_NE(null_file, instream);
117+
EXPECT_EQ(buf, fgets(buf, 64, instream));
118+
EXPECT_STREQ(txt, buf);
119+
EXPECT_EQ(0, fclose(instream));
120+
}
121+
}
122+
#endif

0 commit comments

Comments
 (0)