Import Upstream version 20180207
[hcoop/debian/mlton.git] / lib / mlton / basic / process.sml
CommitLineData
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
9structure Process: PROCESS =
10struct
11
12local
13 open Trace.Immediate
14in
15 val messageStr = messageStr
16end
17
18fun 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
27structure Command =
28 struct
29 type t = In.t * Out.t -> unit
30
31 fun layout _ = Layout.str "<command>"
32 end
33
34type command = Command.t
35
36structure Pid = Pid
37
38structure 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
56structure Status =
57 struct
58 type t = OS.Process.status
59 end
60
61fun succeed (): 'a =
62 let open OS.Process
63 in exit success
64 end
65
66val 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 *)
71val succeed: unit -> 'a = fn () => (succeed (); Error.bug "Process.succeed")
72
73fun 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
81val fork = Trace.trace ("Process.fork", Command.layout, Pid.layout) fork
82
83fun closes l = List.foreach (l, FileDesc.close)
84
85val pname = "<process>"
86
87fun 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
98fun 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
109fun 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
123fun 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
136val wait = Trace.trace ("Process.wait", Pid.layout, Unit.layout) wait
137
138val run = wait o fork
139
140(* doubleFork avoids creating zombies. *)
141fun doubleFork (c: unit -> unit): unit =
142 run (fn () => ignore (fork c))
143
144structure 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
158fun 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
175fun 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
195fun pipe' cs = pipe (cs, In.standard, Out.standard)
196
197fun 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
216val exec =
217 Trace.trace4 ("Process.exec", String.layout, List.layout String.layout,
218 In.layout, Out.layout, Unit.layout)
219 exec
220
221fun call (c, a) (ins, out) = run (fn () => exec (c, a, ins, out))
222
223fun call' ca = call ca (In.standard, Out.standard)
224
225fun 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
230fun doesSucceed c = (run c; true) handle Fail _ => false
231
232val doesSucceed =
233 Trace.trace ("Process.doesSucceed", Function.layout, Bool.layout)
234 doesSucceed
235
236fun 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
252fun makeMain z (): unit =
253 OS.Process.exit (makeCommandLine z (CommandLine.arguments ()))
254
255fun basename s = #file (OS.Path.splitDirFile s)
256
257val commandName = Promise.lazy (fn () => basename (CommandLine.name ()))
258
259local open Posix.SysDB Posix.ProcEnv
260in
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 ()))
268end
269
270fun fail x = raise Fail x
271
272local
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
278in
279 fun hostName () = lookup "nodename"
280end
281
282val getEnv = Posix.ProcEnv.getenv
283
284fun glob (s: string): string list =
285 String.tokens (collect (call ("bash", ["-c", "ls " ^ s])),
286 fn c => c = #"\n")
287
288fun usage {usage: string, msg: string}: 'a =
289 fail (concat [msg, "\n", "Usage: ", commandName (), " ", usage])
290
291val sleep = Posix.Process.sleep
292
293fun 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
302fun signal (p: Pid.t, s: Signal.t): unit =
303 let open Posix.Process
304 in kill (K_PROC p, s)
305 end
306
307fun signalGroup (p: Pid.t, s: Signal.t): unit =
308 let open Posix.Process
309 in kill (K_GROUP p, s)
310 end
311
312local
313 val delay = Time.fromMilliseconds 1
314 val maxDelay = Time.minutes 1
315in
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
325end
326
327structure 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
350val op / = String./
351
352fun 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
372val 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
381fun 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
391fun 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 *)
405fun 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
431fun 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
440val setEnv = MLton.ProcEnv.setenv
441
442val exec = fn (c, a) => exec (c, a, In.standard, Out.standard)
443
444local
445 open MLton.Process
446in
447 val spawn = spawn
448 val spawne = spawne
449 val spawnp = spawnp
450end
451
452end