improve cleanup; release 2
[ginge.git] / prep / main.c
1 // vim:shiftwidth=2:expandtab
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <stdarg.h>
6 #include <unistd.h>
7 #include <elf.h>
8 #include <sys/stat.h>
9 #include <ctype.h>
10
11 #include "../common/host_fb.h"
12 #include "../common/cmn.h"
13
14 #define PFX "ging_prep: "
15 #define LOADER_STATIC   "ginge_sloader"
16 #define LOADER_DYNAMIC  "ginge_dyn.sh"
17 #define LAUNCHER        "gp2xmenu"
18
19 #ifdef PND
20 #define WRAP_APP        "op_runfbapp "
21 #else
22 #define WRAP_APP        ""
23 #endif
24
25 #include "font.c"
26
27 static void *fb_mem;
28 static int fb_stride;
29 static int fb_x, fb_y;
30 static int init_done;
31
32 static char *sskip(char *p)
33 {
34   while (p && *p && isspace(*p))
35     p++;
36   return p;
37 }
38
39 static char *cskip(char *p)
40 {
41   while (p && *p && !isspace(*p))
42     p++;
43   return p;
44 }
45
46 static void fb_text_exit(void)
47 {
48   if (!init_done)
49     return;
50
51   host_video_finish();
52   init_done = 0;
53 }
54
55 static void fb_text_init(void)
56 {
57   int ret = host_video_init(&fb_stride, 1);
58   if (ret == 0)
59     fb_mem = host_video_flip();
60   fb_x = 4;
61   fb_y = 4;
62   init_done = 1;
63   atexit(fb_text_exit);
64 }
65
66 static void fb_syms_out(void *fbi, int x, int y, int dotsz, int stride, const char *text, int count)
67 {
68   int v = -1, n = 0, *p;
69   int i, l;
70   char *fb;
71
72   fb = (char *)fbi + x * dotsz + y * stride;
73
74   for (i = 0; i < count; i++)
75   {
76     for (l = 0; l < 8; l++)
77     {
78       #define pix(fdmask,add) \
79         p = (fontdata8x8[((text[i])*8)+l] & fdmask) ? &v : &n; \
80         memcpy(fb + l*stride + add*dotsz, p, dotsz)
81       pix(0x80,  0);
82       pix(0x40,  1);
83       pix(0x20,  2);
84       pix(0x10,  3);
85       pix(0x08,  4);
86       pix(0x04,  5);
87       pix(0x02,  6);
88       pix(0x01,  7);
89       #undef pix
90     }
91     fb += dotsz * 8;
92   }
93 }
94
95 // FIXME: y overrun
96 static void fb_text_out(char *text)
97 {
98   int dotsz = 2, w = 320; // hardcoded for now
99   char *p, *pe;
100   int l;
101
102   if (!init_done)
103     fb_text_init();
104
105   if (fb_mem == NULL)
106     return;
107
108   p = text;
109   while (*p) {
110     for (; *p && isspace(*p); p++) {
111       if (*p == '\n' || fb_x + dotsz * 8 > w) {
112         fb_x = 4;
113         fb_y += 8;
114       }
115       if (*p >= 0x20)
116         fb_x += 8;
117     }
118
119     pe = cskip(p);
120     l = pe - p;
121     if (fb_x + 8 * l > w) {
122       fb_x = 4;
123       fb_y += 8;
124     }
125     fb_syms_out(fb_mem, fb_x, fb_y, dotsz, fb_stride, p, l);
126     fb_x += 8 * l;
127     p = pe;
128   }
129 }
130
131 static void fbprintf(int is_err, const char *format, ...)
132 {
133   va_list ap;
134   char buff[512];
135
136   va_start(ap, format);
137   vsnprintf(buff, sizeof(buff), format, ap);
138   va_end(ap);
139   fputs(buff, is_err ? stderr : stdout);
140
141   fb_text_out(buff);
142 }
143
144 #define msg(fmt, ...) fbprintf(0, fmt, ##__VA_ARGS__)
145 #define err(fmt, ...) fbprintf(1, fmt, ##__VA_ARGS__)
146
147 static int id_elf(const char *fname)
148 {
149   Elf32_Ehdr hdr;
150   Elf32_Phdr *phdr = NULL;
151   FILE *fi;
152   int i, ret = 0;
153
154   fi = fopen(fname, "rb");
155   if (fi == NULL) {
156     err("open %s: ", fname);
157     perror("");
158     return -1;
159   }
160
161   if (fread(&hdr, 1, sizeof(hdr), fi) != sizeof(hdr))
162     goto out;
163
164   if (memcmp(hdr.e_ident, ELFMAG "\x01\x01", SELFMAG + 2) != 0)
165     goto out;
166
167   if (hdr.e_phentsize != sizeof(Elf32_Phdr) || hdr.e_phnum == 0)
168     goto out;
169
170   phdr = malloc(hdr.e_phnum * hdr.e_phentsize);
171   if (phdr == NULL)
172     goto out;
173
174   if (fread(phdr, hdr.e_phentsize, hdr.e_phnum, fi) != hdr.e_phnum)
175     goto out;
176
177   ret = 1;
178
179   // do what 'file' does - check for PT_INTERP in program headers
180   for (i = 0; i < hdr.e_phnum; i++) {
181     if (phdr[i].p_type == PT_INTERP) {
182       ret = 2;
183       break;
184     }
185   }
186
187 out:
188   fclose(fi);
189   free(phdr);
190   return ret;
191 }
192
193 static void dump_args(FILE *fout, char * const argv[])
194 {
195   const char *p;
196   int i;
197
198   for (i = 0; argv[i] != NULL; i++) {
199     if (i != 0)
200       fputc(' ', fout);
201     fputc('"', fout);
202
203     for (p = argv[i]; *p != 0; p++) {
204       if (*p == '"')
205         fputc('\\', fout);
206       fputc(*p, fout);
207     }
208
209     fputc('"', fout);
210   }
211 }
212
213 static char *get_arg(char *d, size_t size, char *p)
214 {
215   char *pe;
216   int len;
217
218   p = sskip(p);
219   pe = cskip(p);
220   len = pe - p;
221
222   if (len > size - 1) {
223     err(PFX "get_arg: buff to small: %d/%d\n", len, size);
224     len = size - 1;
225   }
226   strncpy(d, p, len);
227   d[len] = 0;
228
229   return sskip(pe);
230 }
231
232 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
233
234 #define CB_ENTRY(x) { x, sizeof(x) - 1 }
235 const struct {
236   const char *name;
237   int len;
238 } conv_blacklist[] = {
239   CB_ENTRY("insmod"),
240   CB_ENTRY("modprobe"),
241   CB_ENTRY("umount"),
242   CB_ENTRY("./cpuctrl_tiny"),
243 };
244
245 static int cmd_in_blacklist(char *cmd)
246 {
247   int i;
248
249   cmd = sskip(cmd);
250   for (i = 0; i < ARRAY_SIZE(conv_blacklist); i++)
251     if (strncmp(cmd, conv_blacklist[i].name, conv_blacklist[i].len) == 0)
252       return 1;
253
254   return 0;
255 }
256
257 int main(int argc, char *argv[])
258 {
259   static const char out_script[] = "/tmp/ginge_conv.sh";
260   char root_path[512], cwd[512];
261   char **argv_app = NULL;
262   int have_cramfs = 0;
263   int rerun_gp2xmenu = 1;
264   int quit_if_no_app = 0;
265   FILE *fin, *fout;
266   int i, ret;
267
268   for (i = 1; i < argc && argv[i][0] == '-' && argv[i][1] == '-'; i++) {
269     if (strcmp(argv[i], "--cleanup") == 0) {
270       // as loader may crash eny time, restore screen for the menu
271       host_video_init(NULL, 1);
272       host_video_finish();
273       quit_if_no_app = 1;
274       continue;
275     }
276     if (strcmp(argv[i], "--nomenu") == 0) {
277       rerun_gp2xmenu = 0;
278       continue;
279     }
280     if (strcmp(argv[i], "--") == 0) {
281       i++;
282       break;
283     }
284
285     fprintf(stderr, PFX "ignoring unknown option \"%s\"\n", argv[i]);
286   }
287
288   if (argc <= i) {
289     if (quit_if_no_app)
290       return 0;
291     err("usage: %s [opts] <script|program> [args]\n", argv[0]);
292     err("  --cleanup  - restore framebuffer state\n");
293     err("  --nomenu   - don't run menu on exit\n");
294     return 1;
295   }
296   argv_app = &argv[i];
297
298   if (getcwd(cwd, sizeof(cwd)) == NULL) {
299     err(PFX "failed to get cwd\n");
300     return 1;
301   }
302
303   ret = make_local_path(root_path, sizeof(root_path), "");
304   if (ret != 0) {
305     err(PFX "failed to generate root path\n");
306     return 1;
307   }
308
309   fout = fopen(out_script, "w");
310   if (fout == NULL) {
311     perror("can't open output script");
312     return 1;
313   }
314
315   fprintf(fout, "#!/bin/sh\n");
316
317   ret = id_elf(argv_app[0]);
318   if (ret == 1 || ret == 2) {
319     if (cmd_in_blacklist(argv_app[0])) {
320       fprintf(stderr, "blacklisted: %s\n", argv_app[0]);
321       goto no_in_script;
322     }
323   }
324
325   switch (ret) {
326   case 0:
327     break;
328
329   case 1:
330     fprintf(fout, WRAP_APP "%s%s ", root_path, LOADER_STATIC);
331     dump_args(fout, argv_app);
332     fprintf(fout, "\n");
333     goto no_in_script;
334
335   case 2:
336     fprintf(fout, WRAP_APP "%s%s \"%s\" ", root_path, LOADER_DYNAMIC, root_path);
337     dump_args(fout, argv_app);
338     fprintf(fout, "\n");
339     goto no_in_script;
340
341   default:
342     return 1;
343   }
344
345   // assume script
346   fin = fopen(argv_app[0], "r");
347   if (fin == NULL)
348     return 1;
349
350   while (1) {
351     char buff[512], fname[512], *p, *p2;
352
353     p = fgets(buff, sizeof(buff), fin);
354     if (p == NULL)
355       break;
356     p = sskip(p);
357
358     if (p[0] == '#' && p[1] == '!')
359       continue;
360
361     if (*p == 0) {
362       fputs("\n", fout);
363       continue;
364     }
365
366     // things we are sure we want to pass
367     if (*p == '#' || strncmp(p, "export ", 7) == 0)
368       goto pass;
369
370     // hmh..
371     if (strncmp(p, "exec ", 5) == 0)
372       p = sskip(p + 5);
373
374     // blacklist some stuff
375     if      (strncmp(p, "/sbin/", 6) == 0)
376       p2 = p + 6;
377     else if (strncmp(p, "/bin/", 5) == 0)
378       p2 = p + 5;
379     else
380       p2 = p;
381     if (strncmp(p2, "mount ", 6) == 0) {
382       p2 = sskip(p2 + 6);
383       // cramfs stuff?
384       if (strstr(p2, "cramfs")) {
385         while (*p2 == '-') {
386           // skip option
387           p2 = sskip(cskip(p2));
388           p2 = sskip(cskip(p2));
389         }
390         if (*p2 == 0) {
391           err(PFX "cramfs: missing mount file in \"%s\"?\n", p);
392           continue;
393         }
394         p2 = get_arg(fname, sizeof(fname), p2);
395         if (*p2 == 0) {
396           err(PFX "cramfs: missing mount point in \"%s\"?\n", p);
397           continue;
398         }
399         get_arg(buff, sizeof(buff), p2);
400         fprintf(fout, "if [ `ls %s | wc -l` -eq 0 ]; then\n", buff);
401         fprintf(fout, "  rmdir \"%s\"\n", buff); // cramfsck doesn't want it
402         fprintf(fout, "  %stools/cramfsck -x \"%s\" \"%s\"\n", root_path, buff, fname);
403         fprintf(fout, "fi\n");
404         have_cramfs = 1;
405       }
406       continue;
407     }
408     if (cmd_in_blacklist(p2))
409       continue;
410
411     // cd?
412     if (strncmp(p, "cd ", 3) == 0) {
413       get_arg(fname, sizeof(fname), p + 3);
414       if (strncmp(fname, "/usr/gp2x", 9) == 0)
415         continue;
416       ret = chdir(fname);
417       if (ret != 0) {
418         err("%s: ", fname);
419         perror("");
420       }
421     }
422
423     // trying to run something from cwd?
424     if ((p[0] == '.' && p[1] == '/') || *p == '/') {
425       get_arg(fname, sizeof(fname), p);
426       p2 = strrchr(fname, '/');
427       if (p2 != NULL && strcmp(p2 + 1, "gp2xmenu") == 0)
428         continue;
429
430       ret = id_elf(fname);
431       switch (ret) {
432       case 1:
433         printf(PFX "prefixing as static: %s", p);
434         fprintf(fout, WRAP_APP "%s%s ", root_path, LOADER_STATIC);
435         break;
436
437       case 2:
438         printf(PFX "prefixing as dynamic: %s", p);
439         fprintf(fout, WRAP_APP "%s%s \"%s\" ", root_path, LOADER_DYNAMIC, root_path);
440         break;
441
442       default:
443         break;
444       }
445     }
446
447 pass:
448     fputs(p, fout);
449   }
450
451   fclose(fin);
452
453 no_in_script:
454 #ifdef WIZ
455   fprintf(fout, "sync\n");
456   // since we don't know if loader manages to do proper cleanup,
457   // need to wait for it's threads to die
458   fprintf(fout, "sleep 1\n");
459   fprintf(fout, "%sginge_prep --cleanup\n", root_path);
460 #endif
461   if (rerun_gp2xmenu) {
462     fprintf(fout, "cd %s\n", root_path);
463     fprintf(fout, "exec %s%s\n", root_path, LAUNCHER);
464   }
465
466   fclose(fout);
467
468   //msg("starting script..\n");
469   if (have_cramfs) {
470     msg("\nsome files need to be unpacked, this may tike a few minutes.\n");
471 #ifdef PND
472     msg("Please wait at least while SD LED is active.\n");
473 #endif
474   }
475   system("echo ---; cat /tmp/ginge_conv.sh; echo ---");
476   chmod(out_script, S_IRWXU|S_IRWXG|S_IRWXO);
477   chdir(cwd);
478   fb_text_exit();
479   execlp(out_script, out_script, NULL);
480   perror("run out_script");
481
482   return 1;
483 }
484