Download
jcs
/subtext
/settings.c
(View History)
jcs *: Minor cleanups, remove dead variables, fix some error paths | Latest amendment: 580 on 2024-01-24 |
1 | /* |
2 | * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdio.h> |
18 | #include <string.h> |
19 | |
20 | #include "ansi.h" |
21 | #include "subtext.h" |
22 | #include "db.h" |
23 | #include "focusable.h" |
24 | #include "main_menu.h" |
25 | #include "settings.h" |
26 | #include "tcp.h" /* for long2ip/ip2long */ |
27 | #include "util.h" |
28 | |
29 | short |
30 | struct_editor(struct session *s, const struct struct_field *fields, |
31 | const size_t nfields, const struct session_menu_option *extra_opts, |
32 | size_t nextra_opts, void *data, size_t dsize, void **result, |
33 | char *title, char *prompt) |
34 | { |
35 | static const struct session_menu_option opts[] = { |
36 | { '#', "", "Edit setting [#]" }, |
37 | { 's', "Ss", "Save and return to main menu" }, |
38 | { 'q', "QqXx", "Discard changes and return to main menu" }, |
39 | { '?', "?", "List menu options" }, |
40 | }; |
41 | struct session_menu_option *dopts = NULL; |
42 | const struct struct_field *sf; |
43 | long lval; |
44 | char initial[20]; |
45 | char *input = NULL, *new_data; |
46 | size_t nopts; |
47 | short n, i, sval, on, ret; |
48 | bool any_changes = false, done, show_list; |
49 | unsigned short c; |
50 | |
51 | new_data = xmalloc(dsize); |
52 | if (new_data == NULL) |
53 | goto done; |
54 | memcpy(new_data, data, dsize); |
55 | |
56 | nopts = nitems(opts) + nextra_opts; |
57 | dopts = xcalloc(sizeof(struct session_menu_option), nopts); |
58 | if (dopts == NULL) |
59 | goto done; |
60 | if (extra_opts != NULL) |
61 | memcpy(dopts, extra_opts, |
62 | sizeof(struct session_menu_option) * nextra_opts); |
63 | memcpy(dopts + nextra_opts, opts, sizeof(opts)); |
64 | |
65 | session_printf(s, "{{B}}Editor: %s{{/B}}\r\n", title); |
66 | session_flush(s); |
67 | |
68 | any_changes = false; |
69 | show_list = true; |
70 | done = false; |
71 | ret = -1; |
72 | |
73 | while (!done && !s->ending) { |
74 | if (show_list) { |
75 | for (n = 0; n < nfields; n++) { |
76 | if ((n + 1) % (s->terminal_lines - 2) == 0) { |
77 | session_printf(s, "-- More --"); |
78 | session_flush(s); |
79 | sval = session_input_char(s); |
80 | if (s->vt100) |
81 | session_printf(s, "\r%s", |
82 | ansi(s, ANSI_ERASE_LINE, ANSI_END)); |
83 | else |
84 | session_output(s, "\r", 1); |
85 | |
86 | if (sval == 'q' || sval == 'Q') { |
87 | /* abort pagination, like less */ |
88 | show_list = false; |
89 | break; |
90 | } |
91 | } |
92 | |
93 | sf = &fields[n]; |
94 | |
95 | session_printf(s, "{{B}}%d{{/B}}: %s [", n + 1, sf->name); |
96 | |
97 | switch (sf->type) { |
98 | case CONFIG_TYPE_STRING: |
99 | session_printf(s, "%s", new_data + sf->off); |
100 | break; |
101 | case CONFIG_TYPE_PASSWORD: |
102 | i = strlen(new_data + sf->off); |
103 | while (i) { |
104 | session_output(s, "*", 1); |
105 | i--; |
106 | } |
107 | break; |
108 | case CONFIG_TYPE_SHORT: |
109 | sval = CHARS_TO_SHORT(new_data[sf->off], |
110 | new_data[sf->off + 1]); |
111 | session_printf(s, "%d", sval); |
112 | break; |
113 | case CONFIG_TYPE_LONG: |
114 | lval = CHARS_TO_LONG(new_data[sf->off], |
115 | new_data[sf->off + 1], new_data[sf->off + 2], |
116 | new_data[sf->off + 3]); |
117 | session_printf(s, "%ld", lval); |
118 | break; |
119 | case CONFIG_TYPE_BOOLEAN: |
120 | session_printf(s, "%s", |
121 | new_data[sf->off] == 0 ? "false" : "true"); |
122 | break; |
123 | case CONFIG_TYPE_IP: { |
124 | char ip_str[16]; |
125 | lval = CHARS_TO_LONG(new_data[sf->off], |
126 | new_data[sf->off + 1], new_data[sf->off + 2], |
127 | new_data[sf->off + 3]); |
128 | if (lval == 0) |
129 | session_printf(s, "0.0.0.0"); |
130 | else { |
131 | long2ip(lval, ip_str); |
132 | session_printf(s, "%s", ip_str); |
133 | } |
134 | break; |
135 | } |
136 | } |
137 | |
138 | session_printf(s, "]\r\n"); |
139 | } |
140 | } |
141 | |
142 | c = session_menu(s, NULL, prompt, NULL, dopts, nopts, show_list, |
143 | "Option #", &on); |
144 | show_list = false; |
145 | |
146 | switch (c) { |
147 | case 's': |
148 | goto done; |
149 | case 'q': |
150 | any_changes = false; |
151 | goto done; |
152 | case '?': |
153 | show_list = true; |
154 | break; |
155 | case '#': |
156 | if (on < 1 || on > nfields) { |
157 | session_printf(s, "Invalid option\r\n"); |
158 | session_flush(s); |
159 | break; |
160 | } |
161 | |
162 | sf = &fields[on - 1]; |
163 | |
164 | get_input: |
165 | session_printf(s, "{{B}}%s:{{/B}} ", sf->name); |
166 | session_flush(s); |
167 | |
168 | switch (sf->type) { |
169 | case CONFIG_TYPE_STRING: |
170 | case CONFIG_TYPE_PASSWORD: |
171 | input = session_field_input(s, sf->max, 50, |
172 | new_data + sf->off, false, |
173 | (sf->type == CONFIG_TYPE_PASSWORD ? '*' : 0)); |
174 | session_output(s, "\r\n", 2); |
175 | session_flush(s); |
176 | if (input == NULL || s->ending) |
177 | break; |
178 | |
179 | if (strlen(input) >= sf->max) { |
180 | session_printf(s, |
181 | "%s is too long (%d max, ^C to cancel)", sf->name, |
182 | sf->max - 1); |
183 | xfree(&input); |
184 | goto get_input; |
185 | } |
186 | strlcpy(new_data + sf->off, input, sf->max); |
187 | any_changes = true; |
188 | break; |
189 | case CONFIG_TYPE_SHORT: |
190 | case CONFIG_TYPE_LONG: |
191 | if (sf->type == CONFIG_TYPE_LONG) { |
192 | lval = CHARS_TO_LONG(new_data[sf->off], |
193 | new_data[sf->off + 1], new_data[sf->off + 2], |
194 | new_data[sf->off + 3]); |
195 | snprintf(initial, sizeof(initial), "%ld", lval); |
196 | } else { |
197 | sval = CHARS_TO_SHORT(new_data[sf->off], |
198 | new_data[sf->off + 1]); |
199 | snprintf(initial, sizeof(initial), "%d", sval); |
200 | } |
201 | input = session_field_input(s, 10, 10, initial, false, 0); |
202 | session_output(s, "\r\n", 2); |
203 | session_flush(s); |
204 | if (input == NULL || s->ending) |
205 | break; |
206 | |
207 | lval = atol(input); |
208 | if (lval < sf->min) { |
209 | session_printf(s, |
210 | "%s must be %ld or higher (^C to cancel)\r\n", |
211 | sf->name, sf->min); |
212 | xfree(&input); |
213 | goto get_input; |
214 | } |
215 | if (lval > sf->max) { |
216 | session_printf(s, |
217 | "%s must be %ld or less (^C to cancel)\r\n", |
218 | sf->name, sf->max); |
219 | xfree(&input); |
220 | goto get_input; |
221 | } |
222 | if (sf->type == CONFIG_TYPE_LONG) { |
223 | new_data[sf->off] = (lval >> 24) & 0xff; |
224 | new_data[sf->off + 1] = (lval >> 16) & 0xff; |
225 | new_data[sf->off + 2] = (lval >> 8) & 0xff; |
226 | new_data[sf->off + 3] = lval & 0xff; |
227 | } else { |
228 | sval = lval; |
229 | new_data[sf->off] = (sval >> 8) & 0xff; |
230 | new_data[sf->off + 1] = sval & 0xff; |
231 | } |
232 | any_changes = true; |
233 | break; |
234 | case CONFIG_TYPE_BOOLEAN: |
235 | if (new_data[sf->off]) |
236 | session_printf(s, "[Y/n] "); |
237 | else |
238 | session_printf(s, "[y/N] "); |
239 | session_flush(s); |
240 | |
241 | for (;;) { |
242 | c = session_input_char(s); |
243 | if (s->ending) |
244 | break; |
245 | if (c == '\r' && new_data[sf->off]) |
246 | c = 'y'; |
247 | else if (c == '\r' && new_data[sf->off] == 0) |
248 | c = 'n'; |
249 | if (c == 'y' || c == 'Y') { |
250 | new_data[sf->off] = 1; |
251 | any_changes = true; |
252 | session_printf(s, "%c\r\n", c); |
253 | break; |
254 | } |
255 | if (c == 'n' || c == 'N') { |
256 | new_data[sf->off] = 0; |
257 | any_changes = true; |
258 | session_printf(s, "%c\r\n", c); |
259 | break; |
260 | } |
261 | } |
262 | break; |
263 | case CONFIG_TYPE_IP: { |
264 | char ip_str[16]; |
265 | lval = CHARS_TO_LONG(new_data[sf->off], |
266 | new_data[sf->off + 1], new_data[sf->off + 2], |
267 | new_data[sf->off + 3]); |
268 | if (lval == 0) |
269 | snprintf(initial, sizeof(initial), "0.0.0.0"); |
270 | else { |
271 | long2ip(lval, ip_str); |
272 | snprintf(initial, sizeof(initial), "%s", ip_str); |
273 | } |
274 | |
275 | input = session_field_input(s, 16, 16, initial, false, 0); |
276 | session_output(s, "\r\n", 2); |
277 | session_flush(s); |
278 | if (input == NULL || s->ending) |
279 | break; |
280 | |
281 | if (input[0] == '\0') { |
282 | lval = 0; |
283 | xfree(&input); |
284 | } else { |
285 | lval = ip2long(input); |
286 | xfree(&input); |
287 | if (lval == 0) { |
288 | session_printf(s, |
289 | "Invalid IP address (^C to cancel)\r\n"); |
290 | goto get_input; |
291 | } |
292 | } |
293 | new_data[sf->off] = (lval >> 24) & 0xff; |
294 | new_data[sf->off + 1] = (lval >> 16) & 0xff; |
295 | new_data[sf->off + 2] = (lval >> 8) & 0xff; |
296 | new_data[sf->off + 3] = lval & 0xff; |
297 | any_changes = true; |
298 | break; |
299 | } |
300 | } |
301 | break; |
302 | default: |
303 | ret = c; |
304 | done = true; |
305 | break; |
306 | } |
307 | |
308 | if (s->ending) { |
309 | any_changes = false; |
310 | ret = -1; |
311 | goto done; |
312 | } |
313 | } |
314 | |
315 | done: |
316 | xfree(&dopts); |
317 | |
318 | if (any_changes) { |
319 | *result = new_data; |
320 | return 0; |
321 | } else { |
322 | if (new_data) |
323 | xfree(&new_data); |
324 | *result = NULL; |
325 | return ret; |
326 | } |
327 | } |