1379 lines
39 KiB
C
1379 lines
39 KiB
C
/*
|
|
* Author: Manoj Ampalam <manoj.ampalam@microsoft.com>
|
|
*
|
|
* Author: Bryan Berns <berns@uwalumni.com>
|
|
* Added symlink support
|
|
*
|
|
* Copyright (c) 2015 Microsoft Corp.
|
|
* All rights reserved
|
|
*
|
|
* Microsoft openssh win32 port
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <fcntl.h>
|
|
#include "inc/sys/stat.h"
|
|
#include "inc/sys/types.h"
|
|
#include <io.h>
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <direct.h>
|
|
|
|
#include "w32fd.h"
|
|
#include "inc\utf.h"
|
|
#include "inc\fcntl.h"
|
|
#include "inc\pwd.h"
|
|
#include "misc_internal.h"
|
|
#include "debug.h"
|
|
#include <Sddl.h>
|
|
|
|
/* internal read buffer size */
|
|
#define READ_BUFFER_SIZE 100*1024
|
|
/* internal write buffer size */
|
|
#define WRITE_BUFFER_SIZE 100*1024
|
|
|
|
/*
|
|
* A ACE is a binary data structure of changeable length
|
|
* https://msdn.microsoft.com/en-us/library/windows/desktop/aa374928(v=vs.85).aspx
|
|
* The value is calculated based on current need: max sid string (184) plus the enough spaces for other fields in ACEs
|
|
*/
|
|
#define MAX_ACE_LENGTH 225
|
|
/*
|
|
* A security descriptor is a binary data structure of changeable length
|
|
* https://msdn.microsoft.com/en-us/library/windows/desktop/aa379570(v=vs.85).aspx
|
|
* The value is calculated based on current need: 4 ACEs plus the enough spaces for owner sid and dcal flag
|
|
*/
|
|
#define SDDL_LENGTH 5* MAX_ACE_LENGTH
|
|
|
|
/*MAX length attribute string looks like 0xffffffff*/
|
|
#define MAX_ATTRIBUTE_LENGTH 10
|
|
|
|
#define errno_from_Win32LastError() errno_from_Win32Error(GetLastError())
|
|
|
|
struct createFile_flags {
|
|
DWORD dwDesiredAccess;
|
|
DWORD dwShareMode;
|
|
SECURITY_ATTRIBUTES securityAttributes;
|
|
DWORD dwCreationDisposition;
|
|
DWORD dwFlagsAndAttributes;
|
|
};
|
|
|
|
int syncio_initiate_read(struct w32_io* pio);
|
|
int syncio_initiate_write(struct w32_io* pio, DWORD num_bytes);
|
|
int syncio_close(struct w32_io* pio);
|
|
|
|
/* maps Win32 error to errno */
|
|
int
|
|
errno_from_Win32Error(int win32_error)
|
|
{
|
|
switch (win32_error) {
|
|
case ERROR_PRIVILEGE_NOT_HELD:
|
|
case ERROR_ACCESS_DENIED:
|
|
return EACCES;
|
|
case ERROR_OUTOFMEMORY:
|
|
return ENOMEM;
|
|
case ERROR_FILE_EXISTS:
|
|
return EEXIST;
|
|
case ERROR_FILE_NOT_FOUND:
|
|
case ERROR_PATH_NOT_FOUND:
|
|
case ERROR_INVALID_NAME:
|
|
return ENOENT;
|
|
case ERROR_INVALID_FUNCTION:
|
|
case ERROR_NOT_SUPPORTED:
|
|
return EOPNOTSUPP;
|
|
default:
|
|
return win32_error;
|
|
}
|
|
}
|
|
|
|
struct w32_io*
|
|
fileio_afunix_socket()
|
|
{
|
|
struct w32_io* ret = (struct w32_io*)malloc(sizeof(struct w32_io));
|
|
if (ret == NULL) {
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
memset(ret, 0, sizeof(struct w32_io));
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
fileio_connect(struct w32_io* pio, char* name)
|
|
{
|
|
wchar_t* name_w = NULL;
|
|
HANDLE h = INVALID_HANDLE_VALUE;
|
|
int ret = 0;
|
|
|
|
if (pio->handle != 0 && pio->handle != INVALID_HANDLE_VALUE) {
|
|
debug3("fileio_connect called in unexpected state, pio = %p", pio);
|
|
errno = EOTHER;
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((name_w = utf8_to_utf16(name)) == NULL) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
do {
|
|
h = CreateFileW(name_w, GENERIC_READ | GENERIC_WRITE, 0,
|
|
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, NULL);
|
|
|
|
if (h != INVALID_HANDLE_VALUE)
|
|
break;
|
|
if (GetLastError() != ERROR_PIPE_BUSY)
|
|
break;
|
|
|
|
debug4("waiting for agent connection, retrying after 1 sec");
|
|
if ((ret = wait_for_any_event(NULL, 0, 1000) != 0) != 0)
|
|
goto cleanup;
|
|
} while(1);
|
|
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
debug3("unable to connect to pipe %ls, error: %d", name_w, GetLastError());
|
|
errno = errno_from_Win32LastError();
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (SetHandleInformation(h, HANDLE_FLAG_INHERIT,
|
|
pio->fd_flags & FD_CLOEXEC ? 0 : HANDLE_FLAG_INHERIT) == FALSE) {
|
|
errno = errno_from_Win32LastError();
|
|
debug3("SetHandleInformation failed, error = %d, pio = %p", GetLastError(), pio);
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
pio->handle = h;
|
|
h = INVALID_HANDLE_VALUE;
|
|
|
|
cleanup:
|
|
if (name_w)
|
|
free(name_w);
|
|
if (h != INVALID_HANDLE_VALUE)
|
|
CloseHandle(h);
|
|
return ret;
|
|
}
|
|
|
|
/* used to name named pipes used to implement pipe() */
|
|
static int pipe_counter = 0;
|
|
|
|
/*
|
|
* pipe() (unidirectional) and socketpair() (duplex)
|
|
* implementation. Creates an inbound named pipe, uses CreateFile to connect
|
|
* to it. These handles are associated with read end and write end of the pipe
|
|
*/
|
|
int
|
|
fileio_pipe(struct w32_io* pio[2], int duplex)
|
|
{
|
|
HANDLE read_handle = INVALID_HANDLE_VALUE, write_handle = INVALID_HANDLE_VALUE;
|
|
struct w32_io *pio_read = NULL, *pio_write = NULL;
|
|
char pipe_name[PATH_MAX];
|
|
SECURITY_ATTRIBUTES sec_attributes;
|
|
|
|
if (pio == NULL) {
|
|
errno = EINVAL;
|
|
debug3("pipe - ERROR invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
/* create name for named pipe */
|
|
if (-1 == sprintf_s(pipe_name, PATH_MAX, "\\\\.\\Pipe\\W32PosixPipe.%08x.%08x",
|
|
GetCurrentProcessId(), pipe_counter++)) {
|
|
errno = EOTHER;
|
|
debug3("pipe - ERROR sprintf_s %d", errno);
|
|
goto error;
|
|
}
|
|
|
|
sec_attributes.bInheritHandle = TRUE;
|
|
sec_attributes.lpSecurityDescriptor = NULL;
|
|
sec_attributes.nLength = sizeof(sec_attributes);
|
|
|
|
/* create named pipe */
|
|
write_handle = CreateNamedPipeA(pipe_name,
|
|
(duplex ? PIPE_ACCESS_DUPLEX : PIPE_ACCESS_OUTBOUND ) | FILE_FLAG_OVERLAPPED,
|
|
PIPE_TYPE_BYTE | PIPE_WAIT,
|
|
1,
|
|
4096,
|
|
4096,
|
|
0,
|
|
&sec_attributes);
|
|
if (write_handle == INVALID_HANDLE_VALUE) {
|
|
errno = errno_from_Win32LastError();
|
|
debug3("pipe - CreateNamedPipe() ERROR:%d", errno);
|
|
goto error;
|
|
}
|
|
|
|
/* connect to named pipe */
|
|
read_handle = CreateFileA(pipe_name,
|
|
duplex ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ,
|
|
0,
|
|
&sec_attributes,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
|
|
NULL);
|
|
if (read_handle == INVALID_HANDLE_VALUE) {
|
|
errno = errno_from_Win32LastError();
|
|
debug3("pipe - ERROR CreateFile() :%d", errno);
|
|
goto error;
|
|
}
|
|
|
|
/* create w32_io objects encapsulating above handles */
|
|
pio_read = (struct w32_io*)malloc(sizeof(struct w32_io));
|
|
pio_write = (struct w32_io*)malloc(sizeof(struct w32_io));
|
|
|
|
if (!pio_read || !pio_write) {
|
|
errno = ENOMEM;
|
|
debug3("pip - ERROR:%d", errno);
|
|
goto error;
|
|
}
|
|
|
|
memset(pio_read, 0, sizeof(struct w32_io));
|
|
memset(pio_write, 0, sizeof(struct w32_io));
|
|
|
|
pio_read->handle = read_handle;
|
|
pio_write->handle = write_handle;
|
|
|
|
pio[0] = pio_read;
|
|
pio[1] = pio_write;
|
|
return 0;
|
|
|
|
error:
|
|
if (read_handle)
|
|
CloseHandle(read_handle);
|
|
if (write_handle)
|
|
CloseHandle(write_handle);
|
|
if (pio_read)
|
|
free(pio_read);
|
|
if (pio_write)
|
|
free(pio_write);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
st_mode_to_file_att(int mode, wchar_t * attributes)
|
|
{
|
|
DWORD att = 0;
|
|
switch (mode) {
|
|
case S_IRWXO:
|
|
swprintf_s(attributes, MAX_ATTRIBUTE_LENGTH, L"FA");
|
|
break;
|
|
default:
|
|
if((mode & S_IROTH) != 0)
|
|
att |= (FILE_GENERIC_READ | FILE_EXECUTE);
|
|
if ((mode & S_IWOTH) != 0)
|
|
att |= (FILE_GENERIC_WRITE | DELETE);
|
|
if ((mode & S_IXOTH) != 0)
|
|
att |= FILE_GENERIC_EXECUTE;
|
|
swprintf_s(attributes, MAX_ATTRIBUTE_LENGTH, L"%#lx", att);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* maps open() file modes and flags to ones needed by CreateFile */
|
|
static int
|
|
createFile_flags_setup(int flags, mode_t mode, struct createFile_flags* cf_flags)
|
|
{
|
|
/* check flags */
|
|
int rwflags = flags & 0x3, c_s_flags = flags & 0xfffffffc, ret = -1;
|
|
PSECURITY_DESCRIPTOR pSD = NULL;
|
|
wchar_t sddl[SDDL_LENGTH + 1] = { 0 }, owner_ace[MAX_ACE_LENGTH + 1] = {0}, everyone_ace[MAX_ACE_LENGTH + 1] = {0};
|
|
wchar_t owner_access[MAX_ATTRIBUTE_LENGTH + 1] = {0}, everyone_access[MAX_ATTRIBUTE_LENGTH + 1] = {0}, *sid_utf16 = NULL;
|
|
PACL dacl = NULL;
|
|
PSID owner_sid = NULL;
|
|
|
|
/*
|
|
* should be one of one of the following access modes:
|
|
* O_RDONLY, O_WRONLY, or O_RDWR
|
|
*/
|
|
if ((rwflags != O_RDONLY) && (rwflags != O_WRONLY) && (rwflags != O_RDWR)) {
|
|
debug3("open - flags ERROR: wrong rw flags: %d", flags);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
/*only following create and status flags currently supported*/
|
|
if (c_s_flags & ~(O_NONBLOCK | O_APPEND | O_CREAT | O_TRUNC | O_EXCL | O_BINARY)) {
|
|
debug3("open - ERROR: Unsupported flags: %d", flags);
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
|
|
cf_flags->dwShareMode = 0;
|
|
|
|
switch (rwflags) {
|
|
case O_RDONLY:
|
|
cf_flags->dwDesiredAccess = GENERIC_READ;
|
|
/* refer to https://msdn.microsoft.com/en-us/library/windows/desktop/aa363874(v=vs.85).aspx */
|
|
cf_flags->dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
|
|
break;
|
|
case O_WRONLY:
|
|
cf_flags->dwDesiredAccess = GENERIC_WRITE;
|
|
cf_flags->dwShareMode = FILE_SHARE_WRITE;
|
|
break;
|
|
case O_RDWR:
|
|
cf_flags->dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
|
|
break;
|
|
}
|
|
cf_flags->dwCreationDisposition = OPEN_EXISTING;
|
|
if (c_s_flags & O_TRUNC)
|
|
cf_flags->dwCreationDisposition = TRUNCATE_EXISTING;
|
|
if (c_s_flags & O_CREAT) {
|
|
if (c_s_flags & O_EXCL)
|
|
cf_flags->dwCreationDisposition = CREATE_NEW;
|
|
else if (c_s_flags & O_TRUNC)
|
|
cf_flags->dwCreationDisposition = CREATE_ALWAYS;
|
|
else
|
|
cf_flags->dwCreationDisposition = OPEN_ALWAYS;
|
|
}
|
|
|
|
if (c_s_flags & O_APPEND)
|
|
cf_flags->dwDesiredAccess = FILE_APPEND_DATA;
|
|
|
|
cf_flags->dwFlagsAndAttributes = FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS;
|
|
|
|
// If the mode is USHRT_MAX then we will inherit the permissions from the parent folder.
|
|
if (mode != USHRT_MAX) {
|
|
/*validate mode*/
|
|
/*
|
|
* __S_IFDIR __S_IFREG are added for compat
|
|
* TODO- open(__S_IFDIR) on a file and vice versa should fail
|
|
*/
|
|
if (mode & ~(S_IRWXU | S_IRWXG | S_IRWXO | __S_IFDIR | __S_IFREG)) {
|
|
debug3("open - ERROR: unsupported mode: %d", mode);
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
|
|
if ((owner_sid = get_sid(NULL)) == NULL || (!ConvertSidToStringSidW(owner_sid, &sid_utf16))) {
|
|
debug3("cannot retrieve current user's SID");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!IsWellKnownSid(owner_sid, WinLocalSystemSid) && ((mode & S_IRWXU) != 0)) {
|
|
if (st_mode_to_file_att((mode & S_IRWXU) >> 6, owner_access) != 0) {
|
|
debug3("st_mode_to_file_att()");
|
|
goto cleanup;
|
|
}
|
|
swprintf_s(owner_ace, MAX_ACE_LENGTH, L"(A;;%s;;;%s)", owner_access, sid_utf16);
|
|
}
|
|
|
|
if (mode & S_IRWXO) {
|
|
if (st_mode_to_file_att(mode & S_IRWXO, everyone_access) != 0) {
|
|
debug3("st_mode_to_file_att()");
|
|
goto cleanup;
|
|
}
|
|
swprintf_s(everyone_ace, MAX_ACE_LENGTH, L"(A;;%s;;;WD)", everyone_access);
|
|
}
|
|
|
|
swprintf_s(sddl, SDDL_LENGTH, L"O:%sD:PAI(A;;FA;;;BA)(A;;FA;;;SY)%s%s", sid_utf16, owner_ace, everyone_ace);
|
|
if (ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl, SDDL_REVISION, &pSD, NULL) == FALSE) {
|
|
debug3("ConvertStringSecurityDescriptorToSecurityDescriptorW failed with error code %d", GetLastError());
|
|
goto cleanup;
|
|
}
|
|
|
|
if (IsValidSecurityDescriptor(pSD) == FALSE) {
|
|
debug3("IsValidSecurityDescriptor return FALSE");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
cf_flags->securityAttributes.lpSecurityDescriptor = pSD;
|
|
cf_flags->securityAttributes.bInheritHandle = TRUE;
|
|
cf_flags->securityAttributes.nLength = sizeof(cf_flags->securityAttributes);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (owner_sid)
|
|
free(owner_sid);
|
|
|
|
if (sid_utf16)
|
|
LocalFree(sid_utf16);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* returns 1 if true, 0 otherwise */
|
|
int
|
|
file_in_chroot_jail(HANDLE handle) {
|
|
/* ensure final path is within chroot */
|
|
wchar_t *final_path;
|
|
|
|
final_path = get_final_path_by_handle(handle);
|
|
if (!final_path)
|
|
return 0;
|
|
|
|
to_wlower_case(final_path);
|
|
if ((wcslen(final_path) < wcslen(chroot_pathw)) ||
|
|
memcmp(final_path, chroot_pathw, 2 * wcslen(chroot_pathw)) != 0 ||
|
|
final_path[wcslen(chroot_pathw)] != '\\') {
|
|
debug3("access denied due to attempt to escape chroot jail");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* open() implementation. Uses CreateFile to open file, console, device, etc */
|
|
struct w32_io*
|
|
fileio_open(const char *path_utf8, int flags, mode_t mode)
|
|
{
|
|
struct w32_io* pio = NULL;
|
|
struct createFile_flags cf_flags;
|
|
HANDLE handle = INVALID_HANDLE_VALUE;
|
|
wchar_t *path_utf16 = NULL;
|
|
int nonfs_dev = 0; /* opening a non file system device */
|
|
|
|
debug4("open - pathname:%s, flags:%d, mode:%d", path_utf8, flags, mode);
|
|
/* check input params*/
|
|
if (path_utf8 == NULL) {
|
|
errno = EINVAL;
|
|
debug3("open - ERROR:%d", errno);
|
|
return NULL;
|
|
}
|
|
|
|
/* if opening null device, point to Windows equivalent */
|
|
if (strncmp(path_utf8, NULL_DEVICE, sizeof(NULL_DEVICE)) == 0
|
|
|| strncmp(path_utf8, NULL_DEVICE_WIN, sizeof(NULL_DEVICE_WIN)) == 0) {
|
|
nonfs_dev = 1;
|
|
path_utf16 = utf8_to_utf16(NULL_DEVICE_WIN);
|
|
}
|
|
else
|
|
path_utf16 = resolved_path_utf16(path_utf8);
|
|
|
|
if (path_utf16 == NULL)
|
|
return NULL;
|
|
|
|
if (createFile_flags_setup(flags, mode, &cf_flags) == -1) {
|
|
debug3("createFile_flags_setup() failed.");
|
|
goto cleanup;
|
|
}
|
|
|
|
handle = CreateFileW(path_utf16, cf_flags.dwDesiredAccess, cf_flags.dwShareMode,
|
|
&cf_flags.securityAttributes, cf_flags.dwCreationDisposition,
|
|
cf_flags.dwFlagsAndAttributes, NULL);
|
|
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
errno = errno_from_Win32LastError();
|
|
debug3("failed to open file:%S error:%d", path_utf16, GetLastError());
|
|
goto cleanup;
|
|
}
|
|
|
|
if (chroot_pathw && !nonfs_dev && !file_in_chroot_jail(handle)) {
|
|
debug3("%s is not in chroot jail", path_utf8);
|
|
errno = EACCES;
|
|
goto cleanup;
|
|
}
|
|
|
|
pio = (struct w32_io*)malloc(sizeof(struct w32_io));
|
|
if (pio == NULL) {
|
|
CloseHandle(handle);
|
|
errno = ENOMEM;
|
|
debug3("fileio_open(), failed to allocate memory error:%d", errno);
|
|
goto cleanup;
|
|
}
|
|
|
|
memset(pio, 0, sizeof(struct w32_io));
|
|
|
|
if (flags & O_NONBLOCK)
|
|
pio->fd_status_flags = O_NONBLOCK;
|
|
|
|
pio->handle = handle;
|
|
handle = INVALID_HANDLE_VALUE;
|
|
|
|
cleanup:
|
|
if ((&cf_flags.securityAttributes != NULL) && (&cf_flags.securityAttributes.lpSecurityDescriptor != NULL))
|
|
LocalFree(cf_flags.securityAttributes.lpSecurityDescriptor);
|
|
if(path_utf16)
|
|
free(path_utf16);
|
|
if (handle != INVALID_HANDLE_VALUE)
|
|
CloseHandle(handle);
|
|
|
|
return pio;
|
|
}
|
|
|
|
VOID CALLBACK
|
|
ReadCompletionRoutine(_In_ DWORD dwErrorCode, _In_ DWORD dwNumberOfBytesTransfered, _Inout_ LPOVERLAPPED lpOverlapped)
|
|
{
|
|
struct w32_io* pio = (struct w32_io*)((char*)lpOverlapped - offsetof(struct w32_io, read_overlapped));
|
|
debug4("ReadCB pio:%p, pending_state:%d, error:%d, received:%d",
|
|
pio, pio->read_details.pending, dwErrorCode, dwNumberOfBytesTransfered);
|
|
pio->read_details.error = dwErrorCode;
|
|
pio->read_details.remaining = dwNumberOfBytesTransfered;
|
|
pio->read_details.completed = 0;
|
|
pio->read_details.pending = FALSE;
|
|
*((__int64*)&lpOverlapped->Offset) += dwNumberOfBytesTransfered;
|
|
}
|
|
|
|
/* initiate an async read */
|
|
/* TODO: make this a void func, store error in context */
|
|
int
|
|
fileio_ReadFileEx(struct w32_io* pio, unsigned int bytes_requested)
|
|
{
|
|
debug4("ReadFileEx io:%p", pio);
|
|
|
|
if (pio->read_details.buf == NULL) {
|
|
pio->read_details.buf = malloc(READ_BUFFER_SIZE);
|
|
if (!pio->read_details.buf) {
|
|
errno = ENOMEM;
|
|
debug4("ReadFileEx - ERROR: %d, io:%p", errno, pio);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (FILETYPE(pio) == FILE_TYPE_DISK)
|
|
pio->read_details.buf_size = min(bytes_requested, READ_BUFFER_SIZE);
|
|
else
|
|
pio->read_details.buf_size = READ_BUFFER_SIZE;
|
|
|
|
if (ReadFileEx(WINHANDLE(pio), pio->read_details.buf, pio->read_details.buf_size,
|
|
&pio->read_overlapped, &ReadCompletionRoutine))
|
|
pio->read_details.pending = TRUE;
|
|
else {
|
|
errno = errno_from_Win32LastError();
|
|
debug3("ReadFileEx() ERROR:%d, io:%p", GetLastError(), pio);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* read() implementation */
|
|
int
|
|
fileio_read(struct w32_io* pio, void *dst, size_t max_bytes)
|
|
{
|
|
int bytes_copied;
|
|
errno_t r = 0;
|
|
|
|
debug5("read - io:%p remaining:%d", pio, pio->read_details.remaining);
|
|
|
|
/* if read is pending */
|
|
if (pio->read_details.pending) {
|
|
if (w32_io_is_blocking(pio)) {
|
|
debug4("read - io is pending, blocking call made, io:%p", pio);
|
|
while (fileio_is_io_available(pio, TRUE) == FALSE) {
|
|
if (-1 == wait_for_any_event(NULL, 0, INFINITE))
|
|
return -1;
|
|
}
|
|
}
|
|
errno = EAGAIN;
|
|
debug4("read - io is already pending, io:%p", pio);
|
|
return -1;
|
|
}
|
|
|
|
if (fileio_is_io_available(pio, TRUE) == FALSE) {
|
|
if (pio->type == NONSOCK_SYNC_FD || FILETYPE(pio) == FILE_TYPE_CHAR) {
|
|
if (-1 == syncio_initiate_read(pio))
|
|
return -1;
|
|
} else {
|
|
if (-1 == fileio_ReadFileEx(pio, (int)max_bytes)) {
|
|
if ((FILETYPE(pio) == FILE_TYPE_PIPE)
|
|
&& (errno == ERROR_BROKEN_PIPE)) {
|
|
/* write end of the pipe closed */
|
|
debug3("read - no more data, io:%p", pio);
|
|
errno = 0;
|
|
return 0;
|
|
}
|
|
/* on W2012, ReadFileEx on file throws a synchronous EOF error*/
|
|
else if ((FILETYPE(pio) == FILE_TYPE_DISK)
|
|
&& (errno == ERROR_HANDLE_EOF)) {
|
|
debug3("read - no more data, io:%p", pio);
|
|
errno = 0;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* pick up APC if IO has completed */
|
|
SleepEx(0, TRUE);
|
|
|
|
if (w32_io_is_blocking(pio)) {
|
|
while (fileio_is_io_available(pio, TRUE) == FALSE) {
|
|
if (-1 == wait_for_any_event(NULL, 0, INFINITE))
|
|
return -1;
|
|
}
|
|
}
|
|
else if (pio->read_details.pending) {
|
|
errno = EAGAIN;
|
|
debug4("read - IO is pending, io:%p", pio);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (pio->read_details.error) {
|
|
errno = errno_from_Win32Error(pio->read_details.error);
|
|
/*write end of the pipe is closed or pipe broken or eof reached*/
|
|
if ((pio->read_details.error == ERROR_BROKEN_PIPE) ||
|
|
(pio->read_details.error == ERROR_HANDLE_EOF)) {
|
|
debug4("read - (2) no more data, io:%p", pio);
|
|
errno = 0;
|
|
pio->read_details.error = 0;
|
|
return 0;
|
|
}
|
|
debug3("read - ERROR from cb :%d, io:%p", errno, pio);
|
|
pio->read_details.error = 0;
|
|
return -1;
|
|
}
|
|
|
|
bytes_copied = min((DWORD)max_bytes, pio->read_details.remaining);
|
|
if ((r = memcpy_s(dst, max_bytes, pio->read_details.buf + pio->read_details.completed, bytes_copied)) != 0) {
|
|
debug3("memcpy_s failed with error: %d.", r);
|
|
return -1;
|
|
}
|
|
pio->read_details.remaining -= bytes_copied;
|
|
pio->read_details.completed += bytes_copied;
|
|
debug4("read - io:%p read: %d remaining: %d", pio, bytes_copied,
|
|
pio->read_details.remaining);
|
|
return bytes_copied;
|
|
}
|
|
|
|
VOID CALLBACK
|
|
WriteCompletionRoutine(_In_ DWORD dwErrorCode,
|
|
_In_ DWORD dwNumberOfBytesTransfered,
|
|
_Inout_ LPOVERLAPPED lpOverlapped)
|
|
{
|
|
struct w32_io* pio =
|
|
(struct w32_io*)((char*)lpOverlapped - offsetof(struct w32_io, write_overlapped));
|
|
debug4("WriteCB - pio:%p, pending_state:%d, error:%d, transferred:%d of remaining: %d",
|
|
pio, pio->write_details.pending, dwErrorCode, dwNumberOfBytesTransfered,
|
|
pio->write_details.remaining);
|
|
pio->write_details.error = dwErrorCode;
|
|
/* TODO - assert that remaining == dwNumberOfBytesTransfered */
|
|
if ((dwErrorCode == 0) && (pio->write_details.remaining != dwNumberOfBytesTransfered)) {
|
|
error("WriteCB - ERROR: broken assumption, io:%p, wrote:%d, remaining:%d", pio,
|
|
dwNumberOfBytesTransfered, pio->write_details.remaining);
|
|
debug_assert_internal();
|
|
}
|
|
pio->write_details.remaining -= dwNumberOfBytesTransfered;
|
|
pio->write_details.pending = FALSE;
|
|
*((__int64*)&lpOverlapped->Offset) += dwNumberOfBytesTransfered;
|
|
}
|
|
|
|
int
|
|
fileio_write_wrapper(struct w32_io* pio, const void* buf, size_t bytes_to_copy)
|
|
{
|
|
int bytes_written = 0;
|
|
if (bytes_to_copy <= WRITE_BUFFER_SIZE) {
|
|
bytes_written = fileio_write(pio, buf, bytes_to_copy);
|
|
return bytes_written;
|
|
}
|
|
|
|
void* chunk_buf = NULL;
|
|
int chunk_count = 0;
|
|
int bytes_copied = -1;
|
|
int chunk_size = 0;
|
|
|
|
for (int i = 0; i < bytes_to_copy; i += WRITE_BUFFER_SIZE, chunk_count++) {
|
|
chunk_buf = (BYTE*)buf + chunk_count * WRITE_BUFFER_SIZE;
|
|
chunk_size = ((bytes_to_copy - i) >= WRITE_BUFFER_SIZE) ? WRITE_BUFFER_SIZE : (bytes_to_copy - i);
|
|
bytes_written = fileio_write(pio, chunk_buf, chunk_size);
|
|
|
|
if (bytes_written == -1)
|
|
return bytes_copied;
|
|
|
|
if (bytes_copied == -1)
|
|
bytes_copied = 0;
|
|
|
|
bytes_copied += bytes_written;
|
|
}
|
|
return bytes_copied;
|
|
|
|
}
|
|
|
|
/* write() implementation */
|
|
int
|
|
fileio_write(struct w32_io* pio, const void *buf, size_t max_bytes)
|
|
{
|
|
int bytes_copied;
|
|
DWORD pipe_flags = 0, pipe_instances = 0;
|
|
errno_t r = 0;
|
|
|
|
debug4("write - io:%p", pio);
|
|
if (pio->write_details.pending) {
|
|
if (w32_io_is_blocking(pio)) {
|
|
debug4("write - io pending, blocking call made, io:%p", pio);
|
|
while (pio->write_details.pending)
|
|
if (wait_for_any_event(NULL, 0, INFINITE) == -1)
|
|
return -1;
|
|
} else {
|
|
errno = EAGAIN;
|
|
debug4("write - IO is already pending, io:%p", pio);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (pio->write_details.error) {
|
|
errno = errno_from_Win32Error(pio->write_details.error);
|
|
debug3("write - ERROR:%d on prior unblocking write, io:%p", errno, pio);
|
|
pio->write_details.error = 0;
|
|
if ((FILETYPE(pio) == FILE_TYPE_PIPE) && (errno == ERROR_BROKEN_PIPE)) {
|
|
debug4("write - ERROR:read end of the pipe closed, io:%p", pio);
|
|
errno = EPIPE;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (pio->write_details.buf == NULL) {
|
|
pio->write_details.buf = malloc(WRITE_BUFFER_SIZE);
|
|
if (pio->write_details.buf == NULL) {
|
|
errno = ENOMEM;
|
|
debug3("write - ERROR:%d, io:%p", errno, pio);
|
|
return -1;
|
|
}
|
|
pio->write_details.buf_size = WRITE_BUFFER_SIZE;
|
|
}
|
|
|
|
bytes_copied = min((int)max_bytes, pio->write_details.buf_size);
|
|
if((r = memcpy_s(pio->write_details.buf, max_bytes, buf, bytes_copied)) != 0) {
|
|
debug3("memcpy_s failed with error: %d.", r);
|
|
return -1;
|
|
}
|
|
|
|
if (pio->type == NONSOCK_SYNC_FD || FILETYPE(pio) == FILE_TYPE_CHAR) {
|
|
if (syncio_initiate_write(pio, bytes_copied) == 0) {
|
|
pio->write_details.pending = TRUE;
|
|
pio->write_details.remaining = bytes_copied;
|
|
} else
|
|
return -1;
|
|
} else {
|
|
if (WriteFileEx(WINHANDLE(pio), pio->write_details.buf, bytes_copied,
|
|
&pio->write_overlapped, &WriteCompletionRoutine)) {
|
|
pio->write_details.pending = TRUE;
|
|
pio->write_details.remaining = bytes_copied;
|
|
} else {
|
|
errno = errno_from_Win32LastError();
|
|
/* read end of the pipe closed ? */
|
|
if ((FILETYPE(pio) == FILE_TYPE_PIPE) && (errno == ERROR_BROKEN_PIPE)) {
|
|
debug3("write - ERROR:read end of the pipe closed, io:%p", pio);
|
|
errno = EPIPE;
|
|
}
|
|
debug3("write ERROR from cb(2):%d, io:%p", errno, pio);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (w32_io_is_blocking(pio)) {
|
|
while (pio->write_details.pending) {
|
|
if (wait_for_any_event(NULL, 0, INFINITE) == -1) {
|
|
/* if interrupted but write has completed, we are good*/
|
|
if ((errno != EINTR) || (pio->write_details.pending))
|
|
return -1;
|
|
errno = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* execute APC to give a chance for write to complete */
|
|
SleepEx(0, TRUE);
|
|
|
|
/* if write has completed, pick up any error reported*/
|
|
if (!pio->write_details.pending && pio->write_details.error) {
|
|
errno = errno_from_Win32Error(pio->write_details.error);
|
|
debug3("write - ERROR from cb:%d, io:%p", pio->write_details.error, pio);
|
|
pio->write_details.error = 0;
|
|
return -1;
|
|
}
|
|
debug4("write - reporting %d bytes written, io:%p", bytes_copied, pio);
|
|
return bytes_copied;
|
|
}
|
|
|
|
/* fstat() implemetation */
|
|
int
|
|
fileio_fstat(struct w32_io* pio, struct _stat64 *buf)
|
|
{
|
|
HANDLE dup_handle = 0;
|
|
if (!DuplicateHandle(GetCurrentProcess(), pio->handle, GetCurrentProcess(), &dup_handle, 0,
|
|
TRUE, DUPLICATE_SAME_ACCESS)) {
|
|
errno = EOTHER;
|
|
return -1;
|
|
}
|
|
|
|
int fd = _open_osfhandle((intptr_t)dup_handle, 0);
|
|
debug4("fstat - pio:%p", pio);
|
|
if (fd == -1) {
|
|
CloseHandle(dup_handle);
|
|
errno = EOTHER;
|
|
return -1;
|
|
}
|
|
|
|
int res = _fstat64(fd, buf);
|
|
_close(fd);
|
|
return res;
|
|
}
|
|
|
|
int
|
|
fileio_stat_or_lstat_internal(const char *path, struct _stat64 *buf, int do_lstat)
|
|
{
|
|
wchar_t *wpath = NULL;
|
|
char link_test = L'\0';
|
|
HANDLE link_handle = INVALID_HANDLE_VALUE;
|
|
WIN32_FILE_ATTRIBUTE_DATA attributes = { 0 };
|
|
int ret = -1;
|
|
int is_link = 0;
|
|
|
|
memset(buf, 0, sizeof(struct _stat64));
|
|
|
|
/* Detect root dir */
|
|
if (path && strcmp(path, "/") == 0) {
|
|
buf->st_mode = _S_IFDIR | _S_IREAD | 0xFF;
|
|
buf->st_dev = USHRT_MAX; // rootdir flag
|
|
return 0;
|
|
}
|
|
|
|
if ((wpath = resolved_path_utf16(path)) == NULL)
|
|
return -1;
|
|
|
|
/* get the file attributes (or symlink attributes if symlink) */
|
|
if (GetFileAttributesExW(wpath, GetFileExInfoStandard, &attributes) == FALSE) {
|
|
errno = errno_from_Win32LastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
/* try to see if it is a symlink */
|
|
is_link = (attributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
|
|
fileio_readlink(path, &link_test, 1) == 1);
|
|
|
|
/* if doing a stat() on a link, then lookup attributes on the target of the link */
|
|
if (!do_lstat && is_link) {
|
|
|
|
/* obtain a file handle to the destination file (not the source link) */
|
|
BY_HANDLE_FILE_INFORMATION link_attributes;
|
|
if ((link_handle = CreateFileW(wpath, 0, 0, NULL, OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS, NULL)) == INVALID_HANDLE_VALUE
|
|
|| GetFileInformationByHandle(link_handle, &link_attributes) == 0)
|
|
{
|
|
errno = errno_from_Win32LastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
/* copy attributes from handle structure to normal structure */
|
|
attributes.ftCreationTime = link_attributes.ftCreationTime;
|
|
attributes.ftLastAccessTime = link_attributes.ftLastAccessTime;
|
|
attributes.ftLastWriteTime = link_attributes.ftLastWriteTime;
|
|
attributes.nFileSizeHigh = link_attributes.nFileSizeHigh;
|
|
attributes.nFileSizeLow = link_attributes.nFileSizeLow;
|
|
attributes.dwFileAttributes = link_attributes.dwFileAttributes;
|
|
is_link = 0;
|
|
}
|
|
|
|
buf->st_ino = 0; /* Has no meaning in the FAT, HPFS, or NTFS file systems*/
|
|
buf->st_gid = 0; /* UNIX - specific; has no meaning on windows */
|
|
buf->st_uid = 0; /* UNIX - specific; has no meaning on windows */
|
|
buf->st_nlink = 1; /* number of hard links. Always 1 on non - NTFS file systems.*/
|
|
buf->st_mode |= file_attr_to_st_mode(wpath, attributes.dwFileAttributes);
|
|
buf->st_size = attributes.nFileSizeLow | (((off_t)attributes.nFileSizeHigh) << 32);
|
|
if (wcslen(wpath) > 1 && __ascii_iswalpha(*wpath) && (*(wpath + 1) == ':'))
|
|
buf->st_dev = buf->st_rdev = towupper(*wpath) - L'A'; /* drive num */
|
|
else
|
|
buf->st_dev = buf->st_rdev = _getdrive() - 1;
|
|
file_time_to_unix_time(&(attributes.ftLastAccessTime), &(buf->st_atime));
|
|
file_time_to_unix_time(&(attributes.ftLastWriteTime), &(buf->st_mtime));
|
|
file_time_to_unix_time(&(attributes.ftCreationTime), &(buf->st_ctime));
|
|
|
|
/* link type supercedes other file type bits */
|
|
if (is_link) {
|
|
buf->st_mode &= ~S_IFMT;
|
|
buf->st_mode |= S_IFLNK;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (link_handle != INVALID_HANDLE_VALUE)
|
|
CloseHandle(link_handle);
|
|
if (wpath)
|
|
free(wpath);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
fileio_stat(const char *path, struct _stat64 *buf)
|
|
{
|
|
return fileio_stat_or_lstat_internal(path, buf, 0);
|
|
}
|
|
|
|
int
|
|
fileio_lstat(const char *path, struct _stat64 *buf)
|
|
{
|
|
return fileio_stat_or_lstat_internal(path, buf, 1);
|
|
}
|
|
|
|
long
|
|
fileio_lseek(struct w32_io* pio, unsigned __int64 offset, int origin)
|
|
{
|
|
debug4("lseek - pio:%p", pio);
|
|
if (origin != SEEK_SET) {
|
|
debug3("lseek - ERROR, origin is not supported %d", origin);
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
|
|
pio->write_overlapped.Offset = pio->read_overlapped.Offset = offset & 0xffffffff;
|
|
pio->write_overlapped.OffsetHigh = pio->read_overlapped.OffsetHigh = (offset & 0xffffffff00000000) >> 32;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* fdopen() to be used on pipe handles */
|
|
static FILE*
|
|
fileio_fdopen_pipe(struct w32_io* pio, const char *mode)
|
|
{
|
|
int fd_flags = 0;
|
|
FILE* ret;
|
|
debug4("fdopen - io:%p", pio);
|
|
|
|
if (mode[1] == '\0') {
|
|
switch (*mode) {
|
|
case 'r':
|
|
fd_flags = _O_RDONLY;
|
|
break;
|
|
case 'w':
|
|
break;
|
|
case 'a':
|
|
fd_flags = _O_APPEND;
|
|
break;
|
|
default:
|
|
errno = ENOTSUP;
|
|
debug3("fdopen - ERROR unsupported mode %s", mode);
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
errno = ENOTSUP;
|
|
debug3("fdopen - ERROR unsupported mode %s", mode);
|
|
return NULL;
|
|
}
|
|
|
|
int fd = _open_osfhandle((intptr_t)pio->handle, fd_flags);
|
|
|
|
if (fd == -1 || (ret = _fdopen(fd, mode)) == NULL) {
|
|
errno = EOTHER;
|
|
debug3("fdopen - ERROR:%d _open_osfhandle()", errno);
|
|
return NULL;
|
|
}
|
|
|
|
// overwrite underlying win32 handle - its expected to be closed via fclose
|
|
// and close pio
|
|
pio->handle = NULL;
|
|
int w32_close(int);
|
|
w32_close(pio->table_index);
|
|
return ret;
|
|
}
|
|
|
|
/* fdopen() to be used on file handles */
|
|
static FILE*
|
|
fileio_fdopen_disk(struct w32_io* pio, const char *mode)
|
|
{
|
|
wchar_t *file_path, *wmode = NULL;
|
|
FILE* ret = NULL;
|
|
|
|
debug4("fdopen - io:%p", pio);
|
|
|
|
if ((wmode = utf8_to_utf16(mode)) == NULL)
|
|
goto cleanup;
|
|
|
|
file_path = get_final_path_by_handle(pio->handle);
|
|
if (!file_path)
|
|
goto cleanup;
|
|
|
|
/*
|
|
* close the win32 handle right away and remove entry from table
|
|
* otherwise, wfopen will get an access denied due to sharing violation
|
|
*/
|
|
int w32_close(int);
|
|
w32_close(pio->table_index);
|
|
errno = _wfopen_s(&ret, file_path, wmode);
|
|
|
|
cleanup:
|
|
if (wmode)
|
|
free(wmode);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* fdopen implementation - use with caution
|
|
* this implementation deviates from POSIX spec the following way
|
|
* - the underlying file descriptor is closed automatically
|
|
* hence no further POSIX io operations (read, write, close, etc) on the
|
|
* underlying file descriptor are supported
|
|
*/
|
|
FILE*
|
|
fileio_fdopen(struct w32_io* pio, const char *mode)
|
|
{
|
|
DWORD type = 0;
|
|
|
|
debug4("fdopen - io:%p", pio);
|
|
|
|
type = GetFileType(pio->handle);
|
|
if (type == FILE_TYPE_DISK) {
|
|
return fileio_fdopen_disk(pio, mode);
|
|
} else if (type == FILE_TYPE_PIPE) {
|
|
return fileio_fdopen_pipe(pio, mode);
|
|
} else {
|
|
errno = ENOTSUP;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
fileio_on_select(struct w32_io* pio, BOOL rd)
|
|
{
|
|
if (!rd)
|
|
return;
|
|
|
|
if (!pio->read_details.pending && !fileio_is_io_available(pio, rd))
|
|
/* initiate read, record any error so read() will pick up */
|
|
if (pio->type == NONSOCK_SYNC_FD || FILETYPE(pio) == FILE_TYPE_CHAR) {
|
|
if (syncio_initiate_read(pio) != 0) {
|
|
pio->read_details.error = errno;
|
|
errno = 0;
|
|
return;
|
|
}
|
|
} else {
|
|
if (fileio_ReadFileEx(pio, INT_MAX) != 0) {
|
|
pio->read_details.error = errno;
|
|
errno = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
fileio_close(struct w32_io* pio)
|
|
{
|
|
debug4("fileclose - pio:%p", pio);
|
|
|
|
if (pio->type == NONSOCK_SYNC_FD || FILETYPE(pio) == FILE_TYPE_CHAR)
|
|
return syncio_close(pio);
|
|
|
|
/* handle can be null on AF_UNIX sockets that are not yet connected */
|
|
if (WINHANDLE(pio) == 0 || WINHANDLE(pio) == INVALID_HANDLE_VALUE) {
|
|
free(pio);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* we report to POSIX app that an async write has completed as soon its
|
|
* copied to internal buffer. The app may subsequently try to close the
|
|
* fd thinking everything is written. IF the Windows handle is closed
|
|
* now, the pipe/file io write operation may terminate prematurely.
|
|
* To compensate for the discrepency
|
|
* wait here until async write has completed.
|
|
* If you see any process waiting here indefinitely - its because no one
|
|
* is draining from other end of the pipe/file. This is an unfortunate
|
|
* consequence that should otherwise have very little impact on practical
|
|
* scenarios.
|
|
*/
|
|
while (pio->write_details.pending)
|
|
if (0 != wait_for_any_event(NULL, 0, INFINITE))
|
|
return -1;
|
|
|
|
CancelIo(WINHANDLE(pio));
|
|
/* let queued APCs (if any) drain */
|
|
SleepEx(0, TRUE);
|
|
CloseHandle(WINHANDLE(pio));
|
|
if (pio->read_details.buf)
|
|
free(pio->read_details.buf);
|
|
if (pio->write_details.buf)
|
|
free(pio->write_details.buf);
|
|
free(pio);
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL
|
|
fileio_is_io_available(struct w32_io* pio, BOOL rd)
|
|
{
|
|
if (rd) {
|
|
if (pio->read_details.remaining || pio->read_details.error)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
} else { /* write */
|
|
return (pio->write_details.pending == FALSE) ? TRUE : FALSE;
|
|
}
|
|
}
|
|
|
|
ssize_t
|
|
fileio_readlink(const char *path, char *buf, size_t bufsiz)
|
|
{
|
|
/* note: there are two approaches for resolving a symlink in Windows:
|
|
*
|
|
* 1) Use CreateFile() to obtain a file handle to the reparse point and
|
|
* send using the DeviceIoControl() call to retrieve the link data from the
|
|
* reparse point.
|
|
* 2) Use CreateFile() to obtain a file handle to the target file followed
|
|
* by a call to GetFinalPathNameByHandle() to get the real path on the
|
|
* file system.
|
|
*
|
|
* This approach uses the first method because the second method does not
|
|
* work on broken link since the target file cannot be opened. It also
|
|
* requires additional I/O to read both the symlink and its target.
|
|
*/
|
|
|
|
/* abbreviated REPARSE_DATA_BUFFER data structure for decoding symlinks;
|
|
* the full definition can be found in ntifs.h within the Windows DDK.
|
|
* we include it here so the DDK does not become prereq to the build.
|
|
* for more info: https://msdn.microsoft.com/en-us/library/cc232006.aspx
|
|
*/
|
|
|
|
typedef struct _REPARSE_DATA_BUFFER_SYMLINK {
|
|
ULONG ReparseTag;
|
|
USHORT ReparseDataLength;
|
|
USHORT Reserved;
|
|
USHORT SubstituteNameOffset;
|
|
USHORT SubstituteNameLength;
|
|
USHORT PrintNameOffset;
|
|
USHORT PrintNameLength;
|
|
ULONG Flags;
|
|
WCHAR PathBuffer[1];
|
|
} REPARSE_DATA_BUFFER_SYMLINK, *PREPARSE_DATA_BUFFER_SYMLINK;
|
|
|
|
/* early declarations for cleanup */
|
|
ssize_t ret = -1;
|
|
wchar_t *wpath = NULL;
|
|
wchar_t *linkpath = NULL;
|
|
char *output = NULL;
|
|
HANDLE handle = INVALID_HANDLE_VALUE;
|
|
PREPARSE_DATA_BUFFER_SYMLINK reparse_buffer = NULL;
|
|
|
|
/* sanity check */
|
|
if (path == NULL || buf == NULL || bufsiz == 0) {
|
|
errno = EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((wpath = resolved_path_utf16(path)) == NULL)
|
|
goto cleanup;
|
|
|
|
/* obtain a handle to send to deviceioctl */
|
|
handle = CreateFileW(wpath, 0, 0, NULL, OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
errno = errno_from_Win32LastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
/* allocate the maximum possible size the reparse buffer size could be */
|
|
reparse_buffer = (PREPARSE_DATA_BUFFER_SYMLINK)malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
if (reparse_buffer == NULL) {
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* send a request to the file system to get the real path */
|
|
DWORD dwBytesReturned = 0;
|
|
if (DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
|
|
(LPVOID) reparse_buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwBytesReturned, 0) == 0) {
|
|
errno = errno_from_Win32LastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
/* ensure file is actually symlink */
|
|
if (reparse_buffer->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
|
|
errno = EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* the symlink structure has a 'Print Name' value that is displayed to the
|
|
* user which is different from the actual value it uses for redirection
|
|
* called the 'Substitute Name'; since the Substitute Name has an odd format
|
|
* that begins with \??\ and it appears that CreateSymbolicLink() always
|
|
* formats the PrintName value consistently we will just use that
|
|
*/
|
|
int symlink_nonnull_size = reparse_buffer->PrintNameLength;
|
|
wchar_t * symlink_nonnull = &reparse_buffer->PathBuffer[reparse_buffer->PrintNameOffset / sizeof(WCHAR)];
|
|
|
|
/* allocate area to hold a null terminated version of the string */
|
|
if ((linkpath = malloc(symlink_nonnull_size + sizeof(wchar_t))) == NULL) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* copy the data out of the reparse buffer and add null terminator */
|
|
memcpy_s(linkpath, symlink_nonnull_size + sizeof(wchar_t), symlink_nonnull, symlink_nonnull_size);
|
|
linkpath[symlink_nonnull_size / sizeof(wchar_t)] = L'\0';
|
|
|
|
/* convert link path to utf8 */
|
|
if ((output = utf16_to_utf8(linkpath)) == NULL) {
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* determine if we need to prepend a forward slash to make this look like
|
|
* an absolute path C:\Path\Target --> /C:/Path/Target
|
|
*/
|
|
int abs_chars = is_absolute_path(output) ? 1 : 0;
|
|
if (abs_chars)
|
|
buf[0] = '/';
|
|
|
|
/* copy link data to output buffer; per specification, truncation is okay */
|
|
convertToForwardslash(output);
|
|
size_t out_size = strlen(output);
|
|
memcpy(buf + abs_chars, output, min(out_size, bufsiz - abs_chars));
|
|
ret = (ssize_t) min(out_size + abs_chars, bufsiz);
|
|
|
|
cleanup:
|
|
|
|
if (linkpath)
|
|
free(linkpath);
|
|
if (reparse_buffer)
|
|
free(reparse_buffer);
|
|
if (handle != INVALID_HANDLE_VALUE)
|
|
CloseHandle(handle);
|
|
if (wpath)
|
|
free(wpath);
|
|
if (output)
|
|
free(output);
|
|
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
int
|
|
fileio_symlink(const char *target, const char *linkpath)
|
|
{
|
|
DWORD ret = -1;
|
|
char target_modified[PATH_MAX] = { 0 };
|
|
char *linkpath_resolved = NULL, *target_resolved = NULL;
|
|
|
|
if (target == NULL || linkpath == NULL) {
|
|
errno = EFAULT;
|
|
return -1;
|
|
}
|
|
|
|
/* First resolve linkpath */
|
|
if (NULL == (linkpath_resolved = resolved_path_utf8(linkpath)))
|
|
goto cleanup;
|
|
|
|
/* Relative targets are relative to the link and not our current directory
|
|
* so attempt to calculate a resolvable path by removing the link file name
|
|
* leaving only the parent path and then append the relative link:
|
|
* C:\Path\Link with Link->SubDir\Target to C:\Path\SubDir\Target
|
|
*/
|
|
if (!is_absolute_path(target)) {
|
|
strcpy_s(target_modified, _countof(target_modified), linkpath_resolved);
|
|
convertToBackslash(target_modified);
|
|
char *tmp = NULL;
|
|
|
|
/* copy the relative target to the end of the link's parent */
|
|
if (tmp = strrchr(target_modified, '\\'))
|
|
strcpy_s(tmp + 1, _countof(target_modified) - (tmp + 1 - target_modified), target);
|
|
else
|
|
strcpy_s(target_modified, _countof(target_modified), target);
|
|
} else {
|
|
/* resolve target */
|
|
if (NULL == (target_resolved = resolved_path_utf8(target)))
|
|
goto cleanup;
|
|
|
|
strcpy_s(target_modified, _countof(target_modified), target_resolved);
|
|
}
|
|
|
|
wchar_t *linkpath_utf16 = resolved_path_utf16(linkpath);
|
|
wchar_t *resolved_target_utf16 = utf8_to_utf16(target_modified);
|
|
if (resolved_target_utf16 == NULL || linkpath_utf16 == NULL) {
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* unlike other platforms, we need to know whether the symbolic link target is
|
|
* a file or a directory. the only way we can confidently do this is to
|
|
* get the attributes of the target. therefore, our symlink() has the
|
|
* limitation of only creating symlink with valid targets
|
|
*/
|
|
WIN32_FILE_ATTRIBUTE_DATA attributes = { 0 };
|
|
if (GetFileAttributesExW(resolved_target_utf16, GetFileExInfoStandard, &attributes) == FALSE) {
|
|
errno = errno_from_Win32LastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
/* use the attribute of the file to determine the proper flag to send */
|
|
DWORD create_flags = (attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
|
|
SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
|
|
|
|
/* symlink creation on earlier versions of windows were a privileged op
|
|
* and then an option was added to create symlink using from an unprivileged
|
|
* context so we try both operations, attempting privileged version first.
|
|
* note: 0x2 = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
|
|
*/
|
|
if (CreateSymbolicLinkW(linkpath_utf16, resolved_target_utf16, create_flags) == 0) {
|
|
if (CreateSymbolicLinkW(linkpath_utf16, resolved_target_utf16, create_flags | 0x2) == 0) {
|
|
errno = errno_from_Win32LastError();
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
|
|
if (linkpath_utf16)
|
|
free(linkpath_utf16);
|
|
|
|
if (resolved_target_utf16)
|
|
free(resolved_target_utf16);
|
|
|
|
if (linkpath_resolved)
|
|
free(linkpath_resolved);
|
|
|
|
if (target_resolved)
|
|
free(target_resolved);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
fileio_link(const char *oldpath, const char *newpath)
|
|
{
|
|
DWORD ret = -1;
|
|
|
|
if (oldpath == NULL || newpath == NULL) {
|
|
errno = EFAULT;
|
|
return -1;
|
|
}
|
|
|
|
wchar_t *oldpath_utf16 = resolved_path_utf16(oldpath);
|
|
wchar_t *newpath_utf16 = resolved_path_utf16(newpath);
|
|
|
|
if (oldpath_utf16 == NULL || newpath_utf16 == NULL)
|
|
goto cleanup;
|
|
|
|
if (CreateHardLinkW(newpath_utf16, oldpath_utf16, NULL) == 0) {
|
|
errno = errno_from_Win32LastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
|
|
if (oldpath_utf16)
|
|
free(oldpath_utf16);
|
|
if (newpath_utf16)
|
|
free(newpath_utf16);
|
|
|
|
return ret;
|
|
}
|