mirror of
https://github.com/vxcontrol/vxcommon.git
synced 2026-07-21 04:25:27 -04:00
273 lines
6.6 KiB
Go
273 lines
6.6 KiB
Go
package lua
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/vxcontrol/golua/lua"
|
|
"github.com/vxcontrol/luar"
|
|
)
|
|
|
|
func moduleLoader(L *lua.State) int {
|
|
moduleName := L.CheckString(1)
|
|
|
|
logger := logrus.WithFields(logrus.Fields{
|
|
"component": "state",
|
|
"module": moduleName,
|
|
})
|
|
var files map[string]string
|
|
L.GetGlobal("__files")
|
|
err := luar.LuaToGo(L, -1, &files)
|
|
L.Pop(1)
|
|
if err != nil {
|
|
logger.WithError(err).Error("failed put module files to the lua state")
|
|
}
|
|
|
|
var moduleNames []string
|
|
pathToModule := strings.Replace(moduleName, ".", "/", -1)
|
|
moduleNames = append(moduleNames, pathToModule+"/init.lua")
|
|
moduleNames = append(moduleNames, pathToModule+".lua")
|
|
for _, filePath := range moduleNames {
|
|
moduleData, ok := files[filePath]
|
|
if ok && L.LoadBuffer([]byte(moduleData), len(moduleData), moduleName) != 0 {
|
|
err = fmt.Errorf(L.ToString(-1))
|
|
logger.WithError(err).Error("failed put module data to the lua state")
|
|
L.Pop(1)
|
|
break
|
|
}
|
|
}
|
|
|
|
return 1
|
|
}
|
|
|
|
func registerCalls(L *lua.State) {
|
|
L.GetGlobal("unsafe_pcall")
|
|
L.SetGlobal("pcall")
|
|
L.GetGlobal("unsafe_xpcall")
|
|
L.SetGlobal("xpcall")
|
|
}
|
|
|
|
func registerLoader(L *lua.State) {
|
|
top := L.GetTop()
|
|
L.GetGlobal(lua.LUA_LOADLIBNAME)
|
|
L.GetField(-1, "loaders")
|
|
L.PushGoFunction(moduleLoader)
|
|
L.RawSeti(-2, int(L.ObjLen(-2)+1))
|
|
L.SetTop(top)
|
|
}
|
|
|
|
func normPath(args ...string) string {
|
|
var path string
|
|
|
|
if runtime.GOOS == "windows" {
|
|
args = append(args, "?.dll")
|
|
path = filepath.Join(args...)
|
|
path = strings.Replace(path, "\\", "\\\\", -1)
|
|
} else if runtime.GOOS == "darwin" {
|
|
path = filepath.Join(append(args, "lib?.dylib")...)
|
|
path = path + ";" + filepath.Join(append(args, "?.dylib")...)
|
|
path = path + ";" + filepath.Join(append(args, "lib?.so")...)
|
|
path = path + ";" + filepath.Join(append(args, "?.so")...)
|
|
} else {
|
|
path = filepath.Join(append(args, "lib?.so")...)
|
|
path = path + ";" + filepath.Join(append(args, "?.so")...)
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
func registerFFILoader(L *lua.State, tmpdir string) error {
|
|
var err error
|
|
regFFILoader := fmt.Sprintf(`
|
|
local ffi = require'ffi'
|
|
local ffi_load = ffi.load
|
|
--overload ffi.load for received system libs
|
|
function ffi.load(name, ...)
|
|
local libpath = package.searchpath(name, "%s")
|
|
if libpath ~= nil then
|
|
return ffi_load(libpath)
|
|
else
|
|
return ffi_load(name)
|
|
end
|
|
end`, normPath(tmpdir, "sys")+";"+normPath(tmpdir))
|
|
|
|
if err = L.DoString(regFFILoader); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func registerPanicRecover(L *lua.State) {
|
|
currentPanicf := L.AtPanic(nil)
|
|
currentPanicf = L.AtPanic(currentPanicf)
|
|
newPanic := func(L1 *lua.State) int {
|
|
message := L.ToString(-1)
|
|
logrus.WithField("message", message).Error("panic recovery")
|
|
if currentPanicf != nil {
|
|
return currentPanicf(L1)
|
|
}
|
|
return 1
|
|
}
|
|
L.AtPanic(newPanic)
|
|
}
|
|
|
|
// State is context of lua module
|
|
type State struct {
|
|
tmpdir string
|
|
closed bool
|
|
L *lua.State
|
|
logger *logrus.Entry
|
|
}
|
|
|
|
func stateDestructor(s *State) {
|
|
if s.L != nil {
|
|
s.L.Close()
|
|
os.RemoveAll(s.tmpdir)
|
|
s.L = nil
|
|
s.logger.Info("the state was destroyed")
|
|
}
|
|
}
|
|
|
|
// NewState is function which constructed State object
|
|
func NewState(files map[string][]byte) (*State, error) {
|
|
if files["main.lua"] == nil {
|
|
return nil, errors.New("main module not found")
|
|
}
|
|
|
|
nfiles := make(map[string][]byte)
|
|
for name, data := range files {
|
|
nfiles[name] = data
|
|
}
|
|
|
|
var err error
|
|
var tmpdir string
|
|
if tmpdir, err = ioutil.TempDir("", "vxlua-"); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pathToPID := filepath.Join(tmpdir, "lock.pid")
|
|
pid := strconv.Itoa(os.Getpid())
|
|
if err = ioutil.WriteFile(pathToPID, []byte(pid), 0640); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s := &State{
|
|
tmpdir: tmpdir,
|
|
closed: true,
|
|
L: luar.Init(),
|
|
logger: logrus.WithFields(logrus.Fields{
|
|
"component": "state",
|
|
"tmpdir": filepath.Base(tmpdir),
|
|
}),
|
|
}
|
|
runtime.SetFinalizer(s, stateDestructor)
|
|
|
|
registerCalls(s.L)
|
|
registerLoader(s.L)
|
|
registerPanicRecover(s.L)
|
|
|
|
if err = s.loadClibs(nfiles); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = s.loadData(nfiles); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lfiles := make(map[string]string)
|
|
for name, data := range nfiles {
|
|
lfiles[name] = string(data)
|
|
}
|
|
registerFFILoader(s.L, s.tmpdir)
|
|
luar.GoToLua(s.L, tmpdir)
|
|
s.L.SetGlobal("__tmpdir")
|
|
luar.GoToLua(s.L, lfiles)
|
|
s.L.SetGlobal("__files")
|
|
|
|
s.logger.Info("the state was created")
|
|
return s, nil
|
|
}
|
|
|
|
func (s *State) loadClibs(files map[string][]byte) error {
|
|
var err error
|
|
clibsPrefix := "clibs/"
|
|
strictPrefix := clibsPrefix + runtime.GOOS + "/" + runtime.GOARCH + "/"
|
|
|
|
for name, data := range files {
|
|
if strings.HasPrefix(name, strictPrefix) {
|
|
fname := filepath.Join(s.tmpdir, strings.TrimPrefix(name, strictPrefix))
|
|
fdir := filepath.Dir(fname)
|
|
os.MkdirAll(fdir, os.ModePerm)
|
|
if err = ioutil.WriteFile(fname, data, 0640); err != nil {
|
|
s.logger.WithError(err).WithField("name", fname).Error("failed to write file")
|
|
return err
|
|
}
|
|
}
|
|
if strings.HasPrefix(name, clibsPrefix) {
|
|
delete(files, name)
|
|
}
|
|
}
|
|
|
|
loadstr := fmt.Sprintf(`package.cpath = package.cpath .. ";%s"`, normPath(s.tmpdir))
|
|
if err = s.L.DoString(loadstr); err != nil {
|
|
s.logger.WithError(err).Error("failed to add new cpath to the lua state")
|
|
return err
|
|
}
|
|
|
|
s.logger.Debug("the state loaded clibs")
|
|
return nil
|
|
}
|
|
|
|
func (s *State) loadData(files map[string][]byte) error {
|
|
var err error
|
|
prefix := "data/"
|
|
|
|
for name, data := range files {
|
|
if strings.HasPrefix(name, prefix) {
|
|
fname := filepath.Join(s.tmpdir, name)
|
|
fdir := filepath.Dir(fname)
|
|
os.MkdirAll(fdir, os.ModePerm)
|
|
if err = ioutil.WriteFile(fname, data, 0640); err != nil {
|
|
s.logger.WithError(err).WithField("name", fname).Error("failed to write file")
|
|
return err
|
|
}
|
|
delete(files, name)
|
|
}
|
|
}
|
|
|
|
s.logger.Debug("the state loaded data")
|
|
return nil
|
|
}
|
|
|
|
// Exec is blocked function for data execution
|
|
func (s *State) Exec() (string, error) {
|
|
s.closed = false
|
|
defer func() {
|
|
s.closed = true
|
|
}()
|
|
|
|
s.logger.Info("the state was started")
|
|
defer s.logger.Info("the state was stopped")
|
|
|
|
err := s.L.DoString(`return require('main')`)
|
|
if err != nil {
|
|
s.logger.WithError(err).Error("the state executing catched error")
|
|
return "", err
|
|
}
|
|
|
|
return s.L.CheckString(1), nil
|
|
}
|
|
|
|
// IsClose is nonblocked function which check a state of module
|
|
func (s *State) IsClose() bool {
|
|
return s.closed
|
|
}
|