242 lines
7.9 KiB
Plaintext
242 lines
7.9 KiB
Plaintext
from stdint import *
|
|
import win32.win32base
|
|
import win32.win32file
|
|
import memhub
|
|
import stdlib
|
|
|
|
|
|
# Maximum Windows path length
|
|
define MAX_PATH_LEN = 260
|
|
|
|
# INVALID_FILE_ATTRIBUTES
|
|
define INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
|
|
|
|
# Module-level global MBuddy pointer (initialized by the user in main)
|
|
_mbuddy: memhub.MemBuddy*
|
|
|
|
|
|
# =============================================================================
|
|
# File/Directory Attribute Query
|
|
# =============================================================================
|
|
|
|
def _get_attrs(path: str) -> ULONG:
|
|
"""Get file attributes. Return INVALID_FILE_ATTRIBUTES on failure."""
|
|
return win32.win32file.GetFileAttributesA(path)
|
|
|
|
|
|
def exists(path: str) -> bool:
|
|
"""Check whether the file/directory exists"""
|
|
attrs: ULONG = _get_attrs(path)
|
|
if attrs == INVALID_FILE_ATTRIBUTES:
|
|
return False
|
|
return True
|
|
|
|
|
|
def isdir(path: str) -> bool:
|
|
"""Check whether the path is a directory"""
|
|
attrs: ULONG = _get_attrs(path)
|
|
if attrs == INVALID_FILE_ATTRIBUTES:
|
|
return False
|
|
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
|
return True
|
|
return False
|
|
|
|
|
|
def isfile(path: str) -> bool:
|
|
"""Check whether the path is a regular file"""
|
|
attrs: ULONG = _get_attrs(path)
|
|
if attrs == INVALID_FILE_ATTRIBUTES:
|
|
return False
|
|
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
|
return False
|
|
return True
|
|
|
|
|
|
def getsize(path: str) -> int64:
|
|
"""Return the file size in bytes. Return -1 on failure."""
|
|
handle: win32.win32base.HANDLE = win32.win32file.CreateFileA(
|
|
path, win32.win32file.GENERIC_READ, win32.win32file.FILE_SHARE_READ,
|
|
None, win32.win32file.OPEN_EXISTING, win32.win32file.FILE_ATTRIBUTE_NORMAL, None
|
|
)
|
|
if handle == win32.win32base.INVALID_HANDLE_VALUE:
|
|
return -1
|
|
li: win32.win32base.LARGE_INTEGER = win32.win32base.LARGE_INTEGER()
|
|
result: BOOL = win32.win32file.GetFileSizeEx(handle, &li)
|
|
win32.win32base.CloseHandle(handle)
|
|
if result == 0:
|
|
return -1
|
|
return li.QuadPart
|
|
|
|
|
|
# =============================================================================
|
|
# Working Directory
|
|
# =============================================================================
|
|
|
|
def getcwd() -> str:
|
|
"""Return the current working directory (memory allocated via _mbuddy)"""
|
|
buf: bytes = _mbuddy.alloc(MAX_PATH_LEN + 1)
|
|
if buf == None: return None
|
|
length: ULONG = win32.win32file.GetCurrentDirectoryA(MAX_PATH_LEN + 1, buf)
|
|
if length == 0:
|
|
return None
|
|
if length > MAX_PATH_LEN:
|
|
# Buffer too small; reallocate
|
|
buf2: bytes = _mbuddy.alloc(length + 1)
|
|
if buf2 == None: return None
|
|
win32.win32file.GetCurrentDirectoryA(length + 1, buf2)
|
|
buf2[length] = 0
|
|
return str(buf2)
|
|
buf[length] = 0
|
|
return str(buf)
|
|
|
|
|
|
def chdir(path: str) -> int:
|
|
"""Switch the current working directory. Return 0 on success, -1 on failure."""
|
|
result: BOOL = win32.win32file.SetCurrentDirectoryA(path)
|
|
if result == 0:
|
|
return -1
|
|
return 0
|
|
|
|
|
|
# =============================================================================
|
|
# File/Directory Operations
|
|
# =============================================================================
|
|
|
|
def mkdir(path: str) -> int:
|
|
"""Create a directory. Return 0 on success, -1 on failure.
|
|
If path is valid UTF-8, convert to UTF-16 and use CreateDirectoryW; otherwise fall back to the A-series."""
|
|
if win32.win32base.utf8_is_valid(path) == 0:
|
|
result: BOOL = win32.win32file.CreateDirectoryA(path, None)
|
|
if result == 0:
|
|
return -1
|
|
return 0
|
|
wp: win32.win32base.WCHARPTR = win32.win32base.utf8_to_utf16_dup(path)
|
|
if wp is None:
|
|
result_a: BOOL = win32.win32file.CreateDirectoryA(path, None)
|
|
if result_a == 0:
|
|
return -1
|
|
return 0
|
|
result_w: BOOL = win32.win32file.CreateDirectoryW(t.CPtr(wp), None)
|
|
stdlib.free(wp)
|
|
if result_w == 0:
|
|
return -1
|
|
return 0
|
|
|
|
|
|
def remove(path: str) -> int:
|
|
"""Delete a file. Return 0 on success, -1 on failure."""
|
|
result: BOOL = win32.win32file.DeleteFileA(path)
|
|
if result == 0:
|
|
return -1
|
|
return 0
|
|
|
|
|
|
def rmdir(path: str) -> int:
|
|
"""Delete an empty directory. Return 0 on success, -1 on failure."""
|
|
result: BOOL = win32.win32file.RemoveDirectoryA(path)
|
|
if result == 0:
|
|
return -1
|
|
return 0
|
|
|
|
|
|
def rename(old_path: str, new_path: str) -> int:
|
|
"""Rename/move a file or directory. Return 0 on success, -1 on failure."""
|
|
result: BOOL = win32.win32file.MoveFileExA(
|
|
old_path, new_path,
|
|
win32.win32file.MOVEFILE_REPLACE_EXISTING | win32.win32file.MOVEFILE_COPY_ALLOWED
|
|
)
|
|
if result == 0:
|
|
return -1
|
|
return 0
|
|
|
|
|
|
def chmod(path: str, mode: int) -> int:
|
|
"""Modify file attributes (simplified Windows: read-only bit only).
|
|
S_IWUSR (0o200) in mode: 0 sets read-only, 1 sets writable. Return 0 on success, -1 on failure."""
|
|
if (mode & 0o200) != 0:
|
|
# Writable: clear the read-only attribute
|
|
result: BOOL = win32.win32file.SetFileAttributesA(
|
|
path, win32.win32file.FILE_ATTRIBUTE_NORMAL
|
|
)
|
|
else:
|
|
# Read-only: set the read-only attribute
|
|
result: BOOL = win32.win32file.SetFileAttributesA(
|
|
path, win32.win32file.FILE_ATTRIBUTE_READONLY
|
|
)
|
|
if result == 0:
|
|
return -1
|
|
return 0
|
|
|
|
|
|
# =============================================================================
|
|
# Directory Listing (FindFirstFile / FindNextFile)
|
|
# =============================================================================
|
|
|
|
def listdir(path: str, out_names: str*, max_count: ULONG) -> ULONG:
|
|
"""List file/subdirectory names under a directory.
|
|
out_names is a caller-provided char** array (each entry points to a filename allocated via _mbuddy); max_count is its capacity. Return the count, or 0 on failure."""
|
|
# Build search pattern: path\*
|
|
path_len: sizet = len(path)
|
|
pattern: bytes = _mbuddy.alloc(path_len + 3)
|
|
if pattern == None: return 0
|
|
i: sizet = 0
|
|
for ch in path:
|
|
pattern[i] = ch
|
|
i += 1
|
|
# Append \* suffix
|
|
if path[path_len - 1] != '\\' and path[path_len - 1] != '/':
|
|
pattern[i] = '\\'
|
|
i += 1
|
|
pattern[i] = '*'
|
|
i += 1
|
|
pattern[i] = 0
|
|
|
|
find_data: win32.win32file.WIN32_FIND_DATAA = win32.win32file.WIN32_FIND_DATAA()
|
|
handle: win32.win32base.HANDLE = win32.win32file.FindFirstFileA(pattern, &find_data)
|
|
if handle == win32.win32base.INVALID_HANDLE_VALUE:
|
|
return 0
|
|
|
|
count: ULONG = 0
|
|
while count < max_count:
|
|
# Skip . and ..
|
|
name: str = find_data.cFileName
|
|
if name[0] == '.':
|
|
if name[1] == 0:
|
|
# "."
|
|
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
|
|
break
|
|
continue
|
|
if name[1] == '.' and name[2] == 0:
|
|
# ".."
|
|
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
|
|
break
|
|
continue
|
|
|
|
# Copy the filename into newly allocated memory
|
|
name_len: sizet = len(name)
|
|
name_buf: bytes = _mbuddy.alloc(name_len + 1)
|
|
if name_buf == None: break
|
|
j: sizet = 0
|
|
for ch in name:
|
|
name_buf[j] = ch
|
|
j += 1
|
|
name_buf[name_len] = 0
|
|
out_names[count] = str(name_buf)
|
|
count += 1
|
|
|
|
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
|
|
break
|
|
|
|
win32.win32file.FindClose(handle)
|
|
return count
|
|
|
|
|
|
# =============================================================================
|
|
# Command Execution
|
|
# =============================================================================
|
|
|
|
def system(cmd: str) -> int:
|
|
"""Execute a command line command (via the C runtime system()).
|
|
Output goes to the child terminal (inherits parent stdout/stderr). Return the exit code (0 = success)."""
|
|
return stdlib.system(cmd)
|