• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2015 Google Inc. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 #include "tensorflow/core/platform/windows/windows_file_system.h"
17 
18 #include <Shlwapi.h>
19 #include <Windows.h>
20 #include <direct.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <io.h>
24 #undef StrCat
25 #include <stdio.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <time.h>
29 
30 #include "tensorflow/core/platform/env.h"
31 #include "tensorflow/core/platform/error.h"
32 #include "tensorflow/core/platform/file_system_helper.h"
33 #include "tensorflow/core/platform/logging.h"
34 #include "tensorflow/core/platform/strcat.h"
35 #include "tensorflow/core/platform/windows/error_windows.h"
36 #include "tensorflow/core/platform/windows/wide_char.h"
37 #include "tensorflow/core/protobuf/error_codes.pb.h"
38 
39 // TODO(mrry): Prevent this Windows.h #define from leaking out of our headers.
40 #undef DeleteFile
41 
42 namespace tensorflow {
43 
44 namespace {
45 
46 // RAII helpers for HANDLEs
__anon9e9b41040202(HANDLE h) 47 const auto CloseHandleFunc = [](HANDLE h) { ::CloseHandle(h); };
48 typedef std::unique_ptr<void, decltype(CloseHandleFunc)> UniqueCloseHandlePtr;
49 
IOErrorFromWindowsError(const string & context)50 inline Status IOErrorFromWindowsError(const string& context) {
51   auto last_error = ::GetLastError();
52   return IOError(
53       context + string(" : ") + internal::WindowsGetLastErrorMessage(),
54       last_error);
55 }
56 
57 // PLEASE NOTE: hfile is expected to be an async handle
58 // (i.e. opened with FILE_FLAG_OVERLAPPED)
pread(HANDLE hfile,char * src,size_t num_bytes,uint64_t offset)59 SSIZE_T pread(HANDLE hfile, char* src, size_t num_bytes, uint64_t offset) {
60   assert(num_bytes <= std::numeric_limits<DWORD>::max());
61   OVERLAPPED overlapped = {0};
62   ULARGE_INTEGER offset_union;
63   offset_union.QuadPart = offset;
64 
65   overlapped.Offset = offset_union.LowPart;
66   overlapped.OffsetHigh = offset_union.HighPart;
67   overlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
68 
69   if (NULL == overlapped.hEvent) {
70     return -1;
71   }
72 
73   SSIZE_T result = 0;
74 
75   unsigned long bytes_read = 0;
76   DWORD last_error = ERROR_SUCCESS;
77 
78   BOOL read_result = ::ReadFile(hfile, src, static_cast<DWORD>(num_bytes),
79                                 &bytes_read, &overlapped);
80   if (TRUE == read_result) {
81     result = bytes_read;
82   } else if ((FALSE == read_result) &&
83              ((last_error = GetLastError()) != ERROR_IO_PENDING)) {
84     result = (last_error == ERROR_HANDLE_EOF) ? 0 : -1;
85   } else {
86     if (ERROR_IO_PENDING ==
87         last_error) {  // Otherwise bytes_read already has the result.
88       BOOL overlapped_result =
89           ::GetOverlappedResult(hfile, &overlapped, &bytes_read, TRUE);
90       if (FALSE == overlapped_result) {
91         result = (::GetLastError() == ERROR_HANDLE_EOF) ? 0 : -1;
92       } else {
93         result = bytes_read;
94       }
95     }
96   }
97 
98   ::CloseHandle(overlapped.hEvent);
99 
100   return result;
101 }
102 
103 // read() based random-access
104 class WindowsRandomAccessFile : public RandomAccessFile {
105  private:
106   string filename_;
107   HANDLE hfile_;
108 
109  public:
WindowsRandomAccessFile(const string & fname,HANDLE hfile)110   WindowsRandomAccessFile(const string& fname, HANDLE hfile)
111       : filename_(fname), hfile_(hfile) {}
~WindowsRandomAccessFile()112   ~WindowsRandomAccessFile() override {
113     if (hfile_ != NULL && hfile_ != INVALID_HANDLE_VALUE) {
114       ::CloseHandle(hfile_);
115     }
116   }
117 
Name(StringPiece * result) const118   Status Name(StringPiece* result) const override {
119     *result = filename_;
120     return Status::OK();
121   }
122 
Read(uint64 offset,size_t n,StringPiece * result,char * scratch) const123   Status Read(uint64 offset, size_t n, StringPiece* result,
124               char* scratch) const override {
125     Status s;
126     char* dst = scratch;
127     while (n > 0 && s.ok()) {
128       size_t requested_read_length;
129       if (n > std::numeric_limits<DWORD>::max()) {
130         requested_read_length = std::numeric_limits<DWORD>::max();
131       } else {
132         requested_read_length = n;
133       }
134       SSIZE_T r = pread(hfile_, dst, requested_read_length, offset);
135       if (r > 0) {
136         offset += r;
137         dst += r;
138         n -= r;
139       } else if (r == 0) {
140         s = Status(error::OUT_OF_RANGE, "Read fewer bytes than requested");
141       } else if (errno == EINTR || errno == EAGAIN) {
142         // Retry
143       } else {
144         s = IOError(filename_, errno);
145       }
146     }
147     *result = StringPiece(scratch, dst - scratch);
148     return s;
149   }
150 
151 #if defined(TF_CORD_SUPPORT)
Read(uint64 offset,size_t n,absl::Cord * cord) const152   Status Read(uint64 offset, size_t n, absl::Cord* cord) const override {
153     if (n == 0) {
154       return Status::OK();
155     }
156     if (n < 0) {
157       return errors::InvalidArgument(
158           "Attempting to read ", n,
159           " bytes. You cannot read a negative number of bytes.");
160     }
161 
162     char* scratch = new char[n];
163     if (scratch == nullptr) {
164       return errors::ResourceExhausted("Unable to allocate ", n,
165                                        " bytes for file reading.");
166     }
167 
168     StringPiece tmp;
169     Status s = Read(offset, n, &tmp, scratch);
170 
171     absl::Cord tmp_cord = absl::MakeCordFromExternal(
172         absl::string_view(static_cast<char*>(scratch), tmp.size()),
173         [scratch](absl::string_view) { delete[] scratch; });
174     cord->Append(tmp_cord);
175     return s;
176   }
177 #endif
178 };
179 
180 class WindowsWritableFile : public WritableFile {
181  private:
182   string filename_;
183   HANDLE hfile_;
184 
185  public:
WindowsWritableFile(const string & fname,HANDLE hFile)186   WindowsWritableFile(const string& fname, HANDLE hFile)
187       : filename_(fname), hfile_(hFile) {}
188 
~WindowsWritableFile()189   ~WindowsWritableFile() override {
190     if (hfile_ != NULL && hfile_ != INVALID_HANDLE_VALUE) {
191       WindowsWritableFile::Close();
192     }
193   }
194 
Append(StringPiece data)195   Status Append(StringPiece data) override {
196     DWORD bytes_written = 0;
197     DWORD data_size = static_cast<DWORD>(data.size());
198     BOOL write_result =
199         ::WriteFile(hfile_, data.data(), data_size, &bytes_written, NULL);
200     if (FALSE == write_result) {
201       return IOErrorFromWindowsError("Failed to WriteFile: " + filename_);
202     }
203 
204     assert(size_t(bytes_written) == data.size());
205     return Status::OK();
206   }
207 
208 #if defined(TF_CORD_SUPPORT)
209   // \brief Append 'data' to the file.
Append(const absl::Cord & cord)210   Status Append(const absl::Cord& cord) override {
211     for (const auto& chunk : cord.Chunks()) {
212       DWORD bytes_written = 0;
213       DWORD data_size = static_cast<DWORD>(chunk.size());
214       BOOL write_result =
215           ::WriteFile(hfile_, chunk.data(), data_size, &bytes_written, NULL);
216       if (FALSE == write_result) {
217         return IOErrorFromWindowsError("Failed to WriteFile: " + filename_);
218       }
219 
220       assert(size_t(bytes_written) == chunk.size());
221     }
222     return Status::OK();
223   }
224 #endif
225 
Tell(int64 * position)226   Status Tell(int64* position) override {
227     Status result = Flush();
228     if (!result.ok()) {
229       return result;
230     }
231 
232     *position = SetFilePointer(hfile_, 0, NULL, FILE_CURRENT);
233 
234     if (*position == INVALID_SET_FILE_POINTER) {
235       return IOErrorFromWindowsError("Tell(SetFilePointer) failed for: " +
236                                      filename_);
237     }
238 
239     return Status::OK();
240   }
241 
Close()242   Status Close() override {
243     assert(INVALID_HANDLE_VALUE != hfile_);
244 
245     Status result = Flush();
246     if (!result.ok()) {
247       return result;
248     }
249 
250     if (FALSE == ::CloseHandle(hfile_)) {
251       return IOErrorFromWindowsError("CloseHandle failed for: " + filename_);
252     }
253 
254     hfile_ = INVALID_HANDLE_VALUE;
255     return Status::OK();
256   }
257 
Flush()258   Status Flush() override {
259     if (FALSE == ::FlushFileBuffers(hfile_)) {
260       return IOErrorFromWindowsError("FlushFileBuffers failed for: " +
261                                      filename_);
262     }
263     return Status::OK();
264   }
265 
Name(StringPiece * result) const266   Status Name(StringPiece* result) const override {
267     *result = filename_;
268     return Status::OK();
269   }
270 
Sync()271   Status Sync() override { return Flush(); }
272 };
273 
274 class WinReadOnlyMemoryRegion : public ReadOnlyMemoryRegion {
275  private:
276   const std::string filename_;
277   HANDLE hfile_;
278   HANDLE hmap_;
279 
280   const void* const address_;
281   const uint64 length_;
282 
283  public:
WinReadOnlyMemoryRegion(const std::string & filename,HANDLE hfile,HANDLE hmap,const void * address,uint64 length)284   WinReadOnlyMemoryRegion(const std::string& filename, HANDLE hfile,
285                           HANDLE hmap, const void* address, uint64 length)
286       : filename_(filename),
287         hfile_(hfile),
288         hmap_(hmap),
289         address_(address),
290         length_(length) {}
291 
~WinReadOnlyMemoryRegion()292   ~WinReadOnlyMemoryRegion() {
293     BOOL ret = ::UnmapViewOfFile(address_);
294     assert(ret);
295 
296     ret = ::CloseHandle(hmap_);
297     assert(ret);
298 
299     ret = ::CloseHandle(hfile_);
300     assert(ret);
301   }
302 
data()303   const void* data() override { return address_; }
length()304   uint64 length() override { return length_; }
305 };
306 
307 }  // namespace
308 
NewRandomAccessFile(const string & fname,TransactionToken * token,std::unique_ptr<RandomAccessFile> * result)309 Status WindowsFileSystem::NewRandomAccessFile(
310     const string& fname, TransactionToken* token,
311     std::unique_ptr<RandomAccessFile>* result) {
312   string translated_fname = TranslateName(fname);
313   std::wstring ws_translated_fname = Utf8ToWideChar(translated_fname);
314   result->reset();
315 
316   // Open the file for read-only random access
317   // Open in async mode which makes Windows allow more parallelism even
318   // if we need to do sync I/O on top of it.
319   DWORD file_flags = FILE_ATTRIBUTE_READONLY | FILE_FLAG_OVERLAPPED;
320   // Shared access is necessary for tests to pass
321   // almost all tests would work with a possible exception of fault_injection.
322   DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
323 
324   HANDLE hfile =
325       ::CreateFileW(ws_translated_fname.c_str(), GENERIC_READ, share_mode, NULL,
326                     OPEN_EXISTING, file_flags, NULL);
327 
328   if (INVALID_HANDLE_VALUE == hfile) {
329     string context = "NewRandomAccessFile failed to Create/Open: " + fname;
330     return IOErrorFromWindowsError(context);
331   }
332 
333   result->reset(new WindowsRandomAccessFile(translated_fname, hfile));
334   return Status::OK();
335 }
336 
NewWritableFile(const string & fname,TransactionToken * token,std::unique_ptr<WritableFile> * result)337 Status WindowsFileSystem::NewWritableFile(
338     const string& fname, TransactionToken* token,
339     std::unique_ptr<WritableFile>* result) {
340   string translated_fname = TranslateName(fname);
341   std::wstring ws_translated_fname = Utf8ToWideChar(translated_fname);
342   result->reset();
343 
344   DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
345   HANDLE hfile =
346       ::CreateFileW(ws_translated_fname.c_str(), GENERIC_WRITE, share_mode,
347                     NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
348 
349   if (INVALID_HANDLE_VALUE == hfile) {
350     string context = "Failed to create a NewWriteableFile: " + fname;
351     return IOErrorFromWindowsError(context);
352   }
353 
354   result->reset(new WindowsWritableFile(translated_fname, hfile));
355   return Status::OK();
356 }
357 
NewAppendableFile(const string & fname,TransactionToken * token,std::unique_ptr<WritableFile> * result)358 Status WindowsFileSystem::NewAppendableFile(
359     const string& fname, TransactionToken* token,
360     std::unique_ptr<WritableFile>* result) {
361   string translated_fname = TranslateName(fname);
362   std::wstring ws_translated_fname = Utf8ToWideChar(translated_fname);
363   result->reset();
364 
365   DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
366   HANDLE hfile =
367       ::CreateFileW(ws_translated_fname.c_str(), GENERIC_WRITE, share_mode,
368                     NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
369 
370   if (INVALID_HANDLE_VALUE == hfile) {
371     string context = "Failed to create a NewAppendableFile: " + fname;
372     return IOErrorFromWindowsError(context);
373   }
374 
375   UniqueCloseHandlePtr file_guard(hfile, CloseHandleFunc);
376 
377   DWORD file_ptr = ::SetFilePointer(hfile, NULL, NULL, FILE_END);
378   if (INVALID_SET_FILE_POINTER == file_ptr) {
379     string context = "Failed to create a NewAppendableFile: " + fname;
380     return IOErrorFromWindowsError(context);
381   }
382 
383   result->reset(new WindowsWritableFile(translated_fname, hfile));
384   file_guard.release();
385 
386   return Status::OK();
387 }
388 
NewReadOnlyMemoryRegionFromFile(const string & fname,TransactionToken * token,std::unique_ptr<ReadOnlyMemoryRegion> * result)389 Status WindowsFileSystem::NewReadOnlyMemoryRegionFromFile(
390     const string& fname, TransactionToken* token,
391     std::unique_ptr<ReadOnlyMemoryRegion>* result) {
392   string translated_fname = TranslateName(fname);
393   std::wstring ws_translated_fname = Utf8ToWideChar(translated_fname);
394   result->reset();
395   Status s = Status::OK();
396 
397   // Open the file for read-only
398   DWORD file_flags = FILE_ATTRIBUTE_READONLY;
399 
400   // Open in async mode which makes Windows allow more parallelism even
401   // if we need to do sync I/O on top of it.
402   file_flags |= FILE_FLAG_OVERLAPPED;
403 
404   DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
405   HANDLE hfile =
406       ::CreateFileW(ws_translated_fname.c_str(), GENERIC_READ, share_mode, NULL,
407                     OPEN_EXISTING, file_flags, NULL);
408 
409   if (INVALID_HANDLE_VALUE == hfile) {
410     return IOErrorFromWindowsError(
411         "NewReadOnlyMemoryRegionFromFile failed to Create/Open: " + fname);
412   }
413 
414   UniqueCloseHandlePtr file_guard(hfile, CloseHandleFunc);
415 
416   // Use mmap when virtual address-space is plentiful.
417   uint64_t file_size;
418   s = GetFileSize(translated_fname, &file_size);
419   if (s.ok()) {
420     // Will not map empty files
421     if (file_size == 0) {
422       return IOError(
423           "NewReadOnlyMemoryRegionFromFile failed to map empty file: " + fname,
424           EINVAL);
425     }
426 
427     HANDLE hmap = ::CreateFileMappingA(hfile, NULL, PAGE_READONLY,
428                                        0,  // Whole file at its present length
429                                        0,
430                                        NULL);  // Mapping name
431 
432     if (!hmap) {
433       string context =
434           "Failed to create file mapping for "
435           "NewReadOnlyMemoryRegionFromFile: " +
436           fname;
437       return IOErrorFromWindowsError(context);
438     }
439 
440     UniqueCloseHandlePtr map_guard(hmap, CloseHandleFunc);
441 
442     const void* mapped_region =
443         ::MapViewOfFileEx(hmap, FILE_MAP_READ,
444                           0,  // High DWORD of access start
445                           0,  // Low DWORD
446                           file_size,
447                           NULL);  // Let the OS choose the mapping
448 
449     if (!mapped_region) {
450       string context =
451           "Failed to MapViewOfFile for "
452           "NewReadOnlyMemoryRegionFromFile: " +
453           fname;
454       return IOErrorFromWindowsError(context);
455     }
456 
457     result->reset(new WinReadOnlyMemoryRegion(fname, hfile, hmap, mapped_region,
458                                               file_size));
459 
460     map_guard.release();
461     file_guard.release();
462   }
463 
464   return s;
465 }
466 
FileExists(const string & fname,TransactionToken * token)467 Status WindowsFileSystem::FileExists(const string& fname,
468                                      TransactionToken* token) {
469   constexpr int kOk = 0;
470   std::wstring ws_translated_fname = Utf8ToWideChar(TranslateName(fname));
471   if (_waccess(ws_translated_fname.c_str(), kOk) == 0) {
472     return Status::OK();
473   }
474   return errors::NotFound(fname, " not found");
475 }
476 
GetChildren(const string & dir,TransactionToken * token,std::vector<string> * result)477 Status WindowsFileSystem::GetChildren(const string& dir,
478                                       TransactionToken* token,
479                                       std::vector<string>* result) {
480   string translated_dir = TranslateName(dir);
481   std::wstring ws_translated_dir = Utf8ToWideChar(translated_dir);
482   result->clear();
483 
484   std::wstring pattern = ws_translated_dir;
485   if (!pattern.empty() && pattern.back() != '\\' && pattern.back() != '/') {
486     pattern += L"\\*";
487   } else {
488     pattern += L'*';
489   }
490 
491   WIN32_FIND_DATAW find_data;
492   HANDLE find_handle = ::FindFirstFileW(pattern.c_str(), &find_data);
493   if (find_handle == INVALID_HANDLE_VALUE) {
494     string context = "FindFirstFile failed for: " + translated_dir;
495     return IOErrorFromWindowsError(context);
496   }
497 
498   do {
499     string file_name = WideCharToUtf8(find_data.cFileName);
500     const StringPiece basename = file_name;
501     if (basename != "." && basename != "..") {
502       result->push_back(file_name);
503     }
504   } while (::FindNextFileW(find_handle, &find_data));
505 
506   if (!::FindClose(find_handle)) {
507     string context = "FindClose failed for: " + translated_dir;
508     return IOErrorFromWindowsError(context);
509   }
510 
511   return Status::OK();
512 }
513 
DeleteFile(const string & fname,TransactionToken * token)514 Status WindowsFileSystem::DeleteFile(const string& fname,
515                                      TransactionToken* token) {
516   Status result;
517   std::wstring file_name = Utf8ToWideChar(fname);
518   if (_wunlink(file_name.c_str()) != 0) {
519     result = IOError("Failed to delete a file: " + fname, errno);
520   }
521   return result;
522 }
523 
CreateDir(const string & name,TransactionToken * token)524 Status WindowsFileSystem::CreateDir(const string& name,
525                                     TransactionToken* token) {
526   Status result;
527   std::wstring ws_name = Utf8ToWideChar(name);
528   if (ws_name.empty()) {
529     return errors::AlreadyExists(name);
530   }
531   if (_wmkdir(ws_name.c_str()) != 0) {
532     result = IOError("Failed to create a directory: " + name, errno);
533   }
534   return result;
535 }
536 
DeleteDir(const string & name,TransactionToken * token)537 Status WindowsFileSystem::DeleteDir(const string& name,
538                                     TransactionToken* token) {
539   Status result;
540   std::wstring ws_name = Utf8ToWideChar(name);
541   if (_wrmdir(ws_name.c_str()) != 0) {
542     result = IOError("Failed to remove a directory: " + name, errno);
543   }
544   return result;
545 }
546 
GetFileSize(const string & fname,TransactionToken * token,uint64 * size)547 Status WindowsFileSystem::GetFileSize(const string& fname,
548                                       TransactionToken* token, uint64* size) {
549   string translated_fname = TranslateName(fname);
550   std::wstring ws_translated_dir = Utf8ToWideChar(translated_fname);
551   Status result;
552   WIN32_FILE_ATTRIBUTE_DATA attrs;
553   if (TRUE == ::GetFileAttributesExW(ws_translated_dir.c_str(),
554                                      GetFileExInfoStandard, &attrs)) {
555     ULARGE_INTEGER file_size;
556     file_size.HighPart = attrs.nFileSizeHigh;
557     file_size.LowPart = attrs.nFileSizeLow;
558     *size = file_size.QuadPart;
559   } else {
560     string context = "Can not get size for: " + fname;
561     result = IOErrorFromWindowsError(context);
562   }
563   return result;
564 }
565 
IsDirectory(const string & fname,TransactionToken * token)566 Status WindowsFileSystem::IsDirectory(const string& fname,
567                                       TransactionToken* token) {
568   TF_RETURN_IF_ERROR(FileExists(fname));
569   std::wstring ws_translated_fname = Utf8ToWideChar(TranslateName(fname));
570   if (PathIsDirectoryW(ws_translated_fname.c_str())) {
571     return Status::OK();
572   }
573   return Status(tensorflow::error::FAILED_PRECONDITION, "Not a directory");
574 }
575 
RenameFile(const string & src,const string & target,TransactionToken * token)576 Status WindowsFileSystem::RenameFile(const string& src, const string& target,
577                                      TransactionToken* token) {
578   Status result;
579   // rename() is not capable of replacing the existing file as on Linux
580   // so use OS API directly
581   std::wstring ws_translated_src = Utf8ToWideChar(TranslateName(src));
582   std::wstring ws_translated_target = Utf8ToWideChar(TranslateName(target));
583   if (!::MoveFileExW(ws_translated_src.c_str(), ws_translated_target.c_str(),
584                      MOVEFILE_REPLACE_EXISTING)) {
585     string context(strings::StrCat("Failed to rename: ", src, " to: ", target));
586     result = IOErrorFromWindowsError(context);
587   }
588   return result;
589 }
590 
GetMatchingPaths(const string & pattern,TransactionToken * token,std::vector<string> * results)591 Status WindowsFileSystem::GetMatchingPaths(const string& pattern,
592                                            TransactionToken* token,
593                                            std::vector<string>* results) {
594   // NOTE(mrry): The existing implementation of FileSystem::GetMatchingPaths()
595   // does not handle Windows paths containing backslashes correctly. Since
596   // Windows APIs will accept forward and backslashes equivalently, we
597   // convert the pattern to use forward slashes exclusively. Note that this
598   // is not ideal, since the API expects backslash as an escape character,
599   // but no code appears to rely on this behavior.
600   string converted_pattern(pattern);
601   std::replace(converted_pattern.begin(), converted_pattern.end(), '\\', '/');
602   TF_RETURN_IF_ERROR(internal::GetMatchingPaths(this, Env::Default(),
603                                                 converted_pattern, results));
604   for (string& result : *results) {
605     std::replace(result.begin(), result.end(), '/', '\\');
606   }
607   return Status::OK();
608 }
609 
Match(const string & filename,const string & pattern)610 bool WindowsFileSystem::Match(const string& filename, const string& pattern) {
611   std::wstring ws_path(Utf8ToWideChar(filename));
612   std::wstring ws_pattern(Utf8ToWideChar(pattern));
613   return PathMatchSpecW(ws_path.c_str(), ws_pattern.c_str()) == TRUE;
614 }
615 
Stat(const string & fname,TransactionToken * token,FileStatistics * stat)616 Status WindowsFileSystem::Stat(const string& fname, TransactionToken* token,
617                                FileStatistics* stat) {
618   Status result;
619   struct _stat64 sbuf;
620   std::wstring ws_translated_fname = Utf8ToWideChar(TranslateName(fname));
621   if (_wstat64(ws_translated_fname.c_str(), &sbuf) != 0) {
622     result = IOError(fname, errno);
623   } else {
624     stat->mtime_nsec = sbuf.st_mtime * 1e9;
625     stat->length = sbuf.st_size;
626     stat->is_directory = IsDirectory(fname).ok();
627   }
628   return result;
629 }
630 
631 }  // namespace tensorflow
632