1
2 /**
3 * Copyright (C) 2018-present MongoDB, Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the Server Side Public License, version 1,
7 * as published by MongoDB, Inc.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * Server Side Public License for more details.
13 *
14 * You should have received a copy of the Server Side Public License
15 * along with this program. If not, see
16 * <http://www.mongodb.com/licensing/server-side-public-license>.
17 *
18 * As a special exception, the copyright holders give permission to link the
19 * code of portions of this program with the OpenSSL library under certain
20 * conditions as described in each individual source file and distribute
21 * linked combinations including the program with the OpenSSL library. You
22 * must comply with the Server Side Public License in all respects for
23 * all of the code used other than as permitted herein. If you modify file(s)
24 * with this exception, you may extend this exception to your version of the
25 * file(s), but you are not obligated to do so. If you do not wish to do so,
26 * delete this exception statement from your version. If you delete this
27 * exception statement from all source files in the program, then also delete
28 * it in the license file.
29 */
30
31 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage
32
33 #include "mongo/platform/basic.h"
34
35 #include "mongo/db/storage/storage_engine_lock_file.h"
36
37 #include <boost/filesystem.hpp>
38 #include <io.h>
39 #include <ostream>
40 #include <sstream>
41
42 #include "mongo/platform/process_id.h"
43 #include "mongo/util/log.h"
44 #include "mongo/util/mongoutils/str.h"
45 #include "mongo/util/text.h"
46
47 namespace mongo {
48
49 namespace {
50
_truncateFile(HANDLE handle)51 Status _truncateFile(HANDLE handle) {
52 invariant(handle != INVALID_HANDLE_VALUE);
53
54 LARGE_INTEGER largeint;
55 largeint.QuadPart = 0;
56 if (::SetFilePointerEx(handle, largeint, NULL, FILE_BEGIN) == FALSE) {
57 int errorcode = GetLastError();
58 return Status(ErrorCodes::FileStreamFailed,
59 str::stream() << "Unable to truncate lock file (SetFilePointerEx failed) "
60 << errnoWithDescription(errorcode));
61 }
62
63 if (::SetEndOfFile(handle) == FALSE) {
64 int errorcode = GetLastError();
65 return Status(ErrorCodes::FileStreamFailed,
66 str::stream() << "Unable to truncate lock file (SetEndOfFile failed) "
67 << errnoWithDescription(errorcode));
68 }
69
70 return Status::OK();
71 }
72
73 } // namespace
74
75 class StorageEngineLockFile::LockFileHandle {
76 public:
LockFileHandle()77 LockFileHandle() : _handle(INVALID_HANDLE_VALUE) {}
isValid() const78 bool isValid() const {
79 return _handle != INVALID_HANDLE_VALUE;
80 }
clear()81 void clear() {
82 _handle = INVALID_HANDLE_VALUE;
83 }
84 HANDLE _handle;
85 };
86
StorageEngineLockFile(const std::string & dbpath,StringData fileName)87 StorageEngineLockFile::StorageEngineLockFile(const std::string& dbpath, StringData fileName)
88 : _dbpath(dbpath),
89 _filespec((boost::filesystem::path(_dbpath) / fileName.toString()).string()),
90 _uncleanShutdown(boost::filesystem::exists(_filespec) &&
91 boost::filesystem::file_size(_filespec) > 0),
92 _lockFileHandle(new LockFileHandle()) {}
93
~StorageEngineLockFile()94 StorageEngineLockFile::~StorageEngineLockFile() {}
95
getFilespec() const96 std::string StorageEngineLockFile::getFilespec() const {
97 return _filespec;
98 }
99
createdByUncleanShutdown() const100 bool StorageEngineLockFile::createdByUncleanShutdown() const {
101 return _uncleanShutdown;
102 }
103
open()104 Status StorageEngineLockFile::open() {
105 try {
106 if (!boost::filesystem::exists(_dbpath)) {
107 return Status(
108 ErrorCodes::NonExistentPath,
109 str::stream() << "Data directory " << _dbpath
110 << " not found. Create the missing directory or specify another path "
111 "using (1) the --dbpath command line option, or (2) by adding the "
112 "'storage.dbPath' option in the configuration file.");
113 }
114 } catch (const std::exception& ex) {
115 return Status(ErrorCodes::UnknownError,
116 str::stream() << "Unable to check existence of data directory " << _dbpath
117 << ": "
118 << ex.what());
119 }
120
121 HANDLE lockFileHandle = CreateFileW(toNativeString(_filespec.c_str()).c_str(),
122 GENERIC_READ | GENERIC_WRITE,
123 FILE_SHARE_READ /* only allow readers access */,
124 NULL,
125 OPEN_ALWAYS /* success if fh can open */,
126 0,
127 NULL);
128
129 if (lockFileHandle == INVALID_HANDLE_VALUE) {
130 int errorcode = GetLastError();
131 if (errorcode == ERROR_ACCESS_DENIED) {
132 return Status(ErrorCodes::IllegalOperation,
133 str::stream()
134 << "Attempted to create a lock file on a read-only directory: "
135 << _dbpath);
136 }
137 return Status(ErrorCodes::DBPathInUse,
138 str::stream() << "Unable to create/open the lock file: " << _filespec << " ("
139 << errnoWithDescription(errorcode)
140 << ")."
141 << " Ensure the user executing mongod is the owner of the lock "
142 "file and has the appropriate permissions. Also make sure "
143 "that another mongod instance is not already running on the "
144 << _dbpath
145 << " directory");
146 }
147 _lockFileHandle->_handle = lockFileHandle;
148 return Status::OK();
149 }
150
close()151 void StorageEngineLockFile::close() {
152 if (!_lockFileHandle->isValid()) {
153 return;
154 }
155 CloseHandle(_lockFileHandle->_handle);
156 _lockFileHandle->clear();
157 }
158
writePid()159 Status StorageEngineLockFile::writePid() {
160 if (!_lockFileHandle->isValid()) {
161 return Status(ErrorCodes::FileNotOpen,
162 str::stream() << "Unable to write process ID to " << _filespec
163 << " because file has not been opened.");
164 }
165
166 Status status = _truncateFile(_lockFileHandle->_handle);
167 if (!status.isOK()) {
168 return status;
169 }
170
171 ProcessId pid = ProcessId::getCurrent();
172 std::stringstream ss;
173 ss << pid << std::endl;
174 std::string pidStr = ss.str();
175 DWORD bytesWritten = 0;
176 if (::WriteFile(_lockFileHandle->_handle,
177 static_cast<LPCVOID>(pidStr.c_str()),
178 static_cast<DWORD>(pidStr.size()),
179 &bytesWritten,
180 NULL) == FALSE) {
181 int errorcode = GetLastError();
182 return Status(ErrorCodes::FileStreamFailed,
183 str::stream() << "Unable to write process id " << pid.toString()
184 << " to file: "
185 << _filespec
186 << ' '
187 << errnoWithDescription(errorcode));
188 } else if (bytesWritten == 0) {
189 return Status(ErrorCodes::FileStreamFailed,
190 str::stream() << "Unable to write process id " << pid.toString()
191 << " to file: "
192 << _filespec
193 << " no data written.");
194 }
195
196 ::FlushFileBuffers(_lockFileHandle->_handle);
197
198 return Status::OK();
199 }
200
clearPidAndUnlock()201 void StorageEngineLockFile::clearPidAndUnlock() {
202 if (!_lockFileHandle->isValid()) {
203 return;
204 }
205 log() << "shutdown: removing fs lock...";
206 // This ought to be an unlink(), but Eliot says the last
207 // time that was attempted, there was a race condition
208 // with StorageEngineLockFile::open().
209 Status status = _truncateFile(_lockFileHandle->_handle);
210 if (!status.isOK()) {
211 log() << "couldn't remove fs lock " << status.toString();
212 }
213 CloseHandle(_lockFileHandle->_handle);
214 _lockFileHandle->clear();
215 }
216
217 } // namespace mongo
218