| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include "platform/globals.h" |
| #if defined(TARGET_OS_WINDOWS) |
| |
| #include "bin/file.h" |
| |
| #include <fcntl.h> // NOLINT |
| #include <io.h> // NOLINT |
| #include <stdio.h> // NOLINT |
| #include <string.h> // NOLINT |
| #include <sys/stat.h> // NOLINT |
| #include <WinIoCtl.h> // NOLINT |
| |
| #include "bin/builtin.h" |
| #include "bin/log.h" |
| |
| |
| namespace dart { |
| namespace bin { |
| |
| class FileHandle { |
| public: |
| explicit FileHandle(int fd) : fd_(fd) { } |
| ~FileHandle() { } |
| int fd() const { return fd_; } |
| void set_fd(int fd) { fd_ = fd; } |
| |
| private: |
| int fd_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FileHandle); |
| }; |
| |
| |
| File::~File() { |
| // Close the file (unless it's a standard stream). |
| if (handle_->fd() > 2) { |
| Close(); |
| } |
| delete handle_; |
| } |
| |
| |
| void File::Close() { |
| ASSERT(handle_->fd() >= 0); |
| int err = close(handle_->fd()); |
| if (err != 0) { |
| Log::PrintErr("%s\n", strerror(errno)); |
| } |
| handle_->set_fd(kClosedFd); |
| } |
| |
| |
| bool File::IsClosed() { |
| return handle_->fd() == kClosedFd; |
| } |
| |
| |
| int64_t File::Read(void* buffer, int64_t num_bytes) { |
| ASSERT(handle_->fd() >= 0); |
| return read(handle_->fd(), buffer, num_bytes); |
| } |
| |
| |
| int64_t File::Write(const void* buffer, int64_t num_bytes) { |
| ASSERT(handle_->fd() >= 0); |
| return write(handle_->fd(), buffer, num_bytes); |
| } |
| |
| |
| off_t File::Position() { |
| ASSERT(handle_->fd() >= 0); |
| return lseek(handle_->fd(), 0, SEEK_CUR); |
| } |
| |
| |
| bool File::SetPosition(int64_t position) { |
| ASSERT(handle_->fd() >= 0); |
| return (lseek(handle_->fd(), position, SEEK_SET) != -1); |
| } |
| |
| |
| bool File::Truncate(int64_t length) { |
| ASSERT(handle_->fd() >= 0); |
| return (chsize(handle_->fd(), length) != -1); |
| } |
| |
| |
| bool File::Flush() { |
| ASSERT(handle_->fd()); |
| return _commit(handle_->fd()) != -1; |
| } |
| |
| |
| off_t File::Length() { |
| ASSERT(handle_->fd() >= 0); |
| struct stat st; |
| if (fstat(handle_->fd(), &st) == 0) { |
| return st.st_size; |
| } |
| return -1; |
| } |
| |
| |
| File* File::Open(const char* name, FileOpenMode mode) { |
| int flags = O_RDONLY | O_BINARY | O_NOINHERIT; |
| if ((mode & kWrite) != 0) { |
| flags = (O_RDWR | O_CREAT | O_BINARY | O_NOINHERIT); |
| } |
| if ((mode & kTruncate) != 0) { |
| flags = flags | O_TRUNC; |
| } |
| const wchar_t* system_name = StringUtils::Utf8ToWide(name); |
| int fd = _wopen(system_name, flags, 0666); |
| free(const_cast<wchar_t*>(system_name)); |
| if (fd < 0) { |
| return NULL; |
| } |
| if (((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) { |
| int position = lseek(fd, 0, SEEK_END); |
| if (position < 0) { |
| return NULL; |
| } |
| } |
| return new File(new FileHandle(fd)); |
| } |
| |
| |
| File* File::OpenStdio(int fd) { |
| UNREACHABLE(); |
| return NULL; |
| } |
| |
| |
| bool File::Exists(const char* name) { |
| struct _stat st; |
| const wchar_t* system_name = StringUtils::Utf8ToWide(name); |
| bool stat_status = _wstat(system_name, &st); |
| free(const_cast<wchar_t*>(system_name)); |
| if (stat_status == 0) { |
| return ((st.st_mode & S_IFMT) == S_IFREG); |
| } else { |
| return false; |
| } |
| } |
| |
| |
| bool File::Create(const char* name) { |
| const wchar_t* system_name = StringUtils::Utf8ToWide(name); |
| int fd = _wopen(system_name, O_RDONLY | O_CREAT, 0666); |
| free(const_cast<wchar_t*>(system_name)); |
| if (fd < 0) { |
| return false; |
| } |
| return (close(fd) == 0); |
| } |
| |
| |
| // This structure is needed for creating and reading Junctions. |
| typedef struct _REPARSE_DATA_BUFFER { |
| ULONG ReparseTag; |
| USHORT ReparseDataLength; |
| USHORT Reserved; |
| |
| union { |
| struct { |
| USHORT SubstituteNameOffset; |
| USHORT SubstituteNameLength; |
| USHORT PrintNameOffset; |
| USHORT PrintNameLength; |
| ULONG Flags; |
| WCHAR PathBuffer[1]; |
| } SymbolicLinkReparseBuffer; |
| |
| struct { |
| USHORT SubstituteNameOffset; |
| USHORT SubstituteNameLength; |
| USHORT PrintNameOffset; |
| USHORT PrintNameLength; |
| WCHAR PathBuffer[1]; |
| } MountPointReparseBuffer; |
| |
| struct { |
| UCHAR DataBuffer[1]; |
| } GenericReparseBuffer; |
| }; |
| } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; |
| |
| |
| static const int kReparseDataHeaderSize = sizeof ULONG + 2 * sizeof USHORT; |
| static const int kMountPointHeaderSize = 4 * sizeof USHORT; |
| |
| |
| bool File::CreateLink(const char* utf8_name, const char* utf8_target) { |
| const wchar_t* name = StringUtils::Utf8ToWide(utf8_name); |
| int create_status = CreateDirectoryW(name, NULL); |
| // If the directory already existed, treat it as a success. |
| if (create_status == 0 && |
| (GetLastError() != ERROR_ALREADY_EXISTS || |
| (GetFileAttributesW(name) & FILE_ATTRIBUTE_DIRECTORY) != 0)) { |
| free(const_cast<wchar_t*>(name)); |
| return false; |
| } |
| |
| HANDLE dir_handle = CreateFileW( |
| name, |
| GENERIC_READ | GENERIC_WRITE, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| NULL, |
| OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, |
| NULL); |
| free(const_cast<wchar_t*>(name)); |
| if (dir_handle == INVALID_HANDLE_VALUE) { |
| return false; |
| } |
| |
| const wchar_t* target = StringUtils::Utf8ToWide(utf8_target); |
| int target_len = wcslen(target); |
| if (target_len > MAX_PATH - 1) { |
| free(const_cast<wchar_t*>(target)); |
| CloseHandle(dir_handle); |
| return false; |
| } |
| |
| int reparse_data_buffer_size = |
| sizeof REPARSE_DATA_BUFFER + 2 * MAX_PATH * sizeof WCHAR; |
| REPARSE_DATA_BUFFER* reparse_data_buffer = |
| static_cast<REPARSE_DATA_BUFFER*>(calloc(reparse_data_buffer_size, 1)); |
| reparse_data_buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; |
| wcscpy(reparse_data_buffer->MountPointReparseBuffer.PathBuffer, target); |
| wcscpy( |
| reparse_data_buffer->MountPointReparseBuffer.PathBuffer + target_len + 1, |
| target); |
| reparse_data_buffer->MountPointReparseBuffer.SubstituteNameOffset = 0; |
| reparse_data_buffer->MountPointReparseBuffer.SubstituteNameLength = |
| target_len * sizeof WCHAR; |
| reparse_data_buffer->MountPointReparseBuffer.PrintNameOffset = |
| (target_len + 1) * sizeof WCHAR; |
| reparse_data_buffer->MountPointReparseBuffer.PrintNameLength = |
| target_len * sizeof WCHAR; |
| reparse_data_buffer->ReparseDataLength = |
| (target_len + 1) * 2 * sizeof WCHAR + kMountPointHeaderSize; |
| DWORD dummy_received_bytes; |
| int result = DeviceIoControl( |
| dir_handle, |
| FSCTL_SET_REPARSE_POINT, |
| reparse_data_buffer, |
| reparse_data_buffer->ReparseDataLength + kReparseDataHeaderSize, |
| NULL, |
| 0, |
| &dummy_received_bytes, |
| NULL); |
| if (CloseHandle(dir_handle) == 0) return false; |
| free(const_cast<wchar_t*>(target)); |
| free(reparse_data_buffer); |
| return (result != 0); |
| } |
| |
| |
| bool File::Delete(const char* name) { |
| const wchar_t* system_name = StringUtils::Utf8ToWide(name); |
| int status = _wremove(system_name); |
| free(const_cast<wchar_t*>(system_name)); |
| return status != -1; |
| } |
| |
| |
| bool File::DeleteLink(const char* name) { |
| const wchar_t* system_name = StringUtils::Utf8ToWide(name); |
| bool result = false; |
| DWORD attributes = GetFileAttributesW(system_name); |
| if ((attributes != INVALID_FILE_ATTRIBUTES) && |
| (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { |
| // It's a junction(link), delete it. |
| result = (RemoveDirectoryW(system_name) != 0); |
| } else { |
| SetLastError(ERROR_NOT_A_REPARSE_POINT); |
| } |
| free(const_cast<wchar_t*>(system_name)); |
| return result; |
| } |
| |
| |
| off_t File::LengthFromPath(const char* name) { |
| struct _stat st; |
| const wchar_t* system_name = StringUtils::Utf8ToWide(name); |
| int stat_status = _wstat(system_name, &st); |
| free(const_cast<wchar_t*>(system_name)); |
| if (stat_status == 0) { |
| return st.st_size; |
| } |
| return -1; |
| } |
| |
| |
| char* File::LinkTarget(const char* pathname) { |
| const wchar_t* name = StringUtils::Utf8ToWide(pathname); |
| HANDLE dir_handle = CreateFileW( |
| name, |
| GENERIC_READ, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| NULL, |
| OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, |
| NULL); |
| free(const_cast<wchar_t*>(name)); |
| if (dir_handle == INVALID_HANDLE_VALUE) { |
| return NULL; |
| } |
| |
| int buffer_size = |
| sizeof REPARSE_DATA_BUFFER + 2 * (MAX_PATH + 1) * sizeof WCHAR; |
| REPARSE_DATA_BUFFER* buffer = |
| static_cast<REPARSE_DATA_BUFFER*>(calloc(buffer_size, 1)); |
| DWORD received_bytes; // Value is not used. |
| int result = DeviceIoControl( |
| dir_handle, |
| FSCTL_GET_REPARSE_POINT, |
| NULL, |
| 0, |
| buffer, |
| buffer_size, |
| &received_bytes, |
| NULL); |
| if (result == 0) { |
| DWORD error = GetLastError(); |
| CloseHandle(dir_handle); |
| SetLastError(error); |
| free(buffer); |
| return NULL; |
| } |
| if (CloseHandle(dir_handle) == 0) { |
| free(buffer); |
| return NULL; |
| } |
| |
| wchar_t* target; |
| size_t target_offset; |
| size_t target_length; |
| if (buffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { |
| target = buffer->MountPointReparseBuffer.PathBuffer; |
| target_offset = buffer->MountPointReparseBuffer.SubstituteNameOffset; |
| target_length = buffer->MountPointReparseBuffer.SubstituteNameLength; |
| } else if (buffer->ReparseTag == IO_REPARSE_TAG_SYMLINK) { |
| target = buffer->SymbolicLinkReparseBuffer.PathBuffer; |
| target_offset = buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset; |
| target_length = buffer->SymbolicLinkReparseBuffer.SubstituteNameLength; |
| } else { // Not a junction or a symbolic link. |
| free(buffer); |
| SetLastError(ERROR_NOT_A_REPARSE_POINT); |
| return NULL; |
| } |
| |
| target_offset /= sizeof(wchar_t); // Offset and length are in bytes. |
| target_length /= sizeof(wchar_t); |
| target += target_offset; |
| // Remove "\??\" from beginning of target. |
| if (target_length > 4 && wcsncmp(L"\\??\\", target, 4) == 0) { |
| target += 4; |
| target_length -=4; |
| } |
| int utf8_length = WideCharToMultiByte(CP_UTF8, |
| 0, |
| target, |
| target_length, |
| NULL, |
| 0, |
| NULL, |
| NULL); |
| char* utf8_target = reinterpret_cast<char*>(malloc(utf8_length + 1)); |
| if (0 == WideCharToMultiByte(CP_UTF8, |
| 0, |
| target, |
| target_length, |
| utf8_target, |
| utf8_length, |
| NULL, |
| NULL)) { |
| free(buffer); |
| free(utf8_target); |
| return NULL; |
| } |
| utf8_target[utf8_length] = '\0'; |
| free(buffer); |
| return utf8_target; |
| } |
| |
| |
| time_t File::LastModified(const char* name) { |
| struct _stat st; |
| const wchar_t* system_name = StringUtils::Utf8ToWide(name); |
| int stat_status = _wstat(system_name, &st); |
| free(const_cast<wchar_t*>(system_name)); |
| if (stat_status == 0) { |
| return st.st_mtime; |
| } |
| return -1; |
| } |
| |
| |
| bool File::IsAbsolutePath(const char* pathname) { |
| // Should we consider network paths? |
| if (pathname == NULL) return false; |
| return (strlen(pathname) > 2) && |
| (pathname[1] == ':') && |
| (pathname[2] == '\\' || pathname[2] == '/'); |
| } |
| |
| |
| char* File::GetCanonicalPath(const char* pathname) { |
| struct _stat st; |
| const wchar_t* system_name = StringUtils::Utf8ToWide(pathname); |
| int stat_status = _wstat(system_name, &st); |
| if (stat_status != 0) { |
| SetLastError(ERROR_FILE_NOT_FOUND); |
| free(const_cast<wchar_t*>(system_name)); |
| return NULL; |
| } |
| int required_size = GetFullPathNameW(system_name, 0, NULL, NULL); |
| wchar_t* path = |
| static_cast<wchar_t*>(malloc(required_size * sizeof(wchar_t))); |
| int written = GetFullPathNameW(system_name, required_size, path, NULL); |
| free(const_cast<wchar_t*>(system_name)); |
| ASSERT(written <= (required_size - 1)); |
| char* result = StringUtils::WideToUtf8(path); |
| free(path); |
| return result; |
| } |
| |
| |
| char* File::GetContainingDirectory(char* pathname) { |
| struct _stat st; |
| wchar_t* system_name = StringUtils::Utf8ToWide(pathname); |
| int stat_status = _wstat(system_name, &st); |
| if (stat_status == 0) { |
| if ((st.st_mode & S_IFMT) != S_IFREG) { |
| SetLastError(ERROR_FILE_NOT_FOUND); |
| free(system_name); |
| return NULL; |
| } |
| } else { |
| SetLastError(ERROR_FILE_NOT_FOUND); |
| free(system_name); |
| return NULL; |
| } |
| int required_size = GetFullPathNameW(system_name, 0, NULL, NULL); |
| wchar_t* path = |
| static_cast<wchar_t*>(malloc(required_size * sizeof(wchar_t))); |
| wchar_t* file_part = NULL; |
| int written = |
| GetFullPathNameW(system_name, required_size, path, &file_part); |
| free(system_name); |
| ASSERT(written == (required_size - 1)); |
| ASSERT(file_part != NULL); |
| ASSERT(file_part > path); |
| ASSERT(file_part[-1] == L'\\'); |
| file_part[-1] = '\0'; |
| char* result = StringUtils::WideToUtf8(path); |
| free(path); |
| return result; |
| } |
| |
| |
| const char* File::PathSeparator() { |
| // This is already UTF-8 encoded. |
| return "\\"; |
| } |
| |
| |
| const char* File::StringEscapedPathSeparator() { |
| // This is already UTF-8 encoded. |
| return "\\\\"; |
| } |
| |
| |
| File::StdioHandleType File::GetStdioHandleType(int fd) { |
| // Treat all stdio handles as pipes. The Windows event handler and |
| // socket code will handle the different handle types. |
| return kPipe; |
| } |
| |
| |
| File::Type File::GetType(const char* pathname, bool follow_links) { |
| const wchar_t* name = StringUtils::Utf8ToWide(pathname); |
| DWORD attributes = GetFileAttributesW(name); |
| File::Type result = kIsFile; |
| if (attributes == INVALID_FILE_ATTRIBUTES) { |
| result = kDoesNotExist; |
| } else if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { |
| if (follow_links) { |
| HANDLE dir_handle = CreateFileW( |
| name, |
| 0, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| NULL, |
| OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS, |
| NULL); |
| if (dir_handle == INVALID_HANDLE_VALUE) { |
| result = File::kIsLink; |
| } else { |
| CloseHandle(dir_handle); |
| result = File::kIsDirectory; |
| } |
| } else { |
| result = kIsLink; |
| } |
| } else if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { |
| result = kIsDirectory; |
| } |
| free(const_cast<wchar_t*>(name)); |
| return result; |
| } |
| |
| |
| File::Identical File::AreIdentical(const char* file_1, const char* file_2) { |
| BY_HANDLE_FILE_INFORMATION file_info[2]; |
| const char* file_names[2] = { file_1, file_2 }; |
| for (int i = 0; i < 2; ++i) { |
| const wchar_t* wide_name = StringUtils::Utf8ToWide(file_names[i]); |
| HANDLE file_handle = CreateFileW( |
| wide_name, |
| 0, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| NULL, |
| OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, |
| NULL); |
| if (file_handle == INVALID_HANDLE_VALUE) { |
| free(const_cast<wchar_t*>(wide_name)); |
| return File::kError; |
| } |
| free(const_cast<wchar_t*>(wide_name)); |
| int result = GetFileInformationByHandle(file_handle, &file_info[i]); |
| if (result == 0) { |
| DWORD error = GetLastError(); |
| CloseHandle(file_handle); |
| SetLastError(error); |
| return File::kError; |
| } |
| if (CloseHandle(file_handle) == 0) { |
| return File::kError; |
| } |
| } |
| if (file_info[0].dwVolumeSerialNumber == file_info[1].dwVolumeSerialNumber && |
| file_info[0].nFileIndexHigh == file_info[1].nFileIndexHigh && |
| file_info[0].nFileIndexLow == file_info[1].nFileIndexLow) { |
| return kIdentical; |
| } else { |
| return kDifferent; |
| } |
| } |
| |
| } // namespace bin |
| } // namespace dart |
| |
| #endif // defined(TARGET_OS_WINDOWS) |