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