changeset 0:73146cb10aa5

add some files
author Nobuyasu Oshiro <dimolto@cr.ie.u-ryukyu.ac.jp>
date Fri, 01 Feb 2013 03:07:15 +0900
parents
children e12fbdb2eac2
files bench/binary-trees.lua bench/bytecode.txt bench/defineClass.lua bench/luac.out bench/test.lua c/Makefile c/readLuaScript.c c/requireLib.lua c/testlib.lua module/a.lua module/secret.lua test.lua tools.lua
diffstat 13 files changed, 688 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bench/binary-trees.lua	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,51 @@
+-- The Computer Language Benchmarks Game
+-- http://shootout.alioth.debian.org/
+-- contributed by Mike Pall
+
+local function BottomUpTree(item, depth)
+  if depth > 0 then
+    local i = item + item
+    depth = depth - 1
+    local left, right = BottomUpTree(i-1, depth), BottomUpTree(i, depth)
+    return { item, left, right }
+  else
+      return { item }
+  end
+end
+
+local function ItemCheck(tree)
+    if tree[2] then
+	return tree[1] + ItemCheck(tree[2]) - ItemCheck(tree[3])
+  else
+      return tree[1]
+  end
+end
+
+local N = tonumber(arg and arg[1]) or 0
+local mindepth = 4
+local maxdepth = mindepth + 2
+if maxdepth < N then maxdepth = N end
+
+do
+  local stretchdepth = maxdepth + 1
+  local stretchtree = BottomUpTree(0, stretchdepth)
+  io.write(string.format("stretch tree of depth %d\t check: %d\n",
+			 stretchdepth, ItemCheck(stretchtree)))
+end
+
+local longlivedtree = BottomUpTree(0, maxdepth)
+
+for depth=mindepth,maxdepth,2 do
+    local iterations = 2 ^ (maxdepth - depth + mindepth)
+  local check = 0
+  for i=1,iterations do
+      check = check + ItemCheck(BottomUpTree(1, depth)) +
+	  ItemCheck(BottomUpTree(-1, depth))
+  end
+  io.write(string.format("%d\t trees of depth %d\t check: %d\n",
+			 iterations*2, depth, check))
+end
+
+io.write(string.format("long lived tree of depth %d\t check: %d\n",
+		       maxdepth, ItemCheck(longlivedtree)))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bench/bytecode.txt	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,139 @@
+
+main <binary-trees.lua:0,0> (89 instructions, 356 bytes at 0x7f8068c01940)
+0+ params, 21 slots, 0 upvalues, 18 locals, 14 constants, 2 functions
+	1	[14]	CLOSURE  	0 0	; 0x7f8068c01b60
+	2	[14]	MOVE     	0 0
+	3	[22]	CLOSURE  	1 1	; 0x7f8068c01ee0
+	4	[22]	MOVE     	0 1
+	5	[24]	GETGLOBAL	2 -1	; tonumber
+	6	[24]	GETGLOBAL	3 -2	; arg
+	7	[24]	TEST     	3 0 0
+	8	[24]	JMP      	2	; to 11
+	9	[24]	GETGLOBAL	3 -2	; arg
+	10	[24]	GETTABLE 	3 3 -3	; 1
+	11	[24]	CALL     	2 2 2
+	12	[24]	TEST     	2 0 1
+	13	[24]	JMP      	1	; to 15
+	14	[24]	LOADK    	2 -4	; 0
+	15	[25]	LOADK    	3 -5	; 4
+	16	[26]	ADD      	4 3 -6	; - 2
+	17	[27]	LT       	0 4 2
+	18	[27]	JMP      	1	; to 20
+	19	[27]	MOVE     	4 2
+	20	[30]	ADD      	5 4 -3	; - 1
+	21	[31]	MOVE     	6 0
+	22	[31]	LOADK    	7 -4	; 0
+	23	[31]	MOVE     	8 5
+	24	[31]	CALL     	6 3 2
+	25	[32]	GETGLOBAL	7 -7	; io
+	26	[32]	GETTABLE 	7 7 -8	; "write"
+	27	[32]	GETGLOBAL	8 -9	; string
+	28	[32]	GETTABLE 	8 8 -10	; "format"
+	29	[32]	LOADK    	9 -11	; "stretch tree of depth %d\t check: %d\n"
+	30	[33]	MOVE     	10 5
+	31	[33]	MOVE     	11 1
+	32	[33]	MOVE     	12 6
+	33	[33]	CALL     	11 2 0
+	34	[32]	CALL     	8 0 0
+	35	[32]	CALL     	7 0 1
+	36	[36]	MOVE     	5 0
+	37	[36]	LOADK    	6 -4	; 0
+	38	[36]	MOVE     	7 4
+	39	[36]	CALL     	5 3 2
+	40	[38]	MOVE     	6 3
+	41	[38]	MOVE     	7 4
+	42	[38]	LOADK    	8 -6	; 2
+	43	[38]	FORPREP  	6 33	; to 77
+	44	[39]	SUB      	10 4 9
+	45	[39]	ADD      	10 10 3
+	46	[39]	POW      	10 -6 10	; 2 -
+	47	[40]	LOADK    	11 -4	; 0
+	48	[41]	LOADK    	12 -3	; 1
+	49	[41]	MOVE     	13 10
+	50	[41]	LOADK    	14 -3	; 1
+	51	[41]	FORPREP  	12 14	; to 66
+	52	[42]	MOVE     	16 1
+	53	[42]	MOVE     	17 0
+	54	[42]	LOADK    	18 -3	; 1
+	55	[42]	MOVE     	19 9
+	56	[42]	CALL     	17 3 0
+	57	[42]	CALL     	16 0 2
+	58	[42]	ADD      	16 11 16
+	59	[43]	MOVE     	17 1
+	60	[43]	MOVE     	18 0
+	61	[43]	LOADK    	19 -12	; -1
+	62	[43]	MOVE     	20 9
+	63	[43]	CALL     	18 3 0
+	64	[43]	CALL     	17 0 2
+	65	[43]	ADD      	11 16 17
+	66	[41]	FORLOOP  	12 -15	; to 52
+	67	[45]	GETGLOBAL	12 -7	; io
+	68	[45]	GETTABLE 	12 12 -8	; "write"
+	69	[45]	GETGLOBAL	13 -9	; string
+	70	[45]	GETTABLE 	13 13 -10	; "format"
+	71	[45]	LOADK    	14 -13	; "%d\t trees of depth %d\t check: %d\n"
+	72	[46]	MUL      	15 10 -6	; - 2
+	73	[46]	MOVE     	16 9
+	74	[46]	MOVE     	17 11
+	75	[45]	CALL     	13 5 0
+	76	[45]	CALL     	12 0 1
+	77	[38]	FORLOOP  	6 -34	; to 44
+	78	[49]	GETGLOBAL	6 -7	; io
+	79	[49]	GETTABLE 	6 6 -8	; "write"
+	80	[49]	GETGLOBAL	7 -9	; string
+	81	[49]	GETTABLE 	7 7 -10	; "format"
+	82	[49]	LOADK    	8 -14	; "long lived tree of depth %d\t check: %d\n"
+	83	[50]	MOVE     	9 4
+	84	[50]	MOVE     	10 1
+	85	[50]	MOVE     	11 5
+	86	[50]	CALL     	10 2 0
+	87	[49]	CALL     	7 0 0
+	88	[49]	CALL     	6 0 1
+	89	[50]	RETURN   	0 1
+
+function <binary-trees.lua:5,14> (24 instructions, 96 bytes at 0x7f8068c01b60)
+2 params, 9 slots, 1 upvalue, 5 locals, 2 constants, 0 functions
+	1	[6]	LT       	0 -1 1	; 0 -
+	2	[6]	JMP      	17	; to 20
+	3	[7]	ADD      	2 0 0
+	4	[8]	SUB      	1 1 -2	; - 1
+	5	[9]	GETUPVAL 	3 0	; BottomUpTree
+	6	[9]	SUB      	4 2 -2	; - 1
+	7	[9]	MOVE     	5 1
+	8	[9]	CALL     	3 3 2
+	9	[9]	GETUPVAL 	4 0	; BottomUpTree
+	10	[9]	MOVE     	5 2
+	11	[9]	MOVE     	6 1
+	12	[9]	CALL     	4 3 2
+	13	[10]	NEWTABLE 	5 3 0
+	14	[10]	MOVE     	6 0
+	15	[10]	MOVE     	7 3
+	16	[10]	MOVE     	8 4
+	17	[10]	SETLIST  	5 3 1	; 1
+	18	[10]	RETURN   	5 2
+	19	[10]	JMP      	4	; to 24
+	20	[12]	NEWTABLE 	2 1 0
+	21	[12]	MOVE     	3 0
+	22	[12]	SETLIST  	2 1 1	; 1
+	23	[12]	RETURN   	2 2
+	24	[14]	RETURN   	0 1
+
+function <binary-trees.lua:16,22> (17 instructions, 68 bytes at 0x7f8068c01ee0)
+1 param, 4 slots, 1 upvalue, 1 local, 3 constants, 0 functions
+	1	[17]	GETTABLE 	1 0 -1	; 2
+	2	[17]	TEST     	1 0 0
+	3	[17]	JMP      	11	; to 15
+	4	[18]	GETTABLE 	1 0 -2	; 1
+	5	[18]	GETUPVAL 	2 0	; ItemCheck
+	6	[18]	GETTABLE 	3 0 -1	; 2
+	7	[18]	CALL     	2 2 2
+	8	[18]	ADD      	1 1 2
+	9	[18]	GETUPVAL 	2 0	; ItemCheck
+	10	[18]	GETTABLE 	3 0 -3	; 3
+	11	[18]	CALL     	2 2 2
+	12	[18]	SUB      	1 1 2
+	13	[18]	RETURN   	1 2
+	14	[18]	JMP      	2	; to 17
+	15	[20]	GETTABLE 	1 0 -2	; 1
+	16	[20]	RETURN   	1 2
+	17	[22]	RETURN   	0 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bench/defineClass.lua	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,27 @@
+Dog = {
+    name = "Jiro",
+    age = 3,
+    showProfile = function(self)
+	prof = string.format("name=%s,age=%d",self.name,self.age)
+	print(prof)
+    end
+}
+
+Dog = {}
+Dog.new = function(name, age)
+    local obj = {}
+    obj.name = name
+    obj.age = age
+    obj.showProfile = function(self)
+	s = string.format("[name=%s,age=%d]",self.name,self.age)
+	print(s)
+    end
+    return obj
+end
+
+jiro = Dog.new("jiro",3)
+sabu = Dog.new("sabu",2)
+
+jiro:showProfile()
+sabu:showProfile()
+
Binary file bench/luac.out has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bench/test.lua	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,6 @@
+
+f = function()
+    print("f()")
+end
+
+f()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/c/Makefile	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,8 @@
+CC = g++
+
+LIBFLAGS = -llua
+
+FLAGS = -O2 $(LDFLAGS) $(LIBFLAGS)
+
+readLuaScript: readLuaScript.c
+	$(CC) $(FLAGS) $(LIBFLAGS) -o readLuaScript $^
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/c/readLuaScript.c	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,91 @@
+#include <lua.hpp>
+
+
+/* 
+ * Reference http://marupeke296.com/LUA_No2_Begin.html
+*/
+void printStack(lua_State *L)
+{
+	const int num = lua_gettop(L);
+	if (num == 0) {
+		printf("no stack\n");
+		return;
+	}
+	for (int i= num; i>= 1; i--) {
+		printf("%03d(%04d):",i, -num + i - 1);
+		int type= lua_type(L, i);
+		switch(type) {
+		case LUA_TNIL:
+			printf("NIL\n");
+			break;
+		case LUA_TBOOLEAN:
+			printf("BOOLEAN %s\n",lua_toboolean(L,i)?"true":"false");
+			break;
+		case LUA_TLIGHTUSERDATA:
+			printf("LIGHTUSERDATA\n");
+			break;
+		case LUA_TNUMBER:
+			printf("NUMBER %f\n", lua_tonumber(L,i));
+			break;
+		case LUA_TSTRING:
+			printf("STRING %s\n", lua_tostring(L, i));
+			break;
+		case LUA_TTABLE:
+			printf("TABLE\n");
+			break;
+		case LUA_TFUNCTION:
+			printf("FUNCTION\n");
+			break;
+		case LUA_TUSERDATA:
+			printf("USERDATA\n");
+			break;
+		case LUA_TTHREAD:
+			printf("THERAD\n");
+			break;
+		}
+	}
+	printf("------------------\n");
+}
+
+int funcC(lua_State *L) 
+{
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	lua_State *L = luaL_newstate();
+	lua_register(L, "funcC", &funcC);
+	if (luaL_dofile(L, "testlib.lua")) {
+		printf("%s\n", lua_tostring(L, lua_gettop(L)));
+		return -1;
+	}
+	lua_getglobal(L,"add");
+	lua_pushnumber(L, 2);
+	lua_pushnumber(L, 3);
+	printStack(L);
+	/* 
+	 * lua_pcall function
+	 * @param lua_State
+	 * @param number of argument value
+	 * @param number of return value
+	 * @param error handle stack ID
+	 */
+	if (lua_pcall(L, 2, 1, 0)) {
+		printf("%s\n",lua_tostring(L, lua_gettop(L)));
+		lua_close(L);
+		return -1;
+	}
+	printStack(L);
+
+	/* get return value */
+	int add_res = (int)lua_tonumber(L, 1);
+	printf("add(2,3) = %d\n",add_res);
+
+	/* remove stack data */
+	int num = lua_gettop(L);
+	lua_pop(L, num);
+	lua_close(L);
+
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/c/requireLib.lua	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,3 @@
+require("testlib")
+
+print(add(2,3))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/c/testlib.lua	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,2 @@
+function add(a,b) return a+b end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/module/a.lua	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,3 @@
+module(..., package.seeall);
+
+function foo() print("Hello World!") end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/module/secret.lua	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,7 @@
+module(..., package.seeall);
+
+secret = 42
+
+function reveal_some() return (secret % 2) end
+
+function foo() print("Hello World!", reveal_some()) end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test.lua	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,33 @@
+
+f = function()
+    print("test")
+end
+
+g = function()
+    print("g function")
+end
+
+function main()
+    g()
+
+    
+end
+
+main()
+
+
+local function ReturnObject() 
+    local one = 1
+    local two = 2
+    return { one, two }
+end
+
+local o = ReturnObject()
+for i,v in ipairs(o) do print(i, v) end
+print(string.format("o[1] = %d",o[1]))
+
+
+local depth;
+for depth=1,2 do
+    print(depth)
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools.lua	Fri Feb 01 03:07:15 2013 +0900
@@ -0,0 +1,318 @@
+
+--- fs operations implemented with third-party tools for Unix platform abstractions.
+module("luarocks.fs.unix.tools", package.seeall)
+
+local fs = require("luarocks.fs")
+local dir = require("luarocks.dir")
+local cfg = require("luarocks.cfg")
+
+local dir_stack = {}
+
+local vars = cfg.variables
+
+local function command_at(directory, cmd)
+   return "cd " .. fs.Q(directory) .. " && " .. cmd
+end
+
+--- Obtain current directory.
+-- Uses the module's internal directory stack.
+-- @return string: the absolute pathname of the current directory.
+function current_dir()
+   local pipe = io.popen(vars.PWD)
+   local current = pipe:read("*l")
+   pipe:close()
+   for _, directory in ipairs(dir_stack) do
+      current = fs.absolute_name(directory, current)
+   end
+   return current
+end
+
+--- Run the given command.
+-- The command is executed in the current directory in the directory stack.
+-- @param cmd string: No quoting/escaping is applied to the command.
+-- @return boolean: true if command succeeds (status code 0), false
+-- otherwise.
+function execute_string(cmd)
+   local code = os.execute(command_at(fs.current_dir(), cmd))
+   if code == 0 or code == true then
+      return true
+   else
+      return false
+   end
+end
+
+--- Change the current directory.
+-- Uses the module's internal directory stack. This does not have exact
+-- semantics of chdir, as it does not handle errors the same way,
+-- but works well for our purposes for now.
+-- @param directory string: The directory to switch to.
+function change_dir(directory)
+   assert(type(directory) == "string")
+   table.insert(dir_stack, directory)
+end
+
+--- Change directory to root.
+-- Allows leaving a directory (e.g. for deleting it) in
+-- a crossplatform way.
+function change_dir_to_root()
+   table.insert(dir_stack, "/")
+end
+
+--- Change working directory to the previous in the directory stack.
+function pop_dir()
+   local directory = table.remove(dir_stack)
+   return directory ~= nil
+end
+
+--- Create a directory if it does not already exist.
+-- If any of the higher levels in the path name does not exist
+-- too, they are created as well.
+-- @param directory string: pathname of directory to create.
+-- @return boolean: true on success, false on failure.
+function make_dir(directory)
+   assert(directory)
+   return fs.execute(vars.MKDIR.." -p", directory)
+end
+
+--- Remove a directory if it is empty.
+-- Does not return errors (for example, if directory is not empty or
+-- if already does not exist)
+-- @param directory string: pathname of directory to remove.
+function remove_dir_if_empty(directory)
+   assert(directory)
+   fs.execute_string(fs.quiet(vars.RMDIR.." "..fs.Q(directory)))
+end
+
+--- Remove a directory if it is empty.
+-- Does not return errors (for example, if directory is not empty or
+-- if already does not exist)
+-- @param directory string: pathname of directory to remove.
+function remove_dir_tree_if_empty(directory)
+   assert(directory)
+   fs.execute_string(fs.quiet(vars.RMDIR.." -p "..fs.Q(directory)))
+end
+
+--- Copy a file.
+-- @param src string: Pathname of source
+-- @param dest string: Pathname of destination
+-- @param perm string or nil: Permissions for destination file,
+-- @return boolean or (boolean, string): true on success, false on failure,
+-- plus an error message.
+function copy(src, dest, perm)
+   assert(src and dest)
+   if fs.execute(vars.CP, src, dest) then
+      if perm then
+         if fs.is_dir(dest) then
+            dest = dir.path(dest, dir.base_name(src))
+         end
+         if fs.chmod(dest, perm) then
+            return true
+         else
+            return false, "Failed setting permissions of "..dest
+         end
+      end
+      return true
+   else
+      return false, "Failed copying "..src.." to "..dest
+   end
+end
+
+--- Recursively copy the contents of a directory.
+-- @param src string: Pathname of source
+-- @param dest string: Pathname of destination
+-- @return boolean or (boolean, string): true on success, false on failure,
+-- plus an error message.
+function copy_contents(src, dest)
+   assert(src and dest)
+   if fs.execute_string(fs.quiet(vars.CP.." -pPR "..fs.Q(src).."/* "..fs.Q(dest))) then
+      return true
+   else
+      return false, "Failed copying "..src.." to "..dest
+   end
+end
+--- Delete a file or a directory and all its contents.
+-- For safety, this only accepts absolute paths.
+-- @param arg string: Pathname of source
+-- @return boolean: true on success, false on failure.
+function delete(arg)
+   assert(arg)
+   assert(arg:sub(1,1) == "/")
+   return fs.execute_string(fs.quiet(vars.RM.." -rf " .. fs.Q(arg)))
+end
+
+--- List the contents of a directory.
+-- @param at string or nil: directory to list (will be the current
+-- directory if none is given).
+-- @return table: an array of strings with the filenames representing
+-- the contents of a directory.
+function list_dir(at)
+   assert(type(at) == "string" or not at)
+   if not at then
+      at = fs.current_dir()
+   end
+   if not fs.is_dir(at) then
+      return {}
+   end
+   local result = {}
+   local pipe = io.popen(command_at(at, vars.LS))
+   for file in pipe:lines() do
+      table.insert(result, file)
+   end
+   pipe:close()
+   return result
+end
+
+--- Recursively scan the contents of a directory.
+-- @param at string or nil: directory to scan (will be the current
+-- directory if none is given).
+-- @return table: an array of strings with the filenames representing
+-- the contents of a directory.
+function find(at)
+   assert(type(at) == "string" or not at)
+   if not at then
+      at = fs.current_dir()
+   end
+   if not fs.is_dir(at) then
+      return {}
+   end
+   local result = {}
+   local pipe = io.popen(command_at(at, vars.FIND.." * 2>/dev/null"))
+   for file in pipe:lines() do
+      table.insert(result, file)
+   end
+   pipe:close()
+   return result
+end
+
+--- Compress files in a .zip archive.
+-- @param zipfile string: pathname of .zip archive to be created.
+-- @param ... Filenames to be stored in the archive are given as
+-- additional arguments.
+-- @return boolean: true on success, false on failure.
+function zip(zipfile, ...)
+   return fs.execute(vars.ZIP.." -r", zipfile, ...)
+end
+
+--- Uncompress files from a .zip archive.
+-- @param zipfile string: pathname of .zip archive to be extracted.
+-- @return boolean: true on success, false on failure.
+function unzip(zipfile)
+   assert(zipfile)
+   return fs.execute(vars.UNZIP, zipfile)
+end
+
+--- Test is file/directory exists
+-- @param file string: filename to test
+-- @return boolean: true if file exists, false otherwise.
+function exists(file)
+   assert(file)
+   return fs.execute(vars.TEST, "-e", file)
+end
+
+--- Test is pathname is a directory.
+-- @param file string: pathname to test
+-- @return boolean: true if it is a directory, false otherwise.
+function is_dir(file)
+   assert(file)
+   return fs.execute(vars.TEST, "-d", file)
+end
+
+--- Test is pathname is a regular file.
+-- @param file string: pathname to test
+-- @return boolean: true if it is a regular file, false otherwise.
+function is_file(file)
+   assert(file)
+   return fs.execute(vars.TEST, "-f", file)
+end
+
+--- Download a remote file.
+-- @param url string: URL to be fetched.
+-- @param filename string or nil: this function attempts to detect the
+-- resulting local filename of the remote file as the basename of the URL;
+-- if that is not correct (due to a redirection, for example), the local
+-- filename can be given explicitly as this second argument.
+-- @return boolean: true on success, false on failure.
+function download(url, filename)
+   assert(type(url) == "string")
+   assert(type(filename) == "string" or not filename)
+
+   if cfg.downloader == "wget" then
+      local wget_cmd = vars.WGET.." --no-check-certificate --no-cache --user-agent='"..cfg.user_agent.." via wget' --quiet --continue "
+      if filename then
+         return fs.execute(wget_cmd.." --output-document ", filename, url)
+      else
+         return fs.execute(wget_cmd, url)
+      end
+   elseif cfg.downloader == "curl" then
+      filename = filename or dir.base_name(url)
+      return fs.execute_string(vars.CURL.." -L --user-agent '"..cfg.user_agent.." via curl' "..fs.Q(url).." 2> /dev/null 1> "..fs.Q(filename))
+   end
+end
+
+function chmod(pathname, mode)
+   if mode then 
+      return fs.execute(vars.CHMOD, mode, pathname)
+   else
+      return false
+   end
+end
+
+--- Apply a patch.
+-- @param patchname string: The filename of the patch.
+function apply_patch(patchname)
+   return fs.execute(vars.PATCH.." -p1 -f -i ", patchname)
+end
+
+--- Unpack an archive.
+-- Extract the contents of an archive, detecting its format by
+-- filename extension.
+-- @param archive string: Filename of archive.
+-- @return boolean or (boolean, string): true on success, false and an error message on failure.
+function unpack_archive(archive)
+   assert(type(archive) == "string")
+
+   local ok
+   if archive:match("%.tar%.gz$") or archive:match("%.tgz$") then
+         ok = fs.execute_string(vars.GUNZIP.." -c "..archive.."|"..vars.TAR.." -xf -")
+   elseif archive:match("%.tar%.bz2$") then
+         ok = fs.execute_string(vars.BUNZIP2.." -c "..archive.."|tar -xf -")
+   elseif archive:match("%.zip$") then
+      ok = fs.execute(vars.UNZIP, archive)
+   elseif archive:match("%.lua$") or archive:match("%.c$") then
+      -- Ignore .lua and .c files; they don't need to be extracted.
+      return true
+   else
+      local ext = archive:match(".*(%..*)")
+      return false, "Unrecognized filename extension "..(ext or "")
+   end
+   if not ok then
+      return false, "Failed extracting "..archive
+   end
+   return true
+end
+
+local md5_cmd = {
+   md5sum = vars.MD5SUM,
+   openssl = vars.OPENSSL.." md5",
+   md5 = vars.MD5,
+}
+
+--- Get the MD5 checksum for a file.
+-- @param file string: The file to be computed.
+-- @return string: The MD5 checksum
+function get_md5(file)
+   local cmd = md5_cmd[cfg.md5checker]
+   if not cmd then return nil end
+   local pipe = io.popen(cmd.." "..fs.absolute_name(file))
+   local computed = pipe:read("*a")
+   pipe:close()
+   if not computed then return nil end
+   return computed:match("("..("%x"):rep(32)..")")
+end
+
+function get_permissions(filename)
+   local pipe = io.popen(vars.STAT.." "..vars.STATFLAG.." "..fs.Q(filename))
+   local ret = pipe:read("*l")
+   pipe:close()
+   return ret
+end