posixc.library: use vfork_jmp consistenty to exit __vfork
[AROS.git] / compiler / posixc / __vfork.c
blobf3caf8ab9cf860cbfb8b4cfa32c05d47deadca2b
1 /*
2 Copyright © 2008-2013, The AROS Development Team. All rights reserved.
3 $Id$
4 */
6 #include <proto/exec.h>
7 #include <proto/dos.h>
8 #include <proto/stdc.h>
9 #include <exec/exec.h>
10 #include <exec/tasks.h>
11 #include <dos/dos.h>
12 #include <libraries/stdc.h>
13 #include <aros/cpu.h>
15 #include <sys/types.h>
16 #include <unistd.h>
17 #include <setjmp.h>
18 #include <errno.h>
19 #include <assert.h>
20 #include <stdlib.h>
21 #include <stdio.h>
23 #include "__posixc_intbase.h"
24 #include "__fdesc.h"
25 #include "__vfork.h"
26 #include "__exec.h"
28 #define DEBUG 0
30 #include <aros/debug.h>
31 #include <aros/startup.h>
33 /*****************************************************************************
35 NAME
36 #include <unistd.h>
38 pid_t vfork(
40 SYNOPSIS
41 void)
43 FUNCTION
44 Function to create a subprocess of the current process.
46 This is there to ease porting of software using the fork()/vfork()
47 POSIX functions. Due to a different memory and process model, fork()
48 is not implemented at the moment in the C library. vfork() is provided
49 with some extended functionality. In the POSIX standard the only
50 guaranteed functionality for vfork() is to have an exec*() function or
51 exit() called right after the vfork() in the child.
53 Extra functionality for vfork():
54 - The child has its own memory heap; memory allocation/deallocation
55 is allowed and the heap will be removed when calling _exit() or will
56 be used for the code started by the exec*() functions.
57 - The child will have a copy of the file descriptors as specified by
58 the POSIX standard for the fork() function. File I/O is possible in
59 the child, as is file manipulation with dup() etc.
61 Difference with fork():
62 - The virtual memory heap is not duplicated as in POSIX but the memory
63 is shared between parent and child. AROS lives in one big single
64 memory region so changes to memory in the child are also seen by the
65 parent.
67 Behaviour for other resources not described in this doc may not be
68 relied on for future compatibility.
70 INPUTS
73 RESULT
74 -1: error, no child is started; errno will be set.
75 0: Running in child
76 >0: Running in parent, pid of child is return value.
78 NOTES
79 Current implementation of vfork() will only really start running things
80 in parallel on an exec*() call. After vfork(), child code will run until
81 _exit() or exec*(). With _exit(), the child will exit and the parent
82 will continue; with exec*(), the child will be detached and the parent
83 will continue.
85 EXAMPLE
87 BUGS
89 SEE ALSO
90 execl(), execve(), execlp(), execv(), execvp()
92 INTERNALS
94 ******************************************************************************/
96 /* The following functions are used to update the child's and parent's privdata
97 for the parent pretending to be running as child and for the child to take
98 over. It is called in the following sequence:
99 parent_enterpretendchild() is called in vfork() so the parent pretends to be
100 running as child; child_takeover() is called by child if exec*() so it can
101 continue from the parent state; parent_leavepretendchild() is called by
102 parent to switch back to be running as parent
104 static void parent_enterpretendchild(struct vfork_data *udata);
105 static void child_takeover(struct vfork_data *udata);
106 static void parent_leavepretendchild(struct vfork_data *udata);
108 static __attribute__((noinline)) void __vfork_exit_controlled_stack(struct vfork_data *udata);
110 LONG launcher()
112 D(bug("launcher: Entered child launcher, ThisTask=%p\n", SysBase->ThisTask));
114 struct Task *this = FindTask(NULL);
115 struct vfork_data *udata = this->tc_UserData;
116 BYTE child_signal;
117 struct PosixCIntBase *PosixCBase = NULL;
118 jmp_buf exec_exitjmp; /* jmp_buf for when calling __exec_do */
119 int exec_error; /* errno for when calling __exec_do */
120 LONG ret = 0;
122 /* Allocate signal for parent->child communication */
123 child_signal = udata->child_signal = AllocSignal(-1);
124 D(bug("launcher: Allocated child signal: %d\n", udata->child_signal));
125 if (udata->child_signal == -1)
127 /* Lie */
128 udata->child_errno = ENOMEM;
129 Signal(udata->parent, 1 << udata->parent_signal);
130 return -1;
133 /* TODO: Can we avoid opening posixc.library for child process? */
134 PosixCBase = (struct PosixCIntBase *)OpenLibrary((STRPTR) "posixc.library", 0);
135 if (PosixCBase)
137 PosixCBase->PosixCBase.StdCBase = (struct StdCBase *)OpenLibrary((STRPTR) "stdc.library", 0);
138 if (!PosixCBase->PosixCBase.StdCBase)
140 CloseLibrary((struct Library *)PosixCBase);
141 PosixCBase = NULL;
144 if (!PosixCBase)
146 D(bug("launcher:Failed to open libraries!\n"));
147 FreeSignal(child_signal);
148 udata->child_errno = ENOMEM;
149 Signal(udata->parent, 1 << udata->parent_signal);
150 return -1;
152 D(bug("launcher: Opened PosixCBase: %x, StdCBase: %x\n",
153 PosixCBase, PosixCBase->PosixCBase.StdCBase
156 udata->child_posixcbase = PosixCBase;
158 if (setjmp(exec_exitjmp) == 0)
160 /* Setup complete, signal parent */
161 D(bug("launcher: Signaling parent that we finished setup\n"));
162 Signal(udata->parent, 1 << udata->parent_signal);
164 D(bug("launcher: Child waiting for exec or exit\n"));
165 Wait(1 << udata->child_signal);
167 if (udata->child_executed)
169 APTR exec_id;
171 D(bug("launcher: child executed\n"));
173 child_takeover(udata);
175 /* Filenames passed from parent obey parent's doupath */
177 PosixCBase->doupath = udata->parent_posixcbase->doupath;
178 D(bug("launcher: doupath == %d for __exec_prepare()\n", PosixCBase->doupath));
180 exec_id = udata->exec_id = __exec_prepare(
181 udata->exec_filename,
183 udata->exec_argv,
184 udata->exec_envp
187 /* Reset handling of upath */
188 PosixCBase->doupath = 0;
189 udata->child_errno = errno;
191 D(bug("launcher: informing parent that we have run __exec_prepare\n"));
192 /* Inform parent that we have run __exec_prepare */
193 Signal(udata->parent, 1 << udata->parent_signal);
195 /* Wait 'till __exec_do() is called on parent process */
196 D(bug("launcher: Waiting parent to get the result\n"));
197 Wait(1 << udata->child_signal);
199 D(bug("launcher: informing parent that we won't use udata anymore\n"));
200 /* Inform parent that we won't use udata anymore */
201 Signal(udata->parent, 1 << udata->parent_signal);
203 D(bug("launcher: waiting for parent to be after _exit()\n"));
204 Wait(1 << udata->child_signal);
206 if (exec_id)
208 D(bug("launcher: catch _exit()\n"));
209 __stdc_program_startup(exec_exitjmp, &exec_error);
211 D(bug("launcher: executing command\n"));
212 __exec_do(exec_id);
214 assert(0); /* Should not be reached */
216 else
218 D(bug("launcher: __exec_prepare returned with an error\n"));
219 ret = -1;
222 else /* !udata->child_executed */
224 D(bug("launcher: child not executed\n"));
226 D(bug("launcher: informing parent that we won't use udata anymore\n"));
227 /* Inform parent that we won't use udata anymore */
228 Signal(udata->parent, 1 << udata->parent_signal);
231 else
233 /* child exited */
234 D(bug("launcher: catched _exit(), errno=%d\n", exec_error));
237 D(bug("launcher: freeing child_signal\n"));
238 FreeSignal(child_signal);
240 D(bug("launcher: closing libraries\n"));
241 CloseLibrary((struct Library *)PosixCBase->PosixCBase.StdCBase);
242 CloseLibrary((struct Library *)PosixCBase);
244 D(bug("Child Done\n"));
246 return ret;
249 /* This can be good for debugging */
250 #ifdef __arm__
251 #define SP 8
252 #define ALT 27
253 #endif
255 #ifndef SP
256 #define SP _JMPLEN - 1
257 #define ALT _JMPLEN - 1
258 #endif
260 pid_t __vfork(jmp_buf env)
262 struct PosixCIntBase *PosixCBase =
263 (struct PosixCIntBase *)__aros_getbase_PosixCBase();
264 struct Task *this = FindTask(NULL);
265 struct ETask *etask = NULL;
266 struct vfork_data *udata = AllocMem(sizeof(struct vfork_data), MEMF_ANY | MEMF_CLEAR);
267 if (udata == NULL)
269 errno = ENOMEM;
270 vfork_longjmp(env, -1);
272 D(bug("__vfork: Parent: allocated udata %p, jmp_buf %p\n", udata, udata->vfork_jmp));
273 *udata->vfork_jmp = *env;
275 struct TagItem tags[] =
277 { NP_Entry, (IPTR) launcher },
278 { NP_CloseInput, (IPTR) FALSE },
279 { NP_CloseOutput, (IPTR) FALSE },
280 { NP_CloseError, (IPTR) FALSE },
281 { NP_Cli, (IPTR) TRUE },
282 { NP_Name, (IPTR) "vfork()" },
283 { NP_UserData, (IPTR) udata },
284 { NP_NotifyOnDeath, (IPTR) TRUE },
285 { TAG_DONE, 0 }
288 D(bug("__vfork: Parent: initial jmp_buf %p\n", env));
289 D(bug("__vfork: Parent: ip: %p, stack: %p, alt: 0x%p\n", env->retaddr, env->regs[SP], env->regs[ALT]));
290 D(bug("__vfork: Parent: Current altstack 0x%p\n", *((void **) this->tc_SPLower)));
291 D(hexdump(env, 0, sizeof(jmp_buf) + sizeof(void *) * 4));
293 udata->parent = this;
294 udata->prev = PosixCBase->vfork_data;
296 D(bug("__vfork: Parent: Saved old parent's vfork_data: %p\n", udata->prev));
297 udata->parent_posixcbase = PosixCBase;
299 D(bug("__vfork: Parent: Allocating parent signal\n"));
300 /* Allocate signal for child->parent communication */
301 udata->parent_signal = AllocSignal(-1);
302 if (udata->parent_signal == -1)
304 /* Couldn't allocate the signal, return -1 */
305 FreeMem(udata, sizeof(struct vfork_data));
306 errno = ENOMEM;
307 vfork_longjmp(udata->vfork_jmp, -1);
310 PosixCBase->flags |= VFORK_PARENT;
312 D(bug("__vfork: Parent: Creating child\n"));
313 udata->child = (struct Task*) CreateNewProc(tags);
315 if (udata->child == NULL)
317 D(bug("__vfork: Child could not be created\n"));
318 /* Something went wrong, return -1 */
319 FreeMem(udata, sizeof(struct vfork_data));
320 errno = ENOMEM; /* Most likely */
321 vfork_longjmp(udata->vfork_jmp, -1);
323 D(bug("__vfork: Parent: Child created %p, waiting to finish setup\n", udata->child));
325 /* Wait for child to finish setup */
326 Wait(1 << udata->parent_signal);
328 if (udata->child_errno)
330 /* An error occured during child setup */
331 D(bug("__vfork: Child returned an error (%d)\n", udata->child_errno));
332 errno = udata->child_errno;
333 vfork_longjmp(udata->vfork_jmp, -1);
336 PosixCBase->flags &= ~VFORK_PARENT;
338 D(bug("__vfork: Parent: Setting jmp_buf at %p\n", udata->parent_newexitjmp));
339 if (setjmp(udata->parent_newexitjmp) == 0)
341 udata->parent_olderrorptr = __stdc_set_errorptr(&udata->child_error);
342 udata->child_error = *udata->parent_olderrorptr;
343 __stdc_set_exitjmp(udata->parent_newexitjmp, udata->parent_oldexitjmp);
345 parent_enterpretendchild(udata);
347 D(bug("__vfork: Child(%p) jumping to jmp_buf %p\n",
348 udata->child, &udata->vfork_jmp
350 D(bug("__vfork: ip: %p, stack: %p alt: %p\n",
351 udata->vfork_jmp[0].retaddr, udata->vfork_jmp[0].regs[SP],
352 udata->vfork_jmp[0].regs[ALT]
355 vfork_longjmp(udata->vfork_jmp, 0);
356 assert(0); /* not reached */
357 return (pid_t) 0;
359 else /* setjmp() != 0; so child has exited() */
361 D(bug("__vfork: Child: child exiting\n or executed\n"));
363 /* Stack may have been overwritten when we return here,
364 * we jump to here from a function lower in the call chain
366 PosixCBase = (struct PosixCIntBase *)__aros_getbase_PosixCBase();
367 udata = PosixCBase->vfork_data;
369 D(bug("__vfork: Child: acb_vfork_data = %x\n", udata));
371 if (!udata->child_executed)
373 D(bug("__vfork: Child: not executed\n"));
375 /* et_Result is normally set in startup code but no exec was performed
376 so we have to mimic the startup code
378 etask = GetETask(udata->child);
379 if (etask)
380 etask->et_Result1 = udata->child_error;
382 D(bug("__vfork: Child: Signaling child %p, signal %d\n", udata->child, udata->child_signal));
383 Signal(udata->child, 1 << udata->child_signal);
386 D(bug("__vfork: Parent: Waiting for child to finish using udata, me=%p, signal %d\n", FindTask(NULL),
387 udata->parent_signal));
388 /* Wait for child to finish using udata */
389 Wait(1 << udata->parent_signal);
391 D(bug("__vfork: Parent: fflushing\n"));
392 fflush(NULL);
394 __vfork_exit_controlled_stack(udata);
396 assert(0); /* not reached */
397 return (pid_t) 1;
402 * The sole purpose of this function is to enable control over allocation of dummy and env.
403 * Previously they were allocated in the ending code of __vfork function. On ARM however
404 * this was causing immediate allocation of space at entry to the __vfork function. Moreover
405 * the jmp_buf is aligned(16) and as such is represented on the stack as a pointer to stack
406 * region instead of offset from stack base.
408 * The exit block of __vfork function represents code that underwent a number of longjumps. The
409 * stack there is not guaranteed to be preserved, thus the on-stack pointer representing dummy
410 * and env were also damaged. Extracting the code below allows to control when the variables
411 * are allocated (as long as the function remains not inlined).
413 static __attribute__((noinline)) void __vfork_exit_controlled_stack(struct vfork_data *udata)
415 jmp_buf dummy;
416 jmp_buf env;
418 D(bug("__vfork: Parent: freeing parent signal\n"));
419 FreeSignal(udata->parent_signal);
421 errno = udata->child_errno;
423 /* leavepretendchild will restore old StdCBase and thus also
424 old startup jmp_buf.
425 This is also the reason this function has to be called before
426 signaling child that we are after _exit().
428 parent_leavepretendchild(udata);
430 if(udata->child_executed)
432 D(bug("__vfork: Inform child that we are after _exit()\n"));
433 Signal(udata->child, 1 << udata->child_signal);
436 D(bug("__vfork: Parent: restoring startup buffer\n"));
437 /* Restore parent errorptr and startup buffer */
438 __stdc_set_errorptr(udata->parent_olderrorptr);
439 __stdc_set_exitjmp(udata->parent_oldexitjmp, dummy);
441 /* Save some data from udata before udata is being freed */
442 *env = *udata->vfork_jmp;
444 D(bug("__vfork: Parent: freeing udata\n"));
445 FreeMem(udata, sizeof(struct vfork_data));
447 D(bug("__vfork: Parent jumping to jmp_buf %p\n", env));
448 D(bug("__vfork: ip: %p, stack: %p\n", env->retaddr, env->regs[SP]));
449 vfork_longjmp(env, GetETaskID(udata->child));
452 static void parent_enterpretendchild(struct vfork_data *udata)
454 struct PosixCIntBase *PosixCBase =
455 (struct PosixCIntBase *)__aros_getbase_PosixCBase();
456 D(bug("parent_enterpretendchild(%x): entered\n", udata));
458 PosixCBase->vfork_data = udata;
460 /* Remember and switch StdCBase */
461 udata->parent_stdcbase = PosixCBase->PosixCBase.StdCBase;
462 PosixCBase->PosixCBase.StdCBase = udata->child_posixcbase->PosixCBase.StdCBase;
463 /* _[eE]xit() can also be called with the switched StdCBase so we also
464 register the exit jmp_buf in this StdCBase. We don't need to remember
465 old as child will overwrite these if it should call __exec_do().
467 __stdc_program_startup(udata->parent_newexitjmp, &udata->child_error);
469 /* Remember and switch env var list */
470 udata->parent_env_list = PosixCBase->env_list;
471 PosixCBase->env_list = udata->child_posixcbase->env_list;
473 /* Remember and switch fd descriptor table */
474 udata->parent_internalpool = PosixCBase->internalpool;
475 PosixCBase->internalpool = udata->child_posixcbase->internalpool;
476 __getfdarray((APTR *)&udata->parent_fd_array, &udata->parent_numslots);
477 __setfdarraybase(udata->child_posixcbase);
479 /* Remember and switch chdir fields */
480 udata->parent_cd_changed = PosixCBase->cd_changed;
481 PosixCBase->cd_changed = udata->child_posixcbase->cd_changed;
482 udata->parent_cd_lock = PosixCBase->cd_lock;
483 PosixCBase->cd_lock = udata->child_posixcbase->cd_lock;
484 udata->parent_curdir = CurrentDir(((struct Process *)udata->child)->pr_CurrentDir);
486 /* Remember and switch upathbuf */
487 udata->parent_upathbuf = PosixCBase->upathbuf;
488 PosixCBase->upathbuf = udata->child_posixcbase->upathbuf;
490 /* Pretend to be running as the child created by vfork */
491 udata->parent_flags = PosixCBase->flags;
492 PosixCBase->flags |= PRETEND_CHILD;
494 D(bug("parent_enterpretendchild: leaving\n"));
497 static void child_takeover(struct vfork_data *udata)
499 struct PosixCIntBase *PosixCBase =
500 (struct PosixCIntBase *)__aros_getbase_PosixCBase();
501 D(bug("child_takeover(%x): entered\n", udata));
503 /* Set current dir to parent's current dir */
504 PosixCBase->cd_changed = udata->parent_posixcbase->cd_changed;
505 PosixCBase->cd_lock = udata->parent_posixcbase->cd_lock;
506 CurrentDir(((struct Process *)udata->parent)->pr_CurrentDir);
508 D(bug("child_takeover(): leaving\n"));
511 static void parent_leavepretendchild(struct vfork_data *udata)
513 struct PosixCIntBase *PosixCBase =
514 (struct PosixCIntBase *)__aros_getbase_PosixCBase();
515 D(bug("parent_leavepretendchild(%x): entered\n", udata));
517 /* Restore parent's StdCBase */
518 PosixCBase->PosixCBase.StdCBase = udata->parent_stdcbase;
520 /* Restore parent's env var list */
521 PosixCBase->env_list = udata->parent_env_list;
523 /* Restore parent's old fd_array */
524 PosixCBase->internalpool = udata->parent_internalpool;
525 __setfdarray(udata->parent_fd_array, udata->parent_numslots);
527 /* Switch to currentdir from before vfork() call */
528 PosixCBase->cd_changed = udata->parent_cd_changed;
529 PosixCBase->cd_lock = udata->parent_cd_lock;
530 CurrentDir(udata->parent_curdir);
532 /* Restore parent's upathbuf */
533 PosixCBase->upathbuf = udata->parent_upathbuf;
535 /* Switch to previous vfork_data */
536 PosixCBase->vfork_data = udata->prev;
537 PosixCBase->flags = udata->parent_flags;
539 D(bug("parent_leavepretendchild: leaving\n"));