(* Lightweight thread library for Objective Caml
 * http://www.ocsigen.org/lwt
 * Module Lwt_unix
 * Copyright (C) 2005-2008 Jérôme Vouillon
 * Laboratoire PPS - CNRS Université Paris Diderot
 *                    2009 Jérémie Dimino
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, with linking exceptions;
 * either version 2.1 of the License, or (at your option) any later
 * version. See COPYING file for details.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *)

#include "src/unix/lwt_config.ml"

open Lwt

(* +-----------------------------------------------------------------+
   | Configuration                                                   |
   +-----------------------------------------------------------------+ *)

type async_method =
  | Async_none
  | Async_detach
  | Async_switch

let default_async_method_var = ref Async_detach

let () =
  try
    match Sys.getenv "LWT_ASYNC_METHOD" with
      | "none" ->
          default_async_method_var := Async_none
      | "detach" ->
          default_async_method_var := Async_detach
      | "switch" ->
          default_async_method_var := Async_switch
      | str ->
          Printf.eprintf
            "%s: invalid lwt async method: '%s', must be 'none', 'detach' or 'switch'\n%!"
            (Filename.basename Sys.executable_name) str
  with Not_found ->
    ()

let default_async_method () = !default_async_method_var
let set_default_async_method am = default_async_method_var := am

let async_method_key = Lwt.new_key ()

let async_method () =
  match Lwt.get async_method_key with
    | Some am -> am
    | None -> !default_async_method_var

let with_async_none f =
  with_value async_method_key (Some Async_none) f

let with_async_detach f =
  with_value async_method_key (Some Async_detach) f

let with_async_switch f =
  with_value async_method_key (Some Async_switch) f

(* +-----------------------------------------------------------------+
   | Notifications management                                        |
   +-----------------------------------------------------------------+ *)

(* Informations about a notifier *)
type notifier = {
  notify_handler : unit -> unit;
  (* The callback *)

  notify_once : bool;
  (* Whether to remove the notifier after the reception of the first
     notification *)
}

module Notifiers = Hashtbl.Make(struct
                                  type t = int
                                  let equal (x : int) (y : int) = x = y
                                  let hash (x : int) = x
                                end)

let notifiers = Notifiers.create 1024

let current_notification_id = ref 0

let rec find_free_id id =
  if Notifiers.mem notifiers id then
    find_free_id (id + 1)
  else
    id

let make_notification ?(once=false) f =
  let id = find_free_id (!current_notification_id + 1) in
  current_notification_id := id;
  Notifiers.add notifiers id { notify_once = once; notify_handler = f };
  id

let stop_notification id =
  Notifiers.remove notifiers id

let set_notification id f =
  let notifier = Notifiers.find notifiers id in
  Notifiers.replace notifiers id { notifier with notify_handler = f }

(* +-----------------------------------------------------------------+
   | Sleepers                                                        |
   +-----------------------------------------------------------------+ *)

let sleep delay =
  let waiter, wakener = Lwt.task () in
  let ev = Lwt_engine.on_timer delay false (fun ev -> Lwt_engine.stop_event ev; Lwt.wakeup wakener ()) in
  Lwt.on_cancel waiter (fun () -> Lwt_engine.stop_event ev);
  waiter

let yield = Lwt_main.yield

let auto_yield timeout =
  let limit = ref (Unix.gettimeofday () +. timeout) in
  fun () ->
    let current = Unix.gettimeofday () in
    if current >= !limit then begin
      limit := current +. timeout;
      yield ();
    end else
      return ()

exception Timeout

let timeout d = sleep d >> Lwt.fail Timeout

let with_timeout d f = Lwt.pick [timeout d; Lwt.apply f ()]

(* +-----------------------------------------------------------------+
   | Jobs                                                            |
   +-----------------------------------------------------------------+ *)

type 'a job

external start_job : 'a job -> async_method -> bool = "lwt_unix_start_job"
    (* Starts the given job with given parameters. It returns [true]
       if the job is already terminated. *)

external check_job : 'a job -> int -> bool = "lwt_unix_check_job" "noalloc"
    (* Check whether that a job has terminated or not. If it has not
       yet terminated, it is marked so it will send a notification
       when it finishes. *)

external cancel_job : 'a job -> unit = "lwt_unix_cancel_job" "noalloc"
    (* Cancel the thread of the given job. *)

(* All running jobs. *)
let jobs = Lwt_sequence.create ()

(* Cancel all running jobs. *)
let rec cancel_jobs () =
  match Lwt_sequence.take_opt_l jobs with
    | Some w -> cancel w; cancel_jobs ()
    | None -> ()

let wait_for_jobs () =
  join (Lwt_sequence.fold_l (fun w l -> w :: l) jobs [])

let execute_job ?async_method ~job ~result ~free =
  let async_method =
    match async_method with
      | Some am -> am
      | None ->
          match Lwt.get async_method_key with
            | Some am -> am
            | None -> !default_async_method_var
  in
  (* Starts the job. *)
  let job_done = start_job job async_method in
  let w =
    lwt status =
      if job_done then
        return None
      else
        (* Create the notification for asynchronous wakeup. *)
        let id = make_notification ~once:true ignore in
        try_lwt
          (* Give some time to the job before we fallback to
             asynchronous notification. *)
          lwt () = pause () in
          if check_job job id then begin
            stop_notification id;
            return None
          end else
            return (Some id)
        with Canceled as exn ->
          cancel_job job;
          (* Free resources when the job terminates. *)
          if check_job job id then begin
            stop_notification id;
            free job
          end else
            set_notification id (fun () -> free job);
          raise_lwt exn
    in
    match status with
      | None ->
          (* The job has already terminated, read and return the result
             immediatly. *)
          let thread =
            try
              return (result job)
            with exn ->
              fail exn
          in
          free job;
          thread
      | Some id ->
          (* The job has not terminated, setup the notification for the
             asynchronous wakeup. *)
          let waiter, wakener = task () in
          set_notification id
            (fun () ->
               begin
                 try
                   wakeup wakener (result job);
                 with exn ->
                   wakeup_exn wakener exn
               end;
               free job);
          on_cancel waiter
            (fun () ->
               cancel_job job;
               set_notification id (fun () -> free job));
          waiter
  in
  if state w = Sleep then begin
    (* Add the job to the sequence of all jobs. *)
    let node = Lwt_sequence.add_l (w >> return ()) jobs in
    (* Remove it on termination. *)
    on_termination w (fun () -> Lwt_sequence.remove node)
  end;
  w

(* +-----------------------------------------------------------------+
   | File descriptor wrappers                                        |
   +-----------------------------------------------------------------+ *)

type state = Opened | Closed | Aborted of exn

type file_descr = {
  fd : Unix.file_descr;
  (* The underlying unix file descriptor *)

  mutable state: state;
  (* The state of the file descriptor *)

  mutable set_flags : bool;
  (* Whether to set file flags *)

  mutable blocking : bool Lwt.t Lazy.t;
  (* Is the file descriptor in blocking or non-blocking mode *)

  mutable event_readable : Lwt_engine.event option;
  (* The event used to check the file descriptor for readability. *)

  mutable event_writable : Lwt_engine.event option;
  (* The event used to check the file descriptor for writability. *)

  hooks_readable : (unit -> unit) Lwt_sequence.t;
  (* Hooks to call when the file descriptor becomes readable. *)

  hooks_writable : (unit -> unit) Lwt_sequence.t;
  (* Hooks to call when the file descriptor becomes writable. *)
}

#if windows

external is_socket : Unix.file_descr -> bool = "lwt_unix_is_socket" "noalloc"

let is_blocking ?blocking ?(set_flags=true) fd =
  if is_socket fd then
    match blocking, set_flags with
      | Some state, false ->
          lazy(return state)
      | Some true, true ->
          Unix.clear_nonblock fd;
          lazy(return true)
      | Some false, true ->
          Unix.set_nonblock fd;
          lazy(return false)
      | None, false ->
          lazy(return false)
      | None, true ->
          Unix.set_nonblock fd;
          lazy(return false)
  else
    match blocking with
      | Some state ->
          lazy(return state)
      | None ->
          lazy(return true)

#else

