posixc.library: refactor __exec_do to make clear separate between regular code path...
[AROS.git] / compiler / posixc / __exec.c
blob84fd9bbc0817fc957aa79ed2d7a42ddf2d3daedc
1 /*
2 Copyright © 2008-2013, The AROS Development Team. All rights reserved.
3 $Id$
5 Support functions for POSIX exec*() functions.
6 */
7 #include <exec/types.h>
8 #include <dos/dos.h>
9 #include <proto/dos.h>
11 #include <aros/libcall.h>
13 #include <errno.h>
14 #include <ctype.h>
15 #include <assert.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <stdarg.h>
19 #include <unistd.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
23 #include "__posixc_intbase.h"
24 #include "__exec.h"
25 #include "__upath.h"
26 #include "__fdesc.h"
27 #include "__vfork.h"
29 #define DEBUG 0
30 #include <aros/debug.h>
32 static BOOL containswhite(const char *str);
33 static char *escape(const char *str);
34 static char *appendarg(char *argptr, int *argptrsize, const char *arg, APTR pool);
35 static char *appendargs(char *argptr, int *argptrsize, char *const args[], APTR pool);
36 static void __exec_cleanup(struct PosixCIntBase *PosixCBase);
38 static void __exec_do_regular(struct PosixCIntBase *PosixCBase);
39 static void __exec_do_pretend_child(struct PosixCIntBase *PosixCBase);
41 /* Public functions */
42 /********************/
44 void __exec_do(APTR id)
46 struct PosixCIntBase *PosixCBase =
47 (struct PosixCIntBase *)__aros_getbase_PosixCBase();
49 D(bug("[__exec_do] Entering, id(%x)\n", id));
51 /* id is unused */
52 (void)id;
54 if (PosixCBase->flags & PRETEND_CHILD)
56 /* forking parent executing child code prior to child taking over */
57 __exec_do_pretend_child(PosixCBase);
59 else
61 /* exec* without fork or forked child executing exec */
62 __exec_do_regular(PosixCBase);
66 APTR __exec_prepare(const char *filename, int searchpath, char *const argv[], char *const envp[])
68 struct PosixCIntBase *PosixCBase =
69 (struct PosixCIntBase *)__aros_getbase_PosixCBase();
70 char *filename2 = NULL;
71 const char *filename2_dos = NULL;
72 int argssize = 512;
73 struct Process *me;
74 char ***environptr = __posixc_get_environptr();
75 char **environ = (environptr != NULL) ? *environptr : NULL;
77 D(bug("Entering __exec_prepare(\"%s\", %d, %x, %x)\n",
78 filename, searchpath, argv, envp
79 ));
80 /* Sanity check */
81 if (filename == NULL || filename[0] == '\0' || argv == NULL)
83 errno = EINVAL;
84 goto error;
87 /* Use own memory to allocate so that no stdc.library functions need to be called
88 exec_pool can also be allocated in __exec_valist2array
90 if (!PosixCBase->exec_pool)
91 PosixCBase->exec_pool = CreatePool(MEMF_PUBLIC, 1024, 512);
92 if (!PosixCBase->exec_pool)
94 errno = ENOMEM;
95 goto error;
98 /* Remember current stdcbase, child may overwrite it and mess up exiting */
99 PosixCBase->exec_oldstdcbase = PosixCBase->PosixCBase.StdCBase;
101 /* Search path if asked and no directory separator is present in the file */
102 if (searchpath && index(filename, '/') == NULL && index(filename, ':') == NULL)
104 int i, len, size;
105 char *path = NULL, *path_ptr, *path_item;
106 BPTR lock;
108 if (environ)
110 for (i=0; environ[i]; i++)
112 if (strncmp(environ[i], "PATH=", 5) == 0)
114 path = &environ[i][5];
115 break;
120 if (!path)
121 path = getenv("PATH");
123 if (!path)
124 path = ":/bin:/usr/bin";
126 D(bug("__exec_prepare: PATH('%s')\n", path));
128 path_ptr = AllocPooled(PosixCBase->exec_pool, strlen(path) + 1);
129 strcpy(path_ptr, path);
130 path = path_ptr;
132 D(bug("__exec_prepare: PATH('%s')\n", path));
134 size = 128;
135 filename2 = AllocPooled(PosixCBase->exec_pool, size);
136 if (!filename2)
138 errno = ENOMEM;
139 goto error;
142 for(path_ptr = path, lock = (BPTR)NULL, path_item = strsep(&path_ptr, ",:");
143 lock == (BPTR)NULL && path_item != NULL;
144 path_item = strsep(&path_ptr, ",:")
147 if(path_item[0] == '\0')
148 path_item = ".";
150 len = strlen(path_item) + strlen(filename) + 2;
152 if (len > size)
154 FreePooled(PosixCBase->exec_pool, filename2, size);
155 size = len;
156 filename2 = AllocPooled(PosixCBase->exec_pool, size);
157 if (!filename2)
159 errno = ENOMEM;
160 goto error;
164 strcpy(filename2, path_item);
165 strcat(filename2, "/");
166 strcat(filename2, filename);
167 filename2_dos = __path_u2a(filename2);
168 lock = Lock(filename2_dos, SHARED_LOCK);
169 D(bug("__exec_prepare: Lock(\"%s\") == %x\n", filename2, (APTR)lock));
172 if(lock != (BPTR)NULL)
173 UnLock(lock);
174 else
176 errno = ENOENT;
177 goto error;
180 else
182 filename2 = (char *)filename;
183 filename2_dos = __path_u2a(filename2);
186 if (PosixCBase->flags & PRETEND_CHILD)
188 struct vfork_data *udata = PosixCBase->vfork_data;
190 udata->exec_filename = filename2;
191 udata->exec_argv = argv;
192 udata->exec_envp = envp;
194 /* Set this so the child knows that __exec_prepare was called */
195 udata->child_executed = 1;
197 D(bug("__exec_prepare: Calling child\n"));
198 /* Now call child process, so it will call __exec_prepare */
199 Signal(udata->child, 1 << udata->child_signal);
201 D(bug("__exec_prepare: Waiting for child to finish __exec_prepare\n"));
202 /* __exec_prepare should be finished now on child */
203 Wait(1 << udata->parent_signal);
205 if (!udata->exec_id)
207 D(bug("__exec_prepare: Continue child immediately on error\n"));
208 Signal(udata->child, 1 << udata->child_signal);
210 return NULL;
213 D(bug("__exec_prepare: Exiting from forked __exec_prepare id=%x, errno=%d\n",
214 udata->exec_id, udata->child_errno
217 return (APTR)PosixCBase;
220 D(bug("__exec_prepare: Not running as PRETEND_CHILD\n"));
221 PosixCBase->exec_args = AllocPooled(PosixCBase->exec_pool, argssize);
222 PosixCBase->exec_args[0] = '\0';
224 /* Let's check if it's a script */
225 BPTR fh = Open((CONST_STRPTR)filename2_dos, MODE_OLDFILE);
226 if(fh)
228 if(FGetC(fh) == '#' && FGetC(fh) == '!')
230 char firstline[128], *linebuf, *inter, *interargs = NULL;
232 /* It is a script, let's read the first line */
233 if(FGets(fh, (STRPTR)firstline, sizeof(firstline) - 1))
235 /* delete end of line if present */
236 if(firstline[0] && firstline[strlen(firstline)-1] == '\n')
237 firstline[strlen(firstline)-1] = '\0';
238 linebuf = firstline;
239 while(isblank(*linebuf)) linebuf++;
240 if(*linebuf != '\0')
242 /* Interpreter name is here */
243 inter = linebuf;
244 while(*linebuf != '\0' && !isblank(*linebuf)) linebuf++;
245 if(*linebuf != '\0')
247 *linebuf++ = '\0';
248 while(isblank(*linebuf)) linebuf++;
249 if(*linebuf != '\0')
250 /* Interpreter arguments are here */
251 interargs = linebuf;
254 /* Add interpeter args and the script name to command line args */
255 char *args[] = {NULL, NULL, NULL};
256 if (interargs)
258 args[0] = interargs;
259 args[1] = filename2;
261 else
263 args[0] = filename2;
265 PosixCBase->exec_args = appendargs(
266 PosixCBase->exec_args, &argssize, args, PosixCBase->exec_pool
268 if (!PosixCBase->exec_args)
270 errno = ENOMEM;
271 goto error;
274 /* Set file to execute as the script interpreter */
275 filename2 = AllocPooled(PosixCBase->exec_pool, strlen(inter) + 1);
276 strcpy(filename2, inter);
277 filename2_dos = __path_u2a(filename2);
281 Close(fh);
283 else
285 /* Simply assume it doesn't exist */
286 errno = __stdc_ioerr2errno(IoErr());
287 goto error;
290 /* Add arguments to command line args */
291 PosixCBase->exec_args = appendargs(PosixCBase->exec_args, &argssize, argv + 1, PosixCBase->exec_pool);
292 if (!PosixCBase->exec_args)
294 errno = ENOMEM;
295 goto error;
298 /* End command line args with '\n' */
299 if(strlen(PosixCBase->exec_args) > 0)
300 PosixCBase->exec_args[strlen(PosixCBase->exec_args) - 1] = '\n';
301 else
302 strcat(PosixCBase->exec_args, "\n");
304 /* let's make some sanity tests */
305 struct stat st;
306 if(stat(filename2, &st) == 0)
308 if(!(st.st_mode & S_IXUSR) && !(st.st_mode & S_IXGRP) && !(st.st_mode & S_IXOTH))
310 /* file is not executable */
311 errno = EACCES;
312 goto error;
314 if(st.st_mode & S_IFDIR)
316 /* it's a directory */
317 errno = EACCES;
318 goto error;
321 else
323 /* couldn't stat file */
324 goto error;
327 /* Set taskname */
328 PosixCBase->exec_taskname = AllocPooled(PosixCBase->exec_pool, strlen(filename2_dos) + 1);
329 if (!PosixCBase->exec_taskname)
331 errno = ENOMEM;
332 goto error;
334 strcpy(PosixCBase->exec_taskname, filename2_dos);
336 /* Load file to execute */
337 PosixCBase->exec_seglist = LoadSeg((CONST_STRPTR)filename2_dos);
338 if (!PosixCBase->exec_seglist)
340 errno = ENOEXEC;
341 goto error;
344 me = (struct Process *)FindTask(NULL);
346 if (envp && envp != environ)
348 struct MinList tempenv;
349 struct LocalVar *lv, *lv2;
350 char *const *envit;
351 int env_ok = 1;
353 /* Remember previous environment variables so they can be put back
354 * if something goes wrong
356 NEWLIST(&tempenv);
357 ForeachNodeSafe(&me->pr_LocalVars, lv, lv2)
359 Remove((struct Node *)lv);
360 AddTail((struct List *)&tempenv, (struct Node *)lv);
363 NEWLIST(&me->pr_LocalVars);
364 environ = NULL;
366 for (envit = envp; *envit && env_ok; envit++)
367 env_ok = putenv(*envit) == 0;
369 if (env_ok)
370 /* Old vars may be freed */
371 ForeachNodeSafe(&tempenv, lv, lv2)
373 Remove((struct Node *)lv);
374 FreeMem(lv->lv_Value, lv->lv_Len);
375 FreeVec(lv);
377 else
379 /* Free new nodes */
380 ForeachNodeSafe(&me->pr_LocalVars, lv, lv2)
382 Remove((struct Node *)lv);
383 FreeMem(lv->lv_Value, lv->lv_Len);
384 FreeVec(lv);
387 /* Put old ones back */
388 ForeachNodeSafe(&tempenv, lv, lv2)
390 Remove((struct Node *)lv);
391 AddTail((struct List *)&me->pr_LocalVars, (struct Node *)lv);
394 errno = ENOMEM;
395 goto error;
399 D(bug("__exec_prepare: Done, returning %p\n", PosixCBase));
401 /* Everything OK */
402 return (APTR)PosixCBase;
404 error:
405 __exec_cleanup(PosixCBase);
407 return (APTR)NULL;
410 static void __exec_do_pretend_child(struct PosixCIntBase *PosixCBase)
412 /* When exec is called under vfork condition, the PRETEND_CHILD flag is set
413 and we need to signal child that exec is called.
416 struct vfork_data *udata = PosixCBase->vfork_data;
418 D(bug("[__exec_do_pretend_child] PRETEND_CHILD\n"));
420 __close_on_exec_fdescs();
422 D(bug("[__exec_do_pretend_child] Notify child to call __exec_do\n"));
424 /* Signal child that __exec_do is called */
425 Signal(udata->child, 1 << udata->child_signal);
427 /* Clean up in parent */
428 D(bug("[__exec_do_pretend_child] Cleaning up parent\n"));
429 __exec_cleanup(PosixCBase);
431 /* Continue as parent process */
432 D(bug("[__exec_do_pretend_child] Stop running as PRETEND_CHILD\n"));
433 _exit(0);
435 assert(0); /* Should not be reached */
438 static void __exec_do_regular(struct PosixCIntBase *PosixCBase)
440 char *oldtaskname;
441 struct CommandLineInterface *cli = Cli();
442 struct Task *self = FindTask(NULL);
443 LONG returncode;
445 PosixCBase->flags |= EXEC_PARENT;
447 oldtaskname = self->tc_Node.ln_Name;
448 self->tc_Node.ln_Name = PosixCBase->exec_taskname;
449 SetProgramName((STRPTR)PosixCBase->exec_taskname);
452 /* Set standard files to the standard files from arosc */
453 fdesc *in = __getfdesc(STDIN_FILENO), *out = __getfdesc(STDOUT_FILENO),
454 *err = __getfdesc(STDERR_FILENO);
455 BPTR oldin = BNULL, oldout = BNULL, olderr = BNULL;
456 char inchanged = 0, outchanged = 0, errchanged = 0;
457 struct Process *me = (struct Process *)FindTask(NULL);
459 if(in && in->fcb->handle != Input())
461 oldin = SelectInput(in->fcb->handle);
462 inchanged = 1;
464 if(out && in->fcb->handle != Output())
466 oldout = SelectOutput(out->fcb->handle);
467 outchanged = 1;
469 if (err)
471 if (me->pr_CES)
473 if (me->pr_CES != err->fcb->handle)
474 errchanged = 1;
476 else /* me->pr_CES */
478 /* Only replace if stdout != stderr */
479 if (out && out->fcb->handle != err->fcb->handle)
480 errchanged = 1;
482 if (errchanged)
484 olderr = me->pr_CES;
485 me->pr_CES = err->fcb->handle;
489 D(bug("[__exec_do_regular] Running program, PosixCBase=%x\n", PosixCBase));
490 returncode = RunCommand(
491 PosixCBase->exec_seglist,
492 cli->cli_DefaultStack * CLI_DEFAULTSTACK_UNIT,
493 (STRPTR)PosixCBase->exec_args,
494 strlen(PosixCBase->exec_args)
497 /* RunCommand() does not Close() standard output so may not flush either.
498 So to be sure flush them here */
499 Flush(Output());
500 if (me->pr_CES)
501 Flush(me->pr_CES);
503 if(inchanged)
504 SelectInput(oldin);
505 if(outchanged)
506 SelectOutput(oldout);
507 if(errchanged)
508 me->pr_CES = olderr;
510 D(bug("[__exec_do_regular] Program ran, PosixCBase=%x, __aros_getbase_PosixCBase()=%x\n",
511 PosixCBase, __aros_getbase_PosixCBase()
515 self->tc_Node.ln_Name = oldtaskname;
516 SetProgramName((STRPTR)oldtaskname);
518 __exec_cleanup(PosixCBase);
520 D(bug("[__exec_do_regular] exiting from __exec()\n"));
521 _Exit(returncode);
525 char *const *__exec_valist2array(const char *arg1, va_list list)
527 struct PosixCIntBase *PosixCBase =
528 (struct PosixCIntBase *)__aros_getbase_PosixCBase();
529 int argc, i;
530 static char *no_arg[] = {NULL};
531 va_list list2;
532 char *argit;
534 assert(PosixCBase->exec_tmparray == NULL);
536 va_copy(list2, list);
538 if (arg1 == NULL)
539 return no_arg;
541 for (argit = va_arg(list, char *), argc = 1;
542 argit != NULL;
543 argit = va_arg(list, char *)
545 argc++;
547 if (!(PosixCBase->exec_tmparray = malloc((argc+1)*(sizeof(char *)))))
549 D(bug("__exec_valist2array: Memory allocation failed\n"));
550 va_end(list2);
551 return NULL;
554 PosixCBase->exec_tmparray[0] = (char *)arg1;
555 for (argit = va_arg(list2, char *), i = 1;
556 i <= argc; /* i == argc will copy the NULL pointer */
557 argit = va_arg(list2, char *), i++
560 D(bug("arg %d: %x\n", i, argit));
561 PosixCBase->exec_tmparray[i] = argit;
564 va_end(list2);
566 return PosixCBase->exec_tmparray;
570 void __exec_cleanup_array()
572 struct PosixCIntBase *PosixCBase =
573 (struct PosixCIntBase *)__aros_getbase_PosixCBase();
575 if (PosixCBase->exec_tmparray)
577 free((void *)PosixCBase->exec_tmparray);
578 PosixCBase->exec_tmparray = NULL;
583 /* Support functions */
584 /*********************/
586 /* Return TRUE if there are any white spaces in the string */
587 static BOOL containswhite(const char *str)
589 while(*str != '\0')
590 if(isspace(*str++)) return TRUE;
591 return FALSE;
594 /* Escape the string and quote it */
595 static char *escape(const char *str)
597 const char *strptr = str;
598 char *escaped, *escptr;
599 /* Additional two characters for '"', and one for '\0' */
600 int bufsize = strlen(str) + 3;
601 /* Take into account characters to ecape */
602 while(*strptr != '\0')
604 switch(*strptr++)
606 case '\n':
607 case '"':
608 case '*':
609 bufsize++;
612 escptr = escaped = (char*) malloc(bufsize);
613 if(!escaped)
614 return NULL;
615 *escptr++ = '"';
616 for(strptr = str; *strptr != '\0'; strptr++)
618 switch(*strptr)
620 case '\n':
621 case '"':
622 case '*':
623 *escptr++ = '*';
624 *escptr++ = (*strptr == '\n' ? 'N' : *strptr);
625 break;
626 default:
627 *escptr++ = *strptr;
628 break;
631 *escptr++ = '"';
632 *escptr = '\0';
633 return escaped;
636 /* Append arg string to argptr increasing argptr if needed */
637 static char *appendarg(char *argptr, int *argptrsize, const char *arg, APTR pool)
639 while(strlen(argptr) + strlen(arg) + 2 > *argptrsize)
641 char *argptr2;
642 int argptrsize2 = 2*(*argptrsize);
644 argptr2 = AllocPooled(pool, argptrsize2);
645 if(!argptr2)
647 FreePooled(pool, argptr, *argptrsize);
648 return NULL;
650 strcpy(argptr2, argptr);
651 FreePooled(pool, argptr, *argptrsize);
652 argptr = argptr2;
653 *argptrsize = argptrsize2;
655 strcat(argptr, arg);
656 strcat(argptr, " ");
658 return argptr;
661 static char *appendargs(char *argptr, int *argssizeptr, char *const args[], APTR pool)
663 char *const *argsit;
665 for (argsit = args; *argsit && argptr; argsit++)
667 if(containswhite(*argsit))
669 char *escaped = escape(*argsit);
670 if(!escaped)
672 FreePooled(pool, argptr, *argssizeptr);
673 return NULL;
675 argptr = appendarg(argptr, argssizeptr, escaped, pool);
676 free(escaped);
678 else
679 argptr = appendarg(argptr, argssizeptr, *argsit, pool);
682 return argptr;
685 static void __exec_cleanup(struct PosixCIntBase *PosixCBase)
687 D(bug("__exec_cleanup: me(%x)\n", FindTask(NULL)));
689 if (PosixCBase->exec_pool)
691 DeletePool(PosixCBase->exec_pool);
692 PosixCBase->exec_pool = NULL;
694 if (PosixCBase->exec_seglist)
696 UnLoadSeg(PosixCBase->exec_seglist);
697 PosixCBase->exec_seglist = (BPTR)NULL;
700 if (PosixCBase->exec_oldstdcbase)
702 PosixCBase->PosixCBase.StdCBase = PosixCBase->exec_oldstdcbase;
703 PosixCBase->exec_oldstdcbase = NULL;