#!/usr/bin/env ocaml
(* hey emacs, this is OCaml code: -*- tuareg -*- *)
(* nbd client library in userspace: generator
 * Copyright (C) 2013-2020 Red Hat Inc.
 *
 * This library 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; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *)

(* This script generates the state machine and language
 * bindings.  After editing this file, run:
 *
 *   generator/generator
 *
 * from the top source directory to regenerate output files.
 *)

#load "str.cma" ;;
#load "unix.cma" ;;

open Unix
open Printf ;;

if not (Sys.file_exists "lib/handle.c") then
  failwith "Wrong directory!  Don't run this script by hand." ;;

(*----------------------------------------------------------------------*)

(* The state machine.
 *
 * Each state has some associated C code which is called when
 * the state is entered, or when the state is re-entered because
 * of an external event.  That code is not in this file, it's
 * in [generator/states*.c].
 *
 * Each handle starts in the top level START state.
 *
 * When you enter a state, the associated C code for that state
 * runs.  If the C code calls SET_NEXT_STATE and returns 0 then
 * the connection enters the next state without blocking.  If the
 * C code calls SET_NEXT_STATE_AND_BLOCK and returns 0 then the
 * connection blocks, but will resume with the code for the next
 * state on the next external event.  If the C code does _not_
 * call either macro but returns 0, the state machine is blocked
 * and will not be re-entered until an external event happens
 * (see below), where the same C code will be executed again on
 * re-entry.  If the C code calls returns -1 after using
 * set_error(), then the state machine blocks and the caller
 * should report failure; the next external event will resume the
 * state machine according to whether SET_NEXT_STATE was used.
 *
 * There are various end states such as CLOSED and DEAD.  These
 * are not special in relation to the above state transition rules,
 * it's just that they have no way to move to another state.  However,
 * the DEAD state expects that set_error() was used in the previous
 * state, and will return -1 itself after performing cleanup actions;
 * the earlier state that wants to transition to DEAD should return 0
 * rather than -1, so as not to bypass this cleanup.
 *
 * An external event is something like the file descriptor being
 * ready to read or write, or the main program calling a function
 * such as [nbd_aio_connect].  Possible external events, and the
 * next state resulting, are listed in the states table below.
 *
 * An empty string [""] for an external event’s next state means
 * the same state is re-entered.  The same C code for the state
 * will be run again.
 *
 * States can be grouped hierarchically.  States can be referred
 * to by an absolute path from the top level, such as ".DEAD",
 * or by a relative path from the current level, such as "CONNECT"
 * (another state at the same level), "REPLY.START" (a state in
 * a sub-group), or "^FINISH_COMMAND" (a state in the level above
 * the current one).  When entering a group you must enter at the
 * START state.  When leaving a group and going to a higher level
 * in the state tree there is no restriction on the next state.
 *)

type external_event =
  | NotifyRead                  (* fd becomes ready to read *)
  | NotifyWrite                 (* fd becomes ready to write *)
  | CmdCreate                   (* [nbd_create] function called *)
  | CmdConnectSockAddr          (* [nbd_aio_connect] function called *)
  | CmdConnectTCP               (* [nbd_aio_connect_tcp] *)
  | CmdConnectCommand           (* [nbd_aio_connect_command] *)
  | CmdConnectSA                (* [nbd_aio_connect_systemd_socket_activation]*)
  | CmdConnectSocket            (* [nbd_aio_connect_socket] *)
  | CmdIssue                    (* issuing an NBD command *)

type location = string * int    (* source location: file, line number *)
let noloc = ("", 0)

type state = {
  (* The state name (without prefix).  If this has the special name
   * "START" then it is the start state of the current group.  Each
   * group can only have one start state.
   *)
  name : string;

  comment : string;             (* comment about the state *)

  (* Possible transitions from this state to a next state.  The
   * external events are coded into the state table below.  The
   * internal transitions are parsed out of the C code.
   *)
  external_events : (external_event * string) list;

  (* After flattening the state machine, the generator fills
   * in the extra fields in [state.parsed].
   *)
  mutable parsed : parsed_state;
}

and parsed_state = {
  (* The hierarchy group prefix.  For states in the top level
   * state machine this is an empty list.  For states in the
   * next level down this is a single element, and so on.
   *)
  prefix : string list;

  (* Hierarchical state name, like "NEWSTYLE.OPT_STARTTLS.CHECK_REPLY"
   * for use in debug messages etc.
   *)
  display_name : string;

  (* The STATE_* enum used in the generated C code. *)
  state_enum : string;

  (* The C code implementing this state. *)
  loc : location;
  code : string;

  (* Internal transitions, parsed out of the C code. *)
  internal_transitions : state list;

  (* External events after resolving them to the destination states. *)
  events : (external_event * state) list;
}

let default_state = { name = ""; comment = ""; external_events = [];
                      parsed = { prefix = []; display_name = "";
                                 state_enum = ""; loc = noloc; code = "";
                                 internal_transitions = []; events = [] } }

(* The type of the hierarchical state machine. *)
type state_machine = state_group list
and state_group =
  | Group of string * state_machine (* string is name/prefix of the group *)
  | State of state

(* Top level state machine. *)
let rec state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Handle after being initially created";
    external_events = [ CmdCreate, "";
                        CmdConnectSockAddr, "CONNECT.START";
                        CmdConnectTCP, "CONNECT_TCP.START";
                        CmdConnectCommand, "CONNECT_COMMAND.START";
                        CmdConnectSA, "CONNECT_SA.START";
                        CmdConnectSocket, "MAGIC.START" ];
  };

  Group ("CONNECT", connect_state_machine);
  Group ("CONNECT_TCP", connect_tcp_state_machine);
  Group ("CONNECT_COMMAND", connect_command_state_machine);
  Group ("CONNECT_SA", connect_sa_state_machine);

  Group ("MAGIC", magic_state_machine);
  Group ("OLDSTYLE", oldstyle_state_machine);
  Group ("NEWSTYLE", newstyle_state_machine);

  State {
    default_state with
    name = "READY";
    comment = "Connection is ready to process NBD commands";
    external_events = [ CmdIssue, "ISSUE_COMMAND.START";
                        NotifyRead, "REPLY.START" ];
  };

  Group ("ISSUE_COMMAND", issue_command_state_machine);
  Group ("REPLY", reply_state_machine);

  State {
    default_state with
    name = "DEAD";
    comment = "Connection is in an unrecoverable error state, can only be closed";
  };

  State {
    default_state with
    name = "CLOSED";
    comment = "Connection is closed";
  };
]

(* State machine implementing [nbd_aio_connect]. *)
and connect_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Initial call to connect(2) on the socket";
    external_events = [ NotifyWrite, "CONNECTING" ];
  };

  State {
    default_state with
    name = "CONNECTING";
    comment = "Connecting to the remote server";
    external_events = [ NotifyWrite, "" ];
  };
]

(* State machine implementing [nbd_aio_connect_tcp]. *)
and connect_tcp_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Connect to a remote TCP server";
    external_events = [];
  };

  State {
    default_state with
    name = "CONNECT";
    comment = "Initial call to connect(2) on a TCP socket";
    external_events = [ NotifyWrite, "CONNECTING" ];
  };

  State {
    default_state with
    name = "CONNECTING";
    comment = "Connecting to the remote server over a TCP socket";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "NEXT_ADDRESS";
    comment = "Connecting to the next address over a TCP socket";
    external_events = [];
  };
]

(* State machine implementing [nbd_aio_connect_command]. *)
and connect_command_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Connect to a subprocess";
    external_events = [];
  };
]

(* State machine implementing [nbd_aio_connect_systemd_socket_activation]. *)
and connect_sa_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Connect to a subprocess with systemd socket activation";
    external_events = [];
  };
]

(* Parse initial magic string from the server. *)
and magic_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Prepare to receive the magic identification from remote";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_MAGIC";
    comment = "Receive initial magic identification from remote";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "CHECK_MAGIC";
    comment = "Check magic and version sent by remote";
  };
]

(* Oldstyle handshake. *)
and oldstyle_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Prepare to receive remainder of oldstyle header";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_REMAINING";
    comment = "Receive remainder of oldstyle header";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "CHECK";
    comment = "Check oldstyle header";
    external_events = [];
  };
]

(* Fixed newstyle handshake. *)
and newstyle_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Prepare to receive newstyle gflags from remote";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_GFLAGS";
    comment = "Receive newstyle gflags from remote";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "CHECK_GFLAGS";
    comment = "Check global flags sent by remote";
  };

  State {
    default_state with
    name = "SEND_CFLAGS";
    comment = "Send newstyle client flags to remote";
    external_events = [ NotifyWrite, "" ];
  };

  (* Options.  These state groups are always entered unconditionally,
   * in this order.  The START state in each group will check if the
   * state needs to run and skip to the next state in the list if not.
   *)
  Group ("OPT_STARTTLS", newstyle_opt_starttls_state_machine);
  Group ("OPT_STRUCTURED_REPLY", newstyle_opt_structured_reply_state_machine);
  Group ("OPT_SET_META_CONTEXT", newstyle_opt_set_meta_context_state_machine);
  Group ("OPT_GO", newstyle_opt_go_state_machine);
  Group ("OPT_EXPORT_NAME", newstyle_opt_export_name_state_machine);

  (* When option parsing has successfully finished negotiation
   * it will jump to this state for final steps before moving to
   * the %READY state.
   *)
  State {
    default_state with
    name = "FINISHED";
    comment = "Finish off newstyle negotiation";
  };
]

(* Fixed newstyle NBD_OPT_STARTTLS option. *)
and newstyle_opt_starttls_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Try to send newstyle NBD_OPT_STARTTLS to upgrade to TLS";
    external_events = [];
  };

  State {
    default_state with
    name = "SEND";
    comment = "Send newstyle NBD_OPT_STARTTLS to upgrade to TLS";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "RECV_REPLY";
    comment = "Receive newstyle NBD_OPT_STARTTLS reply";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "RECV_REPLY_PAYLOAD";
    comment = "Receive any newstyle NBD_OPT_STARTTLS reply payload";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "CHECK_REPLY";
    comment = "Check newstyle NBD_OPT_STARTTLS reply";
    external_events = [];
  };

  State {
    default_state with
    name = "TLS_HANDSHAKE_READ";
    comment = "TLS handshake (reading)";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "TLS_HANDSHAKE_WRITE";
    comment = "TLS handshake (writing)";
    external_events = [ NotifyWrite, "" ];
  };
]

(* Fixed newstyle NBD_OPT_STRUCTURED_REPLY option. *)
and newstyle_opt_structured_reply_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Try to negotiate newstyle NBD_OPT_STRUCTURED_REPLY";
    external_events = [];
  };

  State {
    default_state with
    name = "SEND";
    comment = "Send newstyle NBD_OPT_STRUCTURED_REPLY negotiation request";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "RECV_REPLY";
    comment = "Receive newstyle NBD_OPT_STRUCTURED_REPLY option reply";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "RECV_REPLY_PAYLOAD";
    comment = "Receive any newstyle NBD_OPT_STRUCTURED_REPLY reply payload";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "CHECK_REPLY";
    comment = "Check newstyle NBD_OPT_STRUCTURED_REPLY option reply";
    external_events = [];
  };
]

(* Fixed newstyle NBD_OPT_SET_META_CONTEXT option. *)
and newstyle_opt_set_meta_context_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Try to negotiate newstyle NBD_OPT_SET_META_CONTEXT";
    external_events = [];
  };

  State {
    default_state with
    name = "SEND";
    comment = "Send newstyle NBD_OPT_SET_META_CONTEXT";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "SEND_EXPORTNAMELEN";
    comment = "Send newstyle NBD_OPT_SET_META_CONTEXT export name length";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "SEND_EXPORTNAME";
    comment = "Send newstyle NBD_OPT_SET_META_CONTEXT export name";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "SEND_NRQUERIES";
    comment = "Send newstyle NBD_OPT_SET_META_CONTEXT number of queries";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "PREPARE_NEXT_QUERY";
    comment = "Prepare to send newstyle NBD_OPT_SET_META_CONTEXT query";
    external_events = [];
  };

  State {
    default_state with
    name = "SEND_QUERYLEN";
    comment = "Send newstyle NBD_OPT_SET_META_CONTEXT query length";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "SEND_QUERY";
    comment = "Send newstyle NBD_OPT_SET_META_CONTEXT query";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "PREPARE_FOR_REPLY";
    comment = "Prepare to receive newstyle NBD_OPT_SET_META_CONTEXT option reply";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_REPLY";
    comment = "Receive newstyle NBD_OPT_SET_META_CONTEXT option reply";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "RECV_REPLY_PAYLOAD";
    comment = "Receive newstyle NBD_OPT_SET_META_CONTEXT option reply payload";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "CHECK_REPLY";
    comment = "Check newstyle NBD_OPT_SET_META_CONTEXT option reply";
    external_events = [];
  };
]

(* Fixed newstyle NBD_OPT_GO option. *)
and newstyle_opt_go_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Try to send newstyle NBD_OPT_GO to end handshake";
    external_events = [];
  };

  State {
    default_state with
    name = "SEND";
    comment = "Send newstyle NBD_OPT_GO to end handshake";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "SEND_EXPORTNAMELEN";
    comment = "Send newstyle NBD_OPT_GO export name length";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "SEND_EXPORT";
    comment = "Send newstyle NBD_OPT_GO export name";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "SEND_NRINFOS";
    comment = "Send newstyle NBD_OPT_GO number of infos";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "RECV_REPLY";
    comment = "Receive newstyle NBD_OPT_GO reply";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "RECV_REPLY_PAYLOAD";
    comment = "Receive newstyle NBD_OPT_GO reply payload";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "CHECK_REPLY";
    comment = "Check newstyle NBD_OPT_GO reply";
    external_events = [];
  };
]

(* Newstyle NBD_OPT_EXPORT_NAME option. *)
and newstyle_opt_export_name_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Try to send newstyle NBD_OPT_EXPORT_NAME to end handshake";
    external_events = [];
  };

  State {
    default_state with
    name = "SEND";
    comment = "Send newstyle NBD_OPT_EXPORT_NAME to end handshake";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "SEND_EXPORT";
    comment = "Send newstyle NBD_OPT_EXPORT_NAME export name";
    external_events = [ NotifyWrite, "" ];
  };

  State {
    default_state with
    name = "RECV_REPLY";
    comment = "Receive newstyle NBD_OPT_EXPORT_NAME reply";
    external_events = [ NotifyRead, "" ];
  };

  State {
    default_state with
    name = "CHECK_REPLY";
    comment = "Check newstyle NBD_OPT_EXPORT_NAME reply";
    external_events = [];
  };
]

(* Sending a command to the server. *)
and issue_command_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Begin issuing a command to the remote server";
    external_events = [];
  };

  State {
    default_state with
    name = "SEND_REQUEST";
    comment = "Sending a request to the remote server";
    external_events = [ NotifyWrite, "";
                        NotifyRead, "PAUSE_SEND_REQUEST" ];
  };

  State {
    default_state with
    name = "PAUSE_SEND_REQUEST";
    comment = "Interrupt send request to receive an earlier command's reply";
    external_events = [];
  };

  State {
    default_state with
    name = "PREPARE_WRITE_PAYLOAD";
    comment = "Prepare the write payload to send to the remote server";
    external_events = [];
  };

  State {
    default_state with
    name = "SEND_WRITE_PAYLOAD";
    comment = "Sending the write payload to the remote server";
    external_events = [ NotifyWrite, "";
                        NotifyRead, "PAUSE_WRITE_PAYLOAD" ];
  };

State {
    default_state with
    name = "PAUSE_WRITE_PAYLOAD";
    comment = "Interrupt write payload to receive an earlier command's reply";
    external_events = [];
  };

State {
    default_state with
    name = "FINISH";
    comment = "Finish issuing a command";
    external_events = [];
  };
]

(* Receiving a reply from the server. *)
and reply_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Prepare to receive a reply from the remote server";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_REPLY";
    comment = "Receive a reply from the remote server";
    external_events = [];
  };

  State {
    default_state with
    name = "CHECK_SIMPLE_OR_STRUCTURED_REPLY";
    comment = "Check if the reply is a simple or structured reply";
    external_events = [];
  };

  Group ("SIMPLE_REPLY", simple_reply_state_machine);
  Group ("STRUCTURED_REPLY", structured_reply_state_machine);

  State {
    default_state with
    name = "FINISH_COMMAND";
    comment = "Finish receiving a command";
    external_events = [];
  };
]

(* Receiving a simple reply from the server. *)
and simple_reply_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Parse a simple reply from the server";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_READ_PAYLOAD";
    comment = "Receiving the read payload for a simple reply";
    external_events = [];
  };
]

(* Receiving a structured reply from the server. *)
and structured_reply_state_machine = [
  State {
    default_state with
    name = "START";
    comment = "Prepare to receive the remaining part of a structured reply";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_REMAINING";
    comment = "Receiving the remaining part of a structured reply";
    external_events = [];
  };

  State {
    default_state with
    name = "CHECK";
    comment = "Parse a structured reply from the server";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_ERROR";
    comment = "Receive a structured reply error header";
    external_events = []
  };

  State {
    default_state with
    name = "RECV_ERROR_MESSAGE";
    comment = "Receive a structured reply error message";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_ERROR_TAIL";
    comment = "Receive a structured reply error tail";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_OFFSET_DATA";
    comment = "Receive a structured reply offset-data header";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_OFFSET_DATA_DATA";
    comment = "Receive a structured reply offset-data block of data";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_OFFSET_HOLE";
    comment = "Receive a structured reply offset-hole header";
    external_events = [];
  };

  State {
    default_state with
    name = "RECV_BS_ENTRIES";
    comment = "Receive a structured reply block-status payload";
    external_events = [];
  };

  State {
    default_state with
    name = "FINISH";
    comment = "Finish receiving a structured reply";
    external_events = [];
  };
]

(*----------------------------------------------------------------------*)

(* The API. *)

type call = {
  args : arg list;         (* parameters (except handle) *)
  optargs : optarg list;   (* optional parameters (not optional in C) *)
  ret : ret;               (* return value *)
  shortdesc : string;      (* short description *)
  longdesc : string;       (* long description *)
  example : string option; (* path to example code (under top_srcdir) *)
  see_also : string list;  (* "SEE ALSO" section in man page *)
  (* List of permitted states for making this call.  [[]] = Any state. *)
  permitted_states : permitted_state list;
  (* Most functions must take a lock.  The only known exceptions are:
   * - functions which return a constant (eg. [nbd_supports_uri])
   * - functions which {b only} read from the atomic
   *   [get_public_state] and do nothing else with the handle.
   *)
  is_locked : bool;
  (* Most functions can call set_error.  For functions which are
   * {b guaranteed} never to do that we can save a bit of time by
   * setting this to false.
   *)
  may_set_error : bool;
  (* The first stable version that the symbol appeared in, for
   * example (1, 2) if the symbol was added in development cycle
   * 1.1.x and thus the first stable version was 1.2.  This is
   * filled in by the generator, add new calls to the first_version
   * table instead.
   *)
  mutable first_version : int * int;
}
and arg =
| Bool of string           (* bool *)
| BytesIn of string * string (* byte array + size passed in to the function *)
| BytesOut of string * string(* byte array + size specified by caller,
                              written by the function *)
| BytesPersistIn of string * string (* same as above, but buffer persists *)
| BytesPersistOut of string * string
| Closure of closure       (* function pointer + void *opaque *)
| Enum of string * enum    (* enum/union type, int in C *)
| Fd of string             (* file descriptor *)
| Flags of string * flags  (* flags, uint32_t in C *)
| Int of string            (* small int *)
| Int64 of string          (* 64 bit signed int *)
| Path of string           (* filename or path *)
| SockAddrAndLen of string * string (* struct sockaddr * + socklen_t *)
| String of string         (* string, cannot be NULL *)
| StringList of string     (* argv-style NULL-terminated array of strings *)
| UInt of string           (* small unsigned int *)
| UInt32 of string         (* 32 bit unsigned int *)
| UInt64 of string         (* 64 bit unsigned int *)
and optarg =
| OClosure of closure      (* optional closure *)
| OFlags of string * flags (* optional flags, uint32_t in C *)
and ret =
| RBool                    (* return a boolean, or error *)
| RStaticString            (* return a static string (must be located in
                              .text section), NULL for error *)
| RErr                     (* return 0 = ok, -1 = error *)
| RFd                      (* return a file descriptor, or error *)
| RInt                     (* return a small int, -1 = error *)
| RInt64                   (* 64 bit int, -1 = error *)
| RCookie                  (* 64 bit command cookie (>= 1), -1 = error *)
| RString                  (* return a newly allocated string,
                              caller frees, NULL for error *)
| RUInt                    (* return a bitmask, no error possible *)
and closure = {
  cbname : string;         (* name of callback function *)
  cbargs : cbarg list;     (* all closures return int for now *)
}
and cbarg =
| CBArrayAndLen of arg * string (* array + number of entries *)
| CBBytesIn of string * string (* like BytesIn *)
| CBInt of string          (* like Int *)
| CBInt64 of string        (* like Int64 *)
| CBMutable of arg         (* mutable argument, eg. int* *)
| CBString of string       (* like String *)
| CBUInt of string         (* like UInt *)
| CBUInt64 of string       (* like UInt64 *)
and enum = {
  enum_prefix : string;    (* prefix of each enum variant *)
  enums : (string * int) list (* enum names and their values in C *)
}
and flags = {
  flag_prefix : string;    (* prefix of each flag name *)
  flags : (string * int) list (* flag names and their values in C *)
}
and permitted_state =
| Created                  (* can be called in the START state *)
| Connecting               (* can be called when connecting/handshaking *)
| Connected                (* when connected and READY or processing, but
                              not including CLOSED or DEAD *)
| Closed | Dead            (* can be called when the handle is CLOSED or DEAD *)

let default_call = { args = []; optargs = []; ret = RErr;
                     shortdesc = ""; longdesc = ""; example = None;
                     see_also = [];
                     permitted_states = [];
                     is_locked = true; may_set_error = true;
                     first_version = (0, 0) }
let non_blocking_test_call_description = "\n
This call does not block, because it returns data that is saved in
the handle from the NBD protocol handshake."

(* Closures. *)
let chunk_closure = {
  cbname = "chunk";
  cbargs = [ CBBytesIn ("subbuf", "count");
             CBUInt64 "offset"; CBUInt "status";
             CBMutable (Int "error") ]
}
let completion_closure = {
  cbname = "completion";
  cbargs = [ CBMutable (Int "error") ]
}
let debug_closure = {
  cbname = "debug";
  cbargs = [ CBString "context"; CBString "msg" ]
}
let extent_closure = {
  cbname = "extent";
  cbargs = [ CBString "metacontext";
             CBUInt64 "offset";
             CBArrayAndLen (UInt32 "entries",
                            "nr_entries");
             CBMutable (Int "error") ]
}
let all_closures = [ chunk_closure; completion_closure;
                     debug_closure; extent_closure ]

(* Enums. *)
let tls_enum = {
  enum_prefix = "TLS";
  enums = [
    "DISABLE", 0;
    "ALLOW",   1;
    "REQUIRE", 2;
  ]
}
let all_enums = [ tls_enum ]

(* Flags. *)
let cmd_flags = {
  flag_prefix = "CMD_FLAG";
  flags = [
    "FUA",       1 lsl 0;
    "NO_HOLE",   1 lsl 1;
    "DF",        1 lsl 2;
    "REQ_ONE",   1 lsl 3;
    "FAST_ZERO", 1 lsl 4;
  ]
}
let handshake_flags = {
  flag_prefix = "HANDSHAKE_FLAG";
  flags = [
    "FIXED_NEWSTYLE", 1 lsl 0;
    "NO_ZEROES",      1 lsl 1;
    ]
}
let allow_transport_flags = {
  flag_prefix = "ALLOW_TRANSPORT";
  flags = [
    "TCP",   1 lsl 0;
    "UNIX",  1 lsl 1;
    "VSOCK", 1 lsl 2;
  ]
}
let all_flags = [ cmd_flags; handshake_flags; allow_transport_flags ]

(* Calls.
 *
 * The first parameter [struct nbd_handle *nbd] is implicit.
 *
 * Disable:
 * Warning 23: all the fields are explicitly listed in this record:
 *)