external guess_blocking_job : Unix.file_descr -> [ `unix_guess_blocking ] job = "lwt_unix_guess_blocking_job"
external guess_blocking_result : [ `unix_guess_blocking ] job -> bool = "lwt_unix_guess_blocking_result" "noalloc"
external guess_blocking_free : [ `unix_guess_blocking ] job -> unit = "lwt_unix_guess_blocking_free" "noalloc"

let guess_blocking fd =
  execute_job (guess_blocking_job fd) guess_blocking_result guess_blocking_free

let is_blocking ?blocking ?(set_flags=true) fd =
    match blocking, set_flags with
      | Some state, false ->
          lazy(return state)
      | Some true, true ->
          Unix.clear_nonblock fd;
          lazy(return true)
      | Some false, true ->
          Unix.set_nonblock fd;
          lazy(return false)
      | None, false ->
          lazy(guess_blocking fd)
      | None, true ->
          lazy(guess_blocking fd >>= function
                 | true ->
                     Unix.clear_nonblock fd;
                     return true
                 | false ->
                     Unix.set_nonblock fd;
                     return false)

#endif

let mk_ch ?blocking ?(set_flags=true) fd = {
  fd = fd;
  state = Opened;
  set_flags = set_flags;
  blocking = is_blocking ?blocking ~set_flags fd;
  event_readable = None;
  event_writable = None;
  hooks_readable = Lwt_sequence.create ();
  hooks_writable = Lwt_sequence.create ();
}

let rec check_descriptor ch =
  match ch.state with
    | Opened ->
        ()
    | Aborted e ->
        raise e
    | Closed ->
        raise (Unix.Unix_error (Unix.EBADF, "check_descriptor", ""))

let state ch = ch.state

let blocking ch =
  check_descriptor ch;
  Lazy.force ch.blocking

let set_blocking ?(set_flags=true) ch blocking =
  check_descriptor ch;
  ch.set_flags <- set_flags;
  ch.blocking <- is_blocking ~blocking ~set_flags ch.fd

#if windows

let stub_readable fd = Unix.select [fd] [] [] (-1.0) <> ([], [], [])
let stub_writable fd = Unix.select [] [fd] [] (-1.0) <> ([], [], [])

#else

external stub_readable : Unix.file_descr -> bool = "lwt_unix_readable"
external stub_writable : Unix.file_descr -> bool = "lwt_unix_writable"

#endif

let readable ch =
  check_descriptor ch;
  stub_readable ch.fd

let writable ch =
  check_descriptor ch;
  stub_writable ch.fd

let set_state ch st =
  ch.state <- st

let clear_events ch =
  Lwt_sequence.iter_node_l (fun node -> Lwt_sequence.remove node; Lwt_sequence.get node ()) ch.hooks_readable;
  Lwt_sequence.iter_node_l (fun node -> Lwt_sequence.remove node; Lwt_sequence.get node ()) ch.hooks_writable;
  begin
    match ch.event_readable with
      | Some ev ->
          ch.event_readable <- None;
          Lwt_engine.stop_event ev
      | None ->
          ()
  end;
  begin
    match ch.event_writable with
      | Some ev ->
          ch.event_writable <- None;
          Lwt_engine.stop_event ev
      | None ->
          ()
  end

let abort ch e =
  if ch.state <> Closed then begin
    set_state ch (Aborted e);
    clear_events ch
  end

let unix_file_descr ch = ch.fd

let of_unix_file_descr = mk_ch

let stdin = of_unix_file_descr ~set_flags:false ~blocking:true Unix.stdin
let stdout = of_unix_file_descr ~set_flags:false ~blocking:true Unix.stdout
let stderr = of_unix_file_descr ~set_flags:false ~blocking:true Unix.stderr

(* +-----------------------------------------------------------------+
   | Actions on file descriptors                                     |
   +-----------------------------------------------------------------+ *)

type io_event = Read | Write

exception Retry
exception Retry_write
exception Retry_read

type 'a outcome =
  | Success of 'a
  | Exn of exn
  | Requeued of io_event

(* Wait a bit, then stop events that are no more used. *)
let stop_events ch =
  on_success
    (pause ())
    (fun () ->
       if Lwt_sequence.is_empty ch.hooks_readable then begin
         match ch.event_readable with
           | Some ev ->
               ch.event_readable <- None;
               Lwt_engine.stop_event ev
           | None ->
               ()
       end;
       if Lwt_sequence.is_empty ch.hooks_writable then begin
         match ch.event_writable with
           | Some ev ->
               ch.event_writable <- None;
               Lwt_engine.stop_event ev
           | None ->
               ()
       end)

let register_readable ch =
  if ch.event_readable = None then
    ch.event_readable <- Some(Lwt_engine.on_readable ch.fd (fun _ -> Lwt_sequence.iter_l (fun f -> f ()) ch.hooks_readable))

let register_writable ch =
  if ch.event_writable = None then
    ch.event_writable <- Some(Lwt_engine.on_writable ch.fd (fun _ -> Lwt_sequence.iter_l (fun f -> f ()) ch.hooks_writable))

(* Retry a queued syscall, [wakener] is the thread to wakeup if the
   action succeeds: *)
let rec retry_syscall node event ch wakener action =
  let res =
    try
      check_descriptor ch;
      Success(action ())
    with
      | Retry
      | Unix.Unix_error((Unix.EAGAIN | Unix.EWOULDBLOCK | Unix.EINTR), _, _)
      | Sys_blocked_io ->
          (* EINTR because we are catching SIG_CHLD hence the system
             call might be interrupted to handle the signal; this lets
             us restart the system call eventually. *)
          Requeued event
      | Retry_read ->
          Requeued Read
      | Retry_write ->
          Requeued Write
      | e ->
          Exn e
  in
  match res with
    | Success v ->
        Lwt_sequence.remove !node;
        stop_events ch;
        Lwt.wakeup wakener v
    | Exn e ->
        Lwt_sequence.remove !node;
        stop_events ch;
        Lwt.wakeup_exn wakener e
    | Requeued event' ->
        if event <> event' then begin
          Lwt_sequence.remove !node;
          stop_events ch;
          match event' with
            | Read ->
                node := Lwt_sequence.add_r (fun () -> retry_syscall node Read ch wakener action) ch.hooks_readable ;
                register_readable ch
            | Write ->
                node := Lwt_sequence.add_r (fun () -> retry_syscall node Write ch wakener action) ch.hooks_writable;
                register_writable ch
        end

let dummy = Lwt_sequence.add_r ignore (Lwt_sequence.create ())

let register_action event ch action =
  let waiter, wakener = Lwt.task () in
  match event with
    | Read ->
        let node = ref dummy in
        node := Lwt_sequence.add_r (fun () -> retry_syscall node Read ch wakener action) ch.hooks_readable;
        on_cancel waiter (fun () -> Lwt_sequence.remove !node; stop_events ch);
        register_readable ch;
        waiter
    | Write ->
        let node = ref dummy in
        node := Lwt_sequence.add_r (fun () -> retry_syscall node Write ch wakener action) ch.hooks_writable;
        on_cancel waiter (fun () -> Lwt_sequence.remove !node; stop_events ch);
        register_writable ch;
        waiter

(* Wraps a system call *)
let wrap_syscall event ch action =
  try
    check_descriptor ch;
    lwt blocking = Lazy.force ch.blocking in
    if not blocking || (event = Read && stub_readable ch.fd) || (event = Write && stub_writable ch.fd) then
      return (action ())
    else
      register_action event ch action
  with
    | Retry
    | Unix.Unix_error((Unix.EAGAIN | Unix.EWOULDBLOCK | Unix.EINTR), _, _)
    | Sys_blocked_io ->
        (* The action could not be completed immediatly, register it: *)
        register_action event ch action
    | Retry_read ->
        register_action Read ch action
    | Retry_write ->
        register_action Write ch action
    | e ->
        raise_lwt e

(* +-----------------------------------------------------------------+
   | Basic file input/output                                         |
   +-----------------------------------------------------------------+ *)

type open_flag =
    Unix.open_flag =
  | O_RDONLY
  | O_WRONLY
  | O_RDWR
  | O_NONBLOCK
  | O_APPEND
  | O_CREAT
  | O_TRUNC
  | O_EXCL
  | O_NOCTTY
  | O_DSYNC
  | O_SYNC
  | O_RSYNC

#if windows

let openfile name flags perms =
  return (of_unix_file_descr (Unix.openfile name flags perms))

#else

external open_job : string -> Unix.open_flag list -> int -> [ `unix_open ] job = "lwt_unix_open_job"
external open_result : [ `unix_open ] job -> Unix.file_descr * bool = "lwt_unix_open_result"
external open_free : [ `unix_open ] job -> unit = "lwt_unix_open_free" "noalloc"

let openfile name flags perms =
  lwt fd, blocking =
    execute_job
      (open_job name flags perms)
      open_result
      open_free
  in
  return (of_unix_file_descr ~blocking fd)

#endif

#if windows

let close ch =
  if ch.state = Closed then check_descriptor ch;
  set_state ch Closed;
  clear_events ch;
  return (Unix.close ch.fd)

#else

external close_job : Unix.file_descr -> [ `unix_close ] job = "lwt_unix_close_job"
external close_result : [ `unix_close ] job -> unit = "lwt_unix_close_result"
external close_free : [ `unix_close ] job -> unit = "lwt_unix_close_free" "noalloc"

let close ch =
  if ch.state = Closed then check_descriptor ch;
  set_state ch Closed;
  clear_events ch;
  execute_job (close_job ch.fd) close_result close_free

#endif

let wait_read ch =
  try_lwt
    if readable ch then
      return ()
    else
      register_action Read ch ignore

external stub_read : Unix.file_descr -> string -> int -> int -> int = "lwt_unix_read"
external read_job : Unix.file_descr -> int -> [ `unix_read ] job = "lwt_unix_read_job"
external read_result : [ `unix_read ] job -> string -> int -> int = "lwt_unix_read_result"
external read_free : [ `unix_read ] job -> unit = "lwt_unix_read_free" "noalloc"

let read ch buf pos len =
  if pos < 0 || len < 0 || pos > String.length buf - len then
    invalid_arg "Lwt_unix.read"
  else
    Lazy.force ch.blocking >>= function
      | true ->
          lwt () = wait_read ch in
          execute_job (read_job ch.fd len) (fun job -> read_result job buf pos) read_free
      | false ->
          wrap_syscall Read ch (fun () -> stub_read ch.fd buf pos len)

let wait_write ch =
  try_lwt
    if writable ch then
      return ()
    else
      register_action Write ch ignore

external stub_write : Unix.file_descr -> string -> int -> int -> int = "lwt_unix_write"
external write_job : Unix.file_descr -> string -> int -> int -> [ `unix_write ] job = "lwt_unix_write_job"
external write_result : [ `unix_write ] job -> int = "lwt_unix_write_result"
external write_free : [ `unix_write ] job -> unit = "lwt_unix_write_free" "noalloc"

let write ch buf pos len =
  if pos < 0 || len < 0 || pos > String.length buf - len then
    invalid_arg "Lwt_unix.write"
  else
    Lazy.force ch.blocking >>= function
      | true ->
          lwt () = wait_write ch in
          execute_job (write_job ch.fd buf pos len) write_result write_free
      | false ->
          wrap_syscall Write ch (fun () -> stub_write ch.fd buf pos len)

(* +-----------------------------------------------------------------+
   | Seeking and truncating                                          |
   +-----------------------------------------------------------------+ *)

type seek_command =
    Unix.seek_command =
  | SEEK_SET
  | SEEK_CUR
  | SEEK_END

#if windows

let lseek ch offset whence =
  check_descriptor ch;
  return (Unix.lseek ch.fd offset whence)

#else

external lseek_job : Unix.file_descr -> int -> Unix.seek_command -> [ `unix_lseek ] job = "lwt_unix_lseek_job"
external lseek_result : [ `unix_lseek ] job -> int = "lwt_unix_lseek_result"
external lseek_free : [ `unix_lseek ] job -> unit = "lwt_unix_lseek_free"

let lseek ch offset whence =
  check_descriptor ch;
  execute_job (lseek_job ch.fd offset whence) lseek_result lseek_free

#endif

#if windows

let truncate name offset =
  return (Unix.truncate name offset)

#else

