summaryrefslogtreecommitdiff
path: root/apps/plugins/multiboot_select.c
diff options
context:
space:
mode:
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}