let [@warning "-23"] handle_calls = [
  "set_debug", {
    default_call with
    args = [ Bool "debug" ]; ret = RErr;
    shortdesc = "set or clear the debug flag";
    longdesc = "\
Set or clear the debug flag.  When debugging is enabled,
debugging messages from the library are printed to stderr,
unless a debugging callback has been defined too
(see L<nbd_set_debug_callback(3)>) in which case they are
sent to that function.  This flag defaults to false on
newly created handles, except if C<LIBNBD_DEBUG=1> is
set in the environment in which case it defaults to true.";
    see_also = ["L<nbd_set_handle_name(3)>"; "L<nbd_set_debug_callback(3)>"];
  };

  "get_debug", {
    default_call with
    args = []; ret = RBool;
    may_set_error = false;
    shortdesc = "return the state of the debug flag";
    longdesc = "\
Return the state of the debug flag on this handle.";
  };

  "set_debug_callback", {
    default_call with
    args = [ Closure debug_closure ];
    ret = RErr;
    shortdesc = "set the debug callback";
    longdesc = "\
Set the debug callback.  This function is called when the library
emits debug messages, when debugging is enabled on a handle.  The
callback parameters are C<user_data> passed to this function, the
name of the libnbd function emitting the debug message (C<context>),
and the message itself (C<msg>).  If no debug callback is set on
a handle then messages are printed on C<stderr>.

The callback should not call C<nbd_*> APIs on the same handle since it can
be called while holding the handle lock and will cause a deadlock.";
};

  "clear_debug_callback", {
    default_call with
    args = [];
    ret = RErr;
    shortdesc = "clear the debug callback";
    longdesc = "\
Remove the debug callback if one was previously associated
with the handle (with L<nbd_set_debug_callback(3)>).  If no
callback was associated this does nothing.";
};

  "set_handle_name", {
    default_call with
    args = [ String "handle_name" ]; ret = RErr;
    shortdesc = "set the handle name";
    longdesc = "\
Handles have a name which is unique within the current process.
The handle name is used in debug output.

Handle names are normally generated automatically and have the
form C<\"nbd1\">, C<\"nbd2\">, etc., but you can optionally use
this call to give the handles a name which is meaningful for
your application to make debugging output easier to understand.";
  };

  "get_handle_name", {
    default_call with
    args = []; ret = RString;
    shortdesc = "get the handle name";
    longdesc = "\
Get the name of the handle.  If it was previously set by calling
L<nbd_set_handle_name(3)> then this returns the name that was set.
Otherwise it will return a generic name like C<\"nbd1\">,
C<\"nbd2\">, etc.";
  };

  "set_export_name", {
    default_call with
    args = [ String "export_name" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "set the export name";
    longdesc = "\
For servers which require an export name or can serve different
content on different exports, set the C<export_name> to
connect to.  The default is the empty string C<\"\">.

This is only relevant when connecting to servers using the
newstyle protocol as the oldstyle protocol did not support
export names.  The NBD protocol limits export names to
4096 bytes, but servers may not support the full length.
The encoding of export names is always UTF-8.

This call may be skipped if using L<nbd_connect_uri(3)> to connect
to a URI that includes an export name.";
  see_also = ["L<nbd_get_export_name(3)>"; "L<nbd_connect_uri(3)>"];
  };

  "get_export_name", {
    default_call with
    args = []; ret = RString;
    shortdesc = "get the export name";
    longdesc = "\
Get the export name associated with the handle.";
  see_also = ["L<nbd_set_export_name(3)>"; "L<nbd_connect_uri(3)>"];
  };

  "set_tls", {
    default_call with
    args = [Enum ("tls", tls_enum)]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "enable or require TLS (authentication and encryption)";
    longdesc = "\
Enable or require TLS (authenticated and encrypted connections) to the
NBD server.  The possible settings are:

=over 4

=item C<LIBNBD_TLS_DISABLE>

Disable TLS.  (The default setting, unless using L<nbd_connect_uri(3)> with
a URI that requires TLS)

=item C<LIBNBD_TLS_ALLOW>

Enable TLS if possible.

This option is insecure (or best effort) in that in some cases
it will fall back to an unencrypted and/or unauthenticated
connection if TLS could not be established.  Use
C<LIBNBD_TLS_REQUIRE> below if the connection must be
encrypted.

Some servers will drop the connection if TLS fails
so fallback may not be possible.

=item C<LIBNBD_TLS_REQUIRE>

Require an encrypted and authenticated TLS connection.
Always fail to connect if the connection is not encrypted
and authenticated.

=back

As well as calling this you may also need to supply
the path to the certificates directory (L<nbd_set_tls_certificates(3)>),
the username (L<nbd_set_tls_username(3)>) and/or
the Pre-Shared Keys (PSK) file (L<nbd_set_tls_psk_file(3)>).  For now,
when using L<nbd_connect_uri(3)>, any URI query parameters related to
TLS are not handled automatically.  Setting the level higher than
zero will fail if libnbd was not compiled against gnutls; you can
test whether this is the case with L<nbd_supports_tls(3)>.";
    example = Some "examples/encryption.c";
    see_also = ["L<libnbd(3)/ENCRYPTION AND AUTHENTICATION>";
                "L<nbd_get_tls(3)>"; "L<nbd_get_tls_negotiated(3)>"];
  };

  "get_tls", {
    default_call with
    args = []; ret = RInt;
    may_set_error = false;
    shortdesc = "get the TLS request setting";
    longdesc = "\
Get the TLS request setting.

B<Note:> If you want to find out if TLS was actually negotiated
on a particular connection use L<nbd_get_tls_negotiated(3)> instead.";
    see_also = ["L<nbd_set_tls(3)>"; "L<nbd_get_tls_negotiated(3)>"];
  };

  "get_tls_negotiated", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "find out if TLS was negotiated on a connection";
    longdesc = "\
After connecting you may call this to find out if the
connection is using TLS.

This is only really useful if you set the TLS request mode
to C<LIBNBD_TLS_ALLOW> (see L<nbd_set_tls(3)>), because in this
mode we try to use TLS but fall back to unencrypted if it was
not available.  This function will tell you if TLS was
negotiated or not.

In C<LIBNBD_TLS_REQUIRE> mode (the most secure) the connection
would have failed if TLS could not be negotiated, and in
C<LIBNBD_TLS_DISABLE> mode TLS is not tried.";
    see_also = ["L<nbd_set_tls(3)>"; "L<nbd_get_tls(3)>"];
  };

  "set_tls_certificates", {
    default_call with
    args = [Path "dir"]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "set the path to the TLS certificates directory";
    longdesc = "\
Set the path to the TLS certificates directory.  If not
set and TLS is used then a compiled in default is used.
For root this is C</etc/pki/libnbd/>.  For non-root this is
C<$HOME/.pki/libnbd> and C<$HOME/.config/pki/libnbd>.  If
none of these directories can be found then the system
trusted CAs are used.

This function may be called regardless of whether TLS is
supported, but will have no effect unless L<nbd_set_tls(3)>
is also used to request or require TLS.";
  };

(* Can't implement this because we need a way to return string that
   can be NULL.
  "get_tls_certificates", {
    default_call with
    args = []; ret = RString;
    shortdesc = "get the current TLS certificates directory";
    longdesc = "\
Get the current TLS directory.";
    see_also = ["L<nbd_set_tls_certificates(3)>"];
  };
*)

  "set_tls_verify_peer", {
    default_call with
    args = [Bool "verify"]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "set whether we verify the identity of the server";
    longdesc = "\
Set this flag to control whether libnbd will verify the identity
of the server from the server's certificate and the certificate
authority.  This defaults to true when connecting to TCP servers
using TLS certificate authentication, and false otherwise.

This function may be called regardless of whether TLS is
supported, but will have no effect unless L<nbd_set_tls(3)>
is also used to request or require TLS.";
  };

  "get_tls_verify_peer", {
    default_call with
    args = []; ret = RBool;
    may_set_error = false;
    shortdesc = "get whether we verify the identity of the server";
    longdesc = "\
Get the verify peer flag.";
  };

  "set_tls_username", {
    default_call with
    args = [String "username"]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "set the TLS username";
    longdesc = "\
Set the TLS client username.  This is used
if authenticating with PSK over TLS is enabled.
If not set then the local username is used.

This function may be called regardless of whether TLS is
supported, but will have no effect unless L<nbd_set_tls(3)>
is also used to request or require TLS.";
    example = Some "examples/encryption.c";
  };

  "get_tls_username", {
    default_call with
    args = []; ret = RString;
    shortdesc = "get the current TLS username";
    longdesc = "\
Get the current TLS username.";
    see_also = ["L<nbd_set_tls_username(3)>"];
  };

  "set_tls_psk_file", {
    default_call with
    args = [Path "filename"]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "set the TLS Pre-Shared Keys (PSK) filename";
    longdesc = "\
Set the TLS Pre-Shared Keys (PSK) filename.  This is used
if trying to authenticate to the server using with a pre-shared
key.  There is no default so if this is not set then PSK
authentication cannot be used to connect to the server.

This function may be called regardless of whether TLS is
supported, but will have no effect unless L<nbd_set_tls(3)>
is also used to request or require TLS.";
    example = Some "examples/encryption.c";
  };

(* Can't implement this because we need a way to return string that
   can be NULL.
  "get_tls_psk_file", {
    default_call with
    args = []; ret = RString;
    shortdesc = "get the current TLS PSK filename";
    longdesc = "\
Get the current TLS PSK filename.";
    see_also = ["L<nbd_set_tls_psk_file(3)>"];
  };
*)

  "set_request_structured_replies", {
    default_call with
    args = [Bool "request"]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "control use of structured replies";
    longdesc = "\
By default, libnbd tries to negotiate structured replies with the
server, as this protocol extension must be in use before
C<nbd_can_meta_context> or C<nbd_can_df> can return true.  However,
for integration testing, it can be useful to clear this flag
rather than find a way to alter the server to fail the negotiation
request.";
    see_also = ["L<nbd_get_request_structured_replies(3)>";
                "L<nbd_set_handshake_flags(3)>";
                "L<nbd_get_structured_replies_negotiated(3)>";
                "L<nbd_can_meta_context(3)>"; "L<nbd_can_df(3)>"];
  };

  "get_request_structured_replies", {
    default_call with
    args = []; ret = RBool;
    may_set_error = false;
    shortdesc = "see if structured replies are attempted";
    longdesc = "\
Return the state of the request structured replies flag on this
handle.

B<Note:> If you want to find out if structured replies were actually
negotiated on a particular connection use
C<nbd_get_structured_replies_negotiated> instead.";
    see_also = ["L<nbd_set_request_structured_replies(3)>";
                "L<nbd_get_structured_replies_negotiated(3)>"];
  };

  "get_structured_replies_negotiated", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "see if structured replies are in use";
    longdesc = "\
After connecting you may call this to find out if the connection is
using structured replies.";
    see_also = ["L<nbd_set_request_structured_replies(3)>";
                "L<nbd_get_request_structured_replies(3)>";
                "L<nbd_get_protocol(3)>"];
  };

  "set_handshake_flags", {
    default_call with
    args = [ Flags ("flags", handshake_flags) ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "control use of handshake flags";
    longdesc = "\
By default, libnbd tries to negotiate all possible handshake flags
that are also supported by the server; since omitting a handshake
flag can prevent the use of other functionality such as TLS encryption
or structured replies.  However, for integration testing, it can be
useful to reduce the set of flags supported by the client to test that
a particular server can handle various clients that were compliant to
older versions of the NBD specification.

The C<flags> argument is a bitmask, including zero or more of the
following handshake flags:

=over 4

=item C<LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE> = 1

The server gracefully handles unknown option requests from the
client, rather than disconnecting.  Without this flag, a client
cannot safely request to use extensions such as TLS encryption or
structured replies, as the request may cause an older server to
drop the connection.

=item C<LIBNBD_HANDSHAKE_FLAG_NO_ZEROES> = 2

If the client is forced to use C<NBD_OPT_EXPORT_NAME> instead of
the preferred C<NBD_OPT_GO>, this flag allows the server to send
fewer all-zero padding bytes over the connection.

=back

Future NBD extensions may add further flags.
";
    see_also = ["L<nbd_get_handshake_flags(3)>";
                "L<nbd_set_request_structured_replies(3)>"];
  };

  "get_handshake_flags", {
    default_call with
    args = []; ret = RUInt;
    may_set_error = false;
    shortdesc = "see which handshake flags are supported";
    longdesc = "\
Return the state of the handshake flags on this handle.  When the
handle has not yet completed a connection (see C<nbd_aio_is_created>),
this returns the flags that the client is willing to use, provided
the server also advertises those flags.  After the connection is
ready (see C<nbd_aio_is_ready>), this returns the flags that were
actually agreed on between the server and client.  If the NBD
protocol defines new handshake flags, then the return value from
a newer library version may include bits that were undefined at
the time of compilation.";
    see_also = ["L<nbd_set_handshake_flags(3)>";
                "L<nbd_get_protocol(3)>";
                "L<nbd_aio_is_created(3)>"; "L<nbd_aio_is_ready(3)>"];
  };

  "add_meta_context", {
    default_call with
    args = [ String "name" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "ask server to negotiate metadata context";
    longdesc = "\
During connection libnbd can negotiate zero or more metadata
contexts with the server.  Metadata contexts are features (such
as C<\"base:allocation\">) which describe information returned
by the L<nbd_block_status(3)> command (for C<\"base:allocation\">
this is whether blocks of data are allocated, zero or sparse).

This call adds one metadata context to the list to be negotiated.
You can call it as many times as needed.  The list is initially
empty when the handle is created.

The NBD protocol limits meta context names to 4096 bytes, but
servers may not support the full length.  The encoding of meta
context names is always UTF-8.

Not all servers support all metadata contexts.  To learn if a context
was actually negotiated, call L<nbd_can_meta_context(3)> after
connecting.

The single parameter is the name of the metadata context,
for example C<LIBNBD_CONTEXT_BASE_ALLOCATION>.
B<E<lt>libnbd.hE<gt>> includes defined constants beginning with
C<LIBNBD_CONTEXT_> for some well-known contexts, but you are free
to pass in other contexts.

Other metadata contexts are server-specific, but include
C<\"qemu:dirty-bitmap:...\"> for qemu-nbd
(see qemu-nbd I<-B> option).";
    see_also = ["L<nbd_block_status(3)>"];
  };

  "set_uri_allow_transports", {
    default_call with
    args = [ Flags ("mask", allow_transport_flags) ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "set the allowed transports in NBD URIs";
    longdesc = "\
Set which transports are allowed to appear in NBD URIs.  The
default is to allow any transport.

The C<mask> parameter may contain any of the following flags
ORed together:

=over 4

=item C<LIBNBD_ALLOW_TRANSPORT_TCP>

=item C<LIBNBD_ALLOW_TRANSPORT_UNIX>

=item C<LIBNBD_ALLOW_TRANSPORT_VSOCK>

=back";
    see_also = ["L<nbd_connect_uri(3)>"; "L<nbd_set_uri_allow_tls(3)>"];
  };

  "set_uri_allow_tls", {
    default_call with
    args = [ Enum ("tls", tls_enum) ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "set the allowed TLS settings in NBD URIs";
    longdesc = "\
Set which TLS settings are allowed to appear in NBD URIs.  The
default is to allow either non-TLS or TLS URIs.

The C<tls> parameter can be:

=over 4

=item C<LIBNBD_TLS_DISABLE>

TLS URIs are not permitted, ie. a URI such as C<nbds://...>
will be rejected.

=item C<LIBNBD_TLS_ALLOW>

This is the default.  TLS may be used or not, depending on
whether the URI uses C<nbds> or C<nbd>.

=item C<LIBNBD_TLS_REQUIRE>

TLS URIs are required.  All URIs must use C<nbds>.

=back";
    see_also = ["L<nbd_connect_uri(3)>"; "L<nbd_set_uri_allow_transports(3)>"];
  };

  "set_uri_allow_local_file", {
    default_call with
    args = [ Bool "allow" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "set the allowed transports in NBD URIs";
    longdesc = "\
Allow NBD URIs to reference local files.  This is I<disabled>
by default.

Currently this setting only controls whether the C<tls-psk-file>
parameter in NBD URIs is allowed.";
    see_also = ["L<nbd_connect_uri(3)>"];
  };

  "connect_uri", {
    default_call with
    args = [ String "uri" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to NBD URI";
    longdesc = "\
Connect (synchronously) to an NBD server and export by specifying
the NBD URI.  This call parses the URI and may call
L<nbd_set_export_name(3)> and L<nbd_set_tls(3)> and other
calls as needed, followed by
L<nbd_connect_tcp(3)> or L<nbd_connect_unix(3)>.
This call returns when the connection has been made.

=head2 Example URIs supported

=over 4

=item C<nbd://example.com>

Connect over TCP, unencrypted, to C<example.com> port 10809.

=item C<nbds://example.com>

Connect over TCP with TLS, to C<example.com> port 10809.  If
the server does not support TLS then this will fail.

=item C<nbd+unix:///foo?socket=/tmp/nbd.sock>

Connect over the Unix domain socket F</tmp/nbd.sock> to
an NBD server running locally.  The export name is set to C<foo>
(note without any leading C</> character).

=item C<nbd+vsock:///>

In this scenario libnbd is running in a virtual machine.  Connect
over C<AF_VSOCK> to an NBD server running on the hypervisor.

=back

=head2 Supported URI formats

The following schemes are supported in the current version
of libnbd:

=over 4

=item C<nbd:>

Connect over TCP without using TLS.

=item C<nbds:>

Connect over TCP.  TLS is required and the connection
will fail if the server does not support TLS.

=item C<nbd+unix:>

=item C<nbds+unix:>

Connect over a Unix domain socket, without or with TLS
respectively.  The C<socket> parameter is required.

=item C<nbd+vsock:>

=item C<nbds+vsock:>

Connect over the C<AF_VSOCK> transport, without or with
TLS respectively.

=back

The authority part of the URI (C<[username@][servername][:port]>)
is parsed depending on the transport.  For TCP it specifies the
server to connect to and optional port number.  For C<+unix>
it should not be present.  For C<+vsock> the server name is the
numeric CID (eg. C<2> to connect to the host), and the optional
port number may be present.  If the C<username> is present it
is used for TLS authentication.

For all transports, an export name may be present, parsed in
accordance with the NBD URI specification.

Finally the query part of the URI can contain:

=over 4

=item B<socket=>F<SOCKET>

Specifies the Unix domain socket to connect on.
Must be present for the C<+unix> transport and must not
be present for the other transports.

=item B<tls-psk-file=>F<PSKFILE>

Set the PSK file.  See L<nbd_set_tls_psk_file(3)>.  Note
this is not allowed by default - see next section.

=back

=head2 Disable URI features

For security reasons you might want to disable certain URI
features.  Pre-filtering URIs is error-prone and should not
be attempted.  Instead use the libnbd APIs below to control
what can appear in URIs.  Note you must call these functions
on the same handle before calling C<nbd_connect_uri> or
L<nbd_aio_connect_uri(3)>.

=over 4

=item TCP, Unix domain socket or C<AF_VSOCK> transports

Default: all allowed

To select which transports are allowed call
L<nbd_set_uri_allow_transports(3)>.

=item TLS

Default: both non-TLS and TLS connections allowed

To force TLS off or on in URIs call
L<nbd_set_uri_allow_tls(3)>.

=item Connect to Unix domain socket in the local filesystem

Default: allowed

To prevent this you must disable the C<+unix> transport
using L<nbd_set_uri_allow_transports(3)>.

=item Read from local files

Default: denied

To allow URIs to contain references to local files
(eg. for parameters like C<tls-psk-file>) call
L<nbd_set_uri_allow_local_file(3)>.

=back

=head2 Optional features

This call will fail if libnbd was not compiled with libxml2; you can
test whether this is the case with L<nbd_supports_uri(3)>.

Support for URIs that require TLS will fail if libnbd was not
compiled with gnutls; you can test whether this is the case
with L<nbd_supports_tls(3)>.";
    see_also = ["L<https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md>"];
  };

  "connect_unix", {
    default_call with
    args = [ Path "unixsocket" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to NBD server over a Unix domain socket";
    longdesc = "\
Connect (synchronously) over the named Unix domain socket (C<unixsocket>)
to an NBD server running on the same machine.  This call returns
when the connection has been made.";
    example = Some "examples/fetch-first-sector.c";
  };

  "connect_vsock", {
    default_call with
    args = [ UInt32 "cid"; UInt32 "port" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to NBD server over AF_VSOCK protocol";
    longdesc = "\
Connect (synchronously) over the C<AF_VSOCK> protocol from a
virtual machine to an NBD server, usually running on the host.  The
C<cid> and C<port> parameters specify the server address.  Usually
C<cid> should be C<2> (to connect to the host), and C<port> might be
C<10809> or another port number assigned to you by the host
administrator.  This call returns when the connection has been made.";
  };

  "connect_tcp", {
    default_call with
    args = [ String "hostname"; String "port" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to NBD server over a TCP port";
    longdesc = "\
Connect (synchronously) to the NBD server listening on
C<hostname:port>.  The C<port> may be a port name such
as C<\"nbd\">, or it may be a port number as a string
such as C<\"10809\">.  This call returns when the connection
has been made.";
  };

  "connect_socket", {
    default_call with
    args = [ Fd "sock" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect directly to a connected socket";
    longdesc = "\
Pass a connected socket (file descriptor) which libnbd will
talk to.  The program is responsible for connecting this
somehow to an NBD server.  Once the socket is passed to
libnbd it is the responsibility of libnbd.  Libnbd may
read and write to it, set it to non-blocking, etc., and
will finally close it when the handle is closed.  The
program must no longer use the socket.";
  };

  "connect_command", {
    default_call with
    args = [ StringList "argv" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to NBD server command";
    longdesc = "\
Run the command as a subprocess and connect to it over
stdin/stdout.  This is for use with NBD servers which can
behave like inetd clients, such as C<nbdkit --single>.

=head2 Subprocess

Libnbd will fork the C<argv> command and pass the NBD socket
to it using file descriptors 0 and 1 (stdin/stdout):

 ┌─────────┬─────────┐    ┌────────────────┐
 │ program │ libnbd  │    │   NBD server   │
 │         │         │    │       (argv)   │
 │         │ socket ╍╍╍╍╍╍╍╍▶ stdin/stdout │
 └─────────┴─────────┘    └────────────────┘

When the NBD handle is closed the server subprocess
is killed.";
    see_also = ["L<nbd_kill_subprocess(3)>"];
    example = Some "examples/connect-command.c";
  };

  "connect_systemd_socket_activation", {
    default_call with
    args = [ StringList "argv" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect using systemd socket activation";
    longdesc = "\
Run the command as a subprocess and connect to it using
systemd socket activation.

This is especially useful for running L<qemu-nbd(1)> as
a subprocess of libnbd, for example to use it to open
qcow2 files.  To run nbdkit as a subprocess it is usually
better to use L<nbd_connect_command(3)>.

=head2 Socket activation

Libnbd will fork the C<argv> command and pass an NBD
socket to it using special C<LISTEN_*> environment
variables (as defined by the systemd socket activation
protocol).

 ┌─────────┬─────────┐    ┌───────────────┐
 │ program │ libnbd  │    │  qemu-nbd or  │
 │         │         │    │  other server │
 │         │ socket ╍╍╍╍╍╍╍╍▶             │
 └─────────┴─────────┘    └───────────────┘

When the NBD handle is closed the server subprocess
is killed.";
    see_also = ["L<nbd_connect_command(3)>"; "L<nbd_kill_subprocess(3)>";
                "L<qemu-nbd(1)>";
                "L<http://0pointer.de/blog/projects/socket-activation.html>"];
    example = Some "examples/open-qcow2.c";
  };

  "is_read_only", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "is the NBD export read-only?";
    longdesc = "\
Returns true if the NBD export is read-only; writes and
write-like operations will fail."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>"];
    example = Some "examples/server-flags.c";
  };

  "can_flush", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "does the server support the flush command?";
    longdesc = "\
Returns true if the server supports the flush command
(see L<nbd_flush(3)>, L<nbd_aio_flush(3)>).  Returns false if
the server does not."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>";
                "L<nbd_flush(3)>"; "L<nbd_aio_flush(3)>"];
    example = Some "examples/server-flags.c";
  };

  "can_fua", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "does the server support the FUA flag?";
    longdesc = "\
Returns true if the server supports the FUA flag on
certain commands (see L<nbd_pwrite(3)>)."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>"; "L<nbd_pwrite(3)>";
                "L<nbd_zero(3)>"; "L<nbd_trim(3)>"];
    example = Some "examples/server-flags.c";
  };

  "is_rotational", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "is the NBD disk rotational (like a disk)?";
    longdesc = "\
Returns true if the disk exposed over NBD is rotational
(like a traditional floppy or hard disk).  Returns false if
the disk has no penalty for random access (like an SSD or
RAM disk)."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>"];
    example = Some "examples/server-flags.c";
  };

  "can_trim", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "does the server support the trim command?";
    longdesc = "\
Returns true if the server supports the trim command
(see L<nbd_trim(3)>, L<nbd_aio_trim(3)>).  Returns false if
the server does not."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>";
                "L<nbd_trim(3)>"; "L<nbd_aio_trim(3)>"];
    example = Some "examples/server-flags.c";
  };

  "can_zero", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "does the server support the zero command?";
    longdesc = "\
Returns true if the server supports the zero command
(see L<nbd_zero(3)>, L<nbd_aio_zero(3)>).  Returns false if
the server does not."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>";
                "L<nbd_zero(3)>"; "L<nbd_aio_zero(3)>";
                "L<nbd_can_fast_zero(3)>"];
    example = Some "examples/server-flags.c";
  };

  "can_fast_zero", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "does the server support the fast zero flag?";
    longdesc = "\
Returns true if the server supports the use of the
C<LIBNBD_CMD_FLAG_FAST_ZERO> flag to the zero command
(see C<nbd_zero>, C<nbd_aio_zero>).  Returns false if
the server does not."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>";
                "L<nbd_zero(3)>"; "L<nbd_aio_zero(3)>"; "L<nbd_can_zero(3)>"];
    example = Some "examples/server-flags.c";
  };

  "can_df", {
    default_call with
    args = []; ret = RBool;
    shortdesc = "does the server support the don't fragment flag to pread?";
    longdesc = "\
Returns true if the server supports structured reads with an
ability to request a non-fragmented read (see L<nbd_pread_structured(3)>,
L<nbd_aio_pread_structured(3)>).  Returns false if the server either lacks
structured reads or if it does not support a non-fragmented read request."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>";
                "L<nbd_pread_structured(3)>";
                "L<nbd_aio_pread_structured(3)>"];
    example = Some "examples/server-flags.c";
  };

  "can_multi_conn", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "does the server support multi-conn?";
    longdesc = "\
Returns true if the server supports multi-conn.  Returns
false if the server does not.

