Date: Sat, 12 Jun 2021 23:13:54 +0200 From: Joerg Sonnenberger <jo...@bec.de> Message-ID: <ymujkhwh91de5...@bec.de>
Sorry, missed this message when I was cherry picking messages to read in a timely fashion. | On Wed, Jun 09, 2021 at 01:03:20AM +0700, Robert Elz wrote: | > after a vfork() the child can do anything | This is not true. After vfork(), it is only supposed to call async | signal safe functions. What you said, and what I said are not contradictory. Note I said "can do" and you said "is only supposed to". What is supposed to work and what actually does work (or can be made to work) are two different things. I would note though our man page doesn't make that requirement, or not that I can see anyway, and vfork() is not in POSIX of course - and while I don't have a copy to check, I'd suspect not in the C standards either), so while that sounds like a reasonable requirement to impose for safer operation, I'm not sure that anyone or anything actually does that. | That said, a flag for the double fork semantic might be useful. If the "that said" relates to fork() or vfork() not being async signal safe, so a double fork() (when the first is vfork anyway) would not be condoned, that doesn't matter, there to be an async-signal-safe _Fork() call added to the next version of POSIX, so even with the "only async signal safe" requirement for vfork() a: if ((child = vfork()) == 0) { if (_Fork()) _exit(0); /* do child stuff here */ } else { if (child != -1) waitpid(child, &status, 0); /* parent continues */ } sequence is possible (will be possible, we don't have _Fork() yet) and fully conforming, apart from vfork() not being in the standard. The child of the vfork() only does _Fork() and _exit(), both of which are permitted. The grandchild process is created by a full fork() so can do anything at all. For what it is worth, the definition of _Fork() is (to be): The _Fork() function shall be equivalent to fork(), except that fork handlers established by means of the pthread_atfork() function shall not be called and _Fork() shall be async-signal-safe. Aside from the prototype line, which is just as you'd expect, that's it. If (or when) we add it, we probably need to decide whether we need _VFork() (or some similar name) as well. Probably. However, an extra posix_spawn attribute to handle double fork is almost certainly not warranted, this kind of thing isn't all that common, and adding the mechanism to the posix_spawn set of functions would really just be bloat (there's also a proposal floating around to add the ability to change a terminal's process group as a posix_spawn action -- that's required for shells to be able to use posix_spawn in general cases, but that most likely won't go anywhere). Remember what the standard says about posix_spawn() (in its rationale, this is not strictly part of the standard)... The posix_spawn( ) function and its close relation posix_spawnp( ) have been introduced to overcome the following perceived difficulties with fork( ): the fork( ) function is difficult or impossible to implement without swapping or dynamic address translation. [...] This view of the role of posix_spawn( ) and posix_spawnp( ) influenced the design of their API. It does not attempt to provide the full functionality of fork( )/exec in which arbitrary user-specified operations of any sort are permitted between the creation of the child process and the execution of the new process image; any attempt to reach that level would need to provide a programming language as parameters. [...] [ It lists some of the known problems with the posix_spawn interface ] [...] The posix_spawn( ) and posix_spawnp( ) functions do not have all the power of fork( )/exec. This is to be expected. The fork( ) function is a wonderfully powerful operation. We do not expect to duplicate its functionality in a simple, fast function with no special hardware requirements. It is worth noting that posix_spawn( ) and posix_spawnp( ) are very similar to the process creation operations on many operating systems that are not UNIX systems. It then goes on to list the requirements they had for posix_spawn()... The requirements for posix_spawn( ) and posix_spawnp( ) are: � They must be implementable without an MMU or unusual hardware. � They must be compatible with existing POSIX standards. Additional goals are: � They should be efficiently implementable. � They should be able to replace at least 50% of typical executions of fork( ). � A system with posix_spawn( ) and posix_spawnp( ) and without fork( ) should be useful, at least for realtime applications. � A system with fork( ) and the exec family should be able to implement posix_spawn( ) and posix_spawnp( ) as library routines. Note that their target is just to replace half fork() calls (and just executions, not appearances in the source - most fork() calls executed come from a relatively small set of sources) and they explicitly state that implementing posix_spawn() using fork()+exec() is acceptable - it isn't necessarily intended to be cheaper to use. What it is required to be is implementable on low end, no virtual address space, systems. That was its motivation, not obsoleting vfork(). Please don't see posix_spawn() as being (or ever becoming) a panacea that can replace all fork() (or even just vfork()) calls - even just in the cases where an exec() follows soon after. It works fine for most simple cases, but there are lots of not-so-simple situations that it cannot handle, and burdening the interface with the ability to deal with all of those would reduce the "efficiently implementable" goal for little real benefit. Another example is made obvious by the bug Martin committed a fix for in the past few days ... the order of operations was incorrect in our implementation. But that this problem can exist shows that there are ordering issues - a process that wants to open files using its current privs, then reset those before (perhaps) opening more files, and then doing the exec() part cannot do that with posix_spawn(). Again, this is rare enough that adding the complexity required to allow it to work just isn't worth it. [In this area also note that the POSIX_SPAWN_RESETIDS flag causes (effectively) setuid(getuid()), there's no similar operation to do setuid(geteuid()) ... again too rare to bother.] If someone wanted something to propose that might be more useful, then the ability for posix_spawn() to end with (effectively) an execve() rather than execv() (ie: a call to pass in an environment vector to pass to the new process, rather than having it inherit the parent's) seems like a more useful facility to me. That's something that is currently listed as a known issue (more costly to achieve using posix_spawn() than it is with fork()/exec()). Of course, to propose something like this (with any real hope of success) it needs to be implemented first, ideally in several different systems. kre