external truncate_job : string -> int -> [ `unix_truncate ] job = "lwt_unix_truncate_job"
external truncate_result : [ `unix_truncate ] job -> unit = "lwt_unix_truncate_result"
external truncate_free : [ `unix_truncate ] job -> unit = "lwt_unix_truncate_free"

let truncate name offset =
  execute_job (truncate_job name offset) truncate_result truncate_free

#endif

#if windows

let ftruncate ch offset =
  check_descriptor ch;
  return (Unix.ftruncate ch.fd offset)

#else

external ftruncate_job : Unix.file_descr -> int -> [ `unix_ftruncate ] job = "lwt_unix_ftruncate_job"
external ftruncate_result : [ `unix_ftruncate ] job -> unit = "lwt_unix_ftruncate_result"
external ftruncate_free : [ `unix_ftruncate ] job -> unit = "lwt_unix_ftruncate_free"

let ftruncate ch offset =
  check_descriptor ch;
  execute_job (ftruncate_job ch.fd offset) ftruncate_result ftruncate_free

#endif

(* +-----------------------------------------------------------------+
   | Syncing                                                         |
   +-----------------------------------------------------------------+ *)

external fsync_job : Unix.file_descr -> [ `unix_fsync ] job = "lwt_unix_fsync_job"
external fsync_result : [ `unix_fsync ] job -> unit = "lwt_unix_fsync_result"
external fsync_free : [ `unix_fsync ] job -> unit = "lwt_unix_fsync_free"

let fsync ch =
  check_descriptor ch;
  execute_job (fsync_job ch.fd) fsync_result fsync_free

#if HAVE_FDATASYNC

external fdatasync_job : Unix.file_descr -> [ `unix_fdatasync ] job = "lwt_unix_fdatasync_job"
external fdatasync_result : [ `unix_fdatasync ] job -> unit = "lwt_unix_fdatasync_result"
external fdatasync_free : [ `unix_fdatasync ] job -> unit = "lwt_unix_fdatasync_free"

let fdatasync ch =
  check_descriptor ch;
  execute_job (fdatasync_job ch.fd) fdatasync_result fdatasync_free

#else

let fdatasync ch =
  fail (Lwt_sys.Not_available "fdatasync")

#endif

(* +-----------------------------------------------------------------+
   | File status                                                     |
   +-----------------------------------------------------------------+ *)

type file_perm = Unix.file_perm

type file_kind =
    Unix.file_kind =
  | S_REG
  | S_DIR
  | S_CHR
  | S_BLK
  | S_LNK
  | S_FIFO
  | S_SOCK

type stats =
    Unix.stats =
    {
      st_dev : int;
      st_ino : int;
      st_kind : file_kind;
      st_perm : file_perm;
      st_nlink : int;
      st_uid : int;
      st_gid : int;
      st_rdev : int;
      st_size : int;
      st_atime : float;
      st_mtime : float;
      st_ctime : float;
    }

#if windows

let stat name =
  return (Unix.stat name)

#else

external stat_job : string -> [ `unix_stat ] job = "lwt_unix_stat_job"
external stat_result : [ `unix_stat ] job -> Unix.stats = "lwt_unix_stat_result"
external stat_free : [ `unix_stat ] job -> unit = "lwt_unix_stat_free"

let stat name =
  execute_job (stat_job name) stat_result stat_free

#endif

#if windows

let lstat name =
  return (Unix.lstat name)

#else

external lstat_job : string -> [ `unix_lstat ] job = "lwt_unix_lstat_job"
external lstat_result : [ `unix_lstat ] job -> Unix.stats = "lwt_unix_lstat_result"
external lstat_free : [ `unix_lstat ] job -> unit = "lwt_unix_lstat_free"

let lstat name =
  execute_job (lstat_job name) lstat_result lstat_free

#endif

#if windows

let fstat ch =
  check_descriptor ch;
  return (Unix.fstat ch.fd)

#else

external fstat_job : Unix.file_descr -> [ `unix_fstat ] job = "lwt_unix_fstat_job"
external fstat_result : [ `unix_fstat ] job -> Unix.stats = "lwt_unix_fstat_result"
external fstat_free : [ `unix_fstat ] job -> unit = "lwt_unix_fstat_free"

let fstat ch =
  check_descriptor ch;
  execute_job (fstat_job ch.fd) fstat_result fstat_free

#endif

#if windows

let isatty ch =
  check_descriptor ch;
  return (Unix.isatty ch.fd)

#else

external isatty_job : Unix.file_descr -> [ `unix_isatty ] job = "lwt_unix_isatty_job"
external isatty_result : [ `unix_isatty ] job -> bool = "lwt_unix_isatty_result"
external isatty_free : [ `unix_isatty ] job -> unit = "lwt_unix_isatty_free"

let isatty ch =
  check_descriptor ch;
  execute_job (isatty_job ch.fd) isatty_result isatty_free

#endif

(* +-----------------------------------------------------------------+
   | File operations on large files                                  |
   +-----------------------------------------------------------------+ *)

module LargeFile =
struct

  type stats =
      Unix.LargeFile.stats =
      {
        st_dev : int;
        st_ino : int;
        st_kind : file_kind;
        st_perm : file_perm;
        st_nlink : int;
        st_uid : int;
        st_gid : int;
        st_rdev : int;
        st_size : int64;
        st_atime : float;
        st_mtime : float;
        st_ctime : float;
      }

#if windows

  let lseek ch offset whence =
    check_descriptor ch;
    return (Unix.LargeFile.lseek ch.fd offset whence)

#else

  external lseek_job : Unix.file_descr -> int64 -> Unix.seek_command -> [ `unix_lseek ] job = "lwt_unix_lseek_64_job"
  external lseek_result : [ `unix_lseek ] job -> int64 = "lwt_unix_lseek_64_result"
  external lseek_free : [ `unix_lseek ] job -> unit = "lwt_unix_lseek_64_free"

  let lseek ch offset whence =
    check_descriptor ch;
    execute_job (lseek_job ch.fd offset whence) lseek_result lseek_free

#endif

#if windows

  let truncate name offset =
    return (Unix.LargeFile.truncate name offset)

#else

  external truncate_job : string -> int64 -> [ `unix_truncate ] job = "lwt_unix_truncate_64_job"
  external truncate_result : [ `unix_truncate ] job -> unit = "lwt_unix_truncate_64_result"
  external truncate_free : [ `unix_truncate ] job -> unit = "lwt_unix_truncate_64_free"

  let truncate name offset =
    execute_job (truncate_job name offset) truncate_result truncate_free

#endif

#if windows

  let ftruncate ch offset =
    check_descriptor ch;
    return (Unix.LargeFile.ftruncate ch.fd offset)

#else

  external ftruncate_job : Unix.file_descr -> int64 -> [ `unix_ftruncate ] job = "lwt_unix_ftruncate_64_job"
  external ftruncate_result : [ `unix_ftruncate ] job -> unit = "lwt_unix_ftruncate_64_result"
  external ftruncate_free : [ `unix_ftruncate ] job -> unit = "lwt_unix_ftruncate_64_free"

  let ftruncate ch offset =
    check_descriptor ch;
    execute_job (ftruncate_job ch.fd offset) ftruncate_result ftruncate_free

#endif

#if windows

  let stat name =
    return (Unix.LargeFile.stat name)

#else

  external stat_job : string -> [ `unix_stat ] job = "lwt_unix_stat_64_job"
  external stat_result : [ `unix_stat ] job -> Unix.LargeFile.stats = "lwt_unix_stat_64_result"
  external stat_free : [ `unix_stat ] job -> unit = "lwt_unix_stat_64_free"

  let stat name =
    execute_job (stat_job name) stat_result stat_free

#endif

#if windows

  let lstat name =
    return (Unix.LargeFile.lstat name)

#else

  external lstat_job : string -> [ `unix_lstat ] job = "lwt_unix_lstat_64_job"
  external lstat_result : [ `unix_lstat ] job -> Unix.LargeFile.stats = "lwt_unix_lstat_64_result"
  external lstat_free : [ `unix_lstat ] job -> unit = "lwt_unix_lstat_64_free"

  let lstat name =
    execute_job (lstat_job name) lstat_result lstat_free

#endif

#if windows

  let fstat ch =
    check_descriptor ch;
    return (Unix.LargeFile.fstat ch.fd)

#else

  external fstat_job : Unix.file_descr -> [ `unix_fstat ] job = "lwt_unix_fstat_64_job"
  external fstat_result : [ `unix_fstat ] job -> Unix.LargeFile.stats = "lwt_unix_fstat_64_result"
  external fstat_free : [ `unix_fstat ] job -> unit = "lwt_unix_fstat_64_free"

  let fstat ch =
    check_descriptor ch;
    execute_job (fstat_job ch.fd) fstat_result fstat_free

#endif

end

(* +-----------------------------------------------------------------+
   | Operations on file names                                        |
   +-----------------------------------------------------------------+ *)

#if windows

let unlink name =
  return (Unix.unlink name)

#else

external unlink_job : string -> [ `unix_unlink ] job = "lwt_unix_unlink_job"
external unlink_result : [ `unix_unlink ] job -> unit = "lwt_unix_unlink_result"
external unlink_free : [ `unix_unlink ] job -> unit = "lwt_unix_unlink_free"

let unlink name =
  execute_job (unlink_job name) unlink_result unlink_free

#endif

#if windows

let rename name1 name2 =
  return (Unix.rename name1 name2)

#else

external rename_job : string -> string -> [ `unix_rename ] job = "lwt_unix_rename_job"
external rename_result : [ `unix_rename ] job -> unit = "lwt_unix_rename_result"
external rename_free : [ `unix_rename ] job -> unit = "lwt_unix_rename_free"

let rename name1 name2 =
  execute_job (rename_job name1 name2) rename_result rename_free

#endif

#if windows

let link name1 name2 =
  return (Unix.link name1 name2)

#else

external link_job : string -> string -> [ `unix_link ] job = "lwt_unix_link_job"
external link_result : [ `unix_link ] job -> unit = "lwt_unix_link_result"
external link_free : [ `unix_link ] job -> unit = "lwt_unix_link_free"

let link name1 name2 =
  execute_job (link_job name1 name2) link_result link_free

#endif

(* +-----------------------------------------------------------------+
   | File permissions and ownership                                  |
   +-----------------------------------------------------------------+ *)

#if windows

let chmod name perms =
  return (Unix.chmod name perms)

#else

external chmod_job : string -> Unix.file_perm -> [ `unix_chmod ] job = "lwt_unix_chmod_job"
external chmod_result : [ `unix_chmod ] job -> unit = "lwt_unix_chmod_result"
external chmod_free : [ `unix_chmod ] job -> unit = "lwt_unix_chmod_free"

