Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // +build !windows | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 // +build android | |
| 6 | |
| 7 /* | |
| 8 Watchdog daemon for android devices. It will attempt to reboot the device | |
| 9 if its uptime exceeds a specified maximum. | |
| 10 */ | |
| 2 | 11 |
| 3 package main | 12 package main |
| 4 | 13 |
| 14 // Needed for logcat integration. | |
| 15 /* | |
| 16 #cgo LDFLAGS: -landroid -llog | |
| 17 | |
| 18 #include <android/log.h> | |
| 19 #include <string.h> | |
| 20 */ | |
| 21 import "C" | |
| 22 | |
| 5 import ( | 23 import ( |
| 6 » "C" | 24 » "flag" |
| 7 "fmt" | 25 "fmt" |
| 26 "io/ioutil" | |
| 27 "os" | |
| 28 "strconv" | |
| 29 "strings" | |
| 30 "syscall" | |
| 31 "time" | |
| 8 ) | 32 ) |
| 9 | 33 |
| 34 var ( | |
| 35 logHeader = C.CString("CIT_DeviceWatchdog") | |
| 36 ) | |
| 37 | |
| 38 func logcatInfo(msg string) { | |
|
dnj
2016/08/15 19:43:29
Consider making this accept a format string:
func
bpastene
2016/08/15 22:42:02
Done.
| |
| 39 C.__android_log_write(C.ANDROID_LOG_INFO, logHeader, C.CString(msg)) | |
|
dnj
2016/08/15 19:43:30
You need to free the string that you create:
cmsg
bpastene
2016/08/15 22:42:02
Done.
| |
| 40 } | |
| 41 | |
| 42 func logcatError(msg string) { | |
| 43 C.__android_log_write(C.ANDROID_LOG_ERROR, logHeader, C.CString(msg)) | |
| 44 } | |
| 45 | |
| 46 // Spawn a child process via fork, create new process group, chdir and | |
| 47 // redirect std in and out to /dev/null. | |
| 48 func daemonize() (int, error) { | |
|
dnj
2016/08/15 19:43:29
Consider using a formal package for daemonizing:
h
bpastene
2016/08/15 22:42:02
Yeah, already tried that exact package. I was seei
| |
| 49 ret, _, errno := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0) | |
| 50 pid := int(ret) | |
| 51 if errno != 0 { | |
| 52 return -1, errno | |
|
dnj
2016/08/15 19:43:29
nit: when returning an error code, you can assume
bpastene
2016/08/15 22:42:02
Done.
| |
| 53 } | |
| 54 if pid > 0 { | |
| 55 return pid, nil | |
| 56 } | |
| 57 | |
| 58 _, err := syscall.Setsid() | |
| 59 if err != nil { | |
| 60 return -1, err | |
| 61 } | |
| 62 os.Chdir("/") | |
|
dnj
2016/08/15 19:43:29
(Note this is normally not part of daemonizing, ma
bpastene
2016/08/15 22:42:02
Done.
| |
| 63 | |
| 64 f, err := os.Open("/dev/null") | |
| 65 if err != nil { | |
| 66 return -1, err | |
| 67 } | |
| 68 fd := f.Fd() | |
| 69 syscall.Dup2(int(fd), int(os.Stdin.Fd())) | |
|
dnj
2016/08/15 19:43:30
Because "os.Stdin" can change, I would just hardco
bpastene
2016/08/15 22:42:02
Done.
| |
| 70 syscall.Dup2(int(fd), int(os.Stdout.Fd())) | |
| 71 syscall.Dup2(int(fd), int(os.Stderr.Fd())) | |
| 72 | |
| 73 return pid, nil | |
| 74 } | |
| 75 | |
| 76 // Read from /proc/uptime. Expected format: | |
| 77 // "uptime_in_seconds cpu_idle_time_in_seconds" | |
| 78 func getDeviceUptime() (float64, error) { | |
| 79 bytes, err := ioutil.ReadFile("/proc/uptime") | |
| 80 if err != nil { | |
| 81 return -1, err | |
| 82 } | |
| 83 // Split on the space to get uptime and drop cpu idle time. | |
| 84 uptimeStr := strings.Fields(string(bytes))[0] | |
|
dnj
2016/08/15 19:43:29
I would assert that the field size is what you exp
bpastene
2016/08/15 22:42:02
Done.
| |
| 85 uptime, err := strconv.ParseFloat(uptimeStr, 64) | |
| 86 if err != nil { | |
| 87 return -1, err | |
|
dnj
2016/08/15 19:43:29
nit: surround with context:
return 0, fmt.Errorf(
bpastene
2016/08/15 22:42:02
Done.
| |
| 88 } | |
| 89 return uptime, nil | |
|
dnj
2016/08/15 19:43:30
nit: You might want to make this a little more go
bpastene
2016/08/15 22:42:02
Done.
| |
| 90 } | |
| 91 | |
| 92 // Reboot device by writing to sysrq-trigger. See: | |
| 93 // https://www.kernel.org/doc/Documentation/sysrq.txt | |
| 94 func rebootDevice() { | |
| 95 err := ioutil.WriteFile("/proc/sysrq-trigger", []byte("b"), 0644) | |
|
dnj
2016/08/15 19:43:29
I think rather than WriteFile (which is generally
bpastene
2016/08/15 22:42:02
Done.
| |
| 96 if err != nil { | |
| 97 logcatError(err.Error()) | |
| 98 os.Exit(1) | |
| 99 } | |
| 100 logcatError("I just rebooted. How am I still alive?!?\n") | |
|
dnj
2016/08/15 19:43:30
Is the reboot that immediate? Or could this string
bpastene
2016/08/15 22:42:02
For all local testing, it's been instantaneous.
| |
| 101 os.Exit(1) | |
| 102 } | |
| 103 | |
| 10 func main() { | 104 func main() { |
| 11 » fmt.Println("Is this thing working?") | 105 |
| 106 » maxUptimeFlag := flag.Int("max-uptime", 120, "Maximum uptime in minutes before a reboot is triggered.") | |
| 107 » flag.Parse() | |
| 108 | |
| 109 » pid, err := daemonize() | |
| 110 » if err != nil { | |
| 111 » » logcatError(err.Error()) | |
| 112 » » os.Exit(1) | |
| 113 » } | |
| 114 » if pid > 0 { | |
| 115 » » logcatInfo(fmt.Sprintf("Child spawned with pid %d, exiting paren t\n", pid)) | |
| 116 » » os.Exit(0) | |
| 117 » } | |
| 118 | |
| 119 » maxUptime := float64(*maxUptimeFlag) | |
| 120 » for { | |
| 121 » » uptime, err := getDeviceUptime() | |
| 122 » » if err != nil { | |
| 123 » » » logcatError(err.Error()) | |
| 124 » » » os.Exit(1) | |
| 125 » » } | |
| 126 » » uptime = uptime / 60 | |
| 127 | |
| 128 » » if uptime > maxUptime { | |
| 129 » » » logcatInfo(fmt.Sprintf("Max uptime exceeded: (%.2f > %.0 f)\n", uptime, maxUptime)) | |
| 130 » » » rebootDevice() | |
| 131 » » } else { | |
| 132 » » » logcatInfo(fmt.Sprintf("No need to reboot, uptime < max_ uptime: (%.2f < %.2f)\n", uptime, maxUptime)) | |
| 133 » » } | |
| 134 » » time.Sleep(60 * time.Second) | |
|
dnj
2016/08/15 19:43:30
Any reason not to just sleep the difference? e.g.,
bpastene
2016/08/15 22:42:02
No reason currently; changed it to sleep the diffe
| |
| 135 » } | |
| 12 } | 136 } |
| OLD | NEW |