1 // Copyright 2014 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "util/file/file_io.h"
16
17 #include <algorithm>
18 #include <limits>
19
20 #include "base/files/file_path.h"
21 #include "base/logging.h"
22 #include "base/notreached.h"
23 #include "base/strings/utf_string_conversions.h"
24
25 namespace {
26
IsSocketHandle(HANDLE file)27 bool IsSocketHandle(HANDLE file) {
28 if (GetFileType(file) == FILE_TYPE_PIPE) {
29 // FILE_TYPE_PIPE means that it's a socket, a named pipe, or an anonymous
30 // pipe. If we are unable to retrieve the pipe information, we know it's a
31 // socket.
32 return !GetNamedPipeInfo(file, nullptr, nullptr, nullptr, nullptr);
33 }
34 return false;
35 }
36
37 } // namespace
38
39 namespace crashpad {
40
41 namespace {
42
43 // kMaxReadWriteSize needs to be limited to the range of DWORD for the calls to
44 // ::ReadFile() and ::WriteFile(), and also limited to the range of
45 // FileOperationResult to be able to adequately express the number of bytes read
46 // and written in the return values from ReadFile() and NativeWriteFile(). In a
47 // 64-bit build, the former will control, and the limit will be (2^32)-1. In a
48 // 32-bit build, the latter will control, and the limit will be (2^31)-1.
49 constexpr size_t kMaxReadWriteSize = std::min(
50 static_cast<size_t>(std::numeric_limits<DWORD>::max()),
51 static_cast<size_t>(std::numeric_limits<FileOperationResult>::max()));
52
OpenFileForOutput(DWORD access,const base::FilePath & path,FileWriteMode mode,FilePermissions permissions)53 FileHandle OpenFileForOutput(DWORD access,
54 const base::FilePath& path,
55 FileWriteMode mode,
56 FilePermissions permissions) {
57 DCHECK(access & GENERIC_WRITE);
58 DCHECK_EQ(access & ~(GENERIC_READ | GENERIC_WRITE), 0u);
59
60 DWORD disposition = 0;
61 switch (mode) {
62 case FileWriteMode::kReuseOrFail:
63 disposition = OPEN_EXISTING;
64 break;
65 case FileWriteMode::kReuseOrCreate:
66 disposition = OPEN_ALWAYS;
67 break;
68 case FileWriteMode::kTruncateOrCreate:
69 disposition = CREATE_ALWAYS;
70 break;
71 case FileWriteMode::kCreateOrFail:
72 disposition = CREATE_NEW;
73 break;
74 }
75 return CreateFile(path.value().c_str(),
76 access,
77 FILE_SHARE_READ | FILE_SHARE_WRITE,
78 nullptr,
79 disposition,
80 FILE_ATTRIBUTE_NORMAL,
81 nullptr);
82 }
83
84 } // namespace
85
86 namespace internal {
87
NativeWriteFile(FileHandle file,const void * buffer,size_t size)88 FileOperationResult NativeWriteFile(FileHandle file,
89 const void* buffer,
90 size_t size) {
91 // TODO(scottmg): This might need to handle the limit for pipes across a
92 // network in the future.
93
94 const DWORD write_size =
95 static_cast<DWORD>(std::min(size, kMaxReadWriteSize));
96
97 DWORD bytes_written;
98 if (!::WriteFile(file, buffer, write_size, &bytes_written, nullptr))
99 return -1;
100
101 CHECK_NE(bytes_written, static_cast<DWORD>(-1));
102 DCHECK_LE(static_cast<size_t>(bytes_written), write_size);
103 return bytes_written;
104 }
105
106 } // namespace internal
107
ReadFile(FileHandle file,void * buffer,size_t size)108 FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) {
109 DCHECK(!IsSocketHandle(file));
110
111 const DWORD read_size = static_cast<DWORD>(std::min(size, kMaxReadWriteSize));
112
113 while (true) {
114 DWORD bytes_read;
115 BOOL success = ::ReadFile(file, buffer, read_size, &bytes_read, nullptr);
116 if (!success) {
117 if (GetLastError() == ERROR_BROKEN_PIPE) {
118 // When reading a pipe and the write handle has been closed, ReadFile
119 // fails with ERROR_BROKEN_PIPE, but only once all pending data has been
120 // read. Treat this as EOF.
121 return 0;
122 }
123 return -1;
124 }
125
126 CHECK_NE(bytes_read, static_cast<DWORD>(-1));
127 DCHECK_LE(bytes_read, read_size);
128 if (bytes_read != 0 || GetFileType(file) != FILE_TYPE_PIPE) {
129 // Zero bytes read for a file indicates reaching EOF. Zero bytes read from
130 // a pipe indicates only that there was a zero byte WriteFile issued on
131 // the other end, so continue reading.
132 return bytes_read;
133 }
134 }
135 }
136
OpenFileForRead(const base::FilePath & path)137 FileHandle OpenFileForRead(const base::FilePath& path) {
138 return CreateFile(path.value().c_str(),
139 GENERIC_READ,
140 FILE_SHARE_READ | FILE_SHARE_WRITE,
141 nullptr,
142 OPEN_EXISTING,
143 0,
144 nullptr);
145 }
146
OpenFileForWrite(const base::FilePath & path,FileWriteMode mode,FilePermissions permissions)147 FileHandle OpenFileForWrite(const base::FilePath& path,
148 FileWriteMode mode,
149 FilePermissions permissions) {
150 return OpenFileForOutput(GENERIC_WRITE, path, mode, permissions);
151 }
152
OpenFileForReadAndWrite(const base::FilePath & path,FileWriteMode mode,FilePermissions permissions)153 FileHandle OpenFileForReadAndWrite(const base::FilePath& path,
154 FileWriteMode mode,
155 FilePermissions permissions) {
156 return OpenFileForOutput(
157 GENERIC_READ | GENERIC_WRITE, path, mode, permissions);
158 }
159
LoggingOpenFileForRead(const base::FilePath & path)160 FileHandle LoggingOpenFileForRead(const base::FilePath& path) {
161 FileHandle file = OpenFileForRead(path);
162 PLOG_IF(ERROR, file == INVALID_HANDLE_VALUE)
163 << "CreateFile " << base::WideToUTF8(path.value());
164 return file;
165 }
166
LoggingOpenFileForWrite(const base::FilePath & path,FileWriteMode mode,FilePermissions permissions)167 FileHandle LoggingOpenFileForWrite(const base::FilePath& path,
168 FileWriteMode mode,
169 FilePermissions permissions) {
170 FileHandle file = OpenFileForWrite(path, mode, permissions);
171 PLOG_IF(ERROR, file == INVALID_HANDLE_VALUE)
172 << "CreateFile " << base::WideToUTF8(path.value());
173 return file;
174 }
175
LoggingOpenFileForReadAndWrite(const base::FilePath & path,FileWriteMode mode,FilePermissions permissions)176 FileHandle LoggingOpenFileForReadAndWrite(const base::FilePath& path,
177 FileWriteMode mode,
178 FilePermissions permissions) {
179 FileHandle file = OpenFileForReadAndWrite(path, mode, permissions);
180 PLOG_IF(ERROR, file == INVALID_HANDLE_VALUE)
181 << "CreateFile " << base::WideToUTF8(path.value());
182 return file;
183 }
184
LoggingLockFile(FileHandle file,FileLocking locking)185 bool LoggingLockFile(FileHandle file, FileLocking locking) {
186 DWORD flags =
187 (locking == FileLocking::kExclusive) ? LOCKFILE_EXCLUSIVE_LOCK : 0;
188
189 // Note that the `Offset` fields of overlapped indicate the start location for
190 // locking (beginning of file in this case), and `hEvent` must be also be set
191 // to 0.
192 OVERLAPPED overlapped = {0};
193 if (!LockFileEx(file, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) {
194 PLOG(ERROR) << "LockFileEx";
195 return false;
196 }
197 return true;
198 }
199
LoggingUnlockFile(FileHandle file)200 bool LoggingUnlockFile(FileHandle file) {
201 // Note that the `Offset` fields of overlapped indicate the start location for
202 // locking (beginning of file in this case), and `hEvent` must be also be set
203 // to 0.
204 OVERLAPPED overlapped = {0};
205 if (!UnlockFileEx(file, 0, MAXDWORD, MAXDWORD, &overlapped)) {
206 PLOG(ERROR) << "UnlockFileEx";
207 return false;
208 }
209 return true;
210 }
211
LoggingSeekFile(FileHandle file,FileOffset offset,int whence)212 FileOffset LoggingSeekFile(FileHandle file, FileOffset offset, int whence) {
213 DWORD method = 0;
214 switch (whence) {
215 case SEEK_SET:
216 method = FILE_BEGIN;
217 break;
218 case SEEK_CUR:
219 method = FILE_CURRENT;
220 break;
221 case SEEK_END:
222 method = FILE_END;
223 break;
224 default:
225 NOTREACHED();
226 break;
227 }
228
229 LARGE_INTEGER distance_to_move;
230 distance_to_move.QuadPart = offset;
231 LARGE_INTEGER new_offset;
232 BOOL result = SetFilePointerEx(file, distance_to_move, &new_offset, method);
233 if (!result) {
234 PLOG(ERROR) << "SetFilePointerEx";
235 return -1;
236 }
237 return new_offset.QuadPart;
238 }
239
LoggingTruncateFile(FileHandle file)240 bool LoggingTruncateFile(FileHandle file) {
241 if (LoggingSeekFile(file, 0, SEEK_SET) != 0)
242 return false;
243 if (!SetEndOfFile(file)) {
244 PLOG(ERROR) << "SetEndOfFile";
245 return false;
246 }
247 return true;
248 }
249
LoggingCloseFile(FileHandle file)250 bool LoggingCloseFile(FileHandle file) {
251 BOOL rv = CloseHandle(file);
252 PLOG_IF(ERROR, !rv) << "CloseHandle";
253 return !!rv;
254 }
255
LoggingFileSizeByHandle(FileHandle file)256 FileOffset LoggingFileSizeByHandle(FileHandle file) {
257 LARGE_INTEGER file_size;
258 if (!GetFileSizeEx(file, &file_size)) {
259 PLOG(ERROR) << "GetFileSizeEx";
260 return -1;
261 }
262 return file_size.QuadPart;
263 }
264
StdioFileHandle(StdioStream stdio_stream)265 FileHandle StdioFileHandle(StdioStream stdio_stream) {
266 DWORD standard_handle;
267 switch (stdio_stream) {
268 case StdioStream::kStandardInput:
269 standard_handle = STD_INPUT_HANDLE;
270 break;
271 case StdioStream::kStandardOutput:
272 standard_handle = STD_OUTPUT_HANDLE;
273 break;
274 case StdioStream::kStandardError:
275 standard_handle = STD_ERROR_HANDLE;
276 break;
277 default:
278 NOTREACHED();
279 return INVALID_HANDLE_VALUE;
280 }
281
282 HANDLE handle = GetStdHandle(standard_handle);
283 PLOG_IF(ERROR, handle == INVALID_HANDLE_VALUE) << "GetStdHandle";
284 return handle;
285 }
286
287 } // namespace crashpad
288