13bc6e50925df47966ede75634daf0711fe2bbcf
[project/luci.git] / modules / luci-mod-failsafe / luasrc / controller / failsafe / failsafe.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008-2011 Jo-Philipp Wich <xm@subsignal.org>
3 -- Copyright 2012 Daniel Golle <dgolle@allnet.de>
4 -- Licensed to the public under the Apache License 2.0.
5
6 module("luci.controller.failsafe.failsafe", package.seeall)
7
8 function index()
9         local root = node()
10         if not root.target then
11                 root.target = alias("failsafe")
12                 root.index = true
13         end
14
15         page          = node()
16         page.lock     = true
17         page.target   = alias("failsafe")
18         page.subindex = true
19         page.index    = false
20
21         page          = node("failsafe")
22         page.title    = _("Fail-safe")
23         page.target   = alias("failsafe", "flashops")
24         page.order    = 5
25         page.setuser  = "root"
26         page.setgroup = "root"
27         page.index    = true
28
29         entry({"failsafe", "flashops"}, call("action_flashops"), _("Flash Firmware"), 70).index = true
30         entry({"failsafe", "reboot"}, call("action_reboot"), _("Reboot"), 90)
31 end
32
33 function action_flashops()
34         local sys = require "luci.sys"
35         local fs  = require "nixio.fs"
36
37         local upgrade_avail = fs.access("/lib/upgrade/platform.sh")
38         local reset_avail   = os.execute([[grep '"rootfs_data"' /proc/mtd >/dev/null 2>&1]]) == 0
39
40         local image_tmp   = "/tmp/firmware.img"
41
42         local function image_supported()
43                 -- XXX: yay...
44                 return ( 0 == os.execute(
45                         ". /lib/functions.sh; " ..
46                         "include /lib/upgrade; " ..
47                         "platform_check_image %q >/dev/null"
48                                 % image_tmp
49                 ) )
50         end
51
52         local function image_checksum()
53                 return (luci.sys.exec("md5sum %q" % image_tmp):match("^([^%s]+)"))
54         end
55
56         local function storage_size()
57                 local size = 0
58                 if fs.access("/proc/mtd") then
59                         for l in io.lines("/proc/mtd") do
60                                 local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
61                                 if n == "linux" or n == "firmware" then
62                                         size = tonumber(s, 16)
63                                         break
64                                 end
65                         end
66                 elseif fs.access("/proc/partitions") then
67                         for l in io.lines("/proc/partitions") do
68                                 local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
69                                 if b and n and not n:match('[0-9]') then
70                                         size = tonumber(b) * 1024
71                                         break
72                                 end
73                         end
74                 end
75                 return size
76         end
77
78
79         local fp
80         luci.http.setfilehandler(
81                 function(meta, chunk, eof)
82                         if not fp then
83                                 if meta and meta.name == "image" then
84                                         fp = io.open(image_tmp, "w")
85                                 end
86                         end
87                         if chunk then
88                                 fp:write(chunk)
89                         end
90                         if eof then
91                                 fp:close()
92                         end
93                 end
94         )
95
96         if luci.http.formvalue("image") or luci.http.formvalue("step") then
97                 --
98                 -- Initiate firmware flash
99                 --
100                 local step = tonumber(luci.http.formvalue("step") or 1)
101                 if step == 1 then
102                         if image_supported() then
103                                 luci.template.render("failsafe/upgrade", {
104                                         checksum = image_checksum(),
105                                         storage  = storage_size(),
106                                         size     = (fs.stat(image_tmp, "size") or 0),
107                                         keep     = false
108                                 })
109                         else
110                                 fs.unlink(image_tmp)
111                                 luci.template.render("failsafe/flashops", {
112                                         reset_avail   = reset_avail,
113                                         upgrade_avail = upgrade_avail,
114                                         image_invalid = true
115                                 })
116                         end
117                 --
118                 -- Start sysupgrade flash
119                 --
120                 elseif step == 2 then
121                         local keep = (luci.http.formvalue("keep") == "1") and "" or "-n"
122                         luci.template.render("failsafe/applyreboot", {
123                                 title = luci.i18n.translate("Flashing..."),
124                                 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."),
125                                 addr  = (#keep > 0) and "192.168.1.1" or nil
126                         })
127                         fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
128                 end
129         else
130                 --
131                 -- Overview
132                 --
133                 luci.template.render("failsafe/flashops", {
134                         reset_avail   = reset_avail,
135                         upgrade_avail = upgrade_avail
136                 })
137         end
138 end
139
140 function action_reboot()
141         local reboot = luci.http.formvalue("reboot")
142         luci.template.render("failsafe/reboot", {reboot=reboot})
143         if reboot then
144                 luci.sys.reboot()
145         end
146 end
147
148 function fork_exec(command)
149         local pid = nixio.fork()
150         if pid > 0 then
151                 return
152         elseif pid == 0 then
153                 -- change to root dir
154                 nixio.chdir("/")
155
156                 -- patch stdin, out, err to /dev/null
157                 local null = nixio.open("/dev/null", "w+")
158                 if null then
159                         nixio.dup(null, nixio.stderr)
160                         nixio.dup(null, nixio.stdout)
161                         nixio.dup(null, nixio.stdin)
162                         if null:fileno() > 2 then
163                                 null:close()
164                         end
165                 end
166
167                 -- replace with target command
168                 nixio.exec("/bin/sh", "-c", command)
169         end
170 end
171
172 function ltn12_popen(command)
173
174         local fdi, fdo = nixio.pipe()
175         local pid = nixio.fork()
176
177         if pid > 0 then
178                 fdo:close()
179                 local close
180                 return function()
181                         local buffer = fdi:read(2048)
182                         local wpid, stat = nixio.waitpid(pid, "nohang")
183                         if not close and wpid and stat == "exited" then
184                                 close = true
185                         end
186
187                         if buffer and #buffer > 0 then
188                                 return buffer
189                         elseif close then
190                                 fdi:close()
191                                 return nil
192                         end
193                 end
194         elseif pid == 0 then
195                 nixio.dup(fdo, nixio.stdout)
196                 fdi:close()
197                 fdo:close()
198                 nixio.exec("/bin/sh", "-c", command)
199         end
200 end