Onran aae642a13e
Streaming I/O and support of named pipes (#570)
* added streaming i/o for scripting, and a byteutil.get_size function

* added i/o stream class, also added named pipes support on lua side via ffi

* added constant file.named_pipes_prefix

* added buffered and yield modes for io_stream

* added new time function for work with UTC - utc_time, utc_offset, local_time

* docs updated

* constant pid moved to os.pid

* now gmtime_s and localtime_s used only in windows
2025-08-01 20:26:43 +03:00

144 lines
3.5 KiB
Lua

local path_validate = require "core:internal/stream_providers/named_pipe_path_validate"
local io_stream = require "core:io_stream"
local FFI = ffi
FFI.cdef[[
typedef void* HANDLE;
typedef uint32_t DWORD;
typedef int BOOL;
typedef void* LPVOID;
typedef const char* LPCSTR;
BOOL CloseHandle(HANDLE hObject);
DWORD GetFileType(HANDLE hFile);
BOOL ReadFile(HANDLE hFile, void* lpBuffer, DWORD nNumberOfBytesToRead,
DWORD* lpNumberOfBytesRead, void* lpOverlapped);
BOOL WriteFile(HANDLE hFile, const void* lpBuffer, DWORD nNumberOfBytesToWrite,
DWORD* lpNumberOfBytesWritten, void* lpOverlapped);
HANDLE CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
void* lpSecurityAttributes, DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
BOOL PeekNamedPipe(
HANDLE hNamedPipe,
LPVOID lpBuffer,
DWORD nBufferSize,
DWORD* lpBytesRead,
DWORD* lpTotalBytesAvail,
DWORD* lpBytesLeftThisMessage
);
DWORD GetLastError(void);
BOOL FlushFileBuffers(HANDLE hFile);
]]
local C = FFI.C
local GENERIC_READ = 0x80000000
local GENERIC_WRITE = 0x40000000
local OPEN_EXISTING = 3
local FILE_ATTRIBUTE_NORMAL = 0x00000080
local FILE_TYPE_UNKNOWN = 0x0000
local INVALID_HANDLE_VALUE = FFI.cast("HANDLE", -1)
local lib = {}
local function is_data_available(handle)
local bytes_available = FFI.new("DWORD[1]")
local success = FFI.C.PeekNamedPipe(handle, nil, 0, nil, bytes_available, nil)
if success == 0 then
return -1
end
return bytes_available[0] > 0
end
function lib.read(handle, len)
local out = Bytearray()
local has_data, err = is_data_available(handle)
if not has_data then
return out
elseif hasData == -1 then
error("failed to read from named pipe: "..tostring(C.GetLastError()))
end
local buffer = FFI.new("uint8_t[?]", len)
local read = FFI.new("DWORD[1]")
local ok = C.ReadFile(handle, buffer, len, read, nil)
if ok == 0 or read[0] == 0 then
return out
end
for i = 0, read[0] - 1 do
out[i+1] = buffer[i]
end
return out
end
function lib.write(handle, bytearray)
local len = #bytearray
local buffer = FFI.new("uint8_t[?]", len)
for i = 1, len do
buffer[i-1] = bytearray[i]
end
local written = FFI.new("DWORD[1]")
if C.WriteFile(handle, buffer, len, written, nil) == 0 then
error("failed to write to named pipe: "..tostring(C.GetLastError()))
end
end
function lib.flush(handle)
C.FlushFileBuffers(handle)
end
function lib.is_alive(handle)
if handle == nil or handle == INVALID_HANDLE_VALUE then
return false
else
return C.GetFileType(handle) ~= FILE_TYPE_UNKNOWN
end
end
function lib.close(handle)
C.CloseHandle(handle)
end
return function(path, mode)
path_validate(path)
path = "\\\\.\\pipe\\"..path
local read = mode:find('r') ~= nil
local write = mode:find('w') ~= nil
local flags
if read and write then
flags = bit.bor(GENERIC_READ, GENERIC_WRITE)
elseif read then
flags = GENERIC_READ
elseif write then
flags = GENERIC_WRITE
else
error("mode must contain read or write flag")
end
local handle = C.CreateFileA(path, flags, 0, nil, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nil)
if handle == INVALID_HANDLE_VALUE then
error("failed to open named pipe: "..tostring(C.GetLastError()))
end
return io_stream.new(handle, mode:find('b') ~= nil, lib)
end