uapi_proc/
lib.rs

1extern crate proc_macro;
2
3use lazy_static::lazy_static;
4use proc_macro2::{Punct, TokenStream};
5use quote::quote;
6use regex::Regex;
7use syn::{
8    parenthesized,
9    parse::{Parse, ParseStream},
10    parse_macro_input, Attribute, Ident, LitInt, LitStr, Path,
11};
12
13lazy_static! {
14    static ref TC: TestConditions = {
15        let mut tc = TestConditions {
16            root: unsafe { libc::geteuid() == 0 },
17            ..Default::default()
18        };
19
20        let mut utsname = unsafe { std::mem::zeroed() };
21        unsafe {
22            libc::uname(&mut utsname);
23        }
24
25        let regex = regex::bytes::Regex::new(r"^([0-9]+)\.([0-9]+)").unwrap();
26
27        if let Some(m) = regex.captures(unsafe {
28            &*(&utsname.release[..] as *const [libc::c_char] as *const [u8])
29        }) {
30            let parse = |i| {
31                std::str::from_utf8(m.get(i).unwrap().as_bytes())
32                    .unwrap()
33                    .parse()
34                    .unwrap()
35            };
36            let major: i32 = parse(1);
37            let minor: i32 = parse(2);
38
39            tc.linux_4_16 = major > 4 || (major == 4 && minor >= 16);
40            tc.linux_5_2 = major > 5 || (major == 5 && minor >= 2);
41            tc.linux_5_6 = major > 5 || (major == 5 && minor >= 6);
42            tc.linux_5_9 = major > 5 || (major == 5 && minor >= 9);
43            tc.linux_5_10 = major > 5 || (major == 5 && minor >= 10);
44        }
45
46        tc
47    };
48}
49
50#[derive(Default, Debug)]
51struct TestConditions {
52    root: bool,
53    linux_4_16: bool,
54    linux_5_2: bool,
55    linux_5_6: bool,
56    linux_5_9: bool,
57    linux_5_10: bool,
58}
59
60impl Parse for TestConditions {
61    fn parse(input: ParseStream) -> syn::Result<Self> {
62        let mut tc = TestConditions::default();
63
64        while !input.is_empty() {
65            let name = input.parse::<Ident>()?;
66            match &*name.to_string() {
67                "root" => tc.root = true,
68                "linux_4_16" => tc.linux_4_16 = true,
69                "linux_5_2" => tc.linux_5_2 = true,
70                "linux_5_6" => tc.linux_5_6 = true,
71                "linux_5_9" => tc.linux_5_9 = true,
72                "linux_5_10" => tc.linux_5_10 = true,
73                n => {
74                    return Err(syn::Error::new(
75                        name.span(),
76                        format!("unknown test condition {}", n),
77                    ));
78                }
79            }
80            if !input.is_empty() {
81                parse_comma(input)?;
82            }
83        }
84
85        Ok(tc)
86    }
87}
88
89#[proc_macro_attribute]
90pub fn test_if(
91    attr: proc_macro::TokenStream,
92    item: proc_macro::TokenStream,
93) -> proc_macro::TokenStream {
94    let tc = parse_macro_input!(attr as TestConditions);
95    let ignore = (tc.root && !TC.root)
96        || (tc.linux_4_16 && !TC.linux_4_16)
97        || (tc.linux_5_2 && !TC.linux_5_2)
98        || (tc.linux_5_6 && !TC.linux_5_6)
99        || (tc.linux_5_9 && !TC.linux_5_9)
100        || (tc.linux_5_10 && !TC.linux_5_10);
101    #[allow(clippy::match_bool)] // already disabled upstream
102    let ignore = match ignore {
103        false => quote!(),
104        true => quote!(#[ignore]),
105    };
106    let item = TokenStream::from(item);
107    quote!(
108        #[test]
109        #ignore
110        #item
111    )
112    .into()
113}
114
115#[proc_macro_attribute]
116pub fn notest(
117    _attr: proc_macro::TokenStream,
118    item: proc_macro::TokenStream,
119) -> proc_macro::TokenStream {
120    let item = TokenStream::from(item);
121    quote!(
122        #[deprecated = "there are no tests for this api"]
123        #item
124    )
125    .into()
126}
127
128#[proc_macro_attribute]
129pub fn beta(
130    _attr: proc_macro::TokenStream,
131    item: proc_macro::TokenStream,
132) -> proc_macro::TokenStream {
133    let item = TokenStream::from(item);
134    quote!(
135        #[deprecated = "beta features are subject to change at any time"]
136        #item
137    )
138    .into()
139}
140
141struct Man(String);
142
143impl Parse for Man {
144    fn parse(input: ParseStream) -> syn::Result<Self> {
145        lazy_static! {
146            static ref MAN_REG: Regex =
147                regex::Regex::new(r"([a-zA-Z]*)\((([0-9]+)([^)]*))\)").unwrap();
148        }
149
150        if input.peek(Ident) {
151            let name = input.parse::<Ident>()?;
152            let content;
153            parenthesized!(content in input);
154            let section = content.parse::<LitInt>()?;
155            let doc = format!(
156                "[`{0}({1})`](http://man7.org/linux/man-pages/man{1}/{0}.{1}.html)",
157                name, section
158            );
159            return Ok(Man(doc));
160        }
161        let doc = input.parse::<LitStr>()?.value();
162        let res = MAN_REG.replace_all(
163            &doc,
164            "[`$1($2)`](http://man7.org/linux/man-pages/man$3/$1.$2.html)",
165        );
166        Ok(Man(res.to_string()))
167    }
168}
169
170#[proc_macro_attribute]
171pub fn man(
172    attr: proc_macro::TokenStream,
173    item: proc_macro::TokenStream,
174) -> proc_macro::TokenStream {
175    let man = parse_macro_input!(attr as Man).0;
176    let item = TokenStream::from(item);
177    quote!(
178        #[doc = #man]
179        #item
180    )
181    .into()
182}
183
184struct SockaddrRequest {
185    attr: Vec<Attribute>,
186    get: bool,
187    set: bool,
188    level: Ident,
189    optname: Ident,
190    strct: Path,
191}
192
193fn parse_punct(input: ParseStream, chr: char) -> syn::Result<()> {
194    let punct = input.parse::<Punct>()?;
195    match punct.as_char() {
196        c if c == chr => Ok(()),
197        c => Err(syn::Error::new(
198            punct.span(),
199            format!("Expected '{}', got '{}'", chr, c),
200        )),
201    }
202}
203
204fn parse_comma(input: ParseStream) -> syn::Result<()> {
205    parse_punct(input, ',')
206}
207
208fn parse_equals(input: ParseStream) -> syn::Result<()> {
209    parse_punct(input, '=')
210}
211
212impl Parse for SockaddrRequest {
213    fn parse(input: ParseStream) -> syn::Result<Self> {
214        let attr = syn::Attribute::parse_outer(input)?;
215        let directions = input.parse::<Ident>()?;
216        parse_comma(input)?;
217        let level = input.parse::<Ident>()?;
218        parse_comma(input)?;
219        let optname = input.parse::<Ident>()?;
220        let mut strct = syn::parse_str("c::c_int").unwrap();
221        while !input.is_empty() {
222            parse_comma(input)?;
223            let name = input.parse::<Ident>()?;
224            parse_equals(input)?;
225            match &*name.to_string() {
226                "ty" => {
227                    strct = input.parse::<syn::Path>()?;
228                }
229                n => {
230                    return Err(syn::Error::new(
231                        name.span(),
232                        format!("unknown named parameter {}", n),
233                    ));
234                }
235            }
236        }
237        let (get, set) = match &*directions.to_string() {
238            "get" => (true, false),
239            "set" => (false, true),
240            "bi" => (true, true),
241            o => {
242                return Err(syn::Error::new(
243                    directions.span(),
244                    format!("Expected get/set/bi, got {}", o),
245                ))
246            }
247        };
248        Ok(Self {
249            attr,
250            get,
251            set,
252            level,
253            optname,
254            strct,
255        })
256    }
257}
258
259#[proc_macro]
260pub fn sock_opt(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
261    let SockaddrRequest {
262        attr,
263        get,
264        set,
265        level,
266        optname,
267        strct,
268    } = parse_macro_input!(item as SockaddrRequest);
269    let mut ts = TokenStream::new();
270    if get {
271        let man = format!(
272            "getsockopt(2) with level = `{}` and optname = `{}`",
273            level.to_string(),
274            optname.to_string()
275        );
276        let fnname = Ident::new(
277            &format!("getsockopt_{}", optname.to_string().to_lowercase()),
278            optname.span(),
279        );
280        ts.extend(quote!(
281            #[man(#man)]
282            #(#attr)*
283            pub fn #fnname(sockfd: c::c_int) -> Result<#strct> {
284                let mut val: #strct = unsafe { mem::zeroed() };
285                let mut len = mem::size_of::<#strct>() as _;
286                let res = unsafe { c::getsockopt(sockfd, c::#level, c::#optname, &mut val as *mut _ as *mut _, &mut len) };
287                map_err!(res).map(|_| val)
288            }
289        ));
290    }
291    if set {
292        let man = format!(
293            "setsockopt(2) with level = `{}` and optname = `{}`",
294            level.to_string(),
295            optname.to_string()
296        );
297        let fnname = Ident::new(
298            &format!("setsockopt_{}", optname.to_string().to_lowercase()),
299            optname.span(),
300        );
301        ts.extend(quote!(
302            #[man(#man)]
303            #(#attr)*
304            pub fn #fnname(sockfd: c::c_int, mut val: #strct) -> Result<()> {
305                let mut len = mem::size_of::<#strct>() as _;
306                let res = unsafe { c::setsockopt(sockfd, c::#level, c::#optname, &val as *const _ as *const _, len) };
307                map_err!(res).map(drop)
308            }
309        ));
310    }
311    ts.into()
312}