1 /*
2 * Copyright 2011, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <mntent.h>
20 #include <stdbool.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/cdefs.h>
25 #include <sys/mount.h>
26 #include <sys/reboot.h>
27 #include <sys/stat.h>
28 #include <sys/syscall.h>
29 #include <sys/types.h>
30 #include <unistd.h>
31
32 #include <cutils/android_reboot.h>
33 #include <cutils/klog.h>
34 #include <cutils/list.h>
35
36 #define TAG "android_reboot"
37 #define READONLY_CHECK_MS 5000
38 #define READONLY_CHECK_TIMES 50
39
40 typedef struct {
41 struct listnode list;
42 struct mntent entry;
43 } mntent_list;
44
has_mount_option(const char * opts,const char * opt_to_find)45 static bool has_mount_option(const char* opts, const char* opt_to_find)
46 {
47 bool ret = false;
48 char* copy = NULL;
49 char* opt;
50 char* rem;
51
52 while ((opt = strtok_r(copy ? NULL : (copy = strdup(opts)), ",", &rem))) {
53 if (!strcmp(opt, opt_to_find)) {
54 ret = true;
55 break;
56 }
57 }
58
59 free(copy);
60 return ret;
61 }
62
is_block_device(const char * fsname)63 static bool is_block_device(const char* fsname)
64 {
65 return !strncmp(fsname, "/dev/block", 10);
66 }
67
68 /* Find all read+write block devices in /proc/mounts and add them to
69 * |rw_entries|.
70 */
find_rw(struct listnode * rw_entries)71 static void find_rw(struct listnode* rw_entries)
72 {
73 FILE* fp;
74 struct mntent* mentry;
75
76 if ((fp = setmntent("/proc/mounts", "r")) == NULL) {
77 KLOG_WARNING(TAG, "Failed to open /proc/mounts.\n");
78 return;
79 }
80 while ((mentry = getmntent(fp)) != NULL) {
81 if (is_block_device(mentry->mnt_fsname) &&
82 has_mount_option(mentry->mnt_opts, "rw")) {
83 mntent_list* item = (mntent_list*)calloc(1, sizeof(mntent_list));
84 item->entry = *mentry;
85 item->entry.mnt_fsname = strdup(mentry->mnt_fsname);
86 item->entry.mnt_dir = strdup(mentry->mnt_dir);
87 item->entry.mnt_type = strdup(mentry->mnt_type);
88 item->entry.mnt_opts = strdup(mentry->mnt_opts);
89 list_add_tail(rw_entries, &item->list);
90 }
91 }
92 endmntent(fp);
93 }
94
free_entries(struct listnode * entries)95 static void free_entries(struct listnode* entries)
96 {
97 struct listnode* node;
98 struct listnode* n;
99 list_for_each_safe(node, n, entries) {
100 mntent_list* item = node_to_item(node, mntent_list, list);
101 free(item->entry.mnt_fsname);
102 free(item->entry.mnt_dir);
103 free(item->entry.mnt_type);
104 free(item->entry.mnt_opts);
105 free(item);
106 }
107 }
108
find_item(struct listnode * rw_entries,const char * fsname_to_find)109 static mntent_list* find_item(struct listnode* rw_entries, const char* fsname_to_find)
110 {
111 struct listnode* node;
112 list_for_each(node, rw_entries) {
113 mntent_list* item = node_to_item(node, mntent_list, list);
114 if (!strcmp(item->entry.mnt_fsname, fsname_to_find)) {
115 return item;
116 }
117 }
118 return NULL;
119 }
120
121 /* Remounting filesystems read-only is difficult when there are files
122 * opened for writing or pending deletes on the filesystem. There is
123 * no way to force the remount with the mount(2) syscall. The magic sysrq
124 * 'u' command does an emergency remount read-only on all writable filesystems
125 * that have a block device (i.e. not tmpfs filesystems) by calling
126 * emergency_remount(), which knows how to force the remount to read-only.
127 * Unfortunately, that is asynchronous, and just schedules the work and
128 * returns. The best way to determine if it is done is to read /proc/mounts
129 * repeatedly until there are no more writable filesystems mounted on
130 * block devices.
131 */
remount_ro(void (* cb_on_remount)(const struct mntent *))132 static void remount_ro(void (*cb_on_remount)(const struct mntent*))
133 {
134 int fd, cnt;
135 FILE* fp;
136 struct mntent* mentry;
137 struct listnode* node;
138
139 list_declare(rw_entries);
140 list_declare(ro_entries);
141
142 sync();
143 find_rw(&rw_entries);
144
145 /* Trigger the remount of the filesystems as read-only,
146 * which also marks them clean.
147 */
148 fd = TEMP_FAILURE_RETRY(open("/proc/sysrq-trigger", O_WRONLY));
149 if (fd < 0) {
150 KLOG_WARNING(TAG, "Failed to open sysrq-trigger.\n");
151 /* TODO: Try to remount each rw parition manually in readonly mode.
152 * This may succeed if no process is using the partition.
153 */
154 goto out;
155 }
156 if (TEMP_FAILURE_RETRY(write(fd, "u", 1)) != 1) {
157 close(fd);
158 KLOG_WARNING(TAG, "Failed to write to sysrq-trigger.\n");
159 /* TODO: The same. Manually remount the paritions. */
160 goto out;
161 }
162 close(fd);
163
164 /* Now poll /proc/mounts till it's done */
165 cnt = 0;
166 while (cnt < READONLY_CHECK_TIMES) {
167 if ((fp = setmntent("/proc/mounts", "r")) == NULL) {
168 /* If we can't read /proc/mounts, just give up. */
169 KLOG_WARNING(TAG, "Failed to open /proc/mounts.\n");
170 goto out;
171 }
172 while ((mentry = getmntent(fp)) != NULL) {
173 if (!is_block_device(mentry->mnt_fsname) ||
174 !has_mount_option(mentry->mnt_opts, "ro")) {
175 continue;
176 }
177 mntent_list* item = find_item(&rw_entries, mentry->mnt_fsname);
178 if (item) {
179 /* |item| has now been ro remounted. */
180 list_remove(&item->list);
181 list_add_tail(&ro_entries, &item->list);
182 }
183 }
184 endmntent(fp);
185 if (list_empty(&rw_entries)) {
186 /* All rw block devices are now readonly. */
187 break;
188 }
189 TEMP_FAILURE_RETRY(
190 usleep(READONLY_CHECK_MS * 1000 / READONLY_CHECK_TIMES));
191 cnt++;
192 }
193
194 list_for_each(node, &rw_entries) {
195 mntent_list* item = node_to_item(node, mntent_list, list);
196 KLOG_WARNING(TAG, "Failed to remount %s in readonly mode.\n",
197 item->entry.mnt_fsname);
198 }
199
200 if (cb_on_remount) {
201 list_for_each(node, &ro_entries) {
202 mntent_list* item = node_to_item(node, mntent_list, list);
203 cb_on_remount(&item->entry);
204 }
205 }
206
207 out:
208 free_entries(&rw_entries);
209 free_entries(&ro_entries);
210 }
211
android_reboot_with_callback(int cmd,int flags __unused,const char * arg,void (* cb_on_remount)(const struct mntent *))212 int android_reboot_with_callback(
213 int cmd, int flags __unused, const char *arg,
214 void (*cb_on_remount)(const struct mntent*))
215 {
216 int ret;
217 remount_ro(cb_on_remount);
218 switch (cmd) {
219 case ANDROID_RB_RESTART:
220 ret = reboot(RB_AUTOBOOT);
221 break;
222
223 case ANDROID_RB_POWEROFF:
224 ret = reboot(RB_POWER_OFF);
225 break;
226
227 case ANDROID_RB_RESTART2:
228 ret = syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
229 LINUX_REBOOT_CMD_RESTART2, arg);
230 break;
231
232 default:
233 ret = -1;
234 }
235
236 return ret;
237 }
238
android_reboot(int cmd,int flags,const char * arg)239 int android_reboot(int cmd, int flags, const char *arg)
240 {
241 return android_reboot_with_callback(cmd, flags, arg, NULL);
242 }
243