run-rs 0.6.27

Run a subset of Rust as an interpreted script
//! The `Windows` registry. A `RegKey` is plain struct fields and the real key is opened per call, so
//! no `Native` variant needs a cfg. Registry types map onto script values by shape. Off `Windows`
//! every call returns an error.

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> {
    // `RegType` is an enum value so `{:?}` prints the bare variant name
    if matches!(
        id,
        PathId::RegNone
            | PathId::RegSz
            | PathId::RegExpandSz
            | PathId::RegBinary
            | PathId::RegDword
            | PathId::RegMultiSz
            | PathId::RegQword
    ) {
        return Some(unit_enum(&REG_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)))
}

/// `predef` takes one of the HKEY roots.
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()
    }

    /// either side may be empty
    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),
        }
    }

    /// always within u32, it comes from the bridge constants
    fn mask(n: i64) -> Result<u32> {
        u32::try_from(n).map_err(|_| anyhow!("`{n}` is not a valid registry flag set"))
    }

    /// the roots are handle numbers, `HKEY_LOCAL_MACHINE` is `0x8000_0002`
    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() {
            // the root itself, nothing to open
            return Ok(root);
        }
        root.open_subkey_with_flags(&path, flags)
    }

    fn read(v: &winreg::RegValue) -> Value {
        match v.vtype {
            // each decoder rejects the other width, so they can't share an arm
            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()),
        }
    }

    /// An int that doesn't fit a DWORD widens to a QWORD. `RegValue` borrows its bytes, so the
    /// result is copied into an owned buffer.
    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,
        }
    }

    /// the untyped form with raw bytes and the value type
    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(&REG_TYPE, type_name(&v.vtype))),
            ],
        )
    }

    /// for `set_raw_value`
    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) {
                    // the key plus whether it was created, like the real crate
                    Ok((_, disp)) => Value::ok(Value::tuple(vec![
                        key_value(root, &full, flags),
                        unit_enum(&REG_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)))
            }
            // binary has no typed form, so `REG_BINARY` goes through these 2
            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)
}