- 新增 copyself:延迟后删除目标并复制文件/目录 - ViperC 新增 --build-self:自举到 __vpcbuild_2__ 后由 copyself 替换 __vpcbuild__ - 同步当前 ViperC 工作区已有的 Gen/Diag/Stub 改动 - .gitignore 忽略 __vpcbuild_* 与 _sha1_map.txt
349 lines
13 KiB
Plaintext
349 lines
13 KiB
Plaintext
from stdint import *
|
|
import stdio
|
|
import stdlib
|
|
import string
|
|
import memhub
|
|
import os
|
|
import os._win32
|
|
import os._posix
|
|
import os.path
|
|
import win32.win32base
|
|
import win32.win32file
|
|
import win32.win32console
|
|
|
|
|
|
# copyself waits for a delay, then copies a file or a directory.
|
|
# Directory source copies its contents into the destination directory.
|
|
define CODE_PAGE = 65001
|
|
define POOL_SIZE = 268435456
|
|
define MAX_DELAY_SECONDS = 2147483
|
|
define ERROR_NO_MORE_FILES = 18
|
|
define FILE_ATTRIBUTE_REPARSE_POINT = 1024
|
|
define INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
|
|
|
|
|
|
def _IsDigits(s: str) -> bool:
|
|
if s is None or s[0] == 0:
|
|
return False
|
|
i: sizet = 0
|
|
while s[i] != 0:
|
|
if s[i] < '0' or s[i] > '9':
|
|
return False
|
|
i += 1
|
|
return True
|
|
|
|
|
|
def _Lower(ch: char) -> char:
|
|
if ch >= 'A' and ch <= 'Z':
|
|
return char(int(ch) - int('A') + int('a'))
|
|
return ch
|
|
|
|
|
|
def _SamePrefix(a: str, b: str) -> bool:
|
|
alen: sizet = len(a)
|
|
if len(b) < alen:
|
|
return False
|
|
i: sizet = 0
|
|
while i < alen:
|
|
if _Lower(a[i]) != _Lower(b[i]):
|
|
return False
|
|
i += 1
|
|
return True
|
|
|
|
|
|
def _SamePath(a: str, b: str) -> bool:
|
|
if len(a) != len(b):
|
|
return False
|
|
return _SamePrefix(a, b)
|
|
|
|
|
|
def _Absolute(path: str) -> str:
|
|
if os.path.isabs(path):
|
|
return os.path.normpath(path)
|
|
cwd: str = os.getcwd()
|
|
if cwd is None:
|
|
return os.path.normpath(path)
|
|
return os.path.normpath(os.path.join(cwd, path))
|
|
|
|
|
|
def _IsSubPath(parent: str, child: str) -> bool:
|
|
plen: sizet = len(parent)
|
|
clen: sizet = len(child)
|
|
if plen == 0 or clen <= plen:
|
|
return False
|
|
if not _SamePrefix(parent, child):
|
|
return False
|
|
last: char = parent[plen - 1]
|
|
if last == '\\' or last == '/':
|
|
return True
|
|
ch: char = child[plen]
|
|
return ch == '\\' or ch == '/'
|
|
|
|
|
|
def _EnsureDir(path: str) -> int:
|
|
if path is None or path[0] == 0:
|
|
stdio.printf("copyself: cannot create an empty directory path\n")
|
|
return -1
|
|
if len(path) == 2 and path[1] == ':':
|
|
return 0
|
|
if os.isdir(path):
|
|
return 0
|
|
if os.exists(path):
|
|
stdio.printf("copyself: cannot create directory '%s': a file with the same name exists\n", path)
|
|
return -1
|
|
parent: str = os.path.dirname(path)
|
|
if parent is not None and len(parent) > 0 and not _SamePath(parent, path):
|
|
if not os.isdir(parent):
|
|
if _EnsureDir(parent) != 0:
|
|
return -1
|
|
if os.mkdir(path) != 0:
|
|
if os.isdir(path):
|
|
return 0
|
|
err: uint32 = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to create directory '%s' (Win32 error %d)\n", path, int(err))
|
|
return -1
|
|
return 0
|
|
|
|
|
|
def _CopyFile(src: str, dst: str) -> int:
|
|
if win32.win32file.CopyFileA(src, dst, 0) == 0:
|
|
err: uint32 = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to copy file '%s' -> '%s' (Win32 error %d)\n", src, dst, int(err))
|
|
return -1
|
|
return 0
|
|
|
|
|
|
def _CopyDirContents(src: str, dst: str) -> int:
|
|
pattern: str = os.path.join(src, "*")
|
|
if pattern is None:
|
|
stdio.printf("copyself: failed to build the search pattern for '%s'\n", src)
|
|
return -1
|
|
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:
|
|
err: uint32 = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to list directory '%s' (Win32 error %d)\n", src, int(err))
|
|
return -1
|
|
rc: int = 0
|
|
while True:
|
|
name: str = find_data.cFileName
|
|
skip: bool = False
|
|
if name[0] == '.':
|
|
if name[1] == 0:
|
|
skip = True
|
|
elif name[1] == '.' and name[2] == 0:
|
|
skip = True
|
|
if not skip:
|
|
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0:
|
|
stdio.printf("copyself: reparse point '%s' under '%s' is not supported\n", name, src)
|
|
rc = -1
|
|
break
|
|
child_src: str = os.path.join(src, name)
|
|
child_dst: str = os.path.join(dst, name)
|
|
if child_src is None or child_dst is None:
|
|
stdio.printf("copyself: failed to build child paths for '%s'\n", name)
|
|
rc = -1
|
|
break
|
|
if _CopyEntry(child_src, child_dst) != 0:
|
|
rc = -1
|
|
break
|
|
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
|
|
err2: uint32 = win32.win32base.GetLastError()
|
|
if int(err2) != ERROR_NO_MORE_FILES:
|
|
stdio.printf("copyself: failed while listing directory '%s' (Win32 error %d)\n", src, int(err2))
|
|
rc = -1
|
|
break
|
|
win32.win32file.FindClose(handle)
|
|
return rc
|
|
|
|
|
|
def _RemoveTree(path: str) -> int:
|
|
pattern: str = os.path.join(path, "*")
|
|
if pattern is None:
|
|
stdio.printf("copyself: failed to build the search pattern for '%s'\n", path)
|
|
return -1
|
|
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:
|
|
err: ULONG = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to list directory '%s' (Win32 error %d)\n", path, int(err))
|
|
return -1
|
|
rc: int = 0
|
|
while True:
|
|
name: str = find_data.cFileName
|
|
skip: bool = False
|
|
if name[0] == '.':
|
|
if name[1] == 0:
|
|
skip = True
|
|
elif name[1] == '.' and name[2] == 0:
|
|
skip = True
|
|
if not skip:
|
|
child: str = os.path.join(path, name)
|
|
if child is None:
|
|
stdio.printf("copyself: failed to build the child path for '%s'\n", name)
|
|
rc = -1
|
|
break
|
|
attrs: ULONG = find_data.dwFileAttributes
|
|
if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0:
|
|
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
|
if win32.win32file.RemoveDirectoryA(child) == 0:
|
|
err_jd: ULONG = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to remove junction '%s' (Win32 error %d)\n", child, int(err_jd))
|
|
rc = -1
|
|
break
|
|
else:
|
|
if win32.win32file.DeleteFileA(child) == 0:
|
|
err_jf: ULONG = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to remove '%s' (Win32 error %d)\n", child, int(err_jf))
|
|
rc = -1
|
|
break
|
|
elif (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
|
if _RemoveTree(child) != 0:
|
|
rc = -1
|
|
break
|
|
else:
|
|
if win32.win32file.DeleteFileA(child) == 0:
|
|
err_f: ULONG = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to remove file '%s' (Win32 error %d)\n", child, int(err_f))
|
|
rc = -1
|
|
break
|
|
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
|
|
err_next: ULONG = win32.win32base.GetLastError()
|
|
if int(err_next) != ERROR_NO_MORE_FILES:
|
|
stdio.printf("copyself: failed while listing directory '%s' (Win32 error %d)\n", path, int(err_next))
|
|
rc = -1
|
|
break
|
|
win32.win32file.FindClose(handle)
|
|
if rc != 0:
|
|
return -1
|
|
if win32.win32file.RemoveDirectoryA(path) == 0:
|
|
err_rmdir: ULONG = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to remove directory '%s' (Win32 error %d)\n", path, int(err_rmdir))
|
|
return -1
|
|
return 0
|
|
|
|
|
|
def _DeletePath(path: str) -> int:
|
|
if not os.exists(path):
|
|
return 0
|
|
attrs: ULONG = win32.win32file.GetFileAttributesA(path)
|
|
if attrs == INVALID_FILE_ATTRIBUTES:
|
|
err_attr: ULONG = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to query '%s' (Win32 error %d)\n", path, int(err_attr))
|
|
return -1
|
|
if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0:
|
|
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
|
if win32.win32file.RemoveDirectoryA(path) == 0:
|
|
err_jd: ULONG = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to remove junction '%s' (Win32 error %d)\n", path, int(err_jd))
|
|
return -1
|
|
else:
|
|
if win32.win32file.DeleteFileA(path) == 0:
|
|
err_jf: ULONG = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to remove '%s' (Win32 error %d)\n", path, int(err_jf))
|
|
return -1
|
|
return 0
|
|
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
|
return _RemoveTree(path)
|
|
if win32.win32file.DeleteFileA(path) == 0:
|
|
err_f: ULONG = win32.win32base.GetLastError()
|
|
stdio.printf("copyself: failed to remove file '%s' (Win32 error %d)\n", path, int(err_f))
|
|
return -1
|
|
return 0
|
|
|
|
|
|
def _CopyEntry(src: str, dst: str) -> int:
|
|
if os.isdir(src):
|
|
if os.exists(dst):
|
|
if not os.isdir(dst):
|
|
stdio.printf("copyself: source '%s' is a directory but destination '%s' is a file\n", src, dst)
|
|
return -1
|
|
if _DeletePath(dst) != 0:
|
|
return -1
|
|
if _EnsureDir(dst) != 0:
|
|
return -1
|
|
return _CopyDirContents(src, dst)
|
|
if os.isfile(src):
|
|
if os.isdir(dst):
|
|
stdio.printf("copyself: source '%s' is a file but destination '%s' is a directory\n", src, dst)
|
|
return -1
|
|
if os.exists(dst):
|
|
if _DeletePath(dst) != 0:
|
|
return -1
|
|
parent: str = os.path.dirname(dst)
|
|
if parent is not None and len(parent) > 0 and not os.isdir(parent):
|
|
if _EnsureDir(parent) != 0:
|
|
return -1
|
|
return _CopyFile(src, dst)
|
|
stdio.printf("copyself: source '%s' is neither a regular file nor a directory\n", src)
|
|
return -1
|
|
|
|
|
|
def main(argc: int, argv: []str) -> int:
|
|
win32.win32console.SetConsoleOutputCP(CODE_PAGE)
|
|
win32.win32console.SetConsoleCP(CODE_PAGE)
|
|
if argc != 4:
|
|
stdio.printf("usage: copyself <seconds> <source> <destination>\n")
|
|
return 1
|
|
if argv[2][0] == 0 or argv[3][0] == 0:
|
|
stdio.printf("copyself: source and destination must not be empty\n")
|
|
return 1
|
|
if not _IsDigits(argv[1]):
|
|
stdio.printf("copyself: invalid delay '%s' (expected a non-negative integer)\n", argv[1])
|
|
return 1
|
|
seconds: int64 = string.atoll(argv[1])
|
|
if seconds < 0 or seconds > int64(MAX_DELAY_SECONDS):
|
|
stdio.printf("copyself: delay '%s' is out of range (0..%d seconds)\n", argv[1], MAX_DELAY_SECONDS)
|
|
return 1
|
|
|
|
arena: bytes = stdlib.malloc(sizet(POOL_SIZE))
|
|
if arena is None:
|
|
stdio.printf("copyself: failed to allocate the memory pool\n")
|
|
return 1
|
|
mb: memhub.MemBuddy* = memhub.MemBuddy(arena, sizet(POOL_SIZE), 0, 0)
|
|
if mb is None:
|
|
stdio.printf("copyself: failed to initialize the memory pool\n")
|
|
return 1
|
|
os._mbuddy = mb
|
|
os._win32._mbuddy = mb
|
|
os._posix._mbuddy = mb
|
|
os.path._mbuddy = mb
|
|
|
|
src: str = _Absolute(argv[2])
|
|
dst: str = _Absolute(argv[3])
|
|
if src is None or dst is None:
|
|
stdio.printf("copyself: failed to normalize the source or destination path\n")
|
|
return 1
|
|
if not os.exists(src):
|
|
stdio.printf("copyself: source '%s' does not exist\n", src)
|
|
return 1
|
|
if _SamePath(src, dst):
|
|
stdio.printf("copyself: source and destination are the same path: '%s'\n", src)
|
|
return 1
|
|
if os.isdir(src):
|
|
if _IsSubPath(src, dst):
|
|
stdio.printf("copyself: destination '%s' is inside source directory '%s'\n", dst, src)
|
|
return 1
|
|
if _IsSubPath(dst, src):
|
|
stdio.printf("copyself: source directory '%s' is inside destination '%s'\n", src, dst)
|
|
return 1
|
|
if os.exists(dst) and not os.isdir(dst):
|
|
stdio.printf("copyself: source '%s' is a directory but destination '%s' is a file\n", src, dst)
|
|
return 1
|
|
elif os.isfile(src):
|
|
if os.isdir(dst):
|
|
stdio.printf("copyself: source '%s' is a file but destination '%s' is a directory\n", src, dst)
|
|
return 1
|
|
else:
|
|
stdio.printf("copyself: source '%s' is neither a regular file nor a directory\n", src)
|
|
return 1
|
|
|
|
stdio.printf("copyself: waiting %d second(s) before copying\n", int(seconds))
|
|
win32.win32base.Sleep(ULONG(seconds * 1000))
|
|
stdio.printf("copyself: copying '%s' -> '%s'\n", src, dst)
|
|
rc: int = _CopyEntry(src, dst)
|
|
if rc == 0:
|
|
stdio.printf("copyself: done\n")
|
|
return 0
|
|
return 1
|