door_macros/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5// Copyright 2023 Oxide Computer Company
6// Copyright 2023 Robert D. French
7
8//! This crate contains a single macro [`macro@server_procedure`] for transforming a rust
9//! function into a server procedure.
10
11use proc_macro::TokenStream;
12use quote::{format_ident, quote, ToTokens};
13use syn::spanned::Spanned;
14use syn::{parse_macro_input, Error, FnArg, ItemFn, Pat, ReturnType};
15
16/// This macro transforms a Rust function into a Doors-compatible server
17/// procedure.
18///
19/// This makes it easier to write functions which accept and return
20/// Rust-friendlier types.
21///
22/// ```
23/// use doors::server::Request;
24/// use doors::server::Response;
25///
26/// #[doors::server_procedure]
27/// fn serv_proc(x: Request<'_>) -> Response<[u8; 1]> {
28///     todo!();
29/// }
30/// ```
31#[proc_macro_attribute]
32pub fn server_procedure(_attr: TokenStream, item: TokenStream) -> TokenStream {
33    // parse the function this attribute was applied to
34    let input = parse_macro_input!(item as ItemFn);
35
36    // extract the function name
37    let name = format_ident!("{}", input.sig.ident.to_string());
38
39    // check number of arguments, we only support a single argument
40    if input.sig.inputs.len() != 1 {
41        return Error::new(
42            input.sig.inputs.span(),
43            "doors should take a single Request as input",
44        )
45        .to_compile_error()
46        .into();
47    }
48
49    // extract the single argument and it's type
50    let arg = &input.sig.inputs[0];
51    let (arg_ident, arg_type) = match arg {
52        FnArg::Receiver(_) => {
53            return Error::new(
54                arg.span(),
55                "only standalone functions supported",
56            )
57            .to_compile_error()
58            .into();
59        }
60
61        FnArg::Typed(pt) => {
62            let p = match &*pt.pat {
63                Pat::Ident(i) => i.ident.to_string(),
64
65                _ => {
66                    return Error::new(
67                        arg.span(),
68                        "only identifier arguments supported",
69                    )
70                    .to_compile_error()
71                    .into()
72                }
73            };
74            (format_ident!("{}", p), *pt.ty.clone())
75        }
76    };
77
78    //extract the return type
79    let return_type = match input.sig.output {
80        ReturnType::Default => ReturnType::Default.to_token_stream(),
81        ReturnType::Type(_, t) => (*t).to_token_stream(),
82    };
83
84    // extract the body of the function
85    let blk = input.block;
86
87    // generate the output function
88    let q = quote! {
89
90        extern "C" fn #name(
91            cookie: *const std::os::raw::c_void,
92            argp: *const std::os::raw::c_char,
93            arg_size: usize,
94            dp: *const doors::illumos::door_h::door_desc_t,
95            n_desc: std::os::raw::c_uint,
96         ) {
97
98            let f = || -> #return_type {
99                let #arg_ident: #arg_type = doors::server::Request {
100                    data: unsafe {
101                        std::slice::from_raw_parts::<u8>(
102                            argp as *const u8,
103                            arg_size
104                        )
105                    },
106                    descriptors: unsafe {
107                        std::slice::from_raw_parts(
108                            dp,
109                            n_desc.try_into().unwrap()
110                        )
111                    },
112                    cookie: cookie as u64
113                };
114                #blk
115            };
116
117            let mut response = f();
118            match response.data {
119                Some(data) => unsafe {
120                    doors::illumos::door_h::door_return(
121                        data.as_ref().as_ptr() as *const std::os::raw::c_char,
122                        data.as_ref().len(),
123                        response.descriptors.as_ptr(),
124                        response.num_descriptors,
125                    )
126                },
127                None => unsafe {
128                    doors::illumos::door_h::door_return(
129                        std::ptr::null() as *const std::os::raw::c_char,
130                        0,
131                        response.descriptors.as_ptr(),
132                        response.num_descriptors,
133                    )
134                }
135            }
136
137        }
138
139    };
140
141    TokenStream::from(q)
142}