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