1 /**
2 * @file win32/fs.cpp
3 * @brief Win32 filesystem/directory access/notification
4 *
5 * (c) 2013 by Mega Limited, Auckland, New Zealand
6 *
7 * This file is part of the MEGA SDK - Client Access Engine.
8 *
9 * Applications using the MEGA API must present a valid application key
10 * and comply with the the rules set forth in the Terms of Service.
11 *
12 * The MEGA SDK is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 *
16 * @copyright Simplified (2-clause) BSD License.
17 *
18 * You should have received a copy of the license along with this
19 * program.
20 */
21
22 #include "mega.h"
23 #include <wow64apiset.h>
24
25 namespace mega {
26
27 std::string gWindowsSeparator((const char*)(const wchar_t*)L"\\", 2);
28
WinFileAccess(Waiter * w)29 WinFileAccess::WinFileAccess(Waiter *w) : FileAccess(w)
30 {
31 hFile = INVALID_HANDLE_VALUE;
32 hFind = INVALID_HANDLE_VALUE;
33
34 fsidvalid = false;
35 }
36
~WinFileAccess()37 WinFileAccess::~WinFileAccess()
38 {
39 if (hFile != INVALID_HANDLE_VALUE)
40 {
41 CloseHandle(hFile);
42 assert(hFind == INVALID_HANDLE_VALUE);
43 }
44 else if (hFind != INVALID_HANDLE_VALUE)
45 {
46 FindClose(hFind);
47 }
48 }
49
sysread(byte * dst,unsigned len,m_off_t pos)50 bool WinFileAccess::sysread(byte* dst, unsigned len, m_off_t pos)
51 {
52 DWORD dwRead;
53 assert(hFile != INVALID_HANDLE_VALUE);
54
55 if (!SetFilePointerEx(hFile, *(LARGE_INTEGER*)&pos, NULL, FILE_BEGIN))
56 {
57 DWORD e = GetLastError();
58 retry = WinFileSystemAccess::istransient(e);
59 LOG_err << "SetFilePointerEx failed for reading. Error: " << e;
60 return false;
61 }
62
63 if (!ReadFile(hFile, (LPVOID)dst, (DWORD)len, &dwRead, NULL))
64 {
65 DWORD e = GetLastError();
66 retry = WinFileSystemAccess::istransient(e);
67 LOG_err << "ReadFile failed. Error: " << e;
68 return false;
69 }
70
71 if (dwRead != len)
72 {
73 retry = false;
74 LOG_err << "ReadFile failed (dwRead) " << dwRead << " - " << len;
75 return false;
76 }
77 return true;
78 }
79
fwrite(const byte * data,unsigned len,m_off_t pos)80 bool WinFileAccess::fwrite(const byte* data, unsigned len, m_off_t pos)
81 {
82 DWORD dwWritten;
83
84 if (!SetFilePointerEx(hFile, *(LARGE_INTEGER*)&pos, NULL, FILE_BEGIN))
85 {
86 DWORD e = GetLastError();
87 retry = WinFileSystemAccess::istransient(e);
88 LOG_err << "SetFilePointerEx failed for writing. Error: " << e;
89 return false;
90 }
91
92 if (!WriteFile(hFile, (LPCVOID)data, (DWORD)len, &dwWritten, NULL))
93 {
94 DWORD e = GetLastError();
95 retry = WinFileSystemAccess::istransient(e);
96 LOG_err << "WriteFile failed. Error: " << e;
97 return false;
98 }
99
100 if (dwWritten != len)
101 {
102 retry = false;
103 LOG_err << "WriteFile failed (dwWritten) " << dwWritten << " - " << len;
104 return false;
105 }
106
107 if (!FlushFileBuffers(hFile))
108 {
109 DWORD e = GetLastError();
110 retry = WinFileSystemAccess::istransient(e);
111 LOG_err << "FlushFileBuffers failed. Error: " << e;
112 return false;
113 }
114 return true;
115 }
116
FileTime_to_POSIX(FILETIME * ft)117 m_time_t FileTime_to_POSIX(FILETIME* ft)
118 {
119 LARGE_INTEGER date;
120
121 date.HighPart = ft->dwHighDateTime;
122 date.LowPart = ft->dwLowDateTime;
123
124 // remove the diff between 1970 and 1601 and convert back from 100-nanoseconds to seconds
125 int64_t t = date.QuadPart - 11644473600000 * 10000;
126
127 // clamp
128 if (t < 0) return 0;
129
130 t /= 10000000;
131
132 FileSystemAccess::captimestamp(&t);
133
134 return t;
135 }
136
sysstat(m_time_t * mtime,m_off_t * size)137 bool WinFileAccess::sysstat(m_time_t* mtime, m_off_t* size)
138 {
139 assert(!nonblocking_localname.empty());
140 WIN32_FILE_ATTRIBUTE_DATA fad;
141
142 type = TYPE_UNKNOWN;
143 if (!GetFileAttributesExW((LPCWSTR)nonblocking_localname.editStringDirect()->data(), GetFileExInfoStandard, (LPVOID)&fad))
144 {
145 DWORD e = GetLastError();
146 errorcode = e;
147 retry = WinFileSystemAccess::istransient(e);
148 return false;
149 }
150
151 errorcode = 0;
152 if (SimpleLogger::logCurrentLevel >= logDebug && skipattributes(fad.dwFileAttributes))
153 {
154 WinFileSystemAccess wfsa;
155 LOG_debug << "Incompatible attributes (" << fad.dwFileAttributes << ") for file " << nonblocking_localname.toPath(wfsa);
156 }
157
158 if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
159 {
160 type = FOLDERNODE;
161 retry = false;
162 return false;
163 }
164
165 retry = false;
166 type = FILENODE;
167 *mtime = FileTime_to_POSIX(&fad.ftLastWriteTime);
168 *size = ((m_off_t)fad.nFileSizeHigh << 32) + (m_off_t)fad.nFileSizeLow;
169
170 return true;
171 }
172
sysopen(bool async)173 bool WinFileAccess::sysopen(bool async)
174 {
175 assert(hFile == INVALID_HANDLE_VALUE);
176 assert(!nonblocking_localname.empty());
177
178 if (hFile != INVALID_HANDLE_VALUE)
179 {
180 sysclose();
181 }
182
183 #ifdef WINDOWS_PHONE
184 hFile = CreateFile2((LPCWSTR)localname.data(), GENERIC_READ,
185 FILE_SHARE_WRITE | FILE_SHARE_READ,
186 OPEN_EXISTING, NULL);
187 #else
188 hFile = CreateFileW((LPCWSTR)nonblocking_localname.editStringDirect()->data(), GENERIC_READ,
189 FILE_SHARE_WRITE | FILE_SHARE_READ,
190 NULL, OPEN_EXISTING, async ? FILE_FLAG_OVERLAPPED : 0, NULL);
191 #endif
192
193 if (hFile == INVALID_HANDLE_VALUE)
194 {
195 DWORD e = GetLastError();
196 LOG_debug << "Unable to open file (sysopen). Error code: " << e;
197 retry = WinFileSystemAccess::istransient(e);
198 return false;
199 }
200
201 return true;
202 }
203
sysclose()204 void WinFileAccess::sysclose()
205 {
206 assert(!nonblocking_localname.empty());
207 assert(hFile != INVALID_HANDLE_VALUE);
208
209 if (hFile != INVALID_HANDLE_VALUE)
210 {
211 CloseHandle(hFile);
212 hFile = INVALID_HANDLE_VALUE;
213 }
214 }
215
216 #ifndef WINDOWS_PHONE
217
WinAsyncIOContext()218 WinAsyncIOContext::WinAsyncIOContext() : AsyncIOContext()
219 {
220 overlapped = NULL;
221 }
222
~WinAsyncIOContext()223 WinAsyncIOContext::~WinAsyncIOContext()
224 {
225 LOG_verbose << "Deleting WinAsyncIOContext";
226 finish();
227 }
228
finish()229 void WinAsyncIOContext::finish()
230 {
231 if (overlapped)
232 {
233 if (!finished)
234 {
235 LOG_debug << "Synchronously waiting for async operation";
236 AsyncIOContext::finish();
237 }
238
239 delete overlapped;
240 overlapped = NULL;
241 }
242 assert(finished);
243 }
244
newasynccontext()245 AsyncIOContext *WinFileAccess::newasynccontext()
246 {
247 return new WinAsyncIOContext();
248 }
249
asyncopfinished(DWORD dwErrorCode,DWORD dwNumberOfBytesTransfered,LPOVERLAPPED lpOverlapped)250 VOID WinFileAccess::asyncopfinished(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
251 {
252 WinAsyncIOContext *context = (WinAsyncIOContext *)(lpOverlapped->hEvent);
253 context->failed = dwErrorCode || dwNumberOfBytesTransfered != context->len;
254 if (!context->failed)
255 {
256 if (context->op == AsyncIOContext::READ)
257 {
258 memset((void *)(((char *)(context->buffer)) + context->len), 0, context->pad);
259 LOG_verbose << "Async read finished OK";
260 }
261 else
262 {
263 LOG_verbose << "Async write finished OK";
264 }
265 }
266 else
267 {
268 LOG_warn << "Async operation finished with error: " << dwErrorCode;
269 }
270
271 context->retry = WinFileSystemAccess::istransient(dwErrorCode);
272 context->finished = true;
273 if (context->userCallback)
274 {
275 context->userCallback(context->userData);
276 }
277 }
278
279 #endif
280
asyncavailable()281 bool WinFileAccess::asyncavailable()
282 {
283 #ifdef WINDOWS_PHONE
284 return false;
285 #endif
286 return true;
287 }
288
asyncsysopen(AsyncIOContext * context)289 void WinFileAccess::asyncsysopen(AsyncIOContext *context)
290 {
291 #ifndef WINDOWS_PHONE
292 auto path = LocalPath::fromLocalname(string((char *)context->buffer, context->len));
293 bool read = context->access & AsyncIOContext::ACCESS_READ;
294 bool write = context->access & AsyncIOContext::ACCESS_WRITE;
295
296 context->failed = !fopen_impl(path, read, write, true, nullptr, false);
297 context->retry = retry;
298 context->finished = true;
299 if (context->userCallback)
300 {
301 context->userCallback(context->userData);
302 }
303 #endif
304 }
305
asyncsysread(AsyncIOContext * context)306 void WinFileAccess::asyncsysread(AsyncIOContext *context)
307 {
308 #ifndef WINDOWS_PHONE
309 if (!context)
310 {
311 return;
312 }
313
314 WinAsyncIOContext *winContext = dynamic_cast<WinAsyncIOContext*>(context);
315 if (!winContext)
316 {
317 context->failed = true;
318 context->retry = false;
319 context->finished = true;
320 if (context->userCallback)
321 {
322 context->userCallback(context->userData);
323 }
324 return;
325 }
326
327 OVERLAPPED *overlapped = new OVERLAPPED;
328 memset(overlapped, 0, sizeof (OVERLAPPED));
329
330 overlapped->Offset = winContext->pos & 0xFFFFFFFF;
331 overlapped->OffsetHigh = (winContext->pos >> 32) & 0xFFFFFFFF;
332 overlapped->hEvent = winContext;
333 winContext->overlapped = overlapped;
334
335 if (!ReadFileEx(hFile, (LPVOID)winContext->buffer, (DWORD)winContext->len,
336 overlapped, asyncopfinished))
337 {
338 DWORD e = GetLastError();
339 winContext->retry = WinFileSystemAccess::istransient(e);
340 winContext->failed = true;
341 winContext->finished = true;
342 winContext->overlapped = NULL;
343 delete overlapped;
344
345 LOG_warn << "Async read failed at startup: " << e;
346 if (winContext->userCallback)
347 {
348 winContext->userCallback(winContext->userData);
349 }
350 }
351 #endif
352 }
353
asyncsyswrite(AsyncIOContext * context)354 void WinFileAccess::asyncsyswrite(AsyncIOContext *context)
355 {
356 #ifndef WINDOWS_PHONE
357 if (!context)
358 {
359 return;
360 }
361
362 WinAsyncIOContext *winContext = dynamic_cast<WinAsyncIOContext*>(context);
363 if (!winContext)
364 {
365 context->failed = true;
366 context->retry = false;
367 context->finished = true;
368 if (context->userCallback)
369 {
370 context->userCallback(context->userData);
371 }
372 return;
373 }
374
375 OVERLAPPED *overlapped = new OVERLAPPED;
376 memset(overlapped, 0, sizeof (OVERLAPPED));
377 overlapped->Offset = winContext->pos & 0xFFFFFFFF;
378 overlapped->OffsetHigh = (winContext->pos >> 32) & 0xFFFFFFFF;
379 overlapped->hEvent = winContext;
380 winContext->overlapped = overlapped;
381
382 if (!WriteFileEx(hFile, (LPVOID)winContext->buffer, (DWORD)winContext->len,
383 overlapped, asyncopfinished))
384 {
385 DWORD e = GetLastError();
386 winContext->retry = WinFileSystemAccess::istransient(e);
387 winContext->failed = true;
388 winContext->finished = true;
389 winContext->overlapped = NULL;
390 delete overlapped;
391
392 LOG_warn << "Async write failed at startup: " << e;
393 if (winContext->userCallback)
394 {
395 winContext->userCallback(winContext->userData);
396 }
397 }
398 #endif
399 }
400
401 // update local name
updatelocalname(LocalPath & name)402 void WinFileAccess::updatelocalname(LocalPath& name)
403 {
404 if (!nonblocking_localname.empty())
405 {
406 nonblocking_localname = name;
407 WinFileSystemAccess::sanitizedriveletter(nonblocking_localname);
408 nonblocking_localname.editStringDirect()->append("", 1);
409 }
410 }
411
412 // true if attribute set should not be considered for syncing
413 // (SYSTEM files are only synced if they are not HIDDEN)
skipattributes(DWORD dwAttributes)414 bool WinFileAccess::skipattributes(DWORD dwAttributes)
415 {
416 return (dwAttributes & (FILE_ATTRIBUTE_REPARSE_POINT
417 | FILE_ATTRIBUTE_OFFLINE))
418 || (dwAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN))
419 == (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
420 }
421
422 // emulates Linux open-directory-as-file semantics
423 // FIXME #1: How to open files and directories with a single atomic
424 // CreateFile() operation without first looking at the attributes?
425 // FIXME #2: How to convert a CreateFile()-opened directory directly to a hFind
426 // without doing a FindFirstFile()?
fopen(LocalPath & name,bool read,bool write,DirAccess * iteratingDir,bool ignoreAttributes)427 bool WinFileAccess::fopen(LocalPath& name, bool read, bool write, DirAccess* iteratingDir, bool ignoreAttributes)
428 {
429 return fopen_impl(name, read, write, false, iteratingDir, ignoreAttributes);
430 }
431
fopen_impl(LocalPath & namePath,bool read,bool write,bool async,DirAccess * iteratingDir,bool ignoreAttributes)432 bool WinFileAccess::fopen_impl(LocalPath& namePath, bool read, bool write, bool async, DirAccess* iteratingDir, bool ignoreAttributes)
433 {
434 WIN32_FIND_DATA fad = { 0 };
435 assert(hFile == INVALID_HANDLE_VALUE);
436
437 #ifdef WINDOWS_PHONE
438 FILE_ID_INFO bhfi = { 0 };
439 #else
440 BY_HANDLE_FILE_INFORMATION bhfi = { 0 };
441 #endif
442
443 bool skipcasecheck = false;
444 int added = WinFileSystemAccess::sanitizedriveletter(namePath);
445
446 string* name = namePath.editStringDirect();
447 name->append("", 1);
448
449 if (write)
450 {
451 type = FILENODE;
452 }
453 else
454 {
455 // fill in the `fad` file attribute data in the most efficient way available for its case
456 if (iteratingDir)
457 {
458 fad = static_cast<WinDirAccess*>(iteratingDir)->currentItemAttributes;
459 }
460 else
461 {
462 HANDLE h = name->size() > sizeof(wchar_t)
463 ? FindFirstFileExW((LPCWSTR)name->data(), FindExInfoStandard, &fad,
464 FindExSearchNameMatch, NULL, 0)
465 : INVALID_HANDLE_VALUE;
466
467 if (h != INVALID_HANDLE_VALUE)
468 {
469 // success so `fad` is set
470 FindClose(h);
471 }
472 else
473 {
474 WIN32_FILE_ATTRIBUTE_DATA fatd;
475 if (!GetFileAttributesExW((LPCWSTR)name->data(), GetFileExInfoStandard, (LPVOID)&fatd))
476 {
477 DWORD e = GetLastError();
478 // this is an expected case so no need to log. the FindFirstFileEx did not find the file,
479 // GetFileAttributesEx is only expected to find it if it's a network share point
480 // LOG_debug << "Unable to get the attributes of the file. Error code: " << e;
481 retry = WinFileSystemAccess::istransient(e);
482 name->resize(name->size() - added - 1);
483 return false;
484 }
485 else
486 {
487 LOG_debug << "Possible root of network share";
488 skipcasecheck = true;
489 fad.dwFileAttributes = fatd.dwFileAttributes;
490 fad.ftCreationTime = fatd.ftCreationTime;
491 fad.ftLastAccessTime = fatd.ftLastAccessTime;
492 fad.ftLastWriteTime = fatd.ftLastWriteTime;
493 fad.nFileSizeHigh = fatd.nFileSizeHigh;
494 fad.nFileSizeLow = fatd.nFileSizeLow;
495 }
496 }
497 }
498
499 if (!skipcasecheck)
500 {
501 const char *filename = name->data() + name->size() - 1;
502 int filenamesize = 0;
503 bool separatorfound = false;
504 do {
505 filename -= sizeof(wchar_t);
506 filenamesize += sizeof(wchar_t);
507 separatorfound = !memcmp(L"\\", filename, sizeof(wchar_t)) || !memcmp(L"/", filename, sizeof(wchar_t)) || !memcmp(L":", filename, sizeof(wchar_t));
508 } while (filename > name->data() && !separatorfound);
509
510 if (filenamesize > sizeof(wchar_t) || !separatorfound)
511 {
512 if (separatorfound)
513 {
514 filename += sizeof(wchar_t);
515 }
516 else
517 {
518 filenamesize += sizeof(wchar_t);
519 }
520
521 if (memcmp(filename, fad.cFileName, filenamesize < sizeof(fad.cFileName) ? filenamesize : sizeof(fad.cFileName))
522 && (filenamesize > sizeof(fad.cAlternateFileName) || memcmp(filename, fad.cAlternateFileName, filenamesize))
523 && !((filenamesize == 4 && !memcmp(filename, L".", 4))
524 || (filenamesize == 6 && !memcmp(filename, L"..", 6))))
525 {
526 LOG_warn << "fopen failed due to invalid case";
527 retry = false;
528 name->resize(name->size() - added - 1);
529 return false;
530 }
531 }
532 }
533
534 // ignore symlinks - they would otherwise be treated as moves
535 // also, ignore some other obscure filesystem object categories
536 if (!ignoreAttributes && skipattributes(fad.dwFileAttributes))
537 {
538 name->resize(name->size() - 1);
539 if (SimpleLogger::logCurrentLevel >= logDebug)
540 {
541 string excluded;
542 excluded.resize((name->size() + 1) * 4 / sizeof(wchar_t));
543 excluded.resize(WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)name->data(),
544 int(name->size() / sizeof(wchar_t)),
545 (char*)excluded.data(),
546 int(excluded.size() + 1),
547 NULL, NULL));
548 LOG_debug << "Excluded: " << excluded << " Attributes: " << fad.dwFileAttributes;
549 }
550 retry = false;
551 return false;
552 }
553
554 type = (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FOLDERNODE : FILENODE;
555 }
556
557 // (race condition between GetFileAttributesEx()/FindFirstFile() possible -
558 // fixable with the current Win32 API?)
559 #ifdef WINDOWS_PHONE
560 CREATEFILE2_EXTENDED_PARAMETERS ex = { 0 };
561 ex.dwSize = sizeof(ex);
562
563 if (type == FOLDERNODE)
564 {
565 ex.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS;
566 }
567 else if (async)
568 {
569 ex.dwFileFlags = FILE_FLAG_OVERLAPPED;
570 }
571
572 hFile = CreateFile2((LPCWSTR)name->data(),
573 read ? GENERIC_READ : (write ? GENERIC_WRITE : 0),
574 FILE_SHARE_WRITE | FILE_SHARE_READ,
575 !write ? OPEN_EXISTING : OPEN_ALWAYS,
576 &ex);
577 #else
578 hFile = CreateFileW((LPCWSTR)name->data(),
579 read ? GENERIC_READ : (write ? GENERIC_WRITE : 0),
580 FILE_SHARE_WRITE | FILE_SHARE_READ,
581 NULL,
582 !write ? OPEN_EXISTING : OPEN_ALWAYS,
583 (type == FOLDERNODE) ? FILE_FLAG_BACKUP_SEMANTICS
584 : (async ? FILE_FLAG_OVERLAPPED : 0),
585 NULL);
586 #endif
587
588 name->resize(name->size() - added - 1);
589
590 // FIXME: verify that keeping the directory opened quashes the possibility
591 // of a race condition between CreateFile() and FindFirstFile()
592
593 if (hFile == INVALID_HANDLE_VALUE)
594 {
595 DWORD e = GetLastError();
596 LOG_debug << "Unable to open file. Error code: " << e;
597 retry = WinFileSystemAccess::istransient(e);
598 return false;
599 }
600
601 mtime = FileTime_to_POSIX(&fad.ftLastWriteTime);
602
603 #ifdef WINDOWS_PHONE
604 if (!write && (fsidvalid = !!GetFileInformationByHandleEx(hFile, FileIdInfo, &bhfi, sizeof(bhfi))))
605 {
606 fsid = *(handle*)&bhfi.FileId;
607 }
608 #else
609 if (!write && (fsidvalid = !!GetFileInformationByHandle(hFile, &bhfi)))
610 {
611 fsid = ((handle)bhfi.nFileIndexHigh << 32) | (handle)bhfi.nFileIndexLow;
612 }
613 #endif
614
615 if (type == FOLDERNODE)
616 {
617 ScopedLengthRestore undoStar(namePath);
618 namePath.appendWithSeparator(LocalPath::fromLocalname(std::string((const char*)(const wchar_t*)L"*", 2)), true, gWindowsSeparator);
619 string* searchName = namePath.editStringDirect();
620 searchName->append("", 1);
621
622
623 #ifdef WINDOWS_PHONE
624 hFind = FindFirstFileExW((LPCWSTR)searchName->data(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, 0);
625 #else
626 hFind = FindFirstFileW((LPCWSTR)searchName->data(), &ffd);
627 #endif
628
629 if (hFind == INVALID_HANDLE_VALUE)
630 {
631 DWORD e = GetLastError();
632 LOG_debug << "Unable to open folder. Error code: " << e;
633 retry = WinFileSystemAccess::istransient(e);
634 return false;
635 }
636
637 CloseHandle(hFile);
638 hFile = INVALID_HANDLE_VALUE;
639 retry = false;
640 return true;
641 }
642
643 if (!write)
644 {
645 size = ((m_off_t)fad.nFileSizeHigh << 32) + (m_off_t)fad.nFileSizeLow;
646 if (!size)
647 {
648 LOG_debug << "Zero-byte file. mtime: " << mtime << " ctime: " << FileTime_to_POSIX(&fad.ftCreationTime)
649 << " attrs: " << fad.dwFileAttributes << " access: " << FileTime_to_POSIX(&fad.ftLastAccessTime);
650 }
651 }
652
653 return true;
654 }
655
WinFileSystemAccess()656 WinFileSystemAccess::WinFileSystemAccess()
657 {
658 notifyerr = false;
659 notifyfailed = false;
660
661 localseparator.assign((const char*)(const wchar_t*)L"\\", sizeof(wchar_t));
662 }
663
~WinFileSystemAccess()664 WinFileSystemAccess::~WinFileSystemAccess()
665 {
666 assert(!dirnotifys.size());
667 }
668
669 // append \ to bare Windows drive letter paths
sanitizedriveletter(LocalPath & localpath)670 int WinFileSystemAccess::sanitizedriveletter(LocalPath& localpath)
671 {
672 if (localpath.editStringDirect()->size() > sizeof(wchar_t) && !memcmp(localpath.editStringDirect()->data() + localpath.editStringDirect()->size() - sizeof(wchar_t), (const char*)(const wchar_t*)L":", sizeof(wchar_t)))
673 {
674 localpath.editStringDirect()->append((const char*)(const wchar_t*)L"\\", sizeof(wchar_t));
675 return sizeof(wchar_t);
676 }
677
678 return 0;
679 }
680
istransient(DWORD e)681 bool WinFileSystemAccess::istransient(DWORD e)
682 {
683 return e == ERROR_ACCESS_DENIED
684 || e == ERROR_TOO_MANY_OPEN_FILES
685 || e == ERROR_NOT_ENOUGH_MEMORY
686 || e == ERROR_OUTOFMEMORY
687 || e == ERROR_WRITE_PROTECT
688 || e == ERROR_LOCK_VIOLATION
689 || e == ERROR_SHARING_VIOLATION;
690 }
691
istransientorexists(DWORD e)692 bool WinFileSystemAccess::istransientorexists(DWORD e)
693 {
694 target_exists = e == ERROR_FILE_EXISTS || e == ERROR_ALREADY_EXISTS;
695
696 return istransient(e);
697 }
698
addevents(Waiter * w,int)699 void WinFileSystemAccess::addevents(Waiter* w, int)
700 {
701 }
702
703 // generate unique local filename in the same fs as relatedpath
tmpnamelocal(LocalPath & localname) const704 void WinFileSystemAccess::tmpnamelocal(LocalPath& localname) const
705 {
706 static unsigned tmpindex;
707 char buf[128];
708
709 sprintf(buf, ".getxfer.%lu.%u.mega", GetCurrentProcessId(), tmpindex++);
710 localname = LocalPath::fromName(buf, *this, FS_UNKNOWN);
711 }
712
713 // convert UTF-8 to Windows Unicode
path2local(const string * path,string * local) const714 void WinFileSystemAccess::path2local(const string* path, string* local) const
715 {
716 // make space for the worst case
717 local->resize((path->size() + 1) * sizeof(wchar_t));
718
719 int len = MultiByteToWideChar(CP_UTF8, 0,
720 path->c_str(),
721 -1,
722 (wchar_t*)local->data(),
723 int(local->size() / sizeof(wchar_t) + 1));
724 if (len)
725 {
726 // resize to actual result
727 local->resize(sizeof(wchar_t) * (len - 1));
728 }
729 else
730 {
731 local->clear();
732 }
733 }
734
735 // convert Windows Unicode to UTF-8
local2path(const string * local,string * path) const736 void WinFileSystemAccess::local2path(const string* local, string* path) const
737 {
738 path->resize((local->size() + 1) * 4 / sizeof(wchar_t));
739
740 path->resize(WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)local->data(),
741 int(local->size() / sizeof(wchar_t)),
742 (char*)path->data(),
743 int(path->size() + 1),
744 NULL, NULL));
745 normalize(path);
746 }
747
748 // write short name of the last path component to sname
getsname(LocalPath & namePath,LocalPath & snamePath) const749 bool WinFileSystemAccess::getsname(LocalPath& namePath, LocalPath& snamePath) const
750 {
751 #ifdef WINDOWS_PHONE
752 return false;
753 #else
754 int r, rr;
755
756 string* name = namePath.editStringDirect();
757 string* sname = snamePath.editStringDirect();
758
759 name->append("", 1);
760
761 r = int(name->size() / sizeof(wchar_t) + 1);
762
763 sname->resize(r * sizeof(wchar_t));
764 rr = GetShortPathNameW((LPCWSTR)name->data(), (LPWSTR)sname->data(), r);
765
766 sname->resize(rr * sizeof(wchar_t));
767
768 if (rr >= r)
769 {
770 rr = GetShortPathNameW((LPCWSTR)name->data(), (LPWSTR)sname->data(), rr);
771 sname->resize(rr * sizeof(wchar_t));
772 }
773
774 name->resize(name->size() - 1);
775
776 if (!rr)
777 {
778 sname->clear();
779 return false;
780 }
781
782 // we are only interested in the path's last component
783 wchar_t* ptr;
784
785 if ((ptr = wcsrchr((wchar_t*)sname->data(), '\\')) || (ptr = wcsrchr((wchar_t*)sname->data(), ':')))
786 {
787 sname->erase(0, (char*)ptr - sname->data() + sizeof(wchar_t));
788 }
789
790 return sname->size();
791 #endif
792 }
793
794 // FIXME: if a folder rename fails because the target exists, do a top-down
795 // recursive copy/delete
renamelocal(LocalPath & oldnamePath,LocalPath & newnamePath,bool replace)796 bool WinFileSystemAccess::renamelocal(LocalPath& oldnamePath, LocalPath& newnamePath, bool replace)
797 {
798 string* oldname = oldnamePath.editStringDirect();
799 string* newname = newnamePath.editStringDirect();
800
801 oldname->append("", 1);
802 newname->append("", 1);
803 bool r = !!MoveFileExW((LPCWSTR)oldname->data(), (LPCWSTR)newname->data(), replace ? MOVEFILE_REPLACE_EXISTING : 0);
804 newname->resize(newname->size() - 1);
805 oldname->resize(oldname->size() - 1);
806
807 if (!r)
808 {
809 DWORD e = GetLastError();
810 if (SimpleLogger::logCurrentLevel >= logWarning && !skip_errorreport)
811 {
812 string utf8oldname;
813 client->fsaccess->local2path(oldname, &utf8oldname);
814
815 string utf8newname;
816 client->fsaccess->local2path(newname, &utf8newname);
817 LOG_warn << "Unable to move file: " << utf8oldname.c_str() << " to " << utf8newname.c_str() << ". Error code: " << e;
818 }
819 transient_error = istransientorexists(e);
820 }
821
822 return r;
823 }
824
copylocal(LocalPath & oldnamePath,LocalPath & newnamePath,m_time_t)825 bool WinFileSystemAccess::copylocal(LocalPath& oldnamePath, LocalPath& newnamePath, m_time_t)
826 {
827 string* oldname = oldnamePath.editStringDirect();
828 string* newname = newnamePath.editStringDirect();
829
830 oldname->append("", 1);
831 newname->append("", 1);
832
833 #ifdef WINDOWS_PHONE
834 bool r = SUCCEEDED(CopyFile2((LPCWSTR)oldname->data(), (LPCWSTR)newname->data(), NULL));
835 #else
836 bool r = !!CopyFileW((LPCWSTR)oldname->data(), (LPCWSTR)newname->data(), FALSE);
837 #endif
838
839 newname->resize(newname->size() - 1);
840 oldname->resize(oldname->size() - 1);
841
842 if (!r)
843 {
844 DWORD e = GetLastError();
845 LOG_debug << "Unable to copy file. Error code: " << e;
846 transient_error = istransientorexists(e);
847 }
848
849 return r;
850 }
851
rmdirlocal(LocalPath & namePath)852 bool WinFileSystemAccess::rmdirlocal(LocalPath& namePath)
853 {
854 string* name = namePath.editStringDirect();
855
856 name->append("", 1);
857 bool r = !!RemoveDirectoryW((LPCWSTR)name->data());
858 name->resize(name->size() - 1);
859
860 if (!r)
861 {
862 DWORD e = GetLastError();
863 LOG_debug << "Unable to delete folder. Error code: " << e;
864 transient_error = istransient(e);
865 }
866
867 return r;
868 }
869
unlinklocal(LocalPath & namePath)870 bool WinFileSystemAccess::unlinklocal(LocalPath& namePath)
871 {
872 string* name = namePath.editStringDirect();
873
874 name->append("", 1);
875 bool r = !!DeleteFileW((LPCWSTR)name->data());
876 name->resize(name->size() - 1);
877
878 if (!r)
879 {
880 DWORD e = GetLastError();
881 LOG_debug << "Unable to delete file. Error code: " << e;
882 transient_error = istransient(e);
883 }
884
885 return r;
886 }
887
888 // delete all files and folders contained in the specified folder
889 // (does not recurse into mounted devices)
emptydirlocal(LocalPath & namePath,dev_t basedev)890 void WinFileSystemAccess::emptydirlocal(LocalPath& namePath, dev_t basedev)
891 {
892 HANDLE hDirectory, hFind;
893 dev_t currentdev;
894
895 int added = WinFileSystemAccess::sanitizedriveletter(namePath);
896
897 string* name = namePath.editStringDirect();
898 name->append("", 1);
899
900 WIN32_FILE_ATTRIBUTE_DATA fad;
901 if (!GetFileAttributesExW((LPCWSTR)name->data(), GetFileExInfoStandard, (LPVOID)&fad)
902 || !(fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
903 || fad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
904 {
905 // discard files and resparse points (links, etc.)
906 name->resize(name->size() - added - 1);
907 return;
908 }
909
910 #ifdef WINDOWS_PHONE
911 CREATEFILE2_EXTENDED_PARAMETERS ex = { 0 };
912 ex.dwSize = sizeof(ex);
913 ex.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS;
914 hDirectory = CreateFile2((LPCWSTR)name->data(), GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ,
915 OPEN_EXISTING, &ex);
916 #else
917 hDirectory = CreateFileW((LPCWSTR)name->data(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
918 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
919 #endif
920 name->resize(name->size() - added - 1);
921 if (hDirectory == INVALID_HANDLE_VALUE)
922 {
923 // discard not accessible folders
924 return;
925 }
926
927 #ifdef WINDOWS_PHONE
928 FILE_ID_INFO fi = { 0 };
929 if(!GetFileInformationByHandleEx(hDirectory, FileIdInfo, &fi, sizeof(fi)))
930 #else
931 BY_HANDLE_FILE_INFORMATION fi;
932 if (!GetFileInformationByHandle(hDirectory, &fi))
933 #endif
934 {
935 currentdev = 0;
936 }
937 else
938 {
939 #ifdef WINDOWS_PHONE
940 currentdev = fi.VolumeSerialNumber + 1;
941 #else
942 currentdev = fi.dwVolumeSerialNumber + 1;
943 #endif
944 }
945 CloseHandle(hDirectory);
946 if (basedev && currentdev != basedev)
947 {
948 // discard folders on different devices
949 return;
950 }
951
952 bool removed;
953 for (;;)
954 {
955 // iterate over children and delete
956 removed = false;
957 name->append((const char*)(const wchar_t*)L"\\*", 5);
958 WIN32_FIND_DATAW ffd;
959 #ifdef WINDOWS_PHONE
960 hFind = FindFirstFileExW((LPCWSTR)name->data(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, 0);
961 #else
962 hFind = FindFirstFileW((LPCWSTR)name->data(), &ffd);
963 #endif
964 name->resize(name->size() - 5);
965 if (hFind == INVALID_HANDLE_VALUE)
966 {
967 break;
968 }
969
970 bool morefiles = true;
971 while (morefiles)
972 {
973 if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
974 && (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
975 || *ffd.cFileName != '.'
976 || (ffd.cFileName[1] && ((ffd.cFileName[1] != '.')
977 || ffd.cFileName[2]))))
978 {
979 auto childname = LocalPath::fromLocalname(*name);
980 childname.appendWithSeparator(LocalPath::fromLocalname(std::string((char*)ffd.cFileName, sizeof(wchar_t) * wcslen(ffd.cFileName))), true, gWindowsSeparator);
981 if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
982 {
983 emptydirlocal(childname, currentdev);
984 childname.editStringDirect()->append("", 1);
985 removed |= !!RemoveDirectoryW((LPCWSTR)childname.editStringDirect()->data());
986 }
987 else
988 {
989 childname.editStringDirect()->append("", 1);
990 removed |= !!DeleteFileW((LPCWSTR)childname.editStringDirect()->data());
991 }
992 }
993 morefiles = FindNextFileW(hFind, &ffd);
994 }
995
996 FindClose(hFind);
997 if (!removed)
998 {
999 break;
1000 }
1001 }
1002 }
1003
mkdirlocal(LocalPath & namePath,bool hidden)1004 bool WinFileSystemAccess::mkdirlocal(LocalPath& namePath, bool hidden)
1005 {
1006 string* name = namePath.editStringDirect();
1007
1008 name->append("", 1);
1009 bool r = !!CreateDirectoryW((LPCWSTR)name->data(), NULL);
1010
1011 if (!r)
1012 {
1013 DWORD e = GetLastError();
1014 LOG_debug << "Unable to create folder. Error code: " << e;
1015 transient_error = istransientorexists(e);
1016 }
1017 else if (hidden)
1018 {
1019 #ifdef WINDOWS_PHONE
1020 WIN32_FILE_ATTRIBUTE_DATA a = { 0 };
1021 BOOL res = GetFileAttributesExW((LPCWSTR)name->data(), GetFileExInfoStandard, &a);
1022
1023 if (res)
1024 {
1025 SetFileAttributesW((LPCWSTR)name->data(), a.dwFileAttributes | FILE_ATTRIBUTE_HIDDEN);
1026 }
1027 #else
1028 DWORD a = GetFileAttributesW((LPCWSTR)name->data());
1029
1030 if (a != INVALID_FILE_ATTRIBUTES)
1031 {
1032 SetFileAttributesW((LPCWSTR)name->data(), a | FILE_ATTRIBUTE_HIDDEN);
1033 }
1034 #endif
1035 }
1036
1037 name->resize(name->size() - 1);
1038
1039 return r;
1040 }
1041
setmtimelocal(LocalPath & namePath,m_time_t mtime)1042 bool WinFileSystemAccess::setmtimelocal(LocalPath& namePath, m_time_t mtime)
1043 {
1044 #ifdef WINDOWS_PHONE
1045 return false;
1046 #else
1047 FILETIME lwt;
1048 LONGLONG ll;
1049 HANDLE hFile;
1050
1051 string* name = namePath.editStringDirect();
1052 name->append("", 1);
1053 hFile = CreateFileW((LPCWSTR)name->data(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
1054 name->resize(name->size() - 1);
1055
1056 if (hFile == INVALID_HANDLE_VALUE)
1057 {
1058 DWORD e = GetLastError();
1059 transient_error = istransient(e);
1060 LOG_warn << "Error opening file to change mtime: " << e;
1061 return false;
1062 }
1063
1064 ll = (mtime + 11644473600) * 10000000;
1065
1066 lwt.dwLowDateTime = (DWORD)ll;
1067 lwt.dwHighDateTime = ll >> 32;
1068
1069 int r = !!SetFileTime(hFile, NULL, NULL, &lwt);
1070 if (!r)
1071 {
1072 DWORD e = GetLastError();
1073 transient_error = istransient(e);
1074 LOG_warn << "Error changing mtime: " << e;
1075 }
1076
1077 CloseHandle(hFile);
1078
1079 return r;
1080 #endif
1081 }
1082
chdirlocal(LocalPath & namePath) const1083 bool WinFileSystemAccess::chdirlocal(LocalPath& namePath) const
1084 {
1085 #ifdef WINDOWS_PHONE
1086 return false;
1087 #else
1088 string* name = namePath.editStringDirect();
1089 name->append("", 1);
1090 int r = SetCurrentDirectoryW((LPCWSTR)name->data());
1091 name->resize(name->size() - 1);
1092
1093 return r;
1094 #endif
1095 }
1096
lastpartlocal(const string * name) const1097 size_t WinFileSystemAccess::lastpartlocal(const string* name) const
1098 {
1099 for (size_t i = name->size() / sizeof(wchar_t); i--;)
1100 {
1101 if (((wchar_t*)name->data())[i] == '\\'
1102 || ((wchar_t*)name->data())[i] == '/'
1103 || ((wchar_t*)name->data())[i] == ':')
1104 {
1105 return (i + 1) * sizeof(wchar_t);
1106 }
1107 }
1108
1109 return 0;
1110 }
1111
1112 // return lowercased ASCII file extension, including the . separator
getextension(const LocalPath & filenamePath,char * extension,size_t size) const1113 bool WinFileSystemAccess::getextension(const LocalPath& filenamePath, char* extension, size_t size) const
1114 {
1115 const string* filename = filenamePath.editStringDirect();
1116
1117 const wchar_t* ptr = (const wchar_t*)(filename->data() + filename->size()
1118 - (filename->size() & 1)); // if the string has had an extra null char added for surety, get back on wchar_t boundary.
1119
1120 char c;
1121 size_t i, j;
1122
1123 size--;
1124
1125 if (size * sizeof(wchar_t) > filename->size())
1126 {
1127 size = int(filename->size() / sizeof(wchar_t));
1128 }
1129
1130 for (i = 0; i < size; i++)
1131 {
1132 if (*--ptr == '.')
1133 {
1134 for (j = 0; j <= i; j++)
1135 {
1136 if (*ptr < '.' || *ptr > 'z') return false;
1137
1138 c = (char)*(ptr++);
1139
1140 // tolower()
1141 if (c >= 'A' && c <= 'Z') c |= ' ';
1142
1143 extension[j] = c;
1144 }
1145
1146 extension[j] = 0;
1147
1148 return true;
1149 }
1150 }
1151
1152 return false;
1153 }
1154
expanselocalpath(LocalPath & pathArg,LocalPath & absolutepathArg)1155 bool WinFileSystemAccess::expanselocalpath(LocalPath& pathArg, LocalPath& absolutepathArg)
1156 {
1157 string* path = pathArg.editStringDirect();
1158 string* absolutepath = absolutepathArg.editStringDirect();
1159
1160 string localpath = *path;
1161 localpath.append("", 1);
1162
1163 #ifdef WINDOWS_PHONE
1164 wchar_t full[_MAX_PATH];
1165 if (_wfullpath(full, (wchar_t *)localpath.data(), _MAX_PATH))
1166 {
1167 absolutepath->assign((char *)full, wcslen(full) * sizeof(wchar_t));
1168 return true;
1169 }
1170 *absolutepath = *path;
1171 return false;
1172 #else
1173
1174 int len = GetFullPathNameW((LPCWSTR)localpath.data(), 0, NULL, NULL);
1175 if (len <= 0)
1176 {
1177 *absolutepath = *path;
1178 return false;
1179 }
1180
1181 absolutepath->resize(len * sizeof(wchar_t));
1182 int newlen = GetFullPathNameW((LPCWSTR)localpath.data(), len, (LPWSTR)absolutepath->data(), NULL);
1183 if (newlen <= 0 || newlen >= len)
1184 {
1185 *absolutepath = *path;
1186 return false;
1187 }
1188
1189 if (memcmp(absolutepath->data(), L"\\\\?\\", 8))
1190 {
1191 if (!memcmp(absolutepath->data(), L"\\\\", 4)) //network location
1192 {
1193 absolutepath->insert(0, (const char *)(const wchar_t*)L"\\\\?\\UNC\\", 16);
1194 }
1195 else
1196 {
1197 absolutepath->insert(0, (const char *)(const wchar_t*)L"\\\\?\\", 8);
1198 }
1199 }
1200 absolutepath->resize(absolutepath->size() - 2);
1201 return true;
1202 #endif
1203 }
1204
osversion(string * u,bool includeArchExtraInfo) const1205 void WinFileSystemAccess::osversion(string* u, bool includeArchExtraInfo) const
1206 {
1207 char buf[128];
1208
1209 #ifdef WINDOWS_PHONE
1210 sprintf(buf, "Windows Phone");
1211 #else
1212 typedef LONG MEGANTSTATUS;
1213 typedef struct _MEGAOSVERSIONINFOW {
1214 DWORD dwOSVersionInfoSize;
1215 DWORD dwMajorVersion;
1216 DWORD dwMinorVersion;
1217 DWORD dwBuildNumber;
1218 DWORD dwPlatformId;
1219 WCHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage
1220 } MEGARTL_OSVERSIONINFOW, *PMEGARTL_OSVERSIONINFOW;
1221
1222 typedef MEGANTSTATUS (WINAPI* RtlGetVersionPtr)(PMEGARTL_OSVERSIONINFOW);
1223 MEGARTL_OSVERSIONINFOW version = { 0 };
1224 HMODULE hMod = GetModuleHandleW(L"ntdll.dll");
1225 if (hMod)
1226 {
1227 RtlGetVersionPtr RtlGetVersion = (RtlGetVersionPtr)(void*)GetProcAddress(hMod, "RtlGetVersion");
1228 if (RtlGetVersion)
1229 {
1230 RtlGetVersion(&version);
1231 }
1232 }
1233 snprintf(buf, sizeof(buf), "Windows %d.%d.%d", version.dwMajorVersion, version.dwMinorVersion, version.dwBuildNumber);
1234
1235 if (includeArchExtraInfo)
1236 {
1237 BOOL isWOW = FALSE;
1238 BOOL callSucceeded = IsWow64Process(GetCurrentProcess(), &isWOW);
1239 if (callSucceeded && isWOW)
1240 {
1241 strcat(buf, "/64"); // if the app 32/64 bit matches the OS, then no need to specify the OS separately, so we only need to cover the WOW 32 bit on 64 bit case.
1242 }
1243 }
1244 #endif
1245
1246 u->append(buf);
1247 }
1248
statsid(string * id) const1249 void WinFileSystemAccess::statsid(string *id) const
1250 {
1251 #ifndef WINDOWS_PHONE
1252 LONG hr;
1253 HKEY hKey = NULL;
1254 hr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Cryptography", 0,
1255 KEY_QUERY_VALUE
1256 #ifdef KEY_WOW64_64KEY
1257 | KEY_WOW64_64KEY
1258 #else
1259 | 0x0100
1260 #endif
1261 , &hKey);
1262 if (hr == ERROR_SUCCESS)
1263 {
1264 WCHAR pszData[256];
1265 DWORD cbData = sizeof(pszData);
1266 hr = RegQueryValueExW(hKey, L"MachineGuid", NULL, NULL, (LPBYTE)pszData, &cbData);
1267 if (hr == ERROR_SUCCESS)
1268 {
1269 string localdata;
1270 string utf8data;
1271 localdata.assign((char *)pszData, cbData - sizeof(WCHAR));
1272 local2path(&localdata, &utf8data);
1273 id->append(utf8data);
1274 }
1275 RegCloseKey(hKey);
1276 }
1277 #endif
1278 }
1279
1280 // set DirNotify's root LocalNode
addnotify(LocalNode * l,string *)1281 void WinDirNotify::addnotify(LocalNode* l, string*)
1282 {
1283 #ifdef ENABLE_SYNC
1284 if (!l->parent)
1285 {
1286 localrootnode = l;
1287 }
1288 #endif
1289 }
1290
fsfingerprint() const1291 fsfp_t WinDirNotify::fsfingerprint() const
1292 {
1293 #ifdef WINDOWS_PHONE
1294 FILE_ID_INFO fi = { 0 };
1295 if(!GetFileInformationByHandleEx(hDirectory, FileIdInfo, &fi, sizeof(fi)))
1296 #else
1297 BY_HANDLE_FILE_INFORMATION fi;
1298 if (!GetFileInformationByHandle(hDirectory, &fi))
1299 #endif
1300 {
1301 LOG_err << "Unable to get fsfingerprint. Error code: " << GetLastError();
1302 return 0;
1303 }
1304
1305 #ifdef WINDOWS_PHONE
1306 return fi.VolumeSerialNumber + 1;
1307 #else
1308 return fi.dwVolumeSerialNumber + 1;
1309 #endif
1310 }
1311
fsstableids() const1312 bool WinDirNotify::fsstableids() const
1313 {
1314 #ifdef WINDOWS_PHONE
1315 #error "Not implemented"
1316 #endif
1317 TCHAR volume[MAX_PATH + 1];
1318 if (GetVolumePathNameW((LPCWSTR)localbasepath.editStringDirect()->data(), volume, MAX_PATH + 1))
1319 {
1320 TCHAR fs[MAX_PATH + 1];
1321 if (GetVolumeInformation(volume, NULL, 0, NULL, NULL, NULL, fs, MAX_PATH + 1))
1322 {
1323 LOG_info << "Filesystem type: " << fs;
1324 return _wcsicmp(fs, L"FAT")
1325 && _wcsicmp(fs, L"FAT32")
1326 && _wcsicmp(fs, L"exFAT");
1327 }
1328 }
1329 LOG_err << "Failed to get filesystem type. Error code: " << GetLastError();
1330 return true;
1331 }
1332
completion(DWORD dwErrorCode,DWORD dwBytes,LPOVERLAPPED lpOverlapped)1333 VOID CALLBACK WinDirNotify::completion(DWORD dwErrorCode, DWORD dwBytes, LPOVERLAPPED lpOverlapped)
1334 {
1335 assert( std::this_thread::get_id() == smNotifierThread->get_id());
1336 #ifndef WINDOWS_PHONE
1337 WinDirNotify *dirnotify = (WinDirNotify*)lpOverlapped->hEvent;
1338 if (!dirnotify->mOverlappedExit && dwErrorCode != ERROR_OPERATION_ABORTED)
1339 {
1340 dirnotify->process(dwBytes);
1341 }
1342 else
1343 {
1344 dirnotify->mOverlappedEnabled = false;
1345 }
1346 #endif
1347 }
1348
process(DWORD dwBytes)1349 void WinDirNotify::process(DWORD dwBytes)
1350 {
1351 assert( std::this_thread::get_id() == smNotifierThread->get_id());
1352
1353 #ifndef WINDOWS_PHONE
1354 if (!dwBytes)
1355 {
1356 #ifdef ENABLE_SYNC
1357 int errCount = ++mErrorCount;
1358 LOG_err << "Empty filesystem notification: " << (localrootnode ? localrootnode->name.c_str() : "NULL")
1359 << " errors: " << errCount;
1360 readchanges();
1361 notify(DIREVENTS, localrootnode, LocalPath());
1362 #endif
1363 }
1364 else
1365 {
1366 assert(dwBytes >= offsetof(FILE_NOTIFY_INFORMATION, FileName) + sizeof(wchar_t));
1367
1368 string processbuf;
1369 if (dwBytes <= 4096)
1370 {
1371 processbuf = notifybuf; // even under high load, usually the buffer is under 4k.
1372 }
1373 else
1374 {
1375 processbuf.swap(notifybuf); // use existing buffer, a new one will be allocated for receiving
1376 }
1377 char* ptr = (char*)processbuf.data();
1378
1379 readchanges();
1380
1381 // ensure accuracy of the notification timestamps
1382 WAIT_CLASS::bumpds();
1383
1384 // we trust the OS to always return conformant data
1385 for (;;)
1386 {
1387 FILE_NOTIFY_INFORMATION* fni = (FILE_NOTIFY_INFORMATION*)ptr;
1388
1389 // skip the local debris folder
1390 // also, we skip the old name in case of renames
1391 if (fni->Action != FILE_ACTION_RENAMED_OLD_NAME
1392 && (fni->FileNameLength < ignore.editStringDirect()->size()
1393 || memcmp((char*)fni->FileName, ignore.editStringDirect()->data(), ignore.editStringDirect()->size())
1394 || (fni->FileNameLength > ignore.editStringDirect()->size()
1395 && memcmp((char*)fni->FileName + ignore.editStringDirect()->size(), (const char*)(const wchar_t*)L"\\", sizeof(wchar_t)))))
1396 {
1397 if (SimpleLogger::logCurrentLevel >= logDebug)
1398 {
1399 #ifdef ENABLE_SYNC
1400 // Outputting this logging on the notification thread slows it down considerably, risking missing notifications.
1401 // Let's skip it and log the ones received on the notify queue
1402
1403 //string local, path;
1404 //local.assign((char*)fni->FileName, fni->FileNameLength);
1405 //path.resize((local.size() + 1) * 4 / sizeof(wchar_t));
1406 //path.resize(WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)local.data(),
1407 // int(local.size() / sizeof(wchar_t)),
1408 // (char*)path.data(),
1409 // int(path.size() + 1),
1410 // NULL, NULL));
1411
1412 //LOG_debug << "Filesystem notification. Root: " << (localrootnode ? localrootnode->name.c_str() : "NULL") << " Path: " << path;
1413 #endif
1414 }
1415 #ifdef ENABLE_SYNC
1416 notify(DIREVENTS, localrootnode, LocalPath::fromLocalname(std::string((char*)fni->FileName, fni->FileNameLength)));
1417 #endif
1418 }
1419 else if (SimpleLogger::logCurrentLevel >= logDebug)
1420 {
1421 #ifdef ENABLE_SYNC
1422 // Outputting this logging on the notification thread slows it down considerably, risking missing notifications.
1423 // Let's skip it and log the ones received on the notify queue
1424
1425 //string local, path;
1426 //local.assign((char*)fni->FileName, fni->FileNameLength);
1427 //path.resize((local.size() + 1) * 4 / sizeof(wchar_t));
1428 //path.resize(WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)local.data(),
1429 // int(local.size() / sizeof(wchar_t)),
1430 // (char*)path.data(),
1431 // int(path.size() + 1),
1432 // NULL, NULL));
1433 //LOG_debug << "Skipped filesystem notification. Root: " << (localrootnode ? localrootnode->name.c_str() : "NULL") << " Path: " << path;
1434 #endif
1435 }
1436
1437
1438 if (!fni->NextEntryOffset)
1439 {
1440 break;
1441 }
1442
1443 ptr += fni->NextEntryOffset;
1444 }
1445 }
1446 #endif
1447 clientWaiter->notify();
1448 }
1449
1450 // request change notifications on the subtree under hDirectory
readchanges()1451 void WinDirNotify::readchanges()
1452 {
1453 assert( std::this_thread::get_id() == smNotifierThread->get_id());
1454
1455 #ifndef WINDOWS_PHONE
1456 if (notifybuf.size() != 65534)
1457 {
1458 // Use 65534 for the buffer size becaues (from doco):
1459 // ReadDirectoryChangesW fails with ERROR_INVALID_PARAMETER when the buffer length is greater than 64 KB and the application is
1460 // monitoring a directory over the network. This is due to a packet size limitation with the underlying file sharing protocols.
1461 notifybuf.resize(65534);
1462 }
1463 auto readRet = ReadDirectoryChangesW(hDirectory, (LPVOID)notifybuf.data(),
1464 (DWORD)notifybuf.size(), TRUE,
1465 FILE_NOTIFY_CHANGE_FILE_NAME
1466 | FILE_NOTIFY_CHANGE_DIR_NAME
1467 | FILE_NOTIFY_CHANGE_LAST_WRITE
1468 | FILE_NOTIFY_CHANGE_SIZE
1469 | FILE_NOTIFY_CHANGE_CREATION,
1470 &dwBytes, &overlapped, completion);
1471
1472 if (readRet)
1473 {
1474 setFailed(0, "");
1475 mOverlappedEnabled = true;
1476 }
1477 else
1478 {
1479 mOverlappedEnabled = false;
1480 DWORD e = GetLastError();
1481 LOG_warn << "ReadDirectoryChanges not available. Error code: " << e << " errors: " << mErrorCount.load();
1482 if (e == ERROR_NOTIFY_ENUM_DIR && mErrorCount < 10)
1483 {
1484 // notification buffer overflow
1485 mErrorCount++;
1486 readchanges();
1487 }
1488 else
1489 {
1490 // permanent failure - switch to scanning mode
1491 setFailed(e, "Fatal error returned by ReadDirectoryChangesW");
1492 }
1493 }
1494 #endif
1495 }
1496
1497 std::mutex WinDirNotify::smNotifyMutex;
1498 std::atomic<unsigned> WinDirNotify::smNotifierCount{0};
1499 HANDLE WinDirNotify::smEventHandle = NULL;
1500 std::deque<std::function<void()>> WinDirNotify::smQueue;
1501 std::unique_ptr<std::thread> WinDirNotify::smNotifierThread;
1502
notifierThreadFunction()1503 void WinDirNotify::notifierThreadFunction()
1504 {
1505 LOG_debug << "Filesystem notify thread started";
1506 bool recheck = false;
1507 for (;;)
1508 {
1509 if (!recheck)
1510 {
1511 WaitForSingleObjectEx(smEventHandle, INFINITE, TRUE); // alertable, so filesystem notify callbacks can occur on this thread during this time.
1512 ResetEvent(smEventHandle);
1513 }
1514 recheck = false;
1515
1516 std::function<void()> f;
1517 {
1518 std::unique_lock<std::mutex> g(smNotifyMutex);
1519 if (!smQueue.empty())
1520 {
1521 f = std::move(smQueue.front());
1522 if (!f) break; // nullptr to cause the thread to exit
1523 smQueue.pop_front();
1524 }
1525 }
1526 if (f)
1527 {
1528 f();
1529 recheck = true;
1530 }
1531 }
1532 LOG_debug << "Filesystem notify thread stopped";
1533 }
1534
WinDirNotify(LocalPath & localbasepath,const LocalPath & ignore,WinFileSystemAccess * owner,Waiter * waiter)1535 WinDirNotify::WinDirNotify(LocalPath& localbasepath, const LocalPath& ignore, WinFileSystemAccess* owner, Waiter* waiter) : DirNotify(localbasepath, ignore)
1536 {
1537 fsaccess = owner;
1538 fsaccess->dirnotifys.insert(this);
1539 clientWaiter = waiter;
1540
1541 {
1542 // If this is the first Notifier created, start the thread that queries the OS for notifications.
1543 std::lock_guard<std::mutex> g(smNotifyMutex);
1544 if (++smNotifierCount == 1)
1545 {
1546 smQueue.clear();
1547 smEventHandle = CreateEvent(NULL, FALSE, FALSE, NULL);
1548
1549 // One thread to notify them all
1550 smNotifierThread.reset(new std::thread([](){ notifierThreadFunction(); }));
1551 }
1552 }
1553
1554 #ifndef WINDOWS_PHONE
1555 ZeroMemory(&overlapped, sizeof(overlapped));
1556 overlapped.hEvent = this;
1557 mOverlappedEnabled = false;
1558 mOverlappedExit = false;
1559
1560 ScopedLengthRestore restoreLocalbasePath(localbasepath);
1561 WinFileSystemAccess::sanitizedriveletter(localbasepath);
1562 localbasepath.editStringDirect()->append("", 1);
1563
1564 // ReadDirectoryChangesW: If you opened the file using the short name, you can receive change notifications for the short name. (so make sure it's a long name)
1565 std::wstring longname;
1566 auto r = localbasepath.editStringDirect()->size() / sizeof(wchar_t) + 20;
1567 longname.resize(r);
1568 auto rr = GetLongPathNameW((LPCWSTR)localbasepath.editStringDirect()->data(), (LPWSTR)longname.data(), DWORD(r));
1569
1570 longname.resize(rr);
1571 if (rr >= r)
1572 {
1573 rr = GetLongPathNameW((LPCWSTR)localbasepath.editStringDirect()->data(), (LPWSTR)longname.data(), rr);
1574 longname.resize(rr);
1575 }
1576
1577 if ((hDirectory = CreateFileW((LPCWSTR)longname.data(),
1578 FILE_LIST_DIRECTORY,
1579 FILE_SHARE_READ | FILE_SHARE_WRITE,
1580 NULL,
1581 OPEN_EXISTING,
1582 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
1583 NULL)) != INVALID_HANDLE_VALUE)
1584 {
1585 setFailed(0, "");
1586
1587 {
1588 std::lock_guard<std::mutex> g(smNotifyMutex);
1589 smQueue.push_back([this](){ readchanges(); });
1590 }
1591 SetEvent(smEventHandle);
1592 }
1593 else
1594 {
1595 int err = GetLastError();
1596 setFailed(err, "CreateFileW was unable to open the folder");
1597 LOG_err << "Unable to initialize filesystem notifications. Error: " << err;
1598 }
1599 #endif
1600 }
1601
~WinDirNotify()1602 WinDirNotify::~WinDirNotify()
1603 {
1604 mOverlappedExit = true;
1605
1606 #ifndef WINDOWS_PHONE
1607 if (hDirectory != INVALID_HANDLE_VALUE)
1608 {
1609 if (mOverlappedEnabled)
1610 {
1611 {
1612 std::lock_guard<std::mutex> g(smNotifyMutex);
1613 smQueue.push_back([this](){ CancelIo(hDirectory); });
1614 }
1615 SetEvent(smEventHandle);
1616 while (mOverlappedEnabled)
1617 {
1618 SleepEx(10, true);
1619 }
1620 }
1621
1622 CloseHandle(hDirectory);
1623 }
1624 fsaccess->dirnotifys.erase(this);
1625 #endif
1626
1627 {
1628 if (--smNotifierCount == 0)
1629 {
1630 {
1631 std::lock_guard<std::mutex> g(smNotifyMutex);
1632 smQueue.push_back(nullptr);
1633 }
1634 SetEvent(smEventHandle);
1635 smNotifierThread->join();
1636 smNotifierThread.reset();
1637 CloseHandle(smEventHandle);
1638 smQueue.clear();
1639 }
1640 }
1641
1642 }
1643
newfileaccess(bool followSymLinks)1644 std::unique_ptr<FileAccess> WinFileSystemAccess::newfileaccess(bool followSymLinks)
1645 {
1646 return std::unique_ptr<FileAccess>(new WinFileAccess(waiter));
1647 }
1648
newdiraccess()1649 DirAccess* WinFileSystemAccess::newdiraccess()
1650 {
1651 return new WinDirAccess();
1652 }
1653
newdirnotify(LocalPath & localpath,LocalPath & ignore,Waiter * waiter)1654 DirNotify* WinFileSystemAccess::newdirnotify(LocalPath& localpath, LocalPath& ignore, Waiter* waiter)
1655 {
1656 return new WinDirNotify(localpath, ignore, this, waiter);
1657 }
1658
issyncsupported(LocalPath & localpathArg,bool * isnetwork)1659 bool WinFileSystemAccess::issyncsupported(LocalPath& localpathArg, bool *isnetwork)
1660 {
1661 string* localpath = localpathArg.editStringDirect();
1662
1663 WCHAR VBoxSharedFolderFS[] = L"VBoxSharedFolderFS";
1664 string path, fsname;
1665 bool result = true;
1666
1667 #ifndef WINDOWS_PHONE
1668 localpath->append("", 1);
1669 path.resize(MAX_PATH * sizeof(WCHAR));
1670 fsname.resize(MAX_PATH * sizeof(WCHAR));
1671
1672 if (GetVolumePathNameW((LPCWSTR)localpath->data(), (LPWSTR)path.data(), MAX_PATH)
1673 && GetVolumeInformationW((LPCWSTR)path.data(), NULL, 0, NULL, NULL, NULL, (LPWSTR)fsname.data(), MAX_PATH)
1674 && !memcmp(fsname.data(), VBoxSharedFolderFS, sizeof(VBoxSharedFolderFS)))
1675 {
1676 LOG_warn << "VBoxSharedFolderFS is not supported because it doesn't provide ReadDirectoryChanges() nor unique file identifiers";
1677 result = false;
1678 }
1679
1680 if (GetDriveTypeW((LPCWSTR)path.data()) == DRIVE_REMOTE)
1681 {
1682 LOG_debug << "Network folder detected";
1683 if (isnetwork)
1684 {
1685 *isnetwork = true;
1686 }
1687 }
1688
1689 string utf8fsname;
1690 local2path(&fsname, &utf8fsname);
1691 LOG_debug << "Filesystem type: " << utf8fsname;
1692
1693 localpath->resize(localpath->size() - 1);
1694 #endif
1695
1696 return result;
1697 }
1698
dopen(LocalPath * nameArg,FileAccess * f,bool glob)1699 bool WinDirAccess::dopen(LocalPath* nameArg, FileAccess* f, bool glob)
1700 {
1701 assert(nameArg || f);
1702 assert(!(glob && f));
1703
1704 if (f)
1705 {
1706 if ((hFind = ((WinFileAccess*)f)->hFind) != INVALID_HANDLE_VALUE)
1707 {
1708 ffd = ((WinFileAccess*)f)->ffd;
1709 ((WinFileAccess*)f)->hFind = INVALID_HANDLE_VALUE;
1710 }
1711 }
1712 else
1713 {
1714 string* name = nameArg ? nameArg->editStringDirect() : nullptr;
1715
1716 if (!glob)
1717 {
1718 name->append((const char*)(const wchar_t*)L"\\*", 5);
1719 }
1720 else
1721 {
1722 name->append("", 1);
1723 }
1724
1725 #ifdef WINDOWS_PHONE
1726 hFind = FindFirstFileExW((LPCWSTR)name->data(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, 0);
1727 #else
1728 hFind = FindFirstFileW((LPCWSTR)name->data(), &ffd);
1729 #endif
1730
1731 if (glob)
1732 {
1733 wchar_t* bp = (wchar_t*)name->data();
1734
1735 // store base path for glob() emulation
1736 int p = int(wcslen(bp));
1737
1738 while (p--)
1739 {
1740 if (bp[p] == '/' || bp[p] == '\\')
1741 {
1742 break;
1743 }
1744 }
1745
1746 if (p >= 0)
1747 {
1748 globbase.assign((char*)bp, (p + 1) * sizeof(wchar_t));
1749 }
1750 else
1751 {
1752 globbase.clear();
1753 }
1754 }
1755
1756 name->resize(name->size() - (glob ? 1 : 5));
1757 }
1758
1759 if (!(ffdvalid = hFind != INVALID_HANDLE_VALUE))
1760 {
1761 return false;
1762 }
1763
1764 return true;
1765 }
1766
1767 // FIXME: implement followsymlinks
dnext(LocalPath &,LocalPath & nameArg,bool,nodetype_t * type)1768 bool WinDirAccess::dnext(LocalPath& /*path*/, LocalPath& nameArg, bool /*followsymlinks*/, nodetype_t* type)
1769 {
1770 string* name = nameArg.editStringDirect();
1771
1772 for (;;)
1773 {
1774 if (ffdvalid
1775 && !WinFileAccess::skipattributes(ffd.dwFileAttributes)
1776 && (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1777 || *ffd.cFileName != '.'
1778 || (ffd.cFileName[1] && ((ffd.cFileName[1] != '.') || ffd.cFileName[2]))))
1779 {
1780 name->assign((char*)ffd.cFileName, sizeof(wchar_t) * wcslen(ffd.cFileName));
1781 name->insert(0, globbase);
1782
1783 if (type)
1784 {
1785 *type = (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FOLDERNODE : FILENODE;
1786 }
1787
1788 ffdvalid = false;
1789 currentItemAttributes = ffd;
1790 return true;
1791 }
1792 else
1793 {
1794 if (ffdvalid && SimpleLogger::logCurrentLevel >= logDebug)
1795 {
1796 if (*ffd.cFileName != '.' && (ffd.cFileName[1] && ((ffd.cFileName[1] != '.') || ffd.cFileName[2])))
1797 {
1798 string local, excluded;
1799 local.assign((char*)ffd.cFileName, sizeof(wchar_t) * wcslen(ffd.cFileName));
1800 excluded.resize((local.size() + 1) * 4 / sizeof(wchar_t));
1801 excluded.resize(WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)local.data(),
1802 int(local.size() / sizeof(wchar_t)),
1803 (char*)excluded.data(),
1804 int(excluded.size() + 1),
1805 NULL, NULL));
1806 LOG_debug << "Excluded: " << excluded << " Attributes: " << ffd.dwFileAttributes;
1807 }
1808 }
1809 }
1810
1811 if (!(ffdvalid = FindNextFileW(hFind, &ffd) != 0))
1812 {
1813 return false;
1814 }
1815 }
1816 }
1817
WinDirAccess()1818 WinDirAccess::WinDirAccess()
1819 {
1820 ffdvalid = false;
1821 hFind = INVALID_HANDLE_VALUE;
1822 }
1823
~WinDirAccess()1824 WinDirAccess::~WinDirAccess()
1825 {
1826 if (hFind != INVALID_HANDLE_VALUE)
1827 {
1828 FindClose(hFind);
1829 }
1830 }
1831 } // namespace
1832