Skip to content

Commit f21a892

Browse files
committed
updates to the environment, process, window library tests on linux
1 parent e571ded commit f21a892

7 files changed

Lines changed: 184 additions & 73 deletions

File tree

lang/libs/environment/src/environment.ch

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ using std::vector;
1111

1212
// POSIX: the environ variable (declared in env_os.ch on Windows)
1313
comptime if(!def.windows) {
14-
@extern var environ : **char
14+
// Use get_environ() rather than a direct `environ`/`__environ` import:
15+
// in a non-PIE ELF the public `environ` symbol gets a copy relocation
16+
// that leaves it NULL, while get_environ() returns the real pointer.
1517
}
1618

1719
// ---------------------------------------------------------------------------
@@ -147,12 +149,12 @@ public func all() : vector<string> {
147149
}
148150
FreeEnvironmentStringsA(env_ptr)
149151
} else {
150-
var ep = environ
152+
var ep = get_environ()
151153
if(ep == null) { return result }
152-
var i : int = 0
154+
var i : int = 0;
153155
while(ep[i] != null) {
154-
result.push(string.make_no_len(ep[i]))
155-
i += 1
156+
result.push(string.make_no_len(ep[i] as *char))
157+
i += 1;
156158
}
157159
}
158160
return result
@@ -163,7 +165,9 @@ public func temp_dir() : Option<string> {
163165
comptime if(def.windows) {
164166
return get("TEMP");
165167
} else {
166-
return get("TMPDIR");
168+
var t = get("TMPDIR");
169+
if(t is Option.Some) { return t; }
170+
return Option.Some(string("/tmp"));
167171
}
168172
}
169173

lang/libs/process/posix/posix.ch

Lines changed: 139 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,16 @@ public func posix_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool
6868
close(stdin_pipe_tmp[0])
6969
} else {}
7070
var argv = build_argv(&raw mut cfg.args);
71-
// If env vars are provided, use execve() with custom environment.
71+
// Resolve the program via PATH (execve doesn't search PATH, unlike
72+
// execvp). When env vars are provided we must use execve to replace
73+
// the environment entirely; otherwise execvp is fine.
74+
unsafe var resolved : [4096]char;
75+
var prog = lookup_program(argv.ptrs[0], &raw mut resolved[0], 4096);
7276
if(cfg.env.size() > 0) {
7377
var envp = build_envp(&raw mut cfg.env);
74-
execve(argv.ptrs[0], &raw argv.ptrs[0], &raw envp.ptrs[0]);
78+
execve(prog, &raw argv.ptrs[0], &raw envp.ptrs[0]);
7579
} else {
76-
execvp(argv.ptrs[0], &raw argv.ptrs[0]);
80+
execvp(prog, &raw argv.ptrs[0]);
7781
}
7882
_exit(1);
7983
} else {}
@@ -85,55 +89,44 @@ public func posix_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool
8589
var stdout_data = vector<u8>();
8690
var stderr_data = vector<u8>();
8791

88-
if(cfg.capture_stdout) {
89-
if(!read_all_fd(stdout_pipe[0], &raw mut stdout_data)) {
90-
close(stdout_pipe[0]);
91-
if(cfg.capture_stderr && !cfg.merge_stdout_stderr) { close(stderr_pipe[0]); } else {}
92-
return false
93-
} else {}
94-
close(stdout_pipe[0]);
95-
} else {}
96-
// When merge_stdout_stderr is true, stderr was redirected to stdout_pipe
97-
// (or stderr_pipe), so we only need one read.
98-
if(cfg.capture_stderr && !cfg.merge_stdout_stderr) {
99-
if(!read_all_fd(stderr_pipe[0], &raw mut stderr_data)) {
100-
close(stderr_pipe[0]);
101-
return false
102-
} else {}
103-
close(stderr_pipe[0]);
104-
} else if(cfg.merge_stdout_stderr && !cfg.capture_stdout) {
105-
// merge without capture_stdout: read from stderr_pipe[0] as merged
106-
if(!read_all_fd(stderr_pipe[0], &raw mut stdout_data)) {
107-
close(stderr_pipe[0]);
108-
return false
109-
} else {}
110-
close(stderr_pipe[0]);
111-
} else {}
92+
// Read from the pipes in a non-blocking fashion so we can also poll the
93+
// child and enforce the timeout (otherwise a long-running child would
94+
// block the read forever and the timeout could never fire).
95+
if(cfg.capture_stdout) { set_nonblock(stdout_pipe[0]); } else {}
96+
if(cfg.capture_stderr && !cfg.merge_stdout_stderr) { set_nonblock(stderr_pipe[0]); } else {}
97+
if(cfg.merge_stdout_stderr && !cfg.capture_stdout) { set_nonblock(stderr_pipe[0]); } else {}
11298

