filesystem first pass
This commit is contained in:
parent
a36064bb89
commit
4fbdf995ef
@ -176,6 +176,7 @@ func (s *Moonshark) initRunner() error {
|
||||
runnerOpts := []runner.RunnerOption{
|
||||
runner.WithPoolSize(s.Config.Runner.PoolSize),
|
||||
runner.WithLibDirs(s.Config.Dirs.Libs...),
|
||||
runner.WithFsDir(s.Config.Dirs.FS),
|
||||
runner.WithDataDir(s.Config.Dirs.Data),
|
||||
}
|
||||
|
||||
|
@ -19,14 +19,19 @@ var jsonLuaCode string
|
||||
//go:embed sqlite.lua
|
||||
var sqliteLuaCode string
|
||||
|
||||
//go:embed fs.lua
|
||||
var fsLuaCode string
|
||||
|
||||
// Global bytecode cache to improve performance
|
||||
var (
|
||||
sandboxBytecode atomic.Pointer[[]byte]
|
||||
jsonBytecode atomic.Pointer[[]byte]
|
||||
sqliteBytecode atomic.Pointer[[]byte]
|
||||
fsBytecode atomic.Pointer[[]byte]
|
||||
bytecodeOnce sync.Once
|
||||
jsonBytecodeOnce sync.Once
|
||||
sqliteBytecodeOnce sync.Once
|
||||
fsBytecodeOnce sync.Once
|
||||
)
|
||||
|
||||
// precompileSandboxCode compiles the sandbox.lua code to bytecode once
|
||||
@ -95,6 +100,27 @@ func precompileSqliteModule() {
|
||||
logger.Debug("Successfully precompiled sqlite.lua to bytecode (%d bytes)", len(code))
|
||||
}
|
||||
|
||||
func precompileFsModule() {
|
||||
tempState := luajit.New()
|
||||
if tempState == nil {
|
||||
logger.Fatal("Failed to create temp Lua state for FS module compilation")
|
||||
}
|
||||
defer tempState.Close()
|
||||
defer tempState.Cleanup()
|
||||
|
||||
code, err := tempState.CompileBytecode(fsLuaCode, "fs.lua")
|
||||
if err != nil {
|
||||
logger.Error("Failed to compile FS module: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
bytecode := make([]byte, len(code))
|
||||
copy(bytecode, code)
|
||||
fsBytecode.Store(&bytecode)
|
||||
|
||||
logger.Debug("Successfully precompiled fs.lua to bytecode (%d bytes)", len(code))
|
||||
}
|
||||
|
||||
// loadSandboxIntoState loads the sandbox code into a Lua state
|
||||
func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||
bytecodeOnce.Do(precompileSandboxCode)
|
||||
@ -158,6 +184,32 @@ func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
fsBytecodeOnce.Do(precompileFsModule)
|
||||
fsBytecode := fsBytecode.Load()
|
||||
if fsBytecode != nil && len(*fsBytecode) > 0 {
|
||||
if verbose {
|
||||
logger.Debug("Loading fs.lua from precompiled bytecode")
|
||||
}
|
||||
|
||||
if err := state.LoadBytecode(*fsBytecode, "fs.lua"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := state.RunBytecodeWithResults(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state.SetGlobal("fs")
|
||||
} else {
|
||||
if verbose {
|
||||
logger.Warning("Using non-precompiled fs.lua")
|
||||
}
|
||||
|
||||
if err := state.DoString(fsLuaCode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bytecode := sandboxBytecode.Load()
|
||||
if bytecode != nil && len(*bytecode) > 0 {
|
||||
if verbose {
|
||||
|
508
core/runner/fs.go
Normal file
508
core/runner/fs.go
Normal file
@ -0,0 +1,508 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"Moonshark/core/utils/logger"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
// Global filesystem path (set during initialization)
|
||||
var fsBasePath string
|
||||
|
||||
// InitFS initializes the filesystem with the given base path
|
||||
func InitFS(basePath string) error {
|
||||
if basePath == "" {
|
||||
return errors.New("filesystem base path cannot be empty")
|
||||
}
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
if err := os.MkdirAll(basePath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create filesystem directory: %w", err)
|
||||
}
|
||||
|
||||
// Store the absolute path
|
||||
absPath, err := filepath.Abs(basePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path: %w", err)
|
||||
}
|
||||
|
||||
fsBasePath = absPath
|
||||
logger.Server("Virtual filesystem initialized at: %s", fsBasePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupFS performs any necessary cleanup
|
||||
func CleanupFS() {
|
||||
// Nothing to clean up currently
|
||||
}
|
||||
|
||||
// ResolvePath resolves a given path relative to the filesystem base
|
||||
// Returns the actual path and an error if the path tries to escape the sandbox
|
||||
func ResolvePath(path string) (string, error) {
|
||||
if fsBasePath == "" {
|
||||
return "", errors.New("filesystem not initialized")
|
||||
}
|
||||
|
||||
// Clean the path to remove any .. or . components
|
||||
cleanPath := filepath.Clean(path)
|
||||
|
||||
// Replace backslashes with forward slashes for consistent handling
|
||||
cleanPath = strings.ReplaceAll(cleanPath, "\\", "/")
|
||||
|
||||
// Remove any leading / or drive letter to make it relative
|
||||
cleanPath = strings.TrimPrefix(cleanPath, "/")
|
||||
|
||||
// Remove drive letter on Windows (e.g. C:)
|
||||
if len(cleanPath) >= 2 && cleanPath[1] == ':' {
|
||||
cleanPath = cleanPath[2:]
|
||||
}
|
||||
|
||||
// Ensure the path doesn't contain .. to prevent escaping
|
||||
if strings.Contains(cleanPath, "..") {
|
||||
return "", errors.New("path cannot contain .. components")
|
||||
}
|
||||
|
||||
// Join with the base path
|
||||
fullPath := filepath.Join(fsBasePath, cleanPath)
|
||||
|
||||
// Verify the path is still within the base directory
|
||||
if !strings.HasPrefix(fullPath, fsBasePath) {
|
||||
return "", errors.New("path escapes the filesystem sandbox")
|
||||
}
|
||||
|
||||
return fullPath, nil
|
||||
}
|
||||
|
||||
// fsReadFile reads a file and returns its contents
|
||||
func fsReadFile(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.read_file: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
fullPath, err := ResolvePath(path)
|
||||
if err != nil {
|
||||
state.PushString("fs.read_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
state.PushString("fs.read_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
state.PushString(string(data))
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsWriteFile writes data to a file
|
||||
func fsWriteFile(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.write_file: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
if !state.IsString(2) {
|
||||
state.PushString("fs.write_file: content must be a string")
|
||||
return -1
|
||||
}
|
||||
content := state.ToString(2)
|
||||
|
||||
fullPath, err := ResolvePath(path)
|
||||
if err != nil {
|
||||
state.PushString("fs.write_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
// Ensure the directory exists
|
||||
dir := filepath.Dir(fullPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
state.PushString("fs.write_file: failed to create directory: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
err = os.WriteFile(fullPath, []byte(content), 0644)
|
||||
if err != nil {
|
||||
state.PushString("fs.write_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
state.PushBoolean(true)
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsAppendFile appends data to a file
|
||||
func fsAppendFile(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.append_file: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
if !state.IsString(2) {
|
||||
state.PushString("fs.append_file: content must be a string")
|
||||
return -1
|
||||
}
|
||||
content := state.ToString(2)
|
||||
|
||||
fullPath, err := ResolvePath(path)
|
||||
if err != nil {
|
||||
state.PushString("fs.append_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
// Ensure the directory exists
|
||||
dir := filepath.Dir(fullPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
state.PushString("fs.append_file: failed to create directory: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(fullPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
state.PushString("fs.append_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.Write([]byte(content))
|
||||
if err != nil {
|
||||
state.PushString("fs.append_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
state.PushBoolean(true)
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsExists checks if a file or directory exists
|
||||
func fsExists(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.exists: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
fullPath, err := ResolvePath(path)
|
||||
if err != nil {
|
||||
state.PushString("fs.exists: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
_, err = os.Stat(fullPath)
|
||||
state.PushBoolean(err == nil)
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsRemoveFile removes a file
|
||||
func fsRemoveFile(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.remove_file: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
fullPath, err := ResolvePath(path)
|
||||
if err != nil {
|
||||
state.PushString("fs.remove_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
// Check if it's a directory
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
state.PushString("fs.remove_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
state.PushString("fs.remove_file: cannot remove directory, use remove_dir instead")
|
||||
return -1
|
||||
}
|
||||
|
||||
err = os.Remove(fullPath)
|
||||
if err != nil {
|
||||
state.PushString("fs.remove_file: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
state.PushBoolean(true)
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsGetInfo gets information about a file
|
||||
func fsGetInfo(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.get_info: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
fullPath, err := ResolvePath(path)
|
||||
if err != nil {
|
||||
state.PushString("fs.get_info: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
state.PushString("fs.get_info: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
state.NewTable()
|
||||
|
||||
state.PushString(info.Name())
|
||||
state.SetField(-2, "name")
|
||||
|
||||
state.PushNumber(float64(info.Size()))
|
||||
state.SetField(-2, "size")
|
||||
|
||||
state.PushNumber(float64(info.Mode()))
|
||||
state.SetField(-2, "mode")
|
||||
|
||||
state.PushNumber(float64(info.ModTime().Unix()))
|
||||
state.SetField(-2, "mod_time")
|
||||
|
||||
state.PushBoolean(info.IsDir())
|
||||
state.SetField(-2, "is_dir")
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsMakeDir creates a directory
|
||||
func fsMakeDir(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.make_dir: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
perm := os.FileMode(0755)
|
||||
if state.GetTop() >= 2 && state.IsNumber(2) {
|
||||
perm = os.FileMode(state.ToNumber(2))
|
||||
}
|
||||
|
||||
fullPath, err := ResolvePath(path)
|
||||
if err != nil {
|
||||
state.PushString("fs.make_dir: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
err = os.MkdirAll(fullPath, perm)
|
||||
if err != nil {
|
||||
state.PushString("fs.make_dir: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
state.PushBoolean(true)
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsListDir lists the contents of a directory
|
||||
func fsListDir(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.list_dir: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
fullPath, err := ResolvePath(path)
|
||||
if err != nil {
|
||||
state.PushString("fs.list_dir: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
state.PushString("fs.list_dir: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
state.PushString("fs.list_dir: not a directory")
|
||||
return -1
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(fullPath)
|
||||
if err != nil {
|
||||
state.PushString("fs.list_dir: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
state.NewTable()
|
||||
|
||||
for i, file := range files {
|
||||
state.PushNumber(float64(i + 1))
|
||||
state.PushString(file.Name())
|
||||
state.SetTable(-3)
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsRemoveDir removes a directory
|
||||
func fsRemoveDir(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.remove_dir: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
recursive := false
|
||||
if state.GetTop() >= 2 {
|
||||
recursive = state.ToBoolean(2)
|
||||
}
|
||||
|
||||
fullPath, err := ResolvePath(path)
|
||||
if err != nil {
|
||||
state.PushString("fs.remove_dir: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
state.PushString("fs.remove_dir: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
state.PushString("fs.remove_dir: not a directory")
|
||||
return -1
|
||||
}
|
||||
|
||||
if recursive {
|
||||
err = os.RemoveAll(fullPath)
|
||||
} else {
|
||||
err = os.Remove(fullPath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
state.PushString("fs.remove_dir: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
state.PushBoolean(true)
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsJoinPaths joins path components
|
||||
func fsJoinPaths(state *luajit.State) int {
|
||||
nargs := state.GetTop()
|
||||
if nargs < 1 {
|
||||
state.PushString("fs.join_paths: at least one path component required")
|
||||
return -1
|
||||
}
|
||||
|
||||
components := make([]string, nargs)
|
||||
for i := 1; i <= nargs; i++ {
|
||||
if !state.IsString(i) {
|
||||
state.PushString("fs.join_paths: all arguments must be strings")
|
||||
return -1
|
||||
}
|
||||
components[i-1] = state.ToString(i)
|
||||
}
|
||||
|
||||
result := filepath.Join(components...)
|
||||
result = strings.ReplaceAll(result, "\\", "/")
|
||||
|
||||
state.PushString(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsDirName returns the directory portion of a path
|
||||
func fsDirName(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.dir_name: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
dir = strings.ReplaceAll(dir, "\\", "/")
|
||||
|
||||
state.PushString(dir)
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsBaseName returns the file name portion of a path
|
||||
func fsBaseName(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.base_name: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
base := filepath.Base(path)
|
||||
|
||||
state.PushString(base)
|
||||
return 1
|
||||
}
|
||||
|
||||
// fsExtension returns the file extension
|
||||
func fsExtension(state *luajit.State) int {
|
||||
if !state.IsString(1) {
|
||||
state.PushString("fs.extension: path must be a string")
|
||||
return -1
|
||||
}
|
||||
path := state.ToString(1)
|
||||
|
||||
ext := filepath.Ext(path)
|
||||
|
||||
state.PushString(ext)
|
||||
return 1
|
||||
}
|
||||
|
||||
// RegisterFSFunctions registers filesystem functions with the Lua state
|
||||
func RegisterFSFunctions(state *luajit.State) error {
|
||||
if err := state.RegisterGoFunction("__fs_read_file", fsReadFile); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_write_file", fsWriteFile); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_append_file", fsAppendFile); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_exists", fsExists); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_remove_file", fsRemoveFile); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_get_info", fsGetInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_make_dir", fsMakeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_list_dir", fsListDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_remove_dir", fsRemoveDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_join_paths", fsJoinPaths); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_dir_name", fsDirName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_base_name", fsBaseName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.RegisterGoFunction("__fs_extension", fsExtension); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
139
core/runner/fs.lua
Normal file
139
core/runner/fs.lua
Normal file
@ -0,0 +1,139 @@
|
||||
local fs = {}
|
||||
|
||||
-- File Operations
|
||||
fs.read_file = function(path)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.read_file: path must be a string", 2)
|
||||
end
|
||||
return __fs_read_file(path)
|
||||
end
|
||||
|
||||
fs.write_file = function(path, content)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.write_file: path must be a string", 2)
|
||||
end
|
||||
if type(content) ~= "string" then
|
||||
error("fs.write_file: content must be a string", 2)
|
||||
end
|
||||
return __fs_write_file(path, content)
|
||||
end
|
||||
|
||||
fs.append_file = function(path, content)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.append_file: path must be a string", 2)
|
||||
end
|
||||
if type(content) ~= "string" then
|
||||
error("fs.append_file: content must be a string", 2)
|
||||
end
|
||||
return __fs_append_file(path, content)
|
||||
end
|
||||
|
||||
fs.exists = function(path)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.exists: path must be a string", 2)
|
||||
end
|
||||
return __fs_exists(path)
|
||||
end
|
||||
|
||||
fs.remove_file = function(path)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.remove_file: path must be a string", 2)
|
||||
end
|
||||
return __fs_remove_file(path)
|
||||
end
|
||||
|
||||
fs.get_info = function(path)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.get_info: path must be a string", 2)
|
||||
end
|
||||
local info = __fs_get_info(path)
|
||||
|
||||
-- Convert the Unix timestamp to a readable date
|
||||
if info and info.mod_time then
|
||||
info.mod_time_str = os.date("%Y-%m-%d %H:%M:%S", info.mod_time)
|
||||
end
|
||||
|
||||
return info
|
||||
end
|
||||
|
||||
-- Directory Operations
|
||||
fs.make_dir = function(path, mode)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.make_dir: path must be a string", 2)
|
||||
end
|
||||
mode = mode or 0755
|
||||
return __fs_make_dir(path, mode)
|
||||
end
|
||||
|
||||
fs.list_dir = function(path)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.list_dir: path must be a string", 2)
|
||||
end
|
||||
return __fs_list_dir(path)
|
||||
end
|
||||
|
||||
fs.remove_dir = function(path, recursive)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.remove_dir: path must be a string", 2)
|
||||
end
|
||||
recursive = recursive or false
|
||||
return __fs_remove_dir(path, recursive)
|
||||
end
|
||||
|
||||
-- Path Operations
|
||||
fs.join_paths = function(...)
|
||||
return __fs_join_paths(...)
|
||||
end
|
||||
|
||||
fs.dir_name = function(path)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.dir_name: path must be a string", 2)
|
||||
end
|
||||
return __fs_dir_name(path)
|
||||
end
|
||||
|
||||
fs.base_name = function(path)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.base_name: path must be a string", 2)
|
||||
end
|
||||
return __fs_base_name(path)
|
||||
end
|
||||
|
||||
fs.extension = function(path)
|
||||
if type(path) ~= "string" then
|
||||
error("fs.extension: path must be a string", 2)
|
||||
end
|
||||
return __fs_extension(path)
|
||||
end
|
||||
|
||||
-- Utility Functions
|
||||
fs.read_json = function(path)
|
||||
local content = fs.read_file(path)
|
||||
if not content then
|
||||
return nil, "Could not read file"
|
||||
end
|
||||
|
||||
local ok, result = pcall(json.decode, content)
|
||||
if not ok then
|
||||
return nil, "Invalid JSON: " .. tostring(result)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
fs.write_json = function(path, data, pretty)
|
||||
if type(data) ~= "table" then
|
||||
error("fs.write_json: data must be a table", 2)
|
||||
end
|
||||
|
||||
local content
|
||||
if pretty then
|
||||
content = json.pretty_print(data)
|
||||
else
|
||||
content = json.encode(data)
|
||||
end
|
||||
|
||||
return fs.write_file(path, content)
|
||||
end
|
||||
|
||||
return fs
|
@ -39,6 +39,7 @@ type Runner struct {
|
||||
poolSize int // Size of the state pool
|
||||
moduleLoader *ModuleLoader // Module loader
|
||||
dataDir string // Data directory for SQLite databases
|
||||
fsDir string // Virtual filesystem directory
|
||||
isRunning atomic.Bool // Whether the runner is active
|
||||
mu sync.RWMutex // Mutex for thread safety
|
||||
scriptDir string // Current script directory
|
||||
@ -75,12 +76,22 @@ func WithDataDir(dataDir string) RunnerOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithFsDir sets the virtual filesystem directory
|
||||
func WithFsDir(fsDir string) RunnerOption {
|
||||
return func(r *Runner) {
|
||||
if fsDir != "" {
|
||||
r.fsDir = fsDir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewRunner creates a new Runner with a pool of states
|
||||
func NewRunner(options ...RunnerOption) (*Runner, error) {
|
||||
// Default configuration
|
||||
runner := &Runner{
|
||||
poolSize: runtime.GOMAXPROCS(0),
|
||||
dataDir: "data",
|
||||
fsDir: "fs",
|
||||
}
|
||||
|
||||
// Apply options
|
||||
@ -97,8 +108,8 @@ func NewRunner(options ...RunnerOption) (*Runner, error) {
|
||||
runner.moduleLoader = NewModuleLoader(config)
|
||||
}
|
||||
|
||||
// Initialize SQLite
|
||||
InitSQLite(runner.dataDir)
|
||||
InitFS(runner.fsDir)
|
||||
|
||||
// Initialize states and pool
|
||||
runner.states = make([]*State, runner.poolSize)
|
||||
@ -273,7 +284,7 @@ cleanup:
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up SQLite
|
||||
CleanupFS()
|
||||
CleanupSQLite()
|
||||
|
||||
logger.Debug("Runner closed")
|
||||
|
@ -105,6 +105,10 @@ func (s *Sandbox) registerCoreFunctions(state *luajit.State) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := RegisterFSFunctions(state); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user