/* Copyright 2013 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#include "mongo/platform/basic.h"
#include "mongo/logger/rotatable_file_writer.h"
#include
#include
#include
#include
#include "mongo/base/string_data.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
namespace logger {
namespace {
/**
* Renames file "oldName" to "newName".
*
* Both names are UTF-8 encoded.
*/
int renameFile(const std::string& oldName, const std::string& newName);
} // namespace
#ifdef _WIN32
namespace {
/**
* Converts UTF-8 encoded "utf8Str" to std::wstring.
*/
std::wstring utf8ToWide(StringData utf8Str) {
if (utf8Str.empty())
return std::wstring();
// A Windows wchar_t encoding of a unicode codepoint never takes more instances of wchar_t
// than the UTF-8 encoding takes instances of char.
boost::scoped_array tempBuffer(new wchar_t[utf8Str.size()]);
tempBuffer[0] = L'\0';
int finalSize = MultiByteToWideChar(
CP_UTF8, // Code page
0, // Flags
utf8Str.rawData(), // Input string
utf8Str.size(), // Count
tempBuffer.get(), // UTF-16 output buffer
utf8Str.size() // Buffer size in wide characters
);
// TODO(schwerin): fassert finalSize > 0?
return std::wstring(tempBuffer.get(), finalSize);
}
/**
* Minimal implementation of a std::streambuf for writing to Win32 files via HANDLEs.
*
* We require this implementation and the std::ostream subclass below to handle the following:
* (1) Opening files for shared-delete access, so that open file handles may be renamed.
* (2) Opening files with non-ASCII characters in their names.
*/
class Win32FileStreambuf : public std::streambuf {
MONGO_DISALLOW_COPYING(Win32FileStreambuf);
public:
Win32FileStreambuf();
virtual ~Win32FileStreambuf();
bool open(StringData fileName, bool append);
bool is_open() { return _fileHandle != INVALID_HANDLE_VALUE; }
private:
virtual std::streamsize xsputn(const char* s, std::streamsize count);
virtual int_type overflow(int_type ch = traits_type::eof());
std::streamsize writeToFile(const char* s, std::streamsize count);
HANDLE _fileHandle;
};
/**
* Minimal implementation of a stream to Win32 files.
*/
class Win32FileOStream : public std::ostream {
MONGO_DISALLOW_COPYING(Win32FileOStream);
public:
/**
* Constructs an instance, opening "fileName" in append or truncate mode according to
* "append".
*/
Win32FileOStream(const std::string& fileName, bool append) : std::ostream(&_buf), _buf() {
if (!_buf.open(fileName, append)) {
setstate(failbit);
}
}
virtual ~Win32FileOStream() {}
private:
Win32FileStreambuf _buf;
};
Win32FileStreambuf::Win32FileStreambuf() : _fileHandle(INVALID_HANDLE_VALUE) {}
Win32FileStreambuf::~Win32FileStreambuf() {
if (is_open()) {
CloseHandle(_fileHandle); // TODO(schwerin): Should we check for failure?
}
}
bool Win32FileStreambuf::open(StringData fileName, bool append) {
_fileHandle = CreateFileW(
utf8ToWide(fileName).c_str(), // lpFileName
GENERIC_WRITE, // dwDesiredAccess
FILE_SHARE_DELETE | FILE_SHARE_READ, // dwShareMode
NULL, // lpSecurityAttributes
OPEN_ALWAYS, // dwCreationDisposition
FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
NULL // hTemplateFile
);
if (INVALID_HANDLE_VALUE == _fileHandle)
return false;
LARGE_INTEGER zero;
zero.QuadPart = 0LL;
if (append) {
if (SetFilePointerEx(_fileHandle, zero, NULL, FILE_END)) {
return true;
}
}
else {
if (SetFilePointerEx(_fileHandle, zero, NULL, FILE_BEGIN) &&
SetEndOfFile(_fileHandle)) {
return true;
}
}
// TODO(schwerin): Record error info?
CloseHandle(_fileHandle);
return false;
}
std::streamsize Win32FileStreambuf::writeToFile(const char* s, std::streamsize count) {
DWORD totalBytesWritten = 0;
while (count > totalBytesWritten) {
DWORD bytesWritten;
if (!WriteFile(
_fileHandle,
s,
count - totalBytesWritten,
&bytesWritten,
NULL)) {
break;
}
totalBytesWritten += bytesWritten;
}
return totalBytesWritten;
}
// Called when strings are written to ostream
std::streamsize Win32FileStreambuf::xsputn(const char* s, std::streamsize count) {
DWORD totalBytesWritten = 0;
// Scan for embedded newlines before end
// this should be rare since the newline should only be at the end
const char* startPos = s;
for (int i = 0; i < count; i++) {
if (s[i] == '\n') {
totalBytesWritten += writeToFile(startPos, i - (startPos - s));
writeToFile("\r\n", 2);
totalBytesWritten += 1; // Caller expected we only wrote 1 char, so tell them so
startPos = &s[i + 1];
}
}
// Did the string not end on "\n"? Write the remaining, no need for CRLF
// as upper layers are responsible for it
if ((startPos - s) != count) {
totalBytesWritten += writeToFile(startPos, count - (startPos - s));
}
return totalBytesWritten;
}
// Overflow is called for single character writes to the ostream
Win32FileStreambuf::int_type Win32FileStreambuf::overflow(int_type ch) {
if (ch == traits_type::eof())
return ~ch; // Returning traits_type::eof() => failure, anything else => success.
char toPut = static_cast(ch);
if (1 == xsputn(&toPut, 1))
return ch;
return traits_type::eof();
}
// Win32 implementation of renameFile that handles non-ascii file names.
int renameFile(const std::string& oldName, const std::string& newName) {
return _wrename(utf8ToWide(oldName).c_str(), utf8ToWide(newName).c_str());
}
} // namespace
#else
namespace {
// *nix implementation of renameFile that assumes the OS is encoding file names in UTF-8.
int renameFile(const std::string& oldName, const std::string& newName) {
return rename(oldName.c_str(), newName.c_str());
}
} // namespace
#endif
RotatableFileWriter::RotatableFileWriter() : _stream(NULL) {}
RotatableFileWriter::Use::Use(RotatableFileWriter* writer) :
_writer(writer),
_lock(writer->_mutex) {
}
Status RotatableFileWriter::Use::setFileName(const std::string& name, bool append) {
_writer->_fileName = name;
return _openFileStream(append);
}
Status RotatableFileWriter::Use::rotate(bool renameOnRotate, const std::string& renameTarget) {
if (_writer->_stream) {
_writer->_stream->flush();
if(renameOnRotate) {
try {
if (boost::filesystem::exists(renameTarget)) {
return Status(ErrorCodes::FileRenameFailed, mongoutils::str::stream() <<
"Renaming file " << _writer->_fileName << " to " <<
renameTarget << " failed; destination already exists");
}
} catch (const std::exception& e) {
return Status(ErrorCodes::FileRenameFailed, mongoutils::str::stream() <<
"Renaming file " << _writer->_fileName << " to " <<
renameTarget << " failed; Cannot verify whether destination "
"already exists: " << e.what());
}
if (0 != renameFile(_writer->_fileName, renameTarget)) {
return Status(ErrorCodes::FileRenameFailed, mongoutils::str::stream() <<
"Failed to rename \"" << _writer->_fileName << "\" to \"" <<
renameTarget << "\": " << strerror(errno) << " (" << errno << ')');
//TODO(schwerin): Make errnoWithDescription() available in the logger library, and
//use it here.
}
}
}
return _openFileStream(false);
}
Status RotatableFileWriter::Use::status() {
if (!_writer->_stream) {
return Status(ErrorCodes::FileNotOpen,
mongoutils::str::stream() << "File \"" << _writer->_fileName <<
"\" not open");
}
if (_writer->_stream->fail()) {
return Status(ErrorCodes::FileStreamFailed,
mongoutils::str::stream() << "File \"" << _writer->_fileName <<
"\" in failed state");
}
return Status::OK();
}
Status RotatableFileWriter::Use::_openFileStream(bool append) {
using std::swap;
#ifdef _WIN32
boost::scoped_ptr newStream(
new Win32FileOStream(_writer->_fileName, append));
#else
std::ios::openmode mode = std::ios::out;
if (append) {
mode |= std::ios::app;
}
else {
mode |= std::ios::trunc;
}
boost::scoped_ptr newStream(
new std::ofstream(_writer->_fileName.c_str(), mode));
#endif
if (newStream->fail()) {
return Status(ErrorCodes::FileNotOpen, "Failed to open \"" + _writer->_fileName + "\"");
}
swap(_writer->_stream, newStream);
return status();
}
} // namespace logger
} // namespace mongo