Skip to content

Commit caabce2

Browse files
authored
fd inheritance - per-process stdio & fd inheritance for spawned children (#416)
* Don't open timer.device any tyme a pthread timed function is called * Fixed uuid created as local memory variable * Refactor debug output formatting * fd inheritance: implement per-process stdio and fd inheritance for spawned children - Move struct iob __sf[3] and struct _glue __sglue from shared globals to per-process heap-allocated fields in struct _clib4 (dos.h, clib4.h). Each process now has isolated stdio state; child cleanup no longer corrupts the parent's stdio buffers. - stdio_file_init (file_init.c) allocates per-process iob structs and the root glue node on the heap. __close_all_files (init_exit.c) frees them. __stdin/__stdout/__stderr (__std.c) and _fwalk (fwalk.c) use the per-process pointers. findfp.c and stdio_headers.h updated accordingly. - fd inheritance: parent encodes open fds >= 3 as a spec string (fd:flags:handle_or_token;) passed via spawnData.fdInherit. PIPE: handles are stored as symbolic tokens STDIN/STDOUT/STDERR so the child uses Input()/Output()/ErrorOutput() instead of a disconnected DupFileHandle copy (AmigaOS4 PIPE: limitation). - children.c: insertSpawnedChildren gains fdInherit param; new functions import_inherited_fds_from_spec and import_pending_fds_for_process. FDF_NO_CLOSE_BPTR set for symbolic-token handles. - clib4.c (libOpen): added existing->self == _me guard so NP_Child TRUE children are not mistaken for the parent and get their own _clib4 + run import_pending_fds_for_process after _start_ctors. - Extract build_fd_inherit_spec / close_fd_inherit_spec_handles into spawn_utils.c / spawn_utils.h (shared by spawnvpe, spawnv, popen). spawnvpe passes the explicit fhin/fhout/fherr for token detection; spawnv and popen pass -1/-1/-1 (no stdio redirection). - spawnData changed from stack-allocated struct to heap-allocated pointer (ownership transferred to spawnedProcessEnter / Clib4Children). NP_CopyVars TRUE added to spawnv and popen SystemTags calls. - Remove all debug DOS Printf calls added during development.
1 parent 778afb0 commit caabce2

31 files changed

Lines changed: 797 additions & 192 deletions

library/c.lib_rev.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#define REVISION 1
33
#define SUBREVISION 0
44

5-
#define DATE "14.05.2026"
5+
#define DATE "28.05.2026"
66
#define VERS "clib4.library 2.1"
7-
#define VSTRING "clib4.library 2.1 (14.05.2026)\r\n"
8-
#define VERSTAG "\0$VER: clib4.library 2.1-ab40f98 (14.05.2026)"
7+
#define VSTRING "clib4.library 2.1 (28.05.2026)\r\n"
8+
#define VERSTAG "\0$VER: clib4.library 2.1-ab40f98 (28.05.2026)"

library/dos.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ struct _wchar {
139139
_mbstate_t _wcsrtombs_state;
140140
};
141141

142+
/*
143+
* Forward declarations for stdio internals (full definitions in stdio_headers.h).
144+
* Only pointer types are used in _clib4, so forward declarations are sufficient.
145+
*/
146+
struct iob;
147+
struct _glue;
148+
142149
/*
143150
* Initial _clib4 structure. This contains all fields used by current progream
144151
*/
@@ -589,12 +596,14 @@ struct _clib4 {
589596
int __stdio_initialized; /* Non-zero after __sinit() has run */
590597

591598
/*
592-
* Per-process glue-list root for stream allocation.
593-
* Replaces the global __sglue which is shared across all processes
594-
* in the shared library — using a global caused child processes
595-
* (via spawnvpe) to corrupt the parent's stdin/stdout/stderr buffers.
599+
* Per-process stdio streams and glue-list root.
600+
* Replaces the shared globals __sf[3] and __sglue from findfp.c.
601+
* Each process that opens clib4.library gets its own iob structs and
602+
* glue root, preventing child processes from corrupting the parent's
603+
* stdin/stdout/stderr buffers.
596604
*/
597-
void *__sglue_root; /* per-process root _glue node (struct _glue *) */
605+
struct iob *__sf[3]; /* per-process stdin/stdout/stderr iob pointers */
606+
struct _glue *__sglue; /* per-process root glue node for FILE slots */
598607
};
599608

600609
#ifndef __getClib4

library/misc/children.c

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,89 @@
1313
#include "clib4.h"
1414
#include "children.h"
1515

16+
void
17+
import_inherited_fds_from_spec(struct _clib4 *__clib4, const char *spec) {
18+
char *copy;
19+
char *entry;
20+
21+
if (spec == NULL || spec[0] == '\0')
22+
return;
23+
24+
copy = strdup(spec);
25+
if (copy == NULL)
26+
return;
27+
28+
entry = strtok(copy, ";");
29+
while (entry != NULL) {
30+
char *fd_str = entry;
31+
char *flags_str = strchr(fd_str, ':');
32+
33+
if (flags_str != NULL) {
34+
*flags_str++ = '\0';
35+
char *handle_str = strchr(flags_str, ':');
36+
if (handle_str != NULL) {
37+
*handle_str++ = '\0';
38+
39+
int fd_num = (int) strtol(fd_str, NULL, 10);
40+
ULONG flags = (ULONG) strtoul(flags_str, NULL, 10);
41+
BPTR inherited_handle = BZERO;
42+
BOOL no_close = FALSE;
43+
44+
/*
45+
* Symbolic tokens: the parent detected that this fd shares
46+
* the same underlying pipe handle as fhin/fhout/fherr.
47+
* AmigaOS4's PIPE: device does not properly share the data
48+
* stream with DupFileHandle copies, so we map these fds to
49+
* the process-level Input()/Output()/ErrorOutput() handles
50+
* which are guaranteed to be connected to the right pipe.
51+
* Mark FDF_NO_CLOSE_BPTR so clib4 doesn't close these
52+
* process-managed handles.
53+
*/
54+
if (strcmp(handle_str, "STDIN") == 0) {
55+
inherited_handle = Input();
56+
no_close = TRUE;
57+
} else if (strcmp(handle_str, "STDOUT") == 0) {
58+
inherited_handle = Output();
59+
no_close = TRUE;
60+
} else if (strcmp(handle_str, "STDERR") == 0) {
61+
inherited_handle = ErrorOutput();
62+
no_close = TRUE;
63+
} else {
64+
inherited_handle = (BPTR) strtoul(handle_str, NULL, 16);
65+
}
66+
67+
if (fd_num > STDERR_FILENO && inherited_handle != BZERO) {
68+
if (fd_num >= __clib4->__num_fd) {
69+
if (__grow_fd_table(__clib4, fd_num + 1) < 0) {
70+
entry = strtok(NULL, ";");
71+
continue;
72+
}
73+
}
74+
75+
APTR lock = __create_mutex();
76+
if (lock != NULL) {
77+
ULONG inherited_flags = flags;
78+
CLEAR_FLAG(inherited_flags, FDF_CLOEXEC);
79+
SET_FLAG(inherited_flags, FDF_IN_USE);
80+
if (no_close)
81+
SET_FLAG(inherited_flags, FDF_NO_CLOSE_BPTR);
82+
83+
__initialize_fd(__clib4->__fd[fd_num],
84+
__fd_hook_entry,
85+
inherited_handle,
86+
inherited_flags,
87+
lock);
88+
}
89+
}
90+
}
91+
}
92+
93+
entry = strtok(NULL, ";");
94+
}
95+
96+
free(copy);
97+
}
98+
1699
static void *
17100
gidChildrenScan(const void *children, void *gid) {
18101
const struct Clib4Children *myChildren = children;
@@ -41,7 +124,7 @@ pipeChildrenScan(const void *children, void *pipe) {
41124
}
42125

43126
BOOL
44-
insertSpawnedChildren(uint32 pid, uint32 gid, const char *parentUuid) {
127+
insertSpawnedChildren(uint32 pid, uint32 gid, const char *parentUuid, const char *fdInherit) {
45128
DECLARE_UTILITYBASE();
46129

47130
struct Clib4Resource *res = (APTR) OpenResource(RESOURCE_NAME);
@@ -50,6 +133,9 @@ insertSpawnedChildren(uint32 pid, uint32 gid, const char *parentUuid) {
50133
children.pid = pid;
51134
children.returnCode = 0x10000000; //set this flag for WIFEXITED
52135
children.groupId = gid;
136+
children.pipe = NULL;
137+
/* take direct ownership of the heap-allocated spec; caller must set its ptr to NULL */
138+
children.fdInherit = (fdInherit != NULL && fdInherit[0] != '\0') ? (char *)fdInherit : NULL;
53139

54140
/* Use direct hashmap_get by uuid — avoids hashmap_iter race condition */
55141
struct Clib4Node nodeKey;
@@ -154,16 +240,46 @@ spawnedProcessEnter(int32 entry_data) {
154240
struct Task *parentTask = data->parentTask;
155241

156242
uint32 pid = GetPID(0, GPID_PROCESS);
157-
if (insertSpawnedChildren(pid, groupId, data->parentUuid)) {
243+
/* fdInherit ownership transferred into Clib4Children; consumed later in libOpen */
244+
if (insertSpawnedChildren(pid, groupId, data->parentUuid, data->fdInherit)) {
245+
data->fdInherit = NULL; /* ownership transferred */
158246
__CLIB4->__children++;
159247
D(("Children with pid %ld and gid %ld inserted into list\n", pid, groupId));
160248
}
161249
else {
162250
D(("Cannot insert children with pid %ld and gid %ld into list\n", pid, groupId));
251+
/* insertion failed: free the spec that was not transferred */
252+
free(data->fdInherit);
253+
data->fdInherit = NULL;
163254
}
255+
free(data);
164256
Signal(parentTask, SIGF_CHILD);
165257
}
166258

259+
void
260+
import_pending_fds_for_process(struct _clib4 *__clib4, uint32 pid, uint32 ppid) {
261+
struct Clib4Resource *res = (APTR) OpenResource(RESOURCE_NAME);
262+
if (!res) return;
263+
264+
size_t iter = 0;
265+
void *item;
266+
while (hashmap_iter(res->children, &iter, &item)) {
267+
struct Clib4Node *node = item;
268+
if (node->pid == ppid) {
269+
struct Clib4Children key;
270+
memset(&key, 0, sizeof(key));
271+
key.pid = pid;
272+
struct Clib4Children *ce = (struct Clib4Children *) hashmap_get(node->spawnedProcesses, &key);
273+
if (ce != NULL && ce->fdInherit != NULL) {
274+
import_inherited_fds_from_spec(__clib4, ce->fdInherit);
275+
free(ce->fdInherit);
276+
ce->fdInherit = NULL;
277+
}
278+
break;
279+
}
280+
}
281+
}
282+
167283
void
168284
spawnedProcessExit(int32 rc, int32 data UNUSED) {
169285
struct Clib4Resource *res = (APTR) OpenResource(RESOURCE_NAME);

library/misc/children.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@
77

88
#include "clib4.h"
99

10-
BOOL insertSpawnedChildren(uint32 pid, uint32 gid, const char *parentUuid);
10+
BOOL insertSpawnedChildren(uint32 pid, uint32 gid, const char *parentUuid, const char *fdInherit);
1111
struct Clib4Children *findSpawnedChildrenByPid(uint32 pid);
1212
struct Clib4Children *findSpawnedChildrenByGid(uint32 pid, uint32 gid);
1313
void addSpawnedChildrenPipeHandle(uint32 pid, FILE *pipe);
1414
pid_t findSpawnedChildrenPidByPipe(FILE *pipe);
1515
void spawnedProcessExit(int32 rc, int32 data UNUSED);
1616
void spawnedProcessEnter(int32 entry_data);
17+
void import_inherited_fds_from_spec(struct _clib4 *__clib4, const char *spec);
18+
void import_pending_fds_for_process(struct _clib4 *__clib4, uint32 pid, uint32 ppid);
1719

1820
struct spawnData {
1921
gid_t groupId;
2022
struct Task *parentTask;
2123
char parentUuid[UUID4_LEN + 1];
24+
char *fdInherit;
2225
};
2326

2427
#endif

library/shared_library/clib4.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,16 @@ struct Clib4Library *libOpen(struct LibraryManagerInterface *Self, uint32 versio
415415
struct Process *_me = (struct Process *) IExec->FindTask(NULL);
416416
if (_me->pr_Task.tc_Node.ln_Type == NT_PROCESS && _me->pr_UID != 0) {
417417
struct _clib4 *existing = (struct _clib4 *) _me->pr_UID;
418-
if (existing->__fully_initialized) {
418+
/*
419+
* Only reuse an existing context if it actually belongs to THIS
420+
* process. With NP_Child TRUE, a spawned child inherits the
421+
* parent's pr_UID (pointing to the parent's fully-initialised
422+
* _clib4). We must NOT take the early-return for a new child
423+
* process — it needs its own context so that
424+
* import_pending_fds_for_process() can run and wire up the
425+
* inherited file descriptors.
426+
*/
427+
if (existing->__fully_initialized && existing->self == _me) {
419428
D(bug("(libOpen) Process already has a valid _clib4 (%p) — reusing it\n", existing));
420429
existing->__lib_open_count++;
421430
if (IExpansion != NULL) {
@@ -560,6 +569,14 @@ struct Clib4Library *libOpen(struct LibraryManagerInterface *Self, uint32 versio
560569
_start_ctors(__CTOR_LIST__);
561570
SHOWMSG("Done. All constructors called");
562571

572+
/* Import any file descriptors inherited from the parent process.
573+
* Must be done AFTER _start_ctors (which runs stdio_file_init and
574+
* sets up __fd[0..2]) so that __clib4 is the child's own context. */
575+
{
576+
extern void import_pending_fds_for_process(struct _clib4 *__clib4, uint32 pid, uint32 ppid);
577+
import_pending_fds_for_process(__clib4, pid, ppid);
578+
}
579+
563580
/* Copy environment variables into clib4 reent structure */
564581
SHOWMSG("Make environment");
565582
makeEnvironment(__clib4);

library/shared_library/clib4.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ struct Clib4Children {
7474
gid_t groupId; /* Group ID of process */
7575
uint32 returnCode; /* the return code of process */
7676
FILE *pipe;
77+
char *fdInherit; /* heap-allocated fd inherit spec, consumed by child libOpen */
7778
};
7879

7980
int libReserved(void);

0 commit comments

Comments
 (0)