let chmod name perms =
  execute_job (chmod_job name perms) chmod_result chmod_free

#endif

#if windows

let fchmod ch perms =
  check_descriptor ch;
  return (Unix.fchmod ch.fd perms)

#else

external fchmod_job : Unix.file_descr -> Unix.file_perm -> [ `unix_fchmod ] job = "lwt_unix_fchmod_job"
external fchmod_result : [ `unix_fchmod ] job -> unit = "lwt_unix_fchmod_result"
external fchmod_free : [ `unix_fchmod ] job -> unit = "lwt_unix_fchmod_free"

let fchmod ch perms =
  check_descriptor ch;
  execute_job (fchmod_job ch.fd perms) fchmod_result fchmod_free

#endif

#if windows

let chown name uid gid =
  return (Unix.chown name uid gid)

#else

external chown_job : string -> int -> int -> [ `unix_chown ] job = "lwt_unix_chown_job"
external chown_result : [ `unix_chown ] job -> unit = "lwt_unix_chown_result"
external chown_free : [ `unix_chown ] job -> unit = "lwt_unix_chown_free"

let chown name uid gid =
  execute_job (chown_job name uid gid) chown_result chown_free

#endif

#if windows

let fchown ch uid gid =
  check_descriptor ch;
  return (Unix.fchown ch.fd uid gid)

#else

external fchown_job : Unix.file_descr -> int -> int -> [ `unix_fchown ] job = "lwt_unix_fchown_job"
external fchown_result : [ `unix_fchown ] job -> unit = "lwt_unix_fchown_result"
external fchown_free : [ `unix_fchown ] job -> unit = "lwt_unix_fchown_free"

let fchown ch uid gid =
  check_descriptor ch;
  execute_job (fchown_job ch.fd uid gid) fchown_result fchown_free

#endif

type access_permission =
    Unix.access_permission =
  | R_OK
  | W_OK
  | X_OK
  | F_OK

#if windows

let access name perms =
  return (Unix.access name perms)

#else

external access_job : string -> Unix.access_permission list -> [ `unix_access ] job = "lwt_unix_access_job"
external access_result : [ `unix_access ] job -> unit = "lwt_unix_access_result"
external access_free : [ `unix_access ] job -> unit = "lwt_unix_access_free"

let access name perms =
  execute_job (access_job name perms) access_result access_free

#endif

(* +-----------------------------------------------------------------+
   | Operations on file descriptors                                  |
   +-----------------------------------------------------------------+ *)

let dup ch =
  check_descriptor ch;
  let fd = Unix.dup ch.fd in
  {
    fd = fd;
    state = Opened;
    set_flags = ch.set_flags;
    blocking =
      if ch.set_flags then
        lazy(Lazy.force ch.blocking >>= function
               | true ->
                   Unix.clear_nonblock fd;
                   return true
               | false ->
                   Unix.set_nonblock fd;
                   return false)
      else
        ch.blocking;
    event_readable = None;
    event_writable = None;
    hooks_readable = Lwt_sequence.create ();
    hooks_writable = Lwt_sequence.create ();
  }

let dup2 ch1 ch2 =
  check_descriptor ch1;
  Unix.dup2 ch1.fd ch2.fd;
  ch2.set_flags <- ch1.set_flags;
  ch2.blocking <- (
    if ch2.set_flags then
      lazy(Lazy.force ch1.blocking >>= function
             | true ->
                 Unix.clear_nonblock ch2.fd;
                 return true
             | false ->
                 Unix.set_nonblock ch2.fd;
                 return false)
    else
      ch1.blocking
  )

let set_close_on_exec ch =
  check_descriptor ch;
  Unix.set_close_on_exec ch.fd

let clear_close_on_exec ch =
  check_descriptor ch;
  Unix.clear_close_on_exec ch.fd

(* +-----------------------------------------------------------------+
   | Directories                                                     |
   +-----------------------------------------------------------------+ *)

#if windows

let mkdir name perms =
  return (Unix.mkdir name perms)

#else

external mkdir_job : string -> Unix.file_perm -> [ `unix_mkdir ] job = "lwt_unix_mkdir_job"
external mkdir_result : [ `unix_mkdir ] job -> unit = "lwt_unix_mkdir_result"
external mkdir_free : [ `unix_mkdir ] job -> unit = "lwt_unix_mkdir_free"

let mkdir name perms =
  execute_job (mkdir_job name perms) mkdir_result mkdir_free

#endif

#if windows

let rmdir name =
  return (Unix.rmdir name)

#else

external rmdir_job : string -> [ `unix_rmdir ] job = "lwt_unix_rmdir_job"
external rmdir_result : [ `unix_rmdir ] job -> unit = "lwt_unix_rmdir_result"
external rmdir_free : [ `unix_rmdir ] job -> unit = "lwt_unix_rmdir_free"

let rmdir name =
  execute_job (rmdir_job name) rmdir_result rmdir_free

#endif

#if windows

let chdir name =
  return (Unix.chdir name)

#else

external chdir_job : string -> [ `unix_chdir ] job = "lwt_unix_chdir_job"
external chdir_result : [ `unix_chdir ] job -> unit = "lwt_unix_chdir_result"
external chdir_free : [ `unix_chdir ] job -> unit = "lwt_unix_chdir_free"

let chdir name =
  execute_job (chdir_job name) chdir_result chdir_free

#endif

#if windows

let chroot name =
  return (Unix.chroot name)

#else

external chroot_job : string -> [ `unix_chroot ] job = "lwt_unix_chroot_job"
external chroot_result : [ `unix_chroot ] job -> unit = "lwt_unix_chroot_result"
external chroot_free : [ `unix_chroot ] job -> unit = "lwt_unix_chroot_free"

let chroot name =
  execute_job (chroot_job name) chroot_result chroot_free

#endif

type dir_handle = Unix.dir_handle

#if windows

let opendir name =
  return (Unix.opendir name)

#else

external opendir_job : string -> [ `unix_opendir ] job = "lwt_unix_opendir_job"
external opendir_result : [ `unix_opendir ] job -> Unix.dir_handle = "lwt_unix_opendir_result"
external opendir_free : [ `unix_opendir ] job -> unit = "lwt_unix_opendir_free"

let opendir name =
  execute_job (opendir_job name) opendir_result opendir_free

#endif

#if windows

let readdir handle =
  return (Unix.readdir handle)

#else

external readdir_job : Unix.dir_handle -> [ `unix_readdir ] job = "lwt_unix_readdir_job"
external readdir_result : [ `unix_readdir ] job -> string = "lwt_unix_readdir_result"
external readdir_free : [ `unix_readdir ] job -> unit = "lwt_unix_readdir_free"

let readdir handle =
  execute_job (readdir_job handle) readdir_result readdir_free

#endif

#if windows

let readdir_n handle count =
  if count < 0 then
    fail (Invalid_argument "Lwt_uinx.readdir_n")
  else
    let array = Array.make count "" in
    let rec fill i =
      if i = count then
        return array
      else
        match try array.(i) <- Unix.readdir handle; true with End_of_file -> false with
          | true ->
              fill (i + 1)
          | false ->
              return (Array.sub array 0 i)
    in
    fill 0

#else

external readdir_n_job : Unix.dir_handle -> int -> [ `unix_readdir_n ] job = "lwt_unix_readdir_n_job"
external readdir_n_result : [ `unix_readdir_n ] job -> string array = "lwt_unix_readdir_n_result"
external readdir_n_free : [ `unix_readdir_n ] job -> unit = "lwt_unix_readdir_n_free"

let readdir_n handle count =
  if count < 0 then
    fail (Invalid_argument "Lwt_uinx.readdir_n")
  else
    execute_job (readdir_n_job handle count) readdir_n_result readdir_n_free

#endif

#if windows

let rewinddir handle =
  return (Unix.rewinddir handle)

#else

external rewinddir_job : Unix.dir_handle -> [ `unix_rewinddir ] job = "lwt_unix_rewinddir_job"
external rewinddir_result : [ `unix_rewinddir ] job -> unit = "lwt_unix_rewinddir_result"
external rewinddir_free : [ `unix_rewinddir ] job -> unit = "lwt_unix_rewinddir_free"

let rewinddir handle =
  execute_job (rewinddir_job handle) rewinddir_result rewinddir_free

#endif

#if windows

let closedir handle =
  return (Unix.closedir handle)

#else

external closedir_job : Unix.dir_handle -> [ `unix_closedir ] job = "lwt_unix_closedir_job"
external closedir_result : [ `unix_closedir ] job -> unit = "lwt_unix_closedir_result"
external closedir_free : [ `unix_closedir ] job -> unit = "lwt_unix_closedir_free"

let closedir handle =
  execute_job (closedir_job handle) closedir_result closedir_free

#endif

type list_directory_state  =
  | LDS_not_started
  | LDS_listing of Unix.dir_handle
  | LDS_done

let cleanup_dir_handle state =
  match !state with
    | LDS_listing handle ->
        ignore (closedir handle)
    | LDS_not_started | LDS_done ->
        ()

let files_of_directory path =
  let state = ref LDS_not_started in
  Lwt_stream.concat
    (Lwt_stream.from
       (fun () ->
          match !state with
            | LDS_not_started ->
                lwt handle = opendir path in
                lwt entries =
                  try_lwt
                    readdir_n handle 1024
                  with exn ->
                    lwt () = closedir handle in
                    raise exn
                in
                if Array.length entries < 1024 then begin
                  state := LDS_done;
                  lwt () = closedir handle in
                  return (Some(Lwt_stream.of_array entries))
                end else begin
                  state := LDS_listing handle;
                  Gc.finalise cleanup_dir_handle state;
                  return (Some(Lwt_stream.of_array entries))
                end
            | LDS_listing handle ->
                lwt entries =
                  try_lwt
                    readdir_n handle 1024
                  with exn ->
                    lwt () = closedir handle in
                    raise exn
                in
                if Array.length entries < 1024 then begin
                  state := LDS_done;
                  lwt () = closedir handle in
                  return (Some(Lwt_stream.of_array entries))
                end else
                  return (Some(Lwt_stream.of_array entries))
            | LDS_done ->
                return None))

(* +-----------------------------------------------------------------+
   | Pipes and redirections                                          |
   +-----------------------------------------------------------------+ *)

let pipe () =
  let (out_fd, in_fd) = Unix.pipe() in
  (mk_ch ~blocking:Lwt_sys.windows out_fd, mk_ch ~blocking:Lwt_sys.windows in_fd)

let pipe_in () =
  let (out_fd, in_fd) = Unix.pipe() in
  (mk_ch ~blocking:Lwt_sys.windows out_fd, in_fd)

let pipe_out () =
  let (out_fd, in_fd) = Unix.pipe() in
  (out_fd, mk_ch ~blocking:Lwt_sys.windows in_fd)

#if windows

let mkfifo name perms =
  return (Unix.mkfifo name perms)

#else

external mkfifo_job : string -> Unix.file_perm -> [ `unix_mkfifo ] job = "lwt_unix_mkfifo_job"
external mkfifo_result : [ `unix_mkfifo ] job -> unit = "lwt_unix_mkfifo_result"
external mkfifo_free : [ `unix_mkfifo ] job -> unit = "lwt_unix_mkfifo_free"

