// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
#unsafe_skip_stub_check
fn ms_since_epoch() -> UInt64 = "moonbitlang/async" "time/get_ms_since_epoch"
///|
let tmpdir_seed : @random.Rand = {
let seed = Buffer()
let now = ms_since_epoch()
for _ in 0..<4 {
seed.write_uint64_le(now)
}
@random.Rand::chacha8(seed=seed.contents())
}
///|
#unsafe_skip_stub_check
fn get_tmp_path_len_ffi() -> Int = "moonbitlang/async" "fs/get_tmp_path_len"
///|
#unsafe_skip_stub_check
#borrow(path)
fn get_tmp_path_ffi(path : String, len : Int) -> Int = "moonbitlang/async" "fs/get_tmp_path"
///|
/// Wasm can't access exported memory during initialization,
/// so we have to use a function to get the tmp base path.
fn get_tmp_base_path() -> String {
let len = get_tmp_path_len_ffi()
if len < 0 {
let err = @os_error.errno_to_string(@os_error.get_errno())
abort("failed to obtain directory for temporary files: \{err}")
}
let path = String::make(len, '\u{0}')
if get_tmp_path_ffi(path, len) < 0 {
let err = @os_error.errno_to_string(@os_error.get_errno())
abort("failed to obtain directory for temporary files: \{err}")
}
path
}
///|
/// Create a temporary directory in the system-specific temporary file storage
/// (such as `/tmp/`) with prefix `prefix`.
/// `tmpdir` force-create the directory atomically, so it never reuse directory.
///
/// The format of the directory name is:
///
/// ..
///
/// The random number part cotains 32bits of information,
/// so at most 2^32 temporary directories can be created for every process.
/// However, due to collision, `tmpdir` will slow down
/// when a lot of temporary directories are created by the same process.
///
/// The temporary directory name is not cryptographically secure.
pub async fn tmpdir(prefix~ : StringView) -> String {
let tmp_base_path = get_tmp_base_path()
for _ in 0..<@int.MAX_VALUE {
let hasher = Hasher()
hasher.combine_uint64(tmpdir_seed.uint64())
hasher.combine_uint64(ms_since_epoch())
let h = hasher.finalize().reinterpret_as_uint()
let path = StringBuilder::new()
path.write_string(tmp_base_path)
path.write_stringview(prefix)
path.write_char('.')
path.write_string(@env_util.current_process.to_string())
path.write_char('.')
path.write_string(h.to_string(radix=16))
let path = path.to_string()
try @event_loop.mkdir(path, mode=0o700, context="@fs.tmpdir()") catch {
@os_error.OSError(_) as err if err.is_EEXIST() => ()
err => raise err
} noraise {
_ => return path
}
}
raise Failure::Failure("failed to create tmp directory: too many attempts")
}