use std::sync::Arc;
use anyhow::Result;
use super::bytecode::{MethodName, PathId};
use super::enum_def::{EnumDef, REG_TYPE};
use super::std_bridge::as_i64;
use super::value::{StructData, Value};
fn unit_enum(def: &Arc<EnumDef>, variant: &str) -> Value {
Value::enum_named(def, variant, Vec::new()).expect("every winreg enum variant is listed")
}
pub(super) fn winreg_const(id: PathId) -> Option<Value> {
if matches!(
id,
PathId::RegNone
| PathId::RegSz
| PathId::RegExpandSz
| PathId::RegBinary
| PathId::RegDword
| PathId::RegMultiSz
| PathId::RegQword
) {
return Some(unit_enum(®_TYPE, id.name()));
}
let n = match id {
PathId::HkeyClassesRoot => 0x8000_0000_u32,
PathId::HkeyCurrentUser => 0x8000_0001,
PathId::HkeyLocalMachine => 0x8000_0002,
PathId::HkeyUsers => 0x8000_0003,
PathId::HkeyCurrentConfig => 0x8000_0005,
PathId::KeyQueryValue => 0x0001,
PathId::KeySetValue => 0x0002,
PathId::KeyCreateSubKey => 0x0004,
PathId::KeyEnumerateSubKeys => 0x0008,
PathId::KeyRead => 0x0002_0019,
PathId::KeyWrite => 0x0002_0006,
PathId::KeyAllAccess => 0x000F_003F,
PathId::KeyWow6464key => 0x0100,
PathId::KeyWow6432key => 0x0200,
_ => return None,
};
Some(Value::Int(i64::from(n)))
}
pub(super) fn predef(args: &[Value]) -> Value {
let root = args.first().and_then(as_i64).unwrap_or(0);
key_value(root, "", i64::from(0x000F_003F_u32))
}
fn key_value(root: i64, path: &str, flags: i64) -> Value {
Value::struct_of(
"RegKey",
[
("root".into(), Value::Int(root)),
("path".into(), Value::str(path)),
("flags".into(), Value::Int(flags)),
],
)
}
#[cfg(windows)]
mod imp {
use super::super::bytecode::{BuiltinId, MethodName};
use std::borrow::Cow;
use anyhow::{Result, anyhow, bail};
use std::ptr::with_exposed_provenance_mut;
use winreg::RegKey;
use winreg::enums::RegType;
use winreg::types::{FromRegValue, ToRegValue};
use super::super::enum_def::{REG_DISPOSITION, REG_TYPE};
use super::super::numeric::IntWidth;
use super::super::value::Value;
use super::{as_i64, key_value, unit_enum};
use crate::interpreter::value::StructData;
fn field_str(s: &StructData, name: &str) -> String {
s.get(name).map(|v| v.display()).unwrap_or_default()
}
fn field_i64(s: &StructData, name: &str) -> i64 {
s.get(name).as_ref().and_then(as_i64).unwrap_or_default()
}
fn join(parent: &str, child: &str) -> String {
match (parent.is_empty(), child.is_empty()) {
(true, _) => child.to_string(),
(_, true) => parent.to_string(),
_ => format!("{}\\{}", parent.trim_end_matches('\\'), child),
}
}
fn mask(n: i64) -> Result<u32> {
u32::try_from(n).map_err(|_| anyhow!("`{n}` is not a valid registry flag set"))
}
fn root_key(root: i64) -> Result<RegKey> {
let raw = usize::try_from(root).map_err(|_| anyhow!("`{root}` is not a registry root"))?;
Ok(RegKey::predef(with_exposed_provenance_mut(raw)))
}
fn open(s: &StructData) -> std::io::Result<RegKey> {
let path = field_str(s, "path");
let flags = mask(field_i64(s, "flags")).map_err(std::io::Error::other)?;
let root = root_key(field_i64(s, "root")).map_err(std::io::Error::other)?;
if path.is_empty() {
return Ok(root);
}
root.open_subkey_with_flags(&path, flags)
}
fn read(v: &winreg::RegValue) -> Value {
match v.vtype {
RegType::REG_DWORD => {
Value::Int(u32::from_reg_value(v).map(i64::from).unwrap_or_default())
}
RegType::REG_QWORD => u64::from_reg_value(v).map_or(Value::Int(0), |n| {
Value::int_of_width(i128::from(n), IntWidth::U64)
}),
RegType::REG_SZ | RegType::REG_EXPAND_SZ => {
Value::str(String::from_reg_value(v).unwrap_or_default())
}
RegType::REG_MULTI_SZ => Value::vec(
Vec::<String>::from_reg_value(v)
.unwrap_or_default()
.into_iter()
.map(Value::str)
.collect(),
),
_ => Value::vec(v.bytes.iter().map(|b| Value::Int(i64::from(*b))).collect()),
}
}
fn write(v: &Value) -> Result<winreg::RegValue<'static>> {
match v {
Value::Int(n) => {
if let Ok(small) = u32::try_from(*n) {
Ok(own(small.to_reg_value()))
} else {
let wide = n.cast_unsigned();
Ok(own(wide.to_reg_value()))
}
}
Value::Vec(items) => {
let items = items.lock();
if items.iter().all(|i| matches!(i, Value::Str(_))) && !items.is_empty() {
let strings: Vec<String> = items.iter().map(Value::display).collect();
return Ok(own(strings.to_reg_value()));
}
let mut bytes = Vec::with_capacity(items.len());
for i in items.iter() {
let Some(n) = as_i64(i) else {
bail!("a binary registry value takes a vec of byte ints");
};
bytes.push(u8::try_from(n.rem_euclid(256)).unwrap_or_default());
}
Ok(winreg::RegValue {
bytes: Cow::Owned(bytes),
vtype: RegType::REG_BINARY,
})
}
other => {
let text = other.display();
Ok(own(text.to_reg_value()))
}
}
}
fn own(v: winreg::RegValue<'_>) -> winreg::RegValue<'static> {
winreg::RegValue {
bytes: Cow::Owned(v.bytes.into_owned()),
vtype: v.vtype,
}
}
fn type_name(t: &RegType) -> &'static str {
match t {
RegType::REG_NONE => "REG_NONE",
RegType::REG_SZ => "REG_SZ",
RegType::REG_EXPAND_SZ => "REG_EXPAND_SZ",
RegType::REG_DWORD => "REG_DWORD",
RegType::REG_MULTI_SZ => "REG_MULTI_SZ",
RegType::REG_QWORD => "REG_QWORD",
_ => "REG_BINARY",
}
}
fn type_from_name(name: &str) -> RegType {
match name {
"REG_NONE" => RegType::REG_NONE,
"REG_SZ" => RegType::REG_SZ,
"REG_EXPAND_SZ" => RegType::REG_EXPAND_SZ,
"REG_DWORD" => RegType::REG_DWORD,
"REG_MULTI_SZ" => RegType::REG_MULTI_SZ,
"REG_QWORD" => RegType::REG_QWORD,
_ => RegType::REG_BINARY,
}
}
fn raw_value(v: &winreg::RegValue) -> Value {
Value::struct_of(
"RegValue",
[
(
"bytes".into(),
Value::vec(v.bytes.iter().map(|b| Value::Int(i64::from(*b))).collect()),
),
("vtype".into(), unit_enum(®_TYPE, type_name(&v.vtype))),
],
)
}
fn raw_from(v: &Value) -> Result<winreg::RegValue<'static>> {
let Value::Struct(s) = v else {
bail!("set_raw_value takes a RegValue");
};
let Some(Value::Vec(items)) = s.get("bytes") else {
bail!("a RegValue needs a bytes field holding a vec of byte ints");
};
let items = items.lock();
let mut bytes = Vec::with_capacity(items.len());
for i in items.iter() {
let Some(n) = as_i64(i) else {
bail!("a RegValue bytes field takes byte ints");
};
bytes.push(u8::try_from(n.rem_euclid(256)).unwrap_or_default());
}
let vtype = match s.get("vtype") {
Some(Value::Enum { def, variant, .. }) => type_from_name(def.variant_name(variant)),
_ => RegType::REG_BINARY,
};
Ok(winreg::RegValue {
bytes: Cow::Owned(bytes),
vtype,
})
}
fn io_result(r: std::io::Result<()>) -> Value {
match r {
Ok(()) => Value::ok(Value::Unit),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
pub(super) fn regkey_method(
s: &StructData,
name: &MethodName,
args: &[Value],
) -> Result<Value> {
let first_text = || args.first().map(Value::display).unwrap_or_default();
let root = field_i64(s, "root");
let flags = field_i64(s, "flags");
let path = field_str(s, "path");
Ok(match name.id {
BuiltinId::OpenSubkey | BuiltinId::OpenSubkeyWithFlags => {
let want = args.get(1).and_then(as_i64).unwrap_or(flags);
let full = join(&path, &first_text());
match root_key(root)?.open_subkey_with_flags(&full, mask(want)?) {
Ok(_) => Value::ok(key_value(root, &full, want)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
BuiltinId::CreateSubkey => {
let full = join(&path, &first_text());
match root_key(root)?.create_subkey(&full) {
Ok((_, disp)) => Value::ok(Value::tuple(vec![
key_value(root, &full, flags),
unit_enum(®_DISPOSITION, &format!("{disp:?}")),
])),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
BuiltinId::GetValue => match open(s).and_then(|k| k.get_raw_value(first_text())) {
Ok(v) => Value::ok(read(&v)),
Err(e) => Value::err(Value::str(e.to_string())),
},
BuiltinId::SetValue => {
let Some(v) = args.get(1) else {
bail!("set_value takes a name and a value");
};
let raw = write(v)?;
io_result(open(s).and_then(|k| k.set_raw_value(first_text(), &raw)))
}
BuiltinId::GetRawValue => match open(s).and_then(|k| k.get_raw_value(first_text())) {
Ok(v) => Value::ok(raw_value(&v)),
Err(e) => Value::err(Value::str(e.to_string())),
},
BuiltinId::SetRawValue => {
let Some(v) = args.get(1) else {
bail!("set_raw_value takes a name and a RegValue");
};
let raw = raw_from(v)?;
io_result(open(s).and_then(|k| k.set_raw_value(first_text(), &raw)))
}
BuiltinId::DeleteValue => io_result(open(s).and_then(|k| k.delete_value(first_text()))),
BuiltinId::DeleteSubkey => {
io_result(root_key(root)?.delete_subkey(join(&path, &first_text())))
}
BuiltinId::DeleteSubkeyAll => {
io_result(root_key(root)?.delete_subkey_all(join(&path, &first_text())))
}
BuiltinId::EnumKeys => match open(s) {
Ok(k) => Value::vec(
k.enum_keys()
.map(|r| match r {
Ok(n) => Value::ok(Value::str(n)),
Err(e) => Value::err(Value::str(e.to_string())),
})
.collect(),
),
Err(e) => Value::vec(vec![Value::err(Value::str(e.to_string()))]),
},
BuiltinId::EnumValues => match open(s) {
Ok(k) => Value::vec(
k.enum_values()
.map(|r| match r {
Ok((n, v)) => Value::ok(Value::tuple(vec![Value::str(n), read(&v)])),
Err(e) => Value::err(Value::str(e.to_string())),
})
.collect(),
),
Err(e) => Value::vec(vec![Value::err(Value::str(e.to_string()))]),
},
_ => bail!("unknown method `{name}` on RegKey"),
})
}
}
#[cfg(not(windows))]
mod imp {
use super::super::bytecode::MethodName;
use anyhow::{Result, bail};
use super::super::value::{StructData, Value};
pub(super) fn regkey_method(
_s: &StructData,
name: &MethodName,
_args: &[Value],
) -> Result<Value> {
bail!("RegKey::{name} is the windows registry, it does not exist on this platform")
}
}
pub(super) fn winreg_method(s: &StructData, name: &MethodName, args: &[Value]) -> Result<Value> {
imp::regkey_method(s, name, args)
}