let mkfifo name perms =
  execute_job (mkfifo_job name perms) mkfifo_result mkfifo_free

#endif

(* +-----------------------------------------------------------------+
   | Symbolic links                                                  |
   +-----------------------------------------------------------------+ *)

#if windows

let symlink name1 name2 =
  return (Unix.symlink name1 name2)

#else

external symlink_job : string -> string -> [ `unix_symlink ] job = "lwt_unix_symlink_job"
external symlink_result : [ `unix_symlink ] job -> unit = "lwt_unix_symlink_result"
external symlink_free : [ `unix_symlink ] job -> unit = "lwt_unix_symlink_free"

let symlink name1 name2 =
  execute_job (symlink_job name1 name2) symlink_result symlink_free

#endif

#if windows

let readlink name =
  return (Unix.readlink name)

#else

external readlink_job : string -> [ `unix_readlink ] job = "lwt_unix_readlink_job"
external readlink_result : [ `unix_readlink ] job -> string = "lwt_unix_readlink_result"
external readlink_free : [ `unix_readlink ] job -> unit = "lwt_unix_readlink_free"

let readlink name =
  execute_job (readlink_job name) readlink_result readlink_free

#endif

(* +-----------------------------------------------------------------+
   | Locking                                                         |
   +-----------------------------------------------------------------+ *)

type lock_command =
    Unix.lock_command =
  | F_ULOCK
  | F_LOCK
  | F_TLOCK
  | F_TEST
  | F_RLOCK
  | F_TRLOCK

#if windows

let lockf ch cmd size =
  check_descriptor ch;
  return (Unix.lockf ch.fd cmd size)

#else

external lockf_job : Unix.file_descr -> Unix.lock_command -> int -> [ `unix_lockf ] job = "lwt_unix_lockf_job"
external lockf_result : [ `unix_lockf ] job -> unit = "lwt_unix_lockf_result"
external lockf_free : [ `unix_lockf ] job -> unit = "lwt_unix_lockf_free"

let lockf ch cmd size =
  check_descriptor ch;
  execute_job (lockf_job ch.fd cmd size) lockf_result lockf_free

#endif

(* +-----------------------------------------------------------------+
   | User id, group id                                               |
   +-----------------------------------------------------------------+ *)

type passwd_entry =
    Unix.passwd_entry =
  {
    pw_name : string;
    pw_passwd : string;
    pw_uid : int;
    pw_gid : int;
    pw_gecos : string;
    pw_dir : string;
    pw_shell : string
  }

type group_entry =
    Unix.group_entry =
  {
    gr_name : string;
    gr_passwd : string;
    gr_gid : int;
    gr_mem : string array
  }

#if windows

let getlogin () =
  return (Unix.getlogin ())

#else

external getlogin_job : unit -> [ `unix_getlogin ] job = "lwt_unix_getlogin_job"
external getlogin_result : [ `unix_getlogin ] job -> string = "lwt_unix_getlogin_result"
external getlogin_free : [ `unix_getlogin ] job -> unit = "lwt_unix_getlogin_free"

let getlogin () =
  execute_job (getlogin_job ()) getlogin_result getlogin_free

#endif

#if windows

let getpwnam name =
  return (Unix.getpwnam name)

#else

external getpwnam_job : string -> [ `unix_getpwnam ] job = "lwt_unix_getpwnam_job"
external getpwnam_result : [ `unix_getpwnam ] job -> Unix.passwd_entry = "lwt_unix_getpwnam_result"
external getpwnam_free : [ `unix_getpwnam ] job -> unit = "lwt_unix_getpwnam_free"

let getpwnam name =
  execute_job (getpwnam_job name) getpwnam_result getpwnam_free

#endif

#if windows

let getgrnam name =
  return (Unix.getgrnam name)

#else

external getgrnam_job : string -> [ `unix_getgrnam ] job = "lwt_unix_getgrnam_job"
external getgrnam_result : [ `unix_getgrnam ] job -> Unix.group_entry = "lwt_unix_getgrnam_result"
external getgrnam_free : [ `unix_getgrnam ] job -> unit = "lwt_unix_getgrnam_free"

let getgrnam name =
  execute_job (getgrnam_job name) getgrnam_result getgrnam_free

#endif

#if windows

let getpwuid uid =
  return (Unix.getpwuid uid)

#else

external getpwuid_job : int -> [ `unix_getpwuid ] job = "lwt_unix_getpwuid_job"
external getpwuid_result : [ `unix_getpwuid ] job -> Unix.passwd_entry = "lwt_unix_getpwuid_result"
external getpwuid_free : [ `unix_getpwuid ] job -> unit = "lwt_unix_getpwuid_free"

let getpwuid uid =
  execute_job (getpwuid_job uid) getpwuid_result getpwuid_free

#endif

#if windows

let getgrgid gid =
  return (Unix.getgrgid gid)

#else

external getgrgid_job : int -> [ `unix_getgrgid ] job = "lwt_unix_getgrgid_job"
external getgrgid_result : [ `unix_getgrgid ] job -> Unix.group_entry = "lwt_unix_getgrgid_result"
external getgrgid_free : [ `unix_getgrgid ] job -> unit = "lwt_unix_getgrgid_free"

let getgrgid gid =
  execute_job (getgrgid_job gid) getgrgid_result getgrgid_free

#endif

(* +-----------------------------------------------------------------+
   | Sockets                                                         |
   +-----------------------------------------------------------------+ *)

type msg_flag =
    Unix.msg_flag =
  | MSG_OOB
  | MSG_DONTROUTE
  | MSG_PEEK

#if windows
let stub_recv = Unix.recv
#else
external stub_recv : Unix.file_descr -> string -> int -> int -> Unix.msg_flag list -> int = "lwt_unix_recv"
#endif

let recv ch buf pos len flags =
  if pos < 0 || len < 0 || pos > String.length buf - len then
    invalid_arg "Lwt_unix.recv"
  else
    wrap_syscall Read ch (fun () -> stub_recv ch.fd buf pos len flags)

#if windows
let stub_send = Unix.send
#else
external stub_send : Unix.file_descr -> string -> int -> int -> Unix.msg_flag list -> int = "lwt_unix_send"
#endif

let send ch buf pos len flags =
  if pos < 0 || len < 0 || pos > String.length buf - len then
    invalid_arg "Lwt_unix.send"
  else
    wrap_syscall Write ch (fun () -> stub_send ch.fd buf pos len flags)

#if windows
let stub_recvfrom = Unix.recvfrom
#else
external stub_recvfrom : Unix.file_descr -> string -> int -> int -> Unix.msg_flag list -> int * Unix.sockaddr = "lwt_unix_recvfrom"
#endif

let recvfrom ch buf pos len flags =
  if pos < 0 || len < 0 || pos > String.length buf - len then
    invalid_arg "Lwt_unix.recvfrom"
  else
    wrap_syscall Read ch (fun () -> stub_recvfrom ch.fd buf pos len flags)

#if windows
let stub_sendto = Unix.sendto
#else
external stub_sendto : Unix.file_descr -> string -> int -> int -> Unix.msg_flag list -> Unix.sockaddr -> int = "lwt_unix_sendto_byte" "lwt_unix_sendto"
#endif

let sendto ch buf pos len flags addr =
  if pos < 0 || len < 0 || pos > String.length buf - len then
    invalid_arg "Lwt_unix.sendto"
  else
    wrap_syscall Write ch (fun () -> stub_sendto ch.fd buf pos len flags addr)

type io_vector = {
  iov_buffer : string;
  iov_offset : int;
  iov_length : int;
}

let io_vector ~buffer ~offset ~length = {
  iov_buffer = buffer;
  iov_offset = offset;
  iov_length = length;
}

let check_io_vectors func_name iovs =
  List.iter (fun iov ->
               if iov.iov_offset < 0
                 || iov.iov_length < 0
                 || iov.iov_offset > String.length iov.iov_buffer - iov.iov_length then
                   invalid_arg func_name) iovs

#if windows

let recv_msg ~socket ~io_vectors =
  raise (Lwt_sys.Not_available "recv_msg")

#else

external stub_recv_msg : Unix.file_descr -> int -> io_vector list -> int * Unix.file_descr list = "lwt_unix_recv_msg"

let recv_msg ~socket ~io_vectors =
  check_io_vectors "Lwt_unix.recv_msg" io_vectors;
  let n_iovs = List.length io_vectors in
  wrap_syscall Read socket
    (fun () ->
       stub_recv_msg socket.fd n_iovs io_vectors)

#endif

#if windows

let send_msg ~socket ~io_vectors ~fds =
  raise (Lwt_sys.Not_available "send_msg")

#else

external stub_send_msg : Unix.file_descr -> int -> io_vector list -> int -> Unix.file_descr list -> int = "lwt_unix_send_msg"

let send_msg ~socket ~io_vectors ~fds =
  check_io_vectors "Lwt_unix.send_msg" io_vectors;
  let n_iovs = List.length io_vectors and n_fds = List.length fds in
  wrap_syscall Write socket
    (fun () ->
       stub_send_msg socket.fd n_iovs io_vectors n_fds fds)

#endif

type inet_addr = Unix.inet_addr

type socket_domain =
    Unix.socket_domain =
  | PF_UNIX
  | PF_INET
  | PF_INET6

