Skip to content

Commit 0e8d49f

Browse files
committed
initial commit
0 parents  commit 0e8d49f

File tree

20 files changed

+1452
-0
lines changed

20 files changed

+1452
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target/
2+
Cargo.lock

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "schema"]
2+
path = schema
3+
url = https://github.com/arcnmx/qemu-qapi-filtered.git

Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "qapi"
3+
version = "0.0.1"
4+
5+
[dependencies]
6+
log = "^0.4.1"
7+
serde = "^1.0.27"
8+
serde_json = "^1.0.9"
9+
qapi-spec = { version = "^0.0.1", path = "spec" }
10+
11+
qapi-qga = { version = "^0.0.1", path = "qga", optional = true }
12+
qapi-qmp = { version = "^0.0.1", path = "qmp", optional = true }
13+
14+
[features]
15+
qga = ["qapi-qga"]
16+
qmp = ["qapi-qmp"]
17+
18+
[dev-dependencies]
19+
env_logger = "^0.5.4"

codegen/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "qapi-codegen"
3+
version = "0.0.1"
4+
5+
[dependencies]
6+
qapi-parser = { version = "^0.0.1", path = "../parser" }

codegen/src/lib.rs

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
extern crate qapi_parser as parser;
2+
3+
use parser::{Parser, QemuFileRepo, QemuRepo, spec};
4+
use parser::spec::Spec;
5+
use std::collections::{HashMap, HashSet};
6+
use std::path::{Path, PathBuf};
7+
use std::fs::File;
8+
use std::io::{self, Write};
9+
10+
// kebab-case to PascalCase?
11+
fn type_identifier<S: AsRef<str>>(id: S) -> String {
12+
identifier(id)
13+
}
14+
15+
// kebab-case to snake_case
16+
fn identifier<S: AsRef<str>>(id: S) -> String {
17+
let id = id.as_ref();
18+
match id {
19+
"type" | "static" | "virtual" | "abstract" | "in" | "enum" => format!("{}_", id),
20+
s if s.as_bytes()[0].is_ascii_digit() => format!("_{}", s),
21+
id => id.replace("-", "_")
22+
}
23+
}
24+
25+
// SCREAMING_SNAKE_CASE to PascalCase?
26+
fn event_identifier(id: &str) -> String {
27+
id.into()
28+
}
29+
30+
// no case change, just check for rust primitives
31+
fn typename_s(ty: &str) -> String {
32+
match ty {
33+
"str" => "::std::string::String".into(),
34+
"any" => "::qapi::Any".into(),
35+
"null" => "()".into(),
36+
"number" => "f64".into(),
37+
"int8" => "i8".into(),
38+
"uint8" => "u8".into(),
39+
"int16" => "i16".into(),
40+
"uint16" => "u16".into(),
41+
"int32" => "i32".into(),
42+
"uint32" => "u32".into(),
43+
"int64" => "i64".into(),
44+
"uint64" => "u64".into(),
45+
"size" => "usize".into(),
46+
"int" => "isize".into(), // ???
47+
ty => ty.into(),
48+
}
49+
}
50+
51+
fn typename(ty: &spec::Type) -> String {
52+
if ty.is_array {
53+
format!("Vec<{}>", typename_s(&ty.name))
54+
} else {
55+
typename_s(&ty.name)
56+
}
57+
}
58+
59+
fn valuety(value: &spec::Value, pubvis: bool, super_name: &str) -> String {
60+
// overrides for recursive types:
61+
let boxed = if value.name == "backing-image" && value.ty.name == "ImageInfo" {
62+
true
63+
} else if value.name == "backing" && value.ty.name == "BlockStats" {
64+
true
65+
} else if value.name == "parent" && value.ty.name == "BlockStats" {
66+
true
67+
} else {
68+
false
69+
};
70+
71+
let base64 = value.ty.name == "str" && (
72+
((super_name == "GuestFileRead" || super_name == "guest-file-write") && value.name == "buf-b64") ||
73+
(super_name == "guest-set-user-password" && value.name == "password") ||
74+
(super_name == "GuestExecStatus" && (value.name == "out-data" || value.name == "err-data")) ||
75+
(super_name == "guest-exec" && value.name == "input-data") ||
76+
(super_name == "QCryptoSecretFormat" && value.name == "base64")
77+
// "ringbuf-write", "ringbuf-read" can't be done because weird enums
78+
);
79+
80+
// TODO: handle optional Vec<>s specially?
81+
82+
let ty = typename(&value.ty);
83+
let (attr, ty) = if base64 {
84+
let ty = "Vec<u8>".into();
85+
if value.optional {
86+
(", with = \"qapi::base64_opt\"", ty)
87+
} else {
88+
(", with = \"qapi::base64\"", ty)
89+
}
90+
} else if boxed {
91+
("", format!("Box<{}>", ty))
92+
} else {
93+
("", ty)
94+
};
95+
96+
let (attr, ty) = if value.optional {
97+
(format!("{}, default, skip_serializing_if = \"Option::is_none\"", attr), format!("Option<{}>", ty))
98+
} else {
99+
(attr.into(), ty)
100+
};
101+
102+
format!("#[serde(rename = \"{}\"{})]\n{}{}: {}",
103+
value.name,
104+
attr,
105+
if pubvis { "pub " } else { "" },
106+
identifier(&value.name),
107+
ty
108+
)
109+
}
110+
111+
struct Context<W> {
112+
includes: Vec<String>,
113+
included: HashSet<PathBuf>,
114+
events: Vec<spec::Event>,
115+
unions: Vec<spec::CombinedUnion>,
116+
types: HashMap<String, spec::Struct>,
117+
out: W,
118+
}
119+
120+
impl<W: Write> Context<W> {
121+
fn new(out: W) -> Self {
122+
Context {
123+
includes: Default::default(),
124+
included: Default::default(),
125+
events: Default::default(),
126+
unions: Default::default(),
127+
types: Default::default(),
128+
out: out,
129+
}
130+
}
131+
132+
fn process(&mut self, item: spec::Spec) -> io::Result<()> {
133+
match item {
134+
Spec::Include(include) => {
135+
self.includes.push(include.include);
136+
},
137+
Spec::Command(v) => {
138+
match v.data {
139+
spec::DataOrType::Type(ref ty) if type_identifier(&ty.name) == type_identifier(&v.id) => (),
140+
_ => {
141+
write!(self.out, "
142+
#[derive(Debug, Clone, Serialize, Deserialize)]
143+
pub struct {}", type_identifier(&v.id))?;
144+
match v.data {
145+
spec::DataOrType::Data(ref data) => {
146+
writeln!(self.out, " {{")?;
147+
for data in &data.fields {
148+
writeln!(self.out, "\t{},", valuety(&data, true, &v.id))?;
149+
}
150+
writeln!(self.out, "}}")?;
151+
},
152+
spec::DataOrType::Type(ref ty) => {
153+
writeln!(self.out, "(pub {});", type_identifier(&ty.name))?;
154+
},
155+
}
156+
},
157+
}
158+
159+
write!(self.out, "
160+
impl ::qapi::Command for {} {{
161+
const NAME: &'static str = \"{}\";
162+
163+
type Ok = ", type_identifier(&v.id), v.id)?;
164+
if let Some(ret) = v.returns {
165+
writeln!(self.out, "{};", typename(&ret))
166+
} else {
167+
writeln!(self.out, "::qapi::Empty;")
168+
}?;
169+
writeln!(self.out, "}}")?;
170+
},
171+
Spec::Struct(v) => {
172+
write!(self.out, "
173+
#[derive(Debug, Clone, Serialize, Deserialize)]
174+
pub struct {} {{
175+
", type_identifier(&v.id))?;
176+
for item in &v.data.fields {
177+
writeln!(self.out, "{},", valuety(item, true, &v.id))?;
178+
}
179+
writeln!(self.out, "}}")?;
180+
181+
self.types.insert(v.id.clone(), v);
182+
},
183+
Spec::Alternate(v) => {
184+
write!(self.out, "
185+
#[derive(Debug, Clone, Serialize, Deserialize)]
186+
#[serde(untagged)]
187+
pub enum {} {{
188+
", type_identifier(&v.id))?;
189+
for data in &v.data.fields {
190+
assert!(!data.optional);
191+
let boxed = if data.name == "definition" && data.ty.name == "BlockdevOptions" {
192+
true
193+
} else {
194+
false
195+
};
196+
let ty = if boxed {
197+
format!("Box<{}>", typename(&data.ty))
198+
} else {
199+
typename(&data.ty)
200+
};
201+
writeln!(self.out, "\t#[serde(rename = \"{}\")] {}({}),", data.name, type_identifier(&data.name), ty)?;
202+
}
203+
writeln!(self.out, "}}")?;
204+
},
205+
Spec::Enum(v) => {
206+
write!(self.out, "
207+
#[derive(Debug, Clone, Serialize, Deserialize)]
208+
pub enum {} {{
209+
", type_identifier(&v.id))?;
210+
for item in &v.data {
211+
writeln!(self.out, "\t#[serde(rename = \"{}\")] {},", item, type_identifier(item))?;
212+
}
213+
writeln!(self.out, "}}")?;
214+
},
215+
Spec::Event(v) => {
216+
write!(self.out, "
217+
#[derive(Debug, Clone, Serialize, Deserialize{})]
218+
pub struct {} {{
219+
", if v.data.is_empty() { ", Default" } else { "" }, event_identifier(&v.id))?;
220+
for item in &v.data.fields {
221+
writeln!(self.out, "{},", valuety(item, true, &v.id))?;
222+
}
223+
writeln!(self.out, "}}")?;
224+
writeln!(self.out, "
225+
impl ::qapi::Event for {} {{
226+
const NAME: &'static str = \"{}\";
227+
}}", event_identifier(&v.id), v.id)?;
228+
self.events.push(v);
229+
},
230+
Spec::Union(v) => {
231+
write!(self.out, "
232+
#[derive(Debug, Clone, Serialize, Deserialize)]
233+
#[serde(tag = \"{}\")]
234+
pub enum {} {{
235+
", if let Some(ref tag) = v.discriminator { tag } else { "type" }, type_identifier(&v.id))?;
236+
for data in &v.data.fields {
237+
writeln!(self.out, "\t#[serde(rename = \"{}\")]\n\t{} {{ data: {} }},", data.name, type_identifier(&data.name), typename(&data.ty))?;
238+
}
239+
writeln!(self.out, "}}")?;
240+
},
241+
Spec::CombinedUnion(v) => {
242+
self.unions.push(v);
243+
},
244+
Spec::PragmaWhitelist { .. } => (),
245+
Spec::PragmaDocRequired { .. } => (),
246+
}
247+
248+
Ok(())
249+
}
250+
251+
fn process_unions(&mut self) -> io::Result<()> {
252+
for u in &self.unions {
253+
write!(self.out, "
254+
#[derive(Debug, Clone, Serialize, Deserialize)]
255+
#[serde(tag = \"{}\")]
256+
pub enum {} {{
257+
", if let Some(ref tag) = u.discriminator { tag } else { "type" }, type_identifier(&u.id))?;
258+
259+
for variant in &u.data.fields {
260+
assert!(!variant.optional);
261+
assert!(!variant.ty.is_array);
262+
263+
println!("doing union {}", u.id);
264+
writeln!(self.out, "\t#[serde(rename = \"{}\")]\n\t{} {{\n\t\t// base", variant.name, type_identifier(&variant.name))?;
265+
match u.base {
266+
spec::DataOrType::Data(ref data) => for base in &data.fields {
267+
writeln!(self.out, "\t\t{},", valuety(base, false, &u.id))?;
268+
},
269+
spec::DataOrType::Type(ref ty) => {
270+
let ty = self.types.get(&ty.name).ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("could not find qapi type {}", ty.name)))?;
271+
for base in &ty.data.fields {
272+
writeln!(self.out, "\t\t{},", valuety(base, false, &u.id))?;
273+
}
274+
},
275+
}
276+
277+
let ty = self.types.get(&variant.ty.name).ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("could not find qapi type {}", variant.ty.name)))?;
278+
writeln!(self.out, "\t\t// variant fields")?;
279+
for field in &ty.data.fields {
280+
writeln!(self.out, "\t\t{},", valuety(field, false, &u.id))?;
281+
}
282+
writeln!(self.out, "\t}},")?;
283+
}
284+
writeln!(self.out, "}}")?;
285+
}
286+
287+
Ok(())
288+
}
289+
290+
fn process_events(&mut self) -> io::Result<()> {
291+
writeln!(self.out, "
292+
#[derive(Debug, Clone, Serialize, Deserialize)]
293+
#[serde(tag = \"event\")]
294+
pub enum Event {{")?;
295+
for event in &self.events {
296+
let id = event_identifier(&event.id);
297+
writeln!(self.out, "\t#[serde(rename = \"{}\")] {} {{
298+
{} data: {},
299+
timestamp: ::qapi::Timestamp,
300+
}},", event.id, id, if event.data.is_empty() { "#[serde(default)] " } else { "" }, id)?;
301+
}
302+
writeln!(self.out, "}}")?;
303+
304+
Ok(())
305+
}
306+
}
307+
308+
fn include<W: Write>(context: &mut Context<W>, repo: &mut QemuFileRepo, path: &str) -> io::Result<()> {
309+
let include_path = repo.context().join(path);
310+
if context.included.contains(&include_path) {
311+
return Ok(())
312+
}
313+
context.included.insert(include_path);
314+
315+
let (mut repo, str) = repo.include(path)?;
316+
for item in Parser::from_string(Parser::strip_comments(&str)) {
317+
context.process(item?)?;
318+
}
319+
320+
while !context.includes.is_empty() {
321+
let includes: Vec<_> = context.includes.drain(..).collect();
322+
323+
for inc in includes {
324+
include(context, &mut repo, &inc)?;
325+
}
326+
}
327+
328+
Ok(())
329+
}
330+
331+
pub fn codegen<S: AsRef<Path>, O: AsRef<Path>>(schema_path: S, out_path: O) -> io::Result<HashSet<PathBuf>> {
332+
let mut repo = QemuFileRepo::new(schema_path.as_ref());
333+
{
334+
let mut context = Context::new(File::create(out_path)?);
335+
include(&mut context, &mut repo, "qapi-schema.json")?;
336+
context.process_unions()?;
337+
context.process_events()?;
338+
Ok(context.included)
339+
}
340+
}

examples/guest_info.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#[macro_use]
2+
extern crate log;
3+
extern crate qapi;
4+
5+
use std::os::unix::net::UnixStream;
6+
use std::env::args;
7+
use qapi::{qga, Qga};
8+
9+
fn main() {
10+
env_logger::init();
11+
12+
let socket_addr = args().nth(1).expect("argument: QEMU Guest Agent socket path");
13+
let stream = UnixStream::connect(socket_addr).expect("failed to connect to socket");
14+
15+
let mut qga = Qga::from_stream(&stream);
16+
17+
qga.handshake().expect("handshake failed");
18+
let info = qga.execute(&qga::guest_info { }).unwrap().unwrap();
19+
println!("Guest Agent version: {}", info.version);
20+
}

0 commit comments

Comments
 (0)