Files
libguestfs/generator/rust.ml
Richard W.M. Jones 0ff73a42c7 generator: Implement struct FDevice type
This acts just like FString except that we do reverse device name
translation on it.  The only use is in the 'pvs-full' API where we
will use it (in a subsequent commit) to reverse translate the pv_name
field (a device name) before returning it from the daemon.

Compare this to the 'pvs' API which also returns a list of device
names, but using the generator's 'RStructList (RDevice,...)'  return
type, where RDevice is similarly reverse translated.

Note in the library-side bindings, because the name has already been
translated in the daemon, we just treat it exactly the same as
FString.  The vast majority of this patch is this mechanical change.
2025-04-16 12:27:07 +01:00

595 lines
20 KiB
OCaml

(* libguestfs
* Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(* Please read generator/README first. *)
open Std_utils
open Types
open Utils
open Pr
open Docstrings
open Optgroups
open Actions
open Structs
open C
open Events
let copyrights = ["Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com>"]
(* Utilities for Rust *)
(* Are there corresponding functions to them? *)
(* Should they be placed in utils.ml? *)
let rec indent = function
| x when x > 0 -> pr " "; indent (x - 1)
| _ -> ()
let snake2caml name =
let l = String.nsplit "_" name in
let l = List.map String.capitalize_ascii l in
String.concat "" l
(* because there is a function which contains 'unsafe' field *)
let black_list = ["unsafe"]
let translate_bad_symbols s =
if List.mem s black_list then
s ^ "_"
else
s
let generate_rust () =
generate_header ~copyrights CStyle LGPLv2plus;
pr "
use crate::base::*;
use crate::utils::*;
use crate::error::*;
use std::collections;
use std::convert;
use std::convert::TryFrom;
use std::ffi;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::slice;
extern \"C\" {
fn free(buf: *const c_void);
}
";
(* event enum *)
pr "\n";
pr "pub enum Event {\n";
List.iter (
fun (name, _) ->
pr " %s,\n" (snake2caml name)
) events;
pr "}\n\n";
pr "impl Event {\n";
pr " pub fn to_u64(&self) -> u64 {\n";
pr " match self {\n";
List.iter (
fun (name, i) ->
pr " Event::%s => %d,\n" (snake2caml name) i
) events;
pr " }\n";
pr " }\n";
pr " pub fn from_bitmask(bitmask: u64) -> Option<Event> {\n";
pr " match bitmask {\n";
List.iter (
fun (name, i) ->
pr " %d => Some(Event::%s),\n" i (snake2caml name)
) events;
pr " _ => None,\n";
pr " }\n";
pr " }\n";
pr "}\n\n";
pr "pub const EVENT_ALL: [Event; %d] = [\n" (List.length events);
List.iter (
fun (name, _) ->
pr " Event::%s,\n" (snake2caml name)
) events;
pr "];\n";
List.iter (
fun { s_camel_name = name; s_name = c_name; s_cols = cols } ->
pr "\n";
pr "pub struct %s {\n" name;
List.iter (
function
| n, FChar -> pr " pub %s: i8,\n" n
| n, (FString|FDevice) -> pr " pub %s: String,\n" n
| n, FBuffer -> pr " pub %s: Vec<u8>,\n" n
| n, FUInt32 -> pr " pub %s: u32,\n" n
| n, FInt32 -> pr " pub %s: i32,\n" n
| n, (FUInt64 | FBytes) -> pr " pub %s: u64,\n" n
| n, FInt64 -> pr " pub %s: i64,\n" n
| n, FUUID -> pr " pub %s: UUID,\n" n
| n, FOptPercent -> pr " pub %s: Option<f32>,\n" n
) cols;
pr "}\n";
pr "#[repr(C)]\n";
pr "struct Raw%s {\n" name;
List.iter (
function
| n, FChar -> pr " %s: c_char,\n" n
| n, (FString|FDevice) -> pr " %s: *const c_char,\n" n
| n, FBuffer ->
pr " %s_len: usize,\n" n;
pr " %s: *const c_char,\n" n;
| n, FUUID -> pr " %s: [u8; 32],\n" n
| n, FUInt32 -> pr " %s: u32,\n" n
| n, FInt32 -> pr " %s: i32,\n" n
| n, (FUInt64 | FBytes) -> pr " %s: u64,\n" n
| n, FInt64 -> pr " %s: i64,\n" n
| n, FOptPercent -> pr " %s: f32,\n" n
) cols;
pr "}\n";
pr "\n";
pr "impl TryFrom<*const Raw%s> for %s {\n" name name;
pr " type Error = Error;\n";
pr " fn try_from(raw: *const Raw%s) -> Result<Self, Self::Error> {\n" name;
pr " Ok(unsafe {\n";
pr " %s {\n" name;
List.iter (
fun x ->
indent 4;
match x with
| n, FChar ->
pr "%s: (*raw).%s as i8,\n" n n;
| n, (FString|FDevice) ->
pr "%s: char_ptr_to_string((*raw).%s)?,\n" n n;
| n, FBuffer ->
pr "%s: slice::from_raw_parts((*raw).%s as *const u8, (*raw).%s_len).to_vec(),\n" n n n
| n, FUUID ->
pr "%s: UUID::new((*raw).%s),\n" n n
| n, (FUInt32 | FInt32 | FUInt64 | FInt64 | FBytes) ->
pr "%s: (*raw).%s,\n" n n
| n, FOptPercent ->
pr "%s: if (*raw).%s < 0.0 {\n" n n;
indent 4; pr " None\n";
indent 4; pr "} else {\n";
indent 4; pr " Some((*raw).%s)\n" n;
indent 4; pr"},\n"
) cols;
pr " }\n";
pr " })\n";
pr " }\n";
pr "}\n"
) external_structs;
(* generate free functionf of structs *)
pr "\n";
pr "extern \"C\" {\n";
List.iter (
fun { s_camel_name = name; s_name = c_name; } ->
pr " #[allow(dead_code)]\n";
pr " fn guestfs_free_%s(v: *const Raw%s);\n" c_name name;
pr " #[allow(dead_code)]\n";
pr " fn guestfs_free_%s_list(l: *const RawList<Raw%s>);\n" c_name name;
) external_structs;
pr "}\n";
(* [Outline] There are three types for each optional structs: SOptArgs,
* CExprSOptArgs, RawSOptArgs.
* SOptArgs: for Rust bindings' API. This can be seen by bindings' users.
* CExprSOptArgs: Each field has C expression(e.g. CString, *const c_char)
* RawSOptArgs: Each field has raw pointers or integer values
*
* SOptArgs ---try_into()---> CExprSOptArgs ---to_raw()---> RawSOptArgs
*
* Note: direct translation from SOptArgs to RawSOptArgs will cause a memory
* management problem. Using into_raw/from_raw, this problem can be avoided,
* but it is complex to handle freeing memories manually in Rust because of
* panic/?/etc.
*)
(* generate structs for optional arguments *)
List.iter (
fun ({ name = name; shortdesc = shortdesc;
style = (ret, args, optargs) }) ->
let cname = snake2caml name in
let contains_ptr =
List.exists (
function
| OString _
| OStringList _ -> true
| _ -> false
)
in
let opt_life_parameter = if contains_ptr optargs then "<'a>" else "" in
if optargs <> [] then (
pr "\n";
pr "/* Optional Structs */\n";
pr "#[derive(Default)]\n";
pr "pub struct %sOptArgs%s {\n" cname opt_life_parameter;
List.iter (
fun optarg ->
let n = translate_bad_symbols (name_of_optargt optarg) in
match optarg with
| OBool _ ->
pr " pub %s: Option<bool>,\n" n
| OInt _ ->
pr " pub %s: Option<i32>,\n" n
| OInt64 _ ->
pr " pub %s: Option<i64>,\n" n
| OString _ ->
pr " pub %s: Option<&'a str>,\n" n
| OStringList _ ->
pr " pub %s: Option<&'a [&'a str]>,\n" n
) optargs;
pr "}\n\n";
pr "struct CExpr%sOptArgs {\n" cname;
List.iter (
fun optarg ->
let n = translate_bad_symbols (name_of_optargt optarg) in
match optarg with
| OBool _ | OInt _ ->
pr " %s: Option<c_int>,\n" n
| OInt64 _ ->
pr " %s: Option<i64>,\n" n
| OString _ ->
pr " %s: Option<ffi::CString>,\n" n
| OStringList _ ->
(* buffers and their pointer vector *)
pr " %s: Option<(Vec<ffi::CString>, Vec<*const c_char>)>,\n" n
) optargs;
pr "}\n\n";
pr "impl%s TryFrom<%sOptArgs%s> for CExpr%sOptArgs {\n"
opt_life_parameter cname opt_life_parameter cname;
pr " type Error = Error;\n";
pr " fn try_from(optargs: %sOptArgs%s) -> Result<Self, Self::Error> {\n" cname opt_life_parameter;
pr " Ok(CExpr%sOptArgs {\n" cname;
List.iteri (
fun index optarg ->
let n = translate_bad_symbols (name_of_optargt optarg) in
match optarg with
| OBool _ ->
pr " %s: optargs.%s.map(|b| if b { 1 } else { 0 }),\n" n n;
| OInt _ | OInt64 _ ->
pr " %s: optargs.%s, \n" n n;
| OString _ ->
pr " %s: optargs.%s.map(|v| ffi::CString::new(v)).transpose()?,\n" n n;
| OStringList _ ->
pr " %s: optargs.%s.map(\n" n n;
pr " |v| Ok::<_, Error>({\n";
pr " let v = arg_string_list(v)?;\n";
pr " let mut w = (&v).into_iter()\n";
pr " .map(|v| v.as_ptr())\n";
pr " .collect::<Vec<_>>();\n";
pr " w.push(ptr::null());\n";
pr " (v, w)\n";
pr " })\n";
pr " ).transpose()?,\n";
) optargs;
pr " })\n";
pr " }\n";
pr "}\n";
(* raw struct for C bindings *)
pr "#[repr(C)]\n";
pr "struct Raw%sOptArgs {\n" cname;
pr " bitmask: u64,\n";
List.iter (
fun optarg ->
let n = translate_bad_symbols (name_of_optargt optarg) in
match optarg with
| OBool _ ->
pr " %s: c_int,\n" n
| OInt _ ->
pr " %s: c_int,\n" n
| OInt64 _ ->
pr " %s: i64,\n" n
| OString _ ->
pr " %s: *const c_char,\n" n
| OStringList _ ->
pr " %s: *const *const c_char,\n" n
) optargs;
pr "}\n\n";
pr "impl convert::From<&CExpr%sOptArgs> for Raw%sOptArgs {\n"
cname cname;
pr " fn from(optargs: &CExpr%sOptArgs) -> Self {\n" cname;
pr " let mut bitmask = 0;\n";
pr " Raw%sOptArgs {\n" cname;
List.iteri (
fun index optarg ->
let n = translate_bad_symbols (name_of_optargt optarg) in
match optarg with
| OBool _ | OInt _ | OInt64 _ ->
pr " %s: if let Some(v) = optargs.%s {\n" n n;
pr " bitmask |= 1 << %d;\n" index;
pr " v\n";
pr " } else {\n";
pr " 0\n";
pr " },\n";
| OString _ ->
pr " %s: if let Some(ref v) = optargs.%s {\n" n n;
pr " bitmask |= 1 << %d;\n" index;
pr " v.as_ptr()\n";
pr " } else {\n";
pr " ptr::null()\n";
pr " },\n";
| OStringList _ ->
pr " %s: if let Some((_, ref v)) = optargs.%s {\n" n n;
pr " bitmask |= 1 << %d;\n" index;
pr " v.as_ptr()\n";
pr " } else {\n";
pr " ptr::null()\n";
pr " },\n";
) optargs;
pr " bitmask,\n";
pr " }\n";
pr " }\n";
pr "}\n";
);
) (actions |> external_functions |> sort);
(* extern C APIs *)
pr "extern \"C\" {\n";
List.iter (
fun ({ name = name; shortdesc = shortdesc;
style = (ret, args, optargs) } as f) ->
let cname = snake2caml name in
pr " #[allow(non_snake_case)]\n";
pr " fn %s(g: *const guestfs_h" f.c_function;
List.iter (
fun arg ->
pr ", ";
match arg with
| Bool n -> pr "%s: c_int" n
| String (_, n) -> pr "%s: *const c_char" n
| OptString n -> pr "%s: *const c_char" n
| Int n -> pr "%s: c_int" n
| Int64 n -> pr "%s: i64" n
| Pointer (_, n) -> pr "%s: *const ffi::c_void" n
| StringList (_, n) -> pr "%s: *const *const c_char" n
| BufferIn n -> pr "%s: *const c_char, %s_len: usize" n n
) args;
(match ret with
| RBufferOut _ -> pr ", size: *const usize"
| _ -> ()
);
if optargs <> [] then
pr ", optarg: *const Raw%sOptArgs" cname;
pr ") -> ";
(match ret with
| RErr | RInt _ | RBool _ -> pr "c_int"
| RInt64 _ -> pr "i64"
| RConstString _ | RString _ | RConstOptString _ -> pr "*const c_char"
| RBufferOut _ -> pr "*const u8"
| RStringList _ | RHashtable _-> pr "*const *const c_char"
| RStruct (_, n) ->
let n = camel_name_of_struct n in
pr "*const Raw%s" n
| RStructList (_, n) ->
let n = camel_name_of_struct n in
pr "*const RawList<Raw%s>" n
);
pr ";\n";
) (actions |> external_functions |> sort);
pr "}\n";
pr "impl<'a> Handle<'a> {\n";
List.iter (
fun ({ name = name; shortdesc = shortdesc; longdesc = longdesc;
style = (ret, args, optargs) } as f) ->
let cname = snake2caml name in
pr " /// %s\n" shortdesc;
pr " #[allow(non_snake_case)]\n";
pr " pub fn %s" name;
(* generate arguments *)
pr "(&self, ";
let comma = ref false in
List.iter (
fun arg ->
if !comma then pr ", ";
comma := true;
match arg with
| Bool n -> pr "%s: bool" n
| Int n -> pr "%s: i32" n
| Int64 n -> pr "%s: i64" n
| String (_, n) -> pr "%s: &str" n
| OptString n -> pr "%s: Option<&str>" n
| StringList (_, n) -> pr "%s: &[&str]" n
| BufferIn n -> pr "%s: &[u8]" n
| Pointer (_, n) -> pr "%s: *mut c_void" n
) args;
if optargs <> [] then (
if !comma then pr ", ";
comma := true;
pr "optargs: %sOptArgs" cname
);
pr ")";
(* generate return type *)
pr " -> Result<";
(match ret with
| RErr -> pr "()"
| RInt _ -> pr "i32"
| RInt64 _ -> pr "i64"
| RBool _ -> pr "bool"
| RConstString _ -> pr "&'static str"
| RString _ -> pr "String"
| RConstOptString _ -> pr "Option<&'static str>"
| RStringList _ -> pr "Vec<String>"
| RStruct (_, sn) ->
let sn = camel_name_of_struct sn in
pr "%s" sn
| RStructList (_, sn) ->
let sn = camel_name_of_struct sn in
pr "Vec<%s>" sn
| RHashtable _ -> pr "collections::HashMap<String, String>"
| RBufferOut _ -> pr "Vec<u8>");
pr ", Error> {\n";
let _pr = pr in
let pr fs = indent 2; pr fs in
List.iter (
function
| Bool n ->
pr "let %s = if %s { 1 } else { 0 };\n" n n
| String (_, n) ->
pr "let c_%s = ffi::CString::new(%s)?;\n" n n;
| OptString n ->
pr "let c_%s = %s.map(|s| ffi::CString::new(s)).transpose()?;\n" n n;
| StringList (_, n) ->
pr "let c_%s_v = arg_string_list(%s)?;\n" n n;
pr "let mut c_%s = (&c_%s_v).into_iter().map(|v| v.as_ptr()).collect::<Vec<_>>();\n" n n;
pr "c_%s.push(ptr::null());\n" n;
| BufferIn n ->
pr "let c_%s_len = %s.len();\n" n n;
pr "let c_%s = unsafe { ffi::CString::from_vec_unchecked(%s.to_vec())};\n" n n;
| Int _ | Int64 _ | Pointer _ -> ()
) args;
(match ret with
| RBufferOut _ ->
pr "let mut size = 0usize;\n"
| _ -> ()
);
if optargs <> [] then (
pr "let optargs_cexpr = CExpr%sOptArgs::try_from(optargs)?;\n" cname;
);
pr "\n";
pr "let r = unsafe { %s(self.g" f.c_function;
let pr = _pr in
List.iter (
fun arg ->
pr ", ";
match arg with
| String (_, n) -> pr "(&c_%s).as_ptr()" n
| OptString n -> pr "match &c_%s { Some(ref s) => s.as_ptr(), None => ptr::null() }\n" n
| StringList (_, n) -> pr "(&c_%s).as_ptr() as *const *const c_char" n
| Bool n | Int n | Int64 n | Pointer (_, n) -> pr "%s" n
| BufferIn n -> pr "(&c_%s).as_ptr(), c_%s_len" n n
) args;
(match ret with
| RBufferOut _ -> pr ", &mut size as *mut usize"
| _ -> ()
);
if optargs <> [] then (
pr ", &(Raw%sOptArgs::from(&optargs_cexpr)) as *const Raw%sOptArgs"
cname cname;
);
pr ") };\n";
let _pr = pr in
let pr fs = indent 2; pr fs in
(match errcode_of_ret ret with
| `CannotReturnError -> ()
| `ErrorIsMinusOne ->
pr "if r == -1 {\n";
pr " return Err(self.get_error_from_handle(\"%s\"));\n" name;
pr "}\n"
| `ErrorIsNULL ->
pr "if r.is_null() {\n";
pr " return Err(self.get_error_from_handle(\"%s\"));\n" name;
pr "}\n"
);
(* This part is not required, but type system will guarantee that
* the buffers are still alive. This is useful because Rust cannot
* know whether raw pointers used above are alive or not.
*)
List.iter (
function
| Bool _ | Int _ | Int64 _ | Pointer _ -> ()
| String (_, n)
| OptString n
| BufferIn n -> pr "drop(c_%s);\n" n;
| StringList (_, n) ->
pr "drop(c_%s);\n" n;
pr "drop(c_%s_v);\n" n;
) args;
if optargs <> [] then (
pr "drop(optargs_cexpr);\n";
);
pr "Ok(";
let pr = _pr in
let pr3 fs = indent 3; pr fs in
(match ret with
| RErr -> pr "()"
| RInt _ | RInt64 _ -> pr "r"
| RBool _ -> pr "r != 0"
| RConstString _ ->
pr "unsafe{ ffi::CStr::from_ptr(r) }.to_str()?"
| RString _ ->
pr "{\n";
pr3 "let s = unsafe { char_ptr_to_string(r) };\n";
pr3 "unsafe { free(r as *const c_void) };";
pr3 "s?\n";
indent 2; pr "}";
| RConstOptString _ ->
pr "if r.is_null() {\n";
pr3 "None\n";
indent 2; pr "} else {\n";
pr3 "Some(unsafe { ffi::CStr::from_ptr(r) }.to_str()?)\n";
indent 2; pr "}";
| RStringList _ ->
pr "{\n";
pr3 "let s = string_list(r);\n";
pr3 "free_string_list(r);\n";
pr3 "s?\n";
indent 2; pr "}";
| RStruct (_, n) ->
let sn = camel_name_of_struct n in
pr "{\n";
pr3 "let s = %s::try_from(r);\n" sn;
pr3 "unsafe { guestfs_free_%s(r) };\n" n;
pr3 "s?\n";
indent 2; pr "}";
| RStructList (_, n) ->
let sn = camel_name_of_struct n in
pr "{\n";
pr3 "let l = struct_list::<Raw%s, %s>(r);\n" sn sn;
pr3 "unsafe { guestfs_free_%s_list(r) };\n" n;
pr3 "l?\n";
indent 2; pr "}";
| RHashtable _ ->
pr "{\n";
pr3 "let h = hashmap(r);\n";
pr3 "free_string_list(r);\n";
pr3 "h?\n";
indent 2; pr "}";
| RBufferOut _ ->
pr "{\n";
pr3 "let s = unsafe { slice::from_raw_parts(r, size) }.to_vec();\n";
pr3 "unsafe { free(r as *const c_void) } ;\n";
pr3 "s\n";
indent 2; pr "}";
);
pr ")\n";
pr " }\n\n"
) (actions |> external_functions |> sort);
pr "}\n"