posix_errno_t efile_set_cwd(const efile_path_t *path) { const WCHAR *path_start; ASSERT_PATH_FORMAT(path); /* We have to use _wchdir since that's the only function that updates the * per-drive working directory, but it naively assumes that all paths * starting with \\ are UNC paths, so we have to skip the long-path prefix. * * _wchdir doesn't handle long-prefixed UNC paths either so we hand those * to SetCurrentDirectoryW instead. The per-drive working directory is * irrelevant for such paths anyway. */ if(!IS_LONG_UNC_PATH(PATH_LENGTH(path), path->data)) { path_start = (WCHAR*)path->data + LP_PREFIX_LENGTH; if(_wchdir(path_start)) { return windows_to_posix_errno(GetLastError()); } } else { if(!SetCurrentDirectoryW((WCHAR*)path->data)) { return windows_to_posix_errno(GetLastError()); } } return 0; }
posix_errno_t efile_altname(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result) { ErlNifBinary result_bin; ASSERT_PATH_FORMAT(path); if(is_path_root(path)) { /* Root paths can't be queried so we'll just return them as they are. */ if(!enif_alloc_binary(path->size, &result_bin)) { return ENOMEM; } sys_memcpy(result_bin.data, path->data, path->size); } else { WIN32_FIND_DATAW data; HANDLE handle; WCHAR *name_buffer; int name_length; /* Reject path wildcards. */ if(wcspbrk(&((const WCHAR*)path->data)[LP_PREFIX_LENGTH], L"?*")) { return ENOENT; } handle = FindFirstFileW((const WCHAR*)path->data, &data); if(handle == INVALID_HANDLE_VALUE) { return windows_to_posix_errno(GetLastError()); } FindClose(handle); name_length = wcslen(data.cAlternateFileName); if(name_length > 0) { name_buffer = data.cAlternateFileName; } else { name_length = wcslen(data.cFileName); name_buffer = data.cFileName; } /* Include NUL-terminator; it will be removed after normalization. */ name_length += 1; if(!enif_alloc_binary(name_length * sizeof(WCHAR), &result_bin)) { return ENOMEM; } sys_memcpy(result_bin.data, name_buffer, name_length * sizeof(WCHAR)); } if(!normalize_path_result(&result_bin)) { enif_release_binary(&result_bin); return ENOMEM; } (*result) = enif_make_binary(env, &result_bin); return 0; }
static Sint64 internal_sync_io(efile_win_t *w, io_op_t operation, SysIOVec *iov, int iovlen, OVERLAPPED *overlapped) { Sint64 bytes_processed = 0; for(;;) { DWORD block_bytes_processed, last_error; BOOL succeeded; if(iovlen < 1) { return bytes_processed; } succeeded = operation(w->handle, iov->iov_base, iov->iov_len, &block_bytes_processed, overlapped); last_error = GetLastError(); if(!succeeded && (last_error != ERROR_HANDLE_EOF)) { w->common.posix_errno = windows_to_posix_errno(last_error); return -1; } else if(block_bytes_processed == 0) { /* EOF */ return bytes_processed; } if(overlapped != NULL) { shift_overlapped(overlapped, block_bytes_processed); } shift_iov(&iov, &iovlen, block_bytes_processed); bytes_processed += block_bytes_processed; } }
posix_errno_t efile_read_link(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result) { posix_errno_t posix_errno; ErlNifBinary result_bin; DWORD attributes; ASSERT_PATH_FORMAT(path); attributes = GetFileAttributesW((WCHAR*)path->data); if(attributes == INVALID_FILE_ATTRIBUTES) { return windows_to_posix_errno(GetLastError()); } else if(!(attributes & FILE_ATTRIBUTE_REPARSE_POINT)) { return EINVAL; } if(!is_name_surrogate(path)) { return EINVAL; } posix_errno = internal_read_link(path, &result_bin); if(posix_errno == 0) { if(!normalize_path_result(&result_bin)) { enif_release_binary(&result_bin); return ENOMEM; } (*result) = enif_make_binary(env, &result_bin); } return posix_errno; }
static posix_errno_t internal_read_link(const efile_path_t *path, efile_path_t *result) { DWORD required_length, actual_length; HANDLE link_handle; DWORD last_error; link_handle = CreateFileW((WCHAR*)path->data, GENERIC_READ, FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); last_error = GetLastError(); if(link_handle == INVALID_HANDLE_VALUE) { return windows_to_posix_errno(last_error); } required_length = GetFinalPathNameByHandleW(link_handle, NULL, 0, 0); last_error = GetLastError(); if(required_length <= 0) { CloseHandle(link_handle); return windows_to_posix_errno(last_error); } /* Unlike many other path functions (eg. GetFullPathNameW), this one * includes the NUL terminator in its required length. */ if(!enif_alloc_binary(required_length * sizeof(WCHAR), result)) { CloseHandle(link_handle); return ENOMEM; } actual_length = GetFinalPathNameByHandleW(link_handle, (WCHAR*)result->data, required_length, 0); last_error = GetLastError(); CloseHandle(link_handle); if(actual_length == 0 || actual_length >= required_length) { enif_release_binary(result); return windows_to_posix_errno(last_error); } /* GetFinalPathNameByHandle always prepends with "\\?\" and NUL-terminates, * so we never have to touch-up the resulting path. */ ASSERT_PATH_FORMAT(result); return 0; }
posix_errno_t efile_set_time(const efile_path_t *path, Sint64 a_time, Sint64 m_time, Sint64 c_time) { FILETIME accessed, modified, created; DWORD last_error, attributes; HANDLE handle; attributes = GetFileAttributesW((WCHAR*)path->data); if(attributes == INVALID_FILE_ATTRIBUTES) { return windows_to_posix_errno(GetLastError()); } /* If the file is read-only, we have to make it temporarily writable while * setting new metadata. */ if(attributes & FILE_ATTRIBUTE_READONLY) { DWORD without_readonly = attributes & ~FILE_ATTRIBUTE_READONLY; if(!SetFileAttributesW((WCHAR*)path->data, without_readonly)) { return windows_to_posix_errno(GetLastError()); } } EPOCH_TO_FILETIME(modified, m_time); EPOCH_TO_FILETIME(accessed, a_time); EPOCH_TO_FILETIME(created, c_time); handle = CreateFileW((WCHAR*)path->data, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); last_error = GetLastError(); if(handle != INVALID_HANDLE_VALUE) { if(SetFileTime(handle, &created, &accessed, &modified)) { last_error = ERROR_SUCCESS; } else { last_error = GetLastError(); } CloseHandle(handle); } if(attributes & FILE_ATTRIBUTE_READONLY) { SetFileAttributesW((WCHAR*)path->data, attributes); } return windows_to_posix_errno(last_error); }
posix_errno_t efile_make_dir(const efile_path_t *path) { ASSERT_PATH_FORMAT(path); if(!CreateDirectoryW((WCHAR*)path->data, NULL)) { return windows_to_posix_errno(GetLastError()); } return 0; }
int efile_truncate(efile_data_t *d) { efile_win_t *w = (efile_win_t*)d; if(!SetEndOfFile(w->handle)) { w->common.posix_errno = windows_to_posix_errno(GetLastError()); return 0; } return 1; }
posix_errno_t efile_set_permissions(const efile_path_t *path, Uint32 permissions) { DWORD attributes = GetFileAttributesW((WCHAR*)path->data); if(attributes == INVALID_FILE_ATTRIBUTES) { return windows_to_posix_errno(GetLastError()); } if(permissions & _S_IWRITE) { attributes &= ~FILE_ATTRIBUTE_READONLY; } else { attributes |= FILE_ATTRIBUTE_READONLY; } if(SetFileAttributesW((WCHAR*)path->data, attributes)) { return 0; } return windows_to_posix_errno(GetLastError()); }
posix_errno_t efile_make_hard_link(const efile_path_t *existing_path, const efile_path_t *new_path) { ASSERT_PATH_FORMAT(existing_path); ASSERT_PATH_FORMAT(new_path); if(!CreateHardLinkW((WCHAR*)new_path->data, (WCHAR*)existing_path->data, NULL)) { return windows_to_posix_errno(GetLastError()); } return 0; }
posix_errno_t efile_list_dir(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result) { ERL_NIF_TERM list_head; WIN32_FIND_DATAW data; HANDLE search_handle; WCHAR *search_path; DWORD last_error; ASSERT_PATH_FORMAT(path); search_path = enif_alloc(path->size + 2 * sizeof(WCHAR)); if(search_path == NULL) { return ENOMEM; } sys_memcpy(search_path, path->data, path->size); search_path[PATH_LENGTH(path) + 0] = L'\\'; search_path[PATH_LENGTH(path) + 1] = L'*'; search_path[PATH_LENGTH(path) + 2] = L'\0'; search_handle = FindFirstFileW(search_path, &data); last_error = GetLastError(); enif_free(search_path); if(search_handle == INVALID_HANDLE_VALUE) { return windows_to_posix_errno(last_error); } list_head = enif_make_list(env, 0); do { int name_length = wcslen(data.cFileName); if(!is_ignored_name(name_length, data.cFileName)) { unsigned char *name_bytes; ERL_NIF_TERM name_term; size_t name_size; name_size = name_length * sizeof(WCHAR); name_bytes = enif_make_new_binary(env, name_size, &name_term); sys_memcpy(name_bytes, data.cFileName, name_size); list_head = enif_make_list_cell(env, name_term, list_head); } } while(FindNextFileW(search_handle, &data)); FindClose(search_handle); (*result) = list_head; return 0; }
int efile_sync(efile_data_t *d, int data_only) { efile_win_t *w = (efile_win_t*)d; /* Windows doesn't support data-only syncing. */ (void)data_only; if(!FlushFileBuffers(w->handle)) { w->common.posix_errno = windows_to_posix_errno(GetLastError()); return 0; } return 1; }
posix_errno_t efile_del_dir(const efile_path_t *path) { ASSERT_PATH_FORMAT(path); if(!RemoveDirectoryW((WCHAR*)path->data)) { DWORD last_error = GetLastError(); if(last_error == ERROR_DIRECTORY) { return ENOTDIR; } return windows_to_posix_errno(last_error); } return 0; }
posix_errno_t efile_set_cwd(const efile_path_t *path) { const WCHAR *path_start; ASSERT_PATH_FORMAT(path); /* We have to use _wchdir since that's the only function that updates the * per-drive working directory, but it naively assumes that all paths * starting with \\ are UNC paths, so we have to skip the \\?\-prefix. */ path_start = (WCHAR*)path->data + LP_PREFIX_LENGTH; if(_wchdir(path_start)) { return windows_to_posix_errno(GetLastError()); } return 0; }
int efile_close(efile_data_t *d) { efile_win_t *w = (efile_win_t*)d; HANDLE handle; ASSERT(erts_atomic32_read_nob(&d->state) == EFILE_STATE_CLOSED); ASSERT(w->handle != INVALID_HANDLE_VALUE); handle = w->handle; w->handle = INVALID_HANDLE_VALUE; if(!CloseHandle(handle)) { w->common.posix_errno = windows_to_posix_errno(GetLastError()); return 0; } return 1; }
posix_errno_t efile_make_soft_link(const efile_path_t *existing_path, const efile_path_t *new_path) { DWORD link_flags; ASSERT_PATH_FORMAT(existing_path); ASSERT_PATH_FORMAT(new_path); if(has_file_attributes(existing_path, FILE_ATTRIBUTE_DIRECTORY)) { link_flags = SYMBOLIC_LINK_FLAG_DIRECTORY; } else { link_flags = 0; } if(!CreateSymbolicLinkW((WCHAR*)new_path->data, (WCHAR*)existing_path->data, link_flags)) { return windows_to_posix_errno(GetLastError()); } return 0; }
int efile_close(efile_data_t *d, posix_errno_t *error) { efile_win_t *w = (efile_win_t*)d; HANDLE handle; ASSERT(enif_thread_type() == ERL_NIF_THR_DIRTY_IO_SCHEDULER); ASSERT(erts_atomic32_read_nob(&d->state) == EFILE_STATE_CLOSED); ASSERT(w->handle != INVALID_HANDLE_VALUE); handle = w->handle; w->handle = INVALID_HANDLE_VALUE; enif_release_resource(d); if(!CloseHandle(handle)) { *error = windows_to_posix_errno(GetLastError()); return 0; } return 1; }
posix_errno_t efile_del_file(const efile_path_t *path) { ASSERT_PATH_FORMAT(path); if(!DeleteFileW((WCHAR*)path->data)) { DWORD last_error = GetLastError(); switch(last_error) { case ERROR_INVALID_NAME: /* Attempted to delete a device or similar. */ return EACCES; case ERROR_ACCESS_DENIED: /* Windows NT reports removing a directory as EACCES instead of * EPERM. */ if(has_file_attributes(path, FILE_ATTRIBUTE_DIRECTORY)) { return EPERM; } break; } return windows_to_posix_errno(last_error); } return 0; }
int efile_seek(efile_data_t *d, enum efile_seek_t seek, Sint64 offset, Sint64 *new_position) { efile_win_t *w = (efile_win_t*)d; LARGE_INTEGER large_offset, large_new_position; DWORD whence; switch(seek) { case EFILE_SEEK_BOF: whence = FILE_BEGIN; break; case EFILE_SEEK_CUR: whence = FILE_CURRENT; break; case EFILE_SEEK_EOF: whence = FILE_END; break; default: ERTS_INTERNAL_ERROR("Invalid seek parameter"); } large_offset.QuadPart = offset; if(!SetFilePointerEx(w->handle, large_offset, &large_new_position, whence)) { w->common.posix_errno = windows_to_posix_errno(GetLastError()); return 0; } (*new_position) = large_new_position.QuadPart; return 1; }
posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes, ErlNifResourceType *nif_type, efile_data_t **d) { DWORD attributes, access_flags, open_mode; HANDLE handle; ASSERT_PATH_FORMAT(path); access_flags = 0; open_mode = 0; if(modes & EFILE_MODE_READ && !(modes & EFILE_MODE_WRITE)) { access_flags = GENERIC_READ; open_mode = OPEN_EXISTING; } else if(modes & EFILE_MODE_WRITE && !(modes & EFILE_MODE_READ)) { access_flags = GENERIC_WRITE; open_mode = CREATE_ALWAYS; } else if(modes & EFILE_MODE_READ_WRITE) { access_flags = GENERIC_READ | GENERIC_WRITE; open_mode = OPEN_ALWAYS; } else { return EINVAL; } if(modes & EFILE_MODE_APPEND) { access_flags |= FILE_APPEND_DATA; open_mode = OPEN_ALWAYS; } if(modes & EFILE_MODE_EXCLUSIVE) { open_mode = CREATE_NEW; } if(modes & EFILE_MODE_SYNC) { attributes = FILE_FLAG_WRITE_THROUGH; } else { attributes = FILE_ATTRIBUTE_NORMAL; } handle = CreateFileW((WCHAR*)path->data, access_flags, FILE_SHARE_FLAGS, NULL, open_mode, attributes, NULL); if(handle != INVALID_HANDLE_VALUE) { efile_win_t *w; w = (efile_win_t*)enif_alloc_resource(nif_type, sizeof(efile_win_t)); w->handle = handle; EFILE_INIT_RESOURCE(&w->common, modes); (*d) = &w->common; return 0; } else { DWORD last_error = GetLastError(); /* Rewrite all failures on directories to EISDIR to match the old * driver. */ if(has_file_attributes(path, FILE_ATTRIBUTE_DIRECTORY)) { return EISDIR; } return windows_to_posix_errno(last_error); } }
posix_errno_t efile_rename(const efile_path_t *old_path, const efile_path_t *new_path) { BOOL old_is_directory, new_is_directory; DWORD move_flags, last_error; ASSERT_PATH_FORMAT(old_path); ASSERT_PATH_FORMAT(new_path); move_flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH; if(MoveFileExW((WCHAR*)old_path->data, (WCHAR*)new_path->data, move_flags)) { return 0; } last_error = GetLastError(); old_is_directory = has_file_attributes(old_path, FILE_ATTRIBUTE_DIRECTORY); new_is_directory = has_file_attributes(new_path, FILE_ATTRIBUTE_DIRECTORY); switch(last_error) { case ERROR_SHARING_VIOLATION: case ERROR_ACCESS_DENIED: if(old_is_directory) { BOOL moved_into_itself; moved_into_itself = (old_path->size <= new_path->size) && !_wcsnicmp((WCHAR*)old_path->data, (WCHAR*)new_path->data, PATH_LENGTH(old_path)); if(moved_into_itself) { return EINVAL; } else if(is_path_root(old_path)) { return EINVAL; } /* Renaming a directory across volumes needs to be rewritten as * EXDEV so that the caller can respond by simulating it with * copy/delete operations. * * Files are handled through MOVEFILE_COPY_ALLOWED. */ if(!has_same_mount_point(old_path, new_path)) { return EXDEV; } } break; case ERROR_PATH_NOT_FOUND: case ERROR_FILE_NOT_FOUND: return ENOENT; case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: if(old_is_directory && !new_is_directory) { return ENOTDIR; } else if(!old_is_directory && new_is_directory) { return EISDIR; } else if(old_is_directory && new_is_directory) { /* This will fail if the destination isn't empty. */ if(RemoveDirectoryW((WCHAR*)new_path->data)) { return efile_rename(old_path, new_path); } return EEXIST; } else if(!old_is_directory && !new_is_directory) { /* This is pretty iffy; the public documentation says that the * operation may EACCES on some systems when either file is open, * which gives us room to use MOVEFILE_REPLACE_EXISTING and be done * with it, but the old implementation simulated Unix semantics and * there's a lot of code that relies on that. * * The simulation renames the destination to a scratch name to get * around the fact that it's impossible to open (and by extension * rename) a file that's been deleted while open. It has a few * drawbacks though; * * 1) It's not atomic as there's a small window where there's no * file at all on the destination path. * 2) It will confuse applications that subscribe to folder * changes. * 3) It will fail if we lack general permission to write in the * same folder. */ WCHAR *swap_path = enif_alloc(new_path->size + sizeof(WCHAR) * 64); if(swap_path == NULL) { return ENOMEM; } else { static LONGLONG unique_counter = 0; WCHAR *swap_path_end; /* We swap in the same folder as the destination to be * reasonably sure that it's on the same volume. Note that * we're avoiding GetTempFileNameW as it will fail on long * paths. */ sys_memcpy(swap_path, (WCHAR*)new_path->data, new_path->size); swap_path_end = swap_path + PATH_LENGTH(new_path); while(!IS_SLASH(*swap_path_end)) { ASSERT(swap_path_end > swap_path); swap_path_end--; } StringCchPrintfW(&swap_path_end[1], 64, L"erl-%lx-%llx.tmp", GetCurrentProcessId(), unique_counter); InterlockedIncrement64(&unique_counter); } if(MoveFileExW((WCHAR*)new_path->data, swap_path, MOVEFILE_REPLACE_EXISTING)) { if(MoveFileExW((WCHAR*)old_path->data, (WCHAR*)new_path->data, move_flags)) { last_error = ERROR_SUCCESS; DeleteFileW(swap_path); } else { last_error = GetLastError(); MoveFileW(swap_path, (WCHAR*)new_path->data); } } else { last_error = GetLastError(); DeleteFileW(swap_path); } enif_free(swap_path); return windows_to_posix_errno(last_error); } return EEXIST; } return windows_to_posix_errno(last_error); }
posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_fileinfo_t *result) { BY_HANDLE_FILE_INFORMATION native_file_info; DWORD attributes; int is_link; sys_memset(&native_file_info, 0, sizeof(native_file_info)); is_link = 0; attributes = GetFileAttributesW((WCHAR*)path->data); if(attributes == INVALID_FILE_ATTRIBUTES) { DWORD last_error = GetLastError(); /* Querying a network share root fails with ERROR_BAD_NETPATH, so we'll * fake it as a directory just like local roots. */ if(!is_path_root(path) || last_error != ERROR_BAD_NETPATH) { return windows_to_posix_errno(last_error); } attributes = FILE_ATTRIBUTE_DIRECTORY; } else if(is_path_root(path)) { /* Local (or mounted) roots can be queried with GetFileAttributesW but * lack support for GetFileInformationByHandle, so we'll skip that * part. */ } else { HANDLE handle; if(attributes & FILE_ATTRIBUTE_REPARSE_POINT) { is_link = is_name_surrogate(path); } if(follow_links && is_link) { posix_errno_t posix_errno; efile_path_t resolved_path; posix_errno = internal_read_link(path, &resolved_path); if(posix_errno != 0) { return posix_errno; } return efile_read_info(&resolved_path, 0, result); } handle = CreateFileW((const WCHAR*)path->data, GENERIC_READ, FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); /* The old driver never cared whether this succeeded. */ if(handle != INVALID_HANDLE_VALUE) { GetFileInformationByHandle(handle, &native_file_info); CloseHandle(handle); } FILETIME_TO_EPOCH(result->m_time, native_file_info.ftLastWriteTime); FILETIME_TO_EPOCH(result->a_time, native_file_info.ftLastAccessTime); FILETIME_TO_EPOCH(result->c_time, native_file_info.ftCreationTime); if(result->m_time == -EPOCH_DIFFERENCE) { /* Default to 1970 just like the old driver. */ result->m_time = 0; } if(result->a_time == -EPOCH_DIFFERENCE) { result->a_time = result->m_time; } if(result->c_time == -EPOCH_DIFFERENCE) { result->c_time = result->m_time; } } if(is_link) { result->type = EFILE_FILETYPE_SYMLINK; /* This should be _S_IFLNK, but the old driver always set * non-directories to _S_IFREG. */ result->mode |= _S_IFREG; } else if(attributes & FILE_ATTRIBUTE_DIRECTORY) { result->type = EFILE_FILETYPE_DIRECTORY; result->mode |= _S_IFDIR | _S_IEXEC; } else { if(is_executable_file(path)) { result->mode |= _S_IEXEC; } result->type = EFILE_FILETYPE_REGULAR; result->mode |= _S_IFREG; } if(!(attributes & FILE_ATTRIBUTE_READONLY)) { result->access = EFILE_ACCESS_READ | EFILE_ACCESS_WRITE; result->mode |= _S_IREAD | _S_IWRITE; } else { result->access = EFILE_ACCESS_READ; result->mode |= _S_IREAD; } /* Propagate user mode-bits to group/other fields */ result->mode |= (result->mode & 0700) >> 3; result->mode |= (result->mode & 0700) >> 6; result->size = ((Uint64)native_file_info.nFileSizeHigh << 32ull) | (Uint64)native_file_info.nFileSizeLow; result->links = MAX(1, native_file_info.nNumberOfLinks); result->major_device = get_drive_number(path); result->minor_device = 0; result->inode = 0; result->uid = 0; result->gid = 0; return 0; }