type socket_type =
    Unix.socket_type =
  | SOCK_STREAM
  | SOCK_DGRAM
  | SOCK_RAW
  | SOCK_SEQPACKET

type sockaddr = Unix.sockaddr = ADDR_UNIX of string | ADDR_INET of inet_addr * int

let socket dom typ proto =
  let s = Unix.socket dom typ proto in
  mk_ch ~blocking:false s

type shutdown_command =
    Unix.shutdown_command =
  | SHUTDOWN_RECEIVE
  | SHUTDOWN_SEND
  | SHUTDOWN_ALL

let shutdown ch shutdown_command =
  check_descriptor ch;
  Unix.shutdown ch.fd shutdown_command

let socketpair dom typ proto =
  let (s1, s2) = Unix.socketpair dom typ proto in
  (mk_ch ~blocking:false s1, mk_ch ~blocking:false s2)

let accept ch =
  wrap_syscall Read ch (fun _ -> let (fd, addr) = Unix.accept ch.fd in (mk_ch ~blocking:false fd, addr))

let accept_n ch n =
  let l = ref [] in
  lwt blocking = Lazy.force ch.blocking in
  try_lwt
    wrap_syscall Read ch begin fun () ->
      begin
        try
          for i = 1 to n do
            if blocking && not (stub_readable ch.fd) then raise Retry;
            let fd, addr = Unix.accept ch.fd in
            l := (mk_ch ~blocking:false fd, addr) :: !l
          done
        with
          | (Unix.Unix_error((Unix.EAGAIN | Unix.EWOULDBLOCK | Unix.EINTR), _, _) | Retry) when !l <> [] ->
              (* Ignore blocking errors if we have at least one file-descriptor: *)
              ()
      end;
      (List.rev !l, None)
    end
  with exn ->
    return (List.rev !l, Some exn)

#if windows

let connect ch addr =
  (* [in_progress] tell wether connection has started but not
     terminated: *)
  let in_progress = ref false in
  wrap_syscall Write ch begin fun () ->
    if !in_progress then
      (* Nothing works without this test and i have no idea why... *)
      if writable ch then
        try
          Unix.connect ch.fd addr
        with
          | Unix.Unix_error (Unix.EISCONN, _, _) ->
              (* This is the windows way of telling that the connection
                 has completed. *)
              ()
      else
        raise Retry
    else
      try
        Unix.connect ch.fd addr
      with
        | Unix.Unix_error (Unix.EWOULDBLOCK, _, _) ->
            in_progress := true;
            raise Retry
  end

#else

let connect ch addr =
  (* [in_progress] tell wether connection has started but not
     terminated: *)
  let in_progress = ref false in
  wrap_syscall Write ch begin fun () ->
    if !in_progress then
      (* If the connection is in progress, [getsockopt_error] tells
         wether it succceed: *)
      match Unix.getsockopt_error ch.fd with
        | None ->
            (* The socket is connected *)
            ()
        | Some err ->
            (* An error happened: *)
            raise (Unix.Unix_error(err, "connect", ""))
    else
      try
        (* We should pass only one time here, unless the system call
           is interrupted by a signal: *)
        Unix.connect ch.fd addr
      with
        | Unix.Unix_error (Unix.EINPROGRESS, _, _) ->
            in_progress := true;
            raise Retry
  end

#endif

let setsockopt ch opt v =
  check_descriptor ch;
  Unix.setsockopt ch.fd opt v

let bind ch addr =
  check_descriptor ch;
  Unix.bind ch.fd addr

let listen ch cnt =
  check_descriptor ch;
  Unix.listen ch.fd cnt

let getpeername ch =
  check_descriptor ch;
  Unix.getpeername ch.fd

let getsockname ch =
  check_descriptor ch;
  Unix.getsockname ch.fd

type credentials = {
  cred_pid : int;
  cred_uid : int;
  cred_gid : int;
}

#if HAVE_GET_CREDENTIALS

external stub_get_credentials : Unix.file_descr -> credentials = "lwt_unix_get_credentials"

let get_credentials ch =
  check_descriptor ch;
  stub_get_credentials ch.fd

#else

let get_credentials ch =
  raise (Lwt_sys.Not_available "get_credentials")

#endif

(* +-----------------------------------------------------------------+
   | Socket options                                                  |
   +-----------------------------------------------------------------+ *)

type socket_bool_option =
    Unix.socket_bool_option =
  | SO_DEBUG
  | SO_BROADCAST
  | SO_REUSEADDR
  | SO_KEEPALIVE
  | SO_DONTROUTE
  | SO_OOBINLINE
  | SO_ACCEPTCONN
  | TCP_NODELAY
  | IPV6_ONLY

type socket_int_option =
    Unix.socket_int_option =
  | SO_SNDBUF
  | SO_RCVBUF
  | SO_ERROR
  | SO_TYPE
  | SO_RCVLOWAT
  | SO_SNDLOWAT

type socket_optint_option = Unix.socket_optint_option = SO_LINGER

type socket_float_option =
    Unix.socket_float_option =
  | SO_RCVTIMEO
  | SO_SNDTIMEO

let getsockopt ch opt =
  check_descriptor ch;
  Unix.getsockopt ch.fd opt

let setsockopt ch opt x =
  check_descriptor ch;
  Unix.setsockopt ch.fd opt x

let getsockopt_int ch opt =
  check_descriptor ch;
  Unix.getsockopt_int ch.fd opt

let setsockopt_int ch opt x =
  check_descriptor ch;
  Unix.setsockopt_int ch.fd opt x

let getsockopt_optint ch opt =
  check_descriptor ch;
  Unix.getsockopt_optint ch.fd opt

let setsockopt_optint ch opt x =
  check_descriptor ch;
  Unix.setsockopt_optint ch.fd opt x

let getsockopt_float ch opt =
  check_descriptor ch;
  Unix.getsockopt_float ch.fd opt

let setsockopt_float ch opt x =
  check_descriptor ch;
  Unix.setsockopt_float ch.fd opt x

let getsockopt_error ch =
  check_descriptor ch;
  Unix.getsockopt_error ch.fd

(* +-----------------------------------------------------------------+
   | Host and protocol databases                                     |
   +-----------------------------------------------------------------+ *)

type host_entry =
    Unix.host_entry =
    {
      h_name : string;
      h_aliases : string array;
      h_addrtype : socket_domain;
      h_addr_list : inet_addr array
    }

type protocol_entry =
    Unix.protocol_entry =
    {
      p_name : string;
      p_aliases : string array;
      p_proto : int
    }

type service_entry =
    Unix.service_entry =
    {
      s_name : string;
      s_aliases : string array;
      s_port : int;
      s_proto : string
    }

#if windows

let gethostname () =
  return (Unix.gethostname ())

#else

external gethostname_job : unit -> [ `unix_gethostname ] job = "lwt_unix_gethostname_job"
external gethostname_result : [ `unix_gethostname ] job -> string = "lwt_unix_gethostname_result"
external gethostname_free : [ `unix_gethostname ] job -> unit = "lwt_unix_gethostname_free"

let gethostname () =
  execute_job (gethostname_job ()) gethostname_result gethostname_free

#endif

#if windows

let gethostbyname name =
  return (Unix.gethostbyname name)

#else

external gethostbyname_job : string -> [ `unix_gethostbyname ] job = "lwt_unix_gethostbyname_job"
external gethostbyname_result : [ `unix_gethostbyname ] job -> Unix.host_entry = "lwt_unix_gethostbyname_result"
external gethostbyname_free : [ `unix_gethostbyname ] job -> unit = "lwt_unix_gethostbyname_free"

let gethostbyname name =
  execute_job (gethostbyname_job name) gethostbyname_result gethostbyname_free

#endif

#if windows

let gethostbyaddr addr =
  return (Unix.gethostbyaddr addr)

#else

external gethostbyaddr_job : Unix.inet_addr -> [ `unix_gethostbyaddr ] job = "lwt_unix_gethostbyaddr_job"
external gethostbyaddr_result : [ `unix_gethostbyaddr ] job -> Unix.host_entry = "lwt_unix_gethostbyaddr_result"
external gethostbyaddr_free : [ `unix_gethostbyaddr ] job -> unit = "lwt_unix_gethostbyaddr_free"

let gethostbyaddr addr =
  execute_job (gethostbyaddr_job addr) gethostbyaddr_result gethostbyaddr_free

#endif

#if windows

let getprotobyname name =
  return (Unix.getprotobyname name)

#else

external getprotobyname_job : string -> [ `unix_getprotobyname ] job = "lwt_unix_getprotobyname_job"
external getprotobyname_result : [ `unix_getprotobyname ] job -> Unix.protocol_entry = "lwt_unix_getprotobyname_result"
external getprotobyname_free : [ `unix_getprotobyname ] job -> unit = "lwt_unix_getprotobyname_free"

let getprotobyname name =
  execute_job (getprotobyname_job name) getprotobyname_result getprotobyname_free

#endif

#if windows

let getprotobynumber number =
  return (Unix.getprotobynumber number)

#else

external getprotobynumber_job : int -> [ `unix_getprotobynumber ] job = "lwt_unix_getprotobynumber_job"
external getprotobynumber_result : [ `unix_getprotobynumber ] job -> Unix.protocol_entry = "lwt_unix_getprotobynumber_result"
external getprotobynumber_free : [ `unix_getprotobynumber ] job -> unit = "lwt_unix_getprotobynumber_free"

let getprotobynumber number =
  execute_job (getprotobynumber_job number) getprotobynumber_result getprotobynumber_free

#endif

#if windows

let getservbyname name x =
  return (Unix.getservbyname name x)

#else

external getservbyname_job : string -> string -> [ `unix_getservbyname ] job = "lwt_unix_getservbyname_job"
external getservbyname_result : [ `unix_getservbyname ] job -> Unix.service_entry = "lwt_unix_getservbyname_result"
external getservbyname_free : [ `unix_getservbyname ] job -> unit = "lwt_unix_getservbyname_free"

let getservbyname name x =
  execute_job (getservbyname_job name x) getservbyname_result getservbyname_free

#endif

#if windows

let getservbyport port x =
  return (Unix.getservbyport port x)

#else

