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)] 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}