package stdlib import ( "fmt" "io" "io/ioutil" "os" "os/exec" "runtime" "github.com/d5/tengo/v2" ) var osModule = map[string]tengo.Object{ "platform": &tengo.String{Value: runtime.GOOS}, "arch": &tengo.String{Value: runtime.GOARCH}, "o_rdonly": &tengo.Int{Value: int64(os.O_RDONLY)}, "o_wronly": &tengo.Int{Value: int64(os.O_WRONLY)}, "o_rdwr": &tengo.Int{Value: int64(os.O_RDWR)}, "o_append": &tengo.Int{Value: int64(os.O_APPEND)}, "o_create": &tengo.Int{Value: int64(os.O_CREATE)}, "o_excl": &tengo.Int{Value: int64(os.O_EXCL)}, "o_sync": &tengo.Int{Value: int64(os.O_SYNC)}, "o_trunc": &tengo.Int{Value: int64(os.O_TRUNC)}, "mode_dir": &tengo.Int{Value: int64(os.ModeDir)}, "mode_append": &tengo.Int{Value: int64(os.ModeAppend)}, "mode_exclusive": &tengo.Int{Value: int64(os.ModeExclusive)}, "mode_temporary": &tengo.Int{Value: int64(os.ModeTemporary)}, "mode_symlink": &tengo.Int{Value: int64(os.ModeSymlink)}, "mode_device": &tengo.Int{Value: int64(os.ModeDevice)}, "mode_named_pipe": &tengo.Int{Value: int64(os.ModeNamedPipe)}, "mode_socket": &tengo.Int{Value: int64(os.ModeSocket)}, "mode_setuid": &tengo.Int{Value: int64(os.ModeSetuid)}, "mode_setgui": &tengo.Int{Value: int64(os.ModeSetgid)}, "mode_char_device": &tengo.Int{Value: int64(os.ModeCharDevice)}, "mode_sticky": &tengo.Int{Value: int64(os.ModeSticky)}, "mode_type": &tengo.Int{Value: int64(os.ModeType)}, "mode_perm": &tengo.Int{Value: int64(os.ModePerm)}, "path_separator": &tengo.Char{Value: os.PathSeparator}, "path_list_separator": &tengo.Char{Value: os.PathListSeparator}, "dev_null": &tengo.String{Value: os.DevNull}, "seek_set": &tengo.Int{Value: int64(io.SeekStart)}, "seek_cur": &tengo.Int{Value: int64(io.SeekCurrent)}, "seek_end": &tengo.Int{Value: int64(io.SeekEnd)}, "args": &tengo.UserFunction{ Name: "args", Value: osArgs, }, // args() => array(string) "chdir": &tengo.UserFunction{ Name: "chdir", Value: FuncASRE(os.Chdir), }, // chdir(dir string) => error "chmod": osFuncASFmRE("chmod", os.Chmod), // chmod(name string, mode int) => error "chown": &tengo.UserFunction{ Name: "chown", Value: FuncASIIRE(os.Chown), }, // chown(name string, uid int, gid int) => error "clearenv": &tengo.UserFunction{ Name: "clearenv", Value: FuncAR(os.Clearenv), }, // clearenv() "environ": &tengo.UserFunction{ Name: "environ", Value: FuncARSs(os.Environ), }, // environ() => array(string) "exit": &tengo.UserFunction{ Name: "exit", Value: FuncAIR(os.Exit), }, // exit(code int) "expand_env": &tengo.UserFunction{ Name: "expand_env", Value: osExpandEnv, }, // expand_env(s string) => string "getegid": &tengo.UserFunction{ Name: "getegid", Value: FuncARI(os.Getegid), }, // getegid() => int "getenv": &tengo.UserFunction{ Name: "getenv", Value: FuncASRS(os.Getenv), }, // getenv(s string) => string "geteuid": &tengo.UserFunction{ Name: "geteuid", Value: FuncARI(os.Geteuid), }, // geteuid() => int "getgid": &tengo.UserFunction{ Name: "getgid", Value: FuncARI(os.Getgid), }, // getgid() => int "getgroups": &tengo.UserFunction{ Name: "getgroups", Value: FuncARIsE(os.Getgroups), }, // getgroups() => array(string)/error "getpagesize": &tengo.UserFunction{ Name: "getpagesize", Value: FuncARI(os.Getpagesize), }, // getpagesize() => int "getpid": &tengo.UserFunction{ Name: "getpid", Value: FuncARI(os.Getpid), }, // getpid() => int "getppid": &tengo.UserFunction{ Name: "getppid", Value: FuncARI(os.Getppid), }, // getppid() => int "getuid": &tengo.UserFunction{ Name: "getuid", Value: FuncARI(os.Getuid), }, // getuid() => int "getwd": &tengo.UserFunction{ Name: "getwd", Value: FuncARSE(os.Getwd), }, // getwd() => string/error "hostname": &tengo.UserFunction{ Name: "hostname", Value: FuncARSE(os.Hostname), }, // hostname() => string/error "lchown": &tengo.UserFunction{ Name: "lchown", Value: FuncASIIRE(os.Lchown), }, // lchown(name string, uid int, gid int) => error "link": &tengo.UserFunction{ Name: "link", Value: FuncASSRE(os.Link), }, // link(oldname string, newname string) => error "lookup_env": &tengo.UserFunction{ Name: "lookup_env", Value: osLookupEnv, }, // lookup_env(key string) => string/false "mkdir": osFuncASFmRE("mkdir", os.Mkdir), // mkdir(name string, perm int) => error "mkdir_all": osFuncASFmRE("mkdir_all", os.MkdirAll), // mkdir_all(name string, perm int) => error "readlink": &tengo.UserFunction{ Name: "readlink", Value: FuncASRSE(os.Readlink), }, // readlink(name string) => string/error "remove": &tengo.UserFunction{ Name: "remove", Value: FuncASRE(os.Remove), }, // remove(name string) => error "remove_all": &tengo.UserFunction{ Name: "remove_all", Value: FuncASRE(os.RemoveAll), }, // remove_all(name string) => error "rename": &tengo.UserFunction{ Name: "rename", Value: FuncASSRE(os.Rename), }, // rename(oldpath string, newpath string) => error "setenv": &tengo.UserFunction{ Name: "setenv", Value: FuncASSRE(os.Setenv), }, // setenv(key string, value string) => error "symlink": &tengo.UserFunction{ Name: "symlink", Value: FuncASSRE(os.Symlink), }, // symlink(oldname string newname string) => error "temp_dir": &tengo.UserFunction{ Name: "temp_dir", Value: FuncARS(os.TempDir), }, // temp_dir() => string "truncate": &tengo.UserFunction{ Name: "truncate", Value: FuncASI64RE(os.Truncate), }, // truncate(name string, size int) => error "unsetenv": &tengo.UserFunction{ Name: "unsetenv", Value: FuncASRE(os.Unsetenv), }, // unsetenv(key string) => error "create": &tengo.UserFunction{ Name: "create", Value: osCreate, }, // create(name string) => imap(file)/error "open": &tengo.UserFunction{ Name: "open", Value: osOpen, }, // open(name string) => imap(file)/error "open_file": &tengo.UserFunction{ Name: "open_file", Value: osOpenFile, }, // open_file(name string, flag int, perm int) => imap(file)/error "find_process": &tengo.UserFunction{ Name: "find_process", Value: osFindProcess, }, // find_process(pid int) => imap(process)/error "start_process": &tengo.UserFunction{ Name: "start_process", Value: osStartProcess, }, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error "exec_look_path": &tengo.UserFunction{ Name: "exec_look_path", Value: FuncASRSE(exec.LookPath), }, // exec_look_path(file) => string/error "exec": &tengo.UserFunction{ Name: "exec", Value: osExec, }, // exec(name, args...) => command "stat": &tengo.UserFunction{ Name: "stat", Value: osStat, }, // stat(name) => imap(fileinfo)/error "read_file": &tengo.UserFunction{ Name: "read_file", Value: osReadFile, }, // readfile(name) => array(byte)/error } func osReadFile(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { return nil, tengo.ErrWrongNumArguments } fname, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } bytes, err := ioutil.ReadFile(fname) if err != nil { return wrapError(err), nil } if len(bytes) > tengo.MaxBytesLen { return nil, tengo.ErrBytesLimit } return &tengo.Bytes{Value: bytes}, nil } func osStat(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { return nil, tengo.ErrWrongNumArguments } fname, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } stat, err := os.Stat(fname) if err != nil { return wrapError(err), nil } fstat := &tengo.ImmutableMap{ Value: map[string]tengo.Object{ "name": &tengo.String{Value: stat.Name()}, "mtime": &tengo.Time{Value: stat.ModTime()}, "size": &tengo.Int{Value: stat.Size()}, "mode": &tengo.Int{Value: int64(stat.Mode())}, }, } if stat.IsDir() { fstat.Value["directory"] = tengo.TrueValue } else { fstat.Value["directory"] = tengo.FalseValue } return fstat, nil } func osCreate(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { return nil, tengo.ErrWrongNumArguments } s1, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } res, err := os.Create(s1) if err != nil { return wrapError(err), nil } return makeOSFile(res), nil } func osOpen(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { return nil, tengo.ErrWrongNumArguments } s1, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } res, err := os.Open(s1) if err != nil { return wrapError(err), nil } return makeOSFile(res), nil } func osOpenFile(args ...tengo.Object) (tengo.Object, error) { if len(args) != 3 { return nil, tengo.ErrWrongNumArguments } s1, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } i2, ok := tengo.ToInt(args[1]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } i3, ok := tengo.ToInt(args[2]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), } } res, err := os.OpenFile(s1, i2, os.FileMode(i3)) if err != nil { return wrapError(err), nil } return makeOSFile(res), nil } func osArgs(args ...tengo.Object) (tengo.Object, error) { if len(args) != 0 { return nil, tengo.ErrWrongNumArguments } arr := &tengo.Array{} for _, osArg := range os.Args { if len(osArg) > tengo.MaxStringLen { return nil, tengo.ErrStringLimit } arr.Value = append(arr.Value, &tengo.String{Value: osArg}) } return arr, nil } func osFuncASFmRE( name string, fn func(string, os.FileMode) error, ) *tengo.UserFunction { return &tengo.UserFunction{ Name: name, Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 2 { return nil, tengo.ErrWrongNumArguments } s1, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } i2, ok := tengo.ToInt64(args[1]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } return wrapError(fn(s1, os.FileMode(i2))), nil }, } } func osLookupEnv(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { return nil, tengo.ErrWrongNumArguments } s1, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } res, ok := os.LookupEnv(s1) if !ok { return tengo.FalseValue, nil } if len(res) > tengo.MaxStringLen { return nil, tengo.ErrStringLimit } return &tengo.String{Value: res}, nil } func osExpandEnv(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { return nil, tengo.ErrWrongNumArguments } s1, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } var vlen int var failed bool s := os.Expand(s1, func(k string) string { if failed { return "" } v := os.Getenv(k) // this does not count the other texts that are not being replaced // but the code checks the final length at the end vlen += len(v) if vlen > tengo.MaxStringLen { failed = true return "" } return v }) if failed || len(s) > tengo.MaxStringLen { return nil, tengo.ErrStringLimit } return &tengo.String{Value: s}, nil } func osExec(args ...tengo.Object) (tengo.Object, error) { if len(args) == 0 { return nil, tengo.ErrWrongNumArguments } name, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } var execArgs []string for idx, arg := range args[1:] { execArg, ok := tengo.ToString(arg) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: fmt.Sprintf("args[%d]", idx), Expected: "string(compatible)", Found: args[1+idx].TypeName(), } } execArgs = append(execArgs, execArg) } return makeOSExecCommand(exec.Command(name, execArgs...)), nil } func osFindProcess(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { return nil, tengo.ErrWrongNumArguments } i1, ok := tengo.ToInt(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } proc, err := os.FindProcess(i1) if err != nil { return wrapError(err), nil } return makeOSProcess(proc), nil } func osStartProcess(args ...tengo.Object) (tengo.Object, error) { if len(args) != 4 { return nil, tengo.ErrWrongNumArguments } name, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } var argv []string var err error switch arg1 := args[1].(type) { case *tengo.Array: argv, err = stringArray(arg1.Value, "second") if err != nil { return nil, err } case *tengo.ImmutableArray: argv, err = stringArray(arg1.Value, "second") if err != nil { return nil, err } default: return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "array", Found: arg1.TypeName(), } } dir, ok := tengo.ToString(args[2]) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: "third", Expected: "string(compatible)", Found: args[2].TypeName(), } } var env []string switch arg3 := args[3].(type) { case *tengo.Array: env, err = stringArray(arg3.Value, "fourth") if err != nil { return nil, err } case *tengo.ImmutableArray: env, err = stringArray(arg3.Value, "fourth") if err != nil { return nil, err } default: return nil, tengo.ErrInvalidArgumentType{ Name: "fourth", Expected: "array", Found: arg3.TypeName(), } } proc, err := os.StartProcess(name, argv, &os.ProcAttr{ Dir: dir, Env: env, }) if err != nil { return wrapError(err), nil } return makeOSProcess(proc), nil } func stringArray(arr []tengo.Object, argName string) ([]string, error) { var sarr []string for idx, elem := range arr { str, ok := elem.(*tengo.String) if !ok { return nil, tengo.ErrInvalidArgumentType{ Name: fmt.Sprintf("%s[%d]", argName, idx), Expected: "string", Found: elem.TypeName(), } } sarr = append(sarr, str.Value) } return sarr, nil }