060e45f951313be863d835de274c2c7214f5321f
[project/luci.git] / modules / luci-base / luasrc / util.lua
1 --[[
2 LuCI - Utility library
3
4 Description:
5 Several common useful Lua functions
6
7 License:
8 Copyright 2008 Steven Barth <steven@midlink.org>
9
10 Licensed under the Apache License, Version 2.0 (the "License");
11 you may not use this file except in compliance with the License.
12 You may obtain a copy of the License at
13
14         http://www.apache.org/licenses/LICENSE-2.0
15
16 Unless required by applicable law or agreed to in writing, software
17 distributed under the License is distributed on an "AS IS" BASIS,
18 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 See the License for the specific language governing permissions and
20 limitations under the License.
21
22 ]]--
23
24 local io = require "io"
25 local math = require "math"
26 local table = require "table"
27 local debug = require "debug"
28 local ldebug = require "luci.debug"
29 local string = require "string"
30 local coroutine = require "coroutine"
31 local tparser = require "luci.template.parser"
32
33 local getmetatable, setmetatable = getmetatable, setmetatable
34 local rawget, rawset, unpack = rawget, rawset, unpack
35 local tostring, type, assert = tostring, type, assert
36 local ipairs, pairs, next, loadstring = ipairs, pairs, next, loadstring
37 local require, pcall, xpcall = require, pcall, xpcall
38 local collectgarbage, get_memory_limit = collectgarbage, get_memory_limit
39
40 --- LuCI utility functions.
41 module "luci.util"
42
43 --
44 -- Pythonic string formatting extension
45 --
46 getmetatable("").__mod = function(a, b)
47         if not b then
48                 return a
49         elseif type(b) == "table" then
50                 for k, _ in pairs(b) do if type(b[k]) == "userdata" then b[k] = tostring(b[k]) end end
51                 return a:format(unpack(b))
52         else
53                 if type(b) == "userdata" then b = tostring(b) end
54                 return a:format(b)
55         end
56 end
57
58
59 --
60 -- Class helper routines
61 --
62
63 -- Instantiates a class
64 local function _instantiate(class, ...)
65         local inst = setmetatable({}, {__index = class})
66
67         if inst.__init__ then
68                 inst:__init__(...)
69         end
70
71         return inst
72 end
73
74 --- Create a Class object (Python-style object model).
75 -- The class object can be instantiated by calling itself.
76 -- Any class functions or shared parameters can be attached to this object.
77 -- Attaching a table to the class object makes this table shared between
78 -- all instances of this class. For object parameters use the __init__ function.
79 -- Classes can inherit member functions and values from a base class.
80 -- Class can be instantiated by calling them. All parameters will be passed
81 -- to the __init__ function of this class - if such a function exists.
82 -- The __init__ function must be used to set any object parameters that are not shared
83 -- with other objects of this class. Any return values will be ignored.
84 -- @param base  The base class to inherit from (optional)
85 -- @return              A class object
86 -- @see                 instanceof
87 -- @see                 clone
88 function class(base)
89         return setmetatable({}, {
90                 __call  = _instantiate,
91                 __index = base
92         })
93 end
94
95 --- Test whether the given object is an instance of the given class.
96 -- @param object        Object instance
97 -- @param class         Class object to test against
98 -- @return                      Boolean indicating whether the object is an instance
99 -- @see                         class
100 -- @see                         clone
101 function instanceof(object, class)
102         local meta = getmetatable(object)
103         while meta and meta.__index do
104                 if meta.__index == class then
105                         return true
106                 end
107                 meta = getmetatable(meta.__index)
108         end
109         return false
110 end
111
112
113 --
114 -- Scope manipulation routines
115 --
116
117 local tl_meta = {
118         __mode = "k",
119
120         __index = function(self, key)
121                 local t = rawget(self, coxpt[coroutine.running()]
122                  or coroutine.running() or 0)
123                 return t and t[key]
124         end,
125
126         __newindex = function(self, key, value)
127                 local c = coxpt[coroutine.running()] or coroutine.running() or 0
128                 local r = rawget(self, c)
129                 if not r then
130                         rawset(self, c, { [key] = value })
131                 else
132                         r[key] = value
133                 end
134         end
135 }
136
137 --- Create a new or get an already existing thread local store associated with
138 -- the current active coroutine. A thread local store is private a table object
139 -- whose values can't be accessed from outside of the running coroutine.
140 -- @return      Table value representing the corresponding thread local store
141 function threadlocal(tbl)
142         return setmetatable(tbl or {}, tl_meta)
143 end
144
145
146 --
147 -- Debugging routines
148 --
149
150 --- Write given object to stderr.
151 -- @param obj   Value to write to stderr
152 -- @return              Boolean indicating whether the write operation was successful
153 function perror(obj)
154         return io.stderr:write(tostring(obj) .. "\n")
155 end
156
157 --- Recursively dumps a table to stdout, useful for testing and debugging.
158 -- @param t     Table value to dump
159 -- @param maxdepth      Maximum depth
160 -- @return      Always nil
161 function dumptable(t, maxdepth, i, seen)
162         i = i or 0
163         seen = seen or setmetatable({}, {__mode="k"})
164
165         for k,v in pairs(t) do
166                 perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v))
167                 if type(v) == "table" and (not maxdepth or i < maxdepth) then
168                         if not seen[v] then
169                                 seen[v] = true
170                                 dumptable(v, maxdepth, i+1, seen)
171                         else
172                                 perror(string.rep("\t", i) .. "*** RECURSION ***")
173                         end
174                 end
175         end
176 end
177
178
179 --
180 -- String and data manipulation routines
181 --
182
183 --- Create valid XML PCDATA from given string.
184 -- @param value String value containing the data to escape
185 -- @return              String value containing the escaped data
186 function pcdata(value)
187         return value and tparser.pcdata(tostring(value))
188 end
189
190 --- Strip HTML tags from given string.
191 -- @param value String containing the HTML text
192 -- @return      String with HTML tags stripped of
193 function striptags(value)
194         return value and tparser.striptags(tostring(value))
195 end
196
197 --- Splits given string on a defined separator sequence and return a table
198 -- containing the resulting substrings. The optional max parameter specifies
199 -- the number of bytes to process, regardless of the actual length of the given
200 -- string. The optional last parameter, regex, specifies whether the separator
201 -- sequence is interpreted as regular expression.
202 -- @param str           String value containing the data to split up
203 -- @param pat           String with separator pattern (optional, defaults to "\n")
204 -- @param max           Maximum times to split (optional)
205 -- @param regex         Boolean indicating whether to interpret the separator
206 --                                      pattern as regular expression (optional, default is false)
207 -- @return                      Table containing the resulting substrings
208 function split(str, pat, max, regex)
209         pat = pat or "\n"
210         max = max or #str
211
212         local t = {}
213         local c = 1
214
215         if #str == 0 then
216                 return {""}
217         end
218
219         if #pat == 0 then
220                 return nil
221         end
222
223         if max == 0 then
224                 return str
225         end
226
227         repeat
228                 local s, e = str:find(pat, c, not regex)
229                 max = max - 1
230                 if s and max < 0 then
231                         t[#t+1] = str:sub(c)
232                 else
233                         t[#t+1] = str:sub(c, s and s - 1)
234                 end
235                 c = e and e + 1 or #str + 1
236         until not s or max < 0
237
238         return t
239 end
240
241 --- Remove leading and trailing whitespace from given string value.
242 -- @param str   String value containing whitespace padded data
243 -- @return              String value with leading and trailing space removed
244 function trim(str)
245         return (str:gsub("^%s*(.-)%s*$", "%1"))
246 end
247
248 --- Count the occurences of given substring in given string.
249 -- @param str           String to search in
250 -- @param pattern       String containing pattern to find
251 -- @return                      Number of found occurences
252 function cmatch(str, pat)
253         local count = 0
254         for _ in str:gmatch(pat) do count = count + 1 end
255         return count
256 end
257
258 --- Return a matching iterator for the given value. The iterator will return
259 -- one token per invocation, the tokens are separated by whitespace. If the
260 -- input value is a table, it is transformed into a string first. A nil value
261 -- will result in a valid interator which aborts with the first invocation.
262 -- @param val           The value to scan (table, string or nil)
263 -- @return                      Iterator which returns one token per call
264 function imatch(v)
265         if type(v) == "table" then
266                 local k = nil
267                 return function()
268                         k = next(v, k)
269                         return v[k]
270                 end
271
272         elseif type(v) == "number" or type(v) == "boolean" then
273                 local x = true
274                 return function()
275                         if x then
276                                 x = false
277                                 return tostring(v)
278                         end
279                 end
280
281         elseif type(v) == "userdata" or type(v) == "string" then
282                 return tostring(v):gmatch("%S+")
283         end
284
285         return function() end
286 end
287
288 --- Parse certain units from the given string and return the canonical integer
289 -- value or 0 if the unit is unknown. Upper- or lower case is irrelevant.
290 -- Recognized units are:
291 --      o "y"   - one year   (60*60*24*366)
292 --  o "m"       - one month  (60*60*24*31)
293 --  o "w"       - one week   (60*60*24*7)
294 --  o "d"       - one day    (60*60*24)
295 --  o "h"       - one hour       (60*60)
296 --  o "min"     - one minute (60)
297 --  o "kb"  - one kilobyte (1024)
298 --  o "mb"      - one megabyte (1024*1024)
299 --  o "gb"      - one gigabyte (1024*1024*1024)
300 --  o "kib" - one si kilobyte (1000)
301 --  o "mib"     - one si megabyte (1000*1000)
302 --  o "gib"     - one si gigabyte (1000*1000*1000)
303 -- @param ustr  String containing a numerical value with trailing unit
304 -- @return              Number containing the canonical value
305 function parse_units(ustr)
306
307         local val = 0
308
309         -- unit map
310         local map = {
311                 -- date stuff
312                 y   = 60 * 60 * 24 * 366,
313                 m   = 60 * 60 * 24 * 31,
314                 w   = 60 * 60 * 24 * 7,
315                 d   = 60 * 60 * 24,
316                 h   = 60 * 60,
317                 min = 60,
318
319                 -- storage sizes
320                 kb  = 1024,
321                 mb  = 1024 * 1024,
322                 gb  = 1024 * 1024 * 1024,
323
324                 -- storage sizes (si)
325                 kib = 1000,
326                 mib = 1000 * 1000,
327                 gib = 1000 * 1000 * 1000
328         }
329
330         -- parse input string
331         for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do
332
333                 local num = spec:gsub("[^0-9%.]+$","")
334                 local spn = spec:gsub("^[0-9%.]+", "")
335
336                 if map[spn] or map[spn:sub(1,1)] then
337                         val = val + num * ( map[spn] or map[spn:sub(1,1)] )
338                 else
339                         val = val + num
340                 end
341         end
342
343
344         return val
345 end
346
347 -- also register functions above in the central string class for convenience
348 string.pcdata      = pcdata
349 string.striptags   = striptags
350 string.split       = split
351 string.trim        = trim
352 string.cmatch      = cmatch
353 string.parse_units = parse_units
354
355
356 --- Appends numerically indexed tables or single objects to a given table.
357 -- @param src   Target table
358 -- @param ...   Objects to insert
359 -- @return              Target table
360 function append(src, ...)
361         for i, a in ipairs({...}) do
362                 if type(a) == "table" then
363                         for j, v in ipairs(a) do
364                                 src[#src+1] = v
365                         end
366                 else
367                         src[#src+1] = a
368                 end
369         end
370         return src
371 end
372
373 --- Combines two or more numerically indexed tables and single objects into one table.
374 -- @param tbl1  Table value to combine
375 -- @param tbl2  Table value to combine
376 -- @param ...   More tables to combine
377 -- @return              Table value containing all values of given tables
378 function combine(...)
379         return append({}, ...)
380 end
381
382 --- Checks whether the given table contains the given value.
383 -- @param table Table value
384 -- @param value Value to search within the given table
385 -- @return              Boolean indicating whether the given value occurs within table
386 function contains(table, value)
387         for k, v in pairs(table) do
388                 if value == v then
389                         return k
390                 end
391         end
392         return false
393 end
394
395 --- Update values in given table with the values from the second given table.
396 -- Both table are - in fact - merged together.
397 -- @param t                     Table which should be updated
398 -- @param updates       Table containing the values to update
399 -- @return                      Always nil
400 function update(t, updates)
401         for k, v in pairs(updates) do
402                 t[k] = v
403         end
404 end
405
406 --- Retrieve all keys of given associative table.
407 -- @param t     Table to extract keys from
408 -- @return      Sorted table containing the keys
409 function keys(t)
410         local keys = { }
411         if t then
412                 for k, _ in kspairs(t) do
413                         keys[#keys+1] = k
414                 end
415         end
416         return keys
417 end
418
419 --- Clones the given object and return it's copy.
420 -- @param object        Table value to clone
421 -- @param deep          Boolean indicating whether to do recursive cloning
422 -- @return                      Cloned table value
423 function clone(object, deep)
424         local copy = {}
425
426         for k, v in pairs(object) do
427                 if deep and type(v) == "table" then
428                         v = clone(v, deep)
429                 end
430                 copy[k] = v
431         end
432
433         return setmetatable(copy, getmetatable(object))
434 end
435
436
437 --- Create a dynamic table which automatically creates subtables.
438 -- @return      Dynamic Table
439 function dtable()
440         return setmetatable({}, { __index =
441                 function(tbl, key)
442                         return rawget(tbl, key)
443                          or rawget(rawset(tbl, key, dtable()), key)
444                 end
445         })
446 end
447
448
449 -- Serialize the contents of a table value.
450 function _serialize_table(t, seen)
451         assert(not seen[t], "Recursion detected.")
452         seen[t] = true
453
454         local data  = ""
455         local idata = ""
456         local ilen  = 0
457
458         for k, v in pairs(t) do
459                 if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then
460                         k = serialize_data(k, seen)
461                         v = serialize_data(v, seen)
462                         data = data .. ( #data > 0 and ", " or "" ) ..
463                                 '[' .. k .. '] = ' .. v
464                 elseif k > ilen then
465                         ilen = k
466                 end
467         end
468
469         for i = 1, ilen do
470                 local v = serialize_data(t[i], seen)
471                 idata = idata .. ( #idata > 0 and ", " or "" ) .. v
472         end
473
474         return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data
475 end
476
477 --- Recursively serialize given data to lua code, suitable for restoring
478 -- with loadstring().
479 -- @param val   Value containing the data to serialize
480 -- @return              String value containing the serialized code
481 -- @see                 restore_data
482 -- @see                 get_bytecode
483 function serialize_data(val, seen)
484         seen = seen or setmetatable({}, {__mode="k"})
485
486         if val == nil then
487                 return "nil"
488         elseif type(val) == "number" then
489                 return val
490         elseif type(val) == "string" then
491                 return "%q" % val
492         elseif type(val) == "boolean" then
493                 return val and "true" or "false"
494         elseif type(val) == "function" then
495                 return "loadstring(%q)" % get_bytecode(val)
496         elseif type(val) == "table" then
497                 return "{ " .. _serialize_table(val, seen) .. " }"
498         else
499                 return '"[unhandled data type:' .. type(val) .. ']"'
500         end
501 end
502
503 --- Restore data previously serialized with serialize_data().
504 -- @param str   String containing the data to restore
505 -- @return              Value containing the restored data structure
506 -- @see                 serialize_data
507 -- @see                 get_bytecode
508 function restore_data(str)
509         return loadstring("return " .. str)()
510 end
511
512
513 --
514 -- Byte code manipulation routines
515 --
516
517 --- Return the current runtime bytecode of the given data. The byte code
518 -- will be stripped before it is returned.
519 -- @param val   Value to return as bytecode
520 -- @return              String value containing the bytecode of the given data
521 function get_bytecode(val)
522         local code
523
524         if type(val) == "function" then
525                 code = string.dump(val)
526         else
527                 code = string.dump( loadstring( "return " .. serialize_data(val) ) )
528         end
529
530         return code -- and strip_bytecode(code)
531 end
532
533 --- Strips unnescessary lua bytecode from given string. Information like line
534 -- numbers and debugging numbers will be discarded. Original version by
535 -- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
536 -- @param code  String value containing the original lua byte code
537 -- @return              String value containing the stripped lua byte code
538 function strip_bytecode(code)
539         local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12)
540         local subint
541         if endian == 1 then
542                 subint = function(code, i, l)
543                         local val = 0
544                         for n = l, 1, -1 do
545                                 val = val * 256 + code:byte(i + n - 1)
546                         end
547                         return val, i + l
548                 end
549         else
550                 subint = function(code, i, l)
551                         local val = 0
552                         for n = 1, l, 1 do
553                                 val = val * 256 + code:byte(i + n - 1)
554                         end
555                         return val, i + l
556                 end
557         end
558
559         local function strip_function(code)
560                 local count, offset = subint(code, 1, size)
561                 local stripped = { string.rep("\0", size) }
562                 local dirty = offset + count
563                 offset = offset + count + int * 2 + 4
564                 offset = offset + int + subint(code, offset, int) * ins
565                 count, offset = subint(code, offset, int)
566                 for n = 1, count do
567                         local t
568                         t, offset = subint(code, offset, 1)
569                         if t == 1 then
570                                 offset = offset + 1
571                         elseif t == 4 then
572                                 offset = offset + size + subint(code, offset, size)
573                         elseif t == 3 then
574                                 offset = offset + num
575                         elseif t == 254 or t == 9 then
576                                 offset = offset + lnum
577                         end
578                 end
579                 count, offset = subint(code, offset, int)
580                 stripped[#stripped+1] = code:sub(dirty, offset - 1)
581                 for n = 1, count do
582                         local proto, off = strip_function(code:sub(offset, -1))
583                         stripped[#stripped+1] = proto
584                         offset = offset + off - 1
585                 end
586                 offset = offset + subint(code, offset, int) * int + int
587                 count, offset = subint(code, offset, int)
588                 for n = 1, count do
589                         offset = offset + subint(code, offset, size) + size + int * 2
590                 end
591                 count, offset = subint(code, offset, int)
592                 for n = 1, count do
593                         offset = offset + subint(code, offset, size) + size
594                 end
595                 stripped[#stripped+1] = string.rep("\0", int * 3)
596                 return table.concat(stripped), offset
597         end
598
599         return code:sub(1,12) .. strip_function(code:sub(13,-1))
600 end
601
602
603 --
604 -- Sorting iterator functions
605 --
606
607 function _sortiter( t, f )
608         local keys = { }
609
610         local k, v
611         for k, v in pairs(t) do
612                 keys[#keys+1] = k
613         end
614
615         local _pos = 0
616
617         table.sort( keys, f )
618
619         return function()
620                 _pos = _pos + 1
621                 if _pos <= #keys then
622                         return keys[_pos], t[keys[_pos]], _pos
623                 end
624         end
625 end
626
627 --- Return a key, value iterator which returns the values sorted according to
628 -- the provided callback function.
629 -- @param t     The table to iterate
630 -- @param f A callback function to decide the order of elements
631 -- @return      Function value containing the corresponding iterator
632 function spairs(t,f)
633         return _sortiter( t, f )
634 end
635
636 --- Return a key, value iterator for the given table.
637 -- The table pairs are sorted by key.
638 -- @param t     The table to iterate
639 -- @return      Function value containing the corresponding iterator
640 function kspairs(t)
641         return _sortiter( t )
642 end
643
644 --- Return a key, value iterator for the given table.
645 -- The table pairs are sorted by value.
646 -- @param t     The table to iterate
647 -- @return      Function value containing the corresponding iterator
648 function vspairs(t)
649         return _sortiter( t, function (a,b) return t[a] < t[b] end )
650 end
651
652
653 --
654 -- System utility functions
655 --
656
657 --- Test whether the current system is operating in big endian mode.
658 -- @return      Boolean value indicating whether system is big endian
659 function bigendian()
660         return string.byte(string.dump(function() end), 7) == 0
661 end
662
663 --- Execute given commandline and gather stdout.
664 -- @param command       String containing command to execute
665 -- @return                      String containing the command's stdout
666 function exec(command)
667         local pp   = io.popen(command)
668         local data = pp:read("*a")
669         pp:close()
670
671         return data
672 end
673
674 --- Return a line-buffered iterator over the output of given command.
675 -- @param command       String containing the command to execute
676 -- @return                      Iterator
677 function execi(command)
678         local pp = io.popen(command)
679
680         return pp and function()
681                 local line = pp:read()
682
683                 if not line then
684                         pp:close()
685                 end
686
687                 return line
688         end
689 end
690
691 -- Deprecated
692 function execl(command)
693         local pp   = io.popen(command)
694         local line = ""
695         local data = {}
696
697         while true do
698                 line = pp:read()
699                 if (line == nil) then break end
700                 data[#data+1] = line
701         end
702         pp:close()
703
704         return data
705 end
706
707 --- Returns the absolute path to LuCI base directory.
708 -- @return              String containing the directory path
709 function libpath()
710         return require "nixio.fs".dirname(ldebug.__file__)
711 end
712
713
714 --
715 -- Coroutine safe xpcall and pcall versions modified for Luci
716 -- original version:
717 -- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org)
718 --
719 -- Copyright © 2005 Kepler Project.
720 -- Permission is hereby granted, free of charge, to any person obtaining a
721 -- copy of this software and associated documentation files (the "Software"),
722 -- to deal in the Software without restriction, including without limitation
723 -- the rights to use, copy, modify, merge, publish, distribute, sublicense,
724 -- and/or sell copies of the Software, and to permit persons to whom the
725 -- Software is furnished to do so, subject to the following conditions:
726 --
727 -- The above copyright notice and this permission notice shall be
728 -- included in all copies or substantial portions of the Software.
729 --
730 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
731 -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
732 -- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
733 -- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
734 -- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
735 -- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
736 -- OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
737
738 local performResume, handleReturnValue
739 local oldpcall, oldxpcall = pcall, xpcall
740 coxpt = {}
741 setmetatable(coxpt, {__mode = "kv"})
742
743 -- Identity function for copcall
744 local function copcall_id(trace, ...)
745   return ...
746 end
747
748 --- This is a coroutine-safe drop-in replacement for Lua's "xpcall"-function
749 -- @param f             Lua function to be called protected
750 -- @param err   Custom error handler
751 -- @param ...   Parameters passed to the function
752 -- @return              A boolean whether the function call succeeded and the return
753 --                              values of either the function or the error handler
754 function coxpcall(f, err, ...)
755         local res, co = oldpcall(coroutine.create, f)
756         if not res then
757                 local params = {...}
758                 local newf = function() return f(unpack(params)) end
759                 co = coroutine.create(newf)
760         end
761         local c = coroutine.running()
762         coxpt[co] = coxpt[c] or c or 0
763
764         return performResume(err, co, ...)
765 end
766
767 --- This is a coroutine-safe drop-in replacement for Lua's "pcall"-function
768 -- @param f             Lua function to be called protected
769 -- @param ...   Parameters passed to the function
770 -- @return              A boolean whether the function call succeeded and the returns
771 --                              values of the function or the error object
772 function copcall(f, ...)
773         return coxpcall(f, copcall_id, ...)
774 end
775
776 -- Handle return value of protected call
777 function handleReturnValue(err, co, status, ...)
778         if not status then
779                 return false, err(debug.traceback(co, (...)), ...)
780         end
781
782         if coroutine.status(co) ~= 'suspended' then
783                 return true, ...
784         end
785
786         return performResume(err, co, coroutine.yield(...))
787 end
788
789 -- Resume execution of protected function call
790 function performResume(err, co, ...)
791         return handleReturnValue(err, co, coroutine.resume(co, ...))
792 end