external getservbyport_job : int -> string -> [ `unix_getservbyport ] job = "lwt_unix_getservbyport_job"
external getservbyport_result : [ `unix_getservbyport ] job -> Unix.service_entry = "lwt_unix_getservbyport_result"
external getservbyport_free : [ `unix_getservbyport ] job -> unit = "lwt_unix_getservbyport_free"

let getservbyport port x =
  execute_job (getservbyport_job port x) getservbyport_result getservbyport_free

#endif

type addr_info =
    Unix.addr_info =
    {
      ai_family : socket_domain;
      ai_socktype : socket_type;
      ai_protocol : int;
      ai_addr : sockaddr;
      ai_canonname : string;
    }

type getaddrinfo_option =
    Unix.getaddrinfo_option =
  | AI_FAMILY of socket_domain
  | AI_SOCKTYPE of socket_type
  | AI_PROTOCOL of int
  | AI_NUMERICHOST
  | AI_CANONNAME
  | AI_PASSIVE

#if windows

let getaddrinfo host service opts =
  return (Unix.getaddrinfo host service opts)

#else

external getaddrinfo_job : string -> string -> Unix.getaddrinfo_option list -> [ `unix_getaddrinfo ] job = "lwt_unix_getaddrinfo_job"
external getaddrinfo_result : [ `unix_getaddrinfo ] job -> Unix.addr_info list = "lwt_unix_getaddrinfo_result"
external getaddrinfo_free : [ `unix_getaddrinfo ] job -> unit = "lwt_unix_getaddrinfo_free"

let getaddrinfo host service opts =
  execute_job (getaddrinfo_job host service opts) getaddrinfo_result getaddrinfo_free

#endif

type name_info =
    Unix.name_info =
    {
      ni_hostname : string;
      ni_service : string;
    }

type getnameinfo_option =
    Unix.getnameinfo_option =
  | NI_NOFQDN
  | NI_NUMERICHOST
  | NI_NAMEREQD
  | NI_NUMERICSERV
  | NI_DGRAM

#if windows

let getnameinfo addr opts =
  return (Unix.getnameinfo addr opts)

#else

external getnameinfo_job : Unix.sockaddr -> Unix.getnameinfo_option list -> [ `unix_getnameinfo ] job = "lwt_unix_getnameinfo_job"
external getnameinfo_result : [ `unix_getnameinfo ] job -> Unix.name_info = "lwt_unix_getnameinfo_result"
external getnameinfo_free : [ `unix_getnameinfo ] job -> unit = "lwt_unix_getnameinfo_free"

let getnameinfo addr opts =
  execute_job (getnameinfo_job addr opts) getnameinfo_result getnameinfo_free

#endif

(* +-----------------------------------------------------------------+
   | Terminal interface                                              |
   +-----------------------------------------------------------------+ *)


type terminal_io =
    Unix.terminal_io =
    {
      mutable c_ignbrk : bool;
      mutable c_brkint : bool;
      mutable c_ignpar : bool;
      mutable c_parmrk : bool;
      mutable c_inpck : bool;
      mutable c_istrip : bool;
      mutable c_inlcr : bool;
      mutable c_igncr : bool;
      mutable c_icrnl : bool;
      mutable c_ixon : bool;
      mutable c_ixoff : bool;
      mutable c_opost : bool;
      mutable c_obaud : int;
      mutable c_ibaud : int;
      mutable c_csize : int;
      mutable c_cstopb : int;
      mutable c_cread : bool;
      mutable c_parenb : bool;
      mutable c_parodd : bool;
      mutable c_hupcl : bool;
      mutable c_clocal : bool;
      mutable c_isig : bool;
      mutable c_icanon : bool;
      mutable c_noflsh : bool;
      mutable c_echo : bool;
      mutable c_echoe : bool;
      mutable c_echok : bool;
      mutable c_echonl : bool;
      mutable c_vintr : char;
      mutable c_vquit : char;
      mutable c_verase : char;
      mutable c_vkill : char;
      mutable c_veof : char;
      mutable c_veol : char;
      mutable c_vmin : int;
      mutable c_vtime : int;
      mutable c_vstart : char;
      mutable c_vstop : char;
    }

type setattr_when =
    Unix.setattr_when =
  | TCSANOW
  | TCSADRAIN
  | TCSAFLUSH

type flush_queue =
    Unix.flush_queue =
  | TCIFLUSH
  | TCOFLUSH
  | TCIOFLUSH

type flow_action =
    Unix.flow_action =
  | TCOOFF
  | TCOON
  | TCIOFF
  | TCION

#if windows

let tcgetattr ch =
  check_descriptor ch;
  return (Unix.tcgetattr ch.fd)

#else

external tcgetattr_job : Unix.file_descr -> [ `unix_tcgetattr ] job = "lwt_unix_tcgetattr_job"
external tcgetattr_result : [ `unix_tcgetattr ] job -> Unix.terminal_io = "lwt_unix_tcgetattr_result"
external tcgetattr_free : [ `unix_tcgetattr ] job -> unit = "lwt_unix_tcgetattr_free"

let tcgetattr ch =
  check_descriptor ch;
  execute_job (tcgetattr_job ch.fd) tcgetattr_result tcgetattr_free

#endif

#if windows

let tcsetattr ch when_ attrs =
  check_descriptor ch;
  return (Unix.tcsetattr ch.fd when_ attrs)

#else

external tcsetattr_job : Unix.file_descr -> Unix.setattr_when -> Unix.terminal_io -> [ `unix_tcsetattr ] job = "lwt_unix_tcsetattr_job"
external tcsetattr_result : [ `unix_tcsetattr ] job -> unit = "lwt_unix_tcsetattr_result"
external tcsetattr_free : [ `unix_tcsetattr ] job -> unit = "lwt_unix_tcsetattr_free"

let tcsetattr ch when_ attrs =
  check_descriptor ch;
  execute_job (tcsetattr_job ch.fd when_ attrs) tcsetattr_result tcsetattr_free

#endif

#if windows

let tcsendbreak ch delay =
  check_descriptor ch;
  return (Unix.tcsendbreak ch.fd delay)

#else

external tcsendbreak_job : Unix.file_descr -> int -> [ `unix_tcsendbreak ] job = "lwt_unix_tcsendbreak_job"
external tcsendbreak_result : [ `unix_tcsendbreak ] job -> unit = "lwt_unix_tcsendbreak_result"
external tcsendbreak_free : [ `unix_tcsendbreak ] job -> unit = "lwt_unix_tcsendbreak_free"

let tcsendbreak ch delay =
  check_descriptor ch;
  execute_job (tcsendbreak_job ch.fd delay) tcsendbreak_result tcsendbreak_free

#endif

#if windows

let tcdrain ch =
  check_descriptor ch;
  return (Unix.tcdrain ch.fd)

#else

external tcdrain_job : Unix.file_descr -> [ `unix_tcdrain ] job = "lwt_unix_tcdrain_job"
external tcdrain_result : [ `unix_tcdrain ] job -> unit = "lwt_unix_tcdrain_result"
external tcdrain_free : [ `unix_tcdrain ] job -> unit = "lwt_unix_tcdrain_free"

let tcdrain ch =
  check_descriptor ch;
  execute_job (tcdrain_job ch.fd) tcdrain_result tcdrain_free

#endif

#if windows

let tcflush ch q =
  check_descriptor ch;
  return (Unix.tcflush ch.fd q)

#else

external tcflush_job : Unix.file_descr -> Unix.flush_queue -> [ `unix_tcflush ] job = "lwt_unix_tcflush_job"
external tcflush_result : [ `unix_tcflush ] job -> unit = "lwt_unix_tcflush_result"
external tcflush_free : [ `unix_tcflush ] job -> unit = "lwt_unix_tcflush_free"

let tcflush ch q =
  check_descriptor ch;
  execute_job (tcflush_job ch.fd q) tcflush_result tcflush_free

#endif

#if windows

let tcflow ch act =
  check_descriptor ch;
  return (Unix.tcflow ch.fd act)

#else

