/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* This source file is part of the Cangjie project, licensed under Apache-2.0
* with Runtime Library Exception.
*
* See https://cangjie-lang.cn/pages/LICENSE for license information.
*/
// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.
package std.process
import std.collection.*
import std.fs.*
import std.io.*
import std.sync.*
const RTN_CODE_MIN_VAL: Int64 = -0x0000_0000_8000_0000 //-2147483648
const RTN_CODE_MAX_VAL: Int64 = 0x0000_0000_7fff_ffff //2147483647
const NULL_BYTE = "\0"
let mtx: Mutex = Mutex()
@When[backend == "cjnative"]
func getCommandLineArgsFromRuntime(): CPointer<CPointer<UInt8>> {
unsafe {
let argv = CJ_MRT_GetCommandLineArgs()
return if (argv.isNull()) {
argv
} else {
argv + 1
}
}
}
/**
* CurrentProcess provide the base func for current process.
*/
@Deprecated[message: "Use related global functions in the std.env instead."]
public class CurrentProcess <: Process {
let _stdIn: InputStream = ProcessInputStream(FileDescriptor(getStdInHandle()))
let _stdOut: OutputStream = ProcessOutputStream(FileDescriptor(getStdOutHandle()))
let _stdErr: OutputStream = ProcessOutputStream(FileDescriptor(getStdErrHandle()))
init(pid: Int64, name: ?String, command: ?String, arguments: ?Array<String>, commandLine: ?Array<String>,
workingDirectory: ?Path, environment: ?Map<String, String>) {
super(pid, name, command, arguments, commandLine, workingDirectory, environment)
}
@When[os == "Windows"]
~init() {
if (this._handle != INVALID_HANDLE) {
unsafe { CJ_OS_CloseProcessHandle(this._handle) }
}
}
public prop stdErr: OutputStream {
get() {
this._stdErr
}
}
public prop stdOut: OutputStream {
get() {
this._stdOut
}
}
public prop stdIn: InputStream {
get() {
this._stdIn
}
}
public prop arguments: Array<String> {
get() {
this.getArgs()
}
}
public prop homeDirectory: Path {
get() {
var homeDir: CString = unsafe { CJ_OS_HomeDir() } // Note: no need to free.
var dir: Path = Path(homeDir.toString())
return dir
}
}
public prop tempDirectory: Path {
get() {
var e: Option<String> = getTempDir(this.getEnv("TMPDIR"), this.getEnv("TMP"), this.getEnv("TEMP"),
this.getEnv("TEMPDIR"))
return Path(e ?? "/tmp")
}
}
func transOp(opt1: Option<String>, opt2: Option<String>): Option<String> {
match (opt1) {
case Some(_) => opt1
case None => opt2
}
}
func getTempDir(opt1: Option<String>, opt2: Option<String>, opt3: Option<String>, opt4: Option<String>): Option<String> {
transOp(transOp(transOp(opt1, opt2), opt3), opt4)
}
public func getEnv(key: String): Option<String> {
if (key.contains(NULL_BYTE)) {
throw IllegalArgumentException("The path cannot contain null character!")
}
unsafe {
let ck: CString = LibC.mallocCString(key)
try {
return synchronized(mtx) {
nativeGetEnv(ck) // Note: No need to free.
}
} finally {
LibC.free(ck)
}
}
}
public func setEnv(k: String, v: String): Unit {
if (k.contains(NULL_BYTE) || v.contains(NULL_BYTE)) {
throw IllegalArgumentException("The path cannot contain null character!")
}
unsafe {
try (ck = LibC.mallocCString(k).asResource(), cv = LibC.mallocCString(v).asResource()) {
let errno = synchronized(mtx) {
nativeSetEnv(ck.value, cv.value, Int32(1))
}
if (errno != 0) {
throw ProcessException("Failed to set env ${k}.")
}
}
}
}
public func removeEnv(k: String): Unit {
if (k.contains(NULL_BYTE)) {
throw IllegalArgumentException("The path cannot contain null character!")
}
unsafe {
try (ck = LibC.mallocCString(k).asResource()) {
let errno = synchronized(mtx) {
nativeRemoveEnv(ck.value)
}
if (errno != 0) {
throw ProcessException("Failed to remove env ${k}.")
}
}
}
}
func getArgs(): Array<String> {
let argv = getCommandLineArgsFromRuntime()
if (argv.isNull()) {
return Array<String>()
}
var argc: Int64 = 0
while (true) {
let cstr: CPointer<UInt8> = unsafe { argv.read(argc) }
if (cstr.isNull()) {
break
}
argc++
}
let args: Array<String> = Array<String>(argc, repeat: String())
for (argIndex in 0..argc) {
let cstr: CPointer<UInt8> = unsafe { argv.read(argIndex) }
let cstrLen: Int64 = unsafe { Int64(strlen(cstr)) }
let arg: Array<UInt8> = Array<UInt8>(cstrLen, repeat: 0)
for (charIndex in 0..cstrLen) {
arg[charIndex] = unsafe { UInt8(cstr.read(charIndex)) }
}
args[argIndex] = String.fromUtf8(arg)
}
return args
}
/**
* Sets the function to be called before the program ends normally.
* Function description: atexit() is used to set a function to be called before the program ends normally. When the program returns from main by calling exit(),
* The function specified by the function parameter is called before exit() ends the program.
* Do not use the atexit function of the C language. Otherwise, unexpected problems may arise.
*
* @param callback Called Before Normal Exit.
*
* since 0.24.3
*/
public func atExit(callback: () -> Unit): Unit {
CJ_CORE_AddAtexitCallback(callback)
}
/**
* exit() is used to terminate the execution of the current progetStdoutStreamcess and return the status parameter to the parent Process.
* All buffer data of the Process is automatically written back and the files that are not closed are closed
*
* @param code Register the function to be executed before the Process exits.
*/
public func exit(code: Int64): Nothing {
var c: Int64 = code
if (c < RTN_CODE_MIN_VAL) {
c = RTN_CODE_MIN_VAL
}
if (c > RTN_CODE_MAX_VAL) {
c = RTN_CODE_MAX_VAL
}
CJ_CORE_ExecAtexitCallbacks()
processExit(Int32(c))
}
/**
* Override Process func, support callback functions when currentProcess terminate.
*
* @param pid - process id, need to terminate
* @param force - true terminated forcibly
* false terminated normally
*
* @throws ProcessException - if the pid invaild
* @return Unit
*/
protected func terminateAliveProcess(pid: Int32, force: Bool): Unit {
let status: Int8 = unsafe { CJ_OS_GetProcessAliveStatus(Int32(pid), this._startTime) }
// check the process still running, make sure not kill the wrong process.
if (status == PROCESS_STATUS_ALIVE) {
CJ_CORE_ExecAtexitCallbacks()
unsafe { CJ_OS_Terminate(Int32(pid), force) }
return
}
if (status == PROCESS_STATUS_PID_REUSED) {
throw ProcessException("Process pid \"${pid}\" has been reused, not allow kill the new process.")
}
throw ProcessException("Process pid \"${pid}\" not exist, not allow kill process.")
}
}
@When[backend == "cjnative"]
func processExit(exitCode: Int32): Nothing {
unsafe {
FiniCJRuntime()
exit(exitCode)
}
throw ProcessException("Process is already closed.")
}