github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/lib/runtimelib/runtimelib.go (about) 1 package runtimelib 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "github.com/arnodel/golua/lib/packagelib" 9 rt "github.com/arnodel/golua/runtime" 10 ) 11 12 var LibLoader = packagelib.Loader{ 13 Load: load, 14 Name: "runtime", 15 } 16 17 func load(r *rt.Runtime) (rt.Value, func()) { 18 if !rt.QuotasAvailable { 19 return rt.NilValue, nil 20 } 21 pkg := rt.NewTable() 22 23 rt.SolemnlyDeclareCompliance( 24 rt.ComplyCpuSafe|rt.ComplyMemSafe|rt.ComplyTimeSafe|rt.ComplyIoSafe, 25 26 r.SetEnvGoFunc(pkg, "callcontext", callcontext, 2, true), 27 r.SetEnvGoFunc(pkg, "context", context, 0, false), 28 r.SetEnvGoFunc(pkg, "killcontext", killnow, 1, false), 29 r.SetEnvGoFunc(pkg, "stopcontext", stopnow, 1, false), 30 r.SetEnvGoFunc(pkg, "contextdue", due, 1, false), 31 ) 32 33 createContextMetatable(r) 34 35 return rt.TableValue(pkg), nil 36 } 37 38 func context(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 39 ctx := newContextValue(t.Runtime, t.RuntimeContext()) 40 return c.PushingNext1(t.Runtime, ctx), nil 41 } 42 43 func callcontext(t *rt.Thread, c *rt.GoCont) (next rt.Cont, retErr error) { 44 quotas, err := c.TableArg(0) 45 if err != nil { 46 return nil, err 47 } 48 var ( 49 flagsV = quotas.Get(rt.StringValue("flags")) 50 limitsV = quotas.Get(rt.StringValue("kill")) 51 softLimitsV = quotas.Get(rt.StringValue("stop")) 52 hardLimits rt.RuntimeResources 53 softLimits rt.RuntimeResources 54 f = c.Arg(1) 55 fArgs = c.Etc() 56 flags rt.ComplianceFlags 57 ) 58 if !limitsV.IsNil() { 59 var err error 60 hardLimits, err = getResources(t, limitsV) 61 if err != nil { 62 return nil, err 63 } 64 } 65 if !softLimitsV.IsNil() { 66 var err error 67 softLimits, err = getResources(t, softLimitsV) 68 if err != nil { 69 return nil, err 70 } 71 } 72 if !flagsV.IsNil() { 73 flagsStr, ok := flagsV.TryString() 74 if !ok { 75 return nil, errors.New("flags must be a string") 76 } 77 for _, name := range strings.Fields(flagsStr) { 78 flags, ok = flags.AddFlagWithName(name) 79 if !ok { 80 return nil, fmt.Errorf("unknown flag: %q", name) 81 } 82 } 83 } 84 85 next = c.Next() 86 res := rt.NewTerminationWith(c, 0, true) 87 88 ctx, err := t.CallContext(rt.RuntimeContextDef{ 89 HardLimits: hardLimits, 90 SoftLimits: softLimits, 91 RequiredFlags: flags, 92 }, func() error { 93 return rt.Call(t, f, fArgs, res) 94 }) 95 t.Push1(next, newContextValue(t.Runtime, ctx)) 96 switch ctx.Status() { 97 case rt.StatusDone: 98 t.Push(next, res.Etc()...) 99 case rt.StatusError: 100 t.Push1(next, rt.ErrorValue(err)) 101 } 102 return next, nil 103 } 104 105 func getResources(t *rt.Thread, resources rt.Value) (res rt.RuntimeResources, err error) { 106 res.Cpu, err = getResVal(t, resources, cpuString) 107 if err != nil { 108 return 109 } 110 res.Memory, err = getResVal(t, resources, memoryString) 111 if err != nil { 112 return 113 } 114 res.Millis, err = getTimeVal(t, resources) 115 if err != nil { 116 return 117 } 118 return 119 } 120 121 func getResVal(t *rt.Thread, resources rt.Value, key rt.Value) (uint64, error) { 122 val, err := rt.Index(t, resources, key) 123 if err != nil { 124 return 0, err 125 } 126 return validateResVal(key, val) 127 } 128 129 func validateResVal(key rt.Value, val rt.Value) (uint64, error) { 130 if val.IsNil() { 131 return 0, nil 132 } 133 n, ok := rt.ToIntNoString(val) 134 if !ok { 135 name, _ := key.ToString() 136 return 0, fmt.Errorf("%s must be an integer", name) 137 } 138 if n <= 0 { 139 name, _ := key.ToString() 140 return 0, fmt.Errorf("%s must be a positive integer", name) 141 } 142 return uint64(n), nil 143 } 144 145 func getTimeVal(t *rt.Thread, resources rt.Value) (uint64, error) { 146 val, err := rt.Index(t, resources, secondsString) 147 if err != nil { 148 return 0, err 149 } 150 if !val.IsNil() { 151 return validateTimeVal(val, 1000, secondsName) 152 } 153 val, err = rt.Index(t, resources, millisString) 154 if err != nil { 155 return 0, err 156 } 157 return validateTimeVal(val, 1, millisName) 158 } 159 160 func validateTimeVal(val rt.Value, factor float64, name string) (uint64, error) { 161 if val.IsNil() { 162 return 0, nil 163 } 164 s, ok := rt.ToFloat(val) 165 if !ok { 166 return 0, fmt.Errorf("%s must be a numeric value", name) 167 } 168 if s <= 0 { 169 return 0, fmt.Errorf("%s must be positive", name) 170 } 171 return uint64(s * factor), nil 172 } 173 174 const ( 175 secondsName = "seconds" 176 millisName = "millis" 177 cpuName = "cpu" 178 memoryName = "memory" 179 ) 180 181 var ( 182 secondsString = rt.StringValue(secondsName) 183 millisString = rt.StringValue(millisName) 184 cpuString = rt.StringValue(cpuName) 185 memoryString = rt.StringValue(memoryName) 186 )