forked from vitrine/wmaker
864 lines
27 KiB
Rust
864 lines
27 KiB
Rust
//! Property lists: shared, tree-structured data.
|
|
//!
|
|
//! ## Rust rewrite notes
|
|
//!
|
|
//! This implementation should be good enough to facilitate migrating from C to
|
|
//! Rust and trasitioning away from using property lists everywhere instead of
|
|
//! proper structs. `PropList`s are a really cool general-purpose tool for
|
|
//! attaching data to objects and persisting it to disk. But in Rust, it is
|
|
//! easier to use proper structs with typed fields and appropriate `#[derive]`
|
|
//! declarations to generate code for serialization and deserialization
|
|
//! (presumably using Serde).
|
|
//!
|
|
//! As code that uses `PropList`s is rewritte in Rust, we should work on
|
|
//! migrating away from use of `PropList`s. Objects whose fields that can be
|
|
//! statically typed should be represented as structs. They may still be
|
|
//! persisted by cramming them into `PropList`s and writing those to disk, but
|
|
//! it would be better still to implement Serde-based serialization to and from
|
|
//! the property list format.
|
|
//!
|
|
//! The `PropList` implementation itself can also be improved substantially. See
|
|
//! [`PropList`] for thoughts on this.
|
|
|
|
use atomic_write_file::unix::OpenOptionsExt;
|
|
|
|
use std::{
|
|
cell::RefCell,
|
|
collections::{hash_map, HashMap},
|
|
ffi::{CString, OsStr, OsString},
|
|
fmt, hash,
|
|
io::{self, BufWriter, Write},
|
|
path::Path,
|
|
process::Command,
|
|
ptr,
|
|
rc::Rc,
|
|
};
|
|
|
|
use crate::find_file;
|
|
|
|
pub mod parser;
|
|
pub mod writer;
|
|
|
|
/// Payload of a [`PropList`].
|
|
#[derive(Eq, PartialEq)]
|
|
pub enum Node {
|
|
/// Text data. This is UTF-8 encoded and null-safe.
|
|
///
|
|
/// ## Rust rewrite notes
|
|
///
|
|
/// It would be better for this to be a `String`, but the C interface
|
|
/// requires borrows of C-style strings.
|
|
String(CString),
|
|
/// Array of child `PropList`s.
|
|
Array(Vec<PropList>),
|
|
/// `PropList`-keyed table of child `PropList`s. Keys should only have
|
|
/// `Node::String` or `Node::Data` payloads, although there is almost no
|
|
/// enforcement of this.
|
|
Dictionary(HashMap<PropList, PropList>),
|
|
}
|
|
|
|
impl hash::Hash for Node {
|
|
fn hash<H: hash::Hasher>(&self, h: &mut H) {
|
|
match self {
|
|
Node::String(s) => s.hash(h),
|
|
Node::Array(a) => {
|
|
for p in a {
|
|
p.hash(h);
|
|
}
|
|
}
|
|
Node::Dictionary(d) => {
|
|
for (k, v) in d {
|
|
k.hash(h);
|
|
v.hash(h);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Node {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"{}",
|
|
writer::Display {
|
|
inline: writer::Inline::Soft,
|
|
clear_left: false,
|
|
indentation: 0,
|
|
increment: 2,
|
|
node: self,
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
fn merge_shallow(dest: PropList, source: PropList) {
|
|
if ptr::eq(dest.0.as_ref(), source.0.as_ref()) {
|
|
return;
|
|
}
|
|
let Node::Dictionary(ref mut dest_items) = *dest.0.borrow_mut() else {
|
|
return;
|
|
};
|
|
let Node::Dictionary(ref source) = *source.0.borrow() else {
|
|
return;
|
|
};
|
|
for (k, v) in source {
|
|
if ptr::eq(dest.0.as_ptr(), k.0.as_ptr()) {
|
|
// Don't borrow k if it is already borrowed as dest.
|
|
continue;
|
|
}
|
|
dest_items.insert(k.clone(), v.clone());
|
|
}
|
|
}
|
|
|
|
fn merge_deep(dest: PropList, source: PropList) {
|
|
if ptr::eq(dest.0.as_ptr(), source.0.as_ptr()) {
|
|
return;
|
|
}
|
|
let Node::Dictionary(dest_items) = &mut *dest.0.borrow_mut() else {
|
|
return;
|
|
};
|
|
let Node::Dictionary(source_items) = &*source.0.borrow() else {
|
|
return;
|
|
};
|
|
|
|
for (key, value) in source_items {
|
|
if key.0.try_borrow().is_err() || value.0.try_borrow().is_err() {
|
|
// Something has already borrowed key or value. This may happen if
|
|
// source contains pointers that are also in dest, or if dest is
|
|
// cyclic. This is bad, but we just bail out.
|
|
continue;
|
|
}
|
|
match dest_items.entry(key.clone()) {
|
|
hash_map::Entry::Vacant(v) => {
|
|
// Dest has nothing at key. Insert value from source.
|
|
v.insert(value.clone());
|
|
}
|
|
hash_map::Entry::Occupied(mut o) => {
|
|
let recur = match *o.get().0.borrow() {
|
|
Node::Dictionary(_) => true,
|
|
_ => false,
|
|
};
|
|
if recur {
|
|
// dest[key] is a dictionary. Recur on dest[key] and value from source.
|
|
merge_deep(o.get().clone(), value.clone());
|
|
} else {
|
|
// dest[key] is not a dictionary. Overwrite with value from source.
|
|
o.insert(value.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn subtract_shallow(dest: PropList, source: PropList) {
|
|
if ptr::eq(dest.0.as_ptr(), source.0.as_ptr()) {
|
|
if let Node::Dictionary(ref mut items) = *dest.0.borrow_mut() {
|
|
items.clear();
|
|
}
|
|
return;
|
|
}
|
|
|
|
let Node::Dictionary(ref mut dest_items) = *dest.0.borrow_mut() else {
|
|
return;
|
|
};
|
|
let Node::Dictionary(ref source_items) = *source.0.borrow() else {
|
|
return;
|
|
};
|
|
for (k, v) in source_items.iter() {
|
|
if ptr::eq(dest.0.as_ptr(), k.0.as_ptr()) {
|
|
continue;
|
|
}
|
|
if let hash_map::Entry::Occupied(o) = dest_items.entry(k.clone()) {
|
|
if o.get() == v {
|
|
o.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn subtract_deep(dest: PropList, source: PropList) {
|
|
if ptr::eq(dest.0.as_ptr(), source.0.as_ptr()) {
|
|
if let Node::Dictionary(ref mut items) = *dest.0.borrow_mut() {
|
|
items.clear();
|
|
}
|
|
return;
|
|
}
|
|
|
|
let Node::Dictionary(ref mut dest_items) = *dest.0.borrow_mut() else {
|
|
return;
|
|
};
|
|
let Node::Dictionary(ref source_items) = *source.0.borrow() else {
|
|
return;
|
|
};
|
|
for (k, v) in source_items.iter() {
|
|
if ptr::eq(dest.0.as_ptr(), k.0.as_ptr()) {
|
|
continue;
|
|
}
|
|
if let hash_map::Entry::Occupied(o) = dest_items.entry(k.clone()) {
|
|
if o.get() == v {
|
|
o.remove();
|
|
continue;
|
|
}
|
|
let recur = match (&*o.get().0.borrow(), &*v.0.borrow()) {
|
|
(Node::Dictionary(_), Node::Dictionary(_)) => true,
|
|
_ => false,
|
|
};
|
|
if recur {
|
|
subtract_deep(o.get().clone(), v.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Data graph with convenient (de)serialization to/from the [property
|
|
/// list](https://en.wikipedia.org/wiki/Property_list) format.
|
|
///
|
|
/// ## Rust rewrite notes
|
|
///
|
|
/// The original WUtils `PropList` was a reference-counted pointer, so it
|
|
/// supported shallow copy and shared-memory semantics that we have continued to
|
|
/// try to support in the Rust implementation. As a result, `PropList` is a thin
|
|
/// wrapper around an `Rc<RefCell<Node>>`. There are several reasons why this is
|
|
/// probably unnecessary and something we should migrate away from:
|
|
///
|
|
/// * It allows for the creation of non-tree structures, which was probably
|
|
/// never intended. (A degenerate `PropList` could even have itself as a child.)
|
|
/// * It complicates recursive operations on `PropList`s (equality checks,
|
|
/// merging, or taking differences) because a given `PropList` may occur
|
|
/// multiple times when traversing two `PropList`s, but `Rc` only allows it to be
|
|
/// mutably borrowed once.
|
|
/// * Allowing subtrees to be shared between two different `PropList`s
|
|
/// may lead to spooky action at a distance and may not actually be taken
|
|
/// advantage of by any client code.
|
|
///
|
|
/// As client code is migrated into Rust, it would be great to move away from
|
|
/// this implementation to a simpler one. As discussed in the module-level
|
|
/// rewrite notes, we may even be able to do away with `PropList` itself
|
|
/// (perhaps in favor of using Serde to write to and from property list files on
|
|
/// disk).
|
|
#[derive(Clone)]
|
|
pub struct PropList(Rc<RefCell<Node>>);
|
|
|
|
impl PropList {
|
|
pub fn new(node: Node) -> Self {
|
|
PropList(Rc::new(RefCell::new(node)))
|
|
}
|
|
|
|
/// Reads `r` to the end and tries to parse it into a `PropList`.
|
|
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<PropList, String> {
|
|
let path = path.as_ref().to_path_buf();
|
|
let buf = std::fs::read_to_string(&path).map_err(|e| format!("{}", e))?;
|
|
parser::from_str(buf.as_str())
|
|
}
|
|
|
|
// Runs `command` and tries to parse a PropList from its standard output.
|
|
pub fn from_command<S: AsRef<OsStr>>(command: S) -> Result<PropList, String> {
|
|
let command: OsString = command.as_ref().to_os_string();
|
|
let output = Command::new("/bin/sh")
|
|
.arg("-c")
|
|
.arg(command.clone())
|
|
.output()
|
|
.map_err(|e| format!("{}", e))?;
|
|
let output = str::from_utf8(&output.stdout).map_err(|e| format!("{}", e))?;
|
|
parser::from_str(&output)
|
|
}
|
|
|
|
pub fn display_indented<'s>(&'s self) -> impl fmt::Display + 's {
|
|
writer::Display {
|
|
inline: writer::Inline::Soft,
|
|
clear_left: false,
|
|
indentation: 0,
|
|
increment: 2,
|
|
node: self.0.borrow(),
|
|
}
|
|
}
|
|
|
|
pub fn display_unindented<'s>(&'s self) -> impl fmt::Display + 's {
|
|
writer::Display {
|
|
inline: writer::Inline::Hard,
|
|
clear_left: false,
|
|
indentation: 0,
|
|
increment: 0,
|
|
node: self.0.borrow(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Eq for PropList {}
|
|
|
|
impl PartialEq for PropList {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
*self.0.borrow() == *other.0.borrow()
|
|
}
|
|
}
|
|
|
|
impl hash::Hash for PropList {
|
|
fn hash<H: hash::Hasher>(&self, h: &mut H) {
|
|
self.0.borrow().hash(h)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for PropList {
|
|
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(out, "{:?}", self.0.borrow())?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl PropList {
|
|
pub fn deep_clone(&self) -> Self {
|
|
match &*self.0.borrow() {
|
|
Node::String(s) => PropList::new(Node::String(s.clone())),
|
|
Node::Array(items) => {
|
|
PropList::new(Node::Array(items.iter().map(|x| x.deep_clone()).collect()))
|
|
}
|
|
Node::Dictionary(items) => PropList::new(Node::Dictionary(
|
|
items
|
|
.iter()
|
|
.map(|(k, v)| (k.deep_clone(), v.deep_clone()))
|
|
.collect(),
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Atomically serialize this `PropList` to `path`, creating any necessary
|
|
/// parent directories.
|
|
///
|
|
/// `path` is written to atomically: either the serialized `PropList` will
|
|
/// be completely written to `path`, or the operation will fail and any
|
|
/// existing file at `path` will not be modified.
|
|
///
|
|
/// ## Rust rewrite notes
|
|
///
|
|
/// As originally noted in `proplist.c`, a Coverity security bug report
|
|
/// flagged the need to preserve the permissions on the file being written
|
|
/// to. This should be respected in the rewritten code under Unix-like
|
|
/// operataing systems.
|
|
pub fn write_to_file<P: AsRef<Path>>(&self, path: &P) -> io::Result<()> {
|
|
self.write_to_file_impl(path.as_ref())
|
|
}
|
|
|
|
fn write_to_file_impl(&self, path: &Path) -> io::Result<()> {
|
|
if let Some(parent) = path.parent() {
|
|
find_file::create_path_hierarchy(parent)?;
|
|
}
|
|
let file = atomic_write_file::AtomicWriteFile::options()
|
|
.preserve_mode(true)
|
|
.open(&path)?;
|
|
let mut out = BufWriter::new(file);
|
|
writeln!(&mut out, "{}", self.display_indented())?;
|
|
out.into_inner()?.commit()
|
|
}
|
|
}
|
|
|
|
pub mod ffi {
|
|
use crate::{find_file::path_from_cstr, memory};
|
|
|
|
use super::{
|
|
merge_deep, merge_shallow, parser, subtract_deep, subtract_shallow, Node, PropList,
|
|
};
|
|
|
|
use std::{
|
|
collections::HashMap, ffi::{c_char, c_int, c_uint, CStr, CString, OsString}, ptr, str::FromStr
|
|
};
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMCreatePLString(s: *const c_char) -> *mut PropList {
|
|
if s.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let s = unsafe { CStr::from_ptr(s) };
|
|
Box::leak(Box::new(PropList::new(Node::String(s.into()))))
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMCreatePLArrayFromSlice(
|
|
elems: *mut PropList,
|
|
length: c_uint,
|
|
) -> *mut PropList {
|
|
if elems.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let elems = unsafe { &*ptr::slice_from_raw_parts(elems, length as usize) };
|
|
Box::leak(Box::new(PropList::new(Node::Array(
|
|
elems.iter().cloned().collect(),
|
|
))))
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMCreateEmptyPLArray() -> *mut PropList {
|
|
Box::leak(Box::new(PropList::new(Node::Array(Vec::new()))))
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMCreatePLDictionary(
|
|
key: *mut PropList,
|
|
value: *mut PropList,
|
|
) -> *mut PropList {
|
|
if key.is_null() || value.is_null() {
|
|
return Box::leak(Box::new(PropList::new(Node::Dictionary(HashMap::new()))));
|
|
}
|
|
let key = unsafe { (*key).clone() };
|
|
let value = unsafe { (*value).clone() };
|
|
Box::leak(Box::new(PropList::new(Node::Dictionary(
|
|
[(key, value)].into(),
|
|
))))
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMCreateEmptyPLDictionary() -> *mut PropList {
|
|
Box::leak(Box::new(PropList::new(Node::Dictionary(HashMap::new()))))
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMRetainPropList(plist: *mut PropList) -> *mut PropList {
|
|
if plist.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe { Box::leak(Box::new((*plist).clone())) }
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMReleasePropList(plist: *mut PropList) {
|
|
if plist.is_null() {
|
|
return;
|
|
}
|
|
let _ = unsafe { ptr::read(plist) };
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMInsertInPLArray(
|
|
plist: *mut PropList,
|
|
index: c_int,
|
|
item: *mut PropList,
|
|
) {
|
|
if plist.is_null() || index < 0 || item.is_null() {
|
|
return;
|
|
}
|
|
let plist = unsafe { &mut *plist };
|
|
if let Node::Array(ref mut items) = *plist.0.borrow_mut() {
|
|
let item = unsafe { (*item).clone() };
|
|
items.insert(index as usize, item);
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMAddToPLArray(plist: *mut PropList, item: *mut PropList) {
|
|
if plist.is_null() || item.is_null() {
|
|
return;
|
|
}
|
|
let plist = unsafe { &mut *plist };
|
|
if let Node::Array(ref mut items) = *plist.0.borrow_mut() {
|
|
let item = unsafe { (*item).clone() };
|
|
items.push(item);
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMDeleteFromPLArray(plist: *mut PropList, index: c_int) {
|
|
if plist.is_null() || index < 0 {
|
|
return;
|
|
}
|
|
let plist = unsafe { &mut *plist };
|
|
if let Node::Array(ref mut items) = *plist.0.borrow_mut() {
|
|
items.remove(index as usize);
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMRemoveFromPLArray(plist: *mut PropList, item: *mut PropList) {
|
|
if plist.is_null() || item.is_null() {
|
|
return;
|
|
}
|
|
let plist = unsafe { &mut *plist };
|
|
let item = unsafe { &*item };
|
|
if let Node::Array(ref mut items) = *plist.0.borrow_mut() {
|
|
if let Some((i, _)) = items.iter().enumerate().find(|(_, x)| *x == item) {
|
|
items.remove(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMPutInPLDictionary(
|
|
plist: *mut PropList,
|
|
key: *mut PropList,
|
|
value: *mut PropList,
|
|
) {
|
|
if plist.is_null() || key.is_null() || value.is_null() {
|
|
return;
|
|
}
|
|
let plist = unsafe { &mut *plist };
|
|
if let Node::Dictionary(ref mut items) = *plist.0.borrow_mut() {
|
|
let key = unsafe { (*key).clone() };
|
|
let value = unsafe { (*value).clone() };
|
|
items.insert(key, value);
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMRemoveFromPLDictionary(plist: *mut PropList, key: *mut PropList) {
|
|
if plist.is_null() || key.is_null() {
|
|
return;
|
|
}
|
|
let plist = unsafe { &mut *plist };
|
|
let key = unsafe { &*key };
|
|
if let Node::Dictionary(ref mut items) = *plist.0.borrow_mut() {
|
|
items.remove(key);
|
|
}
|
|
}
|
|
|
|
/// If `dest` and `source` are both dictionaries, overwrites entries in
|
|
/// `dest` with corresponding entries in `source`.
|
|
///
|
|
/// If `recursive` is non-zero, this is done recursively for values in
|
|
/// `dest` and `source` that are both dictionaries.
|
|
///
|
|
/// ## Rust rewrite notes
|
|
///
|
|
/// This operation is used a few times. It may be worth keeping around
|
|
/// longer-term, although it might be hard to express if we do transition
|
|
/// away from `PropList`s to statically typed struct trees.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMMergePLDictionaries(
|
|
dest: *mut PropList,
|
|
source: *mut PropList,
|
|
recursive: c_int,
|
|
) -> *mut PropList {
|
|
if dest.is_null() || ptr::eq(dest, source) || source.is_null() {
|
|
return dest;
|
|
}
|
|
|
|
let dest = unsafe { (*dest).clone() };
|
|
let source = unsafe { (*source).clone() };
|
|
|
|
if recursive == 0 {
|
|
merge_shallow(dest.clone(), source);
|
|
} else {
|
|
merge_deep(dest.clone(), source);
|
|
}
|
|
|
|
return Box::leak(Box::new(dest));
|
|
}
|
|
|
|
/// If `dest` and `source` are both dictionaries, removes from `dest` any
|
|
/// `(k, v)` pairs where `dest[k] == source[k]`.
|
|
///
|
|
/// If `recursive` is non-zero, this is done recursively over subtrees of
|
|
/// `dest` and `source` when both `dest` and `source` are dictionaries for
|
|
/// keys of `source` that are also keys of `dest`.
|
|
///
|
|
/// ## Rust rewrite notes
|
|
///
|
|
/// This operation is only used in one place. It may be better to implement
|
|
/// this behavior as a one-off closer to where it is used, or with a
|
|
/// different API differently (e.g., as a function of a more general
|
|
/// proplist diff).
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMSubtractPLDictionaries(
|
|
dest: *mut PropList,
|
|
source: *mut PropList,
|
|
recursive: c_int,
|
|
) -> *mut PropList {
|
|
if dest.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
if source.is_null() {
|
|
return dest;
|
|
}
|
|
|
|
let dest = unsafe { (*dest).clone() };
|
|
let source = unsafe { (*source).clone() };
|
|
|
|
if recursive == 0 {
|
|
subtract_shallow(dest.clone(), source);
|
|
} else {
|
|
subtract_deep(dest.clone(), source);
|
|
}
|
|
|
|
Box::leak(Box::new(dest))
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetPropListItemCount(plist: *mut PropList) -> c_int {
|
|
if plist.is_null() {
|
|
return 0;
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
match &*plist.0.borrow() {
|
|
Node::Array(xs) => xs.len() as c_int,
|
|
Node::Dictionary(xs) => xs.len() as c_int,
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMIsPLString(plist: *mut PropList) -> c_int {
|
|
if plist.is_null() {
|
|
return 0;
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
match &*plist.0.borrow() {
|
|
Node::String(_) => 1,
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMIsPLArray(plist: *mut PropList) -> c_int {
|
|
if plist.is_null() {
|
|
return 0;
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
match &*plist.0.borrow() {
|
|
Node::Array(_) => 1,
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMIsPLDictionary(plist: *mut PropList) -> c_int {
|
|
if plist.is_null() {
|
|
return 0;
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
match &*plist.0.borrow() {
|
|
Node::Dictionary(_) => 1,
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMIsPropListEqualTo(a: *mut PropList, b: *mut PropList) -> c_int {
|
|
if ptr::eq(a, b) {
|
|
return 1;
|
|
}
|
|
if a.is_null() {
|
|
return 0;
|
|
}
|
|
let a = unsafe { &*a };
|
|
let b = unsafe { &*b };
|
|
if a == b {
|
|
1
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetFromPLString(plist: *mut PropList) -> *const c_char {
|
|
if plist.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
if let Node::String(ref s) = *plist.0.borrow() {
|
|
s.as_ref().as_ptr().cast::<c_char>()
|
|
} else {
|
|
ptr::null()
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetFromPLArray(plist: *mut PropList, index: c_int) -> *mut PropList {
|
|
if plist.is_null() || index < 0 {
|
|
return ptr::null_mut();
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
if let Node::Array(ref items) = *plist.0.borrow() {
|
|
if let Some(x) = items.get(index as usize) {
|
|
return Box::leak(Box::new(x.clone()));
|
|
}
|
|
}
|
|
ptr::null_mut()
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetFromPLDictionary(plist: *mut PropList, key: *mut PropList) -> *mut PropList {
|
|
if plist.is_null() || key.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
let key = unsafe { &*key };
|
|
if let Node::Dictionary(ref items) = *plist.0.borrow() {
|
|
if let Some(item) = items.get(key) {
|
|
return Box::leak(Box::new(item.clone()));
|
|
}
|
|
}
|
|
ptr::null_mut()
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetPLDictionaryKeys(plist: *mut PropList) -> *mut PropList {
|
|
if plist.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
|
|
if let Node::Dictionary(ref items) = *plist.0.borrow() {
|
|
return Box::leak(Box::new(PropList::new(Node::Array(items.keys().cloned().collect()))));
|
|
}
|
|
ptr::null_mut()
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMDeepCopyPropList(plist: *mut PropList) -> *mut PropList {
|
|
if plist.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
Box::leak(Box::new(plist.deep_clone()))
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMCreatePropListFromDescription(desc: *const c_char) -> *mut PropList {
|
|
if desc.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let desc = unsafe { CStr::from_ptr(desc) };
|
|
let Ok(desc) = desc.to_str() else {
|
|
return ptr::null_mut();
|
|
};
|
|
|
|
match parser::from_str(desc) {
|
|
Ok(plist) => Box::leak(Box::new(plist)),
|
|
Err(_) => ptr::null_mut(),
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetPropListDescription(
|
|
plist: *mut PropList,
|
|
indented: c_int,
|
|
) -> *mut c_char {
|
|
use std::io::Write;
|
|
|
|
if plist.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let plist = unsafe { &*plist };
|
|
let mut buf = Vec::new();
|
|
if indented != 0 {
|
|
if let Err(_) = write!(&mut buf, "{}", plist.display_indented()) {
|
|
return ptr::null_mut();
|
|
}
|
|
} else {
|
|
if let Err(_) = write!(&mut buf, "{}", plist.display_unindented()) {
|
|
return ptr::null_mut();
|
|
}
|
|
}
|
|
match CString::new(buf) {
|
|
Ok(s) => memory::alloc_string(s.as_c_str()),
|
|
Err(_) => ptr::null_mut(),
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMReadPropListFromFile(path: *const c_char) -> *mut PropList {
|
|
if path.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let path = unsafe { CStr::from_ptr(path) };
|
|
let Ok(path) = path.to_str() else {
|
|
return ptr::null_mut();
|
|
};
|
|
match PropList::from_file(path) {
|
|
Ok(plist) => Box::leak(Box::new(plist)),
|
|
Err(_) => {
|
|
// TODO: print error message.
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMReadPropListFromPipe(command: *const c_char) -> *mut PropList {
|
|
if command.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
|
|
let command = unsafe { CStr::from_ptr(command) };
|
|
let Ok(command) = command.to_str() else {
|
|
return ptr::null_mut();
|
|
};
|
|
let command = OsString::from_str(command).unwrap();
|
|
|
|
let Ok(output) = std::process::Command::new("/bin/sh")
|
|
.arg("-c")
|
|
.arg(command)
|
|
.output()
|
|
else {
|
|
// TODO: print error message.
|
|
return ptr::null_mut();
|
|
};
|
|
let Ok(output) = String::from_utf8(output.stdout) else {
|
|
// TODO: print error message.
|
|
return ptr::null_mut();
|
|
};
|
|
match parser::from_str(&output) {
|
|
Ok(plist) => Box::leak(Box::new(plist)),
|
|
Err(_) => {
|
|
// TODO: print error message.
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMWritePropListToFile(
|
|
plist: *mut PropList,
|
|
path: *const c_char,
|
|
) -> c_int {
|
|
if plist.is_null() || path.is_null() {
|
|
return 0;
|
|
}
|
|
let plist = unsafe {
|
|
&*plist
|
|
};
|
|
|
|
let path = unsafe {
|
|
CStr::from_ptr(path)
|
|
};
|
|
let Some(path) = path_from_cstr(path) else {
|
|
// TODO: complain.
|
|
return 0;
|
|
};
|
|
|
|
match plist.write_to_file(&path) {
|
|
Ok(_) => return 1,
|
|
Err(_) => {
|
|
// TODO: complain.
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use std::ffi::CString;
|
|
|
|
use crate::memory;
|
|
|
|
use super::{Node, PropList, ffi};
|
|
|
|
#[test]
|
|
fn free_proplist_description() {
|
|
let mut plist = PropList::new(Node::Array(vec![PropList::new(Node::String(CString::from(c"hello"))),
|
|
PropList::new(Node::String(CString::from(c"world!")))]));
|
|
let desc = unsafe { ffi::WMGetPropListDescription(&mut plist, 1) };
|
|
assert!(!desc.is_null());
|
|
|
|
unsafe { memory::ffi::wfree(desc.cast()); }
|
|
}
|
|
|
|
#[test]
|
|
fn oob_array_access_returns_null() {
|
|
// This is the original WMArray behavior. I don't like it, but a bunch
|
|
// of existing code relies on it.
|
|
let mut list = PropList::new(Node::Array(vec![PropList::new(Node::String(CString::from(c"hello"))),
|
|
PropList::new(Node::String(CString::from(c"world!")))]));
|
|
assert!(unsafe { ffi::WMGetFromPLArray(&mut list, 3) }.is_null());
|
|
}
|
|
}
|