It is not safe to open multiple handles connecting to the
same server if you will write to the server and the
server does not advertise multi-conn support.  The safe
way to check for this is to open one connection, check
this flag is true, then open further connections as
required."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Multi-conn>"];
    example = Some "examples/server-flags.c";
  };

  "can_cache", {
    default_call with
    args = []; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "does the server support the cache command?";
    longdesc = "\
Returns true if the server supports the cache command
(see L<nbd_cache(3)>, L<nbd_aio_cache(3)>).  Returns false if
the server does not."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>";
                "L<nbd_cache(3)>"; "L<nbd_aio_cache(3)>"];
    example = Some "examples/server-flags.c";
  };

  "can_meta_context", {
    default_call with
    args = [String "metacontext"]; ret = RBool;
    permitted_states = [ Connected; Closed ];
    shortdesc = "does the server support a specific meta context?";
    longdesc = "\
Returns true if the server supports the given meta context
(see L<nbd_add_meta_context(3)>).  Returns false if
the server does not.

The single parameter is the name of the metadata context,
for example C<LIBNBD_CONTEXT_BASE_ALLOCATION>.
B<E<lt>libnbd.hE<gt>> includes defined constants for well-known
namespace contexts beginning with C<LIBNBD_CONTEXT_>, but you
are free to pass in other contexts."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Flag calls>";
                "L<nbd_add_meta_context(3)>";
                "L<nbd_block_status(3)>"; "L<nbd_aio_block_status(3)>"];
  };

  "get_protocol", {
    default_call with
    args = []; ret = RStaticString;
    permitted_states = [ Connected; Closed ];
    shortdesc = "return the NBD protocol variant";
    longdesc = "\
Return the NBD protocol variant in use on the connection.  At
the moment this returns one of the strings C<\"oldstyle\">,
C<\"newstyle\"> or C<\"newstyle-fixed\">.  Other strings might
be returned in the future.
Most modern NBD servers use C<\"newstyle-fixed\">.
"
^ non_blocking_test_call_description;
    see_also = ["L<nbd_get_handshake_flags(3)>";
                "L<nbd_get_structured_replies_negotiated(3)>";
                "L<nbd_get_tls_negotiated(3)>"];
  };

  "get_size", {
    default_call with
    args = []; ret = RInt64;
    permitted_states = [ Connected; Closed ];
    shortdesc = "return the export size";
    longdesc = "\
Returns the size in bytes of the NBD export."
^ non_blocking_test_call_description;
    see_also = ["L<libnbd(3)/Size of the export>"];
    example = Some "examples/get-size.c";
  };

  "pread", {
    default_call with
    args = [ BytesOut ("buf", "count"); UInt64 "offset" ];
    optargs = [ OFlags ("flags", cmd_flags) ];
    ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "read from the NBD server";
    longdesc = "\
Issue a read command to the NBD server for the range starting
at C<offset> and ending at C<offset> + C<count> - 1.  NBD
can only read all or nothing using this call.  The call
returns when the data has been read fully into C<buf> or there is an
error.  See also L<nbd_pread_structured(3)>, if finer visibility is
required into the server's replies, or if you want to use
C<LIBNBD_CMD_FLAG_DF>.

The C<flags> parameter must be C<0> for now (it exists for future NBD
protocol extensions).";
    see_also = ["L<nbd_aio_pread(3)>"; "L<nbd_pread_structured(3)>"];
    example = Some "examples/fetch-first-sector.c";
  };

  "pread_structured", {
    default_call with
    args = [ BytesOut ("buf", "count"); UInt64 "offset";
             Closure chunk_closure ];
    optargs = [ OFlags ("flags", cmd_flags) ];
    ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "read from the NBD server";
    longdesc = "\
Issue a read command to the NBD server for the range starting
at C<offset> and ending at C<offset> + C<count> - 1.  The server's
response may be subdivided into chunks which may arrive out of order
before reassembly into the original buffer; the C<chunk> callback
is used for notification after each chunk arrives, and may perform
additional sanity checking on the server's reply. The callback cannot
call C<nbd_*> APIs on the same handle since it holds the handle lock
and will cause a deadlock.  If the callback returns C<-1>, and no
earlier error has been detected, then the overall read command will
fail with any non-zero value stored into the callback's C<error>
parameter (with a default of C<EPROTO>); but any further chunks will
still invoke the callback.

The C<chunk> function is called once per chunk of data received, with
the C<user_data> passed to this function.  The
C<subbuf> and C<count> parameters represent the subset of the original
buffer which has just been populated by results from the server (in C,
C<subbuf> always points within the original C<buf>; but this guarantee
may not extend to other language bindings). The C<offset> parameter
represents the absolute offset at which C<subbuf> begins within the
image (note that this is not the relative offset of C<subbuf> within
the original buffer C<buf>). Changes to C<error> on output are ignored
unless the callback fails. The input meaning of the C<error> parameter
is controlled by the C<status> parameter, which is one of

=over 4

=item C<LIBNBD_READ_DATA> = 1

C<subbuf> was populated with C<count> bytes of data. On input, C<error>
contains the errno value of any earlier detected error, or zero.

=item C<LIBNBD_READ_HOLE> = 2

C<subbuf> represents a hole, and contains C<count> NUL bytes. On input,
C<error> contains the errno value of any earlier detected error, or zero.

=item C<LIBNBD_READ_ERROR> = 3

C<count> is 0, so C<subbuf> is unusable. On input, C<error> contains the
errno value reported by the server as occurring while reading that
C<offset>, regardless if any earlier error has been detected.

=back

Future NBD extensions may permit other values for C<status>, but those
will not be returned to a client that has not opted in to requesting
such extensions. If the server is non-compliant, it is possible for
the C<chunk> function to be called more times than you expect or with
C<count> 0 for C<LIBNBD_READ_DATA> or C<LIBNBD_READ_HOLE>. It is also
possible that the C<chunk> function is not called at all (in
particular, C<LIBNBD_READ_ERROR> is used only when an error is
associated with a particular offset, and not when the server reports a
generic error), but you are guaranteed that the callback was called at
least once if the overall read succeeds. Libnbd does not validate that
the server obeyed the requirement that a read call must not have
overlapping chunks and must not succeed without enough chunks to cover
the entire request.

The C<flags> parameter may be C<0> for no flags, or may contain
C<LIBNBD_CMD_FLAG_DF> meaning that the server should not reply with
more than one fragment (if that is supported - some servers cannot do
this, see L<nbd_can_df(3)>). Libnbd does not validate that the server
actually obeys the flag.";
    see_also = ["L<nbd_can_df(3)>"; "L<nbd_pread(3)>";
                "L<nbd_aio_pread_structured(3)>"];
  };

  "pwrite", {
    default_call with
    args = [ BytesIn ("buf", "count"); UInt64 "offset" ];
    optargs = [ OFlags ("flags", cmd_flags) ];
    ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "write to the NBD server";
    longdesc = "\
Issue a write command to the NBD server, writing the data in
C<buf> to the range starting at C<offset> and ending at
C<offset> + C<count> - 1.  NBD can only write all or nothing
using this call.  The call returns when the command has been
acknowledged by the server, or there is an error.

The C<flags> parameter may be C<0> for no flags, or may contain
C<LIBNBD_CMD_FLAG_FUA> meaning that the server should not
return until the data has been committed to permanent storage
(if that is supported - some servers cannot do this, see
L<nbd_can_fua(3)>).";
    see_also = ["L<nbd_can_fua(3)>"; "L<nbd_is_read_only(3)>";
                "L<nbd_aio_pwrite(3)>"];
    example = Some "examples/reads-and-writes.c";
  };

  "shutdown", {
    default_call with
    args = []; optargs = [ OFlags ("flags", cmd_flags) ]; ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "disconnect from the NBD server";
    longdesc = "\
Issue the disconnect command to the NBD server.  This is
a nice way to tell the server we are going away, but from the
client's point of view has no advantage over abruptly closing
the connection (see L<nbd_close(3)>).

This function works whether or not the handle is ready for
transmission of commands, and as such does not take a C<flags>
parameter. If more fine-grained control is needed, see
L<nbd_aio_disconnect(3)>.

The C<flags> parameter must be C<0> for now (it exists for future NBD
protocol extensions).";
    see_also = ["L<nbd_close(3)>"; "L<nbd_aio_disconnect(3)>"];
    example = Some "examples/reads-and-writes.c";
  };

  "flush", {
    default_call with
    args = []; optargs = [ OFlags ("flags", cmd_flags) ]; ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "send flush command to the NBD server";
    longdesc = "\
Issue the flush command to the NBD server.  The function should
return when all write commands which have completed have been
committed to permanent storage on the server.  Note this will
return an error if L<nbd_can_flush(3)> is false.

The C<flags> parameter must be C<0> for now (it exists for future NBD
protocol extensions).";
    see_also = ["L<nbd_can_flush(3)>"; "L<nbd_aio_flush(3)>"];
  };

  "trim", {
    default_call with
    args = [ UInt64 "count"; UInt64 "offset" ];
    optargs = [ OFlags ("flags", cmd_flags) ];
    ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "send trim command to the NBD server";
    longdesc = "\
Issue a trim command to the NBD server, which if supported
by the server causes a hole to be punched in the backing
store starting at C<offset> and ending at C<offset> + C<count> - 1.
The call returns when the command has been acknowledged by the server,
or there is an error.

The C<flags> parameter may be C<0> for no flags, or may contain
C<LIBNBD_CMD_FLAG_FUA> meaning that the server should not
return until the data has been committed to permanent storage
(if that is supported - some servers cannot do this, see
L<nbd_can_fua(3)>).";
    see_also = ["L<nbd_can_fua(3)>"; "L<nbd_can_trim(3)>";
                "L<nbd_aio_trim(3)>"];
  };

  "cache", {
    default_call with
    args = [ UInt64 "count"; UInt64 "offset" ];
    optargs = [ OFlags ("flags", cmd_flags) ];
    ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "send cache (prefetch) command to the NBD server";
    longdesc = "\
Issue the cache (prefetch) command to the NBD server, which
if supported by the server causes data to be prefetched
into faster storage by the server, speeding up a subsequent
L<nbd_pread(3)> call.  The server can also silently ignore
this command.  Note this will return an error if
L<nbd_can_cache(3)> is false.

The C<flags> parameter must be C<0> for now (it exists for future NBD
protocol extensions).";
    see_also = ["L<nbd_can_cache(3)>"; "L<nbd_aio_cache(3)>"];
  };

  "zero", {
    default_call with
    args = [ UInt64 "count"; UInt64 "offset" ];
    optargs = [ OFlags ("flags", cmd_flags) ];
    ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "send write zeroes command to the NBD server";
    longdesc = "\
Issue a write zeroes command to the NBD server, which if supported
by the server causes a zeroes to be written efficiently
starting at C<offset> and ending at C<offset> + C<count> - 1.
The call returns when the command has been acknowledged by the server,
or there is an error.

The C<flags> parameter may be C<0> for no flags, or may contain
C<LIBNBD_CMD_FLAG_FUA> meaning that the server should not
return until the data has been committed to permanent storage
(if that is supported - some servers cannot do this, see
L<nbd_can_fua(3)>), C<LIBNBD_CMD_FLAG_NO_HOLE> meaning that
the server should favor writing actual allocated zeroes over
punching a hole, and/or C<LIBNBD_CMD_FLAG_FAST_ZERO> meaning
that the server must fail quickly if writing zeroes is no
faster than a normal write (if that is supported - some servers
cannot do this, see L<nbd_can_fast_zero(3)>).";
    see_also = ["L<nbd_can_fua(3)>"; "L<nbd_can_zero(3)>";
                "L<nbd_can_fast_zero(3)>"; "L<nbd_aio_zero(3)>"];
  };

  "block_status", {
    default_call with
    args = [ UInt64 "count"; UInt64 "offset"; Closure extent_closure ];
    optargs = [ OFlags ("flags", cmd_flags) ];
    ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "send block status command to the NBD server";
    longdesc = "\
Issue the block status command to the NBD server.  If
supported by the server, this causes metadata context
information about blocks beginning from the specified
offset to be returned. The C<count> parameter is a hint: the
server may choose to return less status, or the final block
may extend beyond the requested range. If multiple contexts
are supported, the number of blocks and cumulative length
of those blocks need not be identical between contexts.

Depending on which metadata contexts were enabled before
connecting (see L<nbd_add_meta_context(3)>) and which are
supported by the server (see L<nbd_can_meta_context(3)>) this call
returns information about extents by calling back to the
C<extent> function.  The callback cannot call C<nbd_*> APIs on the
same handle since it holds the handle lock and will
cause a deadlock.  If the callback returns C<-1>, and no earlier
error has been detected, then the overall block status command
will fail with any non-zero value stored into the callback's
C<error> parameter (with a default of C<EPROTO>); but any further
contexts will still invoke the callback.

The C<extent> function is called once per type of metadata available,
with the C<user_data> passed to this function.  The C<metacontext>
parameter is a string such as C<\"base:allocation\">.  The C<entries>
array is an array of pairs of integers with the first entry in each
pair being the length (in bytes) of the block and the second entry
being a status/flags field which is specific to the metadata context.
(The number of pairs passed to the function is C<nr_entries/2>.)  The
NBD protocol document in the section about
C<NBD_REPLY_TYPE_BLOCK_STATUS> describes the meaning of this array;
for contexts known to libnbd, B<E<lt>libnbd.hE<gt>> contains constants
beginning with C<LIBNBD_STATE_> that may help decipher the values.
On entry to the callback, the C<error> parameter contains the errno
value of any previously detected error.

It is possible for the extent function to be called
more times than you expect (if the server is buggy),
so always check the C<metacontext> field to ensure you
are receiving the data you expect.  It is also possible
that the extent function is not called at all, even for
metadata contexts that you requested.  This indicates
either that the server doesn't support the context
or for some other reason cannot return the data.

The C<flags> parameter may be C<0> for no flags, or may contain
C<LIBNBD_CMD_FLAG_REQ_ONE> meaning that the server should
return only one extent per metadata context where that extent
does not exceed C<count> bytes; however, libnbd does not
validate that the server obeyed the flag.";
    see_also = ["L<nbd_add_meta_context(3)>"; "L<nbd_can_meta_context(3)>";
                "L<nbd_aio_block_status(3)>"];
  };

  "poll", {
    default_call with
    args = [ Int "timeout" ]; ret = RInt;
    shortdesc = "poll the handle once";
    longdesc = "\
This is a simple implementation of L<poll(2)> which is used
internally by synchronous API calls.  On success, it returns
C<0> if the C<timeout> (in milliseconds) occurs, or C<1> if
the poll completed and the state machine progressed. Set
C<timeout> to C<-1> to block indefinitely (but be careful
that eventual action is actually expected - for example, if
the connection is established but there are no commands in
flight, using an infinite timeout will permanently block).

This function is mainly useful as an example of how you might
integrate libnbd with your own main loop, rather than being
intended as something you would use.";
    example = Some "examples/aio-connect-read.c";
  };

  "aio_connect", {
    default_call with
    args = [ SockAddrAndLen ("addr", "addrlen") ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to the NBD server";
    longdesc = "\
Begin connecting to the NBD server.  The C<addr> and C<addrlen>
parameters specify the address of the socket to connect to.

You can check if the connection is still connecting by calling
L<nbd_aio_is_connecting(3)>, or if it has connected to the server
and completed the NBD handshake by calling L<nbd_aio_is_ready(3)>,
on the connection.";
  };

  "aio_connect_uri", {
    default_call with
    args = [ String "uri" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to an NBD URI";
    longdesc = "\
Begin connecting to the NBD URI C<uri>.  Parameters behave as
documented in L<nbd_connect_uri(3)>.

You can check if the connection is still connecting by calling
L<nbd_aio_is_connecting(3)>, or if it has connected to the server
and completed the NBD handshake by calling L<nbd_aio_is_ready(3)>,
on the connection.";
    see_also = ["L<https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md>"]
  };

  "aio_connect_unix", {
    default_call with
    args = [ Path "unixsocket" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to the NBD server over a Unix domain socket";
    longdesc = "\
Begin connecting to the NBD server over Unix domain socket
(C<unixsocket>).  Parameters behave as documented in
L<nbd_connect_unix(3)>.

You can check if the connection is still connecting by calling
L<nbd_aio_is_connecting(3)>, or if it has connected to the server
and completed the NBD handshake by calling L<nbd_aio_is_ready(3)>,
on the connection.";
    example = Some "examples/aio-connect-read.c";
  };

  "aio_connect_vsock", {
    default_call with
    args = [ UInt32 "cid"; UInt32 "port" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to the NBD server over AF_VSOCK socket";
    longdesc = "\
Begin connecting to the NBD server over the C<AF_VSOCK>
protocol to the server C<cid:port>.  Parameters behave as documented in
L<nbd_connect_vsock(3)>.

You can check if the connection is still connecting by calling
L<nbd_aio_is_connecting(3)>, or if it has connected to the server
and completed the NBD handshake by calling L<nbd_aio_is_ready(3)>,
on the connection.";
  };

  "aio_connect_tcp", {
    default_call with
    args = [ String "hostname"; String "port" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to the NBD server over a TCP port";
    longdesc = "\
Begin connecting to the NBD server listening on C<hostname:port>.
Parameters behave as documented in L<nbd_connect_tcp(3)>.

You can check if the connection is still connecting by calling
L<nbd_aio_is_connecting(3)>, or if it has connected to the server
and completed the NBD handshake by calling L<nbd_aio_is_ready(3)>,
on the connection.";
  };

  "aio_connect_socket", {
    default_call with
    args = [ Fd "sock" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect directly to a connected socket";
    longdesc = "\
Begin connecting to the connected socket C<fd>.
Parameters behave as documented in L<nbd_connect_socket(3)>.

You can check if the connection is still connecting by calling
L<nbd_aio_is_connecting(3)>, or if it has connected to the server
and completed the NBD handshake by calling L<nbd_aio_is_ready(3)>,
on the connection.";
  };

  "aio_connect_command", {
    default_call with
    args = [ StringList "argv" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect to the NBD server";
    longdesc = "\
Run the command as a subprocess and begin connecting to it over
stdin/stdout.  Parameters behave as documented in
L<nbd_connect_command(3)>.

You can check if the connection is still connecting by calling
L<nbd_aio_is_connecting(3)>, or if it has connected to the server
and completed the NBD handshake by calling L<nbd_aio_is_ready(3)>,
on the connection.";
  };

  "aio_connect_systemd_socket_activation", {
    default_call with
    args = [ StringList "argv" ]; ret = RErr;
    permitted_states = [ Created ];
    shortdesc = "connect using systemd socket activation";
    longdesc = "\
Run the command as a subprocess and begin connecting to it using
systemd socket activation.  Parameters behave as documented in
L<nbd_connect_systemd_socket_activation(3)>.

You can check if the connection is still connecting by calling
L<nbd_aio_is_connecting(3)>, or if it has connected to the server
and completed the NBD handshake by calling L<nbd_aio_is_ready(3)>,
on the connection.";
  };

  "aio_pread", {
    default_call with
    args = [ BytesPersistOut ("buf", "count"); UInt64 "offset" ];
    optargs = [ OClosure completion_closure; OFlags ("flags", cmd_flags) ];
    ret = RCookie;
    permitted_states = [ Connected ];
    shortdesc = "read from the NBD server";
    longdesc = "\
Issue a read command to the NBD server.

To check if the command completed, call L<nbd_aio_command_completed(3)>.
Or supply the optional C<completion_callback> which will be invoked
as described in L<libnbd(3)/Completion callbacks>.

Note that you must ensure C<buf> is valid until the command has
completed.  Other parameters behave as documented in L<nbd_pread(3)>.";
    example = Some "examples/aio-connect-read.c";
    see_also = ["L<libnbd(3)/Issuing asynchronous commands>";
                "L<nbd_aio_pread_structured(3)>"; "L<nbd_pread(3)>"];
  };

  "aio_pread_structured", {
    default_call with
    args = [ BytesPersistOut ("buf", "count"); UInt64 "offset";
             Closure chunk_closure ];
    optargs = [ OClosure completion_closure; OFlags ("flags", cmd_flags) ];
    ret = RCookie;
    permitted_states = [ Connected ];
    shortdesc = "read from the NBD server";
    longdesc = "\
Issue a read command to the NBD server.

To check if the command completed, call L<nbd_aio_command_completed(3)>.
Or supply the optional C<completion_callback> which will be invoked
as described in L<libnbd(3)/Completion callbacks>.

Other parameters behave as documented in L<nbd_pread_structured(3)>.";
    see_also = ["L<libnbd(3)/Issuing asynchronous commands>";
                "L<nbd_aio_pread(3)>"; "L<nbd_pread_structured(3)>"];
  };

  "aio_pwrite", {
    default_call with
    args = [ BytesPersistIn ("buf", "count"); UInt64 "offset" ];
    optargs = [ OClosure completion_closure; OFlags ("flags", cmd_flags) ];
    ret = RCookie;
    permitted_states = [ Connected ];
    shortdesc = "write to the NBD server";
    longdesc = "\
Issue a write command to the NBD server.

To check if the command completed, call L<nbd_aio_command_completed(3)>.
Or supply the optional C<completion_callback> which will be invoked
as described in L<libnbd(3)/Completion callbacks>.

Note that you must ensure C<buf> is valid until the command has
completed.  Other parameters behave as documented in L<nbd_pwrite(3)>.";
    see_also = ["L<libnbd(3)/Issuing asynchronous commands>";
                "L<nbd_is_read_only(3)>"; "L<nbd_pwrite(3)>"];
  };

  "aio_disconnect", {
    default_call with
    args = []; optargs = [ OFlags ("flags", cmd_flags) ]; ret = RErr;
    permitted_states = [ Connected ];
    shortdesc = "disconnect from the NBD server";
    longdesc = "\
Issue the disconnect command to the NBD server.  This is
not a normal command because NBD servers are not obliged
to send a reply.  Instead you should wait for
L<nbd_aio_is_closed(3)> to become true on the connection.  Once this
command is issued, you cannot issue any further commands.

Although libnbd does not prevent you from issuing this command while
still waiting on the replies to previous commands, the NBD protocol
recommends that you wait until there are no other commands in flight
(see L<nbd_aio_in_flight(3)>), to give the server a better chance at a
clean shutdown.

The C<flags> parameter must be C<0> for now (it exists for future NBD
protocol extensions).  There is no direct synchronous counterpart;
however, L<nbd_shutdown(3)> will call this function if appropriate.";
    see_also = ["L<nbd_aio_in_flight(3)>"];
  };

  "aio_flush", {
    default_call with
    args = [];
    optargs = [ OClosure completion_closure; OFlags ("flags", cmd_flags) ];
    ret = RCookie;
    permitted_states = [ Connected ];
    shortdesc = "send flush command to the NBD server";
    longdesc = "\
Issue the flush command to the NBD server.

To check if the command completed, call L<nbd_aio_command_completed(3)>.
Or supply the optional C<completion_callback> which will be invoked
as described in L<libnbd(3)/Completion callbacks>.

Other parameters behave as documented in L<nbd_flush(3)>.";
    see_also = ["L<libnbd(3)/Issuing asynchronous commands>";
                "L<nbd_can_flush(3)>"; "L<nbd_flush(3)>"];
  };

  "aio_trim", {
    default_call with
    args = [ UInt64 "count"; UInt64 "offset" ];
    optargs = [ OClosure completion_closure; OFlags ("flags", cmd_flags) ];
    ret = RCookie;
    permitted_states = [ Connected ];
    shortdesc = "send trim command to the NBD server";
    longdesc = "\
Issue a trim command to the NBD server.

To check if the command completed, call L<nbd_aio_command_completed(3)>.
Or supply the optional C<completion_callback> which will be invoked
as described in L<libnbd(3)/Completion callbacks>.

Other parameters behave as documented in L<nbd_trim(3)>.";
    see_also = ["L<libnbd(3)/Issuing asynchronous commands>";
                "L<nbd_can_trim(3)>"; "L<nbd_trim(3)>"];
  };

  "aio_cache", {
    default_call with
    args = [ UInt64 "count"; UInt64 "offset" ];
    optargs = [ OClosure completion_closure; OFlags ("flags", cmd_flags) ];
    ret = RCookie;
    permitted_states = [ Connected ];
    shortdesc = "send cache (prefetch) command to the NBD server";
    longdesc = "\
Issue the cache (prefetch) command to the NBD server.

To check if the command completed, call L<nbd_aio_command_completed(3)>.
Or supply the optional C<completion_callback> which will be invoked
as described in L<libnbd(3)/Completion callbacks>.

Other parameters behave as documented in L<nbd_cache(3)>.";
    see_also = ["L<libnbd(3)/Issuing asynchronous commands>";
                "L<nbd_can_cache(3)>"; "L<nbd_cache(3)>"];
  };

  "aio_zero", {
    default_call with
    args = [ UInt64 "count"; UInt64 "offset" ];
    optargs = [ OClosure completion_closure; OFlags ("flags", cmd_flags) ];
    ret = RCookie;
    permitted_states = [ Connected ];
    shortdesc = "send write zeroes command to the NBD server";
    longdesc = "\
Issue a write zeroes command to the NBD server.

To check if the command completed, call L<nbd_aio_command_completed(3)>.
Or supply the optional C<completion_callback> which will be invoked
as described in L<libnbd(3)/Completion callbacks>.

Other parameters behave as documented in L<nbd_zero(3)>.";
    see_also = ["L<libnbd(3)/Issuing asynchronous commands>";
                "L<nbd_can_zero(3)>"; "L<nbd_can_fast_zero(3)>";
                "L<nbd_zero(3)>"];
  };

  "aio_block_status", {
    default_call with
    args = [ UInt64 "count"; UInt64 "offset"; Closure extent_closure ];
    optargs = [ OClosure completion_closure; OFlags ("flags", cmd_flags) ];
    ret = RCookie;
    permitted_states = [ Connected ];
    shortdesc = "send block status command to the NBD server";
    longdesc = "\
Send the block status command to the NBD server.

To check if the command completed, call L<nbd_aio_command_completed(3)>.
Or supply the optional C<completion_callback> which will be invoked
as described in L<libnbd(3)/Completion callbacks>.

Other parameters behave as documented in L<nbd_block_status(3)>.";
    see_also = ["L<libnbd(3)/Issuing asynchronous commands>";
                "L<nbd_can_meta_context(3)>"; "L<nbd_block_status(3)>"];
  };

  "aio_get_fd", {
    default_call with
    args = []; ret = RFd;
    shortdesc = "return file descriptor associated with this connection";
    longdesc = "\
Return the underlying file descriptor associated with this
connection.  You can use this to check if the file descriptor
is ready for reading or writing and call L<nbd_aio_notify_read(3)>
or L<nbd_aio_notify_write(3)>.  See also L<nbd_aio_get_direction(3)>.
Do not do anything else with the file descriptor.";
    see_also = ["L<nbd_aio_get_direction(3)>"];
  };

  "aio_get_direction", {
    default_call with
    args = []; ret = RUInt; is_locked = false; may_set_error = false;
    shortdesc = "return the read or write direction";
    longdesc = "\
Return the current direction of this connection, which means
whether we are next expecting to read data from the server, write
data to the server, or both.  It returns

=over 4

=item 0

We are not expected to interact with the server file descriptor from
the current state. It is not worth attempting to use L<poll(2)>; if
the connection is not dead, then state machine progress must instead
come from some other means such as L<nbd_aio_connect(3)>.

=item C<LIBNBD_AIO_DIRECTION_READ> = 1

We are expected next to read from the server.  If using L<poll(2)>
you would set C<events = POLLIN>.  If C<revents> returns C<POLLIN>
or C<POLLHUP> you would then call L<nbd_aio_notify_read(3)>.

Note that once libnbd reaches L<nbd_aio_is_ready(3)>, this direction is
returned even when there are no commands in flight (see
L<nbd_aio_in_flight(3)>). In a single-threaded use of libnbd, it is not
worth polling until after issuing a command, as otherwise the server
will never wake up the poll. In a multi-threaded scenario, you can
have one thread begin a polling loop prior to any commands, but any
other thread that issues a command will need a way to kick the
polling thread out of poll in case issuing the command changes the
needed polling direction. Possible ways to do this include polling
for activity on a pipe-to-self, or using L<pthread_kill(3)> to send
a signal that is masked except during L<ppoll(2)>.

=item C<LIBNBD_AIO_DIRECTION_WRITE> = 2

We are expected next to write to the server.  If using L<poll(2)>
you would set C<events = POLLOUT>.  If C<revents> returns C<POLLOUT>
you would then call L<nbd_aio_notify_write(3)>.

=item C<LIBNBD_AIO_DIRECTION_BOTH> = 3

We are expected next to either read or write to the server.  If using
L<poll(2)> you would set C<events = POLLIN|POLLOUT>.  If only one of
C<POLLIN> or C<POLLOUT> is returned, then see above.  However, if both
are returned, it is better to call only L<nbd_aio_notify_read(3)>, as
processing the server's reply may change the state of the connection
and invalidate the need to write more commands.

=back";
    see_also = ["L<nbd_aio_in_flight(3)>"];
  };

  "aio_notify_read", {
    default_call with
    args = []; ret = RErr;
    shortdesc = "notify that the connection is readable";
    longdesc = "\
Send notification to the state machine that the connection
is readable.  Typically this is called after your main loop
has detected that the file descriptor associated with this
connection is readable.";
  };

  "aio_notify_write", {
    default_call with
    args = []; ret = RErr;
    shortdesc = "notify that the connection is writable";
    longdesc = "\
Send notification to the state machine that the connection
is writable.  Typically this is called after your main loop
has detected that the file descriptor associated with this
connection is writable.";
  };

  "aio_is_created", {
    default_call with
    args = []; ret = RBool; is_locked = false; may_set_error = false;
    shortdesc = "check if the connection has just been created";
    longdesc = "\
Return true if this connection has just been created.  This
is the state before the handle has started connecting to a
server.  In this state the handle can start to be connected
by calling functions such as L<nbd_aio_connect(3)>.";
  };

  "aio_is_connecting", {
    default_call with
    args = []; ret = RBool; is_locked = false; may_set_error = false;
    shortdesc = "check if the connection is connecting or handshaking";
    longdesc = "\
Return true if this connection is connecting to the server
or in the process of handshaking and negotiating options
which happens before the handle becomes ready to
issue commands (see L<nbd_aio_is_ready(3)>).";
    see_also = ["L<nbd_aio_is_ready(3)>"];
  };

  "aio_is_ready", {
    default_call with
    args = []; ret = RBool; is_locked = false; may_set_error = false;
    shortdesc = "check if the connection is in the ready state";
    longdesc = "\
Return true if this connection is connected to the NBD server,
the handshake has completed, and the connection is idle or
waiting for a reply.  In this state the handle is ready to
issue commands.";
  };

  "aio_is_processing", {
    default_call with
    args = []; ret = RBool; is_locked = false; may_set_error = false;
    shortdesc = "check if the connection is processing a command";
    longdesc = "\
Return true if this connection is connected to the NBD server,
the handshake has completed, and the connection is processing
commands (either writing out a request or reading a reply).

Note the ready state (L<nbd_aio_is_ready(3)>) is not included.
In the ready state commands may be I<in flight> (the I<server>
is processing them), but libnbd is not processing them.";
  };

  "aio_is_dead", {
    default_call with
    args = []; ret = RBool; is_locked = false; may_set_error = false;
    shortdesc = "check if the connection is dead";
    longdesc = "\
Return true if the connection has encountered a fatal
error and is dead.  In this state the handle may only be closed.
There is no way to recover a handle from the dead state.";
  };

  "aio_is_closed", {
    default_call with
    args = []; ret = RBool; is_locked = false; may_set_error = false;
    shortdesc = "check if the connection is closed";
    longdesc = "\
Return true if the connection has closed.  There is no way to
reconnect a closed connection.  Instead you must close the
whole handle.";
  };

  "aio_command_completed", {
    default_call with
    args = [Int64 "cookie"]; ret = RBool;
    shortdesc = "check if the command completed";
    longdesc = "\
Return true if the command completed.  If this function returns
true then the command was successful and it has been retired.
Return false if the command is still in flight.  This can also
fail with an error in case the command failed (in this case
the command is also retired).  A command is retired either via
this command, or by using a completion callback which returns C<1>.

The C<cookie> parameter is the positive unique 64 bit cookie
for the command, as returned by a call such as L<nbd_aio_pread(3)>.";
  };

  "aio_peek_command_completed", {
    default_call with
    args = []; ret = RInt64;
    shortdesc = "check if any command has completed";
    longdesc = "\
Return the unique positive 64 bit cookie of the first non-retired but
completed command, C<0> if there are in-flight commands but none of
them are awaiting retirement, or C<-1> on error including when there
are no in-flight commands. Any cookie returned by this function must
still be passed to L<nbd_aio_command_completed(3)> to actually retire
the command and learn whether the command was successful.";
  };

  "aio_in_flight", {
    default_call with
    args = []; ret = RInt;
    permitted_states = [ Connected; Closed; Dead ];
    (* XXX is_locked = false ? *)
    shortdesc = "check how many aio commands are still in flight";
    longdesc = "\
Return the number of in-flight aio commands that are still awaiting a
response from the server before they can be retired.  If this returns
a non-zero value when requesting a disconnect from the server (see
L<nbd_aio_disconnect(3)> and L<nbd_shutdown(3)>), libnbd does not try to
wait for those commands to complete gracefully; if the server strands
commands while shutting down, L<nbd_aio_command_completed(3)> will report
those commands as failed with a status of C<ENOTCONN>.";
    example = Some "examples/aio-connect-read.c";
    see_also = ["L<nbd_aio_disconnect(3)>"];
  };

  "connection_state", {
    default_call with
    args = []; ret = RStaticString;
    shortdesc = "return string describing the state of the connection";
    longdesc = "\
Returns a descriptive string for the state of the connection.  This
can be used for debugging or troubleshooting, but you should not
rely on the state of connections since it may change in future
versions.";
  };

  "get_package_name", {
    default_call with
    args = []; ret = RStaticString; is_locked = false; may_set_error = false;
    shortdesc = "return the name of the library";
    longdesc = "\
Returns the name of the library, always C<\"libnbd\"> unless
the library was modified with another name at compile time.";
  };

  "get_version", {
    default_call with
    args = []; ret = RStaticString; is_locked = false; may_set_error = false;
    shortdesc = "return the version of the library";
    longdesc = "\
Return the version of libnbd.  This is returned as a string
in the form C<\"major.minor.release\"> where each of major, minor
and release is a small positive integer.  For example:

     minor
       ↓
    \"1.0.3\"
     ↑   ↑
 major   release

=over 4

=item major = 0

The major number was C<0> for the early experimental versions of
libnbd where we still had an unstable API.

=item major = 1

The major number is C<1> for the versions of libnbd with a
long-term stable API and ABI.  It is not anticipated that
major will be any number other than C<1>.

=item minor = 0, 2, ... (even)

The minor number is even for stable releases.

=item minor = 1, 3, ... (odd)

The minor number is odd for development versions.  Note that
new APIs added in a development version remain experimental
and subject to change in that branch until they appear in a stable
release.

=item release

The release number is incremented for each release along a particular
branch.

=back";
  };

  "kill_subprocess", {
    default_call with
    args = [ Int "signum" ]; ret = RErr;
    shortdesc = "kill server running as a subprocess";
    longdesc = "\
This call may be used to kill the server running as a subprocess
that was previously created using L<nbd_connect_command(3)>.  You
do not need to use this call.  It is only needed if the server
does not exit when the socket is closed.

The C<signum> parameter is the optional signal number to send
(see L<signal(7)>).  If C<signum> is C<0> then C<SIGTERM> is sent.";
    see_also = ["L<signal(7)>"; "L<nbd_connect_command(3)>"];
  };

  "supports_tls", {
    default_call with
    args = []; ret = RBool; is_locked = false; may_set_error = false;
    shortdesc = "true if libnbd was compiled with support for TLS";
    longdesc = "\
Returns true if libnbd was compiled with gnutls which is required
to support TLS encryption, or false if not.";
    see_also = ["L<nbd_set_tls(3)>"];
  };

  "supports_uri", {
    default_call with
    args = []; ret = RBool; is_locked = false; may_set_error = false;
    shortdesc = "true if libnbd was compiled with support for NBD URIs";
    longdesc = "\
Returns true if libnbd was compiled with libxml2 which is required
to support NBD URIs, or false if not.";
    see_also = ["L<nbd_connect_uri(3)>"; "L<nbd_aio_connect_uri(3)>"];
  };
]

(* The first stable version that the symbol appeared in, for
 * example (1, 2) if the symbol was added in development cycle
 * 1.1.x and thus the first stable version was 1.2.
 *)
let first_version = [
  "set_debug", (1, 0);
  "get_debug", (1, 0);
  "set_debug_callback", (1, 0);
  "clear_debug_callback", (1, 0);
  "set_handle_name", (1, 0);
  "get_handle_name", (1, 0);
  "set_export_name", (1, 0);
  "get_export_name", (1, 0);
  "set_tls", (1, 0);
  "get_tls", (1, 0);
  "set_tls_certificates", (1, 0);
  "set_tls_verify_peer", (1, 0);
  "get_tls_verify_peer", (1, 0);
  "set_tls_username", (1, 0);
  "get_tls_username", (1, 0);
  "set_tls_psk_file", (1, 0);
  "add_meta_context", (1, 0);
  "connect_uri", (1, 0);
  "connect_unix", (1, 0);
  "connect_tcp", (1, 0);
  "connect_command", (1, 0);
  "is_read_only", (1, 0);
  "can_flush", (1, 0);
  "can_fua", (1, 0);
  "is_rotational", (1, 0);
  "can_trim", (1, 0);
  "can_zero", (1, 0);
  "can_df", (1, 0);
  "can_multi_conn", (1, 0);
  "can_cache", (1, 0);
  "can_meta_context", (1, 0);
  "get_size", (1, 0);
  "pread", (1, 0);
  "pread_structured", (1, 0);
  "pwrite", (1, 0);
  "shutdown", (1, 0);
  "flush", (1, 0);
  "trim", (1, 0);
  "cache", (1, 0);
  "zero", (1, 0);
  "block_status", (1, 0);
  "poll", (1, 0);
  "aio_connect", (1, 0);
  "aio_connect_uri", (1, 0);
  "aio_connect_unix", (1, 0);
  "aio_connect_tcp", (1, 0);
  "aio_connect_command", (1, 0);
  "aio_pread", (1, 0);
  "aio_pread_structured", (1, 0);
  "aio_pwrite", (1, 0);
  "aio_disconnect", (1, 0);
  "aio_flush", (1, 0);
  "aio_trim", (1, 0);
  "aio_cache", (1, 0);
  "aio_zero", (1, 0);
  "aio_block_status", (1, 0);
  "aio_get_fd", (1, 0);
  "aio_get_direction", (1, 0);
  "aio_notify_read", (1, 0);
  "aio_notify_write", (1, 0);
  "aio_is_created", (1, 0);
  "aio_is_connecting", (1, 0);
  "aio_is_ready", (1, 0);
  "aio_is_processing", (1, 0);
  "aio_is_dead", (1, 0);
  "aio_is_closed", (1, 0);
  "aio_command_completed", (1, 0);
  "aio_peek_command_completed", (1, 0);
  "aio_in_flight", (1, 0);
  "connection_state", (1, 0);
  "get_package_name", (1, 0);
  "get_version", (1, 0);
  "kill_subprocess", (1, 0);
  "supports_tls", (1, 0);
  "supports_uri", (1, 0);

  (* Added in 1.1.x development cycle, will be stable and supported in 1.2. *)
  "can_fast_zero", (1, 2);
  "set_request_structured_replies", (1, 2);
  "get_request_structured_replies", (1, 2);
  "get_structured_replies_negotiated", (1, 2);
  "get_tls_negotiated", (1, 2);
  "get_protocol", (1, 2);
  "set_handshake_flags", (1, 2);
  "get_handshake_flags", (1, 2);
  "connect_systemd_socket_activation", (1, 2);
  "aio_connect_systemd_socket_activation", (1, 2);
  "connect_socket", (1, 2);
  "aio_connect_socket", (1, 2);
  "connect_vsock", (1, 2);
  "aio_connect_vsock", (1, 2);
  "set_uri_allow_transports", (1, 2);
  "set_uri_allow_tls", (1, 2);
  "set_uri_allow_local_file", (1, 2);

  (* These calls are proposed for a future version of libnbd, but
   * have not been added to any released version so far.
  "get_tls_certificates", (1, ??);
  "get_tls_psk_file", (1, ??);
   *)
]

(* Constants, etc. *)
let constants = [
  "AIO_DIRECTION_READ",  1;
  "AIO_DIRECTION_WRITE", 2;
  "AIO_DIRECTION_BOTH",  3;

  "READ_DATA",           1;
  "READ_HOLE",           2;
  "READ_ERROR",          3;
]

let metadata_namespaces = [
  "base", [ "allocation", [
    "STATE_HOLE", 1 lsl 0;
    "STATE_ZERO", 1 lsl 1;
  ] ];
]

(*----------------------------------------------------------------------*)

(* Helper functions. *)

let failwithf fs = ksprintf failwith fs

let rec filter_map f = function
  | [] -> []
  | x :: xs ->
      match f x with
      | Some y -> y :: filter_map f xs
      | None -> filter_map f xs

(* group_by [1, "foo"; 2, "bar"; 2, "baz"; 2, "biz"; 3, "boo"; 4, "fizz"]
 * - : (int * string list) list =
 * [(1, ["foo"]); (2, ["bar"; "baz"; "biz"]); (3, ["boo"]); (4, ["fizz"])]
 *)
let rec group_by = function
| [] -> []
| (day1, x1) :: (day2, x2) :: rest when day1 = day2 ->
   let rest = group_by ((day2, x2) :: rest) in
   let day, xs = List.hd rest in
   (day, x1 :: xs) :: List.tl rest
| (day, x) :: rest ->
   (day, [x]) :: group_by rest

let uniq ?(cmp = compare) xs =
  let rec loop acc = function
    | [] -> acc
    | [x] -> x :: acc
    | x :: (y :: _ as xs) when cmp x y = 0 ->
       loop acc xs
    | x :: (y :: _ as xs) ->
       loop (x :: acc) xs
  in
  List.rev (loop [] xs)

(* This is present in OCaml 4.04, so we can remove it when
 * we depend on OCaml >= 4.04.
 *)
let sort_uniq ?(cmp = compare) xs =
  let xs = List.sort cmp xs in
  let xs = uniq ~cmp xs in
  xs

let is_prefix str prefix =
  let n = String.length prefix in
  String.length str >= n && String.sub str 0 n = prefix

let rec find s sub =
  let len = String.length s in
  let sublen = String.length sub in
  let rec loop i =
    if i <= len-sublen then (
      let rec loop2 j =
        if j < sublen then (
          if s.[i+j] = sub.[j] then loop2 (j+1)
          else -1
        ) else
          i (* found *)
      in
      let r = loop2 0 in
      if r = -1 then loop (i+1) else r
    ) else
      -1 (* not found *)
  in
  loop 0

let rec split sep str =
  let len = String.length sep in
  let seplen = String.length str in
  let i = find str sep in
  if i = -1 then str, ""
  else (
    String.sub str 0 i, String.sub str (i + len) (seplen - i - len)
  )

and nsplit sep str =
  if find str sep = -1 then
    [str]
  else (
    let s1, s2 = split sep str in
    s1 :: nsplit sep s2
  )

let char_mem c str = String.contains str c

let span str accept =
  let len = String.length str in
  let rec loop i =
    if i >= len then len
    else if char_mem (String.unsafe_get str i) accept then loop (i+1)
    else i
  in
  loop 0

let cspan str reject =
  let len = String.length str in
  let rec loop i =
    if i >= len then len
    else if char_mem (String.unsafe_get str i) reject then i
    else loop (i+1)
  in
  loop 0

(* Last output column. *)
let col = ref 0

type chan = NoOutput | OutChannel of out_channel | Buffer of Buffer.t
let chan = ref NoOutput
let pr fs =
  ksprintf (
    fun str ->
      (* Maintain the current output column.  We can simply do this
       * by counting backwards from the end of the string until we
       * reach a \n (or the beginning).  The number we count is
       * the new column.  This only works for 7 bit ASCII but
       * that's enough for what we need this for.
       *)
      let rec loop i acc =
        if i >= 0 then (
          if String.unsafe_get str i = '\n' then acc
          else loop (i-1) (acc+1)
        )
        else !col + acc
      in
      col := loop (String.length str - 1) 0;
      match !chan with
      | NoOutput -> failwithf "use ‘output_to’ to set output"
      | OutChannel chan -> output_string chan str
      | Buffer b -> Buffer.add_string b str
  ) fs

let pr_wrap ?(maxcol = 76) c code =
  (* Save the current output channel and replace it with a
   * temporary buffer while running ‘code’.  Then we wrap the
   * buffer and write it to the restored channel.
   *)
  let old_chan = !chan in
  let wrapping_col = !col in
  let b = Buffer.create 1024 in
  chan := Buffer b;
  let exn = try code (); None with exn -> Some exn in
  chan := old_chan;
  col := wrapping_col;
  (match exn with None -> () | Some exn -> raise exn);

  let lines = nsplit "\n" (Buffer.contents b) in
  match lines with
  | [] -> ()
  | line :: rest ->
     let fields = nsplit (String.make 1 c) line in
     let maybe_wrap field =
       if !col > wrapping_col && !col + String.length field >= maxcol then (
         pr "\n";
         for i = 0 to wrapping_col-1 do pr " " done;
         match span field " \t" with
         | 0 -> field
         | i -> String.sub field i (String.length field - i)
       )
       else field
     in
     let rec loop = function
       | [] -> ()
       | f :: [] -> let f = maybe_wrap f in pr "%s" f;
       | f :: fs -> let f = maybe_wrap f in pr "%s%c" f c; loop fs
     in
     loop fields;

     (* There should really only be one line in the buffer, but
      * if there are multiple apply wrapping to only the first one.
      *)
     pr "%s" (String.concat "\n" rest)

type comment_style =
  | CStyle | CPlusPlusStyle | HashStyle | OCamlStyle | HaskellStyle
  | PODCommentStyle

let generate_header ?(extra_sources = []) comment_style =
  let inputs = "generator/generator" :: extra_sources in
  let c = match comment_style with
    | CStyle ->         pr "/* "; " *"
    | CPlusPlusStyle -> pr "// "; "//"
    | HashStyle ->      pr "# ";  "#"
    | OCamlStyle ->     pr "(* "; " *"
    | HaskellStyle ->   pr "{- "; "  "
    | PODCommentStyle -> pr "=begin comment\n\n "; "" in
  pr "NBD client library in userspace\n";
  pr "%s WARNING: THIS FILE IS GENERATED FROM\n" c;
  pr "%s %s\n" c (String.concat " " inputs);
  pr "%s ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.\n" c;
  pr "%s\n" c;
  pr "%s Copyright (C) 2013-2019 Red Hat Inc.\n" c;
  pr "%s\n" c;
  pr "%s This library is free software; you can redistribute it and/or\n" c;
  pr "%s modify it under the terms of the GNU Lesser General Public\n" c;
  pr "%s License as published by the Free Software Foundation; either\n" c;
  pr "%s version 2 of the License, or (at your option) any later version.\n" c;
  pr "%s\n" c;
  pr "%s This library is distributed in the hope that it will be useful,\n" c;
  pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
  pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n" c;
  pr "%s Lesser General Public License for more details.\n" c;
  pr "%s\n" c;
  pr "%s You should have received a copy of the GNU Lesser General Public\n" c;
  pr "%s License along with this library; if not, write to the Free Software\n" c;
  pr "%s Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n" c;
  (match comment_style with
   | CStyle -> pr " */\n"
   | CPlusPlusStyle
   | HashStyle -> ()
   | OCamlStyle -> pr " *)\n"
   | HaskellStyle -> pr "-}\n"
   | PODCommentStyle -> pr "\n=end comment\n"
  );
  pr "\n"

let quote = Filename.quote

let files_equal n1 n2 =
  let cmd = sprintf "cmp -s %s %s" (quote n1) (quote n2) in
  match Sys.command cmd with
  | 0 -> true
  | 1 -> false
  | i -> failwithf "%s: failed with error code %d" cmd i

let output_to filename k =
  let filename_new = filename ^ ".new" in
  let c = open_out filename_new in
  chan := OutChannel c;
  k ();
  close_out c;
  chan := NoOutput;

  (* Is the new file different from the current file? *)
  if Sys.file_exists filename && files_equal filename filename_new then
    unlink filename_new                 (* same, so skip it *)
  else (
    (* different, overwrite old one *)
    (try chmod filename 0o644 with Unix_error _ -> ());
    rename filename_new filename;
    chmod filename 0o444;
    printf "written %s\n%!" filename;
  )

let string_of_location (file, lineno) = sprintf "%s:%d" file lineno
let line_directive_of_location (file, lineno) =
  sprintf "#line %d \"%s\"" lineno file

(*----------------------------------------------------------------------*)

(* Convert POD fragments into plain text.
 *
 * For man pages and Perl documentation we can simply use the POD
 * directly, and that is the best solution.  However for other
 * programming languages we have to convert the POD fragments to
 * plain text by running it through pod2text.
 *
 * The problem is that pod2text is very slow so we must cache
 * the converted fragments to disk.
 *
 * Increment the version in the filename whenever the cache
 * type changes.
 *)

type cache_key = string (* longdesc *)
type cache_value = string list (* list of plain text lines *)

let (cache : (cache_key, cache_value) Hashtbl.t), save_cache =
  let cachefile = "generator/generator-cache.v1" in
  let cache =
    try
      let chan = open_in cachefile in
      let ret = input_value chan in
      close_in chan;
      ret
    with _ ->
      printf "Regenerating the cache, this could take a little while ...\n%!";
      Hashtbl.create 13 in
  let save_cache () =
    let chan = open_out cachefile in
    output_value chan cache;
    close_out chan
  in
  cache, save_cache

let pod2text longdesc =
  let key : cache_key = longdesc in
  try Hashtbl.find cache key
  with Not_found ->
    let filename, chan = Filename.open_temp_file "pod2text" ".tmp" in
    fprintf chan "=encoding utf8\n\n";
    fprintf chan "=head1 NAME\n\n%s\n" longdesc;
    close_out chan;
    let cmd = sprintf "pod2text -w 60 %s" (quote filename) in
    let chan = open_process_in cmd in
    let lines = ref [] in
    let rec loop i =
      let line = input_line chan in
      if i = 1 then (* discard first line of output *)
        loop (i+1)
      else (
        lines := line :: !lines;
        loop (i+1)
      ) in
    let lines : cache_value =
      try loop 1 with End_of_file -> List.rev !lines in
    unlink filename;
    (match close_process_in chan with
     | WEXITED 0 -> ()
     | WEXITED i ->
        failwithf "pod2text: process exited with non-zero status (%d)" i
     | WSIGNALED i | WSTOPPED i ->
        failwithf "pod2text: process signalled or stopped by signal %d" i
    );
    Hashtbl.add cache key lines;
    save_cache ();
    lines

(*----------------------------------------------------------------------*)

(* Implement state machine. *)

module StateMachine : sig
  val generate_lib_states_h : unit -> unit
  val generate_lib_states_c : unit -> unit
  val generate_lib_states_run_c : unit -> unit
end = struct

let all_external_events =
  [NotifyRead; NotifyWrite;
   CmdCreate;
   CmdConnectSockAddr; CmdConnectTCP;
   CmdConnectCommand; CmdConnectSA; CmdConnectSocket;
   CmdIssue]

let string_of_external_event = function
  | NotifyRead -> "NotifyRead"
  | NotifyWrite -> "NotifyWrite"
  | CmdCreate -> "CmdCreate"
  | CmdConnectSockAddr -> "CmdConnectSockAddr"
  | CmdConnectTCP -> "CmdConnectTCP"
  | CmdConnectCommand -> "CmdConnectCommand"
  | CmdConnectSA -> "CmdConnectSA"
  | CmdConnectSocket -> "CmdConnectSocket"
  | CmdIssue -> "CmdIssue"

let c_string_of_external_event = function
  | NotifyRead -> "notify_read"
  | NotifyWrite -> "notify_write"
  | CmdCreate -> "cmd_create"
  | CmdConnectSockAddr -> "cmd_connect_sockaddr"
  | CmdConnectTCP -> "cmd_connect_tcp"
  | CmdConnectCommand -> "cmd_connect_command"
  | CmdConnectSA -> "cmd_connect_sa"
  | CmdConnectSocket -> "cmd_connect_socket"
  | CmdIssue -> "cmd_issue"

(* Find a state in the state machine hierarchy by path.  The [path]
 * parameter is a list like [["READY"]] or [["MAGIC"; "START"]].
 *)
let find_state path =
  let rec find sm = function
    | [] -> raise Not_found
    | [n] ->
       (* Find a state leaf node. *)
       let rec loop = function
         | [] -> raise Not_found
         | State ({ name } as ret) :: _ when n = name -> ret
         | _ :: rest -> loop rest
       in
       loop sm
    | g :: path ->
       (* Find a state machine group. *)
       let rec loop = function
         | [] -> raise Not_found
         | Group (name, group) :: _ when g = name -> find group path
         | _ :: rest -> loop rest
       in
       loop sm
  in
  try (find state_machine path : state)
  with Not_found ->
       failwithf "find_state: ‘%s’ not found" (String.concat "." path)

let dot_rex = Str.regexp "\\."

(* Resolve a stringified path to a state.
 *
 * [prefix] parameter is the current prefix.  We resolve paths
 * relative to this.
 *
 * Stringified paths can be:
 * ["STATE"] => state relative to current level
 * ["GROUP.STATE"] => state below group at current level (to any depth)
 * [".TOP"] => state at the top level
 * ["^UP"] => state at a level above this one
 *)
let rec resolve_state prefix str =
  let len = String.length str in
  if len >= 1 && String.sub str 0 1 = "." then
    resolve_state [] (String.sub str 1 (len-1))
  else if len >= 1 && String.sub str 0 1 = "^" then (
    let parent =
      match List.rev prefix with
      | [] -> failwithf "resolve_state: %s (^) used from top level group" str
      | _ :: rest -> List.rev rest in
    resolve_state parent (String.sub str 1 (len-1))
  )
  else (
    let path = Str.split_delim dot_rex str in
    find_state (prefix @ path)
  )

(* Flatten the state machine hierarchy.  This sets the [parsed.prefix],
 * [parsed.state_enum], [parsed.events] fields in the state.
 *)
let states : state list =
  let rec flatten prefix = function
    | [] -> []
    | State st :: rest ->
       st.parsed <-
         { st.parsed with
           prefix = prefix;
           display_name = (
             match prefix with
             | [] -> st.name
             | prefix -> String.concat "." prefix ^ "." ^ st.name
           );
           state_enum = (
             let path = String.concat "" (List.map ((^) "_") prefix) in
             "STATE" ^ path ^ "_" ^ st.name
           );
           events = (
             List.map (
               fun (ev, str) ->
                 (* In external_events,
                  * special string [""] means current state.
                  *)
                 if str = "" then ev, st
                 else ev, resolve_state prefix str
             ) st.external_events
           )
         };
       st :: flatten prefix rest
    | Group (name, group) :: rest ->
       let states = flatten (prefix @ [name]) group in
       states @ flatten prefix rest
  in
  flatten [] state_machine

(* Read and parse the state machine C code. *)
let state_machine_prologue =
  let parse_states_file filename =
    let chan = open_in filename in
    let lines = ref [] in
    let lineno = ref 1 in
    (try while true do
           let line = input_line chan in
           let loc : location = filename, !lineno in
           incr lineno;
           lines := (loc, line) :: !lines
         done
     with End_of_file -> ());
    close_in chan;
    (* Note this list is initially in reverse order. *)
    let lines = !lines in

    (* The last line in the file must have a particular form, check
     * and remove.
     *)
    if List.length lines = 0 ||
         snd (List.hd lines) <> "} /* END STATE MACHINE */" then
      failwithf "%s: unexpected file ending" filename;
    let lines = List.tl lines in
    let lines = List.rev lines in

    (* Find the start of the state machine and split the list into
     * the prologue and the list of state code fragments.
     *)
    let rec loop acc = function
      | [] -> failwithf "%s: could not find state machine" filename
      | (_, "STATE_MACHINE {") :: lines -> ((filename, 1), acc), lines
      | (_, line) :: lines -> loop (acc ^ line ^ "\n") lines
    in
    let prologue, lines = loop "" lines in

    let statecodes = ref [] in
    let curr_state = ref None in
    let rex = Str.regexp "^ \\([A-Z0-9][A-Z0-9_\\.]*\\):$" in
    List.iter (
      fun (loc, line) ->
        if Str.string_match rex line 0 then ( (* new case *)
          (match !curr_state with
           | None -> ()
           | Some state -> statecodes := state :: !statecodes);
          curr_state := Some (Str.matched_group 1 line, "", loc);
        )
        else (
          (match !curr_state with
           | None ->
              failwithf "%s: missing label" (string_of_location loc)
           | Some (name, code, loc) ->
              curr_state := Some (name, code ^ "\n" ^ line, loc)
          )
        );
      ) lines;
    (match !curr_state with
     | None -> ()
     | Some state -> statecodes := state :: !statecodes);
    let statecodes = List.rev !statecodes in

    prologue, statecodes
  in

  (* Read all the input files, called [generator/states*.c] *)
  let files = List.sort compare (Array.to_list (Sys.readdir "generator")) in
  let files = List.filter (
    fun filename ->
      let len = String.length filename in
      len >= 8 && String.sub filename 0 6 = "states" &&
        String.sub filename (len-2) 2 = ".c"
  ) files in
  let files = List.map ((^) "generator/") files in
  let files = List.map parse_states_file files in

  (* Mash together the prologues and the state codes. *)
  let prologue =
    String.concat "" (
      List.map (
        fun ((loc, prologue), _) ->
          line_directive_of_location loc ^ "\n" ^ prologue ^ "\n"
       ) files
     ) in
  let statecodes = List.concat (List.map snd files) in

  (* Resolve the state names in the code to paths. *)
  let statecodes =
    List.map (
      fun (name, code, loc) ->
        let path = Str.split_delim dot_rex name in
        let state = find_state path in
        state, code, loc
    ) statecodes in

  (* Parse the state code fragments to get internal state
   * transitions, marked by "%STATE".
   *)
  let rex = Str.regexp "%\\([\\^\\.]*[A-Z0-9][A-Z0-9_\\.]*\\)" in
  List.iter (
    fun (state, code, loc) ->
      let code = Str.full_split rex code in
      let code =
        List.map (
          function
          | Str.Delim str ->
             Str.Delim (String.sub str 1 (String.length str - 1))
          | (Str.Text _) as c -> c
      ) code in

      (* Create the list of internal transitions. *)
      state.parsed <-
        { state.parsed with
          internal_transitions = (
            filter_map (
              function
              | Str.Delim str ->
                 let next_state = resolve_state state.parsed.prefix str in
                 Some next_state
              | Str.Text _ -> None
            ) code
        )
        };

      (* Create the final C code fragment. *)
      state.parsed <-
        { state.parsed with
          loc = loc;
          code =
            String.concat "" (
              List.map (
                function
                | Str.Delim str ->
                   let next_state = resolve_state state.parsed.prefix str in
                   next_state.parsed.state_enum
                | Str.Text text -> text
                ) code
            )
        }
  ) statecodes;

  prologue

(* Verify state transitions are permitted. *)
let () =
  let verify_state_transition from_state to_state =
    let from_prefix = from_state.parsed.prefix
    and to_prefix = to_state.parsed.prefix in
    (* Going from state to state within the same group is always allowed. *)
    if from_prefix = to_prefix then
      ()
    (* Going upwards to any state is always allowed. *)
    else if List.length from_prefix > List.length to_prefix then
      ()
    (* When going downwards (even into an adjacent tree) you must
     * always enter a group at the START state.
     *)
    else if to_state.name <> "START" then (
      failwithf "non-permitted state transition: %s.%s -> %s.%s"
                (String.concat "." from_prefix) from_state.name
                (String.concat "." to_prefix) to_state.name
    )
  in
  List.iter (
    fun ({ parsed = { internal_transitions; events } } as state) ->
      List.iter (verify_state_transition state) internal_transitions;
      List.iter (fun (_, next_state) -> verify_state_transition state next_state) events
  ) states

(* Write the state machine code. *)
let generate_lib_states_h () =
  generate_header ~extra_sources:["generator/states*.c"] CStyle;
  pr "enum state {\n";
  List.iter (
    fun ({ comment; parsed = { display_name; state_enum } }) ->
      pr "  /* %s: %s */\n" display_name comment;
      pr "  %s,\n" state_enum;
      pr "\n";
  ) states;
  pr "};\n";
  pr "\n";
  pr "/* These correspond to the external events in generator/generator. */\n";
  pr "enum external_event {\n";
  List.iter (
    fun e -> pr "  %s,\n" (c_string_of_external_event e)
  ) all_external_events;
  pr "};\n";
  pr "\n";
  pr "/* State groups. */\n";
  pr "enum state_group {\n";
  pr "  GROUP_TOP,\n";
  let rec loop prefix = function
    | [] -> ()
    | State _ :: rest ->
       loop prefix rest
    | Group (name, group) :: rest ->
       let enum =
         "GROUP" ^ String.concat "" (List.map ((^) "_") prefix) ^ "_" ^ name in
       pr "  %s,\n" enum;
       loop (prefix @ [name]) group;
       loop prefix rest
  in
  loop [] state_machine;
  pr "};\n";
  pr "\n";
  pr "/* State transitions defined in states.c. */\n";
  List.iter (
    fun { parsed = { state_enum } } ->
      pr "extern int nbd_internal_enter_%s (struct nbd_handle *h, bool *blocked);\n" state_enum;
  ) states

let generate_lib_states_c () =
  generate_header ~extra_sources:["generator/states*.c"] CStyle;

  pr "%s\n" state_machine_prologue;
  pr "\n";
  pr "#define SET_NEXT_STATE(s) (*blocked = false, *next_state = (s))\n";
  pr "#define SET_NEXT_STATE_AND_BLOCK(s) (*next_state = (s))\n";

  (* The state machine C code fragments. *)
  List.iter (
    fun { comment; parsed = { display_name; state_enum; loc; code } } ->
      pr "\n";
      pr "/* %s: %s */\n" display_name comment;
      pr "static int\n";
      pr "enter_%s (struct nbd_handle *h,\n" state_enum;
      pr "             enum state *next_state,\n";
      pr "             bool *blocked)\n";
      pr "{\n";
      if code <> "" then (
        pr "%s\n" (line_directive_of_location loc);
        pr "%s\n" code
      )
      else
        pr "  return 0;\n";
      pr "}\n";
      pr "\n";
      pr "int\n";
      pr "nbd_internal_enter_%s (struct nbd_handle *h, bool *blocked)\n"
        state_enum;
      pr "{\n";
      pr "  int r;\n";
      pr "  enum state next_state = %s;\n" state_enum;
      pr "\n";
      pr "  r = enter_%s (h, &next_state, blocked);\n" state_enum;
      pr "  if (get_next_state (h) != next_state) {\n";
      pr "    debug (h, \"transition: %%s -> %%s\",\n";
      pr "           \"%s\",\n" display_name;
      pr "           nbd_internal_state_short_string (next_state));\n";
      pr "    set_next_state (h, next_state);\n";
      pr "  }\n";
      pr "  return r;\n";
      pr "}\n";
  ) states

let generate_lib_states_run_c () =
  generate_header ~extra_sources:["generator/states*.c"] CStyle;

  pr "#include <config.h>\n";
  pr "\n";
  pr "#include <stdio.h>\n";
  pr "#include <stdlib.h>\n";
  pr "#include <errno.h>\n";
  pr "#include <assert.h>\n";
  pr "\n";
  pr "#include \"libnbd.h\"\n";
  pr "#include \"internal.h\"\n";
  pr "\n";
  pr "/* Run the state machine based on an external event until it would block. */\n";
  pr "int\n";
  pr "nbd_internal_run (struct nbd_handle *h, enum external_event ev)\n";
  pr "{\n";
  pr "  int r;\n";
  pr "  bool blocked;\n";
  pr "\n";
  pr "  /* Validate and handle the external event. */\n";
  pr "  switch (get_next_state (h))\n";
  pr "  {\n";
  List.iter (
    fun ({ parsed = { display_name; state_enum; events } } as state) ->
      pr "  case %s:\n" state_enum;
      if events <> [] then (
        pr "    switch (ev)\n";
        pr "    {\n";
        List.iter (
          fun (e, next_state) ->
            pr "    case %s:\n" (c_string_of_external_event e);
            if state != next_state then (
              pr "      set_next_state (h, %s);\n" next_state.parsed.state_enum;
              pr "      debug (h, \"event %%s: %%s -> %%s\",\n";
              pr "             \"%s\", \"%s\", \"%s\");\n"
                 (string_of_external_event e)
                 display_name next_state.parsed.display_name;
            );
            pr "      goto ok;\n";
        ) events;
        pr "    default: ; /* nothing, silence GCC warning */\n";
        pr "    }\n";
      );
      pr "    break;\n";
  ) states;
  pr "  }\n";
  pr "\n";
  pr "  set_error (EINVAL, \"external event %%d is invalid in state %%s\",\n";
  pr "             ev, nbd_internal_state_short_string (get_next_state (h)));\n";
  pr "  return -1;\n";
  pr "\n";
  pr " ok:\n";
  pr "  do {\n";
  pr "    blocked = true;\n";
  pr "\n";
  pr "    /* Run a single step. */\n";
  pr "    switch (get_next_state (h))\n";
  pr "    {\n";
  List.iter (
    fun { parsed = { state_enum } } ->
      pr "    case %s:\n" state_enum;
      pr "      r = nbd_internal_enter_%s (h, &blocked);\n" state_enum;
      pr "      break;\n"
  ) states;
  pr "    default:\n";
  pr "      abort (); /* Should never happen, but keeps GCC happy. */\n";
  pr "    }\n";
  pr "\n";
  pr "    if (r == -1) {\n";
  pr "      assert (nbd_get_error () != NULL);\n";
  pr "      return -1;\n";
  pr "    }\n";
  pr "  } while (!blocked);\n";
  pr "  return 0;\n";
  pr "}\n";
  pr "\n";

  pr "/* Returns whether in the given state read or write would be valid.\n";
  pr " * NB: is_locked = false, may_set_error = false.\n";
  pr " */\n";
  pr "int\n";
  pr "nbd_internal_aio_get_direction (enum state state)\n";
  pr "{\n";
  pr "  int r = 0;\n";
  pr "\n";
  pr "  switch (state)\n";
  pr "  {\n";
  List.iter (
    fun ({ parsed = { state_enum; events } }) ->
      pr "  case %s:\n" state_enum;
      List.iter (
        fun (e, _) ->
          match e with
          | NotifyRead ->  pr "    r |= LIBNBD_AIO_DIRECTION_READ;\n"
          | NotifyWrite -> pr "    r |= LIBNBD_AIO_DIRECTION_WRITE;\n"
          | CmdCreate
          | CmdConnectSockAddr
          | CmdConnectTCP
          | CmdConnectCommand | CmdConnectSA | CmdConnectSocket
          | CmdIssue -> ()
      ) events;
      pr "    break;\n";
  ) states;
  pr "  }\n";
  pr "\n";
  pr "  return r;\n";
  pr "}\n";
  pr "\n";

  pr "/* Other functions associated with the state machine. */\n";
  pr "const char *\n";
  pr "nbd_internal_state_short_string (enum state state)\n";
  pr "{\n";
  pr "  switch (state)\n";
  pr "  {\n";
  List.iter (
    fun ({ parsed = { display_name; state_enum } }) ->
      pr "  case %s:\n" state_enum;
      pr "    return \"%s\";\n" display_name
  ) states;
  pr "  }\n";
  pr "\n";
  pr "  /* This function is only used for debug messages, and\n";
  pr "   * this should never happen.\n";
  pr "   */\n";
  pr "  return \"UNKNOWN!\";\n";
  pr "}\n";
  pr "\n";

  pr "const char *\n";
  pr "nbd_unlocked_connection_state (struct nbd_handle *h)\n";
  pr "{\n";
  pr "  switch (get_next_state (h))\n";
  pr "  {\n";
  List.iter (
    fun ({ comment; parsed = { display_name; state_enum } }) ->
      pr "  case %s:\n" state_enum;
      pr "    return \"%s\" \": \"\n" display_name;
      pr "           \"%s\";\n" comment;
      pr "\n";
  ) states;
  pr "  }\n";
  pr "\n";
  pr "  return NULL;\n";
  pr "}\n";
  pr "\n";

  pr "/* Map a state to its group name. */\n";
  pr "enum state_group\n";
  pr "nbd_internal_state_group (enum state state)\n";
  pr "{\n";
  pr "  switch (state) {\n";
  List.iter (
    fun ({ parsed = { prefix; state_enum } }) ->
      pr "  case %s:\n" state_enum;
      if prefix = [] then
        pr "    return GROUP_TOP;\n"
      else
        pr "    return GROUP%s;\n"
           (String.concat "" (List.map ((^) "_") prefix))
  ) states;
  pr "  default:\n";
  pr "    abort (); /* Should never happen, but keeps GCC happy. */\n";
  pr "  }\n";
  pr "}\n";
  pr "\n";

  pr "/* Map a state group to its parent group. */\n";
  pr "enum state_group\n";
  pr "nbd_internal_state_group_parent (enum state_group group)\n";
  pr "{\n";
  pr "  switch (group) {\n";
  pr "  case GROUP_TOP:\n";
  pr "    return GROUP_TOP;\n";
  let rec loop prefix = function
    | [] -> ()
    | State _ :: rest ->
       loop prefix rest
    | Group (name, group) :: rest ->
       let enum =
         "GROUP" ^ String.concat "" (List.map ((^) "_") prefix) ^ "_" ^ name in
       pr "  case %s:\n" enum;
       if prefix = [] then
         pr "    return GROUP_TOP;\n"
       else (
         let parent = "GROUP" ^ String.concat "" (List.map ((^) "_") prefix) in
         pr "    return %s;\n" parent
       );
       loop (prefix @ [name]) group;
       loop prefix rest
  in
  loop [] state_machine;
  pr "  default:\n";
  pr "    abort (); /* Should never happen, but keeps GCC happy. */\n";
  pr "  }\n";
  pr "};\n"
end

(*----------------------------------------------------------------------*)

(* Generate C API. *)

module C : sig
  val generate_lib_libnbd_syms : unit -> unit
  val generate_include_libnbd_h : unit -> unit
  val generate_lib_unlocked_h : unit -> unit
  val generate_lib_api_c : unit -> unit
  val generate_docs_Makefile_inc : unit -> unit
  val generate_docs_api_links_pod : unit -> unit
  val generate_docs_api_flag_links_pod : unit -> unit
  val generate_docs_nbd_pod : string -> call -> unit -> unit
  val print_arg_list : ?wrap:bool -> ?maxcol:int ->
                       ?handle:bool -> ?types:bool ->
                       arg list -> optarg list -> unit
  val print_cbarg_list : ?wrap:bool -> ?maxcol:int -> ?types:bool ->
                         cbarg list -> unit
  val errcode_of_ret : ret -> string option
  val type_of_ret : ret -> string
end = struct

(* Check the API definition. *)
let () =
  (* Check functions using may_set_error. *)
  List.iter (
    function
    (* !may_set_error is incompatible with permitted_states != []
     * because an incorrect state will result in set_error being
     * called by the generated wrapper.
     *)
    | name, { permitted_states = (_::_); may_set_error = false } ->
       failwithf "%s: if may_set_error is false, permitted_states must be empty (any permitted state)"
                 name

    (* may_set_error = true is incompatible with RUInt because
     * these calls cannot return an error indication.
     *)
    | name, { ret = RUInt; may_set_error = true } ->
       failwithf "%s: if ret is RUInt, may_set_error must be false" name

    (* !may_set_error is incompatible with certain parameters because
     * we have to do a NULL-check on those which may return an error.
     *)
    | name, { args; may_set_error = false }
         when List.exists
                (function
                 | Closure _ | Enum _ | Flags _ | String _ -> true
                 | _ -> false) args ->
       failwithf "%s: if args contains Closure/Enum/Flags/String parameter, may_set_error must be false" name

    (* !may_set_error is incompatible with certain optargs too.
     *)
    | name, { optargs; may_set_error = false }
         when List.exists
                (function
                 | OFlags _ -> true
                 | _ -> false) optargs ->
       failwithf "%s: if optargs contains an OFlags parameter, may_set_error must be false" name

    | _ -> ()
  ) handle_calls;

  (* first_version must be (0, 0) in handle_calls (we will modify it). *)
  List.iter (
    function
    | (_, { first_version = (0, 0) }) -> ()
    | (name, _) ->
        failwithf "%s: first_version field must not be set in handle_calls table" name
  ) handle_calls;

  (* Check every entry in first_version corresponds 1-1 with handle_calls. *)
  let () =
    let fv_names = List.sort compare (List.map fst first_version) in
    let hc_names = List.sort compare (List.map fst handle_calls) in
    if fv_names <> hc_names then (
      eprintf "first_version names:\n";
      List.iter (eprintf "\t%s\n") fv_names;
      eprintf "handle_calls names:\n";
      List.iter (eprintf "\t%s\n") hc_names;
      failwithf "first_version and handle_calls are not a 1-1 mapping.  You probably forgot to add a new API to the first_version table."
    ) in

  (* Check and update first_version field in handle_calls. *)
  List.iter (
    function
    | (name, entry) ->
       let major, minor = List.assoc name first_version in
       (* First stable version must be 1.x where x is even. *)
       if major <> 1 then
         failwithf "%s: first_version must be 1.x" name;
       if minor mod 2 <> 0 then
         failwithf "%s: first_version must refer to a stable release" name;
       entry.first_version <- (major, minor)
  ) handle_calls;

  (* Because of the way we use completion free callbacks to
   * free persistent buffers in non-C languages, any function
   * with a BytesPersistIn/Out parameter must have only one.
   * And it must have an OClosure completion optarg.
   *)
  List.iter (
    fun (name, { args; optargs }) ->
      let is_persistent_buffer_arg = function
        | BytesPersistIn _ | BytesPersistOut _ -> true
        | _ -> false
      and is_oclosure_completion = function
        | OClosure { cbname = "completion" } -> true
        | _ -> false
      in
      if List.exists is_persistent_buffer_arg args then (
        let bpargs = List.filter is_persistent_buffer_arg args in
        if List.length bpargs >= 2 then
          failwithf "%s: multiple BytesPersistIn/Out params not supported"
            name;
        if not (List.exists is_oclosure_completion optargs) then
          failwithf "%s: functions with BytesPersistIn/Out arg must have completion callback"
            name
      )
  ) handle_calls

let generate_lib_libnbd_syms () =
  generate_header HashStyle;

  (* Sort and group the calls by first_version, and emit them in order. *)
  let cmp (_, {first_version = a}) (_, {first_version = b}) = compare a b in
  let calls = List.sort cmp handle_calls in
  let extract ((_, {first_version}) as call) = first_version, call in
  let calls = List.map extract calls in
  let calls = group_by calls in

  let prev = ref None in
  List.iter (
    fun ((major, minor), calls) ->
      pr "# Public symbols in libnbd %d.%d:\n" major minor;
      pr "LIBNBD_%d.%d {\n" major minor;
      pr "  global:\n";
      if (major, minor) = (1, 0) then (
        pr "    nbd_create;\n";
        pr "    nbd_close;\n";
        pr "    nbd_get_errno;\n";
        pr "    nbd_get_error;\n"
      );
      List.iter (fun (name, _) -> pr "    nbd_%s;\n" name) calls;
      (match !prev with
       | None ->
          pr "  # Everything else is hidden.\n";
          pr "  local: *;\n";
       | Some _ -> ()
      );
      pr "}";
      (match !prev with
       | None -> ()
       | Some (old_major, old_minor) ->
          pr " LIBNBD_%d.%d" old_major old_minor
      );
      pr ";\n";
      pr "\n";
      prev := Some (major, minor)
  ) calls

let errcode_of_ret =
  function
  | RBool | RErr | RFd | RInt | RInt64 | RCookie -> Some "-1"
  | RStaticString | RString -> Some "NULL"
  | RUInt -> None (* errors not possible *)

let type_of_ret =
  function
  | RBool | RErr | RFd | RInt -> "int"
  | RInt64 | RCookie -> "int64_t"
  | RStaticString -> "const char *"
  | RString -> "char *"
  | RUInt -> "unsigned"

let rec name_of_arg = function
| Bool n -> [n]
| BytesIn (n, len) -> [n; len]
| BytesOut (n, len) -> [n; len]
| BytesPersistIn (n, len) -> [n; len]
| BytesPersistOut (n, len) -> [n; len]
| Closure { cbname } ->
   [ sprintf "%s_callback" cbname; sprintf "%s_user_data" cbname ]
| Enum (n, _) -> [n]
| Fd n -> [n]
| Flags (n, _) -> [n]
| Int n -> [n]
| Int64 n -> [n]
| Path n -> [n]
| SockAddrAndLen (n, len) -> [n; len]
| String n -> [n]
| StringList n -> [n]
| UInt n -> [n]
| UInt32 n -> [n]
| UInt64 n -> [n]

let rec print_arg_list ?(wrap = false) ?maxcol ?handle ?types args optargs =
  pr "(";
  if wrap then
    pr_wrap ?maxcol ','
      (fun () -> print_arg_list_no_parens ?handle ?types args optargs)
  else
    print_arg_list_no_parens ?handle ?types args optargs;
  pr ")"

and print_arg_list_no_parens ?(handle = false) ?(types = true) args optargs =
  let comma = ref false in
  if handle then (
    comma := true;
    if types then pr "struct nbd_handle *";
    pr "h"
  );
  List.iter (
    fun arg ->
      if !comma then pr ", ";
      comma := true;
      match arg with
      | Bool n ->
         if types then pr "bool ";
         pr "%s" n
      | BytesIn (n, len)
      | BytesPersistIn (n, len) ->
         if types then pr "const void *";
         pr "%s, " n;
         if types then pr "size_t ";
         pr "%s" len
      | BytesOut (n, len)
      | BytesPersistOut (n, len) ->
         if types then pr "void *";
         pr "%s, " n;
         if types then pr "size_t ";
         pr "%s" len
      | Closure { cbname; cbargs } ->
         if types then pr "nbd_%s_callback " cbname;
         pr "%s_callback" cbname
      | Enum (n, _) ->
         if types then pr "int ";
         pr "%s" n
      | Flags (n, _) ->
         if types then pr "uint32_t ";
         pr "%s" n
      | Fd n | Int n ->
         if types then pr "int ";
         pr "%s" n
      | Int64 n ->
         if types then pr "int64_t ";
         pr "%s" n
      | Path n
      | String n ->
         if types then pr "const char *";
         pr "%s" n
      | StringList n ->
         if types then pr "char **";
         pr "%s" n
      | SockAddrAndLen (n, len) ->
         if types then pr "const struct sockaddr *";
         pr "%s, " n;
         if types then pr "socklen_t ";
         pr "%s" len
      | UInt n ->
         if types then pr "unsigned ";
         pr "%s" n
      | UInt32 n ->
         if types then pr "uint32_t ";
         pr "%s" n
      | UInt64 n ->
         if types then pr "uint64_t ";
         pr "%s" n
  ) args;
  List.iter (
    fun optarg ->
      if !comma then pr ", ";
      comma := true;
      match optarg with
      | OClosure { cbname; cbargs } ->
         if types then pr "nbd_%s_callback " cbname;
         pr "%s_callback" cbname
      | OFlags (n, _) ->
         if types then pr "uint32_t ";
         pr "%s" n
  ) optargs

let print_call ?wrap ?maxcol name args optargs ret =
  pr "%s nbd_%s " (type_of_ret ret) name;
  print_arg_list ~handle:true ?wrap ?maxcol args optargs

let print_extern ?wrap name args optargs ret =
  pr "extern ";
  print_call ?wrap name args optargs ret;
  pr ";\n"

let rec print_cbarg_list ?(wrap = false) ?maxcol ?types cbargs =
  pr "(";
  if wrap then
    pr_wrap ?maxcol ','
      (fun () -> print_cbarg_list_no_parens ?types cbargs)
  else
    print_cbarg_list_no_parens ?types cbargs;
  pr ")"

and print_cbarg_list_no_parens ?(types = true) cbargs =
  if types then pr "void *";
  pr "user_data";

  List.iter (
    fun cbarg ->
      pr ", ";
      match cbarg with
      | CBArrayAndLen (UInt32 n, len) ->
         if types then pr "uint32_t *";
         pr "%s, " n;
         if types then pr "size_t ";
         pr "%s" len
      | CBArrayAndLen _ -> assert false
      | CBBytesIn (n, len) ->
         if types then pr "const void *";
         pr "%s, " n;
         if types then pr "size_t ";
         pr "%s" len
      | CBInt n ->
         if types then pr "int ";
         pr "%s" n
      | CBInt64 n ->
         if types then pr "int64_t ";
         pr "%s" n
      | CBMutable (Int n) ->
         if types then pr "int *";
         pr "%s" n
      | CBMutable arg -> assert false
      | CBString n ->
         if types then pr "const char *";
         pr "%s" n
      | CBUInt n ->
         if types then pr "unsigned ";
         pr "%s" n
      | CBUInt64 n ->
         if types then pr "uint64_t ";
         pr "%s" n
  ) cbargs

(* Callback structs/typedefs in <libnbd.h> *)
let print_closure_structs () =
  pr "/* These are used for callback parameters.  They are passed\n";
  pr " * by value not by reference.  See CALLBACKS in libnbd(3).\n";
  pr " */\n";
  List.iter (
    fun { cbname; cbargs } ->
      pr "typedef struct {\n";
      pr "  int (*callback) ";
      print_cbarg_list ~wrap:true cbargs;
      pr ";\n";
      pr "  void *user_data;\n";
      pr "  void (*free) (void *user_data);\n";
      pr "} nbd_%s_callback;\n" cbname;
      pr "\n";
  ) all_closures;

  pr "/* Note NBD_NULL_* are only generated for callbacks which are\n";
  pr " * optional.  (See OClosure in the generator).\n";
  pr " */\n";
  let oclosures =
    let optargs = List.map (fun (_, { optargs }) -> optargs) handle_calls in
    let optargs = List.flatten optargs in
    let optargs =
      filter_map (function OClosure cb -> Some cb | _ -> None) optargs in
    sort_uniq optargs in
  List.iter (
    fun { cbname } ->
      pr "#define NBD_NULL_%s ((nbd_%s_callback) { .callback = NULL })\n"
        (String.uppercase_ascii cbname) cbname;
  ) oclosures;
  pr "\n"

let print_extern_and_define ?wrap name args optargs ret =
  let name_upper = String.uppercase_ascii name in
  print_extern ?wrap name args optargs ret;
  pr "#define LIBNBD_HAVE_NBD_%s 1\n" name_upper;
  pr "\n"

let print_ns_ctxt ns ns_upper ctxt consts =
  let ctxt_upper = String.uppercase_ascii ctxt in
  pr "#define LIBNBD_CONTEXT_%s_%s \"%s:%s\"\n"
    ns_upper ctxt_upper ns ctxt;
  pr "\n";
  pr "/* \"%s:%s\" context related constants */\n" ns ctxt;
  List.iter (fun (n, i) -> pr "#define LIBNBD_%-30s %d\n" n i) consts

let print_ns ns ctxts =
  let ns_upper = String.uppercase_ascii ns in
  pr "/* \"%s\" namespace */\n" ns;
  pr "#define LIBNBD_NAMESPACE_%s \"%s:\"\n" ns_upper ns;
  pr "\n";
  pr "/* \"%s\" namespace contexts */\n" ns;
  List.iter (
    fun (ctxt, consts) -> print_ns_ctxt ns ns_upper ctxt consts
  ) ctxts;
  pr "\n"

let generate_include_libnbd_h () =
  generate_header CStyle;

  pr "#ifndef LIBNBD_H\n";
  pr "#define LIBNBD_H\n";
  pr "\n";
  pr "/* This is the public interface to libnbd, a client library for\n";
  pr " * accessing Network Block Device (NBD) servers.\n";
  pr " *\n";
  pr " * Please read the libnbd(3) manual page to\n";
  pr " * find out how to use this library.\n";
  pr " */\n";
  pr "\n";
  pr "#include <stdbool.h>\n";
  pr "#include <stdint.h>\n";
  pr "#include <sys/socket.h>\n";
  pr "\n";
  pr "#ifdef __cplusplus\n";
  pr "extern \"C\" {\n";
  pr "#endif\n";
  pr "\n";
  pr "struct nbd_handle;\n";
  pr "\n";
  List.iter (
    fun { enum_prefix; enums } ->
      List.iter (
        fun (enum, i) ->
          let enum = sprintf "LIBNBD_%s_%s" enum_prefix enum in
          pr "#define %-40s %d\n" enum i
      ) enums;
      pr "\n"
  ) all_enums;
  List.iter (
    fun { flag_prefix; flags } ->
      List.iter (
        fun (flag, i) ->
          let flag = sprintf "LIBNBD_%s_%s" flag_prefix flag in
          pr "#define %-40s %d\n" flag i
      ) flags;
      pr "\n"
  ) all_flags;
  List.iter (
    fun (n, i) ->
      let n = sprintf "LIBNBD_%s" n in
      pr "#define %-40s %d\n" n i
  ) constants;
  pr "\n";
  pr "extern struct nbd_handle *nbd_create (void);\n";
  pr "#define LIBNBD_HAVE_NBD_CREATE 1\n";
  pr "\n";
  pr "extern void nbd_close (struct nbd_handle *h);\n";
  pr "#define LIBNBD_HAVE_NBD_CLOSE 1\n";
  pr "\n";
  pr "extern const char *nbd_get_error (void);\n";
  pr "#define LIBNBD_HAVE_NBD_GET_ERROR 1\n";
  pr "\n";
  pr "extern int nbd_get_errno (void);\n";
  pr "#define LIBNBD_HAVE_NBD_GET_ERRNO 1\n";
  pr "\n";
  print_closure_structs ();
  List.iter (
    fun (name, { args; optargs; ret }) ->
      print_extern_and_define ~wrap:true name args optargs ret
  ) handle_calls;
  List.iter (
    fun (ns, ctxts) -> print_ns ns ctxts
  ) metadata_namespaces;
  pr "#ifdef __cplusplus\n";
  pr "}\n";
  pr "#endif\n";
  pr "\n";
  pr "#endif /* LIBNBD_H */\n"

let generate_lib_unlocked_h () =
  generate_header CStyle;

  pr "#ifndef LIBNBD_UNLOCKED_H\n";
  pr "#define LIBNBD_UNLOCKED_H\n";
  pr "\n";
  List.iter (
    fun (name, { args; optargs; ret }) ->
      print_extern ~wrap:true ("unlocked_" ^ name) args optargs ret
  ) handle_calls;
  pr "\n";
  pr "#endif /* LIBNBD_UNLOCKED_H */\n"

let permitted_state_text permitted_states =
  assert (permitted_states <> []);
  String.concat
    ", or "
    (List.map (
         function
         | Created -> "newly created"
         | Connecting -> "connecting"
         | Connected -> "connected and finished handshaking with the server"
         | Closed -> "shut down"
         | Dead -> "dead"
       ) permitted_states
    )

(* Generate wrappers around each API call which are a place to
 * grab the thread mutex (lock) and do logging.
 *)
let generate_lib_api_c () =
  (* Print the wrapper added around all API calls. *)
  let rec print_wrapper (name, {args; optargs; ret; permitted_states;
                                is_locked; may_set_error}) =
    if permitted_states <> [] then (
      pr "static inline bool\n";
      pr "%s_in_permitted_state (struct nbd_handle *h)\n" name;
      pr "{\n";
      pr "  const enum state state = get_public_state (h);\n";
      pr "\n";
      let tests =
        List.map (
          function
          | Created -> "nbd_internal_is_state_created (state)"
          | Connecting -> "nbd_internal_is_state_connecting (state)"
          | Connected -> "nbd_internal_is_state_ready (state) || nbd_internal_is_state_processing (state)"
          | Closed -> "nbd_internal_is_state_closed (state)"
          | Dead -> "nbd_internal_is_state_dead (state)"
        ) permitted_states in
      pr "  if (!(%s)) {\n" (String.concat " ||\n        " tests);
      pr "    set_error (nbd_internal_is_state_created (state) ? ENOTCONN : EINVAL,\n";
      pr "               \"invalid state: %%s: the handle must be %%s\",\n";
      pr "               nbd_internal_state_short_string (state),\n";
      pr "               \"%s\");\n" (permitted_state_text permitted_states);
      pr "    return false;\n";
      pr "  }\n";
      pr "  return true;\n";
      pr "}\n";
      pr "\n"
    );

    let need_out_label = ref false in

    let ret_c_type = type_of_ret ret and errcode = errcode_of_ret ret in
    pr "%s\n" ret_c_type;
    pr "nbd_%s " name;
    print_arg_list ~wrap:true ~handle:true args optargs;
    pr "\n";
    pr "{\n";
    pr "  %s ret;\n" ret_c_type;
    pr "\n";
    if may_set_error then (
      pr "  nbd_internal_set_error_context (\"nbd_%s\");\n" name;
      pr "\n";
    )
    else
      pr "  /* This function must not call set_error. */\n";

    (* Lock the handle. *)
    if is_locked then
      pr "  pthread_mutex_lock (&h->lock);\n";
    if may_set_error then (
      print_trace_enter args optargs;
      pr "\n"
    );

    (* Check current state is permitted for this call. *)
    if permitted_states <> [] then (
      let value = match errcode with
        | Some value -> value
        | None -> assert false in
      pr "  if (unlikely (!%s_in_permitted_state (h))) {\n" name;
      pr "    ret = %s;\n" value;
      pr "    goto out;\n";
      pr "  }\n";
      need_out_label := true
    );

    (* Check parameters are valid. *)
    let print_flags_check n { flag_prefix; flags } =
      let value = match errcode with
        | Some value -> value
        | None -> assert false in
      let mask = List.fold_left (lor) 0 (List.map snd flags) in
      pr "  if (unlikely ((%s & ~%d) != 0)) {\n" n mask;
      pr "    set_error (EINVAL, \"%%s: invalid value for flag: %%d\",\n";
      pr "               \"%s\", %s);\n" n n;
      pr "    ret = %s;\n" value;
      pr "    goto out;\n";
      pr "  }\n";
      need_out_label := true
    in
    List.iter (
      function
      | Closure { cbname } ->
         let value = match errcode with
           | Some value -> value
           | None -> assert false in
         pr "  if (CALLBACK_IS_NULL (%s_callback)) {\n" cbname;
         pr "    set_error (EFAULT, \"%%s cannot be NULL\", \"%s\");\n" cbname;
         pr "    ret = %s;\n" value;
         pr "    goto out;\n";
         pr "  }\n";
         need_out_label := true
      | Enum (n, { enum_prefix; enums }) ->
         let value = match errcode with
           | Some value -> value
           | None -> assert false in
         pr "  switch (%s) {\n" n;
         List.iter (
           fun (enum, _) ->
             pr "  case LIBNBD_%s_%s:\n" enum_prefix enum
         ) enums;
         pr "    break;\n";
         pr "  default:\n";
         pr "    set_error (EINVAL, \"%%s: invalid value for parameter: %%d\",\n";
         pr "               \"%s\", %s);\n" n n;
         pr "    ret = %s;\n" value;
         pr "    goto out;\n";
         pr "  }\n";
         need_out_label := true
      | Flags (n, flags) ->
         print_flags_check n flags
      | String n ->
         let value = match errcode with
           | Some value -> value
           | None -> assert false in
         pr "  if (%s == NULL) {\n" n;
         pr "    set_error (EFAULT, \"%%s cannot be NULL\", \"%s\");\n" n;
         pr "    ret = %s;\n" value;
         pr "    goto out;\n";
         pr "  }\n";
         need_out_label := true
      | _ -> ()
    ) args;
    List.iter (
      function
      | OClosure _ -> ()
      | OFlags (n, flags) ->
         print_flags_check n flags
    ) optargs;

    (* Make the call. *)
    pr "  ret = nbd_unlocked_%s " name;
    print_arg_list ~wrap:true ~types:false ~handle:true args optargs;
    pr ";\n";
    if may_set_error then (
      pr "\n";
      print_trace_leave ret;
      pr "\n"
    );
    if !need_out_label then
      pr " out:\n";
    if is_locked then (
      pr "  if (h->public_state != get_next_state (h))\n";
      pr "    h->public_state = get_next_state (h);\n";
      pr "  pthread_mutex_unlock (&h->lock);\n"
    );
    pr "  return ret;\n";
    pr "}\n";
    pr "\n"

  (* Print the trace when we enter a call with debugging enabled. *)
  and print_trace_enter args optargs =
    pr "  debug (h, \"enter:";
    List.iter (
      function
      | Bool n -> pr " %s=%%s" n
      | BytesIn (n, count)
      | BytesPersistIn (n, count)
      | BytesOut (n, count)
      | BytesPersistOut (n, count) -> pr " %s=<buf> %s=%%zu" n count
      | Closure { cbname } -> pr " %s=<fun>" cbname
      | Enum (n, _) -> pr " %s=%%d" n
      | Flags (n, _) -> pr " %s=0x%%x" n
      | Fd n | Int n -> pr " %s=%%d" n
      | Int64 n -> pr " %s=%%\" PRIi64 \"" n
      | SockAddrAndLen (n, len) -> pr " %s=<sockaddr> %s=%%d" n len
      | Path n
      | String n -> pr " %s=\\\"%%s\\\"" n
      | StringList n -> pr " %s=<list>" n
      | UInt n -> pr " %s=%%u" n
      | UInt32 n -> pr " %s=%%\" PRIu32 \"" n
      | UInt64 n -> pr " %s=%%\" PRIu64 \"" n
    ) args;
    List.iter (
      function
      | OClosure { cbname } -> pr " %s=%%s" cbname
      | OFlags (n, _) -> pr " %s=0x%%x" n
    ) optargs;
    pr "\"";
    List.iter (
      function
      | Bool n -> pr ", %s ? \"true\" : \"false\"" n
      | BytesIn (_, count)
      | BytesPersistIn (_, count)
      | BytesOut (_, count)
      | BytesPersistOut (_, count) -> pr ", %s" count
      | Closure { cbname } -> ()
      | Enum (n, _) -> pr ", %s" n
      | Flags (n, _) -> pr ", %s" n
      | Fd n | Int n -> pr ", %s" n
      | Int64 n -> pr ", %s" n
      | SockAddrAndLen (_, len) -> pr ", (int) %s" len
      | Path n | String n -> pr ", %s ? %s : \"NULL\"" n n
      | StringList n -> ()
      | UInt n | UInt32 n | UInt64 n -> pr ", %s" n
    ) args;
    List.iter (
      function
      | OClosure { cbname } ->
         pr ", CALLBACK_IS_NULL (%s_callback) ? \"<fun>\" : \"NULL\"" cbname
      | OFlags (n, _) -> pr ", %s" n
    ) optargs;
    pr ");\n"
  (* Print the trace when we leave a call with debugging enabled. *)
  and print_trace_leave ret =
    pr "  if_debug (h) {\n";
    let errcode = errcode_of_ret ret in
    (match errcode with
     | Some r -> 
        pr "    if (ret == %s)\n" r;
     | None ->
        pr "    if (0)\n";
    );
    pr "      debug (h, \"leave: error=\\\"%%s\\\"\", nbd_get_error ());\n";
    pr "    else\n";
    pr "      debug (h, \"leave: ret=";
    (match ret with
     | RBool | RErr | RFd | RInt -> pr "%%d"
     | RInt64 | RCookie -> pr "%%\" PRIi64 \""
     | RStaticString | RString -> pr "\\\"%%s\\\""
     | RUInt -> pr "%%u"
    );
    pr "\", ret);\n";
    pr "  }\n"
  in

  generate_header CStyle;

  pr "#include <config.h>\n";
  pr "\n";
  pr "#include <stdio.h>\n";
  pr "#include <stdlib.h>\n";
  pr "#include <stdint.h>\n";
  pr "#include <inttypes.h>\n";
  pr "#include <errno.h>\n";
  pr "\n";
  pr "#include <pthread.h>\n";
  pr "\n";
  pr "#include \"libnbd.h\"\n";
  pr "#include \"internal.h\"\n";
  pr "\n";
  List.iter print_wrapper handle_calls

(* We generate a fragment of Makefile.am containing the list
 * of generated functions, used in rules for building the manual
 * pages.  We exploit GNU make's sinclude to use this file without
 * upsetting automake.
 *)
let generate_docs_Makefile_inc () =
  generate_header HashStyle;

  pr "api_built += \\\n";
  List.iter (
    fun (name, _) ->
      pr "\tnbd_%s \\\n" name;
  ) handle_calls;
  pr "\t$(NULL)\n"

let generate_docs_api_links_pod () =
  let pages =
    List.map (fun (name, _) -> sprintf "nbd_%s(3)" name) handle_calls in
  let pages =
    "nbd_create(3)" ::
    "nbd_close(3)" ::
    "nbd_get_error(3)" ::
    "nbd_get_errno(3)" ::
    pages in
  let pages = List.sort compare pages in

  List.iteri (
    fun i page ->
      if i > 0 then pr ",\n";
      pr "L<%s>" page
  ) pages;
  pr ".\n"

let generate_docs_api_flag_links_pod () =
  let pages =
    filter_map (
      fun (name, _) ->
        if is_prefix name "is_" || is_prefix name "can_" then
          Some (sprintf "nbd_%s(3)" name)
        else
          None
    ) handle_calls in
  let pages = List.sort compare pages in

  List.iteri (
    fun i page ->
      if i > 0 then pr ",\n";
      pr "L<%s>" page
  ) pages;
  pr ".\n"

let print_docs_closure { cbname; cbargs } =
  pr " typedef struct {\n";
  pr "   int (*callback) ";
  print_cbarg_list ~wrap:true ~maxcol:60 cbargs;
  pr ";\n";
  pr "   void *user_data;\n";
  pr "   void (*free) (void *user_data);\n";
  pr " } nbd_%s_callback;\n" cbname

let generate_docs_nbd_pod name { args; optargs; ret;
                                 shortdesc; longdesc; example; see_also;
                                 permitted_states; may_set_error;
                                 first_version = (major, minor) } () =
  pr "=head1 NAME\n";
  pr "\n";
  pr_wrap ' ' (fun () -> pr "nbd_%s - %s" name shortdesc);
  pr "\n";
  pr "\n";

  pr "=head1 SYNOPSIS\n";
  pr "\n";
  pr " #include <libnbd.h>\n";
  pr "\n";

  List.iter (
    function
    | Closure cb -> print_docs_closure cb; pr "\n"
    | _ -> ()
  ) args;
  List.iter (
    function
    | OClosure cb -> print_docs_closure cb; pr "\n"
    | _ -> ()
  ) optargs;

  pr " ";
  print_call ~wrap:true ~maxcol:60 name args optargs ret;
  pr ";\n";
  pr "\n";

  pr "=head1 DESCRIPTION\n";
  pr "\n";
  pr "%s\n" longdesc;
  pr "\n";

  pr "=head1 RETURN VALUE\n";
  pr "\n";
  let errcode = errcode_of_ret ret in
  (match ret with
   | RBool ->
      pr "This call returns a boolean value.\n"
   | RStaticString ->
      pr "This call returns a statically allocated string, valid for the\n";
      pr "lifetime of the process or until libnbd is unloaded by\n";
      pr "L<dlclose(3)>.  You B<must not> try to free the string.\n"
   | RErr ->
      pr "If the call is successful the function returns C<0>.\n"
   | RFd ->
      pr "This call returns a file descriptor.\n"
   | RInt ->
      pr "This call returns an integer E<ge> C<0>.\n";
   | RInt64 ->
      pr "This call returns a 64 bit signed integer E<ge> C<0>.\n";
   | RCookie ->
      pr "This call returns the 64 bit cookie of the command.\n";
      pr "The cookie is E<ge> C<1>.\n";
      pr "Cookies are unique (per libnbd handle, not globally).\n"
   | RString ->
      pr "This call returns a string.  The caller must free the\n";
      pr "returned string to avoid a memory leak.\n";
   | RUInt ->
      pr "This call returns a bitmask.\n"
  );
  pr "\n";

  pr "=head1 ERRORS\n";
  pr "\n";
  if may_set_error then (
    let value = match errcode with
      | Some value -> value
      | None -> assert false in
    pr "On error C<%s> is returned.\n" value;
    pr "\n";
    pr "Refer to L<libnbd(3)/ERROR HANDLING>\n";
    pr "for how to get further details of the error.\n"
  )
  else
    pr "This function does not fail.\n";
  pr "\n";

  if permitted_states <> [] then (
    pr "=head1 HANDLE STATE\n";
    pr "\n";
    pr "The handle must be\n";
    pr "%s,\n" (permitted_state_text permitted_states);
    pr "otherwise this call will return an error.\n";
    pr "\n"
  );

  pr "=head1 VERSION\n";
  pr "\n";
  pr "This function first appeared in libnbd %d.%d.\n" major minor;
  pr "\n";
  pr "If you need to test if this function is available at compile time\n";
  pr "check if the following macro is defined:\n";
  pr "\n";
  pr " #define LIBNBD_HAVE_NBD_%s 1\n" (String.uppercase_ascii name);
  pr "\n";

  (match example with
   | None -> ()
   | Some filename ->
      pr "=head1 EXAMPLE\n";
      pr "\n";
      pr "This example is also available as F<%s>\n" filename;
      pr "in the libnbd source code.\n";
      pr "\n";
      let chan = open_in filename in
      (try
         while true do
           let line = input_line chan in
           if String.length line > 60 then
             failwithf "%s: %s: line too long in example" name filename;
           pr " %s\n" line
         done
       with End_of_file -> ()
      );
      close_in chan;
      pr "\n"
  );

  pr "=head1 SEE ALSO\n";
  pr "\n";
  List.iter (pr "%s,\n") see_also;
  pr "L<nbd_create(3)>,\n";
  pr "L<libnbd(3)>.\n";
  pr "\n";

  pr "\
=head1 AUTHORS

Eric Blake

Richard W.M. Jones

=head1 COPYRIGHT

Copyright (C) 2019 Red Hat Inc.
"
end

(*----------------------------------------------------------------------*)

(* Python bindings. *)

module Python : sig
  val generate_python_methods_h : unit -> unit
  val generate_python_libnbdmod_c : unit -> unit
  val generate_python_methods_c : unit -> unit
  val generate_python_nbd_py : unit -> unit
end = struct

let generate_python_methods_h () =
  generate_header CStyle;

  pr "#ifndef LIBNBD_METHODS_H\n";
  pr "#define LIBNBD_METHODS_H\n";
  pr "\n";
  pr "#define PY_SSIZE_T_CLEAN 1\n";
  pr "#include <Python.h>\n";
  pr "\n";
  pr "#include <assert.h>\n";
  pr "\n";
  pr "\
struct py_aio_buffer {
  Py_ssize_t len;
  void *data;
};

extern char **nbd_internal_py_get_string_list (PyObject *);
extern void nbd_internal_py_free_string_list (char **);
extern struct py_aio_buffer *nbd_internal_py_get_aio_buffer (PyObject *);

static inline struct nbd_handle *
get_handle (PyObject *obj)
{
  assert (obj);
  assert (obj != Py_None);
  return (struct nbd_handle *) PyCapsule_GetPointer(obj, \"nbd_handle\");
}

/* nbd.Error exception. */
extern PyObject *nbd_internal_py_Error;

static inline void
raise_exception ()
{
  PyObject *args = Py_BuildValue (\"si\", nbd_get_error (), nbd_get_errno ());

  if (args != NULL)
    PyErr_SetObject (nbd_internal_py_Error, args);
}

";

  List.iter (
    fun name ->
      pr "extern PyObject *nbd_internal_py_%s (PyObject *self, PyObject *args);\n"
         name;
  ) ([ "create"; "close";
       "alloc_aio_buffer";
       "aio_buffer_from_bytearray";
       "aio_buffer_to_bytearray";
       "aio_buffer_size" ] @ List.map fst handle_calls);

  pr "\n";
  pr "#endif /* LIBNBD_METHODS_H */\n"

let generate_python_libnbdmod_c () =
  generate_header CStyle;

  pr "#include <config.h>\n";
  pr "\n";
  pr "#define PY_SSIZE_T_CLEAN 1\n";
  pr "#include <Python.h>\n";
  pr "\n";
  pr "#include <stdio.h>\n";
  pr "#include <stdlib.h>\n";
  pr "#include <assert.h>\n";
  pr "\n";
  pr "#include <libnbd.h>\n";
  pr "\n";
  pr "#include \"methods.h\"\n";
  pr "\n";
  pr "static PyMethodDef methods[] = {\n";
  List.iter (
    fun name ->
      pr "  { (char *) \"%s\", nbd_internal_py_%s, METH_VARARGS, NULL },\n"
         name name;
  ) ([ "create"; "close";
       "alloc_aio_buffer";
       "aio_buffer_from_bytearray";
       "aio_buffer_to_bytearray";
       "aio_buffer_size" ] @ List.map fst handle_calls);
  pr "  { NULL, NULL, 0, NULL }\n";
  pr "};\n";
  pr "\n";
  pr "\
static struct PyModuleDef moduledef = {
  PyModuleDef_HEAD_INIT,
  \"libnbdmod\",           /* m_name */
  \"libnbd module\",       /* m_doc */
  -1,                    /* m_size */
  methods,               /* m_methods */
  NULL,                  /* m_reload */
  NULL,                  /* m_traverse */
  NULL,                  /* m_clear */
  NULL,                  /* m_free */
};

/* nbd.Error exception. */
PyObject *nbd_internal_py_Error;

extern PyMODINIT_FUNC PyInit_libnbdmod (void);

PyMODINIT_FUNC
PyInit_libnbdmod (void)
{
  PyObject *mod;

  mod = PyModule_Create (&moduledef);
  if (mod == NULL)
    return NULL;

  nbd_internal_py_Error = PyErr_NewException (\"nbd.Error\", NULL, NULL);
  if (nbd_internal_py_Error == NULL)
    return NULL;
  PyModule_AddObject (mod, \"Error\", nbd_internal_py_Error);

  return mod;
}
"

(* Functions with a Closure parameter are special because we
 * have to generate wrapper functions which translate the
 * callbacks back to Python.
 *)
let print_python_closure_wrapper { cbname; cbargs } =
  pr "/* Wrapper for %s callback. */\n" cbname;
  pr "static int\n";
  pr "%s_wrapper " cbname;
  C.print_cbarg_list ~wrap:true cbargs;
  pr "\n";
  pr "{\n";
  pr "  const struct user_data *data = user_data;\n";
  pr "  int ret = 0;\n";
  pr "\n";
  pr "  PyGILState_STATE py_save = PyGILState_UNLOCKED;\n";
  pr "  PyObject *py_args, *py_ret;\n";
  List.iter (
    function
    | CBArrayAndLen (UInt32 n, len) ->
       pr "  PyObject *py_%s = PyList_New (%s);\n" n len;
       pr "  for (size_t i = 0; i < %s; ++i)\n" len;
       pr "    PyList_SET_ITEM (py_%s, i, PyLong_FromUnsignedLong (%s[i]));\n" n n
    | CBBytesIn _
    | CBInt _
    | CBInt64 _ -> ()
    | CBMutable (Int n) ->
       pr "  PyObject *py_%s_modname = PyUnicode_FromString (\"ctypes\");\n" n;
       pr "  if (!py_%s_modname) { PyErr_PrintEx (0); return -1; }\n" n;
       pr "  PyObject *py_%s_mod = PyImport_Import (py_%s_modname);\n" n n;
       pr "  Py_DECREF (py_%s_modname);\n" n;
       pr "  if (!py_%s_mod) { PyErr_PrintEx (0); return -1; }\n" n;
       pr "  PyObject *py_%s = PyObject_CallMethod (py_%s_mod, \"c_int\", \"i\", *%s);\n" n n n;
       pr "  if (!py_%s) { PyErr_PrintEx (0); return -1; }\n" n;
    | CBString _
    | CBUInt _
    | CBUInt64 _ -> ()
    | CBArrayAndLen _ | CBMutable _ -> assert false
  ) cbargs;
  pr "\n";

  pr "  py_args = Py_BuildValue (\"(\"";
  List.iter (
    function
    | CBArrayAndLen (UInt32 n, len) -> pr " \"O\""
    | CBBytesIn (n, len) -> pr " \"y#\""
    | CBInt n -> pr " \"i\""
    | CBInt64 n -> pr " \"L\""
    | CBMutable (Int n) -> pr " \"O\""
    | CBString n -> pr " \"s\""
    | CBUInt n -> pr " \"I\""
    | CBUInt64 n -> pr " \"K\""
    | CBArrayAndLen _ | CBMutable _ -> assert false
  ) cbargs;
  pr " \")\"";
  List.iter (
    function
    | CBArrayAndLen (UInt32 n, _) -> pr ", py_%s" n
    | CBBytesIn (n, len) -> pr ", %s, (int) %s" n len
    | CBMutable (Int n) -> pr ", py_%s" n
    | CBInt n | CBInt64 n
    | CBString n
    | CBUInt n | CBUInt64 n -> pr ", %s" n
    | CBArrayAndLen _ | CBMutable _ -> assert false
  ) cbargs;
  pr ");\n";
  pr "  Py_INCREF (py_args);\n";
  pr "\n";
  pr "  if (PyEval_ThreadsInitialized ())\n";
  pr "    py_save = PyGILState_Ensure ();\n";
  pr "\n";
  pr "  py_ret = PyObject_CallObject (data->fn, py_args);\n";
  pr "\n";
  pr "  if (PyEval_ThreadsInitialized ())\n";
  pr "    PyGILState_Release (py_save);\n";
  pr "\n";
  pr "  Py_DECREF (py_args);\n";
  pr "\n";
  pr "  if (py_ret != NULL) {\n";
  pr "    if (PyLong_Check (py_ret))\n";
  pr "      ret = PyLong_AsLong (py_ret);\n";
  pr "    else\n";
  pr "      /* If it's not a long, just assume it's 0. */\n";
  pr "      ret = 0;\n";
  pr "    Py_DECREF (py_ret);\n";
  pr "  }\n";
  pr "  else {\n";
  pr "    /* Special case failed assertions to be fatal. */\n";
  pr "    if (PyErr_ExceptionMatches (PyExc_AssertionError)) {\n";
  pr "      PyErr_Print ();\n";
  pr "      abort ();\n";
  pr "    }\n";
  pr "    ret = -1;\n";
  pr "    PyErr_PrintEx (0); /* print exception */\n";
  pr "  };\n";
  pr "\n";
  List.iter (
    function
    | CBArrayAndLen (UInt32 n, _) ->
       pr "  Py_DECREF (py_%s);\n" n
    | CBMutable (Int n) ->
       pr "  PyObject *py_%s_ret = PyObject_GetAttrString (py_%s, \"value\");\n" n n;
       pr "  *%s = PyLong_AsLong (py_%s_ret);\n" n n;
       pr "  Py_DECREF (py_%s_ret);\n" n;
       pr "  Py_DECREF (py_%s);\n" n
    | CBBytesIn _
    | CBInt _ | CBInt64 _
    | CBString _
    | CBUInt _ | CBUInt64 _ -> ()
    | CBArrayAndLen _ | CBMutable _ -> assert false
  ) cbargs;
  pr "  return ret;\n";
  pr "}\n";
  pr "\n"

(* Generate the Python binding. *)
let print_python_binding name { args; optargs; ret; may_set_error } =
  pr "PyObject *\n";
  pr "nbd_internal_py_%s (PyObject *self, PyObject *args)\n" name;
  pr "{\n";
  pr "  PyObject *py_h;\n";
  pr "  struct nbd_handle *h;\n";
  pr "  %s ret;\n" (C.type_of_ret ret);
  pr "  PyObject *py_ret;\n";
  List.iter (
    function
    | Bool n -> pr "  int %s;\n" n
    | BytesIn (n, _) ->
       pr "  Py_buffer %s;\n" n
    | BytesOut (n, count) ->
       pr "  char *%s;\n" n;
       pr "  Py_ssize_t %s;\n" count
    | BytesPersistIn (n, _)
    | BytesPersistOut (n, _) ->
       pr "  PyObject *%s; /* PyCapsule pointing to struct py_aio_buffer */\n"
          n;
       pr "  struct py_aio_buffer *%s_buf;\n" n
    | Closure { cbname } ->
       pr "  struct user_data *%s_user_data = alloc_user_data ();\n" cbname;
       pr "  if (%s_user_data == NULL) return NULL;\n" cbname;
       pr "  nbd_%s_callback %s = { .callback = %s_wrapper,\n"
         cbname cbname cbname;
       pr "                         .user_data = %s_user_data,\n" cbname;
       pr "                         .free = free_user_data };\n"
    | Enum (n, _) -> pr "  int %s;\n" n
    | Flags (n, _) ->
       pr "  uint32_t %s_u32;\n" n;
       pr "  unsigned int %s; /* really uint32_t */\n" n
    | Fd n | Int n -> pr "  int %s;\n" n
    | Int64 n ->
       pr "  int64_t %s_i64;\n" n;
       pr "  long long %s; /* really int64_t */\n" n
    | Path n ->
       pr "  PyObject *py_%s = NULL;\n" n;
       pr "  char *%s = NULL;\n" n
    | SockAddrAndLen (n, _) ->
       pr "  /* XXX Complicated - Python uses a tuple of different\n";
       pr "   * lengths for the different socket types.\n";
       pr "   */\n";
       pr "  PyObject *%s;\n" n
    | String n -> pr "  const char *%s;\n" n
    | StringList n ->
       pr "  PyObject *py_%s;\n" n;
       pr "  char **%s = NULL;\n" n
    | UInt n -> pr "  unsigned int %s;\n" n
    | UInt32 n ->
       pr "  uint32_t %s_u32;\n" n;
       pr "  unsigned int %s; /* really uint32_t */\n" n
    | UInt64 n ->
       pr "  uint64_t %s_u64;\n" n;
       pr "  unsigned long long %s; /* really uint64_t */\n" n
  ) args;
  List.iter (
    function
    | OClosure { cbname } ->
       pr "  struct user_data *%s_user_data = alloc_user_data ();\n" cbname;
       pr "  if (%s_user_data == NULL) return NULL;\n" cbname;
       pr "  nbd_%s_callback %s = { .callback = %s_wrapper,\n"
         cbname cbname cbname;
       pr "                         .user_data = %s_user_data,\n" cbname;
       pr "                         .free = free_user_data };\n"
    | OFlags (n, _) ->
       pr "  uint32_t %s_u32;\n" n;
       pr "  unsigned int %s; /* really uint32_t */\n" n
  ) optargs;
  pr "\n";

  (* Parse the Python parameters. *)
  pr "  if (!PyArg_ParseTuple (args, (char *) \"O\"";
  List.iter (
    function
    | Bool n -> pr " \"b\""
    | BytesIn (n, _) -> pr " \"y*\""
    | BytesPersistIn (n, _) -> pr " \"O\""
    | BytesOut (_, count) -> pr " \"n\""
    | BytesPersistOut (_, count) -> pr " \"O\""
    | Closure _ -> pr " \"O\""
    | Enum _ -> pr " \"i\""
    | Flags _ -> pr " \"I\""
    | Fd n | Int n -> pr " \"i\""
    | Int64 n -> pr " \"L\""
    | Path n -> pr " \"O&\""
    | SockAddrAndLen (n, _) -> pr " \"O\""
    | String n -> pr " \"s\""
    | StringList n -> pr " \"O\""
    | UInt n -> pr " \"I\""
    | UInt32 n -> pr " \"I\""
    | UInt64 n -> pr " \"K\""
  ) args;
  List.iter (
    function
    | OClosure _ -> pr " \"O\""
    | OFlags _ -> pr " \"I\""
  ) optargs;
  pr "\n";
  pr "                         \":nbd_%s\",\n" name;
  pr "                         &py_h";
  List.iter (
    function
    | Bool n -> pr ", &%s" n
    | BytesIn (n, _) | BytesPersistIn (n, _)
    | BytesPersistOut (n, _) -> pr ", &%s" n
    | BytesOut (_, count) -> pr ", &%s" count
    | Closure { cbname } -> pr ", &%s_user_data->fn" cbname
    | Enum (n, _) -> pr ", &%s" n
    | Flags (n, _) -> pr ", &%s" n
    | Fd n | Int n -> pr ", &%s" n
    | Int64 n -> pr ", &%s" n
    | Path n -> pr ", PyUnicode_FSConverter, &py_%s" n
    | SockAddrAndLen (n, _) -> pr ", &%s" n
    | String n -> pr ", &%s" n
    | StringList n -> pr ", &py_%s" n
    | UInt n -> pr ", &%s" n
    | UInt32 n -> pr ", &%s" n
    | UInt64 n -> pr ", &%s" n
  ) args;
  List.iter (
    function
    | OClosure { cbname } -> pr ", &%s_user_data->fn" cbname
    | OFlags (n, _) -> pr ", &%s" n
  ) optargs;
  pr "))\n";
  pr "    return NULL;\n";

  pr "  h = get_handle (py_h);\n";
  List.iter (
    function
    | Bool _ -> ()
    | BytesIn _ -> ()
    | BytesOut (n, count) ->
       pr "  %s = malloc (%s);\n" n count
    | BytesPersistIn (n, _) | BytesPersistOut (n, _) ->
       pr "  %s_buf = nbd_internal_py_get_aio_buffer (%s);\n" n n
    | Closure { cbname } ->
       pr "  /* Increment refcount since pointer may be saved by libnbd. */\n";
       pr "  Py_INCREF (%s_user_data->fn);\n" cbname;
       pr "  if (!PyCallable_Check (%s_user_data->fn)) {\n" cbname;
       pr "    PyErr_SetString (PyExc_TypeError,\n";
       pr "                     \"callback parameter %s is not callable\");\n" cbname;
       pr "    return NULL;\n";
       pr "  }\n"
    | Enum _ -> ()
    | Flags (n, _) -> pr "  %s_u32 = %s;\n" n n
    | Fd _ | Int _ -> ()
    | Int64 n -> pr "  %s_i64 = %s;\n" n n
    | Path n ->
       pr "  %s = PyBytes_AS_STRING (py_%s);\n" n n;
       pr "  assert (%s != NULL);\n" n
    | SockAddrAndLen _ ->
       pr "  abort (); /* XXX SockAddrAndLen not implemented */\n";
    | String _ -> ()
    | StringList n ->
       pr "  %s = nbd_internal_py_get_string_list (py_%s);\n" n n;
       pr "  if (!%s) { py_ret = NULL; goto out; }\n" n
    | UInt _ -> ()
    | UInt32 n -> pr "  %s_u32 = %s;\n" n n
    | UInt64 n -> pr "  %s_u64 = %s;\n" n n
  ) args;
  List.iter (
    function
    | OClosure { cbname } ->
       pr "  if (%s_user_data->fn != Py_None) {\n" cbname;
       pr "    /* Increment refcount since pointer may be saved by libnbd. */\n";
       pr "    Py_INCREF (%s_user_data->fn);\n" cbname;
       pr "    if (!PyCallable_Check (%s_user_data->fn)) {\n" cbname;
       pr "      PyErr_SetString (PyExc_TypeError,\n";
       pr "                       \"callback parameter %s is not callable\");\n" cbname;
       pr "      return NULL;\n";
       pr "    }\n";
       pr "  }\n";
       pr "  else\n";
       pr "    %s.callback = NULL; /* we're not going to call it */\n" cbname
    | OFlags (n, _) -> pr "  %s_u32 = %s;\n" n n
  ) optargs;

  (* If there is a BytesPersistIn/Out parameter then we need to
   * increment the refcount and save the pointer into
   * completion_callback.user_data so we can decrement the
   * refcount on command completion.
   *)
  List.iter (
    function
    | BytesPersistIn (n, _) | BytesPersistOut (n, _) ->
       pr "  /* Increment refcount since buffer may be saved by libnbd. */\n";
       pr "  Py_INCREF (%s);\n" n;
       pr "  completion_user_data->buf = %s;\n" n;
    | _ -> ()
  ) args;

  (* Call the underlying C function. *)
  pr "  ret = nbd_%s (h" name;
  List.iter (
    function
    | Bool n -> pr ", %s" n
    | BytesIn (n, _) -> pr ", %s.buf, %s.len" n n
    | BytesOut (n, count) -> pr ", %s, %s" n count
    | BytesPersistIn (n, _)
    | BytesPersistOut (n, _) -> pr ", %s_buf->data, %s_buf->len" n n
    | Closure { cbname } -> pr ", %s" cbname
    | Enum (n, _) -> pr ", %s" n
    | Flags (n, _) -> pr ", %s_u32" n
    | Fd n | Int n -> pr ", %s" n
    | Int64 n -> pr ", %s_i64" n
    | Path n -> pr ", %s" n
    | SockAddrAndLen (n, _) -> pr ", /* XXX */ (void *) %s, 0" n
    | String n -> pr ", %s" n
    | StringList n -> pr ", %s" n
    | UInt n -> pr ", %s" n
    | UInt32 n -> pr ", %s_u32" n
    | UInt64 n -> pr ", %s_u64" n
  ) args;
  List.iter (
    function
    | OClosure { cbname } -> pr ", %s" cbname
    | OFlags (n, _) -> pr ", %s_u32" n
  ) optargs;
  pr ");\n";
  if may_set_error then (
    pr "  if (ret == %s) {\n"
      (match C.errcode_of_ret ret with Some s -> s | None -> assert false);
    pr "    raise_exception ();\n";
    pr "    py_ret = NULL;\n";
    pr "    goto out;\n";
    pr "  }\n"
  );

  (* Convert the result back to a Python object and return it. *)
  let use_ret = ref true in
  List.iter (
    function
    | BytesOut (n, count) ->
       pr "  py_ret = PyBytes_FromStringAndSize (%s, %s);\n" n count;
       use_ret := false
    | Bool _
    | BytesIn _
    | BytesPersistIn _ | BytesPersistOut _
    | Closure _
    | Enum _
    | Flags _
    | Fd _ | Int _
    | Int64 _
    | Path _
    | SockAddrAndLen _
    | String _
    | StringList _
    | UInt _
    | UInt32 _
    | UInt64 _ -> ()
  ) args;
  if !use_ret then (
    match ret with
    | RBool ->
       pr "  py_ret = ret ? Py_True : Py_False;\n";
       pr "  Py_INCREF (py_ret);\n"
    | RStaticString ->
       pr "  py_ret = PyUnicode_FromString (ret);\n"
    | RErr ->
       pr "  py_ret = Py_None;\n";
       pr "  Py_INCREF (py_ret);\n"
    | RFd
    | RInt
    | RUInt ->
       pr "  py_ret = PyLong_FromLong (ret);\n"
    | RInt64 | RCookie ->
       pr "  py_ret = PyLong_FromLongLong (ret);\n"
    | RString ->
       pr "  py_ret = PyUnicode_FromString (ret);\n";
       pr "  free (ret);\n"
  );

  pr "\n";
  if may_set_error then
    pr " out:\n";
  List.iter (
    function
    | Bool _ -> ()
    | BytesIn (n, _) -> pr "  PyBuffer_Release (&%s);\n" n
    | BytesPersistIn _ | BytesOut _ | BytesPersistOut _ -> ()
    | Closure _ -> ()
    | Enum _ -> ()
    | Flags _ -> ()
    | Fd _ | Int _ -> ()
    | Int64 _ -> ()
    | Path n ->
       pr "  Py_XDECREF (py_%s);\n" n
    | SockAddrAndLen _ -> ()
    | String n -> ()
    | StringList n -> pr "  nbd_internal_py_free_string_list (%s);\n" n
    | UInt _ -> ()
    | UInt32 _ -> ()
    | UInt64 _ -> ()
  ) args;
  pr "  return py_ret;\n";
  pr "}\n";
  pr "\n"

let generate_python_methods_c () =
  generate_header CStyle;

  pr "#define PY_SSIZE_T_CLEAN 1\n";
  pr "#include <Python.h>\n";
  pr "\n";
  pr "#include <stdio.h>\n";
  pr "#include <stdlib.h>\n";
  pr "#include <stdint.h>\n";
  pr "#include <stdbool.h>\n";
  pr "\n";
  pr "#include <libnbd.h>\n";
  pr "\n";
  pr "#include <methods.h>\n";
  pr "\n";

  pr "/* This is passed to *_wrapper as the user_data pointer\n";
  pr " * and freed in the free_user_data function below.\n";
  pr " */\n";
  pr "struct user_data {\n";
  pr "  PyObject *fn;    /* Optional pointer to Python function. */\n";
  pr "  PyObject *buf;   /* Optional pointer to persistent buffer. */\n";
  pr "};\n";
  pr "\n";
  pr "static struct user_data *\n";
  pr "alloc_user_data (void)\n";
  pr "{\n";
  pr "  struct user_data *data = calloc (1, sizeof *data);\n";
  pr "  if (data == NULL) {\n";
  pr "    PyErr_NoMemory ();\n";
  pr "    return NULL;\n";
  pr "  }\n";
  pr "  return data;\n";
  pr "}\n";
  pr "\n";
  pr "static void\n";
  pr "free_user_data (void *user_data)\n";
  pr "{\n";
  pr "  struct user_data *data = user_data;\n";
  pr "\n";
  pr "  if (data->fn != NULL)\n";
  pr "    Py_DECREF (data->fn);\n";
  pr "  if (data->buf != NULL)\n";
  pr "    Py_DECREF (data->buf);\n";
  pr "  free (data);\n";
  pr "}\n";
  pr "\n";

  List.iter print_python_closure_wrapper all_closures;
  List.iter (
    fun (name, fn) ->
      print_python_binding name fn
  ) handle_calls

let py_fn_rex = Str.regexp "L<nbd_\\([a-z0-9_]+\\)(3)>"
let py_const_rex = Str.regexp "C<LIBNBD_"

let generate_python_nbd_py () =
  generate_header HashStyle;

  pr "\
'''
Python bindings for libnbd

import nbd
h = nbd.NBD ()
h.connect_tcp (\"localhost\", \"nbd\")
buf = h.pread (512, 0)

Read the libnbd(3) man page to find out how to use the API.
'''

import libnbdmod

# Re-export Error exception as nbd.Error, adding some methods.
from libnbdmod import Error

Error.__doc__ = '''
Exception thrown when the underlying libnbd call fails.

This exception has three properties to query the error.  Use
the .string property to return a printable string containing
the error message.  Use the .errnum property for the associated
numeric error value (which may be 0 if the error did not
correspond to a system call failure), or the .errno property to
return a string containing the Python errno name if one is known
(which may be None if the numeric value does not correspond to
a known errno name).
'''

Error.string = property (lambda self: self.args[0])

def _errno (self):
    import errno
    try:
        return errno.errorcode[self.args[1]]
    except KeyError:
        return None
Error.errno = property (_errno)

Error.errnum = property (lambda self: self.args[1])

def _str (self):
    if self.errno:
        return (\"%%s (%%s)\" %% (self.string, self.errno))
    else:
        return (\"%%s\" %% self.string)
Error.__str__ = _str

";

  List.iter (
    fun { enum_prefix; enums } ->
      List.iter (
        fun (enum, i) ->
          let enum = sprintf "%s_%s" enum_prefix enum in
          pr "%-30s = %d\n" enum i
      ) enums;
      pr "\n"
  ) all_enums;
  List.iter (
    fun { flag_prefix; flags } ->
      List.iter (
        fun (flag, i) ->
          let flag = sprintf "%s_%s" flag_prefix flag in
          pr "%-30s = %d\n" flag i
      ) flags;
      pr "\n"
  ) all_flags;
  List.iter (fun (n, i) -> pr "%-30s = %d\n" n i) constants;
  List.iter (
    fun (ns, ctxts) ->
      let ns_upper = String.uppercase_ascii ns in
      pr "NAMESPACE_%-20s = \"%s:\"\n" ns_upper ns;
      List.iter (
        fun (ctxt, consts) ->
          let ctxt_upper = String.uppercase_ascii ctxt in
          pr "%-30s = \"%s:%s\"\n"
             (sprintf "CONTEXT_%s_%s" ns_upper ctxt_upper) ns ctxt;
          List.iter (fun (n, i) -> pr "%-30s = %d\n" n i) consts
      ) ctxts;
  ) metadata_namespaces;

  pr "\

class Buffer (object):
    '''Asynchronous I/O persistent buffer'''

    def __init__ (self, len):
        '''allocate an uninitialized AIO buffer used for nbd.aio_pread'''
        self._o = libnbdmod.alloc_aio_buffer (len)

    @classmethod
    def from_bytearray (cls, ba):
        '''create an AIO buffer from a bytearray'''
        o = libnbdmod.aio_buffer_from_bytearray (ba)
        self = cls (0)
        self._o = o
        return self

    def to_bytearray (self):
        '''copy an AIO buffer into a bytearray'''
        return libnbdmod.aio_buffer_to_bytearray (self._o)

    def size (self):
        '''return the size of an AIO buffer'''
        return libnbdmod.aio_buffer_size (self._o)

class NBD (object):
    '''NBD handle'''

    def __init__ (self):
        '''create a new NBD handle'''
        self._o = libnbdmod.create ()

    def __del__ (self):
        '''close the NBD handle and underlying connection'''
        libnbdmod.close (self._o)

";

  List.iter (
    fun (name, { args; optargs; shortdesc; longdesc }) ->
      let args =
        List.map (
          function
          | Bool n -> n, None, None
          | BytesIn (n, _) -> n, None, None
          | BytesOut (_, count) -> count, None, None
          | BytesPersistIn (n, _) -> n, None, Some (sprintf "%s._o" n)
          | BytesPersistOut (n, _) -> n, None, Some (sprintf "%s._o" n)
          | Closure { cbname } -> cbname, None, None
          | Enum (n, _) -> n, None, None
          | Flags (n, _) -> n, None, None
          | Fd n | Int n -> n, None, None
          | Int64 n -> n, None, None
          | Path n -> n, None, None
          | SockAddrAndLen (n, _) -> n, None, None
          | String n -> n, None, None
          | StringList n -> n, None, None
          | UInt n -> n, None, None
          | UInt32 n -> n, None, None
          | UInt64 n -> n, None, None
        ) args in
      let optargs =
        List.map (
          function
          | OClosure { cbname } -> cbname, Some "None", None
          | OFlags (n, _) -> n, Some "0", None
        ) optargs in
      let args = args @ optargs in
      pr "    def %s (self" name;
      List.iter (
        function
        | n, None, _ -> pr ", %s" n
        | n, Some default, _ -> pr ", %s=%s" n default
      ) args;
      pr "):\n";
      let longdesc = Str.global_replace py_fn_rex "C<nbd.\\1>" longdesc in
      let longdesc = Str.global_replace py_const_rex "C<" longdesc in
      let longdesc = pod2text longdesc in
      pr "        '''▶ %s\n\n%s'''\n" shortdesc (String.concat "\n" longdesc);
      pr "        return libnbdmod.%s (self._o" name;
      List.iter (
        function
        | _, _, Some getter -> pr ", %s" getter
        | n, _, None -> pr ", %s" n
      ) args;
      pr ")\n";
      pr "\n"
  ) handle_calls;

  (* For nbdsh. *)
  pr "\
package_name = NBD().get_package_name()
__version__ = NBD().get_version()

if __name__ == \"__main__\":
    import nbdsh

    nbdsh.shell()
"
end

(*----------------------------------------------------------------------*)

(* OCaml bindings.
 *
 * Note we always pass the parameters as: optargs, handle, args.
 *)

module OCaml : sig
  val generate_ocaml_nbd_mli : unit -> unit
  val generate_ocaml_nbd_ml : unit -> unit
  val generate_ocaml_nbd_c : unit -> unit
end = struct

(* String representation of args and return value. *)
let rec ocaml_fundecl_to_string args optargs ret =
  let optargs = List.map ocaml_optarg_to_string optargs in
  let args = List.map ocaml_arg_to_string args in
  let ret = ocaml_ret_to_string ret in
  String.concat " -> " (optargs @ ["t"] @ args @ [ret])

(* String representation of a single OCaml arg. *)
and ocaml_arg_to_string = function
  | Bool _ -> "bool"
  | BytesIn _ -> "bytes"
  | BytesPersistIn _ -> "Buffer.t"
  | BytesOut _ -> "bytes"
  | BytesPersistOut _ -> "Buffer.t"
  | Closure { cbargs } ->
     sprintf "(%s)" (ocaml_closuredecl_to_string cbargs)
  | Enum (_, { enum_prefix }) -> sprintf "%s.t" enum_prefix
  | Fd _ -> "Unix.file_descr"
  | Flags (_, { flag_prefix }) -> sprintf "%s.t" flag_prefix
  | Int _ -> "int"
  | Int64 _ -> "int64"
  | Path _ -> "string"
  | SockAddrAndLen _ -> "string" (* XXX not impl *)
  | String _ -> "string"
  | StringList _ -> "string list"
  | UInt _ -> "int"
  | UInt32 _ -> "int32"
  | UInt64 _ -> "int64"

and ocaml_ret_to_string = function
  | RBool -> "bool"
  | RStaticString -> "string"
  | RErr -> "unit"
  | RFd -> "Unix.file_descr"
  | RInt -> "int"
  | RInt64 -> "int64"
  | RCookie -> "cookie"
  | RString -> "string"
  | RUInt -> "int"

and ocaml_optarg_to_string = function
  | OClosure { cbname; cbargs } ->
     sprintf "?%s:(%s)" cbname (ocaml_closuredecl_to_string cbargs)
  | OFlags (n, { flag_prefix }) -> sprintf "?%s:%s.t list" n flag_prefix

and ocaml_closuredecl_to_string cbargs =
  let cbargs = List.map ocaml_cbarg_to_string cbargs in
  String.concat " -> " (cbargs @ ["int"])

and ocaml_cbarg_to_string = function
  | CBArrayAndLen (arg, _) ->
     sprintf "%s array" (ocaml_arg_to_string arg)
  | CBBytesIn _ -> "bytes"
  | CBInt _ -> "int"
  | CBInt64 _ -> "int64"
  | CBMutable arg ->
     sprintf "%s ref" (ocaml_arg_to_string arg)
  | CBString _ -> "string"
  | CBUInt _ -> "int"
  | CBUInt64 _ -> "int64"

let ocaml_name_of_arg = function
  | Bool n -> n
  | BytesIn (n, len) -> n
  | BytesOut (n, len) -> n
  | BytesPersistIn (n, len) -> n
  | BytesPersistOut (n, len) -> n
  | Closure { cbname } -> cbname
  | Enum (n, _) -> n
  | Fd n -> n
  | Flags (n, _) -> n
  | Int n -> n
  | Int64 n -> n
  | Path n -> n
  | SockAddrAndLen (n, len) -> n
  | String n -> n
  | StringList n -> n
  | UInt n -> n
  | UInt32 n -> n
  | UInt64 n -> n

let ocaml_name_of_optarg = function
  | OClosure { cbname } -> cbname
  | OFlags (n, _) -> n

let num_params args optargs =
  List.length optargs + 1 (* handle *) + List.length args

let generate_ocaml_nbd_mli () =
  generate_header OCamlStyle;

  pr "\
(** OCaml bindings for libnbd.

    For full documentation see libnbd-ocaml(3) and libnbd(3).

    For examples written in OCaml see the libnbd source code
    [ocaml/examples] subdirectory.
*)

exception Error of string * int
(** Exception thrown when an API call fails.

    The string is the error message, and the int is the raw errno
    (if available).
*)

exception Closed of string
(** Exception thrown if you call a closed handle. *)

type cookie = int64

";

  List.iter (
    fun { enum_prefix; enums } ->
      pr "module %s : sig\n" enum_prefix;
      pr "  type t =\n";
      List.iter (
        fun (enum, _) ->
          pr "  | %s\n" enum
      ) enums;
      pr "end\n";
      pr "\n"
  ) all_enums;
  List.iter (
    fun { flag_prefix; flags } ->
      pr "module %s : sig\n" flag_prefix;
      pr "  type t =\n";
      List.iter (
        fun (flag, _) ->
          pr "  | %s\n" flag
      ) flags;
      pr "end\n";
      pr "\n"
  ) all_flags;
  List.iter (
    fun (n, _) -> pr "val %s : int32\n" (String.lowercase_ascii n)
  ) constants;
  List.iter (
    fun (ns, ctxts) ->
      pr "val namespace_%s : string\n" ns;
      List.iter (
        fun (ctxt, consts) ->
          pr "val context_%s_%s : string\n" ns ctxt;
          List.iter (fun (n, _) ->
              pr "val %s : int32\n" (String.lowercase_ascii n)
          ) consts
      ) ctxts;
  ) metadata_namespaces;
  pr "\n";

  pr "\
module Buffer : sig
  type t
  (** Persistent, mutable C-compatible malloc'd buffer, used in AIO calls. *)

  val alloc : int -> t
  (** Allocate an uninitialized buffer.  The parameter is the size
      in bytes. *)

  val to_bytes : t -> bytes
  (** Copy buffer to an OCaml [bytes] object. *)

  val of_bytes : bytes -> t
  (** Copy an OCaml [bytes] object to a newly allocated buffer. *)

  val size : t -> int
  (** Return the size of the buffer. *)

end
(** Persistent buffer used in AIO calls. *)

type t
(** The handle. *)

val create : unit -> t
(** Create a new handle. *)

val close : t -> unit
(** Close a handle.

    Handles can also be closed by the garbage collector when
    they become unreachable.  This call is used only if you want
    to force the handle to close now and reclaim resources
    immediately.
*)

";

  List.iter (
    fun (name, { args; optargs; ret; shortdesc; longdesc }) ->
      pr "val %s : %s\n" name (ocaml_fundecl_to_string args optargs ret);

      pr "(** %s\n" shortdesc;
      pr "\n";
      pr "%s" (String.concat "\n" (pod2text longdesc));
      pr "*)\n";
      pr "\n";

  ) handle_calls

let generate_ocaml_nbd_ml () =
  generate_header OCamlStyle;

  pr "\
exception Error of string * int
exception Closed of string
type cookie = int64

(* Give the exceptions names so that they can be raised from the C code. *)
let () =
  Callback.register_exception \"nbd_internal_ocaml_error\" (Error (\"\", 0));
  Callback.register_exception \"nbd_internal_ocaml_closed\" (Closed \"\")

";

  List.iter (
    fun { enum_prefix; enums } ->
      pr "module %s = struct\n" enum_prefix;
      pr "  type t =\n";
      List.iter (
        fun (enum, _) ->
          pr "  | %s\n" enum
      ) enums;
      pr "end\n";
      pr "\n"
  ) all_enums;
  List.iter (
    fun { flag_prefix; flags } ->
      pr "module %s = struct\n" flag_prefix;
      pr "  type t =\n";
      List.iter (
        fun (flag, _) ->
          pr "  | %s\n" flag
      ) flags;
      pr "end\n";
      pr "\n"
  ) all_flags;
  List.iter (
    fun (n, i) -> pr "let %s = %d_l\n" (String.lowercase_ascii n) i
  ) constants;
  List.iter (
    fun (ns, ctxts) ->
      pr "let namespace_%s = \"%s:\"\n" ns ns;
      List.iter (
        fun (ctxt, consts) ->
          pr "let context_%s_%s = \"%s:%s\"\n" ns ctxt ns ctxt;
          List.iter (fun (n, i) ->
              pr "let %s = %d_l\n" (String.lowercase_ascii n) i
          ) consts
      ) ctxts;
  ) metadata_namespaces;
  pr "\n";

  pr "\
module Buffer = struct
  type t
  external alloc : int -> t = \"nbd_internal_ocaml_buffer_alloc\"
  external to_bytes : t -> bytes = \"nbd_internal_ocaml_buffer_to_bytes\"
  external of_bytes : bytes -> t = \"nbd_internal_ocaml_buffer_of_bytes\"
  external size : t -> int = \"nbd_internal_ocaml_buffer_size\"
end

type t

external create : unit -> t = \"nbd_internal_ocaml_nbd_create\"
external close : t -> unit = \"nbd_internal_ocaml_nbd_close\"

";

  List.iter (
    fun (name, { args; optargs; ret }) ->
      pr "external %s : %s\n" name (ocaml_fundecl_to_string args optargs ret);
      pr "    = ";
      (* In OCaml, argument lists longer than 5 elements require
       * special handling in the C bindings.
       *)
      if num_params args optargs > 5 then
        pr "\"nbd_internal_ocaml_nbd_%s_byte\" " name;
      pr "\"nbd_internal_ocaml_nbd_%s\"\n" name
  ) handle_calls

let print_ocaml_enum_val { enum_prefix; enums } =
  pr "/* Convert OCaml %s.t to int. */\n" enum_prefix;
  pr "static int\n";
  pr "%s_val (value v)\n" enum_prefix;
  pr "{\n";
  pr "  /* NB: No allocation in this function, don't need to use\n";
  pr "   * CAML* wrappers.\n";
  pr "   */\n";
  pr "  int i, r = 0;\n";
  pr "\n";
  pr "  i = Int_val (Field (v, 0));\n";
  pr "  /* i is the index of the enum in the type\n";
  pr "   * (eg. i = 0 => enum = %s.%s).\n" enum_prefix (fst (List.hd enums));
  pr "   * Convert it to the C representation.\n";
  pr "   */\n";
  pr "  switch (i) {\n";
  List.iteri (
    fun i (enum, _) ->
      pr "  case %d: r = LIBNBD_%s_%s; break;\n" i enum_prefix enum
  ) enums;
  pr "  }\n";
  pr "\n";
  pr "  return r;\n";
  pr "}\n";
  pr "\n"

let print_ocaml_flag_val { flag_prefix; flags } =
  pr "/* Convert OCaml %s.t list to uint32_t bitmask. */\n" flag_prefix;
  pr "static uint32_t\n";
  pr "%s_val (value v)\n" flag_prefix;
  pr "{\n";
  pr "  /* NB: No allocation in this function, don't need to use\n";
  pr "   * CAML* wrappers.\n";
  pr "   */\n";
  pr "  int i;\n";
  pr "  uint32_t r = 0;\n";
  pr "\n";
  pr "  for (; v != Val_emptylist; v = Field (v, 1)) {\n";
  pr "    i = Int_val (Field (v, 0));\n";
  pr "    /* i is the index of the flag in the type\n";
  pr "     * (eg. i = 0 => flag = %s.%s).\n" flag_prefix (fst (List.hd flags));
  pr "     * Convert it to the C representation.\n";
  pr "     */\n";
  pr "    switch (i) {\n";
  List.iteri (
    fun i (flag, _) ->
      pr "    case %d: r |= LIBNBD_%s_%s; break;\n" i flag_prefix flag
  ) flags;
  pr "    }\n";
  pr "  }\n";
  pr "\n";
  pr "  return r;\n";
  pr "}\n";
  pr "\n"

let print_ocaml_closure_wrapper { cbname; cbargs } =
  let argnames =
    List.map (
      function
      | CBArrayAndLen (UInt32 n, _) | CBBytesIn (n, _)
      | CBInt n | CBInt64 n
      | CBMutable (Int n) | CBString n | CBUInt n | CBUInt64 n ->
         n ^ "v"
      | CBArrayAndLen _ | CBMutable _ -> assert false
      ) cbargs in

  pr "/* Wrapper for %s callback. */\n" cbname;
  pr "static int\n";
  pr "%s_wrapper_locked " cbname;
  C.print_cbarg_list ~wrap:true cbargs;
  pr "\n";
  pr "{\n";
  pr "  CAMLparam0 ();\n";
  assert (List.length argnames <= 5);
  pr "  CAMLlocal%d (%s);\n" (List.length argnames)
    (String.concat ", " argnames);
  pr "  CAMLlocal2 (exn, rv);\n";
  pr "  const struct user_data *data = user_data;\n";
  pr "  int r;\n";
  pr "  value args[%d];\n" (List.length argnames);
  pr "\n";

  List.iter (
    function
    | CBArrayAndLen (UInt32 n, count) ->
       pr "  %sv = nbd_internal_ocaml_alloc_int32_array (%s, %s);\n"
         n n count;
    | CBBytesIn (n, len) ->
       pr "  %sv = caml_alloc_initialized_string (%s, %s);\n" n len n
    | CBInt n | CBUInt n ->
       pr "  %sv = Val_int (%s);\n" n n
    | CBInt64 n ->
       pr "  %sv = caml_copy_int64 (%s);\n" n n
    | CBString n ->
       pr "  %sv = caml_copy_string (%s);\n" n n
    | CBUInt64 n ->
       pr "  %sv = caml_copy_int64 (%s);\n" n n
    | CBMutable (Int n) ->
       pr "  %sv = caml_alloc_tuple (1);\n" n;
       pr "  Store_field (%sv, 0, Val_int (*%s));\n" n n
    | CBArrayAndLen _ | CBMutable _ -> assert false
  ) cbargs;

  List.iteri (fun i n -> pr "  args[%d] = %s;\n" i n) argnames;

  pr "  rv = caml_callbackN_exn (data->fnv, %d, args);\n"
    (List.length argnames);

  List.iter (
    function
    | CBArrayAndLen (UInt32 _, _)
    | CBBytesIn _
    | CBInt _
    | CBInt64 _
    | CBString _
    | CBUInt _
    | CBUInt64 _ -> ()
    | CBMutable (Int n) ->
       pr "  *%s = Int_val (Field (%sv, 0));\n" n n
    | CBArrayAndLen _ | CBMutable _ -> assert false
  ) cbargs;

  pr "  if (Is_exception_result (rv)) {\n";
  pr "    nbd_internal_ocaml_exception_in_wrapper (\"%s\", rv);\n" cbname;
  pr "    CAMLreturnT (int, -1);\n";
  pr "  }\n";

  pr "\n";
  pr "  r = Int_val (rv);\n";
  pr "  assert (r >= 0);\n";
  pr "  CAMLreturnT (int, r);\n";
  pr "}\n";
  pr "\n";
  pr "static int\n";
  pr "%s_wrapper " cbname;
  C.print_cbarg_list ~wrap:true cbargs;
  pr "\n";
  pr "{\n";
  pr "  int ret = 0;\n";
  pr "\n";
  pr "  caml_leave_blocking_section ();\n";
  pr "  ret = %s_wrapper_locked " cbname;
  C.print_cbarg_list ~wrap:true ~types:false cbargs;
  pr ";\n";
  pr "  caml_enter_blocking_section ();\n";
  pr "  return ret;\n";
  pr "}\n";
  pr "\n"

let print_ocaml_binding (name, { args; optargs; ret }) =
  (* Get the names of all the value arguments including the handle. *)
  let values =
    List.map ocaml_name_of_optarg optargs @ ["h"] @
      List.map ocaml_name_of_arg args in
  let values = List.map (fun v -> v ^ "v") values in

  (* Create the binding. *)
  pr "value\n";
  let params = List.map (sprintf "value %s") values in
  let params = String.concat ", " params in
  pr "nbd_internal_ocaml_nbd_%s (" name;
  pr_wrap ',' (fun () -> pr "%s" params);
  pr ")\n";
  pr "{\n";
  (* CAMLparam<N> can only take up to 5 parameters.  Further parameters
   * have to be passed in groups of 5 to CAMLxparam<N> calls.
   *)
  (match values with
   | p1 :: p2 :: p3 :: p4 :: p5 :: rest ->
      pr "  CAMLparam5 (%s);\n" (String.concat ", " [p1; p2; p3; p4; p5]);
      let rec loop = function
        | [] -> ()
        | p1 :: p2 :: p3 :: p4 :: p5 :: rest ->
           pr "  CAMLxparam5 (%s);\n"
              (String.concat ", " [p1; p2; p3; p4; p5]);
           loop rest
        | rest ->
           pr "  CAMLxparam%d (%s);\n"
              (List.length rest) (String.concat ", " rest)
      in
      loop rest
   | ps ->
      pr "  CAMLparam%d (%s);\n" (List.length ps) (String.concat ", " ps)
  );
  pr "  CAMLlocal1 (rv);\n";
  pr "\n";

  pr "  struct nbd_handle *h = NBD_val (hv);\n";
  pr "  if (h == NULL)\n";
  pr "    nbd_internal_ocaml_raise_closed (\"NBD.%s\");\n" name;
  pr "\n";

  List.iter (
    function
    | OClosure { cbname } ->
       pr "  nbd_%s_callback %s_callback = {0};\n" cbname cbname;
       pr "  struct user_data *%s_user_data = alloc_user_data ();\n" cbname;
       pr "  if (%sv != Val_int (0)) { /* Some closure */\n" cbname;
       pr "    /* The function may save a reference to the closure, so we\n";
       pr "     * must treat it as a possible GC root.\n";
       pr "     */\n";
       pr "    %s_user_data->fnv = Field (%sv, 0);\n" cbname cbname;
       pr "    caml_register_generational_global_root (&%s_user_data->fnv);\n"
         cbname;
       pr "    %s_callback.callback = %s_wrapper;\n" cbname cbname;
       pr "  }\n";
       pr "  %s_callback.user_data = %s_user_data;\n" cbname cbname;
       pr "  %s_callback.free = free_user_data;\n" cbname;
    | OFlags (n, { flag_prefix }) ->
       pr "  uint32_t %s;\n" n;
       pr "  if (%sv != Val_int (0)) /* Some [ list of %s.t ] */\n"
         n flag_prefix;
       pr "    %s = %s_val (Field (%sv, 0));\n" n flag_prefix n;
       pr "  else /* None */\n";
       pr "    %s = 0;\n" n
  ) optargs;

  List.iter (
    function
    | Bool n ->
       pr "  bool %s = Bool_val (%sv);\n" n n
    | BytesIn (n, count) ->
       pr "  const void *%s = Bytes_val (%sv);\n" n n;
       pr "  size_t %s = caml_string_length (%sv);\n" count n
    | BytesPersistIn (n, count) ->
       pr "  struct nbd_buffer *%s_buf = NBD_buffer_val (%sv);\n" n n;
       pr "  const void *%s = %s_buf->data;\n" n n;
       pr "  size_t %s = %s_buf->len;\n" count n
    | BytesOut (n, count) ->
       pr "  void *%s = Bytes_val (%sv);\n" n n;
       pr "  size_t %s = caml_string_length (%sv);\n" count n
    | BytesPersistOut (n, count) ->
       pr "  struct nbd_buffer *%s_buf = NBD_buffer_val (%sv);\n" n n;
       pr "  void *%s = %s_buf->data;\n" n n;
       pr "  size_t %s = %s_buf->len;\n" count n
    | Closure { cbname } ->
       pr "  nbd_%s_callback %s_callback;\n" cbname cbname;
       pr "  struct user_data *%s_user_data = alloc_user_data ();\n" cbname;
       pr "  /* The function may save a reference to the closure, so we\n";
       pr "   * must treat it as a possible GC root.\n";
       pr "   */\n";
       pr "  %s_user_data->fnv = %sv;\n" cbname cbname;
       pr "  caml_register_generational_global_root (&%s_user_data->fnv);\n"
         cbname;
       pr "  %s_callback.callback = %s_wrapper;\n" cbname cbname;
       pr "  %s_callback.user_data = %s_user_data;\n" cbname cbname;
       pr "  %s_callback.free = free_user_data;\n" cbname
    | Enum (n, { enum_prefix }) ->
       pr "  int %s = %s_val (%sv);\n" n enum_prefix n
    | Fd n ->
       pr "  /* OCaml Unix.file_descr is just an int, at least on Unix. */\n";
       pr "  int %s = Int_val (%sv);\n" n n
    | Flags (n, { flag_prefix }) ->
       pr "  uint32_t %s = %s_val (%sv);\n" n flag_prefix n
    | Int n ->
       pr "  int %s = Int_val (%sv);\n" n n
    | Int64 n ->
       pr "  int64_t %s = Int64_val (%sv);\n" n n
    | Path n | String n ->
       pr "  const char *%s = String_val (%sv);\n" n n
    | SockAddrAndLen (n, len) ->
       pr "  const struct sockaddr *%s;\n" n;
       pr "  socklen_t %s;\n" len;
       pr "  abort ();\n" (* XXX *)
    | StringList n ->
       pr "  char **%s = (char **) nbd_internal_ocaml_string_list (%sv);\n" n n
    | UInt n ->
       pr "  unsigned %s = Int_val (%sv);\n" n n
    | UInt32 n ->
       pr "  uint32_t %s = Int32_val (%sv);\n" n n
    | UInt64 n ->
       pr "  uint64_t %s = Int64_val (%sv);\n" n n
  ) args;

  (* If there is a BytesPersistIn/Out parameter then we need to
   * register it as a global root and save that into the
   * completion_callback.user_data so the root is removed on
   * command completion.
   *)
  List.iter (
    function
    | BytesPersistIn (n, _) | BytesPersistOut (n, _) ->
       pr "  completion_user_data->bufv = %sv;\n" n;
       pr "  caml_register_generational_global_root (&completion_user_data->bufv);\n"
    | _ -> ()
  ) args;

  let ret_c_type = C.type_of_ret ret and errcode = C.errcode_of_ret ret in
  pr "  %s r;\n" ret_c_type;
  pr "\n";
  pr "  caml_enter_blocking_section ();\n";
  pr "  r =  nbd_%s " name;
  C.print_arg_list ~wrap:true ~handle:true ~types:false args optargs;
  pr ";\n";
  pr "  caml_leave_blocking_section ();\n";
  pr "\n";
  (match errcode with
   | Some code ->
      pr "  if (r == %s)\n" code;
      pr "    nbd_internal_ocaml_raise_error ();\n";
      pr "\n"
   | None -> ()
  );
  (match ret with
   | RBool -> pr "  rv = Val_bool (r);\n"
   | RErr -> pr "  rv = Val_unit;\n"
   | RFd | RInt | RUInt -> pr "  rv = Val_int (r);\n"
   | RInt64 | RCookie -> pr "  rv = caml_copy_int64 (r);\n"
   | RStaticString -> pr "  rv = caml_copy_string (r);\n"
   | RString ->
      pr "  rv = caml_copy_string (r);\n";
      pr "  free (r);\n"
  );

  (* Any parameters which need to be freed. *)
  List.iter (
    function
    | StringList n -> pr "  free (%s);\n" n
    | Bool _
    | BytesIn _
    | BytesPersistIn _
    | BytesOut _
    | BytesPersistOut _
    | Closure _
    | Enum _
    | Fd _
    | Flags _
    | Int _
    | Int64 _
    | Path _
    | String _
    | SockAddrAndLen _
    | UInt _
    | UInt32 _
    | UInt64 _ -> ()
  ) args;

  pr "  CAMLreturn (rv);\n";
  pr "}\n";
  pr "\n";

  if num_params args optargs > 5 then (
    pr "/* Byte-code compat function because this method has > 5 parameters.\n";
    pr " */\n";
    pr "value\n";
    pr "nbd_internal_ocaml_nbd_%s_byte (value *argv, int argn)\n" name;
    pr "{\n";
    pr "  return nbd_internal_ocaml_nbd_%s (" name;
    for i = 0 to num_params args optargs - 1 do
      if i > 0 then pr ", ";
      pr "argv[%d]" i
    done;
    pr ");\n";
    pr "}\n";
    pr "\n"
  )

let generate_ocaml_nbd_c () =
  generate_header CStyle;

  pr "#include <config.h>\n";
  pr "\n";
  pr "#include <stdio.h>\n";
  pr "#include <stdlib.h>\n";
  pr "#include <string.h>\n";
  pr "#include <assert.h>\n";
  pr "\n";
  pr "#include <libnbd.h>\n";
  pr "\n";
  pr "#include \"nbd-c.h\"\n";
  pr "\n";
  pr "#include <caml/alloc.h>\n";
  pr "#include <caml/callback.h>\n";
  pr "#include <caml/fail.h>\n";
  pr "#include <caml/memory.h>\n";
  pr "#include <caml/mlvalues.h>\n";
  pr "#include <caml/threads.h>\n";
  pr "\n";
  pr "#pragma GCC diagnostic ignored \"-Wmissing-prototypes\"\n";
  pr "\n";

  pr "/* This is passed to *_wrapper as the user_data pointer\n";
  pr " * and freed in the free_user_data function below.\n";
  pr " */\n";
  pr "struct user_data {\n";
  pr "  value fnv;     /* Optional GC root pointing to OCaml function. */\n";
  pr "  value bufv;    /* Optional GC root pointing to persistent buffer. */\n";
  pr "};\n";
  pr "\n";
  pr "static struct user_data *\n";
  pr "alloc_user_data (void)\n";
  pr "{\n";
  pr "  struct user_data *data = calloc (1, sizeof *data);\n";
  pr "  if (data == NULL)\n";
  pr "    caml_raise_out_of_memory ();\n";
  pr "  return data;\n";
  pr "}\n";
  pr "\n";
  pr "static void\n";
  pr "free_user_data (void *user_data)\n";
  pr "{\n";
  pr "  struct user_data *data = user_data;\n";
  pr "\n";
  pr "  if (data->fnv != 0)\n";
  pr "    caml_remove_generational_global_root (&data->fnv);\n";
  pr "  if (data->bufv != 0)\n";
  pr "    caml_remove_generational_global_root (&data->bufv);\n";
  pr "  free (data);\n";
  pr "}\n";
  pr "\n";

  List.iter print_ocaml_closure_wrapper all_closures;
  List.iter print_ocaml_enum_val all_enums;
  List.iter print_ocaml_flag_val all_flags;
  List.iter print_ocaml_binding handle_calls
end

(*----------------------------------------------------------------------*)

(* Write the output files. *)
let () =
  output_to "lib/states.h" StateMachine.generate_lib_states_h;
  output_to "lib/states.c" StateMachine.generate_lib_states_c;
  output_to "lib/states-run.c" StateMachine.generate_lib_states_run_c;
  output_to "lib/libnbd.syms" C.generate_lib_libnbd_syms;
  output_to "include/libnbd.h" C.generate_include_libnbd_h;
  output_to "lib/unlocked.h" C.generate_lib_unlocked_h;
  output_to "lib/api.c" C.generate_lib_api_c;
  output_to "docs/Makefile.inc" C.generate_docs_Makefile_inc;
  output_to "docs/api-links.pod" C.generate_docs_api_links_pod;
  output_to "docs/api-flag-links.pod" C.generate_docs_api_flag_links_pod;
  List.iter (
    fun (name, call) ->
      output_to (sprintf "docs/nbd_%s.pod" name)
                (C.generate_docs_nbd_pod name call)
  ) handle_calls;
  output_to "python/methods.h" Python.generate_python_methods_h;
  output_to "python/libnbdmod.c" Python.generate_python_libnbdmod_c;
  output_to "python/methods.c" Python.generate_python_methods_c;
  output_to "python/nbd.py" Python.generate_python_nbd_py;
  output_to "ocaml/NBD.mli" OCaml.generate_ocaml_nbd_mli;
  output_to "ocaml/NBD.ml" OCaml.generate_ocaml_nbd_ml;
  output_to "ocaml/nbd-c.c" OCaml.generate_ocaml_nbd_c;
