Ocaml/FAQ/Process Management and Communication
Материал из Wiki.crossplatform.ru
Версия от 20:49, 24 ноября 2010; ViGOur (Обсуждение | вклад)
[править] 16. Process Management and Communication
[править] Gathering Output from a Program
(* Process support is mostly in the "unix" library. *) #load "unix.cma";; (* Run a command and return its results as a string. *) let read_process command = let buffer_size = 2048 in let buffer = Buffer.create buffer_size in let string = String.create buffer_size in let in_channel = Unix.open_process_in command in let chars_read = ref 1 in while !chars_read <> 0 do chars_read := input in_channel string 0 buffer_size; Buffer.add_substring buffer string 0 !chars_read done; ignore (Unix.close_process_in in_channel); Buffer.contents buffer (* Run a command and return its results as a list of strings, one per line. *) let read_process_lines command = let lines = ref [] in let in_channel = Unix.open_process_in command in begin try while true do lines := input_line in_channel :: !lines done; with End_of_file -> ignore (Unix.close_process_in in_channel) end; List.rev !lines (* Example: *) let output_string = read_process "program args" let output_lines = read_process_lines "program args" (*-----------------------------*) (* Create a pipe for the subprocess output. *) let readme, writeme = Unix.pipe () (* Launch the program, redirecting its stdout to the pipe. By calling Unix.create_process, we can avoid running the command through the shell. *) let () = let pid = Unix.create_process program [| program; arg1; arg2 |] Unix.stdin writeme Unix.stderr in Unix.close writeme; let in_channel = Unix.in_channel_of_descr readme in let lines = ref [] in begin try while true do lines := input_line in_channel :: !lines done with End_of_file -> () end; Unix.close readme; List.iter print_endline (List.rev !lines)
[править] Running Another Program
(* Run a simple command and retrieve its result code. *) let status = Sys.command ("vi " ^ myfile) (*-----------------------------*) (* Use the shell to perform redirection. *) let _ = Sys.command "cmd1 args | cmd2 | cmd3 >outfile" let _ = Sys.command "cmd args <infile >outfile 2>errfile" (*-----------------------------*) (* Run a command, handling its result code or signal. *) #load "unix.cma";; let () = match Unix.system command with | Unix.WEXITED status -> Printf.printf "program exited with status %d\n" status | Unix.WSIGNALED signal -> Printf.printf "program killed by signal %d\n" signal | Unix.WSTOPPED signal -> Printf.printf "program stopped by signal %d\n" signal (*-----------------------------*) (* Run a command while blocking interrupt signals. *) #load "unix.cma";; let () = match Unix.fork () with | 0 -> (* child ignores INT and does its thing *) Sys.set_signal Sys.sigint Sys.Signal_ignore; Unix.execv "/bin/sleep" [| "/bin/sleep"; "10" |] | pid -> (* parent catches INT and berates user *) Sys.set_signal Sys.sigint (Sys.Signal_handle (fun _ -> print_endline "Tsk tsk, no process interruptus")); let running = ref true in while !running do try (ignore (Unix.waitpid [] pid); running := false) with Unix.Unix_error _ -> () done; Sys.set_signal Sys.sigint Sys.Signal_default (*-----------------------------*) (* Run a command with a different name in the process table. *) #load "unix.cma";; let shell = "/bin/tcsh" let () = match Unix.fork () with | 0 -> Unix.execv shell [| "-csh" |] (* pretend it's a login shell *) | pid -> ignore (Unix.waitpid [] pid)
[править] Replacing the Current Program with a Different One
#load "unix.cma";; (* Transfer control to the shell to run another program. *) let () = Unix.execv "/bin/sh" [| "/bin/sh"; "-c"; "archive *.data" |] (* Transfer control directly to another program in the path. *) let () = Unix.execvp "archive" [| "archive"; "accounting.data" |]
[править] Reading or Writing to Another Program
#load "unix.cma";; (*-----------------------------*) (* Handle each line in the output of a process. *) let () = let readme = Unix.open_process_in "program arguments" in let rec loop line = (* ... *) loop (input_line readme) in try loop (input_line readme) with End_of_file -> ignore (Unix.close_process_in readme) (*-----------------------------*) (* Write to the input of a process. *) let () = let writeme = Unix.open_process_out "program arguments" in output_string writeme "data\n"; ignore (Unix.close_process_out writeme) (*-----------------------------*) (* Wait for a process to complete. *) let () = (* child goes to sleep *) let f = Unix.open_process_in "sleep 100000" in (* and parent goes to lala land *) ignore (Unix.close_process_in f); ignore (Unix.wait ()) (*-----------------------------*) let () = let writeme = Unix.open_process_out "program args" in (* program will get hello\n on STDIN *) output_string writeme "hello\n"; (* program will get EOF on STDIN *) ignore (Unix.close_process_out writeme) (*-----------------------------*) (* Redirect standard output to the pager. *) let () = let pager = try Sys.getenv "PAGER" (* XXX: might not exist *) with Not_found -> "/usr/bin/less" in let reader, writer = Unix.pipe () in match Unix.fork () with | 0 -> Unix.close writer; Unix.dup2 reader Unix.stdin; Unix.close reader; Unix.execvp pager [| pager |] | pid -> Unix.close reader; Unix.dup2 writer Unix.stdout; Unix.close writer (* Do something useful that writes to standard output, then close the stream and wait for the pager to finish. *) let () = (* ... *) close_out stdout; ignore (Unix.wait ())
[править] Filtering Your Own Output
#load "unix.cma";; (* Fork a process that calls f to post-process standard output. *) let push_output_filter f = let reader, writer = Unix.pipe () in match Unix.fork () with | 0 -> Unix.close writer; Unix.dup2 reader Unix.stdin; Unix.close reader; f (); exit 0 | pid -> Unix.close reader; Unix.dup2 writer Unix.stdout; Unix.close writer (* Only display a certain number of lines of output. *) let head ?(lines=20) () = push_output_filter (fun () -> let lines = ref lines in try while !lines > 0 do print_endline (read_line ()); decr lines done with End_of_file -> ()) (* Prepend line numbers to each line of output. *) let number () = push_output_filter (fun () -> let line_number = ref 0 in try while true do let line = read_line () in incr line_number; Printf.printf "%d: %s\n" !line_number line done with End_of_file -> ()) (* Prepend "> " to each line of output. *) let quote () = push_output_filter (fun () -> try while true do let line = read_line () in Printf.printf "> %s\n" line done with End_of_file -> ()) let () = head ~lines:100 (); (* push head filter on STDOUT *) number (); (* push number filter on STDOUT *) quote (); (* push quote filter on STDOUT *) (* act like /bin/cat *) begin try while true do print_endline (read_line ()) done with End_of_file -> () end; (* tell kids we're done--politely *) close_out stdout; ignore (Unix.waitpid [] (-1)); exit 0
[править] Preprocessing Input
#load "unix.cma";; #load "str.cma";; (* Tagged filename or URL type. *) type filename = | Uncompressed of string | Compressed of string | URL of string (* try/finally-like construct to ensure we dispose of resources properly. *) let finally handler f x = let result = try f x with e -> handler (); raise e in handler (); result (* Call f with an in_channel given a tagged filename. If the filename is tagged Uncompressed, open it normally. If it is tagged Compressed then pipe it through gzip. If it is tagged URL, pipe it through "lynx -dump". Ensure that the channel is closed and any created processes have terminated before returning. As a special case, a filename of Uncompressed "-" will result in stdin being passed, and no channel will be closed. *) let with_in_channel filename f = let pipe_input args f = let reader, writer = Unix.pipe () in let pid = Unix.create_process args.(0) args Unix.stdin writer Unix.stderr in Unix.close writer; let in_channel = Unix.in_channel_of_descr reader in finally (fun () -> close_in in_channel; ignore (Unix.waitpid [] pid)) f in_channel in match filename with | Uncompressed "-" -> f stdin | Uncompressed filename -> let in_channel = open_in filename in finally (fun () -> close_in in_channel) f in_channel | Compressed filename -> pipe_input [| "gzip"; "-dc"; filename |] f | URL url -> pipe_input [| "lynx"; "-dump"; url |] f (* Return true if the string s starts with the given prefix. *) let starts_with s prefix = try Str.first_chars s (String.length prefix) = prefix with Invalid_argument _ -> false (* Return true if the string s ends with the given suffix. *) let ends_with s suffix = try Str.last_chars s (String.length suffix) = suffix with Invalid_argument _ -> false (* Return true if the string s contains the given substring. *) let contains s substring = try ignore (Str.search_forward (Str.regexp_string substring) s 0); true with Not_found -> false (* Tag the filename depending on its contents or extension. *) let tag_filename filename = if contains filename "://" then URL filename else if List.exists (ends_with filename) [".gz"; ".Z"] then Compressed filename else Uncompressed filename (* Process a tagged filename. *) let process filename = with_in_channel filename (fun in_channel -> try while true do let line = input_line in_channel in (* ... *) () done with End_of_file -> ()) (* Parse the command-line arguments and process each file or URL. *) let () = let args = if Array.length Sys.argv > 1 then (List.tl (Array.to_list Sys.argv)) else ["-"] in List.iter process (List.map tag_filename args)
[править] Reading STDERR from a Program
#load "unix.cma";; (* Read STDERR and STDOUT at the same time. *) let () = let ph = Unix.open_process_in "cmd 2>&1" in while true do let line = input_line ph in (* ... *) () done (*-----------------------------*) (* Read STDOUT and discard STDERR. *) let output = read_process "cmd 2>/dev/null" (* or *) let () = let ph = Unix.open_process_in "cmd 2>/dev/null" in while true do let line = input_line ph in (* ... *) () done (*-----------------------------*) (* Read STDERR and discard STDOUT. *) let output = read_process "cmd 2>&1 1>/dev/null" (* or *) let () = let ph = Unix.open_process_in "cmd 2>&1 1>/dev/null" in while true do let line = input_line ph in (* ... *) () done (*-----------------------------*) (* Swap STDOUT with STDERR and read original STDERR. *) let output = read_process "cmd 3>&1 1>&2 2>&3 3>&-" (* or *) let () = let ph = Unix.open_process_in "cmd 3>&1 1>&2 2>&3 3>&-" in while true do let line = input_line ph in (* ... *) () done (*-----------------------------*) (* Redirect STDOUT and STDERR to temporary files. *) let () = ignore (Sys.command "program args 1>/tmp/program.stdout 2>/tmp/program.stderr") (*-----------------------------*) (* If the following redirections were done in OCaml... *) let output = read_process "cmd 3>&1 1>&2 2>&3 3>&-" (* ...they would look something like this: *) let fd3 = fd1 let fd1 = fd2 let fd2 = fd3 let fd3 = undef (*-----------------------------*) (* Send STDOUT and STDERR to a temporary file. *) let () = ignore (Sys.command "prog args 1>tmpfile 2>&1") (* Send STDOUT to a temporary file and redirect STDERR to STDOUT. *) let () = ignore (Sys.command "prog args 2>&1 1>tmpfile") (*-----------------------------*) (* If the following redirections were done in OCaml... *) let () = ignore (Sys.command "prog args 1>tmpfile 2>&1") (* ...they would look something like this: *) let fd1 = "tmpfile" (* change stdout destination first *) let fd2 = fd1 (* now point stderr there, too *) (*-----------------------------*) (* If the following redirections were done in OCaml... *) let () = ignore (Sys.command "prog args 2>&1 1>tmpfile") (* ...they would look something like this: *) let fd2 = fd1 (* stderr same destination as stdout *) let fd1 = "tmpfile" (* but change stdout destination *)
[править] Controlling Input and Output of Another Program
#load "unix.cma";; let () = let (readme, writeme) = Unix.open_process program in output_string writeme "here's your input\n"; close_out writeme; let output = input_line readme in ignore (Unix.close_process (readme, writeme))
[править] Controlling the Input, Output, and Error of Another Program
#load "unix.cma";; let () = let proc = Unix.open_process_in ("(" ^ cmd ^ " | sed -e 's/^/stdout: /' ) 2>&1") in try while true do let line = input_line proc in if String.length line >= 8 && String.sub line 0 8 = "stdout: " then Printf.printf "STDOUT: %s\n" (String.sub line 8 (String.length line - 8)) else Printf.printf "STDERR: %s\n" line done with End_of_file -> ignore (Unix.close_process_in proc) (*-----------------------------*) #!/usr/bin/ocaml (* cmd3sel - control all three of kids in, out, and error. *) #load "unix.cma";; let cmd = "grep vt33 /none/such - /etc/termcap" let cmd_out, cmd_in, cmd_err = Unix.open_process_full cmd [| |] let () = output_string cmd_in "This line has a vt33 lurking in it\n"; close_out cmd_in; let cmd_out_descr = Unix.descr_of_in_channel cmd_out in let cmd_err_descr = Unix.descr_of_in_channel cmd_err in let selector = ref [cmd_err_descr; cmd_out_descr] in while !selector <> [] do let can_read, _, _ = Unix.select !selector [] [] 1.0 in List.iter (fun fh -> try if fh = cmd_err_descr then Printf.printf "STDERR: %s\n" (input_line cmd_err) else Printf.printf "STDOUT: %s\n" (input_line cmd_out) with End_of_file -> selector := List.filter (fun fh' -> fh <> fh') !selector) can_read done; ignore (Unix.close_process_full (cmd_out, cmd_in, cmd_err))
[править] Communicating Between Related Processes
(* pipe1 - use pipe and fork so parent can send to child *) #load "unix.cma" open Unix let reader, writer = pipe () let () = match fork () with | 0 -> close writer; let input = in_channel_of_descr reader in let line = input_line input in Printf.printf "Child Pid %d just read this: `%s'\n" (getpid ()) line; close reader; (* this will happen anyway *) exit 0 | pid -> close reader; let output = out_channel_of_descr writer in Printf.fprintf output "Parent Pid %d is sending this\n" (getpid ()); flush output; close writer; ignore (waitpid [] pid) (*-----------------------------*) (* pipe2 - use pipe and fork so child can send to parent *) #load "unix.cma" open Unix let reader, writer = pipe () let () = match fork () with | 0 -> close reader; let output = out_channel_of_descr writer in Printf.fprintf output "Child Pid %d is sending this\n" (getpid ()); flush output; close writer; (* this will happen anyway *) exit 0 | pid -> close writer; let input = in_channel_of_descr reader in let line = input_line input in Printf.printf "Parent Pid %d just read this: `%s'\n" (getpid ()) line; close reader; ignore (waitpid [] pid) (*-----------------------------*) (* pipe3 and pipe4 demonstrate the use of perl's "forking open" feature to * reimplement pipe1 and pipe2. Since OCaml does not support such a feature, * these are skipped here. *) (*-----------------------------*) (* pipe5 - bidirectional communication using two pipe pairs designed for the socketpair-challenged *) #load "unix.cma" open Unix let parent_rdr, child_wtr = pipe () let child_rdr, parent_wtr = pipe () let () = match fork () with | 0 -> close child_rdr; close child_wtr; let input = in_channel_of_descr parent_rdr in let output = out_channel_of_descr parent_wtr in let line = input_line input in Printf.printf "Child Pid %d just read this: `%s'\n" (getpid ()) line; Printf.fprintf output "Child Pid %d is sending this\n" (getpid ()); flush output; close parent_rdr; close parent_wtr; exit 0 | pid -> close parent_rdr; close parent_wtr; let input = in_channel_of_descr child_rdr in let output = out_channel_of_descr child_wtr in Printf.fprintf output "Parent Pid %d is sending this\n" (getpid()); flush output; let line = input_line input in Printf.printf "Parent Pid %d just read this: `%s'\n" (getpid ()) line; close child_rdr; close child_wtr; ignore (waitpid [] pid) (*-----------------------------*) (* pipe6 - bidirectional communication using socketpair "the best ones always go both ways" *) #load "unix.cma" open Unix let child, parent = socketpair PF_UNIX SOCK_STREAM 0 let () = match fork () with | 0 -> close child; let input = in_channel_of_descr parent in let output = out_channel_of_descr parent in let line = input_line input in Printf.printf "Child Pid %d just read this: `%s'\n" (getpid ()) line; Printf.fprintf output "Child Pid %d is sending this\n" (getpid ()); flush output; close parent; exit 0 | pid -> close parent; let input = in_channel_of_descr child in let output = out_channel_of_descr child in Printf.fprintf output "Parent Pid %d is sending this\n" (getpid ()); flush output; let line = input_line input in Printf.printf "Parent Pid %d just read this: `%s'\n" (getpid ()) line; close child; ignore (waitpid [] pid) (*-----------------------------*) (* Simulating a pipe using a socketpair. *) let reader, writer = socketpair PF_UNIX SOCK_STREAM 0 in shutdown reader SHUTDOWN_SEND; (* no more writing for reader *) shutdown writer SHUTDOWN_RECEIVE; (* no more reading for writer *)
[править] Making a Process Look Like a File with Named Pipes
% mkfifo /path/to/named.pipe (*-----------------------------*) let () = let fifo = open_in "/path/to/named.pipe" in try while true do let line = input_line fifo in Printf.printf "Got: %s\n" line done with End_of_file -> close_in fifo (*-----------------------------*) let () = let fifo = open_out "/path/to/named.pipe" in output_string fifo "Smoke this.\n"; close_out fifo (*-----------------------------*) % mkfifo ~/.plan # isn't this everywhere yet? % mknod ~/.plan p # in case you don't have mkfifo (*-----------------------------*) (* dateplan - place current date and time in .plan file *) #load "unix.cma";; let () = while true do let home = Unix.getenv "HOME" in let fifo = open_out (home ^ "/.plan") in Printf.fprintf fifo "The current time is %s\n" (format_time (Unix.time ())); close_out fifo; Unix.sleep 1 done (*-----------------------------*) #!/usr/bin/ocaml (* fifolog - read and record log msgs from fifo *) #load "unix.cma";; let fifo = ref None let handle_alarm signal = match !fifo with | Some channel -> (* move on to the next queued process *) close_in channel; fifo := None | None -> () let () = Sys.set_signal Sys.sigalrm (Sys.Signal_handle handle_alarm) let read_fifo () = try match !fifo with | Some channel -> Some (input_line channel) | None -> None with | End_of_file -> None | Sys_error e -> Printf.eprintf "Error reading fifo: %s\n%!" e; fifo := None; None let days = [| "Sun"; "Mon"; "Tue"; "Wed"; "Thu"; "Fri"; "Sat" |] let months = [| "Jan"; "Feb"; "Mar"; "Apr"; "May"; "Jun"; "Jul"; "Aug"; "Sep"; "Oct"; "Nov"; "Dec" |] let format_time time = let tm = Unix.localtime time in Printf.sprintf "%s %s %2d %02d:%02d:%02d %04d" days.(tm.Unix.tm_wday) months.(tm.Unix.tm_mon) tm.Unix.tm_mday tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec (tm.Unix.tm_year + 1900) let () = while true do (* turn off alarm for blocking open *) ignore (Unix.alarm 0); begin try fifo := Some (open_in "/tmp/log") with Sys_error e -> Printf.eprintf "Can't open /tmp/log: %s\n%!" e; exit 1 end; (* you have 1 second to log *) ignore (Unix.alarm 1); let service = read_fifo () in let message = read_fifo () in (* turn off alarms for message processing *) ignore (Unix.alarm 0); begin match service, message with | None, _ | _, None -> (* interrupted or nothing logged *) () | Some service, Some message -> if service = "http" then () (* ignoring *) else if service = "login" then begin (* log to /tmp/login *) try let log = open_out_gen [Open_wronly; Open_creat; Open_append] 0o666 "/tmp/login" in Printf.fprintf log "%s %s %s\n%!" (format_time (Unix.time ())) service message; close_out log with Sys_error e -> Printf.eprintf "Couldn't log %s %s to /tmp/login: %s\n%!" service message e end end done
[править] Sharing Variables in Different Processes
(* OCaml does not currently support SysV IPC. *)
[править] Listing Available Signals
% echo 'module M = Sys;;' | ocaml | grep 'val sig' val sigabrt : int val sigalrm : int val sigfpe : int val sighup : int val sigill : int val sigint : int val sigkill : int val sigpipe : int val sigquit : int val sigsegv : int val sigterm : int val sigusr1 : int val sigusr2 : int val sigchld : int val sigcont : int val sigstop : int val sigtstp : int val sigttin : int val sigttou : int val sigvtalrm : int val sigprof : int % grep -A1 'val sig' sys.mli val sigabrt : int (** Abnormal termination *) -- val sigalrm : int (** Timeout *) -- val sigfpe : int (** Arithmetic exception *) -- val sighup : int (** Hangup on controlling terminal *) -- val sigill : int (** Invalid hardware instruction *) -- val sigint : int (** Interactive interrupt (ctrl-C) *) -- val sigkill : int (** Termination (cannot be ignored) *) -- val sigpipe : int (** Broken pipe *) -- val sigquit : int (** Interactive termination *) -- val sigsegv : int (** Invalid memory reference *) -- val sigterm : int (** Termination *) -- val sigusr1 : int (** Application-defined signal 1 *) -- val sigusr2 : int (** Application-defined signal 2 *) -- val sigchld : int (** Child process terminated *) -- val sigcont : int (** Continue *) -- val sigstop : int (** Stop *) -- val sigtstp : int (** Interactive stop *) -- val sigttin : int (** Terminal read from background process *) -- val sigttou : int (** Terminal write from background process *) -- val sigvtalrm : int (** Timeout in virtual time *) -- val sigprof : int (** Profiling interrupt *)
[править] Sending a Signal
#load "unix.cma";; let () = (* send pid a signal 9 *) Unix.kill pid 9; (* send whole job a signal 1 *) Unix.kill pgrp (-1); (* send myself a SIGUSR1 *) Unix.kill (Unix.getpid ()) Sys.sigusr1; (* send a SIGHUP to processes in pids *) List.iter (fun pid -> Unix.kill pid Sys.sighup) pids (*-----------------------------*) (* Use kill with pseudo-signal 0 to see if process is alive. *) let () = try Unix.kill minion 0; Printf.printf "%d is alive!\n" minion with | Unix.Unix_error (Unix.EPERM, _, _) -> (* changed uid *) Printf.printf "%d has escaped my control!\n" minion | Unix.Unix_error (Unix.ESRCH, _, _) -> Printf.printf "%d is deceased.\n" (* or zombied *) minion | e -> Printf.printf "Odd; I couldn't check on the status of %d: %s\n" minion (Printexc.to_string e)
[править] Installing a Signal Handler
let () = (* call got_sig_quit for every SIGQUIT *) Sys.set_signal Sys.sigquit (Sys.Signal_handle got_sig_quit); (* call got_sig_pipe for every SIGPIPE *) Sys.set_signal Sys.sigpipe (Sys.Signal_handle got_sig_pipe); (* increment ouch for every SIGINT *) Sys.set_signal Sys.sigint (Sys.Signal_handle (fun _ -> incr ouch)); (* ignore the signal INT *) Sys.set_signal Sys.sigint Sys.Signal_ignore; (* restore default STOP signal handling *) Sys.set_signal Sys.sigstop Sys.Signal_default
[править] Temporarily Overriding a Signal Handler
let finally handler f x = let result = try f x with e -> handler (); raise e in handler (); result (* call f with signal behavior temporarily set *) let local_set_signal signal behavior f = let old_behavior = Sys.signal signal behavior in finally (fun () -> Sys.set_signal signal old_behavior) f () (* the signal handler *) let rec ding _ = Sys.set_signal Sys.sigint (Sys.Signal_handle ding); prerr_endline "\x07Enter your name!" (* prompt for name, overriding SIGINT *) let get_name () = local_set_signal Sys.sigint (Sys.Signal_handle ding) (fun () -> print_string "Kindly Stranger, please enter your name: "; read_line ())
[править] Writing a Signal Handler
let rec got_int _ = Sys.set_signal Sys.sigint (Sys.Signal_handle got_int); (* but not for SIGCHLD! *) (* ... *) () (*-----------------------------*) let rec got_int _ = Sys.set_signal Sys.sigint Sys.Signal_default; (* or Signal_ignore *) failwith "interrupted" let () = Sys.set_signal Sys.sigint (Sys.Signal_handle got_int); try (* ... long-running code that you don't want to restart *) () with Failure "interrupted" -> (* deal with the signal *) ()
[править] Catching Ctrl-C
let () = (* ignore signal INT *) Sys.set_signal Sys.sigint Sys.Signal_ignore; (* install signal handler *) let rec tsktsk signal = Sys.set_signal Sys.sigint (Sys.Signal_handle tsktsk); print_endline "\x07The long habit of living indisposeth us for dying." in Sys.set_signal Sys.sigint (Sys.Signal_handle tsktsk)
[править] Avoiding Zombie Processes
#load "unix.cma";; let () = Sys.set_signal Sys.sigchld Sys.Signal_ignore (*-----------------------------*) let rec reaper signal = try while true do ignore (Unix.waitpid [Unix.WNOHANG] (-1)) done with Unix.Unix_error (Unix.ECHILD, _, _) -> (); Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper) let () = Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper) (*-----------------------------*) let rec reaper signal = begin try let pid, status = Unix.waitpid [Unix.WNOHANG] (-1) in begin match status with | Unix.WEXITED _ -> Printf.printf "Process %d exited.\n" pid | _ -> Printf.printf "False alarm on %d.\n" pid; end; reaper signal with Unix.Unix_error (Unix.ECHILD, _, _) -> () (* No child waiting. Ignore it. *) end; Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper) let () = Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper)
[править] Blocking Signals
#load "unix.cma";; (* define the signals to block *) let sigset = [Sys.sigint; Sys.sigkill] let () = (* block signals *) let old_sigset = Unix.sigprocmask Unix.SIG_BLOCK sigset in (* ... *) (* unblock signals *) (* the original recipe uses SIG_UNBLOCK, but that doesn't seem right... *) ignore (Unix.sigprocmask Unix.SIG_SETMASK old_sigset)
[править] Timing Out an Operation
#load "unix.cma";; let () = Sys.set_signal Sys.sigalrm (Sys.Signal_handle (fun _ -> failwith "timeout")); ignore (Unix.alarm 3600); try (* long-time operations here *) ignore (Unix.alarm 0) with | Failure "timeout" -> (* timed out; do what you will here *) () | e -> (* clear the still-pending alarm *) ignore (Unix.alarm 0); (* propagate unexpected exception *) raise e
[править] Program: sigrand
#!/usr/bin/ocaml (* sigrand - supply random fortunes for .signature file *) #load "str.cma";; #load "unix.cma";; (* globals *) let pwd = Unix.getpwuid (Unix.getuid ()) let home = try Unix.getenv "HOME" with Not_found -> try Unix.getenv "LOGDIR" with Not_found -> pwd.Unix.pw_dir let fortune_path = ref "" (**************************************************************) (* begin configuration section *) (* for rec/humor/funny instead of rec.humor.funny *) let ng_is_dir = true let fullname = home ^ "/.fullname" let fifo = home ^ "/.signature" let art = home ^ "/.article" let news = home ^ "/News" let sigs = news ^ "/SIGNATURES" let sema = home ^ "/.sigrandpid" let globrand = 0.25 (* chance to use global sigs anyway *) (* name should be (1) left None to have program guess read address for signature maybe looking in ~/.fullname, (2) set to an exact address, or (3) set to empty string to be omitted entirely. *) (* let name = ref None *) (* let name = ref (Some ("me@home.org")) *) let name = ref (Some "") (* end configuration section *) (**************************************************************) let read_process_lines command = let lines = ref [] in let in_channel = Unix.open_process_in command in begin try while true do lines := input_line in_channel :: !lines done; with End_of_file -> ignore (Unix.close_process_in in_channel) end; List.rev !lines let line_stream_of_channel channel = Stream.from (fun _ -> try Some (input_line channel) with End_of_file -> None) let delimited_stream_of_channel delim channel = let lines = line_stream_of_channel channel in let rec next para_lines i = match Stream.peek lines, para_lines with | None, [] -> None | Some delim', [] when delim' = delim -> Stream.junk lines; next para_lines i | Some delim', _ when delim' = delim -> Some (String.concat "\n" (List.rev para_lines)) | None, _ -> Some (String.concat "\n" (List.rev para_lines)) | Some line, _ -> Stream.junk lines; next (line :: para_lines) i in Stream.from (next []) (* Make sure there's a fortune program. Search for its full path and set global to that. *) let check_fortunes () = if !fortune_path <> "" then () (* already set *) else let path = Str.split (Str.regexp ":") (Unix.getenv "PATH") in let rec check = function | [] -> Printf.eprintf "Need either %s or a fortune program, bailing out\n" sigs; exit 1 | dir :: dirs -> let p = Filename.concat dir "fortune" in if Sys.file_exists p then p else check dirs in fortune_path := check (path @ ["/usr/games"]) (* Call the fortune program with -s for short flag until we get a small enough fortune or ask too much. *) let fortune () = let cmd = !fortune_path ^ " -s" in let rec loop tries = let lines = read_process_lines cmd in if List.length lines < 5 then lines else if tries < 20 then loop (tries + 1) else [] in match loop 0 with | [] -> [" SIGRAND: deliver random signals to all processes."] | lines -> List.map (( ^ ) " ") lines (* See whether ~/.article contains a Newsgroups line. if so, see the first group posted to and find out whether it has a dedicated set of fortunes. otherwise return the global one. Also, return the global one randomly now and then to spice up the sigs. *) let signame () = if Random.float 1.0 > globrand then begin try let channel = open_in art in let regexp = Str.regexp "Newsgroups:[ \t]*\\([^, \r\n\t]*\\)" in let ng = ref "" in begin try while true do let line = input_line channel in if Str.string_match regexp line 0 then ng := Str.matched_group 1 line done with End_of_file -> close_in channel end; if ng_is_dir then ng := Str.global_replace (Str.regexp "\\.") "/" !ng; ng := news ^ "/" ^ !ng ^ "/" ^ "SIGNATURES"; if Sys.file_exists !ng then !ng else sigs with Sys_error e -> sigs end else sigs (* choose a random signature *) let pick_quote () = let sigfile = signame () in if not (Sys.file_exists sigfile) then fortune () else begin let channel = open_in sigfile in let stream = delimited_stream_of_channel "%%" channel in let quip = ref [] in let num = ref 1 in Stream.iter (fun chunk -> if Random.int !num = 0 then quip := Str.split (Str.regexp "\n") chunk; incr num) stream; close_in channel; if !quip <> [] then List.map (( ^ ) " ") !quip else [" ENOSIG: This signature file is empty."] end (* Ignore SIGPIPE in case someone opens us up and then closes the fifo without reading it; look in a .fullname file for their login name. Try to determine the fully qualified hostname. Make sure we have signatures or fortunes. Build a fifo if we need to. *) let setup () = Sys.set_signal Sys.sigpipe Sys.Signal_ignore; if !name = Some "" then begin try let channel = open_in fullname in name := Some (input_line channel); close_in channel with Sys_error _ -> name := Some (Str.global_replace (Str.regexp ",.*") "" pwd.Unix.pw_gecos) end; if not (Sys.file_exists sigs) then check_fortunes (); if Sys.file_exists fifo then (if (Unix.stat fifo).Unix.st_kind = Unix.S_FIFO then (Printf.eprintf "%s: using existing named pipe %s\n" Sys.argv.(0) fifo) else (Printf.eprintf "%s: won't overwrite file %s\n" Sys.argv.(0) fifo; exit 1)) else (Unix.mkfifo fifo 0o666; Printf.eprintf "%s: created %s as a named pipe\n" Sys.argv.(0) fifo); Random.self_init () (* "There can be only one." --the Highlander *) let justme () = let channel = try Some (open_in sema) with Sys_error _ -> None in match channel with | Some channel -> begin let pid = int_of_string (input_line channel) in try Unix.kill pid 0; Printf.eprintf "%s already running (pid %d), bailing out\n" Sys.argv.(0) pid; exit 1 with _ -> close_in channel end | None -> () let () = setup (); (* pull in inits *) justme (); (* make sure program not already running *) match Unix.fork () with (* background ourself and go away *) | 0 -> let channel = open_out sema in output_string channel (string_of_int (Unix.getpid ())); output_string channel "\n"; close_out channel; (* now loop forever, writing a signature into the fifo file. if you don't have real fifos, change sleep time at bottom of loop to like 10 to update only every 10 seconds. *) while true do let channel = open_out fifo in let sig' = pick_quote () in let sig' = Array.of_list sig' in (* trunc to 4 lines *) let sig' = if Array.length sig' > 4 then Array.sub sig' 0 4 else sig' in (* trunc long lines *) let sig' = Array.map (fun line -> if String.length line > 80 then String.sub line 0 80 else line) sig' in (* print sig, with name if present, padded to four lines *) begin match !name with | None | Some "" -> Array.iter (fun line -> output_string channel line; output_string channel "\n") sig' | Some name -> output_string channel name; for i = 4 downto Array.length sig' do output_string channel "\n"; done; Array.iter (fun line -> output_string channel line; output_string channel "\n") sig' end; close_out channel; (* Without a microsleep, the reading process doesn't finish before the writer tries to open it again, which since the reader exists, succeeds. They end up with multiple signatures. Sleep a tiny bit between opens to give readers a chance to finish reading and close our pipe so we can block when opening it the next time. *) ignore (Unix.select [] [] [] 0.2) (* sleep 1/5 second *) done | _ -> exit 0