summaryrefslogtreecommitdiffstats
path: root/processinfo.lua
blob: 75b9672a2ccd74d44901528171b5b5c4d5d605e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247

local mymodule = {}

posix = require("posix")
fs = require("acf.fs")
format = require("acf.format")
apk = require("acf.apk")
subprocess = require("subprocess")

function mymodule.package_version(packagename)
	local result = apk.version(packagename)
	local errtxt
	if not result then
		errtxt = "Program not installed"
	end
	return result,errtxt
end

function mymodule.process_autostart(servicename)
	local result
	local errtxt = "Not programmed to autostart"
	local code, cmdresult = subprocess.call_capture({"rc-update", "show"})
	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 mymodule.read_initrunlevels()
	local config = {}
	local code, cmdresult = subprocess.call_capture({"rc-update", "show", "-v"})
	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 and service ~= "rcK" and service ~= "rcL" and service ~= "rcS" 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 mymodule.add_runlevels(servicename, runlevels)
	local cmdresult,cmderrors
	if not servicename then
		cmderrors = "Invalid service name"
	else
		if runlevels and #runlevels > 0 then
			local cmd = {"rc-update", "add"}
			cmd[#cmd+1] = servicename
			for i,lev in ipairs(runlevels) do
				cmd[#cmd+1] = lev
			end
			cmd.stderr = subprocess.STDOUT
			local code
			code, cmdresult = subprocess.call_capture(cmd)
			cmdresult = string.gsub(cmdresult, "\n+$", "")
		else
			cmdresult = "No runlevels added"
		end
	end

	return cmdresult,cmderrors
end

function mymodule.delete_runlevels(servicename, runlevels)
	local cmdresult,cmderrors
	if not servicename then
		cmderrors = "Invalid service name"
	else
		if runlevels and #runlevels > 0 then
			local cmd = {"rc-update", "del"}
			cmd[#cmd+1] = servicename
			for i,lev in ipairs(runlevels) do
				cmd[#cmd+1] = lev
			end
			cmd.stderr = subprocess.STDOUT
			local code
			code, cmdresult = subprocess.call_capture(cmd)
			cmdresult = string.gsub(cmdresult, "\n+$", "")
		else
			cmdresult = "No runlevels deleted"
		end
	end

	return cmdresult,cmderrors
end

function mymodule.daemoncontrol (process, action)
	local cmdresult
	local cmderrors
	if not process then
		cmderrors = "Invalid service name"
	elseif not action then
		cmderrors = "Invalid action"
	else
		local res, err = pcall(function()
			local code
			code, cmdresult = subprocess.call_capture({"/etc/init.d/" .. process, string.lower(action), stderr=subprocess.STDOUT})
		end)
		if not res or err then
			cmderrors = err
		end
	end
	return cmdresult,cmderrors
end

function mymodule.daemon_actions (process)
	local actions = {"status", "start", "stop", "restart", "describe", "zap"}
	local reverse = {}
	for i,a in ipairs(actions) do
		reverse[a] = i
	end
	local description
	local res, err = mymodule.daemoncontrol(process, "describe")
	if err then
		return nil, err
	else
		lines = format.string_to_table(res, "\n")
		-- Description is last line before first action
		-- Actions are of the form " * action: description"
		local found = false
		for i,l in ipairs(lines) do
			local line = string.gsub(l, "^%s*%*%s*", "")
			if string.find(line, "^%S*:") then
				found = true
				local act = string.match(line, "^([^:]*)")
				if act and not reverse[act] then
					actions[#actions+1] = act
					reverse[act] = #actions
				end
			elseif not found then
				description = line
			end
		end
	end
	return actions, description
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 mymodule.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

return mymodule