Globally reduce copyright headers
[project/luci.git] / modules / luci-mod-admin-full / luasrc / controller / admin / system.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008-2011 Jo-Philipp Wich <xm@subsignal.org>
3 -- Licensed to the public under the Apache License 2.0.
4
5 module("luci.controller.admin.system", package.seeall)
6
7 function index()
8         local fs = require "nixio.fs"
9
10         entry({"admin", "system"}, alias("admin", "system", "system"), _("System"), 30).index = true
11         entry({"admin", "system", "system"}, cbi("admin_system/system"), _("System"), 1)
12         entry({"admin", "system", "clock_status"}, call("action_clock_status"))
13
14         entry({"admin", "system", "admin"}, cbi("admin_system/admin"), _("Administration"), 2)
15
16         if fs.access("/bin/opkg") then
17                 entry({"admin", "system", "packages"}, call("action_packages"), _("Software"), 10)
18                 entry({"admin", "system", "packages", "ipkg"}, form("admin_system/ipkg"))
19         end
20
21         entry({"admin", "system", "startup"}, form("admin_system/startup"), _("Startup"), 45)
22         entry({"admin", "system", "crontab"}, form("admin_system/crontab"), _("Scheduled Tasks"), 46)
23
24         if fs.access("/etc/config/fstab") then
25                 entry({"admin", "system", "fstab"}, cbi("admin_system/fstab"), _("Mount Points"), 50)
26                 entry({"admin", "system", "fstab", "mount"}, cbi("admin_system/fstab/mount"), nil).leaf = true
27                 entry({"admin", "system", "fstab", "swap"},  cbi("admin_system/fstab/swap"),  nil).leaf = true
28         end
29
30         if fs.access("/sys/class/leds") then
31                 entry({"admin", "system", "leds"}, cbi("admin_system/leds"), _("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), 60)
32         end
33
34         entry({"admin", "system", "flashops"}, call("action_flashops"), _("Backup / Flash Firmware"), 70)
35         entry({"admin", "system", "flashops", "backupfiles"}, form("admin_system/backupfiles"))
36
37         entry({"admin", "system", "reboot"}, call("action_reboot"), _("Reboot"), 90)
38 end
39
40 function action_clock_status()
41         local set = tonumber(luci.http.formvalue("set"))
42         if set ~= nil and set > 0 then
43                 local date = os.date("*t", set)
44                 if date then
45                         luci.sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d'" %{
46                                 date.year, date.month, date.day, date.hour, date.min, date.sec
47                         })
48                 end
49         end
50
51         luci.http.prepare_content("application/json")
52         luci.http.write_json({ timestring = os.date("%c") })
53 end
54
55 function action_packages()
56         local fs = require "nixio.fs"
57         local ipkg = require "luci.model.ipkg"
58         local submit = luci.http.formvalue("submit")
59         local changes = false
60         local install = { }
61         local remove  = { }
62         local stdout  = { "" }
63         local stderr  = { "" }
64         local out, err
65
66         -- Display
67         local display = luci.http.formvalue("display") or "installed"
68
69         -- Letter
70         local letter = string.byte(luci.http.formvalue("letter") or "A", 1)
71         letter = (letter == 35 or (letter >= 65 and letter <= 90)) and letter or 65
72
73         -- Search query
74         local query = luci.http.formvalue("query")
75         query = (query ~= '') and query or nil
76
77
78         -- Packets to be installed
79         local ninst = submit and luci.http.formvalue("install")
80         local uinst = nil
81
82         -- Install from URL
83         local url = luci.http.formvalue("url")
84         if url and url ~= '' and submit then
85                 uinst = url
86         end
87
88         -- Do install
89         if ninst then
90                 install[ninst], out, err = ipkg.install(ninst)
91                 stdout[#stdout+1] = out
92                 stderr[#stderr+1] = err
93                 changes = true
94         end
95
96         if uinst then
97                 local pkg
98                 for pkg in luci.util.imatch(uinst) do
99                         install[uinst], out, err = ipkg.install(pkg)
100                         stdout[#stdout+1] = out
101                         stderr[#stderr+1] = err
102                         changes = true
103                 end
104         end
105
106         -- Remove packets
107         local rem = submit and luci.http.formvalue("remove")
108         if rem then
109                 remove[rem], out, err = ipkg.remove(rem)
110                 stdout[#stdout+1] = out
111                 stderr[#stderr+1] = err
112                 changes = true
113         end
114
115
116         -- Update all packets
117         local update = luci.http.formvalue("update")
118         if update then
119                 update, out, err = ipkg.update()
120                 stdout[#stdout+1] = out
121                 stderr[#stderr+1] = err
122         end
123
124
125         -- Upgrade all packets
126         local upgrade = luci.http.formvalue("upgrade")
127         if upgrade then
128                 upgrade, out, err = ipkg.upgrade()
129                 stdout[#stdout+1] = out
130                 stderr[#stderr+1] = err
131         end
132
133
134         -- List state
135         local no_lists = true
136         local old_lists = false
137         if fs.access("/var/opkg-lists/") then
138                 local list
139                 for list in fs.dir("/var/opkg-lists/") do
140                         no_lists = false
141                         if (fs.stat("/var/opkg-lists/"..list, "mtime") or 0) < (os.time() - (24 * 60 * 60)) then
142                                 old_lists = true
143                                 break
144                         end
145                 end
146         end
147
148
149         luci.template.render("admin_system/packages", {
150                 display   = display,
151                 letter    = letter,
152                 query     = query,
153                 install   = install,
154                 remove    = remove,
155                 update    = update,
156                 upgrade   = upgrade,
157                 no_lists  = no_lists,
158                 old_lists = old_lists,
159                 stdout    = table.concat(stdout, ""),
160                 stderr    = table.concat(stderr, "")
161         })
162
163         -- Remove index cache
164         if changes then
165                 fs.unlink("/tmp/luci-indexcache")
166         end
167 end
168
169 function action_flashops()
170         local sys = require "luci.sys"
171         local fs  = require "nixio.fs"
172
173         local upgrade_avail = fs.access("/lib/upgrade/platform.sh")
174         local reset_avail   = os.execute([[grep '"rootfs_data"' /proc/mtd >/dev/null 2>&1]]) == 0
175
176         local restore_cmd = "tar -xzC/ >/dev/null 2>&1"
177         local backup_cmd  = "sysupgrade --create-backup - 2>/dev/null"
178         local image_tmp   = "/tmp/firmware.img"
179
180         local function image_supported()
181                 -- XXX: yay...
182                 return ( 0 == os.execute(
183                         ". /lib/functions.sh; " ..
184                         "include /lib/upgrade; " ..
185                         "platform_check_image %q >/dev/null"
186                                 % image_tmp
187                 ) )
188         end
189
190         local function image_checksum()
191                 return (luci.sys.exec("md5sum %q" % image_tmp):match("^([^%s]+)"))
192         end
193
194         local function storage_size()
195                 local size = 0
196                 if fs.access("/proc/mtd") then
197                         for l in io.lines("/proc/mtd") do
198                                 local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
199                                 if n == "linux" or n == "firmware" then
200                                         size = tonumber(s, 16)
201                                         break
202                                 end
203                         end
204                 elseif fs.access("/proc/partitions") then
205                         for l in io.lines("/proc/partitions") do
206                                 local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
207                                 if b and n and not n:match('[0-9]') then
208                                         size = tonumber(b) * 1024
209                                         break
210                                 end
211                         end
212                 end
213                 return size
214         end
215
216
217         local fp
218         luci.http.setfilehandler(
219                 function(meta, chunk, eof)
220                         if not fp then
221                                 if meta and meta.name == "image" then
222                                         fp = io.open(image_tmp, "w")
223                                 else
224                                         fp = io.popen(restore_cmd, "w")
225                                 end
226                         end
227                         if chunk then
228                                 fp:write(chunk)
229                         end
230                         if eof then
231                                 fp:close()
232                         end
233                 end
234         )
235
236         if luci.http.formvalue("backup") then
237                 --
238                 -- Assemble file list, generate backup
239                 --
240                 local reader = ltn12_popen(backup_cmd)
241                 luci.http.header('Content-Disposition', 'attachment; filename="backup-%s-%s.tar.gz"' % {
242                         luci.sys.hostname(), os.date("%Y-%m-%d")})
243                 luci.http.prepare_content("application/x-targz")
244                 luci.ltn12.pump.all(reader, luci.http.write)
245         elseif luci.http.formvalue("restore") then
246                 --
247                 -- Unpack received .tar.gz
248                 --
249                 local upload = luci.http.formvalue("archive")
250                 if upload and #upload > 0 then
251                         luci.template.render("admin_system/applyreboot")
252                         luci.sys.reboot()
253                 end
254         elseif luci.http.formvalue("image") or luci.http.formvalue("step") then
255                 --
256                 -- Initiate firmware flash
257                 --
258                 local step = tonumber(luci.http.formvalue("step") or 1)
259                 if step == 1 then
260                         if image_supported() then
261                                 luci.template.render("admin_system/upgrade", {
262                                         checksum = image_checksum(),
263                                         storage  = storage_size(),
264                                         size     = (fs.stat(image_tmp, "size") or 0),
265                                         keep     = (not not luci.http.formvalue("keep"))
266                                 })
267                         else
268                                 fs.unlink(image_tmp)
269                                 luci.template.render("admin_system/flashops", {
270                                         reset_avail   = reset_avail,
271                                         upgrade_avail = upgrade_avail,
272                                         image_invalid = true
273                                 })
274                         end
275                 --
276                 -- Start sysupgrade flash
277                 --
278                 elseif step == 2 then
279                         local keep = (luci.http.formvalue("keep") == "1") and "" or "-n"
280                         luci.template.render("admin_system/applyreboot", {
281                                 title = luci.i18n.translate("Flashing..."),
282                                 msg   = luci.i18n.translate("The system is flashing now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a few minutes before you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings."),
283                                 addr  = (#keep > 0) and "192.168.1.1" or nil
284                         })
285                         fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
286                 end
287         elseif reset_avail and luci.http.formvalue("reset") then
288                 --
289                 -- Reset system
290                 --
291                 luci.template.render("admin_system/applyreboot", {
292                         title = luci.i18n.translate("Erasing..."),
293                         msg   = luci.i18n.translate("The system is erasing the configuration partition now and will reboot itself when finished."),
294                         addr  = "192.168.1.1"
295                 })
296                 fork_exec("killall dropbear uhttpd; sleep 1; mtd -r erase rootfs_data")
297         else
298                 --
299                 -- Overview
300                 --
301                 luci.template.render("admin_system/flashops", {
302                         reset_avail   = reset_avail,
303                         upgrade_avail = upgrade_avail
304                 })
305         end
306 end
307
308 function action_passwd()
309         local p1 = luci.http.formvalue("pwd1")
310         local p2 = luci.http.formvalue("pwd2")
311         local stat = nil
312
313         if p1 or p2 then
314                 if p1 == p2 then
315                         stat = luci.sys.user.setpasswd("root", p1)
316                 else
317                         stat = 10
318                 end
319         end
320
321         luci.template.render("admin_system/passwd", {stat=stat})
322 end
323
324 function action_reboot()
325         local reboot = luci.http.formvalue("reboot")
326         luci.template.render("admin_system/reboot", {reboot=reboot})
327         if reboot then
328                 luci.sys.reboot()
329         end
330 end
331
332 function fork_exec(command)
333         local pid = nixio.fork()
334         if pid > 0 then
335                 return
336         elseif pid == 0 then
337                 -- change to root dir
338                 nixio.chdir("/")
339
340                 -- patch stdin, out, err to /dev/null
341                 local null = nixio.open("/dev/null", "w+")
342                 if null then
343                         nixio.dup(null, nixio.stderr)
344                         nixio.dup(null, nixio.stdout)
345                         nixio.dup(null, nixio.stdin)
346                         if null:fileno() > 2 then
347                                 null:close()
348                         end
349                 end
350
351                 -- replace with target command
352                 nixio.exec("/bin/sh", "-c", command)
353         end
354 end
355
356 function ltn12_popen(command)
357
358         local fdi, fdo = nixio.pipe()
359         local pid = nixio.fork()
360
361         if pid > 0 then
362                 fdo:close()
363                 local close
364                 return function()
365                         local buffer = fdi:read(2048)
366                         local wpid, stat = nixio.waitpid(pid, "nohang")
367                         if not close and wpid and stat == "exited" then
368                                 close = true
369                         end
370
371                         if buffer and #buffer > 0 then
372                                 return buffer
373                         elseif close then
374                                 fdi:close()
375                                 return nil
376                         end
377                 end
378         elseif pid == 0 then
379                 nixio.dup(fdo, nixio.stdout)
380                 fdi:close()
381                 fdo:close()
382                 nixio.exec("/bin/sh", "-c", command)
383         end
384 end