summaryrefslogtreecommitdiff
path: root/apps/plugins/multiboot_select.c
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2022-04-16 12:13:19 +0100
committerAidan MacDonald <amachronic@protonmail.com>2022-04-16 14:36:39 +0100
commit3fcdadce240d2227c1864f2a42a6ae0db1bb372c (patch)
tree61e9b1141f684b7e0ff61f05243ec82ef549e83f /apps/plugins/multiboot_select.c
parentd55dceff371c4080d179fb26e6f175927cc48768 (diff)
downloadrockbox-3fcdadce240d2227c1864f2a42a6ae0db1bb372c.tar.gz
rockbox-3fcdadce240d2227c1864f2a42a6ae0db1bb372c.zip
plugins: Add multiboot select plugin
This plugin provides a menu for easily editing the root redirect file on targets that support multiboot. You can select a new root from a list of Rockbox installations detected on the filesystem or remove all redirects to boot from the default location. To avoid searching the whole filesystem, only subdirectories of the volume roots are checked for valid installations. Only installations that are compatible with the current player will be displayed. Change-Id: I7dcbadfd97873b87817870e61d2ae37956d2da00
Diffstat (limited to 'apps/plugins/multiboot_select.c')
-rw-r--r--apps/plugins/multiboot_select.c346
1 files changed, 346 insertions, 0 deletions
diff --git a/apps/plugins/multiboot_select.c b/apps/plugins/multiboot_select.c
new file mode 100644
index 0000000000..63babbb2c1
--- /dev/null
+++ b/apps/plugins/multiboot_select.c
@@ -0,0 +1,346 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2022 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "plugin.h"
23
24/* should be more than enough */
25#define MAX_ROOTS 128
26
27static enum plugin_status plugin_status = PLUGIN_OK;
28static char tmpbuf[MAX_PATH+1];
29static char tmpbuf2[MAX_PATH+1];
30static char cur_root[MAX_PATH];
31static char roots[MAX_ROOTS][MAX_PATH];
32static int nroots;
33
34/* Read a redirect file and return the path */
35static char* read_redirect_file(const char* filename)
36{
37 int fd = rb->open(filename, O_RDONLY);
38 if(fd < 0)
39 return NULL;
40
41 ssize_t ret = rb->read(fd, tmpbuf, sizeof(tmpbuf));
42 rb->close(fd);
43 if(ret < 0 || (size_t)ret >= sizeof(tmpbuf))
44 return NULL;
45
46 /* relative paths are ignored */
47 if(tmpbuf[0] != '/')
48 ret = 0;
49
50 /* remove trailing control chars (newlines etc.) */
51 for(ssize_t i = ret - 1; i >= 0 && tmpbuf[i] < 0x20; --i)
52 tmpbuf[i] = '\0';
53
54 return tmpbuf;
55}
56
57/* Search for a redirect file, like get_redirect_dir() */
58static const char* read_redirect(void)
59{
60 for(int vol = NUM_VOLUMES-1; vol >= MULTIBOOT_MIN_VOLUME; --vol) {
61 rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s", vol, BOOT_REDIR);
62 const char* path = read_redirect_file(tmpbuf);
63 if(path && path[0] == '/') {
64 /* prepend the volume because that's what we expect */
65 rb->snprintf(tmpbuf2, sizeof(tmpbuf2), "/<%d>%s", vol, path);
66 return tmpbuf2;
67 }
68 }
69
70 tmpbuf[0] = '\0';
71 return tmpbuf;
72}
73
74/* Remove all redirect files except for the one on volume 'exclude_vol' */
75static int clear_redirect(int exclude_vol)
76{
77 int ret = 0;
78 for(int vol = MULTIBOOT_MIN_VOLUME; vol < NUM_VOLUMES; ++vol) {
79 if(vol == exclude_vol)
80 continue;
81
82 rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s", vol, BOOT_REDIR);
83 if(rb->file_exists(tmpbuf) && rb->remove(tmpbuf) < 0)
84 ret = -1;
85 }
86
87 return ret;
88}
89
90/* Save a path to the redirect file */
91static int write_redirect(const char* abspath)
92{
93 /* get the volume (required) */
94 const char* path = abspath;
95 int vol = rb->path_strip_volume(abspath, &path, false);
96 if(path == abspath)
97 return -1;
98
99 /* remove all other redirect files */
100 if(clear_redirect(vol))
101 return -1;
102
103 /* open the redirect file on the same volume */
104 rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s", vol, BOOT_REDIR);
105 int fd = rb->open(tmpbuf, O_WRONLY|O_CREAT|O_TRUNC, 0644);
106 if(fd < 0)
107 return fd;
108
109 size_t pathlen = rb->strlen(path);
110 ssize_t ret = rb->write(fd, path, pathlen);
111 if(ret < 0 || (size_t)ret != pathlen) {
112 rb->close(fd);
113 return -1;
114 }
115
116 ret = rb->write(fd, "\n", 1);
117 rb->close(fd);
118 if(ret != 1)
119 return -1;
120
121 return 0;
122}
123
124/* Check if the firmware file is valid
125 * TODO: this should at least check model number or something */
126static bool check_firmware(const char* path)
127{
128 return rb->file_exists(path);
129}
130
131static int root_compare(const void* a, const void* b)
132{
133 const char* as = a;
134 const char* bs = b;
135 return rb->strcmp(as, bs);
136}
137
138/* Scan the filesystem for possible redirect targets. To prevent this from
139 * taking too long we only check the directories in the root of each volume
140 * look check for a rockbox firmware binary underneath /dir/.rockbox. If it
141 * exists then we report /<vol#>/dir as a root. */
142static int find_roots(void)
143{
144 const char* bootdir = *BOOTDIR == '/' ? &BOOTDIR[1] : BOOTDIR;
145 nroots = 0;
146
147 for(int vol = MULTIBOOT_MIN_VOLUME; vol < NUM_VOLUMES; ++vol) {
148 rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>", vol);
149
150 /* try to open the volume root; ignore failures since they'll
151 * occur if the volume is unmounted */
152 DIR* dir = rb->opendir(tmpbuf);
153 if(!dir)
154 continue;
155
156 struct dirent* ent;
157 while((ent = rb->readdir(dir))) {
158 int r = rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s/%s/%s",
159 vol, ent->d_name, bootdir, BOOTFILE);
160 if(r < 0 || (size_t)r >= sizeof(tmpbuf))
161 continue;
162
163 if(check_firmware(tmpbuf)) {
164 rb->snprintf(roots[nroots], MAX_PATH, "/<%d>/%s",
165 vol, ent->d_name);
166 nroots += 1;
167
168 /* quit if we hit the maximum */
169 if(nroots == MAX_ROOTS) {
170 vol = NUM_VOLUMES;
171 break;
172 }
173 }
174 }
175
176 rb->closedir(dir);
177 }
178
179 rb->qsort(roots, nroots, MAX_PATH, root_compare);
180 return nroots;
181}
182
183static const char* picker_get_name_cb(int selected, void* data,
184 char* buffer, size_t buffer_len)
185{
186 (void)data;
187 (void)buffer;
188 (void)buffer_len;
189 return roots[selected];
190}
191
192static int picker_action_cb(int action, struct gui_synclist* lists)
193{
194 (void)lists;
195 if(action == ACTION_STD_OK)
196 action = ACTION_STD_CANCEL;
197 return action;
198}
199
200static bool show_picker_menu(int* selection)
201{
202 struct simplelist_info info;
203 rb->simplelist_info_init(&info, "Select new root", nroots, NULL);
204 info.selection = *selection;
205 info.get_name = picker_get_name_cb;
206 info.action_callback = picker_action_cb;
207 if(rb->simplelist_show_list(&info))
208 return true;
209
210 if(0 <= info.selection && info.selection < nroots)
211 *selection = info.selection;
212
213 return false;
214}
215
216enum {
217 MB_SELECT_ROOT,
218 MB_CLEAR_REDIRECT,
219 MB_CURRENT_ROOT,
220 MB_SAVE_AND_EXIT,
221 MB_SAVE_AND_REBOOT,
222 MB_EXIT,
223 MB_NUM_ITEMS,
224};
225
226static const char* menu_get_name_cb(int selected, void* data,
227 char* buffer, size_t buffer_len)
228{
229 (void)data;
230
231 switch(selected) {
232 case MB_SELECT_ROOT:
233 return "Select root";
234
235 case MB_CLEAR_REDIRECT:
236 return "Clear redirect";
237
238 case MB_CURRENT_ROOT:
239 if(cur_root[0]) {
240 rb->snprintf(buffer, buffer_len, "Using root: %s", cur_root);
241 return buffer;
242 }
243
244 return "Using default root";
245
246 case MB_SAVE_AND_EXIT:
247 return "Save and Exit";
248
249 case MB_SAVE_AND_REBOOT:
250 return "Save and Reboot";
251
252 case MB_EXIT:
253 default:
254 return "Exit";
255 }
256}
257
258static int menu_action_cb(int action, struct gui_synclist* lists)
259{
260 int selected = rb->gui_synclist_get_sel_pos(lists);
261
262 if(action != ACTION_STD_OK)
263 return action;
264
265 switch(selected) {
266 case MB_SELECT_ROOT: {
267 if(find_roots() <= 0) {
268 rb->splashf(3*HZ, "No roots found");
269 break;
270 }
271
272 int root_sel = nroots-1;
273 for(; root_sel > 0; --root_sel)
274 if(!rb->strcmp(roots[root_sel], cur_root))
275 break;
276
277 if(show_picker_menu(&root_sel))
278 return SYS_USB_CONNECTED;
279
280 rb->strcpy(cur_root, roots[root_sel]);
281 action = ACTION_REDRAW;
282 } break;
283
284 case MB_CLEAR_REDIRECT:
285 if(cur_root[0]) {
286 memset(cur_root, 0, sizeof(cur_root));
287 rb->splashf(HZ, "Cleared redirect");
288 }
289
290 action = ACTION_REDRAW;
291 break;
292
293 case MB_SAVE_AND_REBOOT:
294 case MB_SAVE_AND_EXIT: {
295 int ret;
296 if(cur_root[0])
297 ret = write_redirect(cur_root);
298 else
299 ret = clear_redirect(-1);
300
301 if(ret < 0)
302 rb->splashf(3*HZ, "Couldn't save settings");
303 else
304 rb->splashf(HZ, "Settings saved");
305
306 action = ACTION_STD_CANCEL;
307 plugin_status = (ret < 0) ? PLUGIN_ERROR : PLUGIN_OK;
308
309 if(ret >= 0 && selected == MB_SAVE_AND_REBOOT)
310 rb->sys_reboot();
311 } break;
312
313 case MB_EXIT:
314 return ACTION_STD_CANCEL;
315
316 default:
317 action = ACTION_REDRAW;
318 break;
319 }
320
321 return action;
322}
323
324static bool show_menu(void)
325{
326 struct simplelist_info info;
327 rb->simplelist_info_init(&info, "Multiboot Settings", MB_NUM_ITEMS, NULL);
328 info.get_name = menu_get_name_cb;
329 info.action_callback = menu_action_cb;
330 return rb->simplelist_show_list(&info);
331}
332
333enum plugin_status plugin_start(const void* param)
334{
335 (void)param;
336
337 /* load the current root */
338 const char* myroot = read_redirect();
339 rb->strcpy(cur_root, myroot);
340
341 /* display the menu */
342 if(show_menu())
343 return PLUGIN_USB_CONNECTED;
344 else
345 return plugin_status;
346}