summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTed Trask <ttrask01@yahoo.com>2009-12-31 14:14:17 +0000
committerTed Trask <ttrask01@yahoo.com>2009-12-31 14:14:17 +0000
commit30e3d9b315164804de9738efebf4d6aaaad50197 (patch)
tree0dc401aea8a81625e0fa3389e0b831fdfc7f4875
downloadacf-lib-30e3d9b315164804de9738efebf4d6aaaad50197.tar.bz2
acf-lib-30e3d9b315164804de9738efebf4d6aaaad50197.tar.xz
Lua libraries for standard functions moved out of acf-core 0.9.0v0.1.0
-rw-r--r--Makefile42
-rw-r--r--README1
-rw-r--r--apk.lua44
-rw-r--r--date.lua328
-rw-r--r--format.lua512
-rw-r--r--fs.lua222
-rw-r--r--html.lua262
-rw-r--r--processinfo.lua221
-rw-r--r--validator.lua172
9 files changed, 1804 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5a93e73
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,42 @@
+APP_NAME=lib
+PACKAGE=acf-$(APP_NAME)
+VERSION=0.1.0
+
+APP_DIST=\
+ *.lua\
+
+
+EXTRA_DIST=README Makefile
+
+DISTFILES=$(APP_DIST) $(EXTRA_DIST)
+
+TAR=tar
+
+P=$(PACKAGE)-$(VERSION)
+tarball=$(P).tar.bz2
+install_dir=/usr/share/lua/5.1/
+
+all:
+clean:
+ rm -rf $(tarball) $(P)
+
+dist: $(tarball)
+
+install:
+ mkdir -p "$(install_dir)"
+ cp -a $(APP_DIST) "$(install_dir)"
+
+$(tarball): $(DISTFILES)
+ rm -rf $(P)
+ mkdir -p $(P)
+ cp -a $(DISTFILES) $(P)
+ $(TAR) -jcf $@ $(P)
+ rm -rf $(P)
+
+# target that creates a tar package, unpacks is and install from package
+dist-install: $(tarball)
+ $(TAR) -jxf $(tarball)
+ $(MAKE) -C $(P) install DESTDIR=$(DESTDIR)
+ rm -rf $(P)
+
+.PHONY: all clean dist install dist-install
diff --git a/README b/README
new file mode 100644
index 0000000..026d8b4
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+ACF libraries for standard usage
diff --git a/apk.lua b/apk.lua
new file mode 100644
index 0000000..b4cb6ff
--- /dev/null
+++ b/apk.lua
@@ -0,0 +1,44 @@
+-- apk library
+module (..., package.seeall)
+
+local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin "
+
+delete = function(package)
+ local success = false
+ local cmdresult
+ local cmd = path .. "apk del " .. package .. " 2>&1"
+ local f = io.popen( cmd )
+ cmdresult = f:read("*a") or ""
+ f:close()
+ if string.find(cmdresult, "^OK") then
+ cmdresult = "ERROR: Package not found\n"..cmdresult
+ elseif not string.find(cmdresult, "ERROR") then
+ success = true
+ end
+ return success, cmdresult
+end
+
+install = function(package)
+ local success = true
+ local cmdresult
+ local cmd = path .. "apk add " .. package .. " 2>&1"
+ local f = io.popen( cmd )
+ cmdresult = f:read("*a")
+ f:close()
+ if string.find(cmdresult, "^ERROR") then
+ success = false
+ end
+ return success, cmdresult
+end
+
+version = function(package)
+ local cmdresult
+ local cmd = path .. "apk info -ve " .. package .. " 2>&1"
+ local f = io.popen( cmd )
+ cmdresult = f:read("*a")
+ f:close()
+ if string.find(cmdresult, "^%s*$") then
+ cmdresult = nil
+ end
+ return cmdresult
+end
diff --git a/date.lua b/date.lua
new file mode 100644
index 0000000..09592fc
--- /dev/null
+++ b/date.lua
@@ -0,0 +1,328 @@
+--date and time functions
+
+module(..., package.seeall)
+
+require("posix")
+require("format")
+require("fs")
+
+--global for date formating see below for more information
+--Mon Nov 26 19:56:10 UTC 2007 looks like most systems use this
+--print(os.date(date.format))
+formats = "%a %b %d %X %Z %Y"
+
+months ={ {"January","Jan"},
+ {"February", "Feb"},
+ {"March","Mar"},
+ {"April", "Apr"},
+ {"May","May"},
+ {"June","Jun"},
+ {"July","Jul"},
+ {"August","Aug"},
+ {"September","Sep"},
+ {"October","Oct"},
+ {"November","Nov"},
+ {"December","Dec"}
+ }
+
+revmonths = {["january"] = 1, ["jan"] = 1,
+ ["february"] = 2, ["feb"] = 2,
+ ["march"] = 3, ["mar"] = 3,
+ ["april"] = 4, ["apr"] = 4,
+ ["may"] = 5,
+ ["june"] = 6, ["jun"] = 6,
+ ["july"] = 7, ["jul"] = 7,
+ ["august"] = 8, ["aug"] = 8,
+ ["september"] = 9, ["sep"] = 9,
+ ["october"] = 10, ["oct"] = 10,
+ ["november"] = 11, ["nov"] = 11,
+ ["december"] = 12, ["dec"] = 12
+ }
+
+dow = { {"Sunday","Sun"},
+ {"Monday","Mon"},
+ {"Tuesday","Tue"},
+ {"Wednesday","Wed"},
+ {"Thursday","Thu"},
+ {"Friday","Fri"},
+ {"Saturday","Sat"}
+ }
+
+revdow = { ["sunday"] = 1, ["sun"] = 2,
+ ["monday"] = 2, ["mon"] = 2,
+ ["tuesday"] = 3, ["tue"] = 3,
+ ["wednesday"] = 4, ["wed"] = 4,
+ ["thursday"] = 5, ["thu"] = 5,
+ ["friday"] = 6, ["fri"] = 6,
+ ["saturday"] = 7, ["sat"] =7
+ }
+
+-- + usually denotes right of PM and - means left. It seems that /etc/TZ needs these
+--reversed for alpine/busybox. difference in col 2 and 5
+--this list is not full. May need some more added. No Africa or Asia
+--Abrr TZ,Real Offset, FullName, Location, What would be put in /etc/TZ(busybox needed offset)
+
+timezones = {
+
+{"A","+1","Alpha Time Zone","Military","Alpha-1"},
+{"ACDT","+10:30","Australian Central Daylight Time","Australia","ACDT-10:30"},
+{"ACST","+9:30","Australian Central Standard Time","Australia","ACST-9:30"},
+{"ADT","-3","Atlantic Daylight Time","North America","ADT+3"},
+{"AEDT","+11","Australian Eastern Daylight Time","Australia","AEDT-11"},
+{"AEST","+10","Australian Eastern Standard Time","Australia","AEST-10"},
+{"AKDT","-8","Alaska Daylight Time","North America","AKDT+8"},
+{"AKST","-9","Alaska Standard Time","North America","AKST+9"},
+{"AST","-4","Atlantic Standard Time","North America","AST+4"},
+{"AWDT","+9","Australian Western Daylight Time","Australia","AWDT-9"},
+{"AWST","+8","Australian Western Standard Time","Australia","AWST-8"},
+
+{"B","+2","Bravo Time Zone","Military","Bravo-2"},
+{"BST","+1","British Summer Time","Europe","BST-1"},
+
+{"C","+3","Charlie Time Zone","Military","Charlie-3"},
+{"CDT","-5","Central Daylight Time","North America","CDT+5"},
+{"CEDT","+2","Central European Daylight Time","Europe","CEDT-2"},
+{"CEST","+2","Central European Summer Time","Europe","CEST-2"},
+{"CET","+1","Central European Time","Europe","CET-1"},
+{"CST","+10:30","Central Summer(Daylight) Time","Australia","CST-10:30"},
+{"CST","+9:30","Central Standard Time","Australia","CST-9:30"},
+{"CST","-6","Central Standard Time","North America","CST+6"},
+{"CXT","+7","Christmas Island Time","Australia","CXT-7"},
+
+{"D","+4","Delta Time Zone","Military","Delta-4"},
+
+{"E","+5","Echo Time Zone","Military","Echo-5"},
+{"EDT","-4","Eastern Daylight Time","North America","EDT+4"},
+{"EEDT","+3","Eastern European Daylight Time","Europe","EEDT-3"},
+{"EEST","+3","Eastern European Summer Time","Europe","EEST-3"},
+{"EET","+2","Eastern European Time","Europe","EET-2"},
+{"EST","+11","Eastern Summer(Daylight) Time","Australia","EST-11"},
+{"EST","+10","Eastern Standard Time","Australia","EST-10"},
+{"EST","-5","Eastern Standard Time","North America","EST+5"},
+
+{"F","+6","Foxtrot Time Zone","Military","Foxtrot-6"},
+
+{"G","+7","Golf Time Zone","Military","Golf-7"},
+{"GMT","+0","Greenwich Mean Time","Europe","GMT+0"},
+
+{"H","+8","Hotel Time Zone","Military","Hotel-8"},
+{"HAA","-3","Heure Avancée de l'Atlantique","North America","HAA+3"},
+{"HAC","-5","Heure Avancée du Centre","North America","HAC+5"},
+{"HADT","-9","Hawaii-Aleutian Daylight Time","North America","HADT+9"},
+{"HAE","-4","Heure Avancée de l'Est","North America","HAE+4"},
+{"HAP","-7","Heure Avancée du Pacifique","North America","HAP+7"},
+{"HAR","-6","Heure Avancée des Rocheuses","North America","HAR+6"},
+{"HAST","-10","Hawaii-Aleutian Standard Time","North America","HAST+10"},
+{"HAT","-2:30","Heure Avancée de Terre-Neuve","North America","HAT+2:30"},
+{"HAY","-8","Heure Avancée du Yukon","North America","HAY+8"},
+{"HNA","-4","Heure Normale de l'Atlantique","North America","HNA+4"},
+{"HNC","-6","Heure Normale du Centre","North America","HNC+6"},
+{"HNE","-5","Heure Normale de l'Est","North America","HNE+5"},
+{"HNP","-8","Heure Normale du Pacifique","North America","HNP+8"},
+{"HNR","-7","Heure Normale des Rocheuses","North America","HNR+7"},
+{"HNT","-3:30","Heure Normale de Terre-Neuve","North America","HNT+3:30"},
+{"HNY","-9","Heure Normale du Yukon","North America","HNY+9"},
+
+{"I","+9","India Time Zone","Military","India-9"},
+{"IST","+1","Irish Summer Time","Europe","IST-1"},
+
+{"K","+10","Kilo Time Zone","Military","Kilo-10"},
+
+{"L","+11","Lima Time Zone","Military","Lima-11"},
+
+{"M","+12","Mike Time Zone","Military","Mike-12"},
+{"MDT","-6","Mountain Daylight Time","North America","MDT+6"},
+{"MESZ","+2","Mitteleuroäische Sommerzeit","Europe","MESZ-2"},
+{"MEZ","+1","Mitteleuropäische Zeit","Europe","MEZ-1"},
+{"MST","-7","Mountain Standard Time","North America","MST+7"},
+
+{"N","-1","November Time Zone","Military","November+1"},
+{"NDT","-2:30","Newfoundland Daylight Time","North America","NDT+2:30"},
+{"NFT","+11:30","Norfolk (Island) Time","Australia","NFT-11:30"},
+{"NST","-3:30","Newfoundland Standard Time","North America","NST+3:30"},
+
+{"O","-2","Oscar Time Zone","Military","Oscar+2"},
+
+{"P","-3","Papa Time Zone","Military","Papa+3"},
+{"PDT","-7","Pacific Daylight Time","North America","PDT+7"},
+{"PST","-8","Pacific Standard Time","North America","PST+8"},
+
+{"Q","-4","Quebec Time Zone","Military","Quebec+4"},
+
+{"R","-5","Romeo Time Zone","Military","Romeo+5"},
+
+{"S","-6","Sierra Time Zone","Military","Sierra+6"},
+
+{"T","-7","Tango Time Zone","Military","Tango+7"},
+
+{"U","-8","Uniform Time Zone","Military","Uniform+8"},
+{"UTC","+0","Coordinated Universal Time","Europe","UTC+0"},
+
+{"V","-9","Victor Time Zone","Military","Victor+9"},
+
+{"W","-10","Whiskey Time Zone","Military","Whiskey+10"},
+{"WEDT","+1","Western European Daylight Time","Europe","WEDT-1"},
+{"WEST","+1","Western European Summer Time","Europe","WEST-1"},
+{"WET","+0","Western European Time","Europe","WET+0"},
+{"WST","+9","Western Summer(Daylight) Time","Australia","WST-9"},
+{"WST","+8","Western Standard Time","Australia","WST-8"},
+
+{"X","-11","X-ray Time Zone","Military","X-ray+11"},
+
+{"Y","-12","Yankee Time Zone","Military","Yankee+12"},
+
+{"Z","+0","Zulu Time Zone","Military","Zulu+0"}
+
+}
+
+--os.time() will give seconds since 1970-epoch
+--os.date() will give formated time strings
+--os.time{year=2007,month=1,day=1,hour=2,min=1,sec=1}
+--os.date(date.format,os.time())
+
+--give me a table
+--t = { {year=2007,month=1,day=2,hour=2}, {year=2006,month=1,day=5} }
+--will return a table sorted by oldest <-> newest
+--to grab the largest and smallest a,b=g[1],g[table.maxn(g)]
+function date_to_seconds (t)
+ g = {}
+ count = table.maxn(t)
+ for i = 1,count do
+ g[#g+1] = os.time(t[i])
+ end
+ table.sort(g)
+ return g
+end
+
+-- the reverse of date_to_seconds. expecting a table of seconds
+--format can be changed. This seems to be standard, dow,mon,dom,time,zone,year
+-- seems like %z- +0000 time zone format and %Z- 3 letter timezone undocumented or new
+
+function seconds_to_date (t)
+ g = {}
+ count = table.maxn(t)
+ for i = 1,count do
+ g[#g+1] = os.date(formats,t[i])
+ end
+
+ return g
+end
+
+--Wed Nov 28 14:01:23 UTC 2007
+--os.date(date.formats) put into a table
+--year,month,day,hour,min,sec,isdst- may need a dst table to set this automatically
+function string_to_table (str)
+ if str == nil then str = os.date(formats) end
+ g = {}
+ temp = format.string_to_table(str,"%s")
+ month = abr_month_num(temp[2])
+ g["month"] = month
+ day = temp[3]
+ g["day"] = day
+ --may do something with this if have a tz table ??
+ tz = temp[5]
+ year = temp[6]
+ g["year"] = year
+ temp2 = format.string_to_table(temp[4],":")
+ hour = temp2[1]
+ g["hour"] = hour
+ min = temp2[2]
+ g["min"] = min
+ sec = temp2[3]
+ g["sec"] = sec
+ return g
+
+end
+
+
+--give dates in seconds and gives the difference in years,months,days,...
+--gives a table back with hour,min,month,sec,day,year to display something like
+--you have 10 years, 14 hours, 10 days to renew you certificate
+-- in secs - year, day, hour,min,sec
+t_time = { field_names = {"years","days","hours","minutes","seconds"},
+ 31556926,86400,3600,60,1
+ }
+
+function date_diff (d1, d2)
+ g = {}
+ if d2 == nil then d2 = os.time() end
+ --first sum of seconds
+ sum = math.abs(os.difftime(d1,d2))
+ --going to go through and get it smaller with each pass through the table
+ for a,b in ipairs(t_time) do
+ print(sum)
+ hold = math.modf(sum/b)
+ g[t_time.field_names[a]] = hold
+ sum = (sum - (hold*b))
+ end
+
+ return g
+end
+
+--give a search number and return the month name
+
+function num_month_name (search)
+ return months[search][1]
+end
+
+--give a search number and return the month abr
+
+function num_month_name_abr (search)
+ return months[search][2]
+end
+
+function name_month_num (search)
+ return revmonths[string.lower(search)]
+end
+
+function abr_month_num (search)
+ return revmonths[string.lower(search)]
+end
+
+function num_dow_name (search)
+ return dow[search][1]
+end
+
+function num_dow_name_abr (search)
+ return dow[search][2]
+end
+
+function name_dow_num (search)
+ return revdow[string.lower(search)]
+end
+
+function abr_dow_num (search)
+ return revdow[string.lower(search)]
+end
+
+--tell me what TimeZone my system is set to
+
+function what_tz ()
+ f = fs.read_file_as_array("/etc/TZ") or {}
+ local tz = f[1]
+ return tz
+end
+
+--change the timezone my system is set to
+
+function change_tz ( tz )
+ --give us something like CET-1, this is busy box offset need to fix.
+
+ tz = string.gsub(tz, "%+", "%%+")
+ tz = string.gsub(tz, "%-", "%%-")
+ tz = "^" .. tz .. "$"
+ result = {}
+ for a=1,table.maxn(date.timezones) do
+ c = string.match(date.timezones[a][5], tz)
+ if c ~= nil then result[#result +1] = c end
+ end
+
+ if table.maxn(result) == 1 then
+ fs.write_file("/etc/TZ", result[1])
+ mess = "Success"
+ else
+ mess = "Too many matches."
+ end
+
+ return mess,date.what_tz()
+end
diff --git a/format.lua b/format.lua
new file mode 100644
index 0000000..0076fdf
--- /dev/null
+++ b/format.lua
@@ -0,0 +1,512 @@
+--[[
+ module for format changes in tables and strings
+ try to keep non input specific
+]]--
+
+module (..., package.seeall)
+
+-- find all return characters and removes them, may get this from a browser
+-- that is why didn't do file specific
+
+function dostounix ( str )
+ local data = string.gsub(str, "\r", "")
+ return data
+end
+
+-- Escape Lua magic characters
+function escapemagiccharacters ( str )
+ return (string.gsub(str or "", "[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1"))
+end
+
+-- Escape shell special characters
+function escapespecialcharacters ( str )
+ return (string.gsub(str or "", "[~`#%$&%*%(%)\\|%[%]{};\'\"<>/]", "\\%1"))
+end
+
+-- search and remove all blank and commented lines from a string or table of lines
+-- returns a table to iterate over without the blank or commented lines
+
+function parse_lines ( input, comment )
+ local lines = {}
+ comment = comment or "#"
+
+ function parse(line)
+ if not string.match(line, "^%s*$") and not string.match(line, "^%s*"..comment) then
+ lines[#lines + 1] = line
+ end
+ end
+
+ if type(input) == "string" then
+ for line in string.gmatch(input, "([^\n]*)\n?") do
+ parse(line)
+ end
+ elseif type(input) == "table" then
+ for i,line in ipairs(input) do
+ parse(line)
+ end
+ end
+
+ return lines
+end
+
+-- search and remove all blank and commented lines from a string or table of lines
+-- parse the lines for words, looking for quotes and removing comments
+-- returns a table with an array of words for each line
+
+function parse_linesandwords ( input, comment )
+ local lines = {}
+ local linenum = 0
+ comment = comment or "#"
+
+ function parse(line)
+ linenum = linenum + 1
+ if not string.match(line, "^%s*$") and not string.match(line, "^%s*"..comment) then
+ local linetable = {linenum=linenum, line=line}
+ local offset = 1
+ while string.find(line, "%S", offset) do
+ local word = string.match(line, "%S+", offset)
+ local endword
+ if string.find(word, "^"..comment) then
+ break
+ elseif string.find(word, "^\"") then
+ endword = select(2, string.find(line, "\"[^\"]*\"", offset))
+ word = string.sub(line, string.find(line, "\"", offset), endword)
+ else
+ endword = select(2, string.find(line, "%S+", offset))
+ end
+ table.insert(linetable, word)
+ offset = endword + 1
+ end
+ lines[#lines + 1] = linetable
+ end
+ end
+
+ if type(input) == "string" then
+ for line in string.gmatch(input, "([^\n]*)\n?") do
+ parse(line)
+ end
+ elseif type(input) == "table" then
+ for i,line in ipairs(input) do
+ parse(line)
+ end
+ end
+
+ return lines
+end
+
+-- returns a table with label value pairs
+
+function parse_configfile( input, comment )
+ local config = {}
+ local lines = parse_linesandwords(input, comment)
+
+ for i,linetable in ipairs(lines) do
+ config[linetable[1]] = table.concat(linetable, " ", 2) or ""
+ end
+ return config
+end
+
+-- search and replace through a table
+-- string is easy string.gsub(string, find, replace)
+
+function search_replace (input, find, replace)
+ local lines = {}
+ for i,line in ipairs(input) do
+ lines[#lines + 1] = string.gsub(line, find, replace)
+ end
+ return lines
+end
+
+-- great for line searches through a file. /etc/conf.d/ ???
+-- might be looking for more than one thing so will return a table
+-- will likely want to match whole line entries
+-- so we change find to include the rest of the line
+-- say want all the _OPTS from a file format.search_for_lines (fs.read_file("/etc/conf.d/cron"), "OPT")
+-- if want to avoid commented lines, call parse_lines first
+
+function search_for_lines (input, find)
+ local lines = {}
+
+ function findfn(line)
+ if string.find(line, find) then
+ lines[#lines + 1] = line
+ end
+ end
+
+ if type(input) == "string" then
+ for line in string.gmatch(input, "([^\n]*)\n?") do
+ findfn(line)
+ end
+ elseif type(input) == "table" then
+ for i,line in ipairs(input) do
+ findfn(line)
+ end
+ end
+
+ return lines
+end
+
+--string format function to capitalize the beginging of each word.
+function cap_begin_word ( str )
+ --first need to do the first word
+ local data = string.gsub(str, "^%l", string.upper)
+ --word is any space cause no <> regex
+ data = string.gsub(data, "%s%l", string.upper)
+ return data
+end
+
+--for cut functionality do something like
+--print(format.string_to_table("This is a test", " ")[2])
+--gives you the second field which is .... is
+
+-- This code comes from http://lua-users.org/wiki/SplitJoin
+-- example: format.string_to_table( "Anna, Bob, Charlie,Dolores", ",%s*")
+function string_to_table ( text, delimiter)
+ local list = {}
+ if text then
+ -- this would result in endless loops
+ if string.find("", delimiter) then
+ -- delimiter matches empty string!
+ for i=1,#text do
+ list[#list + 1] = string.sub(text, i, i)
+ end
+ else
+ local pos = 1
+ while 1 do
+ local first, last = string.find(text, delimiter, pos)
+ if first then -- found?
+ table.insert(list, string.sub(text, pos, first-1))
+ pos = last+1
+ else
+ table.insert(list, string.sub(text, pos))
+ break
+ end
+ end
+ end
+ end
+ return list
+end
+
+
+-- Takes a str and expands any ${...} constructs with the Lua variable
+-- ex: a="foo"; print(expand_bash_syntax_vars("a=${a}) - > "a=foo"
+expand_bash_syntax_vars = function (str)
+ local deref = function (f)
+ local v = getfenv(3) -- get the upstream global env
+ for w in string.gfind(f, "[%w_]+") do
+ if v then v = v[w] end
+ end
+ return v
+ end
+
+ for w in string.gmatch (str, "${[^}]*}" ) do
+ local rvar = string.sub(w,3,-2)
+ local rval = ( deref(rvar) or "nil" )
+ str = string.gsub (str, w, escapespecialcharacters(rval))
+ end
+
+ return (str)
+end
+
+-- Removes the linenum line from str and replaces it with line.
+-- Do nothing if doesn't exist
+-- Set line to nil to remove the line
+function replace_line(str, linenum, line)
+ -- Split the str to remove the line
+ local startchar, endchar = string.match(str, "^" .. string.rep("[^\n]*\n", linenum-1) .. "()[^\n]*\n?()")
+ if startchar and endchar then
+ local lines = {}
+ lines[1] = string.sub(str, 1, startchar-1)
+ lines[2] = string.sub(str, endchar, -1)
+ if line then
+ table.insert(lines, 2, line .. "\n")
+ end
+ str = table.concat(lines)
+ end
+ return str
+end
+
+-- Inserts the line into the str after the linenum (or at the end)
+function insert_line(str, linenum, line)
+ -- Split the str to remove the line
+ local startchar = string.match(str, "^" .. string.rep("[^\n]*\n", linenum) .. "()")
+ local lines = {}
+ if startchar then
+ lines[1] = string.sub(str, 1, startchar-1)
+ lines[2] = string.sub(str, startchar, -1)
+ else
+ lines[1] = str
+ end
+ if line then
+ table.insert(lines, 2, line .. "\n")
+ end
+ str = table.concat(lines)
+ return str
+end
+
+function get_line(str, linenum)
+ -- Split the str to remove the line
+ local startchar, endchar = string.match(str, "^" .. string.rep("[^\n]*\n", linenum-1) .. "()[^\n]*()")
+ local line
+ if startchar and endchar then
+ line = string.sub(str, startchar, endchar-1)
+ end
+ return line
+end
+
+-- Search the option string for separate options (-x or --xyz) and put them in a table
+function opts_to_table ( optstring, filter )
+ local optsparams
+ if optstring then
+ local optstr = optstring .. " "
+ for o in string.gmatch(optstr, "%-%-?%a+%s+[^-%s]*") do
+ local option = string.match(o, "%-%-?%a+")
+ if not filter or filter == option then
+ if not optsparams then optsparams = {} end
+ optsparams[option] = string.match(o, "%S*$")
+ end
+ end
+ end
+ return optsparams
+end
+
+-- Go through an options table and create the option string
+function table_to_opts ( optsparams )
+ local optstring = {}
+ for opt,val in pairs(optsparams) do
+ optstring[#optstring + 1] = opt
+ if val ~= "" then
+ optstring[#optstring + 1] = val
+ end
+ end
+ return table.concat(optstring, " ")
+end
+
+-- The following functions deal with ini files. ini files contain comments, sections, names and values
+-- commented lines begin with '#' or ';', in-line comments begin with '#' and run to the end of the line
+-- sections are defined by "[section]" on a line. Anything before the first section definition is in section ""
+-- name value pairs are defined by "name = value". Names and values may contain spaces but not '#'
+-- lines ending with '\' are continued on the next line
+
+
+-- Set a name=value pair in a string
+-- If search_section is undefined or "", goes in the default section
+-- If value is defined we put "search_name=value" into search_section
+-- If value is undefined, we clear search_name out of search section
+-- Try not to touch anything but the value we're interested in (although will combine multi-line into one)
+-- If the search_section is not found, we'll add it at the end of the string
+-- If the search_name is not found, we'll add it at the end of the section
+function update_ini_file (file, search_section, search_name, value)
+ if not file or not search_name or search_name == "" then
+ return file, false
+ end
+
+ search_section = search_section or ""
+ local new_conf_file = {}
+ local section = ""
+ local done = false
+ local skip_lines = {}
+ for l in string.gmatch(file, "([^\n]*)\n?") do
+ if done == false then
+ if string.find ( l, "\\%s*$" ) then
+ skip_lines[#skip_lines+1] = string.match(l, "^(.*)\\%s*$")
+ l = nil
+ else
+ if #skip_lines then
+ skip_lines[#skip_lines+1] = l
+ l = table.concat(skip_lines, " ")
+ end
+ -- check if comment line
+ if not string.find ( l, "^%s*[#;]" ) then
+ -- find section name
+ local a = string.match ( l, "^%s*%[%s*(%S+)%s*%]" )
+ if a then
+ -- we reached a new section, if we were in the one we wanted
+ -- we have to add in the name:value pair now
+ if (search_section == section) then
+ new_conf_file[#new_conf_file + 1] = search_name.."="..value
+ done = true
+ end
+ section = a
+ elseif (search_section == section) then
+ -- find name
+ a = string.match ( l, "^%s*([^=]*%S)%s*=" )
+ if a and (search_name == a) then
+ -- We found the name, change the value, keep any comment
+ local comment = string.match(l, " #.*$") or ""
+ l = search_name.."="..value..comment
+ skip_lines = {} -- replacing line
+ done = true
+ end
+ end
+ end
+ if #skip_lines > 0 then
+ for i,line in ipairs(skip_lines) do
+ new_conf_file[#new_conf_file + 1] = line
+ end
+ skip_lines = {}
+ l = nil
+ end
+ end
+ end
+ new_conf_file[#new_conf_file + 1] = l
+ end
+
+ if done == false then
+ -- we didn't find the section:name, add it now
+ if section ~= search_section then
+ new_conf_file[#new_conf_file + 1] = '[' .. search_section .. ']'
+ end
+ new_conf_file[#new_conf_file + 1] = search_name.."="..value
+ end
+
+ file = table.concat(new_conf_file, '\n')
+
+ return file, true
+end
+
+-- Parse string for name=value pairs, returned in a table
+-- If search_section is defined, only report values in matching section
+-- If search_name is defined, only report matching name (possibly in multiple sections)
+function parse_ini_file (file, search_section, search_name)
+ if not file or file == "" then
+ return nil
+ end
+ local opts = nil
+ local section = ""
+ local skip_lines = {}
+ for l in string.gmatch(file, "([^\n]*)\n?") do
+ if string.find ( l, "\\%s*$" ) then
+ skip_lines[#skip_lines+1] = string.match(l, "^(.*)\\%s*$")
+ else
+ if #skip_lines then
+ skip_lines[#skip_lines+1] = l
+ l = table.concat(skip_lines, " ")
+ skip_lines = {}
+ end
+ -- check if comment line
+ if not string.find ( l, "^%s*[#;]" ) then
+ -- find section name
+ local a = string.match ( l, "^%s*%[%s*(%S+)%s*%]" )
+ if a then
+ if (search_section == section) then break end
+ section = a
+ elseif not (search_section) or (search_section == section) then
+ -- find name
+ a = string.match ( l, "^%s*([^=]*%S)%s*=" )
+ if a and (not (search_name) or (search_name == a)) then
+ -- Figure out the value
+ local b = string.match ( l, '=%s*(.*)$' ) or ""
+ -- remove comments from end of line
+ if string.find ( b, '#' ) then
+ b = string.match ( b, '^(.*)#.*$' ) or ""
+ end
+ -- remove spaces from front and back
+ b = string.gsub ( b, '%s+$', '' )
+ if not (opts) then opts = {} end
+ if not (opts[section]) then opts[section] = {} end
+ opts[section][a] = b
+ end
+ end
+ end
+ end
+ end
+
+ if opts and search_section and search_name then
+ return opts[search_section][search_name]
+ elseif opts and search_section then
+ return opts[search_section]
+ end
+ return opts
+end
+
+function get_ini_section (file, search_section)
+ if not file then
+ return nil
+ end
+ search_section = search_section or ""
+ local sectionlines = {}
+ local section = ""
+ for l in string.gmatch(file, "([^\n]*)\n?") do
+ -- find section name
+ local a = string.match ( l, "^%s*%[%s*(%S+)%s*%]" )
+ if a then
+ if (search_section == section) then break end
+ section = a
+ elseif (search_section == section) then
+ sectionlines[#sectionlines + 1] = l
+ end
+ end
+
+ return table.concat(sectionlines, "\n")
+end
+
+function set_ini_section (file, search_section, section_content)
+ if not file then
+ return file, false
+ end
+ search_section = search_section or ""
+ section_content = section_content or ""
+ local new_conf_file = {}
+ local done = false
+ local section = ""
+ if search_section == "" then new_conf_file[1] = section_content end
+ for l in string.gmatch(file, "([^\n]*)\n?") do
+ -- find section name
+ if not done then
+ local a = string.match ( l, "^%s*%[%s*(%S+)%s*%]" )
+ if a then
+ if (search_section == section) then
+ done = true
+ else
+ section = a
+ if (search_section == section) then
+ l = l .. "\n" .. section_content
+ end
+ end
+ elseif (search_section == section) then
+ l = nil
+ end
+ end
+ new_conf_file[#new_conf_file + 1] = l
+ end
+
+ if not done then
+ -- we didn't find the section, add it now
+ if section ~= search_section then
+ new_conf_file[#new_conf_file + 1] = '[' .. search_section .. ']'
+ new_conf_file[#new_conf_file + 1] = section_content
+ end
+ end
+
+ file = table.concat(new_conf_file, '\n')
+
+ return file, true
+end
+
+-- Find the value of an entry allowing for parent section and $variables
+-- the file parameter can be a string or structure returned by parse_ini_file
+-- beginning and ending quotes are removed
+-- returns value or "" if not found
+function get_ini_entry (file, section, value)
+ local opts = file
+ if not file or not value then
+ return nil
+ elseif type(file) == "string" then
+ opts = parse_ini_file(file)
+ end
+ section = section or ""
+ local result = opts[section][value]
+ if not result then
+ section = ""
+ result = opts[section][value] or ""
+ end
+ while string.find(result, "%$[%w_]+") do
+ local sub = string.match(result, "%$[%w_]+")
+ result = string.gsub(result, escapemagiccharacters(sub), get_ini_entry(opts, section, sub))
+ end
+ if string.find(result, '^"') and string.find(result, '"$') then
+ result = string.sub(result, 2, -2)
+ end
+ return result
+end
diff --git a/fs.lua b/fs.lua
new file mode 100644
index 0000000..63b996d
--- /dev/null
+++ b/fs.lua
@@ -0,0 +1,222 @@
+--[[
+ module for generic filesystem funcs
+
+ Copyright (c) Natanael Copa 2006
+ MM edited to use "posix"
+]]--
+
+module (..., package.seeall)
+
+require("posix")
+require("format")
+
+-- generic wrapper funcs
+function is_dir ( pathstr )
+ return posix.stat ( pathstr or "", "type" ) == "directory"
+end
+
+function is_file ( pathstr )
+ return posix.stat ( pathstr or "", "type" ) == "regular"
+end
+
+function is_link ( pathstr )
+ return posix.stat ( pathstr or "", "type" ) == "link"
+end
+
+-- Creates a directory if it doesn't exist, including the parent dirs
+function create_directory ( path )
+ local pos = string.find(path, "/")
+ while pos do
+ posix.mkdir(string.sub(path, 1, pos))
+ pos = string.find(path, "/", pos+1)
+ end
+ posix.mkdir(path)
+ return is_dir(path)
+end
+
+-- Deletes a directory along with its contents
+function remove_directory ( path )
+ if fs.is_dir(path) then
+ for d in posix.files(path) do
+ if (d == ".") or (d == "..") then
+ -- ignore
+ elseif fs.is_dir(path .. "/" .. d) then
+ remove_directory(path .. "/" ..d)
+ else
+ os.remove(path .. "/" ..d)
+ end
+ end
+ os.remove(path)
+ return true
+ end
+ return false
+end
+
+-- Creates a blank file (and the directory if necessary)
+function create_file ( path )
+ path = path or ""
+ if not posix.stat(posix.dirname(path)) then create_directory(posix.dirname(path)) end
+ local f = io.open(path, "w")
+ if f then f:close() end
+ return is_file(path)
+end
+
+-- Copies a file to a directory or new filename (creating the directory if necessary)
+-- fails if new file is already a directory (this is different than cp function)
+-- if newpath ends in "/", will treat as a directory
+function copy_file(oldpath, newpath)
+ local use_dir = string.find(newpath or "", "/%s*$")
+ if not is_file(oldpath) or not newpath or newpath == "" or (not use_dir and is_dir(newpath)) or (use_dir and is_dir(newpath .. posix.basename(oldpath))) then
+ return false
+ end
+ if use_dir then newpath = newpath .. posix.basename(oldpath) end
+ if not posix.stat(posix.dirname(newpath)) then create_directory(posix.dirname(newpath)) end
+ local old = io.open(oldpath, "r")
+ local new = io.open(newpath, "w")
+ new:write(old:read("*a"))
+ new:close()
+ old:close()
+ return is_file(newpath)
+end
+
+-- Moves a file to a directory or new filename (creating the directory if necessary)
+-- fails if new file is already a directory (this is different than mv function)
+-- if newpath ends in "/", will treat as a directory
+function move_file(oldpath, newpath)
+ local use_dir = string.find(newpath or "", "/%s*$")
+ if not is_file(oldpath) or not newpath or newpath == "" or (not use_dir and is_dir(newpath)) or (use_dir and is_dir(newpath .. posix.basename(oldpath))) then
+ return false
+ end
+ if use_dir then newpath = newpath .. posix.basename(oldpath) end
+ if not posix.stat(posix.dirname(newpath)) then create_directory(posix.dirname(newpath)) end
+ local status, errstr, errno = os.rename(oldpath, newpath)
+ -- errno 18 means Invalid cross-device link
+ if status or errno ~= 18 then
+ -- successful move or failure due to something else
+ return (status ~= nil), errstr, errno
+ else
+ status = copy_file(oldpath, newpath)
+ if status then
+ os.remove(oldpath)
+ end
+ return status
+ end
+end
+
+-- Returns the contents of a file as a string
+function read_file ( path )
+ local file = io.open(path or "")
+ if ( file ) then
+ local f = file:read("*a")
+ file:close()
+ return f
+ else
+ return nil
+ end
+end
+
+-- Returns an array with the contents of a file,
+-- or nil and the error message
+function read_file_as_array ( path )
+ local file, error = io.open(path or "")
+ if ( file == nil ) then
+ return nil, error
+ end
+ local f = {}
+ for line in file:lines() do
+ table.insert ( f , line )
+ --sometimes you will see it like f[#f+1] = line
+ end
+ file:close()
+ return f
+end
+
+-- write a string to a file, will replace file contents
+function write_file ( path, str )
+ path = path or ""
+ if not posix.stat(posix.dirname(path)) then create_directory(posix.dirname(path)) end
+ local file = io.open(path, "w")
+ --append a newline char to EOF
+ str = string.gsub(str or "", "\n*$", "\n")
+ if ( file ) then
+ file:write(str)
+ file:close()
+ end
+end
+
+-- this could do more than a line. This will append
+-- fs.write_line_file ("filename", "Line1 \nLines2 \nLines3")
+function write_line_file ( path, str )
+ path = path or ""
+ if not posix.stat(posix.dirname(path)) then create_directory(posix.dirname(path)) end
+ local file = io.open(path)
+ if ( file) then
+ local c = file:read("*a") or ""
+ file:close()
+ fs.write_file(path, c .. (str or ""))
+ end
+end
+
+-- returns an array of files under "where" that match "what" (a Lua pattern)
+function find_files_as_array ( what, where, follow, t )
+ where = where or posix.getcwd()
+ what = what or ".*"
+ t = t or {}
+
+ local link
+ if follow and fs.is_link(where) then
+ link = posix.readlink(where)
+ if not string.find(link, "^/") then
+ link = posix.dirname(where).."/"..link
+ end
+ end
+
+ if fs.is_dir(where) or (link and fs.is_dir(link)) then
+ for d in posix.files ( where ) do
+ if (d == ".") or ( d == "..") then
+ -- do nothing
+ elseif fs.is_dir ( where .. "/" .. d ) then
+ find_files_as_array (what, where .. "/" .. d, follow, t )
+ elseif follow and fs.is_link ( where .. "/" .. d ) then
+ find_files_as_array (what, where .. "/" .. d, follow, t )
+ elseif (string.match (d, "^" .. what .. "$" )) then
+ table.insert (t, ( string.gsub ( where .. "/" .. d, "/+", "/" ) ) )
+ end
+ end
+ elseif (string.match (posix.basename(where), "^" .. what .. "$" )) and posix.stat(where) then
+ table.insert (t, where )
+ end
+
+ return (t)
+end
+
+-- iterator function for finding dir entries matching (what) (a Lua pattern)
+-- starting at where, or currentdir if not specified.
+function find ( what, where, follow )
+ local t = find_files_as_array ( what, where, follow )
+ local idx = 0
+ return function ()
+ idx = idx + 1
+ return t[idx]
+ end
+end
+
+-- This function does almost the same as posix.stat, but instead it writes the output human readable.
+function stat ( path )
+ local filedetails = posix.stat(path or "")
+ if (filedetails) then
+ filedetails["ctime"]=os.date("%c", filedetails["ctime"])
+ filedetails["mtime"]=os.date("%c", filedetails["mtime"])
+ filedetails["path"]=path
+ if ( filedetails["size"] > 1073741824 ) then
+ filedetails["size"]=((filedetails["size"]/1073741824) - (filedetails["size"]/1073741824%0.1)) .. "G"
+ elseif ( filedetails["size"] > 1048576 ) then
+ filedetails["size"]=((filedetails["size"]/1048576) - (filedetails["size"]/1048576%0.1)) .. "M"
+ elseif ( filedetails["size"] > 1024 ) then
+ filedetails["size"]=((filedetails["size"]/1024) - (filedetails["size"]/1024%0.1)) .. "k"
+ else
+ filedetails["size"]=filedetails["size"]
+ end
+ end
+ return filedetails
+end
diff --git a/html.lua b/html.lua
new file mode 100644
index 0000000..33d6d71
--- /dev/null
+++ b/html.lua
@@ -0,0 +1,262 @@
+--[[ lowlevel html functions
+ Written for Alpine Configuration Framework (ACF) -- see www.alpinelinux.org
+ Copyright (C) 2007 Nathan Angelacos
+ Licensed under the terms of GPL2
+]]--
+module (..., package.seeall)
+
+--[[ Cookie functions ]]------------------------------------------------------
+cookie={}
+
+-- Set a cookie - returns a string suitable for setting a cookie
+-- if the value is the boolean "false", then set the cookie to expire
+cookie.set = function ( name, value, path )
+ local expires = ""
+ if name == nil then
+ return ("")
+ end
+ if value == false then
+ expires = 'expires=Thu Jan 1 00:00:00 EST 1970'
+ value = ""
+ end
+ if path == nil then
+ path = "/"
+ end
+ return (string.format('Set-Cookie: %s=%s; path=%s; %s\n', html_escape(tostring(name)),
+ html_escape(tostring(value)), html_escape(path), html_escape(expires)))
+end
+
+
+-- wrapper function to clear a cookie
+cookie.unset = function ( name, path)
+ return cookie.set (name, false, path)
+end
+
+
+
+-- escape unsafe html characters
+function html_escape (text )
+ text = text or ""
+ local str = string.gsub (text, "&", "&amp;" )
+ str = string.gsub (str, "<", "&lt;" )
+ str = string.gsub (str, ">", "&gt;" )
+ str = string.gsub (str, "'", "&#39;" )
+ return (string.gsub (str, '"', "&quot;" ))
+end
+
+-- return a name,value pair as a string.
+local nv_pair = function ( name, value)
+ if ( name == nil ) then
+ return ( value or "" )
+ end
+
+ if ( type(value) == "boolean" ) then
+ value = tostring(value)
+ end
+
+ if ( value == nil ) then
+ return ( "" )
+ else
+ return (string.format (' %s="%s" ', html_escape(name) , html_escape(value) ))
+ end
+end
+
+
+--[[
+ each of these functions take a table that has an associative array of
+ the values we might care about:
+
+ value -- this is the value in the form element, or the selected element
+ name -- this is the name of the element
+ cols, rows
+ class
+ id
+ etc.
+]]--
+
+local generic_input = function ( field_type, v )
+ if type(v.value) == "table" then
+ ret = {}
+ local vals = v.value
+ for n, val in ipairs(vals) do
+ v.value = val
+ table.insert(ret, generic_input(field_type, v))
+ end
+ v.value = vals
+ return table.concat(ret)
+ end
+ if ( field_type == nil ) then
+ return nil
+ end
+
+ local str = string.format ( '<input class="%s" type="%s" ', html_escape(field_type), html_escape(field_type) )
+
+ for i,k in ipairs ( {
+ "name", "size", "checked", "maxlength",
+ "value", "length", "class", "id", "src",
+ "align", "alt", "contenteditable", "readonly",
+ "tabindex", "accesskey", "onfocus", "onblur"
+ } ) do
+ str = str .. nv_pair ( k, v[k] )
+ end
+
+ if ( v.disabled ~= nil ) then
+ str = str .. " disabled"
+ end
+
+ return ( str .. ">" )
+end
+
+
+--[[ Form functions ]]------------------------------------------------------
+-- These expect something like a cfe to work (see mvc.lua)
+
+form = {}
+form.text = function ( v )
+ return generic_input ( "text", v )
+end
+
+
+form.longtext = function ( v )
+ local str = "<textarea"
+ for i,k in ipairs ( {
+ "name", "rows", "cols",
+ "class", "id", "tabindex", "accesskey",
+ "onfocus", "onblur", "readonly"
+ } ) do
+ str = str .. nv_pair ( k, v[k] )
+ end
+ str = str .. nv_pair (nil, v.disabled)
+ return ( str .. ">" .. html_escape(v.value) .. "</textarea>" )
+end
+
+
+function form.password ( v )
+ return generic_input ( "password", v )
+end
+
+function form.hidden ( v )
+ return generic_input ( "hidden", v )
+end
+
+
+function form.submit ( v )
+ return generic_input ( "submit", v )
+end
+
+
+function form.action (v)
+ return generic_input ("submit", v)
+end
+
+function form.file ( v )
+ return generic_input ( "file", v )
+end
+
+function form.image ( v )
+ return generic_input ( "image", v )
+end
+
+
+-- v.value is the selected item (or an array if multiple)
+-- v.option is an array of valid options
+-- NOTE use of value and values (plural)
+function form.select ( v )
+ if ( v.name == nil ) then
+ return nil
+ end
+ local str = "<select"
+ for i,k in ipairs ( {
+ "name", "size", "tabindex", "accesskey",
+ "onfocus", "onblur", "onchange", "id",
+ "class", "multiple"
+ } ) do
+ str = str .. nv_pair ( k, v[k] )
+ end
+
+ if ( v.disabled ~= nil ) then
+ str = str .. " disabled"
+ end
+ str = str .. ">"
+ -- now the options
+ local reverseval = {}
+ if type(v.value) == "table" then
+ for x,val in ipairs(v.value) do
+ reverseval[val]=x
+ end
+ end
+ local selected = false
+ for i, k in ipairs ( v.option ) do
+ local val = k
+ local txt = nil
+ if type(val) == "table" then
+ txt=val[1]
+ val=val[0]
+ end
+ str = str .. "<option "
+ if type(v.value) == "table" then
+ if reverseval[val] then
+ str = str .. " selected"
+ selected = true
+ end
+ elseif ( v.value == val ) then
+ str = str .. " selected"
+ selected = true
+ end
+ str = str .. nv_pair("value", val) .. ">" .. html_escape(val) .. "</option>"
+ end
+ if not selected then
+ str = str .. '<option selected value="' .. html_escape(v.value) ..'">[' .. html_escape(v.value) .. ']</option>'
+ end
+ str = str .. "</select>"
+ return (str)
+end
+
+function form.checkbox ( v )
+ return generic_input ( "checkbox", v )
+end
+
+
+-- NOTE: VALUE of a form is a table containing the form elements ...
+function form.start ( v)
+ if ( v.action == nil ) then
+ return nil
+ end
+
+ local method = v.method or "get"
+ return ( string.format (
+ '<form %s%s%s>',
+ nv_pair ( "class", html_escape(v.class) ),
+ nv_pair ( "method", html_escape(v.method) ),
+ nv_pair ( "action", html_escape(v.action) )
+ ) )
+end
+
+function form.stop ( )
+ return ("</form>")
+end
+
+-- For "h1, h2, p," etc
+-- WARNING - Text is printed verbatim - you may want to
+-- wrap the text in html_escape
+function entity (tag, text, class, id)
+ return ( string.format (
+ "<%s%s%s>%s</%s>",
+ html_escape(tag),
+ nv_pair ("class", class),
+ nv_pair("id", id), html_escape(text), html_escape(tag))
+ )
+end
+
+
+function link ( v )
+ if ( v.value == nil ) then
+ return nil
+ end
+ local str = nv_pair ( "href", v.value )
+ for i,k in ipairs( { "class", "id" }) do
+ str = str .. nv_pair ( k, v[k] )
+ end
+
+ return ( "<a " .. str .. ">" .. html_escape(v.label) .. "</a>" )
+end
diff --git a/processinfo.lua b/processinfo.lua
new file mode 100644
index 0000000..7058b04
--- /dev/null
+++ b/processinfo.lua
@@ -0,0 +1,221 @@
+
+module(..., package.seeall)
+
+require("posix")
+require("fs")
+require("format")
+require("apk")
+
+local path = "PATH=/usr/bin:/bin:/usr/sbin:/sbin "
+
+function package_version(packagename)
+ local result = apk.version(packagename)
+ local errtxt
+ if not result then
+ errtxt = "Program not installed"
+ end
+ return result,errtxt
+end
+
+function process_autostart(servicename)
+ local result
+ local errtxt = "Not programmed to autostart"
+ local f = io.popen( "/sbin/rc-update show" )
+ local cmdresult = f:read("*a") or ""
+ f:close()
+ for line in string.gmatch(cmdresult, "[^\n]+") do
+ if string.match(line, "^%s*"..format.escapemagiccharacters(servicename).."%s+|") then
+ local runlevels = string.match(line, "|(.*)")
+ -- ignore the shutdown runlevel
+ runlevels = string.gsub(runlevels, "%sshutdown%s", " ")
+ runlevels = string.gsub(runlevels, "^%s+", "")
+ runlevels = string.gsub(runlevels, "%s+$", "")
+ if runlevels ~= "" then
+ result = "Service will autostart at next boot (at runlevel '" .. runlevels .. "')"
+ errtxt = nil
+ end
+ break
+ end
+ end
+ return result,errtxt
+end
+
+function read_initrunlevels()
+ local config = {}
+ local f = io.popen( "/sbin/rc-update show -v" )
+ local cmdresult = f:read("*a") or ""
+ f:close()
+ for line in string.gmatch(cmdresult, "([^\n]*)\n?") do
+ local service = string.match(line, "^%s*(%S+)")
+ local runlevels = string.match(line, "|%s*(%S.*)")
+ if service then
+ local runlevel = {}
+ if runlevels then
+ runlevel = format.string_to_table(string.gsub(runlevels, "%s+$", ""), "%s+") or {}
+ end
+ config[#config+1] = {servicename=service, runlevels=runlevel}
+ end
+ end
+ table.sort(config, function(a,b) return a.servicename < b.servicename end)
+ return config
+end
+
+function add_runlevels(servicename, runlevels)
+ local cmdresult,cmderrors
+ if not servicename then
+ cmderrors = "Invalid service name"
+ else
+ if runlevels and #runlevels > 0 then
+ local cmd = {path, "rc-update add"}
+ cmd[#cmd+1] = format.escapespecialcharacters(servicename)
+ for i,lev in ipairs(runlevels) do
+ cmd[#cmd+1] = lev
+ end
+ cmd[#cmd+1] = "2>&1"
+ local f = io.popen(table.concat(cmd, " "))
+ cmdresult = f:read("*a")
+ f:close()
+ cmdresult = string.gsub(cmdresult, "\n+$", "")
+ else
+ cmdresult = "No runlevels added"
+ end
+ end
+
+ return cmdresult,cmderrors
+end
+
+function delete_runlevels(servicename, runlevels)
+ local cmdresult,cmderrors
+ if not servicename then
+ cmderrors = "Invalid service name"
+ else
+ if runlevels and #runlevels > 0 then
+ local cmd = {path, "rc-update del"}
+ cmd[#cmd+1] = format.escapespecialcharacters(servicename)
+ for i,lev in ipairs(runlevels) do
+ cmd[#cmd+1] = lev
+ end
+ cmd[#cmd+1] = "2>&1"
+ local f = io.popen(table.concat(cmd, " "))
+ cmdresult = f:read("*a")
+ f:close()
+ cmdresult = string.gsub(cmdresult, "\n+$", "")
+ else
+ cmdresult = "No runlevels deleted"
+ end
+ end
+
+ return cmdresult,cmderrors
+end
+
+function daemoncontrol (process, action)
+
+ local cmdresult = ""
+ local cmderrors
+ if not process then
+ cmderrors = "Invalid service name"
+ elseif not action then
+ cmderrors = "Invalid action"
+ else
+ local file = io.popen( "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin /etc/init.d/" ..
+ format.escapespecialcharacters(process) .. " " .. format.escapespecialcharacters(string.lower(action)) .. " 2>&1" )
+ if file ~= nil then
+ cmdresult = file:read( "*a" )
+ file:close()
+ end
+ end
+ return cmdresult,cmderrors
+end
+
+-- the following methods are available:
+-- /proc/<pid>/stat the comm field (2nd) field contains name but only up
+-- to 15 chars. does not resolve links
+--
+-- /proc/<pid>/cmdline argv[0] contains the command. However if it is a script
+-- then will the interpreter show up
+--
+-- /proc/<pid>/exe link to exe file. this will resolv links
+--
+-- returns list of all pids for given exe name
+
+--[[
+-- gives lots of false positives for busybox
+local function is_exe(path, name)
+ local f = posix.readlink(path.."/exe")
+ if f and (f == name or posix.basename(f) == name) then
+ return true
+ else
+ return false
+ end
+end
+]]--
+
+
+local function is_stat(path, name)
+ local f = io.open(path.."/stat")
+ if (f) then
+ local line = f:read()
+ local p = string.gsub(line, ".*%(", "")
+ p = string.gsub(p, "%).*", "")
+ f:close()
+ end
+ if p ~= nil then
+ if string.len(name) <= 15 and p == name then
+ return true
+ end
+ end
+ return false
+end
+
+local function is_cmdline(path, name)
+ local f = io.open(path.."/cmdline")
+ if f == nil then
+ return false
+ end
+ local line = f:read()
+ f:close()
+ if line == nil then
+ return false
+ end
+ local arg0 = string.gsub(line, string.char(0)..".*", "")
+ if posix.basename(arg0) == name then
+ return true
+ end
+end
+
+local function has_pidfile(name)
+ local pid
+ local file = "/var/run/"..name..".pid"
+ if fs.is_file(file) then
+ -- check to see if there's a matching proc directory and that it was created slightly after the pid file
+ -- this allows us to find init scripts with differing process names and avoids the problem with
+ -- proc numbers wrapping
+ local tmp = string.match(fs.read_file(file) or "", "%d+")
+ if tmp then
+ local dir = "/proc/" .. tmp
+ filetime = posix.stat(file, "ctime")
+ dirtime = posix.stat(dir, "ctime")
+ if dirtime and (tonumber(dirtime) - tonumber(filetime) < 100) then
+ pid = tmp
+ end
+ end
+ end
+ return pid
+end
+
+function pidof(name)
+ local pids = {has_pidfile(name)}
+ local i, j
+
+ for i,j in pairs(posix.glob("/proc/[0-9]*")) do
+ local pid = tonumber(posix.basename(j))
+ if is_stat(j, name) or is_cmdline(j, name) then
+ table.insert(pids, pid)
+ end
+ end
+ if #pids == 0 then
+ pids = nil
+ end
+ return pids
+end
+
diff --git a/validator.lua b/validator.lua
new file mode 100644
index 0000000..ca8ed41
--- /dev/null
+++ b/validator.lua
@@ -0,0 +1,172 @@
+--------------------------------------------------
+-- Validation Functions for Alpine Linux' Webconf
+--------------------------------------------------
+module (..., package.seeall)
+
+function is_string ( str )
+ return (type(str) == "string")
+end
+
+function is_boolean ( str )
+ return (type(str) == "boolean")
+end
+
+function is_number ( str )
+ return (type(str) == "number")
+end
+
+--
+-- This function validates an ipv4 address.
+--
+function is_ipv4(ipv4)
+ local retval = false;
+ local nums = {};
+ local iplen = string.len(ipv4);
+
+ -- check the ipv4's length
+ if (iplen < 7 or iplen > 15) then
+ return false, "Invalid Length"
+ end
+
+ -- NC: Split the string into an array. separate with '.' (dots)
+ -- ^ beginning of string
+ -- () capture
+ -- %. litteral '.' The % neutralizes the . character class.
+ -- %d+ one or more digits
+ -- $ end of string
+ nums = {ipv4:match ("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")}
+
+ -- check if all nums are filled
+ if ( nums[1] == nil or
+ nums[2] == nil or
+ nums[3] == nil or
+ nums[4] == nil) then
+ -- we have an empty number
+ return false, "Invalid Format"
+ end
+
+ -- too big?
+ if (tonumber(nums[1]) > 255 or
+ tonumber(nums[2]) > 255 or
+ tonumber(nums[3]) > 255 or
+ tonumber(nums[4]) > 255) then
+ -- at least one number is too big
+ return false, "Invalid Value"
+ end
+
+ return true
+end
+
+--
+-- This function validates a partial ipv4 address.
+--
+function is_partial_ipv4(ipv4)
+ local retval = false;
+ local nums = {};
+
+ -- Check to see if any invalid characters
+ if not ipv4 or not ipv4:match("^[%d%.]+$") then
+ return false, "Invalid Format"
+ end
+
+ -- NC: Split the string into an array. separate with '.' (dots)
+ -- %d+ one or more digits
+ for num in ipv4:gmatch("%d+") do
+ nums[#nums+1] = num
+ -- too big?
+ if tonumber(num) > 255 then
+ return false, "Invalid Format"
+ end
+ end
+
+ -- too many numbers
+ if #nums > 4 then
+ return false, "Invalid Format"
+ end
+
+ return true
+end
+
+function is_mac(mac)
+
+ local tmpmac = string.upper(mac)
+
+ if (string.len(tmpmac) ~= 17) then
+ return false, "Invalid Length"
+ end
+
+ -- check for valid characters
+ local step = 1;
+ while (step <= 17) do
+ if (string.sub(tmpmac, step, step) ~= ":") and
+ (string.sub(tmpmac, step, step) < "0" or string.sub(tmpmac, step, step) > "9") and
+ (string.sub(tmpmac, step, step) < "A" or string.sub(tmpmac, step, step) > "F") then
+ -- we have found an invalid character!
+ return false, "Invalid Chars"
+ end
+ step = step + 1;
+ end
+
+ -- check for valid colon positions
+ if (string.sub(tmpmac, 3, 3) ~= ":" or
+ string.sub(tmpmac, 6, 6) ~= ":" or
+ string.sub(tmpmac, 9, 9) ~= ":" or
+ string.sub(tmpmac, 12, 12) ~= ":" or
+ string.sub(tmpmac, 15, 15) ~= ":") then
+ return false, "Invalid Format"
+ end
+
+ -- check for valid non colon positions
+ step = 1;
+ while (step <= 17) do
+ if ((string.sub(tmpmac, step, step) == ":") and
+ ((step ~= 3) and (step ~= 6) and (step ~= 9) and (step ~= 12) and
+ (step ~= 15))) then
+ return false, "Invalid Value"
+ end
+ step = step + 1;
+ end
+
+ return true
+end
+
+--
+-- This function checks if the given input
+-- consists of number-chars between 0..9 only
+-- and eventually a leading '-'
+--
+function is_integer(numstr)
+ -- ^ beginning of string
+ -- -? one or zero of the char '-'
+ -- %d+ one or more digits
+ -- $ end of string
+ return string.find(numstr, "^-?%d+$") ~= nil
+end
+
+
+--
+-- This function checks if the given input
+-- consists of number-chars between 0..9 only
+-- and if it is within a given range.
+--
+function is_integer_in_range(numstr, min, max)
+ return is_integer(numstr)
+ and tonumber(numstr) >= min
+ and tonumber(numstr) <= max
+
+end
+
+--
+-- This function checks if the given number is an integer
+-- and wheter it is between 1 .. 65535
+--
+function is_port(numstr)
+ return is_integer_in_range(numstr, 1, 65535)
+end
+
+function is_valid_filename ( path, restriction )
+ if not (path) or ((restriction) and (string.find (path, "^" .. format.escapemagiccharacters(restriction) ) == nil or string.find (path, "/", #restriction+2) )) then
+ return false
+ end
+ return true
+end