Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | (* Copyright (C) 2017 Matthew Fluet. |
2 | * Copyright (C) 1999-2006 Henry Cejtin, Matthew Fluet, Suresh | |
3 | * Jagannathan, and Stephen Weeks. | |
4 | * | |
5 | * MLton is released under a BSD-style license. | |
6 | * See the file MLton-LICENSE for details. | |
7 | *) | |
8 | ||
9 | structure Process: PROCESS = | |
10 | struct | |
11 | ||
12 | local | |
13 | open Trace.Immediate | |
14 | in | |
15 | val messageStr = messageStr | |
16 | end | |
17 | ||
18 | fun system s = | |
19 | let | |
20 | val status = OS.Process.system s | |
21 | in | |
22 | if OS.Process.isSuccess status | |
23 | then () | |
24 | else Error.bug (concat ["Process.system: command failed: ", s]) | |
25 | end | |
26 | ||
27 | structure Command = | |
28 | struct | |
29 | type t = In.t * Out.t -> unit | |
30 | ||
31 | fun layout _ = Layout.str "<command>" | |
32 | end | |
33 | ||
34 | type command = Command.t | |
35 | ||
36 | structure Pid = Pid | |
37 | ||
38 | structure PosixStatus = | |
39 | struct | |
40 | open Posix.Process | |
41 | ||
42 | type t = exit_status | |
43 | ||
44 | fun toString (s: t): string = | |
45 | case s of | |
46 | W_EXITED => "exited" | |
47 | | W_EXITSTATUS w => concat ["exit status ", Word8.toString w] | |
48 | | W_SIGNALED s => concat ["signal ", | |
49 | SysWord.toString (Posix.Signal.toWord s)] | |
50 | | W_STOPPED s => concat ["stop signal ", | |
51 | SysWord.toString (Posix.Signal.toWord s)] | |
52 | ||
53 | val layout = Layout.str o toString | |
54 | end | |
55 | ||
56 | structure Status = | |
57 | struct | |
58 | type t = OS.Process.status | |
59 | end | |
60 | ||
61 | fun succeed (): 'a = | |
62 | let open OS.Process | |
63 | in exit success | |
64 | end | |
65 | ||
66 | val succeed = Trace.trace ("Process.succeed", Unit.layout, Unit.layout) succeed | |
67 | ||
68 | (* This song and dance is so that succeed can have the right type, unit -> 'a, | |
69 | * instead of unit -> unit. | |
70 | *) | |
71 | val succeed: unit -> 'a = fn () => (succeed (); Error.bug "Process.succeed") | |
72 | ||
73 | fun fork (c: unit -> unit): Pid.t = | |
74 | case Posix.Process.fork () of | |
75 | NONE => (Trace.Immediate.inChildProcess () | |
76 | ; let open OS.Process | |
77 | in exit ((c (); success) handle _ => failure) | |
78 | end) | |
79 | | SOME pid => pid | |
80 | ||
81 | val fork = Trace.trace ("Process.fork", Command.layout, Pid.layout) fork | |
82 | ||
83 | fun closes l = List.foreach (l, FileDesc.close) | |
84 | ||
85 | val pname = "<process>" | |
86 | ||
87 | fun forkIn (c: Out.t -> unit): Pid.t * In.t = | |
88 | let | |
89 | val {infd, outfd} = FileDesc.pipe () | |
90 | val pid = fork (fn () => | |
91 | (FileDesc.close infd | |
92 | ; c (MLton.TextIO.newOut (outfd, pname)))) | |
93 | val _ = FileDesc.close outfd | |
94 | in | |
95 | (pid, MLton.TextIO.newIn (infd, pname)) | |
96 | end | |
97 | ||
98 | fun forkOut (c: In.t -> unit): Pid.t * Out.t = | |
99 | let | |
100 | val {infd, outfd} = FileDesc.pipe () | |
101 | val pid = fork (fn () => | |
102 | (FileDesc.close outfd | |
103 | ; c (MLton.TextIO.newIn (infd, pname)))) | |
104 | val _ = FileDesc.close infd | |
105 | in | |
106 | (pid, MLton.TextIO.newOut (outfd, pname)) | |
107 | end | |
108 | ||
109 | fun forkInOut (c: In.t * Out.t -> unit): Pid.t * In.t * Out.t = | |
110 | let | |
111 | val {infd = in1, outfd = out1} = FileDesc.pipe () | |
112 | val {infd = in2, outfd = out2} = FileDesc.pipe () | |
113 | val pid = fork (fn () => | |
114 | (closes [in1, out2] | |
115 | ; c (MLton.TextIO.newIn (in2, pname), | |
116 | MLton.TextIO.newOut (out1, pname)))) | |
117 | val _ = closes [in2, out1] | |
118 | in (pid, | |
119 | MLton.TextIO.newIn (in1, pname), | |
120 | MLton.TextIO.newOut (out2, pname)) | |
121 | end | |
122 | ||
123 | fun wait (p: Pid.t): unit = | |
124 | let val (p', s) = Posix.Process.waitpid (Posix.Process.W_CHILD p, []) | |
125 | in if p <> p' | |
126 | then Error.bug (concat ["Process.wait: expected pid ", | |
127 | Pid.toString p, | |
128 | " but got pid ", | |
129 | Pid.toString p']) | |
130 | else () | |
131 | ; (case s of | |
132 | PosixStatus.W_EXITED => () | |
133 | | _ => raise Fail (concat [PosixStatus.toString s])) | |
134 | end | |
135 | ||
136 | val wait = Trace.trace ("Process.wait", Pid.layout, Unit.layout) wait | |
137 | ||
138 | val run = wait o fork | |
139 | ||
140 | (* doubleFork avoids creating zombies. *) | |
141 | fun doubleFork (c: unit -> unit): unit = | |
142 | run (fn () => ignore (fork c)) | |
143 | ||
144 | structure Posix = | |
145 | struct | |
146 | open Posix | |
147 | structure Process = | |
148 | struct | |
149 | open Process | |
150 | ||
151 | val wait = | |
152 | Trace.trace ("Process.Posix.Process.wait", Unit.layout, | |
153 | Layout.tuple2 (Pid.layout, PosixStatus.layout)) | |
154 | wait | |
155 | end | |
156 | end | |
157 | ||
158 | fun waits (pids: Pid.t list): unit = | |
159 | case pids of | |
160 | [] => () | |
161 | | _ => | |
162 | let | |
163 | val (pid, status) = Posix.Process.wait () | |
164 | val pids = | |
165 | case status of | |
166 | Posix.Process.W_EXITED => | |
167 | List.keepAll (pids, fn p => p <> pid) | |
168 | | _ => Error.bug (concat ["Process.waits: child ", | |
169 | Pid.toString pid, | |
170 | " failed with ", | |
171 | PosixStatus.toString status]) | |
172 | in waits pids | |
173 | end | |
174 | ||
175 | fun pipe (cs: command list, ins: In.t, out: Out.t): unit = | |
176 | let | |
177 | fun loop (cs: command list, | |
178 | ins: In.t, | |
179 | maybeClose, | |
180 | pids: Pid.t list): unit = | |
181 | case cs of | |
182 | [] => () | |
183 | | [c] => let val pid = fork (fn () => c (ins, out)) | |
184 | val _ = maybeClose () | |
185 | in waits (pid :: pids) | |
186 | end | |
187 | | c :: cs => | |
188 | let val (pid, ins) = forkIn (fn out => c (ins, out)) | |
189 | val _ = maybeClose () | |
190 | in loop (cs, ins, fn _ => In.close ins, pid :: pids) | |
191 | end | |
192 | in loop (cs, ins, fn _ => (), []) | |
193 | end | |
194 | ||
195 | fun pipe' cs = pipe (cs, In.standard, Out.standard) | |
196 | ||
197 | fun exec (c: string, a: string list, ins: In.t, out: Out.t): unit = | |
198 | let | |
199 | open FileDesc | |
200 | in | |
201 | if MLton.isMLton | |
202 | then (move {from = MLton.TextIO.inFd ins, | |
203 | to = stdin} | |
204 | ; move {from = MLton.TextIO.outFd out, | |
205 | to = stdout} | |
206 | ; move {from = MLton.TextIO.outFd Out.error, | |
207 | to = stderr}) | |
208 | else () | |
209 | ; (Posix.Process.execp (c, c :: a) | |
210 | handle _ => (Out.output (Out.error, | |
211 | (concat ("unable to exec " | |
212 | :: List.separate (c :: a, " ")))) | |
213 | ; OS.Process.exit OS.Process.failure)) | |
214 | end | |
215 | ||
216 | val exec = | |
217 | Trace.trace4 ("Process.exec", String.layout, List.layout String.layout, | |
218 | In.layout, Out.layout, Unit.layout) | |
219 | exec | |
220 | ||
221 | fun call (c, a) (ins, out) = run (fn () => exec (c, a, ins, out)) | |
222 | ||
223 | fun call' ca = call ca (In.standard, Out.standard) | |
224 | ||
225 | fun collect (c: Command.t): string = | |
226 | let val (pid, ins) = forkIn (fn out => c (In.standard, out)) | |
227 | in In.inputAll ins before (In.close ins; wait pid) | |
228 | end | |
229 | ||
230 | fun doesSucceed c = (run c; true) handle Fail _ => false | |
231 | ||
232 | val doesSucceed = | |
233 | Trace.trace ("Process.doesSucceed", Function.layout, Bool.layout) | |
234 | doesSucceed | |
235 | ||
236 | fun makeCommandLine (commandLine: string list -> unit) args = | |
237 | ((commandLine args; OS.Process.success) | |
238 | handle e => | |
239 | let | |
240 | val out = Out.error | |
241 | in | |
242 | Out.output (out, concat ["unhandled exception: ", Exn.toString e, "\n"]) | |
243 | ; (case Exn.history e of | |
244 | [] => () | |
245 | | l => (Out.output (out, "with history: \n") | |
246 | ; List.foreach | |
247 | (l, fn s => | |
248 | Out.output (out, concat ["\t", s, "\n"])))) | |
249 | ; OS.Process.failure | |
250 | end) | |
251 | ||
252 | fun makeMain z (): unit = | |
253 | OS.Process.exit (makeCommandLine z (CommandLine.arguments ())) | |
254 | ||
255 | fun basename s = #file (OS.Path.splitDirFile s) | |
256 | ||
257 | val commandName = Promise.lazy (fn () => basename (CommandLine.name ())) | |
258 | ||
259 | local open Posix.SysDB Posix.ProcEnv | |
260 | in | |
261 | fun su (name: string): unit = | |
262 | let val p = getpwnam name | |
263 | in setgid (Passwd.gid p) | |
264 | ; setuid (Passwd.uid p) | |
265 | end | |
266 | val su = Trace.trace ("Process.su", String.layout, Unit.layout) su | |
267 | fun userName () = Passwd.name (getpwuid (getuid ())) | |
268 | end | |
269 | ||
270 | fun fail x = raise Fail x | |
271 | ||
272 | local | |
273 | val z = Posix.ProcEnv.uname () | |
274 | fun lookup s = | |
275 | case List.peek (z, fn (s', _) => s = s') of | |
276 | NONE => fail (concat [s, " unknown"]) | |
277 | | SOME (_, s) => s | |
278 | in | |
279 | fun hostName () = lookup "nodename" | |
280 | end | |
281 | ||
282 | val getEnv = Posix.ProcEnv.getenv | |
283 | ||
284 | fun glob (s: string): string list = | |
285 | String.tokens (collect (call ("bash", ["-c", "ls " ^ s])), | |
286 | fn c => c = #"\n") | |
287 | ||
288 | fun usage {usage: string, msg: string}: 'a = | |
289 | fail (concat [msg, "\n", "Usage: ", commandName (), " ", usage]) | |
290 | ||
291 | val sleep = Posix.Process.sleep | |
292 | ||
293 | fun watch (f: unit -> unit) = | |
294 | let | |
295 | fun loop () = | |
296 | wait (fork f) | |
297 | handle _ => (messageStr "watcher noticed child failure" | |
298 | ; loop ()) | |
299 | in loop () | |
300 | end | |
301 | ||
302 | fun signal (p: Pid.t, s: Signal.t): unit = | |
303 | let open Posix.Process | |
304 | in kill (K_PROC p, s) | |
305 | end | |
306 | ||
307 | fun signalGroup (p: Pid.t, s: Signal.t): unit = | |
308 | let open Posix.Process | |
309 | in kill (K_GROUP p, s) | |
310 | end | |
311 | ||
312 | local | |
313 | val delay = Time.fromMilliseconds 1 | |
314 | val maxDelay = Time.minutes 1 | |
315 | in | |
316 | fun try (f: unit -> 'a, msg: string): 'a = | |
317 | let | |
318 | fun loop (delay: Time.t): 'a = | |
319 | if Time.> (delay, maxDelay) | |
320 | then fail msg | |
321 | else (f () handle _ => (ignore (sleep delay) | |
322 | ; loop (Time.+ (delay, delay)))) | |
323 | in loop delay | |
324 | end | |
325 | end | |
326 | ||
327 | structure State = | |
328 | struct | |
329 | datatype t = DiskSleep | Running | Sleeping | Traced | Zombie | |
330 | ||
331 | fun fromString s = | |
332 | case s of | |
333 | "D" => SOME DiskSleep | |
334 | | "R" => SOME Running | |
335 | | "S" => SOME Sleeping | |
336 | | "T" => SOME Traced | |
337 | | "Z" => SOME Zombie | |
338 | | _ => NONE | |
339 | ||
340 | val toString = | |
341 | fn DiskSleep => "DiskSleep" | |
342 | | Running => "Running" | |
343 | | Sleeping => "Sleeping" | |
344 | | Traced => "Traced" | |
345 | | Zombie => "Zombie" | |
346 | ||
347 | val layout = Layout.str o toString | |
348 | end | |
349 | ||
350 | val op / = String./ | |
351 | ||
352 | fun ps () = | |
353 | Dir.inDir | |
354 | ("/proc", fn () => | |
355 | List.fold | |
356 | (Dir.lsDirs ".", [], fn (d, ac) => | |
357 | case Pid.fromString d of | |
358 | NONE => ac | |
359 | | SOME pid => | |
360 | case String.tokens (hd (File.lines ("/proc"/d/"stat")), | |
361 | Char.isSpace) of | |
362 | _ :: name :: state :: ppid :: pgrp :: _ => | |
363 | {(* drop the ( ) around the name *) | |
364 | name = String.substring (name, 1, String.size name - 2), | |
365 | pgrp = valOf (Pid.fromString pgrp), | |
366 | pid = pid, | |
367 | ppid = valOf (Pid.fromString ppid), | |
368 | state = valOf (State.fromString state) | |
369 | } :: ac | |
370 | | _ => fail "ps")) | |
371 | ||
372 | val ps = | |
373 | Trace.trace | |
374 | ("Process.ps", Unit.layout, | |
375 | List.layout (fn {name, pid, state, ...} => | |
376 | Layout.record [("pid", Pid.layout pid), | |
377 | ("name", String.layout name), | |
378 | ("state", State.layout state)])) | |
379 | ps | |
380 | ||
381 | fun callWithIn (name, args, f: In.t -> 'a) = | |
382 | let | |
383 | val pid = Unix.execute (name, args) | |
384 | val ins = Unix.textInstreamOf pid | |
385 | in | |
386 | Exn.finally | |
387 | (fn () => f ins, | |
388 | fn () => ignore (Unix.reap pid)) | |
389 | end | |
390 | ||
391 | fun callWithOut (name, args, f: Out.t -> 'a) = | |
392 | let | |
393 | val pid = Unix.execute (name, args) | |
394 | val out = Unix.textOutstreamOf pid | |
395 | in | |
396 | Exn.finally | |
397 | (fn () => f out, | |
398 | fn () => ignore (Unix.reap pid)) | |
399 | end | |
400 | ||
401 | (* | |
402 | * text data bss dec hex filename | |
403 | * 3272995 818052 24120 4115167 3ecadf mlton | |
404 | *) | |
405 | fun size (f: File.t): {text: int, data: int, bss: int} = | |
406 | let | |
407 | val fail = fn () => fail (concat ["size failed on ", f]) | |
408 | in | |
409 | File.withTemp | |
410 | (fn sizeRes => | |
411 | let | |
412 | val _ = OS.Process.system (concat ["size ", f, ">", sizeRes]) | |
413 | in | |
414 | File.withIn | |
415 | (sizeRes, fn ins => | |
416 | case In.lines ins of | |
417 | [_, nums] => | |
418 | (case String.tokens (nums, Char.isSpace) of | |
419 | text :: data :: bss :: _ => | |
420 | (case (Int.fromString text, | |
421 | Int.fromString data, | |
422 | Int.fromString bss) of | |
423 | (SOME text, SOME data, SOME bss) => | |
424 | {text = text, data = data, bss = bss} | |
425 | | _ => fail ()) | |
426 | | _ => fail ()) | |
427 | | _ => fail ()) | |
428 | end) | |
429 | end | |
430 | ||
431 | fun time (f: unit -> unit) = | |
432 | let | |
433 | val {children = {utime = u, stime = s}, ...} = Time.times () | |
434 | val _ = f () | |
435 | val {children = {utime = u', stime = s'}, ...} = Time.times () | |
436 | in | |
437 | {system = Time.- (s', s), user = Time.- (u', u)} | |
438 | end | |
439 | ||
440 | val setEnv = MLton.ProcEnv.setenv | |
441 | ||
442 | val exec = fn (c, a) => exec (c, a, In.standard, Out.standard) | |
443 | ||
444 | local | |
445 | open MLton.Process | |
446 | in | |
447 | val spawn = spawn | |
448 | val spawne = spawne | |
449 | val spawnp = spawnp | |
450 | end | |
451 | ||
452 | end |