11399
var status : int = 0;
114100
var timed_out = false
115-
if(cfg.timeout_ms > 0) {
116-
// Poll with timeout: sleep in 10ms increments
117-
var elapsed : int = 0
118-
while(elapsed < cfg.timeout_ms) {
119-
var ret = waitpid(pid, &raw mut status, 1) // WNOHANG
120-
if(ret != 0) { break }
101+
var elapsed : int = 0;
102+
var done = false;
103+
while(!done) {
104+
if(cfg.capture_stdout) { read_available(stdout_pipe[0], &raw mut stdout_data); } else {}
105+
if(cfg.capture_stderr && !cfg.merge_stdout_stderr) { read_available(stderr_pipe[0], &raw mut stderr_data); } else {}
106+
if(cfg.merge_stdout_stderr && !cfg.capture_stdout) { read_available(stderr_pipe[0], &raw mut stdout_data); } else {}
107+
108+
var ret = waitpid(pid, &raw mut status, 1) // WNOHANG
109+
if(ret != 0) {
110+
done = true
111+
} else if(cfg.timeout_ms > 0) {
121112
usleep(10000) // 10ms
122113
elapsed += 10
123-
}
124-
if(elapsed >= cfg.timeout_ms) {
125-
// Check one more time
126-
var ret = waitpid(pid, &raw mut status, 1)
127-
if(ret == 0) {
114+
if(elapsed >= cfg.timeout_ms) {
128115
timed_out = true
129116
kill(pid, 9) // SIGKILL
130117
waitpid(pid, &raw mut status, 0)
118+
done = true
131119
}
120+
} else {
121+
usleep(5000)
132122
}
133-
} else {
134-
waitpid(pid, &raw mut status, 0)
135123
}
136124

125+
// Final drain now that the child is dead (pipe yields remaining data then EOF).
126+
if(cfg.capture_stdout) { read_available(stdout_pipe[0], &raw mut stdout_data); close(stdout_pipe[0]); } else {}
127+
if(cfg.capture_stderr && !cfg.merge_stdout_stderr) { read_available(stderr_pipe[0], &raw mut stderr_data); close(stderr_pipe[0]); } else {}
128+
if(cfg.merge_stdout_stderr && !cfg.capture_stdout) { read_available(stderr_pipe[0], &raw mut stdout_data); close(stderr_pipe[0]); } else {}
129+
137130
var exit_code : int = 0;
138131
var signaled : bool = false;
139132
var signal_no : int = 0;
@@ -150,6 +143,34 @@ public func posix_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool
150143
return true
151144
}
152145

146+
// Make a file descriptor non-blocking so reads don't stall the timeout loop.
147+
func set_nonblock(fd : int) {
148+
var flags = fcntl(fd, F_GETFL, 0);
149+
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
150+
}
151+
152+
// Read whatever is currently available from a non-blocking fd, stopping on
153+
// EOF or when no more data is immediately readable (EAGAIN).
154+
func read_available(fd : int, data : *mut vector<u8>) : bool {
155+
unsafe var buf : [4096]u8;
156+
while(true) {
157+
var n = read(fd, &raw mut buf[0], 4096);
158+
if(n > 0) {
159+
var i : size_t = 0;
160+
while(i < n as size_t) {
161+
data.push(buf[i]);
162+
i += 1;
163+
}
164+
} else if(n == 0) {
165+
return true
166+
} else {
167+
if(*__errno_location() == EAGAIN) { return true }
168+
return false
169+
}
170+
}
171+
return false
172+
}
173+
153174
public func posix_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool {
154175
unsafe var stdout_pipe : [2]int;
155176
unsafe var stderr_pipe : [2]int;
@@ -158,7 +179,7 @@ public func posix_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool
158179
if(cfg.capture_stdout) {
159180
if(pipe(&raw mut stdout_pipe[0]) != 0) { return false } else {}
160181
} else {}
161-
if(cfg.capture_stderr) {
182+
if(cfg.capture_stderr || cfg.merge_stdout_stderr) {
162183
if(pipe(&raw mut stderr_pipe[0]) != 0) {
163184
if(cfg.capture_stdout) { close(stdout_pipe[0]); close(stdout_pipe[1]); } else {}
164185
return false
@@ -184,8 +205,29 @@ public func posix_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool
184205
if(cfg.working_dir.size() > 0) {
185206
chdir(cfg.working_dir.data())
186207
} else {}
187-
if(cfg.capture_stdout) { close(stdout_pipe[0]); dup2(stdout_pipe[1], 1); close(stdout_pipe[1]); } else {}
188-
if(cfg.capture_stderr) { close(stderr_pipe[0]); dup2(stderr_pipe[1], 2); close(stderr_pipe[1]); } else {}
208+
if(cfg.capture_stdout) {
209+
close(stdout_pipe[0])
210+
if(cfg.merge_stdout_stderr) {
211+
// Both stdout and stderr go to stdout_pipe[1]
212+
dup2(stdout_pipe[1], 1)
213+
dup2(stdout_pipe[1], 2)
214+
close(stdout_pipe[1])
215+
} else {
216+
dup2(stdout_pipe[1], 1)
217+
close(stdout_pipe[1])
218+
}
219+
} else {}
220+
if(cfg.capture_stderr && !cfg.merge_stdout_stderr) {
221+
close(stderr_pipe[0])
222+
dup2(stderr_pipe[1], 2)
223+
close(stderr_pipe[1])
224+
} else if(cfg.merge_stdout_stderr && !cfg.capture_stdout) {
225+
// merge without capture_stdout: redirect both to stderr_pipe[1]
226+
close(stderr_pipe[0])
227+
dup2(stderr_pipe[1], 1)
228+
dup2(stderr_pipe[1], 2)
229+
close(stderr_pipe[1])
230+
} else {}
189231
// Child reads from stdin_pipe[0]; parent writes to stdin_pipe[1].
190232
close(stdin_pipe[1]); dup2(stdin_pipe[0], 0); close(stdin_pipe[0]);
191233
var argv = build_argv(&raw mut cfg.args);
@@ -199,12 +241,12 @@ public func posix_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool
199241
} else {}
200242

201243
if(cfg.capture_stdout) { close(stdout_pipe[1]); } else {}
202-
if(cfg.capture_stderr) { close(stderr_pipe[1]); } else {}
244+
if(cfg.capture_stderr || cfg.merge_stdout_stderr) { close(stderr_pipe[1]); } else {}
203245
close(stdin_pipe[0]); // parent writes to stdin_pipe[1]
204246

205247
child._unix.pid = pid;
206248
child._unix.stdout_fd = if(cfg.capture_stdout) stdout_pipe[0] else -1;
207-
child._unix.stderr_fd = if(cfg.capture_stderr) stderr_pipe[0] else -1;
249+
child._unix.stderr_fd = if(cfg.capture_stderr) stderr_pipe[0] else if(cfg.merge_stdout_stderr && !cfg.capture_stdout) stderr_pipe[0] else -1;
208250
child._unix.stdin_fd = stdin_pipe[1];
209251
child.is_running = true;
210252
return true
@@ -247,6 +289,7 @@ public func posix_wait(child : *mut ChildProcess, out : *mut ProcessResult) : bo
247289
else if(WIFSIGNALED(status)) { signaled = true; signal_no = WTERMSIG(status); exit_code = -1; } else {}
248290

249291
child.is_running = false;
292+
child._unix.pid = 0;
250293
out.output.stdout_data = stdout_data;
251294
out.output.stderr_data = stderr_data;
252295
out.status.code = exit_code;
@@ -317,6 +360,57 @@ func read_all_fd(fd : int, data : *mut vector<u8>) : bool {
317360
@extern public func usleep(usec : int) : int
318361
@extern public func chdir(path : *char) : int
319362
@extern public func kill(pid : int, sig : int) : int
363+
@extern public func fcntl(fd : int, cmd : int, arg : int) : int
364+
@extern public func __errno_location() : *mut int
365+
@extern public func access(path : *char, mode : int) : int
366+
367+
const F_GETFL = 3
368+
const F_SETFL = 4
369+
const O_NONBLOCK = 2048
370+
const EAGAIN = 11
371+
const X_OK = 1
372+
373+
// Resolve an executable name to a path usable by execve, searching PATH when
374+
// the name doesn't already contain a '/'. `out` is filled with the result and
375+
// its pointer is returned (must remain valid until exec).
376+
func lookup_program(prog : *char, out : *mut char, out_size : size_t) : *mut char {
377+
var i : size_t = 0;
378+
while(prog[i] != 0) {
379+
if(prog[i] == '/') {
380+
var k : size_t = 0;
381+
while(prog[k] != 0 && k < out_size) { out[k] = prog[k]; k += 1 }
382+
if(k < out_size) { out[k] = 0 }
383+
return out
384+
}
385+
i += 1;
386+
}
387+
var path = getenv("PATH");
388+
if(path == null) {
389+
var k : size_t = 0;
390+
while(prog[k] != 0 && k < out_size) { out[k] = prog[k]; k += 1 }
391+
if(k < out_size) { out[k] = 0 }
392+
return out
393+
}
394+
var seg_start : size_t = 0;
395+
while(true) {
396+
var seg_end : size_t = seg_start;
397+
while(path[seg_end] != 0 && path[seg_end] != ':') { seg_end += 1 }
398+
var len : size_t = 0;
399+
var k = seg_start;
400+
while(k < seg_end && len + 1 < out_size) { out[len] = path[k]; len += 1; k += 1 }
401+
if(len < out_size) { out[len] = '/'; len += 1 }
402+
var pi : size_t = 0;
403+
while(prog[pi] != 0 && len < out_size) { out[len] = prog[pi]; len += 1; pi += 1 }
404+
if(len < out_size) { out[len] = 0 }
405+
if(access(out, X_OK) == 0) { return out }
406+
if(path[seg_end] == 0) { break }
407+
seg_start = seg_end + 1;
408+
}
409+
var k : size_t = 0;
410+
while(prog[k] != 0 && k < out_size) { out[k] = prog[k]; k += 1 }
411+
if(k < out_size) { out[k] = 0 }
412+
return out
413+
}
320414

321415
const _WIFEXITED_MASK = 0x7f;
322416
func WIFEXITED(status : int) : bool { return (status & _WIFEXITED_MASK) == 0; }

lang/libs/process/src/process.ch

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ public func wait(child : *mut ChildProcess) : PR_Result {
176176
return std::replace<PR_Result>(&mut ret, zeroed:unsafe<PR_Result>())
177177
} else {}
178178
} else {
179-
if(!child.is_running) {
179+
// Allow wait() after kill(): the process was signalled but still needs
180+
// to be reaped. Only reject once it has already been waited on (pid 0).
181+
if(child._unix.pid == 0) {
180182
var e = ProcessError.NotRunning()
181183
pr_err(e, &mut ret)
182184
return std::replace<PR_Result>(&mut ret, zeroed:unsafe<PR_Result>())
@@ -205,7 +207,7 @@ public func wait(child : *mut ChildProcess) : PR_Result {
205207
}
206208
}
207209

208-
public func kill(child : *mut ChildProcess, signal : int) : UT_Result {
210+
public func kill_process(child : *mut ChildProcess, signal : int) : UT_Result {
209211
var ret = zeroed:unsafe<UT_Result>()
210212
if(!child.is_running) {
211213
var e = ProcessError.NotRunning()
@@ -236,6 +238,12 @@ public func kill(child : *mut ChildProcess, signal : int) : UT_Result {
236238
std::replace(&mut ret, Result.Ok<UnitTy, ProcessError>(UnitTy{}))
237239
return std::replace<UT_Result>(&mut ret, zeroed:unsafe<UT_Result>())
238240
} else {
241+
var r = kill(child._unix.pid, signal);
242+
if(r != 0) {
243+
var e = ProcessError.OperationFailed(string("kill failed"))
244+
std::replace(&mut ret, Result.Err<UnitTy, ProcessError>(std::replace<ProcessError>(&mut e, ProcessError.NotRunning())))
245+
return std::replace<UT_Result>(&mut ret, zeroed:unsafe<UT_Result>())
246+
} else {}
239247
child.is_running = false
240248
std::replace(&mut ret, Result.Ok<UnitTy, ProcessError>(UnitTy{}))
241249
return std::replace<UT_Result>(&mut ret, zeroed:unsafe<UT_Result>())

lang/libs/window/posix/linux.ch

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
public namespace window {
1111

1212
using std::string;
13+
using std::Option;
14+
using std::string_view;
1315

1416
// 32-bit unsigned, matching GDK's guint32 event fields (cstd only defines
1517
// DWORD on Windows; the Windows backend maps it to ulong there).
@@ -162,6 +164,7 @@ const GDK_ACTION_COPY = 2
162164
// ===========================================================================
163165

164166
@extern public func gtk_init(argc : *mut int, argv : *mut *mut *char) : int
167+
@extern public func gtk_init_check(argc : *mut int, argv : *mut *mut *char) : int
165168
@extern public func gtk_main()
166169
@extern public func gtk_main_quit()
167170

@@ -879,8 +882,8 @@ public func window_restore(w : *mut Window) {
879882
public func window_show(w : *mut Window) {
880883
if(w.widget != null) {
881884
gtk_widget_show_all(w.widget)
882-
w.visible = true
883885
}
886+
w.visible = true
884887
}
885888

886889
public func window_hide(w : *mut Window) {
@@ -1064,10 +1067,24 @@ public func window_post_empty_event() {
10641067
@extern public func gtk_clipboard_set_text(clipboard : *mut void, text : *char, len : int) : void
10651068
@extern public func gtk_clipboard_wait_for_text(clipboard : *mut void) : *char
10661069
@extern public func gtk_selection_data_get_text(data : *mut void) : *char
1067-
@extern public func g_free(mem : *mut void)
1070+
1071+
/// Lazily initialize GTK exactly once. The toolkit must be initialized before
1072+
/// any clipboard call; doing it here (rather than relying on the caller having
1073+
/// created a window first) keeps the clipboard API self-contained while only
1074+
/// paying the init cost a single time per process.
1075+
unsafe var g_gtk_initialized : bool = false
1076+
func ensure_gtk_init() {
1077+
if(!g_gtk_initialized) {
1078+
var argc : int = 0
1079+
var argv : *mut *mut *char = null
1080+
gtk_init_check(&raw mut argc, argv)
1081+
g_gtk_initialized = true
1082+
}
1083+
}
10681084

10691085
/// Get the current clipboard text content.
10701086
public func window_get_clipboard() : Option<string> {
1087+
ensure_gtk_init()
10711088
// GDK_SELECTION_CLIPBOARD = gdk_atom_intern("CLIPBOARD", 1)
10721089
// We use a simpler approach: get the default clipboard
10731090
var sel = gdk_atom_intern("CLIPBOARD", 1)
@@ -1082,6 +1099,7 @@ public func window_get_clipboard() : Option<string> {
10821099

10831100
/// Set the clipboard text content.
10841101
public func window_set_clipboard(text : string_view) : bool {
1102+
ensure_gtk_init()
10851103
var sel = gdk_atom_intern("CLIPBOARD", 1)
10861104
var cb = gtk_clipboard_get(sel)
10871105
if(cb == null) { return false }

0 commit comments

Comments
 (0)