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