winbrew_windows\deployment\msi/
database.rs1use anyhow::{Context, Result, bail};
15use std::collections::HashMap;
16use std::os::windows::ffi::OsStrExt;
17use std::path::Path;
18use windows::Win32::Foundation::{ERROR_MORE_DATA, ERROR_NO_MORE_ITEMS, ERROR_SUCCESS};
19use windows::Win32::System::ApplicationInstallationAndServicing::{
20 MSIDBOPEN_READONLY, MSIHANDLE, MsiCloseHandle, MsiDatabaseOpenViewW, MsiOpenDatabaseW,
21 MsiRecordGetInteger, MsiRecordGetStringW, MsiRecordIsNull, MsiViewExecute, MsiViewFetch,
22};
23use windows::core::{HSTRING, PCWSTR, PWSTR};
24
25use super::{ComponentRow, DirectoryRow, FileRow, RegistryRow, ShortcutRow};
26
27pub(super) struct MsiDatabase(MsiHandle);
28
29impl MsiDatabase {
30 pub(super) fn open(path: &Path) -> Result<Self> {
32 let wide_path = wide_path(path);
33 let mut handle = MSIHANDLE(0);
34 let status = unsafe {
35 MsiOpenDatabaseW(PCWSTR(wide_path.as_ptr()), MSIDBOPEN_READONLY, &mut handle)
36 };
37
38 ensure_msi_success(status, "open MSI database")?;
39
40 Ok(Self(MsiHandle::new(handle)))
41 }
42
43 pub(super) fn handle(&self) -> MSIHANDLE {
45 self.0.raw()
46 }
47}
48
49#[derive(Debug)]
54struct MsiHandle(MSIHANDLE);
55
56impl MsiHandle {
57 fn new(handle: MSIHANDLE) -> Self {
58 Self(handle)
59 }
60
61 fn raw(&self) -> MSIHANDLE {
62 self.0
63 }
64}
65
66impl Drop for MsiHandle {
67 fn drop(&mut self) {
68 if self.0.0 != 0 {
69 unsafe {
70 let _ = MsiCloseHandle(self.0);
71 }
72 }
73 }
74}
75
76pub(super) fn load_directory_rows(database: MSIHANDLE) -> Result<HashMap<String, DirectoryRow>> {
77 let rows = collect_rows(
79 database,
80 "SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`",
81 |record| {
82 Ok((
83 record_string(record, 1)?,
84 DirectoryRow {
85 parent: record_optional_string(record, 2)?,
86 default_dir: record_string(record, 3)?,
87 },
88 ))
89 },
90 )?;
91
92 Ok(rows.into_iter().collect())
93}
94
95pub(super) fn load_component_rows(database: MSIHANDLE) -> Result<HashMap<String, ComponentRow>> {
96 let rows = collect_rows(
98 database,
99 "SELECT `Component`, `Directory_`, `KeyPath` FROM `Component`",
100 |record| {
101 Ok((
102 record_string(record, 1)?,
103 ComponentRow {
104 directory_id: record_string(record, 2)?,
105 key_path: record_optional_string(record, 3)?,
106 },
107 ))
108 },
109 )?;
110
111 Ok(rows.into_iter().collect())
112}
113
114pub(super) fn load_file_rows(database: MSIHANDLE) -> Result<Vec<FileRow>> {
115 collect_rows(
117 database,
118 "SELECT `File`, `Component_`, `FileName` FROM `File`",
119 |record| {
120 Ok(FileRow {
121 file_key: record_string(record, 1)?,
122 component_id: record_string(record, 2)?,
123 file_name: record_string(record, 3)?,
124 })
125 },
126 )
127}
128
129pub(super) fn load_registry_rows(database: MSIHANDLE) -> Result<Vec<RegistryRow>> {
130 collect_rows(
132 database,
133 "SELECT `Root`, `Key`, `Name`, `Value` FROM `Registry`",
134 |record| {
135 Ok(RegistryRow {
136 root: record_integer(record, 1),
137 key_path: record_string(record, 2)?,
138 name: record_optional_string(record, 3)?,
139 value: record_optional_string(record, 4)?,
140 })
141 },
142 )
143}
144
145pub(super) fn load_shortcut_rows(database: MSIHANDLE) -> Result<Vec<ShortcutRow>> {
146 collect_rows(
148 database,
149 "SELECT `Directory_`, `Name`, `Target` FROM `Shortcut`",
150 |record| {
151 Ok(ShortcutRow {
152 directory_id: record_string(record, 1)?,
153 name: record_string(record, 2)?,
154 target: record_string(record, 3)?,
155 })
156 },
157 )
158}
159
160pub(super) fn query_required_string(database: MSIHANDLE, query: &str) -> Result<String> {
161 query_optional_string(database, query)?
163 .with_context(|| format!("missing MSI query result for '{query}'"))
164}
165
166pub(super) fn query_optional_string(database: MSIHANDLE, query: &str) -> Result<Option<String>> {
167 let rows = collect_rows(database, query, |record| record_string(record, 1))?;
169
170 Ok(rows.into_iter().next())
171}
172
173fn collect_rows<T, F>(database: MSIHANDLE, query: &str, mut parse_row: F) -> Result<Vec<T>>
174where
175 F: FnMut(MSIHANDLE) -> Result<T>,
176{
177 let view = open_view(database, query)?;
179 let view = MsiHandle::new(view);
180 execute_view(view.raw())?;
181
182 let mut rows = Vec::new();
183
184 loop {
185 let mut record = MSIHANDLE(0);
186 let status = unsafe { MsiViewFetch(view.raw(), &mut record) };
187
188 if status == ERROR_NO_MORE_ITEMS.0 || record.0 == 0 {
189 break;
190 }
191
192 ensure_msi_success(status, "fetch MSI record")?;
193
194 let record = MsiHandle::new(record);
195 rows.push(parse_row(record.raw())?);
196 }
197
198 Ok(rows)
199}
200
201fn open_view(database: MSIHANDLE, query: &str) -> Result<MSIHANDLE> {
202 let query = HSTRING::from(query);
204 let mut view = MSIHANDLE(0);
205 let status = unsafe { MsiDatabaseOpenViewW(database, &query, &mut view) };
206
207 ensure_msi_success(status, "open MSI view")?;
208
209 Ok(view)
210}
211
212fn execute_view(view: MSIHANDLE) -> Result<()> {
213 let status = unsafe { MsiViewExecute(view, MSIHANDLE(0)) };
215
216 ensure_msi_success(status, "execute MSI view")
217}
218
219fn record_optional_string(record: MSIHANDLE, field: u32) -> Result<Option<String>> {
220 if unsafe { MsiRecordIsNull(record, field).as_bool() } {
222 return Ok(None);
223 }
224
225 record_string(record, field).map(Some)
226}
227
228fn record_string(record: MSIHANDLE, field: u32) -> Result<String> {
229 let mut probe = [0u16; 1];
235 let mut length = 0u32;
236 let status = unsafe {
237 MsiRecordGetStringW(
238 record,
239 field,
240 Some(PWSTR(probe.as_mut_ptr())),
241 Some(&mut length),
242 )
243 };
244
245 if status == ERROR_SUCCESS.0 {
246 return Ok(String::new());
247 }
248
249 if status != ERROR_MORE_DATA.0 {
250 ensure_msi_success(status, "probe MSI record string")?;
251 }
252
253 let mut buffer = vec![0u16; length as usize + 1];
254 let mut written = buffer.len() as u32;
255 let status = unsafe {
256 MsiRecordGetStringW(
257 record,
258 field,
259 Some(PWSTR(buffer.as_mut_ptr())),
260 Some(&mut written),
261 )
262 };
263 ensure_msi_success(status, "read MSI record string")?;
264
265 buffer.truncate(written as usize);
266 String::from_utf16(&buffer).context("MSI record string contained invalid UTF-16")
267}
268
269fn record_integer(record: MSIHANDLE, field: u32) -> i32 {
270 unsafe { MsiRecordGetInteger(record, field) }
272}
273
274fn ensure_msi_success(status: u32, context: &str) -> Result<()> {
275 if status == ERROR_SUCCESS.0 {
277 Ok(())
278 } else {
279 bail!("{context} failed with MSI error code {status}")
280 }
281}
282
283fn wide_path(path: &Path) -> Vec<u16> {
284 path.as_os_str().encode_wide().chain(Some(0)).collect()
286}