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