]> Dogcows Code - chaz/yoink/blob - build/dialog.c
build system enhancements
[chaz/yoink] / build / dialog.c
1
2 /*] Copyright (c) 2011, Charles McGarvey [*******************************
3 **] All rights reserved.
4 *
5 * vi:ts=4 sw=4 tw=75
6 *
7 * Distributable under the terms and conditions of the 2-clause BSD license;
8 * see the file COPYING for a complete text of the license.
9 *
10 **************************************************************************/
11
12 #define LUA_DIALOG_NAME "dialog"
13 #define LUA_DIALOG_VERSION "1.0"
14
15
16 #include <assert.h>
17 #include <stdarg.h>
18 #include <stdlib.h>
19 #include <string.h>
20
21 #include <sys/wait.h>
22 #include <unistd.h>
23
24 #define LUA_LIB
25 #include "lua.h"
26 #include "lauxlib.h"
27
28
29 #ifdef NDEBUG
30 #define printarray(A)
31 #else
32 #include <stdio.h>
33 static void printarray(const char* argv[])
34 {
35 printf("%s", argv[0]);
36 int i; for (i = 1; argv[i]; ++i) printf(" %s", argv[i]);
37 }
38 #endif
39
40
41 /**
42 * Fork and execute a command with arguments and optionally get a file
43 * descriptor connected to one of the child's own file descriptors. The
44 * process id of the child is returned, or -1 on error. If fd is not NULL,
45 * a pipe will be created and connected to *fd. If *fd is 0, then *fd will
46 * be set to a write file descriptor connected to the child's standard
47 * input. If *fd is not 0, then *fd will be set to a read file descriptor
48 * set connected to the specified file descriptor of the child. In either
49 * case, the caller has the responsibility to close fd when it is no longer
50 * needed.
51 */
52 static pid_t myexec(const char* command, char* const argv[], int* fd)
53 {
54 int parentFd = -1;
55 int p[2];
56 pid_t child;
57
58 if (fd)
59 {
60 if (pipe(p) != 0) return -1;
61 parentFd = (*fd == 0);
62 }
63 if (!(child = fork()))
64 {
65 if (fd)
66 {
67 close(p[parentFd]);
68 if (dup2(p[!parentFd], *fd) == -1) _exit(127);
69 }
70 execv(command, argv);
71 _exit(127);
72 }
73 if (child == -1)
74 {
75 if (parentFd != -1)
76 {
77 close(p[0]); close(p[1]);
78 }
79 return -1;
80 }
81 if (parentFd != -1)
82 {
83 close(p[!parentFd]);
84 *fd = p[parentFd];
85 }
86 return child;
87 }
88
89 /**
90 * Wait on a child process. Returns the exit status of the process if the
91 * child terminates normally, or -1 on error or abnormal child termination.
92 */
93 static int mywait(pid_t pid)
94 {
95 int status;
96 if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) return -1;
97 return WEXITSTATUS(status);
98 }
99
100
101 /**
102 * Read from a file descriptor until EOF and push contents to the top of
103 * the Lua stack. Closes the file descriptor afterward.
104 */
105 static void pushstream(lua_State* L, int fd)
106 {
107 luaL_Buffer B;
108 luaL_buffinit(L, &B);
109
110 char buffer[BUFSIZ];
111 ssize_t bytes;
112 while ((bytes = read(fd, buffer, sizeof(buffer))))
113 {
114 if (bytes == -1) break;
115 luaL_addlstring(&B, buffer, bytes);
116 }
117
118 close(fd);
119 luaL_pushresult(&B);
120 }
121
122 /**
123 * Write some arbitrary bytes to a file descriptor, appending a newline.
124 */
125 static void writelstring(int fd, const char* str, size_t len)
126 {
127 ssize_t bytes;
128 while ((bytes = write(fd, str, len)))
129 {
130 if (bytes == -1) break;
131 str += bytes;
132 len -= bytes;
133 }
134 bytes = write(fd, "\n", 1);
135 }
136
137 /**
138 * Write a NULL-terminate string to a file descriptor. Uses writelstring.
139 */
140 static void writestring(int fd, const char* str)
141 {
142 writelstring(fd, str, strlen(str));
143 }
144
145 /**
146 * Write a Lua value, that is able to be converted to a string, to a file
147 * descriptor.
148 */
149 static void tostream(lua_State* L, int index, int fd)
150 {
151 size_t len = 0;
152 const char* buffer = lua_tolstring(L, index, &len);
153 writelstring(fd, buffer, len);
154 }
155
156
157 /**
158 * Add one or more strings to the end of a NULL-terminated array of
159 * strings. The parameter list itself should also be terminated with a
160 * NULL. The terminating NULL of the array of strings will be moved back
161 * as needed. It is the responsibility of the caller to assert there is
162 * enough room in the array so that an overflow doesn't occur. Strings are
163 * not duplicated as they are added to the array.
164 */
165 static void addstrings(const char* argv[], ...)
166 {
167 va_list ap;
168 int i; for (i = 0; argv[i]; ++i);
169 va_start(ap, argv);
170 const char* arg = va_arg(ap, const char*);
171 while (arg)
172 {
173 argv[i++] = arg;
174 arg = va_arg(ap, const char*);
175 }
176 va_end(ap);
177 argv[i] = NULL;
178 }
179
180 /**
181 * Search an array of strings for a particular string, returning the index
182 * where the first equivalent string is found or -1 if it wasn't found.
183 */
184 static int searchstrings(const char* argv[], const char* str)
185 {
186 int i; for (i = 0; argv[i]; ++i) if (strcmp(argv[i], str) == 0) return i;
187 return -1;
188 }
189
190
191 /**
192 * Add the command and backtitle to the argument list from the Lua state.
193 */
194 static void addcommand(lua_State* L, const char* argv[])
195 {
196 lua_getglobal(L, LUA_DIALOG_NAME);
197 lua_getfield(L, -1, "command");
198 lua_getfield(L, -2, "title");
199 addstrings(argv, lua_tostring(L, -2), NULL);
200 if (lua_isstring(L, -1))
201 {
202 addstrings(argv, "--backtitle", lua_tostring(L, -1), NULL);
203 }
204 lua_pop(L, 3);
205 }
206
207 /**
208 * Add the height and width to the argument list from the Lua state.
209 */
210 static int addsize(lua_State* L, const char* argv[], int extra, int n)
211 {
212 int m = n;
213 lua_getglobal(L, LUA_DIALOG_NAME);
214 if (lua_isnumber(L, m)) lua_pushvalue(L, m++);
215 else lua_getfield(L, -1, "height");
216 if (lua_isnumber(L, m)) lua_pushvalue(L, m++);
217 else lua_getfield(L, -2, "width");
218 addstrings(argv, lua_tostring(L, -2), lua_tostring(L, -1), NULL);
219 if (extra) addstrings(argv, lua_tostring(L, -2), NULL);
220 lua_pop(L, 3);
221 return m - n;
222 }
223
224 /**
225 * Add the dialog title to the argument list from the Lua state. The title
226 * should be at index 1 on the stack, and an error is thrown if it is not
227 * found.
228 */
229 static void addtitle(lua_State* L, const char* argv[])
230 {
231 addstrings(argv, "--title", luaL_checkstring(L, 1), NULL);
232 }
233
234 /**
235 * Add the caption to the argument list from the Lua state. The caption
236 * should be at index 2 on the stack, and an error is thrown if it is not
237 * found.
238 */
239 static void addcaption(lua_State* L, const char* argv[])
240 {
241 addstrings(argv, luaL_optstring(L, 2, ""), NULL);
242 }
243
244 /**
245 * Get the string found at a certain index on the Lua stack, returned in
246 * the text argument. Returns whether the text was set.
247 */
248 static int getstring(lua_State* L, const char* argv[], int n, const char** text)
249 {
250 if (lua_type(L, n) != LUA_TSTRING) return 0;
251 *text = lua_tostring(L, n);
252 return 1;
253 }
254
255 /**
256 * Add extra arguments to the argument list from the Lua state. The extra
257 * arguments exist in a table where the key is a flag and its value (if it
258 * is a string) is added as a flag option.
259 */
260 static int addextra(lua_State* L, const char* argv[], int n)
261 {
262 if (!lua_istable(L, n)) return 0;
263 lua_pushnil(L);
264 while (lua_next(L, n))
265 {
266 addstrings(argv, lua_tostring(L, -2), NULL);
267 if (lua_isstring(L, -1)) addstrings(argv, lua_tostring(L, -1), NULL);
268 lua_pop(L, 1);
269 }
270 return 1;
271 }
272
273 /**
274 * Add the default item to the argument list from the Lua stack for a menu
275 * command. Returns whether or not the arguments were found and added. If
276 * the value on the stack is nil, no arguments are added to the list, but
277 * the return value is still 1.
278 */
279 static int addselected(lua_State* L, const char* argv[], int n)
280 {
281 if (lua_isnil(L, n)) return 1;
282 if (!lua_isstring(L, n)) return 0;
283 addstrings(argv, "--default-item", lua_tostring(L, n), NULL);
284 return 1;
285 }
286
287 /**
288 * Add menu items to the argument list from the Lua stack. They should
289 * exist in a table at index 3 of a menu command. Each item should be a
290 * table itself with two or three strings, depending on whether --item-help
291 * exists in the argument list.
292 */
293 static void addmenuitems(lua_State* L, const char* argv[])
294 {
295 int fields = 2;
296 if (searchstrings(argv, "--item-help") != -1) fields = 3;
297
298 if (!lua_istable(L, 3)) luaL_argerror(L, 3, "menu items");
299 int i; for (i = 1;; ++i)
300 {
301 lua_pushinteger(L, i);
302 lua_gettable(L, 3);
303 if (lua_isnil(L, -1))
304 {
305 lua_pop(L, 1);
306 break;
307 }
308 else if (lua_istable(L, -1))
309 {
310 int subtable = lua_gettop(L);
311 lua_pushnil(L);
312 int j; for (j = 0; j < fields; ++j)
313 {
314 if (!lua_next(L, subtable)) luaL_argerror(L, 3, "not enough fields");
315 addstrings(argv, lua_tostring(L, -1), NULL);
316 lua_pop(L, 1);
317 }
318 lua_pop(L, 1);
319 }
320 else
321 {
322 if (fields == 2) addstrings(argv, "", "", NULL);
323 else addstrings(argv, "", "", "", NULL);
324 }
325 lua_pop(L, 1);
326 }
327 }
328
329 /**
330 * Fill out the argument list from the Lua state according to the standard
331 * 3 or 4 argument dialog command format.
332 */
333 static void addargs(lua_State* L, const char* command, int nargs, const char* argv[])
334 {
335 assert((nargs == 3 || nargs == 4) && "nargs should be 3 or 4");
336
337 addcommand(L, argv);
338 addtitle(L, argv);
339 const char* text = NULL;
340 int n = 3;
341 if (nargs == 4) n += getstring(L, argv, n, &text);
342 n += addextra(L, argv, n);
343 addstrings(argv, command, NULL);
344 addcaption(L, argv);
345 addsize(L, argv, 0, n);
346 if (text) addstrings(argv, text, NULL);
347 }
348
349
350 /**
351 * Close a gauge dialog if one is running. If a gauge is in progress, the
352 * status code will be pushed onto the stack and 1 is returned; otherwise,
353 * 0 is returned.
354 */
355 static void closegauge(lua_State* L)
356 {
357 lua_getfield(L, LUA_REGISTRYINDEX, "dialog_gauge_pid");
358 lua_getfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd");
359 if (!lua_isnumber(L, -2) || !lua_isnumber(L, -1))
360 {
361 lua_pop(L, 2);
362 return;
363 }
364 pid_t pid = lua_tointeger(L, -2);
365 int fd = lua_tointeger(L, -1);
366 lua_pop(L, 2);
367
368 lua_pushnil(L);
369 lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_pid");
370 lua_pushnil(L);
371 lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd");
372
373 close(fd);
374 int code = mywait(pid);
375 if (code == -1) luaL_error(L, "dialog killed abnormally");
376 }
377
378 /**
379 * Updates the progress of a gauge dialog. Takes a number argument in the
380 * range of 0-100 or 0.0-1.0 as the progress. Optionally takes a string as
381 * the second argument which causes the caption to change. The last call
382 * to this function should be with no arguments to end the gauge dialog.
383 * Returns nothing except the last call which returns the status code of
384 * the dialog which should always be OK.
385 */
386 static int updategauge(lua_State* L)
387 {
388 if (!lua_isnumber(L, 1))
389 {
390 closegauge(L);
391 return 0;
392 }
393
394 lua_getfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd");
395 if (!lua_isnumber(L, -1)) return 0;
396 int fd = lua_tointeger(L, -1);
397
398 lua_Number percent = lua_tonumber(L, 1);
399 if (0.0 <= percent && percent <= 1.0) percent *= 100.0;
400 lua_pushinteger(L, (lua_Integer)percent);
401 lua_replace(L, 1);
402 if (lua_isstring(L, 2))
403 {
404 writestring(fd, "XXX");
405 tostream(L, 1, fd);
406 tostream(L, 2, fd);
407 writestring(fd, "XXX");
408 }
409 else
410 {
411 tostream(L, 1, fd);
412 }
413 return 0;
414 }
415
416 /**
417 * Display a gauge dialog. Required arguments are dialog title and
418 * caption. Optional arguments include a table of extra flags. Returns a
419 * new function which can be used to update the progress.
420 */
421 static int dialog_gauge(lua_State* L)
422 {
423 const char* argv[40] = {NULL};
424 closegauge(L);
425 addargs(L, "--gauge", 3, argv);
426
427 int fd = 0;
428 pid_t pid = myexec(argv[0], (char* const*)argv, &fd);
429 if (pid == -1) luaL_error(L, "dialog failed to execute");
430 lua_pushinteger(L, pid);
431 lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_pid");
432 lua_pushinteger(L, fd);
433 lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd");
434 lua_pushcfunction(L, updategauge);
435 return 1;
436 }
437 /**
438 * Display a menu dialog. Required arguments are dialog title, caption,
439 * and table of menu items. Optional arguments are selected item and table
440 * of extra flags.
441 */
442 static int dialog_menu(lua_State* L)
443 {
444 const char* argv[1024] = {NULL};
445 closegauge(L);
446
447 addcommand(L, argv);
448 addtitle(L, argv);
449 int n = 4;
450 n += addselected(L, argv, n);
451 n += addextra(L, argv, n);
452 addstrings(argv, "--menu", NULL);
453 addcaption(L, argv);
454 addsize(L, argv, 1, n);
455 addmenuitems(L, argv);
456
457 printarray(argv);
458
459 int fd = 2;
460 pid_t pid = myexec(argv[0], (char* const*)argv, &fd);
461 if (pid == -1) luaL_error(L, "dialog failed to execute");
462 int code = mywait(pid);
463 if (code == -1) luaL_error(L, "dialog killed abnormally");
464 lua_pushinteger(L, code);
465 pushstream(L, fd);
466 return 2;
467 }
468
469 /**
470 * Display a message dialog. Required arguments are dialog title and
471 * caption. Optional arguments include a table of extra flags.
472 */
473 static int dialog_msgbox(lua_State* L)
474 {
475 const char* argv[40] = {NULL};
476 closegauge(L);
477 addargs(L, "--msgbox", 3, argv);
478
479 pid_t pid = myexec(argv[0], (char* const*)argv, NULL);
480 if (pid == -1) luaL_error(L, "dialog failed to execute");
481 int code = mywait(pid);
482 if (code == -1) luaL_error(L, "dialog killed abnormally");
483 lua_pushinteger(L, code);
484 return 1;
485 }
486
487 /**
488 * Display an input dialog. Required arguments are dialog title and
489 * caption. Optional arguments are the default text and a table of extra
490 * flags.
491 */
492 static int dialog_inputbox(lua_State* L)
493 {
494 const char* argv[40] = {NULL};
495 closegauge(L);
496 addargs(L, "--inputbox", 4, argv);
497
498 int fd = 2;
499 pid_t pid = myexec(argv[0], (char* const*)argv, &fd);
500 if (pid == -1) luaL_error(L, "dialog failed to execute");
501 int code = mywait(pid);
502 if (code == -1) luaL_error(L, "dialog killed abnormally");
503 lua_pushinteger(L, code);
504 pushstream(L, fd);
505 return 2;
506 }
507
508 /**
509 * Display a yes/no dialog. Required arguments are dialog title and
510 * caption. Optional arguments include a table of extra flags.
511 */
512 static int dialog_yesno(lua_State* L)
513 {
514 const char* argv[40] = {NULL};
515 closegauge(L);
516 addargs(L, "--yesno", 3, argv);
517
518 pid_t pid = myexec(argv[0], (char* const*)argv, NULL);
519 if (pid == -1) luaL_error(L, "dialog failed to execute");
520 int code = mywait(pid);
521 if (code == -1) luaL_error(L, "dialog killed abnormally");
522 lua_pushinteger(L, code);
523 return 1;
524 }
525
526
527 /**
528 * Register the module functions and find a dialog command in the path.
529 */
530 LUALIB_API int luaopen_dialog(lua_State* L)
531 {
532 const struct luaL_Reg dialog_funcs[] = {
533 {"gauge", dialog_gauge},
534 {"inputbox", dialog_inputbox},
535 {"menu", dialog_menu},
536 {"msgbox", dialog_msgbox},
537 {"yesno", dialog_yesno},
538 {NULL, NULL}
539 };
540 luaL_register(L, LUA_DIALOG_NAME, dialog_funcs);
541
542 const char* names[] = {getenv("DIALOG"), "dialog", "cdialog"};
543 int i; for (i = 0; i < 3; ++i)
544 {
545 if (names[i])
546 {
547 char* path = strdup(getenv("PATH"));
548 char* token; char** paths = &path;
549 while ((token = strsep(paths, ":")))
550 {
551 luaL_Buffer B;
552 luaL_buffinit(L, &B);
553 luaL_addstring(&B, token);
554 luaL_addstring(&B, "/");
555 luaL_addstring(&B, names[i]);
556 luaL_pushresult(&B);
557 if (access(lua_tostring(L, -1), X_OK) == 0)
558 {
559 lua_setfield(L, -2, "command");
560 goto break2;
561 }
562 lua_pop(L, 1);
563 }
564 free(path);
565 }
566 }
567 luaL_error(L, "cannot find dialog executable in the path; set DIALOG");
568
569 break2:
570
571 lua_pushinteger(L, 0);
572 lua_setfield(L, -2, "height");
573 lua_pushinteger(L, 0);
574 lua_setfield(L, -2, "width");
575
576 return 1;
577 }
578
This page took 0.062956 seconds and 4 git commands to generate.