#include "spvm_native.h"
static
const
char
* FILE_NAME =
"Sys/IO/Windows.c"
;
#if defined(_WIN32)
#include <sys/stat.h>
#include <unistd.h>
#include <windows.h>
#include <errno.h>
#include <winbase.h>
#include <fcntl.h>
static
OSVERSIONINFO g_osver = {0, 0, 0, 0, 0,
""
};
typedef
struct
{
USHORT
SubstituteNameOffset;
USHORT
SubstituteNameLength;
USHORT
PrintNameOffset;
USHORT
PrintNameLength;
ULONG
Flags;
WCHAR
PathBuffer[MAX_PATH*3];
} MY_SYMLINK_REPARSE_BUFFER;
typedef
struct
{
USHORT
SubstituteNameOffset;
USHORT
SubstituteNameLength;
USHORT
PrintNameOffset;
USHORT
PrintNameLength;
WCHAR
PathBuffer[MAX_PATH*3];
} MY_MOUNT_POINT_REPARSE_BUFFER;
typedef
struct
{
ULONG
ReparseTag;
USHORT
ReparseDataLength;
USHORT
Reserved;
union
{
MY_SYMLINK_REPARSE_BUFFER SymbolicLinkReparseBuffer;
MY_MOUNT_POINT_REPARSE_BUFFER MountPointReparseBuffer;
struct
{
UCHAR
DataBuffer[1];
} GenericReparseBuffer;
} Data;
} MY_REPARSE_DATA_BUFFER;
#define _S_IFLNK ((unsigned)(_S_IFDIR | _S_IFCHR))
#ifndef EDQUOT /* Not in errno.h but wanted by POSIX.pm */
# define EDQUOT WSAEDQUOT
#endif
#define strEQ(string1, string2) (strcmp(string1, string2) == 0)
#define isSLASH(c) ((c) == '/' || (c) == '\\')
#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY
# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x1
#endif
#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2
#endif
typedef
BOOLEAN
(__stdcall *pCreateSymbolicLinkA_t)(
LPCSTR
,
LPCSTR
,
DWORD
);
static
BOOL
is_symlink(
HANDLE
h) {
MY_REPARSE_DATA_BUFFER linkdata;
const
MY_SYMLINK_REPARSE_BUFFER *
const
sd =
&linkdata.Data.SymbolicLinkReparseBuffer;
DWORD
linkdata_returned;
if
(!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, &linkdata,
sizeof
(linkdata), &linkdata_returned, NULL)) {
return
FALSE;
}
if
(linkdata_returned < offsetof(MY_REPARSE_DATA_BUFFER, Data.SymbolicLinkReparseBuffer.PathBuffer)
|| (linkdata.ReparseTag != IO_REPARSE_TAG_SYMLINK
&& linkdata.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)) {
return
FALSE;
}
return
TRUE;
}
static
BOOL
is_symlink_name(
const
char
*name) {
HANDLE
f = CreateFileA(name, GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
BOOL
result;
if
(f == INVALID_HANDLE_VALUE) {
return
FALSE;
}
result = is_symlink(f);
CloseHandle(f);
return
result;
}
static
int
win32_unlink(
const
char
*filename)
{
int
ret;
DWORD
attrs;
attrs = GetFileAttributesA(filename);
if
(attrs == 0xFFFFFFFF) {
errno
= ENOENT;
return
-1;
}
if
(attrs & FILE_ATTRIBUTE_READONLY) {
(
void
)SetFileAttributesA(filename, attrs & ~FILE_ATTRIBUTE_READONLY);
ret = unlink(filename);
if
(ret == -1)
(
void
)SetFileAttributesA(filename, attrs);
}
else
if
((attrs & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
== (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)
&& is_symlink_name(filename)) {
ret = rmdir(filename);
}
else
{
ret = unlink(filename);
}
return
ret;
}
static
int
win32_rename(
const
char
*oname,
const
char
*newname)
{
char
szOldName[MAX_PATH+1];
BOOL
bResult;
DWORD
dwFlags = MOVEFILE_COPY_ALLOWED;
if
(stricmp(newname, oname))
dwFlags |= MOVEFILE_REPLACE_EXISTING;
strcpy
(szOldName, oname);
bResult = MoveFileExA(szOldName,newname, dwFlags);
if
(!bResult) {
DWORD
err = GetLastError();
switch
(err) {
case
ERROR_BAD_NET_NAME:
case
ERROR_BAD_NETPATH:
case
ERROR_BAD_PATHNAME:
case
ERROR_FILE_NOT_FOUND:
case
ERROR_FILENAME_EXCED_RANGE:
case
ERROR_INVALID_DRIVE:
case
ERROR_NO_MORE_FILES:
case
ERROR_PATH_NOT_FOUND:
errno
= ENOENT;
break
;
case
ERROR_DISK_FULL:
errno
= ENOSPC;
break
;
case
ERROR_NOT_ENOUGH_QUOTA:
errno
= EDQUOT;
break
;
default
:
errno
= EACCES;
break
;
}
return
-1;
}
return
0;
}
static
void
translate_to_errno(
void
)
{
switch
(GetLastError()) {
case
ERROR_BAD_NET_NAME:
case
ERROR_BAD_NETPATH:
case
ERROR_BAD_PATHNAME:
case
ERROR_FILE_NOT_FOUND:
case
ERROR_FILENAME_EXCED_RANGE:
case
ERROR_INVALID_DRIVE:
case
ERROR_PATH_NOT_FOUND:
errno
= ENOENT;
break
;
case
ERROR_ALREADY_EXISTS:
errno
= EEXIST;
break
;
case
ERROR_ACCESS_DENIED:
errno
= EACCES;
break
;
case
ERROR_PRIVILEGE_NOT_HELD:
errno
= EPERM;
break
;
case
ERROR_NOT_SAME_DEVICE:
errno
= EXDEV;
break
;
case
ERROR_DISK_FULL:
errno
= ENOSPC;
break
;
case
ERROR_NOT_ENOUGH_QUOTA:
errno
= EDQUOT;
break
;
default
:
errno
= EINVAL;
break
;
}
}
static
int
do_readlink_handle(
HANDLE
hlink,
char
*buf,
size_t
bufsiz,
BOOL
*is_symlink) {
MY_REPARSE_DATA_BUFFER linkdata;
DWORD
linkdata_returned;
if
(is_symlink)
*is_symlink = FALSE;
if
(!DeviceIoControl(hlink, FSCTL_GET_REPARSE_POINT, NULL, 0, &linkdata,
sizeof
(linkdata), &linkdata_returned, NULL)) {
translate_to_errno();
return
-1;
}
int
bytes_out;
BOOL
used_default;
switch
(linkdata.ReparseTag) {
case
IO_REPARSE_TAG_SYMLINK:
{
const
MY_SYMLINK_REPARSE_BUFFER *
const
sd =
&linkdata.Data.SymbolicLinkReparseBuffer;
if
(linkdata_returned < offsetof(MY_REPARSE_DATA_BUFFER, Data.SymbolicLinkReparseBuffer.PathBuffer)) {
errno
= EINVAL;
return
-1;
}
bytes_out =
WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS,
sd->PathBuffer + sd->PrintNameOffset/2,
sd->PrintNameLength/2,
buf, (
int
)bufsiz, NULL, &used_default);
if
(is_symlink)
*is_symlink = TRUE;
}
break
;
case
IO_REPARSE_TAG_MOUNT_POINT:
{
const
MY_MOUNT_POINT_REPARSE_BUFFER *
const
rd =
&linkdata.Data.MountPointReparseBuffer;
if
(linkdata_returned < offsetof(MY_REPARSE_DATA_BUFFER, Data.MountPointReparseBuffer.PathBuffer)) {
errno
= EINVAL;
return
-1;
}
bytes_out =
WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS,
rd->PathBuffer + rd->PrintNameOffset/2,
rd->PrintNameLength/2,
buf, (
int
)bufsiz, NULL, &used_default);
if
(is_symlink)
*is_symlink = TRUE;
}
break
;
default
:
errno
= EINVAL;
return
-1;
}
if
(bytes_out == 0 || used_default) {
errno
= EINVAL;
return
-1;
}
return
bytes_out;
}
static
int
win32_readlink(
const
char
*pathname,
char
*buf,
size_t
bufsiz) {
if
(pathname == NULL || buf == NULL) {
errno
= EFAULT;
return
-1;
}
if
(bufsiz <= 0) {
errno
= EINVAL;
return
-1;
}
DWORD
fileattr = GetFileAttributes(pathname);
if
(fileattr == INVALID_FILE_ATTRIBUTES) {
translate_to_errno();
return
-1;
}
if
(!(fileattr & FILE_ATTRIBUTE_REPARSE_POINT)) {
errno
= EINVAL;
return
-1;
}
HANDLE
hlink =
CreateFileA(pathname, GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
if
(hlink == INVALID_HANDLE_VALUE) {
translate_to_errno();
return
-1;
}
int
bytes_out = do_readlink_handle(hlink, buf, bufsiz, NULL);
CloseHandle(hlink);
if
(bytes_out < 0) {
return
-1;
}
if
((
size_t
)bytes_out > bufsiz) {
errno
= EINVAL;
return
-1;
}
return
bytes_out;
}
static
int
win32_symlink(SPVM_ENV* env, SPVM_VALUE* stack,
const
char
*oldfile,
const
char
*newfile)
{
size_t
oldfile_len =
strlen
(oldfile);
pCreateSymbolicLinkA_t pCreateSymbolicLinkA =
(pCreateSymbolicLinkA_t)GetProcAddress(GetModuleHandle(
"kernel32.dll"
),
"CreateSymbolicLinkA"
);
DWORD
create_flags = 0;
if
(g_osver.dwMajorVersion > 10 ||
(g_osver.dwMajorVersion == 10 &&
(g_osver.dwMinorVersion > 0 || g_osver.dwBuildNumber > 15063)))
{
create_flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
}
if
(!pCreateSymbolicLinkA) {
errno
= ENOSYS;
return
-1;
}
if
(
strchr
(oldfile,
'/'
)) {
char
*temp = (
char
*)env->get_chars(env, stack, env->new_string(env, stack, oldfile,
strlen
(oldfile)));
char
*p = temp;
while
(*p) {
if
(*p ==
'/'
) {
*p =
'\\'
;
}
++p;
}
*p = 0;
oldfile = temp;
oldfile_len = p - temp;
}
if
((oldfile_len >= 1 && isSLASH(oldfile[oldfile_len-1])) ||
strEQ(oldfile,
".."
) ||
strEQ(oldfile,
"."
) ||
(isSLASH(oldfile[oldfile_len-2]) && oldfile[oldfile_len-1] ==
'.'
) ||
strEQ(oldfile+oldfile_len-3,
"\\.."
) ||
(oldfile_len == 2 && oldfile[1] ==
':'
)) {
create_flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
}
else
{
DWORD
dest_attr;
const
char
*dest_path = oldfile;
char
szTargetName[MAX_PATH+1];
if
(oldfile_len >= 3 && oldfile[1] ==
':'
) {
}
else
if
(oldfile[0] !=
'\\'
) {
size_t
newfile_len =
strlen
(newfile);
char
*last_slash =
strrchr
(newfile,
'/'
);
char
*last_bslash =
strrchr
(newfile,
'\\'
);
char
*end_dir = last_slash && last_bslash
? ( last_slash > last_bslash ? last_slash : last_bslash)
: last_slash ? last_slash : last_bslash ? last_bslash : NULL;
if
(end_dir) {
if
((end_dir - newfile + 1) + oldfile_len > MAX_PATH) {
errno
= EINVAL;
return
-1;
}
memcpy
(szTargetName, newfile, end_dir - newfile + 1);
strcpy
(szTargetName + (end_dir - newfile + 1), oldfile);
dest_path = szTargetName;
}
else
{
}
}
dest_attr = GetFileAttributes(dest_path);
if
(dest_attr != (
DWORD
)-1 && (dest_attr & FILE_ATTRIBUTE_DIRECTORY)) {
create_flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
}
}
if
(!pCreateSymbolicLinkA(newfile, oldfile, create_flags)) {
translate_to_errno();
return
-1;
}
return
0;
}
static
int
win32_lstat(
const
char
* path,
struct
stat* sbuf)
{
HANDLE
f;
int
result;
DWORD
attr = GetFileAttributes(path);
if
(attr == INVALID_FILE_ATTRIBUTES) {
translate_to_errno();
return
-1;
}
if
(!(attr & FILE_ATTRIBUTE_REPARSE_POINT)) {
return
stat(path, sbuf);
}
f = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
if
(f == INVALID_HANDLE_VALUE) {
translate_to_errno();
return
-1;
}
BOOL
is_symlink;
int
size = do_readlink_handle(f, NULL, 0, &is_symlink);
if
(!is_symlink) {
CloseHandle(f);
return
stat(path, sbuf);
}
else
if
(size < 0) {
CloseHandle(f);
return
-1;
}
int32_t fd = _open_osfhandle((
intptr_t
)f, _O_RDONLY);
result = fstat(fd, sbuf);
if
(result != -1){
sbuf->st_mode = (sbuf->st_mode & ~_S_IFMT) | _S_IFLNK;
sbuf->st_size = size;
}
_close(fd);
return
result;
}
#endif // _WIN32
int32_t SPVM__Sys__IO__Windows__unlink(SPVM_ENV* env, SPVM_VALUE* stack) {
#if !defined(_WIN32)
env->die(env, stack,
"Sys::IO::Windows#unlink method is not supported in this system(!defined(_WIN32))."
, __func__, FILE_NAME, __LINE__);
return
SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
void
* obj_pathname = stack[0].oval;
if
(!obj_pathname) {
return
env->die(env, stack,
"The path $pathname must be defined."
, __func__, FILE_NAME, __LINE__);
}
const
char
* pathname = env->get_chars(env, stack, obj_pathname);
int32_t status = win32_unlink(pathname);
stack[0].ival = status;
if
(status == -1) {
env->die(env, stack,
"[System Error]unlink() failed:%s. $pathname is \"%s\"."
, env->strerror_nolen(env, stack,
errno
), pathname, __func__, FILE_NAME, __LINE__);
return
SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
}
return
0;
#endif
}
int32_t SPVM__Sys__IO__Windows__rename(SPVM_ENV* env, SPVM_VALUE* stack) {
#if !defined(_WIN32)
env->die(env, stack,
"Sys::IO::Windows#rename method is not supported in this system(!defined(_WIN32))."
, __func__, FILE_NAME, __LINE__);
return
SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
void
* obj_oldpath = stack[0].oval;
if
(!obj_oldpath) {
return
env->die(env, stack,
"The old path $oldpath must be defined."
, __func__, FILE_NAME, __LINE__);
}
const
char
* oldpath = env->get_chars(env, stack, obj_oldpath);
void
* obj_newpath = stack[0].oval;
if
(!obj_newpath) {
return
env->die(env, stack,
"The new path $newpath must be defined."
, __func__, FILE_NAME, __LINE__);
}
const
char
* newpath = env->get_chars(env, stack, obj_newpath);
int32_t status = win32_rename(oldpath, newpath);
if
(status == -1) {
env->die(env, stack,
"[System Error]rename() failed:%s. $oldpath is \"%s\". $newpath is \"%s\"."
, env->strerror_nolen(env, stack,
errno
), oldpath, newpath, __func__, FILE_NAME, __LINE__);
return
SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
}
stack[0].ival = status;
return
0;
#endif
}
int32_t SPVM__Sys__IO__Windows__readlink(SPVM_ENV* env, SPVM_VALUE* stack) {
#if !defined(_WIN32)
env->die(env, stack,
"Sys::IO::Windows#readlink method is not supported in this system(!defined(_WIN32))."
, __func__, FILE_NAME, __LINE__);
return
SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
int32_t error_id = 0;
void
* obj_path = stack[0].oval;
if
(!obj_path) {
return
env->die(env, stack,
"The path $path must be defined."
, __func__, FILE_NAME, __LINE__);
}
const
char
* path = env->get_chars(env, stack, obj_path);
void
* obj_buf = stack[1].oval;
if
(!obj_buf) {
return
env->die(env, stack,
"The buffer $buf must be defined."
, __func__, FILE_NAME, __LINE__);
}
char
* buf = (
char
*)env->get_chars(env, stack, obj_buf);
int32_t buf_length = env->length(env, stack, obj_buf);
int32_t bufsiz = stack[2].ival;
if
(!(bufsiz >= 0)) {
return
env->die(env, stack,
"The buffer size $bufsiz must be greater than or equal to 0."
, __func__, FILE_NAME, __LINE__);
}
if
(!(bufsiz <= buf_length)) {
return
env->die(env, stack,
"The buffer size $bufsiz must be less than or equal to the length of the buffer $buf."
, __func__, FILE_NAME, __LINE__);
}
errno
= 0;
int32_t placed_length = win32_readlink(path, buf, bufsiz);
if
(placed_length == -1) {
env->die(env, stack,
"[System Error]readlink() failed:%s. $path is \"%s\"."
, env->strerror_nolen(env, stack,
errno
), path, __func__, FILE_NAME, __LINE__);
return
SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
}
stack[0].ival = placed_length;
return
0;
#endif
}
int32_t SPVM__Sys__IO__Windows__symlink(SPVM_ENV* env, SPVM_VALUE* stack) {
#if !defined(_WIN32)
env->die(env, stack,
"Sys::IO::Windows#symlink method is not supported in this system(!defined(_WIN32))."
, __func__, FILE_NAME, __LINE__);
return
SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
int32_t error_id = 0;
void
* obj_oldpath = stack[0].oval;
if
(!obj_oldpath) {
return
env->die(env, stack,
"The old path $oldpath must be defined."
, __func__, FILE_NAME, __LINE__);
}
const
char
* oldpath = env->get_chars(env, stack, obj_oldpath);
void
* obj_newpath = stack[1].oval;
if
(!obj_newpath) {
return
env->die(env, stack,
"The new path $newpath must be defined."
, __func__, FILE_NAME, __LINE__);
}
const
char
* newpath = env->get_chars(env, stack, obj_newpath);
errno
= 0;
int32_t status = win32_symlink(env, stack, oldpath, newpath);
if
(status == -1) {
env->die(env, stack,
"[System Error]win32_symlink() failed:%s. $oldpath is \"%s\". $newpath is \"%s\"."
, env->strerror_nolen(env, stack,
errno
), oldpath, newpath, __func__, FILE_NAME, __LINE__);
return
SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
}
stack[0].ival = status;
return
0;
#endif
}
int32_t SPVM__Sys__IO__Windows__lstat(SPVM_ENV* env, SPVM_VALUE* stack) {
#if !defined(_WIN32)
return
env->die(env, stack,
"Sys::IO::Windows#lstat method is not supported in this system(!defined(_WIN32))."
, __func__, FILE_NAME, __LINE__);
#else
int32_t error_id = 0;
void
* obj_path = stack[0].oval;
if
(!obj_path) {
return
env->die(env, stack,
"The path $path must be defined."
, __func__, FILE_NAME, __LINE__);
}
const
char
* path = env->get_chars(env, stack, obj_path);
void
* obj_lstat = stack[1].oval;
if
(!obj_lstat) {
return
env->die(env, stack,
"The stat object $lstat must be defined."
, __func__, FILE_NAME, __LINE__);
}
struct
stat* stat_buf = env->get_pointer(env, stack, obj_lstat);
int32_t status = win32_lstat(path, stat_buf);
if
(status == -1) {
const
char
* path = env->get_chars(env, stack, obj_path);
env->die(env, stack,
"[System Error]lstat() failed:%s. $path is \"%s\"."
, path, env->strerror_nolen(env, stack,
errno
), path, __func__, FILE_NAME, __LINE__);
return
SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
}
stack[0].ival = status;
return
0;
#endif
return
0;
}