external tcflow_job : Unix.file_descr -> Unix.flow_action -> [ `unix_tcflow ] job = "lwt_unix_tcflow_job"
external tcflow_result : [ `unix_tcflow ] job -> unit = "lwt_unix_tcflow_result"
external tcflow_free : [ `unix_tcflow ] job -> unit = "lwt_unix_tcflow_free"

let tcflow ch act =
  check_descriptor ch;
  execute_job (tcflow_job ch.fd act) tcflow_result tcflow_free

#endif

(* +-----------------------------------------------------------------+
   | Reading notifications                                           |
   +-----------------------------------------------------------------+ *)

(* Buffer used to receive notifications: *)
let notification_buffer = String.create 4

external init_notification : unit -> Unix.file_descr = "lwt_unix_init_notification"
external send_notification : int -> unit = "lwt_unix_send_notification_stub"
external recv_notifications : unit -> int array = "lwt_unix_recv_notifications"

let handle_notification id =
  match try Some(Notifiers.find notifiers id) with Not_found -> None with
    | Some notifier ->
        if notifier.notify_once then
          stop_notification id;
        notifier.notify_handler ()
    | None ->
        ()

let rec handle_notifications ev =
  (* Process available notifications. *)
  Array.iter handle_notification (recv_notifications ())

let event_notifications = ref (Lwt_engine.on_readable (init_notification ()) handle_notifications)

(* +-----------------------------------------------------------------+
   | Signals                                                         |
   +-----------------------------------------------------------------+ *)

module Signal_map = Map.Make(struct type t = int let compare a b = a - b end)

let signals = ref Signal_map.empty
let signal_count () =
  Signal_map.fold
    (fun signum (id, actions) len -> len + Lwt_sequence.length actions)
    !signals
    0

type signal_handler_id = unit Lazy.t

let on_signal signum handler =
  let notification, actions =
    try
      Signal_map.find signum !signals
    with Not_found ->
      let actions = Lwt_sequence.create () in
      let notification = make_notification (fun () -> Lwt_sequence.iter_l (fun f -> f signum) actions) in
      (try
         Sys.set_signal signum (Sys.Signal_handle (fun signum -> send_notification notification))
       with exn ->
         stop_notification notification;
         raise exn);
      signals := Signal_map.add signum (notification, actions) !signals;
      (notification, actions)
  in
  let node = Lwt_sequence.add_r handler actions in
  lazy(Lwt_sequence.remove node;
       if Lwt_sequence.is_empty actions then begin
         signals := Signal_map.remove signum !signals;
         stop_notification notification;
         Sys.set_signal signum Sys.Signal_default
       end)

let disable_signal_handler = Lazy.force

let reinstall_signal_handler signum =
  match try Some (Signal_map.find signum !signals) with Not_found -> None with
    | Some (notification, actions) ->
        Sys.set_signal signum (Sys.Signal_handle (fun signum -> send_notification notification))
    | None ->
        ()

(* +-----------------------------------------------------------------+
   | Processes                                                       |
   +-----------------------------------------------------------------+ *)

external reset_after_fork : unit -> unit = "lwt_unix_reset_after_fork"

let fork () =
  match Unix.fork () with
    | 0 ->
        (* Reset threading. *)
        reset_after_fork ();
        (* Stop the old event for notifications. *)
        Lwt_engine.stop_event !event_notifications;
        (* Reinitialise the notification system. *)
        event_notifications := Lwt_engine.on_readable (init_notification ()) handle_notifications;
        (* Collect all pending jobs. *)
        let l = Lwt_sequence.fold_l (fun w l -> w :: l) jobs [] in
        (* Remove them all. *)
        Lwt_sequence.iter_node_l Lwt_sequence.remove jobs;
        (* And cancel them all. We yield first so that if the program
           do an exec just after, it won't be executed. *)
        on_termination (Lwt_main.yield ()) (fun () -> List.iter cancel l);
        0
    | pid ->
        pid

type process_status =
    Unix.process_status =
  | WEXITED of int
  | WSIGNALED of int
  | WSTOPPED of int

type wait_flag =
    Unix.wait_flag =
  | WNOHANG
  | WUNTRACED

let has_wait4 = not Lwt_sys.windows

type resource_usage = { ru_utime : float; ru_stime : float }

#if windows

let stub_wait4 flags pid =
  let pid, status = Unix.waitpid flags pid in
  (pid, status, { ru_utime = 0.0; ru_stime = 0.0 })

#else

external stub_wait4 : Unix.wait_flag list -> int -> int * Unix.process_status * resource_usage = "lwt_unix_wait4"

#endif

let wait_children = Lwt_sequence.create ()
let wait_count () = Lwt_sequence.length wait_children

#if not windows
let () =
  ignore begin
    on_signal Sys.sigchld
      (fun _ ->
         Lwt_sequence.iter_node_l begin fun node ->
           let wakener, flags, pid = Lwt_sequence.get node in
           try
             let (pid', _, _) as v = stub_wait4 flags pid in
             if pid' <> 0 then begin
               Lwt_sequence.remove node;
               Lwt.wakeup wakener v
             end
           with e ->
             Lwt_sequence.remove node;
             Lwt.wakeup_exn wakener e
         end wait_children)
  end
#endif

let _waitpid flags pid =
  try_lwt
    return (Unix.waitpid flags pid)

#if windows

let waitpid = _waitpid

#else

let waitpid flags pid =
  if List.mem Unix.WNOHANG flags then
    _waitpid flags pid
  else
    let flags = Unix.WNOHANG :: flags in
    lwt ((pid', _) as res) = _waitpid flags pid in
    if pid' <> 0 then
      return res
    else begin
      let (res, w) = Lwt.task () in
      let node = Lwt_sequence.add_l (w, flags, pid) wait_children in
      Lwt.on_cancel res (fun _ -> Lwt_sequence.remove node);
      lwt (pid, status, _) = res in
      return (pid, status)
    end

#endif

let _wait4 flags pid =
  try_lwt
    return (stub_wait4 flags pid)

#if windows

let wait4 = _wait4

#else

let wait4 flags pid =
  if List.mem Unix.WNOHANG flags then
    _wait4 flags pid
  else
    let flags = Unix.WNOHANG :: flags in
    lwt (pid', _, _) as res = _wait4 flags pid in
    if pid' <> 0 then
      return res
    else begin
      let (res, w) = Lwt.task () in
      let node = Lwt_sequence.add_l (w, flags, pid) wait_children in
      Lwt.on_cancel res (fun _ -> Lwt_sequence.remove node);
      res
    end

#endif

let wait () = waitpid [] (-1)

let system cmd =
  match Unix.fork () with
    | 0 ->
        begin try
          Unix.execv "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
        with _ ->
          exit 127
        end
    | id ->
        waitpid [] id >|= snd

(* +-----------------------------------------------------------------+
   | Misc                                                            |
   +-----------------------------------------------------------------+ *)

let run = Lwt_main.run

let handle_unix_error f x =
  try_lwt
    f x
  with exn ->
    Unix.handle_unix_error (fun () -> raise exn) ()

(* +-----------------------------------------------------------------+
   | System thread pool                                              |
   +-----------------------------------------------------------------+ *)

external pool_size : unit -> int = "lwt_unix_pool_size" "noalloc"
external set_pool_size : int -> unit = "lwt_unix_set_pool_size" "noalloc"
external thread_count : unit -> int = "lwt_unix_thread_count" "noalloc"
external thread_waiting_count : unit -> int = "lwt_unix_thread_waiting_count" "noalloc"

(* +-----------------------------------------------------------------+
   | CPUs                                                            |
   +-----------------------------------------------------------------+ *)

#if HAVE_GETCPU
external get_cpu : unit -> int = "lwt_unix_get_cpu"
#else
let get_cpu () = raise (Lwt_sys.Not_available "get_cpu")
#endif

#if HAVE_AFFINITY

external stub_get_affinity : int -> int list = "lwt_unix_get_affinity"
external stub_set_affinity : int -> int list -> unit = "lwt_unix_set_affinity"

let get_affinity ?(pid=0) () = stub_get_affinity pid
let set_affinity ?(pid=0) l = stub_set_affinity pid l

#else

let get_affinity ?pid () = raise (Lwt_sys.Not_available "get_affinity")
let set_affinity ?pid l = raise (Lwt_sys.Not_available "set_affinity")

#endif

(* +-----------------------------------------------------------------+
   | Error printing                                                  |
   +-----------------------------------------------------------------+ *)

let () =
  Printexc.register_printer
    (function
       | Unix.Unix_error(error, func, arg) ->
           let error =
             match error with
               | Unix.E2BIG -> "E2BIG"
               | Unix.EACCES -> "EACCES"
               | Unix.EAGAIN -> "EAGAIN"
               | Unix.EBADF -> "EBADF"
               | Unix.EBUSY -> "EBUSY"
               | Unix.ECHILD -> "ECHILD"
               | Unix.EDEADLK -> "EDEADLK"
               | Unix.EDOM -> "EDOM"
               | Unix.EEXIST -> "EEXIST"
               | Unix.EFAULT -> "EFAULT"
               | Unix.EFBIG -> "EFBIG"
               | Unix.EINTR -> "EINTR"
               | Unix.EINVAL -> "EINVAL"
               | Unix.EIO -> "EIO"
               | Unix.EISDIR -> "EISDIR"
               | Unix.EMFILE -> "EMFILE"
               | Unix.EMLINK -> "EMLINK"
               | Unix.ENAMETOOLONG -> "ENAMETOOLONG"
               | Unix.ENFILE -> "ENFILE"
               | Unix.ENODEV -> "ENODEV"
               | Unix.ENOENT -> "ENOENT"
               | Unix.ENOEXEC -> "ENOEXEC"
               | Unix.ENOLCK -> "ENOLCK"
               | Unix.ENOMEM -> "ENOMEM"
               | Unix.ENOSPC -> "ENOSPC"
               | Unix.ENOSYS -> "ENOSYS"
               | Unix.ENOTDIR -> "ENOTDIR"
               | Unix.ENOTEMPTY -> "ENOTEMPTY"
               | Unix.ENOTTY -> "ENOTTY"
               | Unix.ENXIO -> "ENXIO"
               | Unix.EPERM -> "EPERM"
               | Unix.EPIPE -> "EPIPE"
               | Unix.ERANGE -> "ERANGE"
               | Unix.EROFS -> "EROFS"
               | Unix.ESPIPE -> "ESPIPE"
               | Unix.ESRCH -> "ESRCH"
               | Unix.EXDEV -> "EXDEV"
               | Unix.EWOULDBLOCK -> "EWOULDBLOCK"
               | Unix.EINPROGRESS -> "EINPROGRESS"
               | Unix.EALREADY -> "EALREADY"
               | Unix.ENOTSOCK -> "ENOTSOCK"
               | Unix.EDESTADDRREQ -> "EDESTADDRREQ"
               | Unix.EMSGSIZE -> "EMSGSIZE"
               | Unix.EPROTOTYPE -> "EPROTOTYPE"
               | Unix.ENOPROTOOPT -> "ENOPROTOOPT"
               | Unix.EPROTONOSUPPORT -> "EPROTONOSUPPORT"
               | Unix.ESOCKTNOSUPPORT -> "ESOCKTNOSUPPORT"
               | Unix.EOPNOTSUPP -> "EOPNOTSUPP"
               | Unix.EPFNOSUPPORT -> "EPFNOSUPPORT"
               | Unix.EAFNOSUPPORT -> "EAFNOSUPPORT"
               | Unix.EADDRINUSE -> "EADDRINUSE"
               | Unix.EADDRNOTAVAIL -> "EADDRNOTAVAIL"
               | Unix.ENETDOWN -> "ENETDOWN"
               | Unix.ENETUNREACH -> "ENETUNREACH"
               | Unix.ENETRESET -> "ENETRESET"
               | Unix.ECONNABORTED -> "ECONNABORTED"
               | Unix.ECONNRESET -> "ECONNRESET"
               | Unix.ENOBUFS -> "ENOBUFS"
               | Unix.EISCONN -> "EISCONN"
               | Unix.ENOTCONN -> "ENOTCONN"
               | Unix.ESHUTDOWN -> "ESHUTDOWN"
               | Unix.ETOOMANYREFS -> "ETOOMANYREFS"
               | Unix.ETIMEDOUT -> "ETIMEDOUT"
               | Unix.ECONNREFUSED -> "ECONNREFUSED"
               | Unix.EHOSTDOWN -> "EHOSTDOWN"
               | Unix.EHOSTUNREACH -> "EHOSTUNREACH"
               | Unix.ELOOP -> "ELOOP"
               | Unix.EOVERFLOW -> "EOVERFLOW"
               | Unix.EUNKNOWNERR n -> Printf.sprintf "EUNKNOWNERR %d" n
           in
           Some(Printf.sprintf "Unix.Unix_error(Unix.%s, %S, %S)" error func arg)
       | _ ->
           None)
