From cf435134aefef5ea8adc8258487a6926f3fb42ff Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 18 Feb 2024 20:58:38 +0000 Subject: [PATCH] cxx experiment --- Cargo.lock | 156 ++- Cargo.toml | 6 +- src/tc/CMakeLists.txt | 7 +- src/tc/Cargo.toml | 15 + src/tc/build.rs | 6 + src/tc/src/lib.rs | 17 + src/tc/src/main.cpp | 5 + taskchampion/lib/Cargo.toml | 17 - taskchampion/lib/Makefile | 2 - taskchampion/lib/src/annotation.rs | 186 ---- taskchampion/lib/src/atomic.rs | 34 - taskchampion/lib/src/kv.rs | 155 --- taskchampion/lib/src/lib.rs | 172 ---- taskchampion/lib/src/replica.rs | 904 ------------------ taskchampion/lib/src/result.rs | 25 - taskchampion/lib/src/server.rs | 234 ----- taskchampion/lib/src/status.rs | 60 -- taskchampion/lib/src/string.rs | 773 --------------- taskchampion/lib/src/task.rs | 1304 -------------------------- taskchampion/lib/src/traits.rs | 351 ------- taskchampion/lib/src/uda.rs | 188 ---- taskchampion/lib/src/util.rs | 23 - taskchampion/lib/src/uuid.rs | 239 ----- taskchampion/lib/src/workingset.rs | 141 --- taskchampion/lib/taskchampion.h | 917 ------------------ taskchampion/taskchampion/Cargo.toml | 3 + 26 files changed, 127 insertions(+), 5813 deletions(-) create mode 100644 src/tc/Cargo.toml create mode 100644 src/tc/build.rs create mode 100644 src/tc/src/lib.rs create mode 100644 src/tc/src/main.cpp delete mode 100644 taskchampion/lib/Cargo.toml delete mode 100644 taskchampion/lib/Makefile delete mode 100644 taskchampion/lib/src/annotation.rs delete mode 100644 taskchampion/lib/src/atomic.rs delete mode 100644 taskchampion/lib/src/kv.rs delete mode 100644 taskchampion/lib/src/lib.rs delete mode 100644 taskchampion/lib/src/replica.rs delete mode 100644 taskchampion/lib/src/result.rs delete mode 100644 taskchampion/lib/src/server.rs delete mode 100644 taskchampion/lib/src/status.rs delete mode 100644 taskchampion/lib/src/string.rs delete mode 100644 taskchampion/lib/src/task.rs delete mode 100644 taskchampion/lib/src/traits.rs delete mode 100644 taskchampion/lib/src/uda.rs delete mode 100644 taskchampion/lib/src/util.rs delete mode 100644 taskchampion/lib/src/uuid.rs delete mode 100644 taskchampion/lib/src/workingset.rs delete mode 100644 taskchampion/lib/taskchampion.h diff --git a/Cargo.lock b/Cargo.lock index 8d217647d..197651953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,6 +525,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -588,6 +598,50 @@ dependencies = [ "typenum", ] +[[package]] +name = "cxx" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88abab2f5abbe4c56e8f1fb431b784d710b709888f35755a160e62e33fe38e8" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0c11acd0e63bae27dcd2afced407063312771212b7a823b4fd72d633be30fb" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.18", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3816ed957c008ccd4728485511e3d9aaf7db419aa321e3d2c5a2f3411e36c8" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26acccf6f445af85ea056362561a24ef56cdc15fcc685f03aec50b9c702cb6d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + [[package]] name = "der" version = "0.7.8" @@ -628,12 +682,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - [[package]] name = "encoding_rs" version = "0.8.31" @@ -698,29 +746,6 @@ dependencies = [ "instant", ] -[[package]] -name = "ffizz-header" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a1a52e9f00aa4c639059d977e1289a13963117d8e60ccb25e86cca2aab98538" -dependencies = [ - "ffizz-macros", - "itertools", - "linkme", -] - -[[package]] -name = "ffizz-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af208cea557bab3ec4f05fb0c26460f1c61fdb204f3738f471b6b1ecd58d7a04" -dependencies = [ - "itertools", - "proc-macro2", - "quote", - "syn 1.0.104", -] - [[package]] name = "flate2" version = "1.0.24" @@ -1141,24 +1166,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "integration-tests" -version = "0.4.1" -dependencies = [ - "actix-rt", - "actix-web", - "anyhow", - "cc", - "env_logger", - "lazy_static", - "log", - "pretty_assertions", - "taskchampion", - "taskchampion-lib", - "taskchampion-sync-server", - "tempfile", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1188,15 +1195,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.2" @@ -1271,23 +1269,12 @@ dependencies = [ ] [[package]] -name = "linkme" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc2b30967da1bcca8f15aa741f2b949a315ef0eabd0ef630a5a0643d7a45260" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.8" +name = "link-cplusplus" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a440f823b734f5a90d7cc2850a2254611092e88fa13fb1948556858ce2d35d2a" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.104", + "cc", ] [[package]] @@ -1869,6 +1856,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + [[package]] name = "sct" version = "0.7.0" @@ -2102,10 +2095,8 @@ dependencies = [ name = "taskchampion-lib" version = "0.1.0" dependencies = [ - "anyhow", - "ffizz-header", - "libc", - "pretty_assertions", + "cxx", + "cxx-build", "taskchampion", ] @@ -2366,6 +2357,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "untrusted" version = "0.7.1" @@ -2815,15 +2812,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "xtask" -version = "0.4.1" -dependencies = [ - "anyhow", - "regex", - "taskchampion-lib", -] - [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 34828a6bf..c240f18e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,9 @@ members = [ "taskchampion/taskchampion", "taskchampion/sync-server", - "taskchampion/lib", - "taskchampion/integration-tests", - "taskchampion/xtask", + "src/tc", + #"taskchampion/integration-tests", + #"taskchampion/xtask", ] resolver = "2" diff --git a/src/tc/CMakeLists.txt b/src/tc/CMakeLists.txt index 500ad1727..0a866ec48 100644 --- a/src/tc/CMakeLists.txt +++ b/src/tc/CMakeLists.txt @@ -13,6 +13,11 @@ corrosion_import_crate( LOCKED CRATES "taskchampion-lib") +corrosion_add_cxxbridge(taskchampion-cpp + CRATE taskchampion-lib + FILES src/lib.rs +) + include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/tc @@ -29,4 +34,4 @@ set (tc_SRCS Task.cpp Task.h) add_library (tc STATIC ${tc_SRCS}) -target_link_libraries(tc taskchampion-lib) +target_link_libraries(tc taskchampion-lib taskchampion-cpp) diff --git a/src/tc/Cargo.toml b/src/tc/Cargo.toml new file mode 100644 index 000000000..ca19ffe3e --- /dev/null +++ b/src/tc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "taskchampion-lib" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["staticlib"] + +[dependencies] +taskchampion = { path = "../../taskchampion/taskchampion", features = ["server-sync"] } +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" diff --git a/src/tc/build.rs b/src/tc/build.rs new file mode 100644 index 000000000..7ad128819 --- /dev/null +++ b/src/tc/build.rs @@ -0,0 +1,6 @@ +#[allow(unused_must_use)] +fn main() { + cxx_build::bridge("src/lib.rs"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/lib.rs"); +} diff --git a/src/tc/src/lib.rs b/src/tc/src/lib.rs new file mode 100644 index 000000000..b1620ea4b --- /dev/null +++ b/src/tc/src/lib.rs @@ -0,0 +1,17 @@ +use taskchampion::{Replica as RealReplica, StorageConfig}; + +#[cxx::bridge(namespace = "tc")] +mod ffi { + // Rust types and signatures exposed to C++. + extern "Rust" { + type Replica; + fn new_replica() -> Box; + } +} + +struct Replica(RealReplica); + +fn new_replica() -> Box { + let storage = StorageConfig::InMemory.into_storage().unwrap(); + Box::new(Replica(RealReplica::new(storage))) +} diff --git a/src/tc/src/main.cpp b/src/tc/src/main.cpp new file mode 100644 index 000000000..0a00abd9d --- /dev/null +++ b/src/tc/src/main.cpp @@ -0,0 +1,5 @@ +#include "lib.rs.h" + +int main() { + auto replica = tc::new_replica(); +} diff --git a/taskchampion/lib/Cargo.toml b/taskchampion/lib/Cargo.toml deleted file mode 100644 index e9257da28..000000000 --- a/taskchampion/lib/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "taskchampion-lib" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["staticlib", "rlib"] - -[dependencies] -libc.workspace = true -anyhow.workspace = true -ffizz-header.workspace = true - -taskchampion = { path = "../taskchampion" } - -[dev-dependencies] -pretty_assertions.workspace = true diff --git a/taskchampion/lib/Makefile b/taskchampion/lib/Makefile deleted file mode 100644 index d2dbe101b..000000000 --- a/taskchampion/lib/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -taskchampion.h: cbindgen.toml ../target/debug/libtaskchampion.so - cbindgen --config cbindgen.toml --crate taskchampion-lib --output $@ diff --git a/taskchampion/lib/src/annotation.rs b/taskchampion/lib/src/annotation.rs deleted file mode 100644 index 5edfda4c8..000000000 --- a/taskchampion/lib/src/annotation.rs +++ /dev/null @@ -1,186 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use taskchampion::chrono::prelude::*; - -#[ffizz_header::item] -#[ffizz(order = 400)] -/// ***** TCAnnotation ***** -/// -/// TCAnnotation contains the details of an annotation. -/// -/// # Safety -/// -/// An annotation must be initialized from a tc_.. function, and later freed -/// with `tc_annotation_free` or `tc_annotation_list_free`. -/// -/// Any function taking a `*TCAnnotation` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from a tc_… function; -/// - the memory referenced by the pointer must never be modified by C code; and -/// - ownership transfers to the called function, and the value must not be used -/// after the call returns. In fact, the value will be zeroed out to ensure this. -/// -/// TCAnnotations are not threadsafe. -/// -/// ```c -/// typedef struct TCAnnotation { -/// // Time the annotation was made. Must be nonzero. -/// time_t entry; -/// // Content of the annotation. Must not be NULL. -/// TCString description; -/// } TCAnnotation; -/// ``` -#[repr(C)] -pub struct TCAnnotation { - pub entry: libc::time_t, - pub description: TCString, -} - -impl PassByValue for TCAnnotation { - // NOTE: we cannot use `RustType = Annotation` here because conversion of the - // Rust to a String can fail. - type RustType = (DateTime, RustString<'static>); - - unsafe fn from_ctype(mut self) -> Self::RustType { - // SAFETY: - // - any time_t value is valid - // - time_t is copy, so ownership is not important - let entry = unsafe { libc::time_t::val_from_arg(self.entry) }.unwrap(); - // SAFETY: - // - self.description is valid (came from return_val in as_ctype) - // - self is owned, so we can take ownership of this TCString - let description = - unsafe { TCString::take_val_from_arg(&mut self.description, TCString::default()) }; - (entry, description) - } - - fn as_ctype((entry, description): Self::RustType) -> Self { - TCAnnotation { - entry: libc::time_t::as_ctype(Some(entry)), - // SAFETY: - // - ownership of the TCString tied to ownership of Self - description: unsafe { TCString::return_val(description) }, - } - } -} - -impl Default for TCAnnotation { - fn default() -> Self { - TCAnnotation { - entry: 0 as libc::time_t, - description: TCString::default(), - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 410)] -/// ***** TCAnnotationList ***** -/// -/// TCAnnotationList represents a list of annotations. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCAnnotationList { -/// // number of annotations in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of annotations. These remain owned by the TCAnnotationList instance and will be freed by -/// // tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. -/// struct TCAnnotation *items; -/// } TCAnnotationList; -/// ``` -#[repr(C)] -pub struct TCAnnotationList { - len: libc::size_t, - /// total size of items (internal use only) - capacity: libc::size_t, - items: *mut TCAnnotation, -} - -impl CList for TCAnnotationList { - type Element = TCAnnotation; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCAnnotationList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 401)] -/// Free a TCAnnotation instance. The instance, and the TCString it contains, must not be used -/// after this call. -/// -/// ```c -/// EXTERN_C void tc_annotation_free(struct TCAnnotation *tcann); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_annotation_free(tcann: *mut TCAnnotation) { - debug_assert!(!tcann.is_null()); - // SAFETY: - // - tcann is not NULL - // - *tcann is a valid TCAnnotation (caller promised to treat it as read-only) - let annotation = unsafe { TCAnnotation::take_val_from_arg(tcann, TCAnnotation::default()) }; - drop(annotation); -} - -#[ffizz_header::item] -#[ffizz(order = 411)] -/// Free a TCAnnotationList instance. The instance, and all TCAnnotations it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCAnnotationList. -/// -/// ```c -/// EXTERN_C void tc_annotation_list_free(struct TCAnnotationList *tcanns); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_annotation_list_free(tcanns: *mut TCAnnotationList) { - // SAFETY: - // - tcanns is not NULL and points to a valid TCAnnotationList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tcanns) } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tcanns = unsafe { TCAnnotationList::return_val(Vec::new()) }; - assert!(!tcanns.items.is_null()); - assert_eq!(tcanns.len, 0); - assert_eq!(tcanns.capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcanns = unsafe { TCAnnotationList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_annotation_list_free(&mut tcanns) }; - assert!(tcanns.items.is_null()); - assert_eq!(tcanns.len, 0); - assert_eq!(tcanns.capacity, 0); - } -} diff --git a/taskchampion/lib/src/atomic.rs b/taskchampion/lib/src/atomic.rs deleted file mode 100644 index 01c72059e..000000000 --- a/taskchampion/lib/src/atomic.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Trait implementations for a few atomic types - -use crate::traits::*; -use taskchampion::chrono::{DateTime, Utc}; -use taskchampion::utc_timestamp; - -impl PassByValue for usize { - type RustType = usize; - - unsafe fn from_ctype(self) -> usize { - self - } - - fn as_ctype(arg: usize) -> usize { - arg - } -} - -/// Convert an Option> to a libc::time_t, or zero if not set. -impl PassByValue for libc::time_t { - type RustType = Option>; - - unsafe fn from_ctype(self) -> Option> { - if self != 0 { - return Some(utc_timestamp(self)); - } - None - } - - fn as_ctype(arg: Option>) -> libc::time_t { - arg.map(|ts| ts.timestamp() as libc::time_t) - .unwrap_or(0 as libc::time_t) - } -} diff --git a/taskchampion/lib/src/kv.rs b/taskchampion/lib/src/kv.rs deleted file mode 100644 index 3831c7016..000000000 --- a/taskchampion/lib/src/kv.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::traits::*; -use crate::types::*; - -#[ffizz_header::item] -#[ffizz(order = 600)] -/// ***** TCKV ***** -/// -/// TCKV contains a key/value pair that is part of a task. -/// -/// Neither key nor value are ever NULL. They remain owned by the TCKV and -/// will be freed when it is freed with tc_kv_list_free. -/// -/// ```c -/// typedef struct TCKV { -/// struct TCString key; -/// struct TCString value; -/// } TCKV; -/// ``` -#[repr(C)] -#[derive(Debug)] -pub struct TCKV { - pub key: TCString, - pub value: TCString, -} - -impl PassByValue for TCKV { - type RustType = (RustString<'static>, RustString<'static>); - - unsafe fn from_ctype(self) -> Self::RustType { - // SAFETY: - // - self.key is not NULL (field docstring) - // - self.key came from return_ptr in as_ctype - // - self is owned, so we can take ownership of this TCString - let key = unsafe { TCString::val_from_arg(self.key) }; - // SAFETY: (same) - let value = unsafe { TCString::val_from_arg(self.value) }; - (key, value) - } - - fn as_ctype((key, value): Self::RustType) -> Self { - TCKV { - // SAFETY: - // - ownership of the TCString tied to ownership of Self - key: unsafe { TCString::return_val(key) }, - // SAFETY: - // - ownership of the TCString tied to ownership of Self - value: unsafe { TCString::return_val(value) }, - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 610)] -/// ***** TCKVList ***** -/// -/// TCKVList represents a list of key/value pairs. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCKVList { -/// // number of key/value pairs in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of TCKV's. These remain owned by the TCKVList instance and will be freed by -/// // tc_kv_list_free. This pointer is never NULL for a valid TCKVList. -/// struct TCKV *items; -/// } TCKVList; -/// ``` -#[repr(C)] -#[derive(Debug)] -pub struct TCKVList { - pub len: libc::size_t, - /// total size of items (internal use only) - pub _capacity: libc::size_t, - pub items: *mut TCKV, -} - -impl CList for TCKVList { - type Element = TCKV; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCKVList { - len, - _capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -impl Default for TCKVList { - fn default() -> Self { - // SAFETY: - // - caller will free this list - unsafe { TCKVList::return_val(Vec::new()) } - } -} - -// NOTE: callers never have a TCKV that is not in a list, so there is no tc_kv_free. - -#[ffizz_header::item] -#[ffizz(order = 611)] -/// Free a TCKVList instance. The instance, and all TCKVs it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCKVList. -/// -/// ```c -/// EXTERN_C void tc_kv_list_free(struct TCKVList *tckvs); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_kv_list_free(tckvs: *mut TCKVList) { - // SAFETY: - // - tckvs is not NULL and points to a valid TCKVList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tckvs) } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tckvs = unsafe { TCKVList::return_val(Vec::new()) }; - assert!(!tckvs.items.is_null()); - assert_eq!(tckvs.len, 0); - assert_eq!(tckvs._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tckvs = unsafe { TCKVList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_kv_list_free(&mut tckvs) }; - assert!(tckvs.items.is_null()); - assert_eq!(tckvs.len, 0); - assert_eq!(tckvs._capacity, 0); - } -} diff --git a/taskchampion/lib/src/lib.rs b/taskchampion/lib/src/lib.rs deleted file mode 100644 index ad1fc31f7..000000000 --- a/taskchampion/lib/src/lib.rs +++ /dev/null @@ -1,172 +0,0 @@ -#![warn(unsafe_op_in_unsafe_fn)] -#![allow(unused_unsafe)] -// Not working yet in stable - https://github.com/rust-lang/rust-clippy/issues/8020 -// #![warn(clippy::undocumented_unsafe_blocks)] - -// docstrings for extern "C" functions are reflected into C, and do not benefit -// from safety docs. -#![allow(clippy::missing_safety_doc)] -// deny some things that are typically warnings -#![deny(clippy::derivable_impls)] -#![deny(clippy::wrong_self_convention)] -#![deny(clippy::extra_unused_lifetimes)] -#![deny(clippy::unnecessary_to_owned)] - -// ffizz_header orders: -// -// 000-099: header matter -// 100-199: TCResult -// 200-299: TCString / List -// 300-399: TCUuid / List -// 400-499: TCAnnotation / List -// 500-599: TCUda / List -// 600-699: TCKV / List -// 700-799: TCStatus -// 800-899: TCServer -// 900-999: TCReplica -// 1000-1099: TCTask / List -// 1100-1199: TCWorkingSet -// 10000-10099: footer - -ffizz_header::snippet! { -#[ffizz(name="intro", order=0)] -/// TaskChampion -/// -/// This file defines the C interface to libtaskchampion. This is a thin wrapper around the Rust -/// `taskchampion` crate. Refer to the documentation for that crate at -/// https://docs.rs/taskchampion/latest/taskchampion/ for API details. The comments in this file -/// focus mostly on the low-level details of passing values to and from TaskChampion. -/// -/// # Overview -/// -/// This library defines four major types used to interact with the API, that map directly to Rust -/// types. -/// -/// * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html -/// * TCTask - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html -/// * TCServer - see https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html -/// * TCWorkingSet - see https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html -/// -/// It also defines a few utility types: -/// -/// * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. -/// * TC…List - a list of objects represented as a C array -/// * see below for the remainder -/// -/// # Safety -/// -/// Each type contains specific instructions to ensure memory safety. The general rules are as -/// follows. -/// -/// No types in this library are threadsafe. All values should be used in only one thread for their -/// entire lifetime. It is safe to use unrelated values in different threads (for example, -/// different threads may use different TCReplica values concurrently). -/// -/// ## Pass by Pointer -/// -/// Several types such as TCReplica and TCString are "opaque" types and always handled as pointers -/// in C. The bytes these pointers address are private to the Rust implementation and must not be -/// accessed from C. -/// -/// Pass-by-pointer values have exactly one owner, and that owner is responsible for freeing the -/// value (using a `tc_…_free` function), or transferring ownership elsewhere. Except where -/// documented otherwise, when a value is passed to C, ownership passes to C as well. When a value -/// is passed to Rust, ownership stays with the C code. The exception is TCString, ownership of -/// which passes to Rust when it is used as a function argument. -/// -/// The limited circumstances where one value must not outlive another, due to pointer references -/// between them, are documented below. -/// -/// ## Pass by Value -/// -/// Types such as TCUuid and TC…List are passed by value, and contain fields that are accessible -/// from C. C code is free to access the content of these types in a _read_only_ fashion. -/// -/// Pass-by-value values that contain pointers also have exactly one owner, responsible for freeing -/// the value or transferring ownership. The tc_…_free functions for these types will replace the -/// pointers with NULL to guard against use-after-free errors. The interior pointers in such values -/// should never be freed directly (for example, `tc_string_free(tcuda.value)` is an error). -/// -/// TCUuid is a special case, because it does not contain pointers. It can be freely copied and -/// need not be freed. -/// -/// ## Lists -/// -/// Lists are a special kind of pass-by-value type. Each contains `len` and `items`, where `items` -/// is an array of length `len`. Lists, and the values in the `items` array, must be treated as -/// read-only. On return from an API function, a list's ownership is with the C caller, which must -/// eventually free the list. List data must be freed with the `tc_…_list_free` function. It is an -/// error to free any value in the `items` array of a list. -} - -ffizz_header::snippet! { -#[ffizz(name="topmatter", order=1)] -/// ```c -/// #ifndef TASKCHAMPION_H -/// #define TASKCHAMPION_H -/// -/// #include -/// #include -/// #include -/// -/// #ifdef __cplusplus -/// #define EXTERN_C extern "C" -/// #else -/// #define EXTERN_C -/// #endif // __cplusplus -/// ``` -} - -ffizz_header::snippet! { -#[ffizz(name="bottomatter", order=10000)] -/// ```c -/// #endif /* TASKCHAMPION_H */ -/// ``` -} - -mod traits; -mod util; - -pub mod annotation; -pub use annotation::*; -pub mod atomic; -pub mod kv; -pub use kv::*; -pub mod replica; -pub use replica::*; -pub mod result; -pub use result::*; -pub mod server; -pub use server::*; -pub mod status; -pub use status::*; -pub mod string; -pub use string::*; -pub mod task; -pub use task::*; -pub mod uda; -pub use uda::*; -pub mod uuid; -pub use uuid::*; -pub mod workingset; -pub use workingset::*; - -pub(crate) mod types { - pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList}; - pub(crate) use crate::kv::TCKVList; - pub(crate) use crate::replica::TCReplica; - pub(crate) use crate::result::TCResult; - pub(crate) use crate::server::TCServer; - pub(crate) use crate::status::TCStatus; - pub(crate) use crate::string::{RustString, TCString, TCStringList}; - pub(crate) use crate::task::{TCTask, TCTaskList}; - pub(crate) use crate::uda::{TCUda, TCUdaList, Uda}; - pub(crate) use crate::uuid::{TCUuid, TCUuidList}; - pub(crate) use crate::workingset::TCWorkingSet; -} - -#[cfg(debug_assertions)] -/// Generate the taskchapion.h header -pub fn generate_header() -> String { - ffizz_header::generate() -} diff --git a/taskchampion/lib/src/replica.rs b/taskchampion/lib/src/replica.rs deleted file mode 100644 index e6956f431..000000000 --- a/taskchampion/lib/src/replica.rs +++ /dev/null @@ -1,904 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use crate::util::err_to_ruststring; -use std::ptr::NonNull; -use taskchampion::storage::ReplicaOp; -use taskchampion::{Replica, StorageConfig}; - -#[ffizz_header::item] -#[ffizz(order = 900)] -/// ***** TCReplica ***** -/// -/// A replica represents an instance of a user's task data, providing an easy interface -/// for querying and modifying that data. -/// -/// # Error Handling -/// -/// When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then -/// `tc_replica_error` will return the error message. -/// -/// # Safety -/// -/// The `*TCReplica` returned from `tc_replica_new…` functions is owned by the caller and -/// must later be freed to avoid a memory leak. -/// -/// Any function taking a `*TCReplica` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from a tc_… function; -/// - the memory referenced by the pointer must never be modified by C code; and -/// - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller. -/// -/// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. -/// -/// TCReplicas are not threadsafe. -/// -/// ```c -/// typedef struct TCReplica TCReplica; -/// ``` -pub struct TCReplica { - /// The wrapped Replica - inner: Replica, - - /// If true, this replica has an outstanding &mut (for a TaskMut) - mut_borrowed: bool, - - /// The error from the most recent operation, if any - error: Option>, -} - -impl PassByPointer for TCReplica {} - -impl TCReplica { - /// Mutably borrow the inner Replica - pub(crate) fn borrow_mut(&mut self) -> &mut Replica { - if self.mut_borrowed { - panic!("replica is already borrowed"); - } - self.mut_borrowed = true; - &mut self.inner - } - - /// Release the borrow made by [`borrow_mut`] - pub(crate) fn release_borrow(&mut self) { - if !self.mut_borrowed { - panic!("replica is not borrowed"); - } - self.mut_borrowed = false; - } -} - -impl From for TCReplica { - fn from(rep: Replica) -> TCReplica { - TCReplica { - inner: rep, - mut_borrowed: false, - error: None, - } - } -} - -/// Utility function to allow using `?` notation to return an error value. This makes -/// a mutable borrow, because most Replica methods require a `&mut`. -fn wrap(rep: *mut TCReplica, f: F, err_value: T) -> T -where - F: FnOnce(&mut Replica) -> anyhow::Result, -{ - debug_assert!(!rep.is_null()); - // SAFETY: - // - rep is not NULL (promised by caller) - // - *rep is a valid TCReplica (promised by caller) - // - rep is valid for the duration of this function - // - rep is not modified by anything else (not threadsafe) - let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; - if rep.mut_borrowed { - panic!("replica is borrowed and cannot be used"); - } - rep.error = None; - match f(&mut rep.inner) { - Ok(v) => v, - Err(e) => { - rep.error = Some(err_to_ruststring(e)); - err_value - } - } -} - -/// Utility function to allow using `?` notation to return an error value in the constructor. -fn wrap_constructor(f: F, error_out: *mut TCString, err_value: T) -> T -where - F: FnOnce() -> anyhow::Result, -{ - if !error_out.is_null() { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - unsafe { *error_out = TCString::default() }; - } - - match f() { - Ok(v) => v, - Err(e) => { - if !error_out.is_null() { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - unsafe { - TCString::val_to_arg_out(err_to_ruststring(e), error_out); - } - } - err_value - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 900)] -/// ***** TCReplicaOpType ***** -/// -/// ```c -/// enum TCReplicaOpType -/// #ifdef __cplusplus -/// : uint32_t -/// #endif // __cplusplus -/// { -/// Create = 0, -/// Delete = 1, -/// Update = 2, -/// UndoPoint = 3, -/// }; -/// #ifndef __cplusplus -/// typedef uint32_t TCReplicaOpType; -/// #endif // __cplusplus -/// ``` -#[derive(Debug, Default)] -#[repr(u32)] -pub enum TCReplicaOpType { - Create = 0, - Delete = 1, - Update = 2, - UndoPoint = 3, - #[default] - Error = 4, -} - -#[ffizz_header::item] -#[ffizz(order = 901)] -/// Create a new TCReplica with an in-memory database. The contents of the database will be -/// lost when it is freed with tc_replica_free. -/// -/// ```c -/// EXTERN_C struct TCReplica *tc_replica_new_in_memory(void); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { - let storage = StorageConfig::InMemory - .into_storage() - .expect("in-memory always succeeds"); - // SAFETY: - // - caller promises to free this value - unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } -} - -#[ffizz_header::item] -#[ffizz(order = 901)] -/// Create a new TCReplica with an on-disk database having the given filename. On error, a string -/// is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller -/// must free this string. -/// -/// ```c -/// EXTERN_C struct TCReplica *tc_replica_new_on_disk(struct TCString path, -/// bool create_if_missing, -/// struct TCString *error_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_new_on_disk( - path: TCString, - create_if_missing: bool, - error_out: *mut TCString, -) -> *mut TCReplica { - wrap_constructor( - || { - // SAFETY: - // - path is valid (promised by caller) - // - caller will not use path after this call (convention) - let mut path = unsafe { TCString::val_from_arg(path) }; - let storage = StorageConfig::OnDisk { - taskdb_dir: path.to_path_buf_mut()?, - create_if_missing, - } - .into_storage()?; - - // SAFETY: - // - caller promises to free this value - Ok(unsafe { TCReplica::from(Replica::new(storage)).return_ptr() }) - }, - error_out, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 901)] -/// ***** TCReplicaOp ***** -/// -/// ```c -/// struct TCReplicaOp { -/// TCReplicaOpType operation_type; -/// void* inner; -/// }; -/// -/// typedef struct TCReplicaOp TCReplicaOp; -/// ``` -#[derive(Debug)] -#[repr(C)] -pub struct TCReplicaOp { - operation_type: TCReplicaOpType, - inner: Box, -} - -impl From for TCReplicaOp { - fn from(replica_op: ReplicaOp) -> TCReplicaOp { - match replica_op { - ReplicaOp::Create { .. } => TCReplicaOp { - operation_type: TCReplicaOpType::Create, - inner: Box::new(replica_op), - }, - ReplicaOp::Delete { .. } => TCReplicaOp { - operation_type: TCReplicaOpType::Delete, - inner: Box::new(replica_op), - }, - ReplicaOp::Update { .. } => TCReplicaOp { - operation_type: TCReplicaOpType::Update, - inner: Box::new(replica_op), - }, - ReplicaOp::UndoPoint => TCReplicaOp { - operation_type: TCReplicaOpType::UndoPoint, - inner: Box::new(replica_op), - }, - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 901)] -/// ***** TCReplicaOpList ***** -/// -/// ```c -/// struct TCReplicaOpList { -/// struct TCReplicaOp *items; -/// size_t len; -/// size_t capacity; -/// }; -/// -/// typedef struct TCReplicaOpList TCReplicaOpList; -/// ``` -#[repr(C)] -#[derive(Debug)] -pub struct TCReplicaOpList { - items: *mut TCReplicaOp, - len: usize, - capacity: usize, -} - -impl Default for TCReplicaOpList { - fn default() -> Self { - // SAFETY: - // - caller will free this value - unsafe { TCReplicaOpList::return_val(Vec::new()) } - } -} - -impl CList for TCReplicaOpList { - type Element = TCReplicaOp; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCReplicaOpList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get a list of all tasks in the replica. -/// -/// Returns a TCTaskList with a NULL items field on error. -/// -/// ```c -/// EXTERN_C struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList { - wrap( - rep, - |rep| { - // note that the Replica API returns a hashmap here, but we discard - // the keys and return a simple list. The task UUIDs are available - // from task.get_uuid(), so information is not lost. - let tasks: Vec<_> = rep - .all_tasks()? - .drain() - .map(|(_uuid, t)| { - Some( - NonNull::new( - // SAFETY: - // - caller promises to free this value (via freeing the list) - unsafe { TCTask::from(t).return_ptr() }, - ) - .expect("TCTask::return_ptr returned NULL"), - ) - }) - .collect(); - // SAFETY: - // - value is not allocated and need not be freed - Ok(unsafe { TCTaskList::return_val(tasks) }) - }, - TCTaskList::null_value(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get a list of all uuids for tasks in the replica. -/// -/// Returns a TCUuidList with a NULL items field on error. -/// -/// The caller must free the UUID list with `tc_uuid_list_free`. -/// -/// ```c -/// EXTERN_C struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUuidList { - wrap( - rep, - |rep| { - let uuids: Vec<_> = rep - .all_task_uuids()? - .drain(..) - // SAFETY: - // - value is not allocated and need not be freed - .map(|uuid| unsafe { TCUuid::return_val(uuid) }) - .collect(); - // SAFETY: - // - value will be freed (promised by caller) - Ok(unsafe { TCUuidList::return_val(uuids) }) - }, - TCUuidList::null_value(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get the current working set for this replica. The resulting value must be freed -/// with tc_working_set_free. -/// -/// Returns NULL on error. -/// -/// ```c -/// EXTERN_C struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCWorkingSet { - wrap( - rep, - |rep| { - let ws = rep.working_set()?; - // SAFETY: - // - caller promises to free this value - Ok(unsafe { TCWorkingSet::return_ptr(ws.into()) }) - }, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get an existing task by its UUID. -/// -/// Returns NULL when the task does not exist, and on error. Consult tc_replica_error -/// to distinguish the two conditions. -/// -/// ```c -/// EXTERN_C struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask { - wrap( - rep, - |rep| { - // SAFETY: - // - tcuuid is a valid TCUuid (all bytes are valid) - // - tcuuid is Copy so ownership doesn't matter - let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; - if let Some(task) = rep.get_task(uuid)? { - // SAFETY: - // - caller promises to free this task - Ok(unsafe { TCTask::from(task).return_ptr() }) - } else { - Ok(std::ptr::null_mut()) - } - }, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Create a new task. The task must not already exist. -/// -/// Returns the task, or NULL on error. -/// -/// ```c -/// EXTERN_C struct TCTask *tc_replica_new_task(struct TCReplica *rep, -/// enum TCStatus status, -/// struct TCString description); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_new_task( - rep: *mut TCReplica, - status: TCStatus, - description: TCString, -) -> *mut TCTask { - // SAFETY: - // - description is valid (promised by caller) - // - caller will not use description after this call (convention) - let mut description = unsafe { TCString::val_from_arg(description) }; - wrap( - rep, - |rep| { - let task = rep.new_task(status.into(), description.as_str()?.to_string())?; - // SAFETY: - // - caller promises to free this task - Ok(unsafe { TCTask::from(task).return_ptr() }) - }, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Create a new task. The task must not already exist. -/// -/// Returns the task, or NULL on error. -/// -/// ```c -/// EXTERN_C struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_import_task_with_uuid( - rep: *mut TCReplica, - tcuuid: TCUuid, -) -> *mut TCTask { - wrap( - rep, - |rep| { - // SAFETY: - // - tcuuid is a valid TCUuid (all bytes are valid) - // - tcuuid is Copy so ownership doesn't matter - let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; - let task = rep.import_task_with_uuid(uuid)?; - // SAFETY: - // - caller promises to free this task - Ok(unsafe { TCTask::from(task).return_ptr() }) - }, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Synchronize this replica with a server. -/// -/// The `server` argument remains owned by the caller, and must be freed explicitly. -/// -/// ```c -/// EXTERN_C TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_sync( - rep: *mut TCReplica, - server: *mut TCServer, - avoid_snapshots: bool, -) -> TCResult { - wrap( - rep, - |rep| { - debug_assert!(!server.is_null()); - // SAFETY: - // - server is not NULL - // - *server is a valid TCServer (promised by caller) - // - server is valid for the lifetime of tc_replica_sync (not threadsafe) - // - server will not be accessed simultaneously (not threadsafe) - let server = unsafe { TCServer::from_ptr_arg_ref_mut(server) }; - rep.sync(server.as_mut(), avoid_snapshots)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Return undo local operations until the most recent UndoPoint. -/// -/// ```c -/// EXTERN_C TCReplicaOpList tc_replica_get_undo_ops(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_get_undo_ops(rep: *mut TCReplica) -> TCReplicaOpList { - wrap( - rep, - |rep| { - // SAFETY: - // - caller will free this value, either with tc_replica_commit_undo_ops or - // tc_replica_op_list_free. - Ok(unsafe { - TCReplicaOpList::return_val( - rep.get_undo_ops()? - .into_iter() - .map(TCReplicaOp::from) - .collect(), - ) - }) - }, - TCReplicaOpList::default(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Undo local operations in storage. -/// -/// If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if -/// there are no operations that can be done. -/// -/// ```c -/// EXTERN_C TCResult tc_replica_commit_undo_ops(struct TCReplica *rep, TCReplicaOpList tc_undo_ops, int32_t *undone_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_commit_undo_ops( - rep: *mut TCReplica, - tc_undo_ops: TCReplicaOpList, - undone_out: *mut i32, -) -> TCResult { - wrap( - rep, - |rep| { - // SAFETY: - // - `tc_undo_ops` is a valid value, as it was acquired from `tc_replica_get_undo_ops`. - let undo_ops: Vec = unsafe { TCReplicaOpList::val_from_arg(tc_undo_ops) } - .into_iter() - .map(|op| *op.inner) - .collect(); - let undone = i32::from(rep.commit_undo_ops(undo_ops)?); - if !undone_out.is_null() { - // SAFETY: - // - undone_out is not NULL (just checked) - // - undone_out is properly aligned (implicitly promised by caller) - unsafe { *undone_out = undone }; - } - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get the number of local, un-synchronized operations (not including undo points), or -1 on -/// error. -/// -/// ```c -/// EXTERN_C int64_t tc_replica_num_local_operations(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_num_local_operations(rep: *mut TCReplica) -> i64 { - wrap( - rep, - |rep| { - let count = rep.num_local_operations()? as i64; - Ok(count) - }, - -1, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get the number of undo points (number of undo calls possible), or -1 on error. -/// -/// ```c -/// EXTERN_C int64_t tc_replica_num_undo_points(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_num_undo_points(rep: *mut TCReplica) -> i64 { - wrap( - rep, - |rep| { - let count = rep.num_undo_points()? as i64; - Ok(count) - }, - -1, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically -/// when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already -/// been created by this Replica, and may be useful when a Replica instance is held for a long time -/// and used to apply more than one user-visible change. -/// -/// ```c -/// EXTERN_C TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_add_undo_point(rep: *mut TCReplica, force: bool) -> TCResult { - wrap( - rep, - |rep| { - rep.add_undo_point(force)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Rebuild this replica's working set, based on whether tasks are pending or not. If `renumber` -/// is true, then existing tasks may be moved to new working-set indices; in any case, on -/// completion all pending tasks are in the working set and all non- pending tasks are not. -/// -/// ```c -/// EXTERN_C TCResult tc_replica_rebuild_working_set(struct TCReplica *rep, bool renumber); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_rebuild_working_set( - rep: *mut TCReplica, - renumber: bool, -) -> TCResult { - wrap( - rep, - |rep| { - rep.rebuild_working_set(renumber)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get the latest error for a replica, or a string with NULL ptr if no error exists. Subsequent -/// calls to this function will return NULL. The rep pointer must not be NULL. The caller must -/// free the returned string. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_error(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> TCString { - // SAFETY: - // - rep is not NULL (promised by caller) - // - *rep is a valid TCReplica (promised by caller) - // - rep is valid for the duration of this function - // - rep is not modified by anything else (not threadsafe) - let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; - if let Some(rstring) = rep.error.take() { - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(rstring) } - } else { - TCString::default() - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Free a replica. The replica may not be used after this function returns and must not be freed -/// more than once. -/// -/// ```c -/// EXTERN_C void tc_replica_free(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) { - // SAFETY: - // - replica is not NULL (promised by caller) - // - replica is valid (promised by caller) - // - caller will not use description after this call (promised by caller) - let replica = unsafe { TCReplica::take_from_ptr_arg(rep) }; - if replica.mut_borrowed { - panic!("replica is borrowed and cannot be freed"); - } - drop(replica); -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Free a vector of ReplicaOp. The vector may not be used after this function returns and must not be freed -/// more than once. -/// -/// ```c -/// EXTERN_C void tc_replica_op_list_free(struct TCReplicaOpList *oplist); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_list_free(oplist: *mut TCReplicaOpList) { - debug_assert!(!oplist.is_null()); - // SAFETY: - // - arg is not NULL (just checked) - // - `*oplist` is valid (guaranteed by caller not double-freeing this value) - unsafe { - TCReplicaOpList::take_val_from_arg( - oplist, - // SAFETY: - // - value is empty, so the caller need not free it. - TCReplicaOpList::return_val(Vec::new()), - ) - }; -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return uuid field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_uuid(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_uuid(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Create { uuid } - | ReplicaOp::Delete { uuid, .. } - | ReplicaOp::Update { uuid, .. } = rop - { - let uuid_rstr: RustString = uuid.to_string().into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(uuid_rstr) } - } else { - panic!("Operation has no uuid: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return property field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_property(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_property(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Update { property, .. } = rop { - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(property.clone().into()) } - } else { - panic!("Operation has no property: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return value field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_value(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_value(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Update { value, .. } = rop { - let value_rstr: RustString = value.clone().unwrap_or(String::new()).into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(value_rstr) } - } else { - panic!("Operation has no value: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return old value field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_old_value(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_old_value(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Update { old_value, .. } = rop { - let old_value_rstr: RustString = old_value.clone().unwrap_or(String::new()).into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(old_value_rstr) } - } else { - panic!("Operation has no old value: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return timestamp field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_timestamp(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_timestamp(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Update { timestamp, .. } = rop { - let timestamp_rstr: RustString = timestamp.to_string().into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(timestamp_rstr) } - } else { - panic!("Operation has no timestamp: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return description field of old task field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_old_task_description(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_old_task_description( - op: *const TCReplicaOp, -) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Delete { old_task, .. } = rop { - let description_rstr: RustString = old_task["description"].clone().into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(description_rstr) } - } else { - panic!("Operation has no timestamp: {:#?}", rop); - } -} diff --git a/taskchampion/lib/src/result.rs b/taskchampion/lib/src/result.rs deleted file mode 100644 index bb72efb1d..000000000 --- a/taskchampion/lib/src/result.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[ffizz_header::item] -#[ffizz(order = 100)] -/// ***** TCResult ***** -/// -/// A result from a TC operation. Typically if this value is TC_RESULT_ERROR, -/// the associated object's `tc_.._error` method will return an error message. -/// -/// ```c -/// enum TCResult -/// #ifdef __cplusplus -/// : int32_t -/// #endif // __cplusplus -/// { -/// TC_RESULT_ERROR = -1, -/// TC_RESULT_OK = 0, -/// }; -/// #ifndef __cplusplus -/// typedef int32_t TCResult; -/// #endif // __cplusplus -/// ``` -#[repr(i32)] -pub enum TCResult { - Error = -1, - Ok = 0, -} diff --git a/taskchampion/lib/src/server.rs b/taskchampion/lib/src/server.rs deleted file mode 100644 index 47b79debc..000000000 --- a/taskchampion/lib/src/server.rs +++ /dev/null @@ -1,234 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use crate::util::err_to_ruststring; -use taskchampion::{Server, ServerConfig}; - -#[ffizz_header::item] -#[ffizz(order = 800)] -/// ***** TCServer ***** -/// -/// TCServer represents an interface to a sync server. Aside from new and free, a server -/// has no C-accessible API, but is designed to be passed to `tc_replica_sync`. -/// -/// ## Safety -/// -/// TCServer are not threadsafe, and must not be used with multiple replicas simultaneously. -/// -/// ```c -/// typedef struct TCServer TCServer; -/// ``` -pub struct TCServer(Box); - -impl PassByPointer for TCServer {} - -impl From> for TCServer { - fn from(server: Box) -> TCServer { - TCServer(server) - } -} - -impl AsMut> for TCServer { - fn as_mut(&mut self) -> &mut Box { - &mut self.0 - } -} - -/// Utility function to allow using `?` notation to return an error value. -fn wrap(f: F, error_out: *mut TCString, err_value: T) -> T -where - F: FnOnce() -> anyhow::Result, -{ - if !error_out.is_null() { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - unsafe { *error_out = TCString::default() }; - } - - match f() { - Ok(v) => v, - Err(e) => { - if !error_out.is_null() { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - unsafe { - TCString::val_to_arg_out(err_to_ruststring(e), error_out); - } - } - err_value - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 801)] -/// Create a new TCServer that operates locally (on-disk). See the TaskChampion docs for the -/// description of the arguments. -/// -/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -/// returned. The caller must free this string. -/// -/// The server must be freed after it is used - tc_replica_sync does not automatically free it. -/// -/// ```c -/// EXTERN_C struct TCServer *tc_server_new_local(struct TCString server_dir, struct TCString *error_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_server_new_local( - server_dir: TCString, - error_out: *mut TCString, -) -> *mut TCServer { - wrap( - || { - // SAFETY: - // - server_dir is valid (promised by caller) - // - caller will not use server_dir after this call (convention) - let mut server_dir = unsafe { TCString::val_from_arg(server_dir) }; - let server_config = ServerConfig::Local { - server_dir: server_dir.to_path_buf_mut()?, - }; - let server = server_config.into_server()?; - // SAFETY: caller promises to free this server. - Ok(unsafe { TCServer::return_ptr(server.into()) }) - }, - error_out, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 801)] -/// Create a new TCServer that connects to a remote server. See the TaskChampion docs for the -/// description of the arguments. -/// -/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -/// returned. The caller must free this string. -/// -/// The server must be freed after it is used - tc_replica_sync does not automatically free it. -/// -/// ```c -/// EXTERN_C struct TCServer *tc_server_new_sync(struct TCString origin, -/// struct TCUuid client_id, -/// struct TCString encryption_secret, -/// struct TCString *error_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_server_new_sync( - origin: TCString, - client_id: TCUuid, - encryption_secret: TCString, - error_out: *mut TCString, -) -> *mut TCServer { - wrap( - || { - // SAFETY: - // - origin is valid (promised by caller) - // - origin ownership is transferred to this function - let origin = unsafe { TCString::val_from_arg(origin) }.into_string()?; - - // SAFETY: - // - client_id is a valid Uuid (any 8-byte sequence counts) - let client_id = unsafe { TCUuid::val_from_arg(client_id) }; - - // SAFETY: - // - encryption_secret is valid (promised by caller) - // - encryption_secret ownership is transferred to this function - let encryption_secret = unsafe { TCString::val_from_arg(encryption_secret) } - .as_bytes() - .to_vec(); - - let server_config = ServerConfig::Remote { - origin, - client_id, - encryption_secret, - }; - let server = server_config.into_server()?; - // SAFETY: caller promises to free this server. - Ok(unsafe { TCServer::return_ptr(server.into()) }) - }, - error_out, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 802)] -/// Create a new TCServer that connects to the Google Cloud Platform. See the TaskChampion docs -/// for the description of the arguments. -/// -/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -/// returned. The caller must free this string. -/// -/// The server must be freed after it is used - tc_replica_sync does not automatically free it. -/// -/// ```c -/// EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket, -/// struct TCString credential_path, -/// struct TCString encryption_secret, -/// struct TCString *error_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_server_new_gcp( - bucket: TCString, - credential_path_argument: TCString, - encryption_secret: TCString, - error_out: *mut TCString, -) -> *mut TCServer { - wrap( - || { - // SAFETY: - // - bucket is valid (promised by caller) - // - bucket ownership is transferred to this function - let bucket = unsafe { TCString::val_from_arg(bucket) }.into_string()?; - - // SAFETY: - // - credential_path is valid (promised by caller) - // - credential_path ownership is transferred to this function - - let credential_path = - unsafe { TCString::val_from_arg(credential_path_argument) }.into_string()?; - let credential_path = if credential_path.is_empty() { - None - } else { - Some(credential_path) - }; - - // SAFETY: - // - encryption_secret is valid (promised by caller) - // - encryption_secret ownership is transferred to this function - let encryption_secret = unsafe { TCString::val_from_arg(encryption_secret) } - .as_bytes() - .to_vec(); - let server_config = ServerConfig::Gcp { - bucket, - credential_path, - encryption_secret, - }; - let server = server_config.into_server()?; - // SAFETY: caller promises to free this server. - Ok(unsafe { TCServer::return_ptr(server.into()) }) - }, - error_out, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 899)] -/// Free a server. The server may not be used after this function returns and must not be freed -/// more than once. -/// -/// ```c -/// EXTERN_C void tc_server_free(struct TCServer *server); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_server_free(server: *mut TCServer) { - debug_assert!(!server.is_null()); - // SAFETY: - // - server is not NULL - // - server came from tc_server_new_.., which used return_ptr - // - server will not be used after (promised by caller) - let server = unsafe { TCServer::take_from_ptr_arg(server) }; - drop(server); -} diff --git a/taskchampion/lib/src/status.rs b/taskchampion/lib/src/status.rs deleted file mode 100644 index e0d370136..000000000 --- a/taskchampion/lib/src/status.rs +++ /dev/null @@ -1,60 +0,0 @@ -pub use taskchampion::Status; - -#[ffizz_header::item] -#[ffizz(order = 700)] -/// ***** TCStatus ***** -/// -/// The status of a task, as defined by the task data model. -/// -/// ```c -/// #ifdef __cplusplus -/// typedef enum TCStatus : int32_t { -/// #else // __cplusplus -/// typedef int32_t TCStatus; -/// enum TCStatus { -/// #endif // __cplusplus -/// TC_STATUS_PENDING = 0, -/// TC_STATUS_COMPLETED = 1, -/// TC_STATUS_DELETED = 2, -/// TC_STATUS_RECURRING = 3, -/// // Unknown signifies a status in the task DB that was not -/// // recognized. -/// TC_STATUS_UNKNOWN = -1, -/// #ifdef __cplusplus -/// } TCStatus; -/// #else // __cplusplus -/// }; -/// #endif // __cplusplus -/// ``` -#[repr(i32)] -pub enum TCStatus { - Pending = 0, - Completed = 1, - Deleted = 2, - Recurring = 3, - Unknown = -1, -} - -impl From for Status { - fn from(status: TCStatus) -> Status { - match status { - TCStatus::Pending => Status::Pending, - TCStatus::Completed => Status::Completed, - TCStatus::Deleted => Status::Deleted, - TCStatus::Recurring => Status::Recurring, - _ => Status::Unknown(format!("unknown TCStatus {}", status as u32)), - } - } -} - -impl From for TCStatus { - fn from(status: Status) -> TCStatus { - match status { - Status::Pending => TCStatus::Pending, - Status::Completed => TCStatus::Completed, - Status::Deleted => TCStatus::Deleted, - Status::Recurring => TCStatus::Recurring, - Status::Unknown(_) => TCStatus::Unknown, - } - } -} diff --git a/taskchampion/lib/src/string.rs b/taskchampion/lib/src/string.rs deleted file mode 100644 index 01036c999..000000000 --- a/taskchampion/lib/src/string.rs +++ /dev/null @@ -1,773 +0,0 @@ -use crate::traits::*; -use crate::util::{string_into_raw_parts, vec_into_raw_parts}; -use std::ffi::{CStr, CString, OsString}; -use std::os::raw::c_char; -use std::path::PathBuf; - -#[ffizz_header::item] -#[ffizz(order = 200)] -/// ***** TCString ***** -/// -/// TCString supports passing strings into and out of the TaskChampion API. -/// -/// # Rust Strings and C Strings -/// -/// A Rust string can contain embedded NUL characters, while C considers such a character to mark -/// the end of a string. Strings containing embedded NULs cannot be represented as a "C string" -/// and must be accessed using `tc_string_content_and_len` and `tc_string_clone_with_len`. In -/// general, these two functions should be used for handling arbitrary data, while more convenient -/// forms may be used where embedded NUL characters are impossible, such as in static strings. -/// -/// # UTF-8 -/// -/// TaskChampion expects all strings to be valid UTF-8. `tc_string_…` functions will fail if given -/// a `*TCString` containing invalid UTF-8. -/// -/// # Safety -/// -/// The `ptr` field may be checked for NULL, where documentation indicates this is possible. All -/// other fields in a TCString are private and must not be used from C. They exist in the struct -/// to ensure proper allocation and alignment. -/// -/// When a `TCString` appears as a return value or output argument, ownership is passed to the -/// caller. The caller must pass that ownership back to another function or free the string. -/// -/// Any function taking a `TCString` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from a tc_… function; and -/// - the memory referenced by the pointer must never be modified by C code. -/// -/// Unless specified otherwise, TaskChampion functions take ownership of a `TCString` when it is -/// given as a function argument, and the caller must not use or free TCStrings after passing them -/// to such API functions. -/// -/// A TCString with a NULL `ptr` field need not be freed, although tc_free_string will not fail -/// for such a value. -/// -/// TCString is not threadsafe. -/// -/// ```c -/// typedef struct TCString { -/// void *ptr; // opaque, but may be checked for NULL -/// size_t _u1; // reserved -/// size_t _u2; // reserved -/// uint8_t _u3; // reserved -/// } TCString; -/// ``` -#[repr(C)] -#[derive(Debug)] -pub struct TCString { - // defined based on the type - ptr: *mut libc::c_void, - len: usize, - cap: usize, - - // type of TCString this represents - ty: u8, -} - -// TODO: figure out how to ignore this but still use it in TCString -/// A discriminator for TCString -#[repr(u8)] -enum TCStringType { - /// Null. Nothing is contained in this string. - /// - /// * `ptr` is NULL. - /// * `len` and `cap` are zero. - Null = 0, - - /// A CString. - /// - /// * `ptr` is the result of CString::into_raw, containing a terminating NUL. It may not be - /// valid UTF-8. - /// * `len` and `cap` are zero. - CString, - - /// A CStr, referencing memory borrowed from C - /// - /// * `ptr` points to the string, containing a terminating NUL. It may not be valid UTF-8. - /// * `len` and `cap` are zero. - CStr, - - /// A String. - /// - /// * `ptr`, `len`, and `cap` are as would be returned from String::into_raw_parts. - String, - - /// A byte sequence. - /// - /// * `ptr`, `len`, and `cap` are as would be returned from Vec::into_raw_parts. - Bytes, -} - -impl Default for TCString { - fn default() -> Self { - TCString { - ptr: std::ptr::null_mut(), - len: 0, - cap: 0, - ty: TCStringType::Null as u8, - } - } -} - -impl TCString { - pub(crate) fn is_null(&self) -> bool { - self.ptr.is_null() - } -} - -#[derive(PartialEq, Eq, Debug, Default)] -pub enum RustString<'a> { - #[default] - Null, - CString(CString), - CStr(&'a CStr), - String(String), - Bytes(Vec), -} - -impl PassByValue for TCString { - type RustType = RustString<'static>; - - unsafe fn from_ctype(self) -> Self::RustType { - match self.ty { - ty if ty == TCStringType::CString as u8 => { - // SAFETY: - // - ptr was derived from CString::into_raw - // - data was not modified since that time (caller promises) - RustString::CString(unsafe { CString::from_raw(self.ptr as *mut c_char) }) - } - ty if ty == TCStringType::CStr as u8 => { - // SAFETY: - // - ptr was created by CStr::as_ptr - // - data was not modified since that time (caller promises) - RustString::CStr(unsafe { CStr::from_ptr(self.ptr as *mut c_char) }) - } - ty if ty == TCStringType::String as u8 => { - // SAFETY: - // - ptr was created by string_into_raw_parts - // - data was not modified since that time (caller promises) - RustString::String(unsafe { - String::from_raw_parts(self.ptr as *mut u8, self.len, self.cap) - }) - } - ty if ty == TCStringType::Bytes as u8 => { - // SAFETY: - // - ptr was created by vec_into_raw_parts - // - data was not modified since that time (caller promises) - RustString::Bytes(unsafe { - Vec::from_raw_parts(self.ptr as *mut u8, self.len, self.cap) - }) - } - _ => RustString::Null, - } - } - - fn as_ctype(arg: Self::RustType) -> Self { - match arg { - RustString::Null => Self { - ty: TCStringType::Null as u8, - ..Default::default() - }, - RustString::CString(cstring) => Self { - ty: TCStringType::CString as u8, - ptr: cstring.into_raw() as *mut libc::c_void, - ..Default::default() - }, - RustString::CStr(cstr) => Self { - ty: TCStringType::CStr as u8, - ptr: cstr.as_ptr() as *mut libc::c_void, - ..Default::default() - }, - RustString::String(string) => { - let (ptr, len, cap) = string_into_raw_parts(string); - Self { - ty: TCStringType::String as u8, - ptr: ptr as *mut libc::c_void, - len, - cap, - } - } - RustString::Bytes(bytes) => { - let (ptr, len, cap) = vec_into_raw_parts(bytes); - Self { - ty: TCStringType::Bytes as u8, - ptr: ptr as *mut libc::c_void, - len, - cap, - } - } - } - } -} - -impl<'a> RustString<'a> { - /// Get a regular Rust &str for this value. - pub(crate) fn as_str(&mut self) -> Result<&str, std::str::Utf8Error> { - match self { - RustString::CString(cstring) => cstring.as_c_str().to_str(), - RustString::CStr(cstr) => cstr.to_str(), - RustString::String(ref string) => Ok(string.as_ref()), - RustString::Bytes(_) => { - self.bytes_to_string()?; - self.as_str() // now the String variant, so won't recurse - } - RustString::Null => unreachable!(), - } - } - - /// Consume this RustString and return an equivalent String, or an error if not - /// valid UTF-8. In the error condition, the original data is lost. - pub(crate) fn into_string(mut self) -> Result { - match self { - RustString::CString(cstring) => cstring.into_string().map_err(|e| e.utf8_error()), - RustString::CStr(cstr) => cstr.to_str().map(|s| s.to_string()), - RustString::String(string) => Ok(string), - RustString::Bytes(_) => { - self.bytes_to_string()?; - self.into_string() // now the String variant, so won't recurse - } - RustString::Null => unreachable!(), - } - } - - pub(crate) fn as_bytes(&self) -> &[u8] { - match self { - RustString::CString(cstring) => cstring.as_bytes(), - RustString::CStr(cstr) => cstr.to_bytes(), - RustString::String(string) => string.as_bytes(), - RustString::Bytes(bytes) => bytes.as_ref(), - RustString::Null => unreachable!(), - } - } - - /// Convert the RustString, in place, from the Bytes to String variant. On successful return, - /// the RustString has variant RustString::String. - fn bytes_to_string(&mut self) -> Result<(), std::str::Utf8Error> { - let mut owned = RustString::Null; - // temporarily swap a Null value into self; we'll swap that back - // shortly. - std::mem::swap(self, &mut owned); - match owned { - RustString::Bytes(bytes) => match String::from_utf8(bytes) { - Ok(string) => { - *self = RustString::String(string); - Ok(()) - } - Err(e) => { - let (e, bytes) = (e.utf8_error(), e.into_bytes()); - // put self back as we found it - *self = RustString::Bytes(bytes); - Err(e) - } - }, - _ => { - // not bytes, so just swap back - std::mem::swap(self, &mut owned); - Ok(()) - } - } - } - - /// Convert the RustString, in place, into one of the C variants. If this is not - /// possible, such as if the string contains an embedded NUL, then the string - /// remains unchanged. - fn string_to_cstring(&mut self) { - let mut owned = RustString::Null; - // temporarily swap a Null value into self; we'll swap that back shortly - std::mem::swap(self, &mut owned); - match owned { - RustString::String(string) => { - match CString::new(string) { - Ok(cstring) => { - *self = RustString::CString(cstring); - } - Err(nul_err) => { - // recover the underlying String from the NulError and restore - // the RustString - let original_bytes = nul_err.into_vec(); - // SAFETY: original_bytes came from a String moments ago, so still valid utf8 - let string = unsafe { String::from_utf8_unchecked(original_bytes) }; - *self = RustString::String(string); - } - } - } - _ => { - // not a CString, so just swap back - std::mem::swap(self, &mut owned); - } - } - } - - pub(crate) fn to_path_buf_mut(&mut self) -> Result { - #[cfg(unix)] - let path: OsString = { - // on UNIX, we can use the bytes directly, without requiring that they - // be valid UTF-8. - use std::ffi::OsStr; - use std::os::unix::ffi::OsStrExt; - OsStr::from_bytes(self.as_bytes()).to_os_string() - }; - #[cfg(windows)] - let path: OsString = { - // on Windows, we assume the filename is valid Unicode, so it can be - // represented as UTF-8. - OsString::from(self.as_str()?.to_string()) - }; - Ok(path.into()) - } -} - -impl<'a> From for RustString<'a> { - fn from(string: String) -> RustString<'a> { - RustString::String(string) - } -} - -impl From<&str> for RustString<'static> { - fn from(string: &str) -> RustString<'static> { - RustString::String(string.to_string()) - } -} - -/// Utility function to borrow a TCString from a pointer arg, modify it, -/// and restore it. -/// -/// This implements a kind of "interior mutability", relying on the -/// single-threaded use of all TC* types. -/// -/// # SAFETY -/// -/// - tcstring must not be NULL -/// - *tcstring must be a valid TCString -/// - *tcstring must not be accessed by anything else, despite the *const -unsafe fn wrap(tcstring: *const TCString, f: F) -> T -where - F: FnOnce(&mut RustString) -> T, -{ - debug_assert!(!tcstring.is_null()); - - // SAFETY: - // - we have exclusive to *tcstring (promised by caller) - let tcstring = tcstring as *mut TCString; - - // SAFETY: - // - tcstring is not NULL - // - *tcstring is a valid string (promised by caller) - let mut rstring = unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) }; - - let rv = f(&mut rstring); - - // update the caller's TCString with the updated RustString - // SAFETY: - // - tcstring is not NULL (we just took from it) - // - tcstring points to valid memory (we just took from it) - unsafe { TCString::val_to_arg_out(rstring, tcstring) }; - - rv -} - -#[ffizz_header::item] -#[ffizz(order = 210)] -/// ***** TCStringList ***** -/// -/// TCStringList represents a list of strings. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCStringList { -/// // number of strings in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // TCStringList representing each string. These remain owned by the TCStringList instance and will -/// // be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the -/// // *TCStringList at indexes 0..len-1 are not NULL. -/// struct TCString *items; -/// } TCStringList; -/// ``` -#[repr(C)] -pub struct TCStringList { - len: libc::size_t, - /// total size of items (internal use only) - capacity: libc::size_t, - items: *mut TCString, -} - -impl CList for TCStringList { - type Element = TCString; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCStringList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Create a new TCString referencing the given C string. The C string must remain valid and -/// unchanged until after the TCString is freed. It's typically easiest to ensure this by using a -/// static string. -/// -/// NOTE: this function does _not_ take responsibility for freeing the given C string. The -/// given string can be freed once the TCString referencing it has been freed. -/// -/// For example: -/// -/// ```text -/// char *url = get_item_url(..); // dynamically allocate C string -/// tc_task_annotate(task, tc_string_borrow(url)); // TCString created, passed, and freed -/// free(url); // string is no longer referenced and can be freed -/// ``` -/// -/// ```c -/// EXTERN_C struct TCString tc_string_borrow(const char *cstr); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> TCString { - debug_assert!(!cstr.is_null()); - // SAFETY: - // - cstr is not NULL (promised by caller, verified by assertion) - // - cstr's lifetime exceeds that of the TCString (promised by caller) - // - cstr contains a valid NUL terminator (promised by caller) - // - cstr's content will not change before it is destroyed (promised by caller) - let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(RustString::CStr(cstr)) } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Create a new TCString by cloning the content of the given C string. The resulting TCString -/// is independent of the given string, which can be freed or overwritten immediately. -/// -/// ```c -/// EXTERN_C struct TCString tc_string_clone(const char *cstr); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> TCString { - debug_assert!(!cstr.is_null()); - // SAFETY: - // - cstr is not NULL (promised by caller, verified by assertion) - // - cstr's lifetime exceeds that of this function (by C convention) - // - cstr contains a valid NUL terminator (promised by caller) - // - cstr's content will not change before it is destroyed (by C convention) - let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - let cstring: CString = cstr.into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(RustString::CString(cstring)) } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Create a new TCString containing the given string with the given length. This allows creation -/// of strings containing embedded NUL characters. As with `tc_string_clone`, the resulting -/// TCString is independent of the passed buffer, which may be reused or freed immediately. -/// -/// The length should _not_ include any trailing NUL. -/// -/// The given length must be less than half the maximum value of usize. -/// -/// ```c -/// EXTERN_C struct TCString tc_string_clone_with_len(const char *buf, size_t len); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_clone_with_len( - buf: *const libc::c_char, - len: usize, -) -> TCString { - debug_assert!(!buf.is_null()); - debug_assert!(len < isize::MAX as usize); - // SAFETY: - // - buf is valid for len bytes (by C convention) - // - (no alignment requirements for a byte slice) - // - content of buf will not be mutated during the lifetime of this slice (lifetime - // does not outlive this function call) - // - the length of the buffer is less than isize::MAX (promised by caller) - let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) }; - - // allocate and copy into Rust-controlled memory - let vec = slice.to_vec(); - - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(RustString::Bytes(vec)) } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Get the content of the string as a regular C string. The given string must be valid. The -/// returned value is NULL if the string contains NUL bytes or (in some cases) invalid UTF-8. The -/// returned C string is valid until the TCString is freed or passed to another TC API function. -/// -/// In general, prefer [`tc_string_content_with_len`] except when it's certain that the string is -/// valid and NUL-free. -/// -/// This function takes the TCString by pointer because it may be modified in-place to add a NUL -/// terminator. The pointer must not be NULL. -/// -/// This function does _not_ take ownership of the TCString. -/// -/// ```c -/// EXTERN_C const char *tc_string_content(const struct TCString *tcstring); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_content(tcstring: *const TCString) -> *const libc::c_char { - // SAFETY; - // - tcstring is not NULL (promised by caller) - // - *tcstring is valid (promised by caller) - // - *tcstring is not accessed concurrently (single-threaded) - unsafe { - wrap(tcstring, |rstring| { - // try to eliminate the Bytes variant. If this fails, we'll return NULL - // below, so the error is ignorable. - let _ = rstring.bytes_to_string(); - - // and eliminate the String variant - rstring.string_to_cstring(); - - match &rstring { - RustString::CString(cstring) => cstring.as_ptr(), - RustString::String(_) => std::ptr::null(), // string_to_cstring failed - RustString::CStr(cstr) => cstr.as_ptr(), - RustString::Bytes(_) => std::ptr::null(), // already returned above - RustString::Null => unreachable!(), - } - }) - } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Get the content of the string as a pointer and length. The given string must not be NULL. -/// This function can return any string, even one including NUL bytes or invalid UTF-8. The -/// returned buffer is valid until the TCString is freed or passed to another TaskChampio -/// function. -/// -/// This function takes the TCString by pointer because it may be modified in-place to add a NUL -/// terminator. The pointer must not be NULL. -/// -/// This function does _not_ take ownership of the TCString. -/// -/// ```c -/// EXTERN_C const char *tc_string_content_with_len(const struct TCString *tcstring, size_t *len_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_content_with_len( - tcstring: *const TCString, - len_out: *mut usize, -) -> *const libc::c_char { - // SAFETY; - // - tcstring is not NULL (promised by caller) - // - *tcstring is valid (promised by caller) - // - *tcstring is not accessed concurrently (single-threaded) - unsafe { - wrap(tcstring, |rstring| { - let bytes = rstring.as_bytes(); - - // SAFETY: - // - len_out is not NULL (promised by caller) - // - len_out points to valid memory (promised by caller) - // - len_out is properly aligned (C convention) - usize::val_to_arg_out(bytes.len(), len_out); - bytes.as_ptr() as *const libc::c_char - }) - } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Free a TCString. The given string must not be NULL. The string must not be used -/// after this function returns, and must not be freed more than once. -/// -/// ```c -/// EXTERN_C void tc_string_free(struct TCString *tcstring); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { - // SAFETY: - // - tcstring is not NULL (promised by caller) - // - caller is exclusive owner of tcstring (promised by caller) - drop(unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) }); -} - -#[ffizz_header::item] -#[ffizz(order = 211)] -/// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. -/// -/// ```c -/// EXTERN_C void tc_string_list_free(struct TCStringList *tcstrings); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { - // SAFETY: - // - tcstrings is not NULL and points to a valid TCStringList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tcstrings) }; -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn empty_list_has_non_null_pointer() { - let tcstrings = unsafe { TCStringList::return_val(Vec::new()) }; - assert!(!tcstrings.items.is_null()); - assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings.capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcstrings = unsafe { TCStringList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_string_list_free(&mut tcstrings) }; - assert!(tcstrings.items.is_null()); - assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings.capacity, 0); - } - - const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28"; - - fn make_cstring() -> RustString<'static> { - RustString::CString(CString::new("a string").unwrap()) - } - - fn make_cstr() -> RustString<'static> { - let cstr = CStr::from_bytes_with_nul(b"a string\0").unwrap(); - RustString::CStr(cstr) - } - - fn make_string() -> RustString<'static> { - RustString::String("a string".into()) - } - - fn make_string_with_nul() -> RustString<'static> { - RustString::String("a \0 nul!".into()) - } - - fn make_invalid_bytes() -> RustString<'static> { - RustString::Bytes(INVALID_UTF8.to_vec()) - } - - fn make_bytes() -> RustString<'static> { - RustString::Bytes(b"bytes".to_vec()) - } - - #[test] - fn cstring_as_str() { - assert_eq!(make_cstring().as_str().unwrap(), "a string"); - } - - #[test] - fn cstr_as_str() { - assert_eq!(make_cstr().as_str().unwrap(), "a string"); - } - - #[test] - fn string_as_str() { - assert_eq!(make_string().as_str().unwrap(), "a string"); - } - - #[test] - fn string_with_nul_as_str() { - assert_eq!(make_string_with_nul().as_str().unwrap(), "a \0 nul!"); - } - - #[test] - fn invalid_bytes_as_str() { - let as_str_err = make_invalid_bytes().as_str().unwrap_err(); - assert_eq!(as_str_err.valid_up_to(), 3); // "abc" is valid - } - - #[test] - fn valid_bytes_as_str() { - assert_eq!(make_bytes().as_str().unwrap(), "bytes"); - } - - #[test] - fn cstring_as_bytes() { - assert_eq!(make_cstring().as_bytes(), b"a string"); - } - - #[test] - fn cstr_as_bytes() { - assert_eq!(make_cstr().as_bytes(), b"a string"); - } - - #[test] - fn string_as_bytes() { - assert_eq!(make_string().as_bytes(), b"a string"); - } - - #[test] - fn string_with_nul_as_bytes() { - assert_eq!(make_string_with_nul().as_bytes(), b"a \0 nul!"); - } - - #[test] - fn invalid_bytes_as_bytes() { - assert_eq!(make_invalid_bytes().as_bytes(), INVALID_UTF8); - } - - #[test] - fn cstring_string_to_cstring() { - let mut tcstring = make_cstring(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_cstring()); // unchanged - } - - #[test] - fn cstr_string_to_cstring() { - let mut tcstring = make_cstr(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_cstr()); // unchanged - } - - #[test] - fn string_string_to_cstring() { - let mut tcstring = make_string(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_cstring()); // converted to CString, same content - } - - #[test] - fn string_with_nul_string_to_cstring() { - let mut tcstring = make_string_with_nul(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_string_with_nul()); // unchanged - } - - #[test] - fn bytes_string_to_cstring() { - let mut tcstring = make_bytes(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_bytes()); // unchanged - } -} diff --git a/taskchampion/lib/src/task.rs b/taskchampion/lib/src/task.rs deleted file mode 100644 index a5d2e80de..000000000 --- a/taskchampion/lib/src/task.rs +++ /dev/null @@ -1,1304 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use crate::util::err_to_ruststring; -use crate::TCKV; -use std::convert::TryFrom; -use std::ops::Deref; -use std::ptr::NonNull; -use std::str::FromStr; -use taskchampion::{utc_timestamp, Annotation, Tag, Task, TaskMut, Uuid}; - -#[ffizz_header::item] -#[ffizz(order = 1000)] -/// ***** TCTask ***** -/// -/// A task, as publicly exposed by this library. -/// -/// A task begins in "immutable" mode. It must be converted to "mutable" mode -/// to make any changes, and doing so requires exclusive access to the replica -/// until the task is freed or converted back to immutable mode. -/// -/// An immutable task carries no reference to the replica that created it, and can be used until it -/// is freed or converted to a TaskMut. A mutable task carries a reference to the replica and -/// must be freed or made immutable before the replica is freed. -/// -/// All `tc_task_..` functions taking a task as an argument require that it not be NULL. -/// -/// When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then -/// `tc_task_error` will return the error message. -/// -/// # Safety -/// -/// A task is an owned object, and must be freed with tc_task_free (or, if part of a list, -/// with tc_task_list_free). -/// -/// Any function taking a `*TCTask` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from a tc_… function; -/// - the memory referenced by the pointer must never be modified by C code; and -/// - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller. -/// -/// Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again. -/// -/// TCTasks are not threadsafe. -/// -/// ```c -/// typedef struct TCTask TCTask; -/// ``` -pub struct TCTask { - /// The wrapped Task or TaskMut - inner: Inner, - - /// The error from the most recent operation, if any - error: Option>, -} - -enum Inner { - /// A regular, immutable task - Immutable(Task), - - /// A mutable task, together with the replica to which it holds an exclusive - /// reference. - Mutable(TaskMut<'static>, *mut TCReplica), - - /// A transitional state for a TCTask as it goes from mutable to immutable and back. A task - /// can only be in this state outside of [`to_mut`] and [`to_immut`] if a panic occurs during - /// one of those methods. - Invalid, -} - -impl PassByPointer for TCTask {} - -impl TCTask { - /// Make an immutable TCTask into a mutable TCTask. Does nothing if the task - /// is already mutable. - /// - /// # Safety - /// - /// The tcreplica pointer must not be NULL, and the replica it points to must not - /// be freed before TCTask.to_immut completes. - unsafe fn to_mut(&mut self, tcreplica: *mut TCReplica) { - self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) { - Inner::Immutable(task) => { - // SAFETY: - // - tcreplica is not null (promised by caller) - // - tcreplica outlives the pointer in this variant (promised by caller) - let tcreplica_ref: &mut TCReplica = - unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) }; - let rep_ref = tcreplica_ref.borrow_mut(); - Inner::Mutable(task.into_mut(rep_ref), tcreplica) - } - Inner::Mutable(task, tcreplica) => Inner::Mutable(task, tcreplica), - Inner::Invalid => unreachable!(), - } - } - - /// Make an mutable TCTask into a immutable TCTask. Does nothing if the task - /// is already immutable. - #[allow(clippy::wrong_self_convention)] // to_immut_mut is not better! - fn to_immut(&mut self) { - self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) { - Inner::Immutable(task) => Inner::Immutable(task), - Inner::Mutable(task, tcreplica) => { - // SAFETY: - // - tcreplica is not null (promised by caller of to_mut, which created this - // variant) - // - tcreplica is still alive (promised by caller of to_mut) - let tcreplica_ref: &mut TCReplica = - unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) }; - tcreplica_ref.release_borrow(); - Inner::Immutable(task.into_immut()) - } - Inner::Invalid => unreachable!(), - } - } -} - -impl From for TCTask { - fn from(task: Task) -> TCTask { - TCTask { - inner: Inner::Immutable(task), - error: None, - } - } -} - -/// Utility function to get a shared reference to the underlying Task. All Task getters -/// are error-free, so this does not handle errors. -fn wrap(task: *mut TCTask, f: F) -> T -where - F: FnOnce(&Task) -> T, -{ - // SAFETY: - // - task is not null (promised by caller) - // - task outlives this function (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - let task: &Task = match &tctask.inner { - Inner::Immutable(t) => t, - Inner::Mutable(t, _) => t.deref(), - Inner::Invalid => unreachable!(), - }; - tctask.error = None; - f(task) -} - -/// Utility function to get a mutable reference to the underlying Task. The -/// TCTask must be mutable. The inner function may use `?` syntax to return an -/// error, which will be represented with the `err_value` returned to C. -fn wrap_mut(task: *mut TCTask, f: F, err_value: T) -> T -where - F: FnOnce(&mut TaskMut) -> anyhow::Result, -{ - // SAFETY: - // - task is not null (promised by caller) - // - task outlives this function (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - let task: &mut TaskMut = match tctask.inner { - Inner::Immutable(_) => panic!("Task is immutable"), - Inner::Mutable(ref mut t, _) => t, - Inner::Invalid => unreachable!(), - }; - tctask.error = None; - match f(task) { - Ok(rv) => rv, - Err(e) => { - tctask.error = Some(err_to_ruststring(e)); - err_value - } - } -} - -impl TryFrom> for Tag { - type Error = anyhow::Error; - - fn try_from(mut rstring: RustString) -> Result { - let tagstr = rstring.as_str()?; - Tag::from_str(tagstr) - } -} - -#[ffizz_header::item] -#[ffizz(order = 1010)] -/// ***** TCTaskList ***** -/// -/// TCTaskList represents a list of tasks. -/// -/// The content of this struct must be treated as read-only: no fields or anything they reference -/// should be modified directly by C code. -/// -/// When an item is taken from this list, its pointer in `items` is set to NULL. -/// -/// ```c -/// typedef struct TCTaskList { -/// // number of tasks in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of pointers representing each task. These remain owned by the TCTaskList instance and -/// // will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList. -/// // Pointers in the array may be NULL after `tc_task_list_take`. -/// struct TCTask **items; -/// } TCTaskList; -/// ``` -#[repr(C)] -pub struct TCTaskList { - /// number of tasks in items - len: libc::size_t, - - /// total size of items (internal use only) - capacity: libc::size_t, - - /// Array of pointers representing each task. These remain owned by the TCTaskList instance and - /// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList. - /// Pointers in the array may be NULL after `tc_task_list_take`. - items: *mut Option>, -} - -impl CList for TCTaskList { - type Element = Option>; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCTaskList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get a task's UUID. -/// -/// ```c -/// EXTERN_C struct TCUuid tc_task_get_uuid(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { - wrap(task, |task| { - // SAFETY: - // - value is not allocated and need not be freed - unsafe { TCUuid::return_val(task.get_uuid()) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get a task's status. -/// -/// ```c -/// EXTERN_C enum TCStatus tc_task_get_status(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_status(task: *mut TCTask) -> TCStatus { - wrap(task, |task| task.get_status().into()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the underlying key/value pairs for this task. The returned TCKVList is -/// a "snapshot" of the task and will not be updated if the task is subsequently -/// modified. It is the caller's responsibility to free the TCKVList. -/// -/// ```c -/// EXTERN_C struct TCKVList tc_task_get_taskmap(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { - wrap(task, |task| { - let vec: Vec = task - .get_taskmap() - .iter() - .map(|(k, v)| { - let key = RustString::from(k.as_ref()); - let value = RustString::from(v.as_ref()); - TCKV::as_ctype((key, value)) - }) - .collect(); - // SAFETY: - // - caller will free this list - unsafe { TCKVList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get a task's description. -/// -/// ```c -/// EXTERN_C struct TCString tc_task_get_description(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> TCString { - wrap(task, |task| { - let descr = task.get_description(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(descr.into()) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get a task property's value, or NULL if the task has no such property, (including if the -/// property name is not valid utf-8). -/// -/// ```c -/// EXTERN_C struct TCString tc_task_get_value(struct TCTask *task, struct TCString property); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_value(task: *mut TCTask, property: TCString) -> TCString { - // SAFETY: - // - property is valid (promised by caller) - // - caller will not use property after this call (convention) - let mut property = unsafe { TCString::val_from_arg(property) }; - wrap(task, |task| { - if let Ok(property) = property.as_str() { - let value = task.get_value(property); - if let Some(value) = value { - // SAFETY: - // - caller promises to free this string - return unsafe { TCString::return_val(value.into()) }; - } - } - TCString::default() // null value - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the entry timestamp for a task (when it was created), or 0 if not set. -/// -/// ```c -/// EXTERN_C time_t tc_task_get_entry(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_entry(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| libc::time_t::as_ctype(task.get_entry())) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the wait timestamp for a task, or 0 if not set. -/// -/// ```c -/// EXTERN_C time_t tc_task_get_wait(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_wait(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| libc::time_t::as_ctype(task.get_wait())) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the modified timestamp for a task, or 0 if not set. -/// -/// ```c -/// EXTERN_C time_t tc_task_get_modified(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_modified(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| libc::time_t::as_ctype(task.get_modified())) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task is waiting. -/// -/// ```c -/// EXTERN_C bool tc_task_is_waiting(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool { - wrap(task, |task| task.is_waiting()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task is active (started and not stopped). -/// -/// ```c -/// EXTERN_C bool tc_task_is_active(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { - wrap(task, |task| task.is_active()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task is blocked (depends on at least one other task). -/// -/// ```c -/// EXTERN_C bool tc_task_is_blocked(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_is_blocked(task: *mut TCTask) -> bool { - wrap(task, |task| task.is_blocked()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task is blocking (at least one other task depends on it). -/// -/// ```c -/// EXTERN_C bool tc_task_is_blocking(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_is_blocking(task: *mut TCTask) -> bool { - wrap(task, |task| task.is_blocking()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task has the given tag. If the tag is invalid, this function will return false, as -/// that (invalid) tag is not present. No error will be reported via `tc_task_error`. -/// -/// ```c -/// EXTERN_C bool tc_task_has_tag(struct TCTask *task, struct TCString tag); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: TCString) -> bool { - // SAFETY: - // - tag is valid (promised by caller) - // - caller will not use tag after this call (convention) - let tcstring = unsafe { TCString::val_from_arg(tag) }; - wrap(task, |task| { - if let Ok(tag) = Tag::try_from(tcstring) { - task.has_tag(&tag) - } else { - false - } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the tags for the task. -/// -/// The caller must free the returned TCStringList instance. The TCStringList instance does not -/// reference the task and the two may be freed in any order. -/// -/// ```c -/// EXTERN_C struct TCStringList tc_task_get_tags(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList { - wrap(task, |task| { - let vec: Vec = task - .get_tags() - .map(|t| { - // SAFETY: - // - this TCString will be freed via tc_string_list_free. - unsafe { TCString::return_val(t.as_ref().into()) } - }) - .collect(); - // SAFETY: - // - caller will free the list - unsafe { TCStringList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the annotations for the task. -/// -/// The caller must free the returned TCAnnotationList instance. The TCStringList instance does not -/// reference the task and the two may be freed in any order. -/// -/// ```c -/// EXTERN_C struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_annotations(task: *mut TCTask) -> TCAnnotationList { - wrap(task, |task| { - let vec: Vec = task - .get_annotations() - .map(|a| { - let description = RustString::from(a.description); - TCAnnotation::as_ctype((a.entry, description)) - }) - .collect(); - // SAFETY: - // - caller will free the list - unsafe { TCAnnotationList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the named UDA from the task. -/// -/// Returns a TCString with NULL ptr field if the UDA does not exist. -/// -/// ```c -/// EXTERN_C struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_uda( - task: *mut TCTask, - ns: TCString, - key: TCString, -) -> TCString { - wrap(task, |task| { - // SAFETY: - // - ns is valid (promised by caller) - // - caller will not use ns after this call (convention) - if let Ok(ns) = unsafe { TCString::val_from_arg(ns) }.as_str() { - // SAFETY: same - if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() { - if let Some(value) = task.get_uda(ns, key) { - // SAFETY: - // - caller will free this string (caller promises) - return unsafe { TCString::return_val(value.into()) }; - } - } - } - TCString::default() - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the named legacy UDA from the task. -/// -/// Returns NULL if the UDA does not exist. -/// -/// ```c -/// EXTERN_C struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_legacy_uda(task: *mut TCTask, key: TCString) -> TCString { - wrap(task, |task| { - // SAFETY: - // - key is valid (promised by caller) - // - caller will not use key after this call (convention) - if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() { - if let Some(value) = task.get_legacy_uda(key) { - // SAFETY: - // - caller will free this string (caller promises) - return unsafe { TCString::return_val(value.into()) }; - } - } - TCString::default() - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get all UDAs for this task. -/// -/// Legacy UDAs are represented with an empty string in the ns field. -/// -/// ```c -/// EXTERN_C struct TCUdaList tc_task_get_udas(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUdaList { - wrap(task, |task| { - let vec: Vec = task - .get_udas() - .map(|((ns, key), value)| { - // SAFETY: - // - will be freed by tc_uda_list_free - unsafe { - TCUda::return_val(Uda { - ns: Some(ns.into()), - key: key.into(), - value: value.into(), - }) - } - }) - .collect(); - // SAFETY: - // - caller will free this list - unsafe { TCUdaList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get all UDAs for this task. -/// -/// All TCUdas in this list have a NULL ns field. The entire UDA key is -/// included in the key field. The caller must free the returned list. -/// -/// ```c -/// EXTERN_C struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUdaList { - wrap(task, |task| { - let vec: Vec = task - .get_legacy_udas() - .map(|(key, value)| { - // SAFETY: - // - will be freed by tc_uda_list_free - unsafe { - TCUda::return_val(Uda { - ns: None, - key: key.into(), - value: value.into(), - }) - } - }) - .collect(); - // SAFETY: - // - caller will free this list - unsafe { TCUdaList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get all dependencies for a task. -/// -/// ```c -/// EXTERN_C struct TCUuidList tc_task_get_dependencies(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_dependencies(task: *mut TCTask) -> TCUuidList { - wrap(task, |task| { - let vec: Vec = task - .get_dependencies() - .map(|u| { - // SAFETY: - // - value is not allocated - unsafe { TCUuid::return_val(u) } - }) - .collect(); - // SAFETY: - // - caller will free this list - unsafe { TCUuidList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1002)] -/// Convert an immutable task into a mutable task. -/// -/// The task must not be NULL. It is modified in-place, and becomes mutable. -/// -/// The replica must not be NULL. After this function returns, the replica _cannot be used at all_ -/// until this task is made immutable again. This implies that it is not allowed for more than one -/// task associated with a replica to be mutable at any time. -/// -/// Typically mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`: -/// -/// ```text -/// tc_task_to_mut(task, rep); -/// success = tc_task_done(task); -/// tc_task_to_immut(task, rep); -/// if (!success) { ... } -/// ``` -/// -/// ```c -/// EXTERN_C void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_to_mut(task: *mut TCTask, tcreplica: *mut TCReplica) { - // SAFETY: - // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - // SAFETY: - // - tcreplica is not NULL (promised by caller) - // - tcreplica lives until later call to to_immut via tc_task_to_immut (promised by caller, - // who cannot call tc_replica_free during this time) - unsafe { tctask.to_mut(tcreplica) }; -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's status. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_status(task: *mut TCTask, status: TCStatus) -> TCResult { - wrap_mut( - task, - |task| { - task.set_status(status.into())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's property value by name. If value.ptr is NULL, the property is removed. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_value(struct TCTask *task, struct TCString property, struct TCString value); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_value( - task: *mut TCTask, - property: TCString, - value: TCString, -) -> TCResult { - // SAFETY: - // - property is valid (promised by caller) - // - caller will not use property after this call (convention) - let mut property = unsafe { TCString::val_from_arg(property) }; - let value = if value.is_null() { - None - } else { - // SAFETY: - // - value is valid (promised by caller, after NULL check) - // - caller will not use value after this call (convention) - Some(unsafe { TCString::val_from_arg(value) }) - }; - wrap_mut( - task, - |task| { - let value_str = if let Some(mut v) = value { - Some(v.as_str()?.to_string()) - } else { - None - }; - task.set_value(property.as_str()?.to_string(), value_str)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's description. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_description(struct TCTask *task, struct TCString description); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_description( - task: *mut TCTask, - description: TCString, -) -> TCResult { - // SAFETY: - // - description is valid (promised by caller) - // - caller will not use description after this call (convention) - let mut description = unsafe { TCString::val_from_arg(description) }; - wrap_mut( - task, - |task| { - task.set_description(description.as_str()?.to_string())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's entry (creation time). Pass entry=0 to unset -/// the entry field. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_entry(struct TCTask *task, time_t entry); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult { - wrap_mut( - task, - |task| { - // SAFETY: any time_t value is a valid timestamp - task.set_entry(unsafe { entry.from_ctype() })?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_wait(struct TCTask *task, time_t wait); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult { - wrap_mut( - task, - |task| { - // SAFETY: any time_t value is a valid timestamp - task.set_wait(unsafe { wait.from_ctype() })?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's modified timestamp. The value cannot be zero. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_modified(struct TCTask *task, time_t modified); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_modified( - task: *mut TCTask, - modified: libc::time_t, -) -> TCResult { - wrap_mut( - task, - |task| { - task.set_modified( - // SAFETY: any time_t value is a valid timestamp - unsafe { modified.from_ctype() } - .ok_or_else(|| anyhow::anyhow!("modified cannot be zero"))?, - )?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Start a task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_start(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { - wrap_mut( - task, - |task| { - task.start()?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Stop a task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_stop(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { - wrap_mut( - task, - |task| { - task.stop()?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Mark a task as done. -/// -/// ```c -/// EXTERN_C TCResult tc_task_done(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { - wrap_mut( - task, - |task| { - task.done()?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Mark a task as deleted. -/// -/// ```c -/// EXTERN_C TCResult tc_task_delete(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { - wrap_mut( - task, - |task| { - task.delete()?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Add a tag to a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: TCString) -> TCResult { - // SAFETY: - // - tag is valid (promised by caller) - // - caller will not use tag after this call (convention) - let tcstring = unsafe { TCString::val_from_arg(tag) }; - wrap_mut( - task, - |task| { - let tag = Tag::try_from(tcstring)?; - task.add_tag(&tag)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove a tag from a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: TCString) -> TCResult { - // SAFETY: - // - tag is valid (promised by caller) - // - caller will not use tag after this call (convention) - let tcstring = unsafe { TCString::val_from_arg(tag) }; - wrap_mut( - task, - |task| { - let tag = Tag::try_from(tcstring)?; - task.remove_tag(&tag)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Add an annotation to a mutable task. This call takes ownership of the -/// passed annotation, which must not be used after the call returns. -/// -/// ```c -/// EXTERN_C TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_add_annotation( - task: *mut TCTask, - annotation: *mut TCAnnotation, -) -> TCResult { - // SAFETY: - // - annotation is not NULL (promised by caller) - // - annotation is return from a tc_string_.. so is valid - // - caller will not use annotation after this call - let (entry, description) = - unsafe { TCAnnotation::take_val_from_arg(annotation, TCAnnotation::default()) }; - wrap_mut( - task, - |task| { - let description = description.into_string()?; - task.add_annotation(Annotation { entry, description })?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove an annotation from a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64) -> TCResult { - wrap_mut( - task, - |task| { - task.remove_annotation(utc_timestamp(entry))?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a UDA on a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_uda(struct TCTask *task, -/// struct TCString ns, -/// struct TCString key, -/// struct TCString value); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_uda( - task: *mut TCTask, - ns: TCString, - key: TCString, - value: TCString, -) -> TCResult { - // safety: - // - ns is valid (promised by caller) - // - caller will not use ns after this call (convention) - let mut ns = unsafe { TCString::val_from_arg(ns) }; - // SAFETY: same - let mut key = unsafe { TCString::val_from_arg(key) }; - // SAFETY: same - let mut value = unsafe { TCString::val_from_arg(value) }; - wrap_mut( - task, - |task| { - task.set_uda(ns.as_str()?, key.as_str()?, value.as_str()?.to_string())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove a UDA fraom a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_uda( - task: *mut TCTask, - ns: TCString, - key: TCString, -) -> TCResult { - // safety: - // - ns is valid (promised by caller) - // - caller will not use ns after this call (convention) - let mut ns = unsafe { TCString::val_from_arg(ns) }; - // SAFETY: same - let mut key = unsafe { TCString::val_from_arg(key) }; - wrap_mut( - task, - |task| { - task.remove_uda(ns.as_str()?, key.as_str()?)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a legacy UDA on a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_legacy_uda( - task: *mut TCTask, - key: TCString, - value: TCString, -) -> TCResult { - // safety: - // - key is valid (promised by caller) - // - caller will not use key after this call (convention) - let mut key = unsafe { TCString::val_from_arg(key) }; - // SAFETY: same - let mut value = unsafe { TCString::val_from_arg(value) }; - wrap_mut( - task, - |task| { - task.set_legacy_uda(key.as_str()?.to_string(), value.as_str()?.to_string())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove a UDA fraom a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_legacy_uda(task: *mut TCTask, key: TCString) -> TCResult { - // safety: - // - key is valid (promised by caller) - // - caller will not use key after this call (convention) - let mut key = unsafe { TCString::val_from_arg(key) }; - wrap_mut( - task, - |task| { - task.remove_legacy_uda(key.as_str()?.to_string())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Add a dependency. -/// -/// ```c -/// EXTERN_C TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_add_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult { - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) }; - wrap_mut( - task, - |task| { - task.add_dependency(dep)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove a dependency. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_dependency(struct TCTask *task, struct TCUuid dep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult { - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) }; - wrap_mut( - task, - |task| { - task.remove_dependency(dep)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1004)] -/// Convert a mutable task into an immutable task. -/// -/// The task must not be NULL. It is modified in-place, and becomes immutable. -/// -/// The replica passed to `tc_task_to_mut` may be used freely after this call. -/// -/// ```c -/// EXTERN_C void tc_task_to_immut(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_to_immut(task: *mut TCTask) { - // SAFETY: - // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - tctask.to_immut(); -} - -#[ffizz_header::item] -#[ffizz(order = 1005)] -/// Get the latest error for a task, or a string NULL ptr field if the last operation succeeded. -/// Subsequent calls to this function will return NULL. The task pointer must not be NULL. The -/// caller must free the returned string. -/// -/// ```c -/// EXTERN_C struct TCString tc_task_error(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> TCString { - // SAFETY: - // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let task: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - if let Some(rstring) = task.error.take() { - // SAFETY: - // - caller promises to free this value - unsafe { TCString::return_val(rstring) } - } else { - TCString::default() - } -} - -#[ffizz_header::item] -#[ffizz(order = 1006)] -/// Free a task. The given task must not be NULL. The task must not be used after this function -/// returns, and must not be freed more than once. -/// -/// If the task is currently mutable, it will first be made immutable. -/// -/// ```c -/// EXTERN_C void tc_task_free(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) { - // SAFETY: - // - task is not NULL (promised by caller) - // - caller will not use the TCTask after this (promised by caller) - let mut tctask = unsafe { TCTask::take_from_ptr_arg(task) }; - - // convert to immut if it was mutable - tctask.to_immut(); - - drop(tctask); -} - -#[ffizz_header::item] -#[ffizz(order = 1011)] -/// Take an item from a TCTaskList. After this call, the indexed item is no longer associated -/// with the list and becomes the caller's responsibility, just as if it had been returned from -/// `tc_replica_get_task`. -/// -/// The corresponding element in the `items` array will be set to NULL. If that field is already -/// NULL (that is, if the item has already been taken), this function will return NULL. If the -/// index is out of bounds, this function will also return NULL. -/// -/// The passed TCTaskList remains owned by the caller. -/// -/// ```c -/// EXTERN_C struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_list_take(tasks: *mut TCTaskList, index: usize) -> *mut TCTask { - // SAFETY: - // - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to - // modify the list directly, and tc_task_list_take leaves the list valid) - let p = unsafe { take_optional_pointer_list_item(tasks, index) }; - if let Some(p) = p { - p.as_ptr() - } else { - std::ptr::null_mut() - } -} - -#[ffizz_header::item] -#[ffizz(order = 1011)] -/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. -/// -/// ```c -/// EXTERN_C void tc_task_list_free(struct TCTaskList *tasks); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_list_free(tasks: *mut TCTaskList) { - // SAFETY: - // - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to - // modify the list directly, and tc_task_list_take leaves the list valid) - // - caller promises not to use the value after return - unsafe { drop_optional_pointer_list(tasks) }; -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tasks = unsafe { TCTaskList::return_val(Vec::new()) }; - assert!(!tasks.items.is_null()); - assert_eq!(tasks.len, 0); - assert_eq!(tasks.capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tasks = unsafe { TCTaskList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_task_list_free(&mut tasks) }; - assert!(tasks.items.is_null()); - assert_eq!(tasks.len, 0); - assert_eq!(tasks.capacity, 0); - } -} diff --git a/taskchampion/lib/src/traits.rs b/taskchampion/lib/src/traits.rs deleted file mode 100644 index c9040071d..000000000 --- a/taskchampion/lib/src/traits.rs +++ /dev/null @@ -1,351 +0,0 @@ -use crate::util::vec_into_raw_parts; -use std::ptr::NonNull; - -/// Support for values passed to Rust by value. These are represented as full structs in C. Such -/// values are implicitly copyable, via C's struct assignment. -/// -/// The Rust and C types may differ, with from_ctype and as_ctype converting between them. -/// Implement this trait for the C type. -/// -/// The RustType must be droppable (not containing raw pointers). -pub(crate) trait PassByValue: Sized { - type RustType; - - /// Convert a C value to a Rust value. - /// - /// # Safety - /// - /// `self` must be a valid CType. - #[allow(clippy::wrong_self_convention)] - unsafe fn from_ctype(self) -> Self::RustType; - - /// Convert a Rust value to a C value. - fn as_ctype(arg: Self::RustType) -> Self; - - /// Take a value from C as an argument. - /// - /// # Safety - /// - /// - `self` must be a valid instance of the C type. This is typically ensured either by - /// requiring that C code not modify it, or by defining the valid values in C comments. - unsafe fn val_from_arg(arg: Self) -> Self::RustType { - // SAFETY: - // - arg is a valid CType (promised by caller) - unsafe { arg.from_ctype() } - } - - /// Take a value from C as a pointer argument, replacing it with the given value. This is used - /// to invalidate the C value as an additional assurance against subsequent use of the value. - /// - /// # Safety - /// - /// - arg must not be NULL - /// - *arg must be a valid, properly aligned instance of the C type - unsafe fn take_val_from_arg(arg: *mut Self, mut replacement: Self) -> Self::RustType { - // SAFETY: - // - arg is valid (promised by caller) - // - replacement is valid and aligned (guaranteed by Rust) - unsafe { std::ptr::swap(arg, &mut replacement) }; - // SAFETY: - // - replacement (formerly *arg) is a valid CType (promised by caller) - unsafe { PassByValue::val_from_arg(replacement) } - } - - /// Return a value to C - /// - /// # Safety - /// - /// - if the value is allocated, the caller must ensure that the value is eventually freed - unsafe fn return_val(arg: Self::RustType) -> Self { - Self::as_ctype(arg) - } - - /// Return a value to C, via an "output parameter" - /// - /// # Safety - /// - /// - `arg_out` must not be NULL and must be properly aligned and pointing to valid memory - /// of the size of CType. - unsafe fn val_to_arg_out(val: Self::RustType, arg_out: *mut Self) { - debug_assert!(!arg_out.is_null()); - // SAFETY: - // - arg_out is not NULL (promised by caller, asserted) - // - arg_out is properly aligned and points to valid memory (promised by caller) - unsafe { *arg_out = Self::as_ctype(val) }; - } -} - -/// Support for values passed to Rust by pointer. These are represented as opaque structs in C, -/// and always handled as pointers. -pub(crate) trait PassByPointer: Sized { - /// Take a value from C as an argument. - /// - /// # Safety - /// - /// - arg must not be NULL - /// - arg must be a value returned from Box::into_raw (via return_ptr or ptr_to_arg_out) - /// - arg becomes invalid and must not be used after this call - unsafe fn take_from_ptr_arg(arg: *mut Self) -> Self { - debug_assert!(!arg.is_null()); - // SAFETY: see docstring - unsafe { *(Box::from_raw(arg)) } - } - - /// Borrow a value from C as an argument. - /// - /// # Safety - /// - /// - arg must not be NULL - /// - *arg must be a valid instance of Self - /// - arg must be valid for the lifetime assigned by the caller - /// - arg must not be modified by anything else during that lifetime - unsafe fn from_ptr_arg_ref<'a>(arg: *const Self) -> &'a Self { - debug_assert!(!arg.is_null()); - // SAFETY: see docstring - unsafe { &*arg } - } - - /// Mutably borrow a value from C as an argument. - /// - /// # Safety - /// - /// - arg must not be NULL - /// - *arg must be a valid instance of Self - /// - arg must be valid for the lifetime assigned by the caller - /// - arg must not be accessed by anything else during that lifetime - unsafe fn from_ptr_arg_ref_mut<'a>(arg: *mut Self) -> &'a mut Self { - debug_assert!(!arg.is_null()); - // SAFETY: see docstring - unsafe { &mut *arg } - } - - /// Return a value to C, transferring ownership - /// - /// # Safety - /// - /// - the caller must ensure that the value is eventually freed - unsafe fn return_ptr(self) -> *mut Self { - Box::into_raw(Box::new(self)) - } - - /// Return a value to C, transferring ownership, via an "output parameter". - /// - /// # Safety - /// - /// - the caller must ensure that the value is eventually freed - /// - arg_out must not be NULL - /// - arg_out must point to valid, properly aligned memory for a pointer value - unsafe fn ptr_to_arg_out(self, arg_out: *mut *mut Self) { - debug_assert!(!arg_out.is_null()); - // SAFETY: see docstring - unsafe { *arg_out = self.return_ptr() }; - } -} - -/// Support for C lists of objects referenced by value. -/// -/// The underlying C type should have three fields, containing items, length, and capacity. The -/// required trait functions just fetch and set these fields. -/// -/// The PassByValue trait will be implemented automatically, converting between the C type and -/// `Vec`. -/// -/// The element type can be PassByValue or PassByPointer. If the latter, it should use either -/// `NonNull` or `Option>` to represent the element. The latter is an "optional -/// pointer list", where elements can be omitted. -/// -/// For most cases, it is only necessary to implement `tc_.._free` that calls one of the -/// drop_..._list functions. -/// -/// # Safety -/// -/// The C type must be documented as read-only. None of the fields may be modified, nor anything -/// accessible via the `items` array. The exception is modification via "taking" elements. -/// -/// This class guarantees that the items pointer is non-NULL for any valid list (even when len=0). -pub(crate) trait CList: Sized { - type Element; - - /// Create a new CList from the given items, len, and capacity. - /// - /// # Safety - /// - /// The arguments must either: - /// - be NULL, 0, and 0, respectively; or - /// - be valid for Vec::from_raw_parts - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self; - - /// Return a mutable slice representing the elements in this list. - fn slice(&mut self) -> &mut [Self::Element]; - - /// Get the items, len, and capacity (in that order) for this instance. These must be - /// precisely the same values passed tearlier to `from_raw_parts`. - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize); - - /// Generate a NULL value. By default this is a NULL items pointer with zero length and - /// capacity. - fn null_value() -> Self { - // SAFETY: - // - satisfies the first case in from_raw_parts' safety documentation - unsafe { Self::from_raw_parts(std::ptr::null_mut(), 0, 0) } - } -} - -/// Given a CList containing pass-by-value values, drop all of the values and -/// the list. -/// -/// This is a convenience function for `tc_.._list_free` functions. -/// -/// # Safety -/// -/// - List must be non-NULL and point to a valid CL instance -/// - The caller must not use the value array points to after this function, as -/// it has been freed. It will be replaced with the null value. -pub(crate) unsafe fn drop_value_list(list: *mut CL) -where - CL: CList, - T: PassByValue, -{ - debug_assert!(!list.is_null()); - - // SAFETY: - // - *list is a valid CL (promised by caller) - let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; - - // first, drop each of the elements in turn - for e in vec.drain(..) { - // SAFETY: - // - e is a valid Element (promised by caller) - // - e is owned - drop(unsafe { PassByValue::val_from_arg(e) }); - } - // then drop the vector - drop(vec); -} - -/// Given a CList containing NonNull pointers, drop all of the pointed-to values and the list. -/// -/// This is a convenience function for `tc_.._list_free` functions. -/// -/// # Safety -/// -/// - List must be non-NULL and point to a valid CL instance -/// - The caller must not use the value array points to after this function, as -/// it has been freed. It will be replaced with the null value. -#[allow(dead_code)] // this was useful once, and might be again? -pub(crate) unsafe fn drop_pointer_list(list: *mut CL) -where - CL: CList>, - T: PassByPointer, -{ - debug_assert!(!list.is_null()); - // SAFETY: - // - *list is a valid CL (promised by caller) - let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; - - // first, drop each of the elements in turn - for e in vec.drain(..) { - // SAFETY: - // - e is a valid Element (promised by caller) - // - e is owned - drop(unsafe { PassByPointer::take_from_ptr_arg(e.as_ptr()) }); - } - // then drop the vector - drop(vec); -} - -/// Given a CList containing optional pointers, drop all of the non-null pointed-to values and the -/// list. -/// -/// This is a convenience function for `tc_.._list_free` functions, for lists from which items -/// can be taken. -/// -/// # Safety -/// -/// - List must be non-NULL and point to a valid CL instance -/// - The caller must not use the value array points to after this function, as -/// it has been freed. It will be replaced with the null value. -pub(crate) unsafe fn drop_optional_pointer_list(list: *mut CL) -where - CL: CList>>, - T: PassByPointer, -{ - debug_assert!(!list.is_null()); - // SAFETY: - // - *list is a valid CL (promised by caller) - let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; - - // first, drop each of the elements in turn - for e in vec.drain(..).flatten() { - // SAFETY: - // - e is a valid Element (promised by caller) - // - e is owned - drop(unsafe { PassByPointer::take_from_ptr_arg(e.as_ptr()) }); - } - // then drop the vector - drop(vec); -} - -/// Take a value from an optional pointer list, returning the value and replacing its array -/// element with NULL. -/// -/// This is a convenience function for `tc_.._list_take` functions, for lists from which items -/// can be taken. -/// -/// The returned value will be None if the element has already been taken, or if the index is -/// out of bounds. -/// -/// # Safety -/// -/// - List must be non-NULL and point to a valid CL instance -pub(crate) unsafe fn take_optional_pointer_list_item( - list: *mut CL, - index: usize, -) -> Option> -where - CL: CList>>, - T: PassByPointer, -{ - debug_assert!(!list.is_null()); - - // SAFETy: - // - list is properly aligned, dereferencable, and points to an initialized CL, since it is valid - // - the lifetime of the resulting reference is limited to this function, during which time - // nothing else refers to this memory. - let slice = unsafe { list.as_mut() }.unwrap().slice(); - if let Some(elt_ref) = slice.get_mut(index) { - let mut rv = None; - if let Some(elt) = elt_ref.as_mut() { - rv = Some(*elt); - *elt_ref = None; // clear out the array element - } - rv - } else { - None // index out of bounds - } -} - -impl PassByValue for A -where - A: CList, -{ - type RustType = Vec; - - unsafe fn from_ctype(self) -> Self::RustType { - let (items, len, cap) = self.into_raw_parts(); - debug_assert!(!items.is_null()); - // SAFETY: - // - CList::from_raw_parts requires that items, len, and cap be valid for - // Vec::from_raw_parts if not NULL, and they are not NULL (as promised by caller) - // - CList::into_raw_parts returns precisely the values passed to from_raw_parts. - // - those parts are passed to Vec::from_raw_parts here. - unsafe { Vec::from_raw_parts(items as *mut _, len, cap) } - } - - fn as_ctype(arg: Self::RustType) -> Self { - let (items, len, cap) = vec_into_raw_parts(arg); - // SAFETY: - // - satisfies the second case in from_raw_parts' safety documentation - unsafe { Self::from_raw_parts(items, len, cap) } - } -} diff --git a/taskchampion/lib/src/uda.rs b/taskchampion/lib/src/uda.rs deleted file mode 100644 index 3994bfc82..000000000 --- a/taskchampion/lib/src/uda.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::traits::*; -use crate::types::*; - -#[ffizz_header::item] -#[ffizz(order = 500)] -/// ***** TCUda ***** -/// -/// TCUda contains the details of a UDA. -/// -/// ```c -/// typedef struct TCUda { -/// // Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. -/// struct TCString ns; -/// // UDA key. Must not be NULL. -/// struct TCString key; -/// // Content of the UDA. Must not be NULL. -/// struct TCString value; -/// } TCUda; -/// ``` -#[repr(C)] -#[derive(Default)] -pub struct TCUda { - pub ns: TCString, - pub key: TCString, - pub value: TCString, -} - -pub(crate) struct Uda { - pub ns: Option>, - pub key: RustString<'static>, - pub value: RustString<'static>, -} - -impl PassByValue for TCUda { - type RustType = Uda; - - unsafe fn from_ctype(self) -> Self::RustType { - Uda { - ns: if self.ns.is_null() { - None - } else { - // SAFETY: - // - self is owned, so we can take ownership of this TCString - // - self.ns is a valid, non-null TCString (NULL just checked) - Some(unsafe { TCString::val_from_arg(self.ns) }) - }, - // SAFETY: - // - self is owned, so we can take ownership of this TCString - // - self.key is a valid, non-null TCString (see type docstring) - key: unsafe { TCString::val_from_arg(self.key) }, - // SAFETY: - // - self is owned, so we can take ownership of this TCString - // - self.value is a valid, non-null TCString (see type docstring) - value: unsafe { TCString::val_from_arg(self.value) }, - } - } - - fn as_ctype(uda: Uda) -> Self { - TCUda { - // SAFETY: caller assumes ownership of this value - ns: if let Some(ns) = uda.ns { - unsafe { TCString::return_val(ns) } - } else { - TCString::default() - }, - // SAFETY: caller assumes ownership of this value - key: unsafe { TCString::return_val(uda.key) }, - // SAFETY: caller assumes ownership of this value - value: unsafe { TCString::return_val(uda.value) }, - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 510)] -/// ***** TCUdaList ***** -/// -/// TCUdaList represents a list of UDAs. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCUdaList { -/// // number of UDAs in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of UDAs. These remain owned by the TCUdaList instance and will be freed by -/// // tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. -/// struct TCUda *items; -/// } TCUdaList; -/// ``` -#[repr(C)] -pub struct TCUdaList { - /// number of UDAs in items - len: libc::size_t, - - /// total size of items (internal use only) - _capacity: libc::size_t, - - /// Array of UDAs. These remain owned by the TCUdaList instance and will be freed by - /// tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. - items: *mut TCUda, -} - -impl CList for TCUdaList { - type Element = TCUda; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCUdaList { - len, - _capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 501)] -/// Free a TCUda instance. The instance, and the TCStrings it contains, must not be used -/// after this call. -/// -/// ```c -/// EXTERN_C void tc_uda_free(struct TCUda *tcuda); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUda) { - debug_assert!(!tcuda.is_null()); - // SAFETY: - // - *tcuda is a valid TCUda (caller promises to treat it as read-only) - let uda = unsafe { TCUda::take_val_from_arg(tcuda, TCUda::default()) }; - drop(uda); -} - -#[ffizz_header::item] -#[ffizz(order = 511)] -/// Free a TCUdaList instance. The instance, and all TCUdas it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList. -/// -/// ```c -/// EXTERN_C void tc_uda_list_free(struct TCUdaList *tcudas); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uda_list_free(tcudas: *mut TCUdaList) { - // SAFETY: - // - tcudas is not NULL and points to a valid TCUdaList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tcudas) } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tcudas = unsafe { TCUdaList::return_val(Vec::new()) }; - assert!(!tcudas.items.is_null()); - assert_eq!(tcudas.len, 0); - assert_eq!(tcudas._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcudas = unsafe { TCUdaList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_uda_list_free(&mut tcudas) }; - assert!(tcudas.items.is_null()); - assert_eq!(tcudas.len, 0); - assert_eq!(tcudas._capacity, 0); - } -} diff --git a/taskchampion/lib/src/util.rs b/taskchampion/lib/src/util.rs deleted file mode 100644 index bfd739282..000000000 --- a/taskchampion/lib/src/util.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::string::RustString; - -pub(crate) fn err_to_ruststring(e: impl std::string::ToString) -> RustString<'static> { - RustString::from(e.to_string()) -} - -/// An implementation of Vec::into_raw_parts, which is still unstable. Returns ptr, len, cap. -pub(crate) fn vec_into_raw_parts(vec: Vec) -> (*mut T, usize, usize) { - // emulate Vec::into_raw_parts(): - // - disable dropping the Vec with ManuallyDrop - // - extract ptr, len, and capacity using those methods - let mut vec = std::mem::ManuallyDrop::new(vec); - (vec.as_mut_ptr(), vec.len(), vec.capacity()) -} - -/// An implementation of String::into_raw_parts, which is still unstable. Returns ptr, len, cap. -pub(crate) fn string_into_raw_parts(string: String) -> (*mut u8, usize, usize) { - // emulate String::into_raw_parts(): - // - disable dropping the String with ManuallyDrop - // - extract ptr, len, and capacity using those methods - let mut string = std::mem::ManuallyDrop::new(string); - (string.as_mut_ptr(), string.len(), string.capacity()) -} diff --git a/taskchampion/lib/src/uuid.rs b/taskchampion/lib/src/uuid.rs deleted file mode 100644 index c979074ce..000000000 --- a/taskchampion/lib/src/uuid.rs +++ /dev/null @@ -1,239 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use libc; -use taskchampion::Uuid; - -#[ffizz_header::item] -#[ffizz(order = 300)] -/// ***** TCUuid ***** -/// -/// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. -/// Uuids are typically treated as opaque, but the bytes are available in big-endian format. -/// -/// ```c -/// typedef struct TCUuid { -/// uint8_t bytes[16]; -/// } TCUuid; -/// ``` -#[repr(C)] -#[derive(Debug, Default)] -pub struct TCUuid([u8; 16]); - -impl PassByValue for TCUuid { - type RustType = Uuid; - - unsafe fn from_ctype(self) -> Self::RustType { - // SAFETY: - // - any 16-byte value is a valid Uuid - Uuid::from_bytes(self.0) - } - - fn as_ctype(arg: Uuid) -> Self { - TCUuid(*arg.as_bytes()) - } -} - -#[ffizz_header::item] -#[ffizz(order = 301)] -/// Length, in bytes, of the string representation of a UUID (without NUL terminator) -/// -/// ```c -/// #define TC_UUID_STRING_BYTES 36 -/// ``` -// TODO: debug_assert or static_assert this somewhere? -pub const TC_UUID_STRING_BYTES: usize = 36; - -#[ffizz_header::item] -#[ffizz(order = 310)] -/// TCUuidList represents a list of uuids. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCUuidList { -/// // number of uuids in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of uuids. This pointer is never NULL for a valid TCUuidList. -/// struct TCUuid *items; -/// } TCUuidList; -/// ``` -#[repr(C)] -pub struct TCUuidList { - /// number of uuids in items - len: libc::size_t, - - /// total size of items (internal use only) - capacity: libc::size_t, - - /// Array of uuids. This pointer is never NULL for a valid TCUuidList. - items: *mut TCUuid, -} - -impl CList for TCUuidList { - type Element = TCUuid; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCUuidList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Create a new, randomly-generated UUID. -/// -/// ```c -/// EXTERN_C struct TCUuid tc_uuid_new_v4(void); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_new_v4() -> TCUuid { - // SAFETY: - // - value is not allocated - unsafe { TCUuid::return_val(Uuid::new_v4()) } -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Create a new UUID with the nil value. -/// -/// ```c -/// EXTERN_C struct TCUuid tc_uuid_nil(void); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid { - // SAFETY: - // - value is not allocated - unsafe { TCUuid::return_val(Uuid::nil()) } -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Write the string representation of a TCUuid into the given buffer, which must be -/// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. -/// -/// ```c -/// EXTERN_C void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) { - debug_assert!(!buf.is_null()); - // SAFETY: - // - buf is valid for len bytes (by C convention) - // - (no alignment requirements for a byte slice) - // - content of buf will not be mutated during the lifetime of this slice (lifetime - // does not outlive this function call) - // - the length of the buffer is less than isize::MAX (promised by caller) - let buf: &mut [u8] = - unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, TC_UUID_STRING_BYTES) }; - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; - uuid.as_hyphenated().encode_lower(buf); -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Return the hyphenated string representation of a TCUuid. The returned string -/// must be freed with tc_string_free. -/// -/// ```c -/// EXTERN_C struct TCString tc_uuid_to_str(struct TCUuid tcuuid); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> TCString { - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; - let s = uuid.to_string(); - // SAFETY: - // - caller promises to free this value. - unsafe { TCString::return_val(s.into()) } -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given -/// string is not valid. -/// -/// ```c -/// EXTERN_C TCResult tc_uuid_from_str(struct TCString s, struct TCUuid *uuid_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_from_str(s: TCString, uuid_out: *mut TCUuid) -> TCResult { - debug_assert!(!s.is_null()); - debug_assert!(!uuid_out.is_null()); - // SAFETY: - // - s is valid (promised by caller) - // - caller will not use s after this call (convention) - let mut s = unsafe { TCString::val_from_arg(s) }; - if let Ok(s) = s.as_str() { - if let Ok(u) = Uuid::parse_str(s) { - // SAFETY: - // - uuid_out is not NULL (promised by caller) - // - alignment is not required - unsafe { TCUuid::val_to_arg_out(u, uuid_out) }; - return TCResult::Ok; - } - } - TCResult::Error -} - -#[ffizz_header::item] -#[ffizz(order = 312)] -/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. -/// -/// ```c -/// EXTERN_C void tc_uuid_list_free(struct TCUuidList *tcuuids); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { - // SAFETY: - // - tcuuids is not NULL and points to a valid TCUuidList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tcuuids) }; -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tcuuids = unsafe { TCUuidList::return_val(Vec::new()) }; - assert!(!tcuuids.items.is_null()); - assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids.capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcuuids = unsafe { TCUuidList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_uuid_list_free(&mut tcuuids) }; - assert!(tcuuids.items.is_null()); - assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids.capacity, 0); - } -} diff --git a/taskchampion/lib/src/workingset.rs b/taskchampion/lib/src/workingset.rs deleted file mode 100644 index ef9ceaf99..000000000 --- a/taskchampion/lib/src/workingset.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use taskchampion::{Uuid, WorkingSet}; - -#[ffizz_header::item] -#[ffizz(order = 1100)] -/// ***** TCWorkingSet ***** -/// -/// A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically -/// updated based on changes in the replica. Its lifetime is independent of the replica and it can -/// be freed at any time. -/// -/// To iterate over a working set, search indexes 1 through largest_index. -/// -/// # Safety -/// -/// The `*TCWorkingSet` returned from `tc_replica_working_set` is owned by the caller and -/// must later be freed to avoid a memory leak. Its lifetime is independent of the replica -/// from which it was generated. -/// -/// Any function taking a `*TCWorkingSet` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from `tc_replica_working_set` -/// - the memory referenced by the pointer must never be accessed by C code; and -/// - except for `tc_replica_free`, ownership of a `*TCWorkingSet` remains with the caller. -/// -/// Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again. -/// -/// TCWorkingSet is not threadsafe. -/// -/// ```c -/// typedef struct TCWorkingSet TCWorkingSet; -/// ``` -pub struct TCWorkingSet(WorkingSet); - -impl PassByPointer for TCWorkingSet {} - -impl From for TCWorkingSet { - fn from(ws: WorkingSet) -> TCWorkingSet { - TCWorkingSet(ws) - } -} - -/// Utility function to get a shared reference to the underlying WorkingSet. -fn wrap(ws: *mut TCWorkingSet, f: F) -> T -where - F: FnOnce(&WorkingSet) -> T, -{ - // SAFETY: - // - ws is not null (promised by caller) - // - ws outlives 'a (promised by caller) - let tcws: &TCWorkingSet = unsafe { TCWorkingSet::from_ptr_arg_ref(ws) }; - f(&tcws.0) -} - -#[ffizz_header::item] -#[ffizz(order = 1101)] -/// Get the working set's length, or the number of UUIDs it contains. -/// -/// ```c -/// EXTERN_C size_t tc_working_set_len(struct TCWorkingSet *ws); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_len(ws: *mut TCWorkingSet) -> usize { - wrap(ws, |ws| ws.len()) -} - -#[ffizz_header::item] -#[ffizz(order = 1101)] -/// Get the working set's largest index. -/// -/// ```c -/// EXTERN_C size_t tc_working_set_largest_index(struct TCWorkingSet *ws); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_largest_index(ws: *mut TCWorkingSet) -> usize { - wrap(ws, |ws| ws.largest_index()) -} - -#[ffizz_header::item] -#[ffizz(order = 1101)] -/// Get the UUID for the task at the given index. Returns true if the UUID exists in the working -/// set. If not, returns false and does not change uuid_out. -/// -/// ```c -/// EXTERN_C bool tc_working_set_by_index(struct TCWorkingSet *ws, size_t index, struct TCUuid *uuid_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_by_index( - ws: *mut TCWorkingSet, - index: usize, - uuid_out: *mut TCUuid, -) -> bool { - debug_assert!(!uuid_out.is_null()); - wrap(ws, |ws| { - if let Some(uuid) = ws.by_index(index) { - // SAFETY: - // - uuid_out is not NULL (promised by caller) - // - alignment is not required - unsafe { TCUuid::val_to_arg_out(uuid, uuid_out) }; - true - } else { - false - } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1101)] -/// Get the working set index for the task with the given UUID. Returns 0 if the task is not in -/// the working set. -/// -/// ```c -/// EXTERN_C size_t tc_working_set_by_uuid(struct TCWorkingSet *ws, struct TCUuid uuid); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_by_uuid(ws: *mut TCWorkingSet, uuid: TCUuid) -> usize { - wrap(ws, |ws| { - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { TCUuid::val_from_arg(uuid) }; - ws.by_uuid(uuid).unwrap_or(0) - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1102)] -/// Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this -/// function returns, and must not be freed more than once. -/// -/// ```c -/// EXTERN_C void tc_working_set_free(struct TCWorkingSet *ws); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_free(ws: *mut TCWorkingSet) { - // SAFETY: - // - rep is not NULL (promised by caller) - // - caller will not use the TCWorkingSet after this (promised by caller) - let ws = unsafe { TCWorkingSet::take_from_ptr_arg(ws) }; - drop(ws); -} diff --git a/taskchampion/lib/taskchampion.h b/taskchampion/lib/taskchampion.h deleted file mode 100644 index 2f3237ab2..000000000 --- a/taskchampion/lib/taskchampion.h +++ /dev/null @@ -1,917 +0,0 @@ -// TaskChampion -// -// This file defines the C interface to libtaskchampion. This is a thin wrapper around the Rust -// `taskchampion` crate. Refer to the documentation for that crate at -// https://docs.rs/taskchampion/latest/taskchampion/ for API details. The comments in this file -// focus mostly on the low-level details of passing values to and from TaskChampion. -// -// # Overview -// -// This library defines four major types used to interact with the API, that map directly to Rust -// types. -// -// * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html -// * TCTask - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html -// * TCServer - see https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html -// * TCWorkingSet - see https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html -// -// It also defines a few utility types: -// -// * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. -// * TC…List - a list of objects represented as a C array -// * see below for the remainder -// -// # Safety -// -// Each type contains specific instructions to ensure memory safety. The general rules are as -// follows. -// -// No types in this library are threadsafe. All values should be used in only one thread for their -// entire lifetime. It is safe to use unrelated values in different threads (for example, -// different threads may use different TCReplica values concurrently). -// -// ## Pass by Pointer -// -// Several types such as TCReplica and TCString are "opaque" types and always handled as pointers -// in C. The bytes these pointers address are private to the Rust implementation and must not be -// accessed from C. -// -// Pass-by-pointer values have exactly one owner, and that owner is responsible for freeing the -// value (using a `tc_…_free` function), or transferring ownership elsewhere. Except where -// documented otherwise, when a value is passed to C, ownership passes to C as well. When a value -// is passed to Rust, ownership stays with the C code. The exception is TCString, ownership of -// which passes to Rust when it is used as a function argument. -// -// The limited circumstances where one value must not outlive another, due to pointer references -// between them, are documented below. -// -// ## Pass by Value -// -// Types such as TCUuid and TC…List are passed by value, and contain fields that are accessible -// from C. C code is free to access the content of these types in a _read_only_ fashion. -// -// Pass-by-value values that contain pointers also have exactly one owner, responsible for freeing -// the value or transferring ownership. The tc_…_free functions for these types will replace the -// pointers with NULL to guard against use-after-free errors. The interior pointers in such values -// should never be freed directly (for example, `tc_string_free(tcuda.value)` is an error). -// -// TCUuid is a special case, because it does not contain pointers. It can be freely copied and -// need not be freed. -// -// ## Lists -// -// Lists are a special kind of pass-by-value type. Each contains `len` and `items`, where `items` -// is an array of length `len`. Lists, and the values in the `items` array, must be treated as -// read-only. On return from an API function, a list's ownership is with the C caller, which must -// eventually free the list. List data must be freed with the `tc_…_list_free` function. It is an -// error to free any value in the `items` array of a list. - -#ifndef TASKCHAMPION_H -#define TASKCHAMPION_H - -#include -#include -#include - -#ifdef __cplusplus -#define EXTERN_C extern "C" -#else -#define EXTERN_C -#endif // __cplusplus - -// ***** TCResult ***** -// -// A result from a TC operation. Typically if this value is TC_RESULT_ERROR, -// the associated object's `tc_.._error` method will return an error message. -enum TCResult -#ifdef __cplusplus - : int32_t -#endif // __cplusplus -{ - TC_RESULT_ERROR = -1, - TC_RESULT_OK = 0, -}; -#ifndef __cplusplus -typedef int32_t TCResult; -#endif // __cplusplus - -// ***** TCString ***** -// -// TCString supports passing strings into and out of the TaskChampion API. -// -// # Rust Strings and C Strings -// -// A Rust string can contain embedded NUL characters, while C considers such a character to mark -// the end of a string. Strings containing embedded NULs cannot be represented as a "C string" -// and must be accessed using `tc_string_content_and_len` and `tc_string_clone_with_len`. In -// general, these two functions should be used for handling arbitrary data, while more convenient -// forms may be used where embedded NUL characters are impossible, such as in static strings. -// -// # UTF-8 -// -// TaskChampion expects all strings to be valid UTF-8. `tc_string_…` functions will fail if given -// a `*TCString` containing invalid UTF-8. -// -// # Safety -// -// The `ptr` field may be checked for NULL, where documentation indicates this is possible. All -// other fields in a TCString are private and must not be used from C. They exist in the struct -// to ensure proper allocation and alignment. -// -// When a `TCString` appears as a return value or output argument, ownership is passed to the -// caller. The caller must pass that ownership back to another function or free the string. -// -// Any function taking a `TCString` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from a tc_… function; and -// - the memory referenced by the pointer must never be modified by C code. -// -// Unless specified otherwise, TaskChampion functions take ownership of a `TCString` when it is -// given as a function argument, and the caller must not use or free TCStrings after passing them -// to such API functions. -// -// A TCString with a NULL `ptr` field need not be freed, although tc_free_string will not fail -// for such a value. -// -// TCString is not threadsafe. -typedef struct TCString { - void *ptr; // opaque, but may be checked for NULL - size_t _u1; // reserved - size_t _u2; // reserved - uint8_t _u3; // reserved -} TCString; - -// Create a new TCString referencing the given C string. The C string must remain valid and -// unchanged until after the TCString is freed. It's typically easiest to ensure this by using a -// static string. -// -// NOTE: this function does _not_ take responsibility for freeing the given C string. The -// given string can be freed once the TCString referencing it has been freed. -// -// For example: -// -// ```text -// char *url = get_item_url(..); // dynamically allocate C string -// tc_task_annotate(task, tc_string_borrow(url)); // TCString created, passed, and freed -// free(url); // string is no longer referenced and can be freed -// ``` -EXTERN_C struct TCString tc_string_borrow(const char *cstr); - -// Create a new TCString by cloning the content of the given C string. The resulting TCString -// is independent of the given string, which can be freed or overwritten immediately. -EXTERN_C struct TCString tc_string_clone(const char *cstr); - -// Create a new TCString containing the given string with the given length. This allows creation -// of strings containing embedded NUL characters. As with `tc_string_clone`, the resulting -// TCString is independent of the passed buffer, which may be reused or freed immediately. -// -// The length should _not_ include any trailing NUL. -// -// The given length must be less than half the maximum value of usize. -EXTERN_C struct TCString tc_string_clone_with_len(const char *buf, size_t len); - -// Get the content of the string as a regular C string. The given string must be valid. The -// returned value is NULL if the string contains NUL bytes or (in some cases) invalid UTF-8. The -// returned C string is valid until the TCString is freed or passed to another TC API function. -// -// In general, prefer [`tc_string_content_with_len`] except when it's certain that the string is -// valid and NUL-free. -// -// This function takes the TCString by pointer because it may be modified in-place to add a NUL -// terminator. The pointer must not be NULL. -// -// This function does _not_ take ownership of the TCString. -EXTERN_C const char *tc_string_content(const struct TCString *tcstring); - -// Get the content of the string as a pointer and length. The given string must not be NULL. -// This function can return any string, even one including NUL bytes or invalid UTF-8. The -// returned buffer is valid until the TCString is freed or passed to another TaskChampio -// function. -// -// This function takes the TCString by pointer because it may be modified in-place to add a NUL -// terminator. The pointer must not be NULL. -// -// This function does _not_ take ownership of the TCString. -EXTERN_C const char *tc_string_content_with_len(const struct TCString *tcstring, size_t *len_out); - -// Free a TCString. The given string must not be NULL. The string must not be used -// after this function returns, and must not be freed more than once. -EXTERN_C void tc_string_free(struct TCString *tcstring); - -// ***** TCStringList ***** -// -// TCStringList represents a list of strings. -// -// The content of this struct must be treated as read-only. -typedef struct TCStringList { - // number of strings in items - size_t len; - // reserved - size_t _u1; - // TCStringList representing each string. These remain owned by the TCStringList instance and will - // be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the - // *TCStringList at indexes 0..len-1 are not NULL. - struct TCString *items; -} TCStringList; - -// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. -EXTERN_C void tc_string_list_free(struct TCStringList *tcstrings); - -// ***** TCUuid ***** -// -// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. -// Uuids are typically treated as opaque, but the bytes are available in big-endian format. -typedef struct TCUuid { - uint8_t bytes[16]; -} TCUuid; - -// Length, in bytes, of the string representation of a UUID (without NUL terminator) -#define TC_UUID_STRING_BYTES 36 - -// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given -// string is not valid. -EXTERN_C TCResult tc_uuid_from_str(struct TCString s, struct TCUuid *uuid_out); - -// Create a new, randomly-generated UUID. -EXTERN_C struct TCUuid tc_uuid_new_v4(void); - -// Create a new UUID with the nil value. -EXTERN_C struct TCUuid tc_uuid_nil(void); - -// Write the string representation of a TCUuid into the given buffer, which must be -// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. -EXTERN_C void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); - -// Return the hyphenated string representation of a TCUuid. The returned string -// must be freed with tc_string_free. -EXTERN_C struct TCString tc_uuid_to_str(struct TCUuid tcuuid); - -// TCUuidList represents a list of uuids. -// -// The content of this struct must be treated as read-only. -typedef struct TCUuidList { - // number of uuids in items - size_t len; - // reserved - size_t _u1; - // Array of uuids. This pointer is never NULL for a valid TCUuidList. - struct TCUuid *items; -} TCUuidList; - -// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. -EXTERN_C void tc_uuid_list_free(struct TCUuidList *tcuuids); - -// ***** TCAnnotation ***** -// -// TCAnnotation contains the details of an annotation. -// -// # Safety -// -// An annotation must be initialized from a tc_.. function, and later freed -// with `tc_annotation_free` or `tc_annotation_list_free`. -// -// Any function taking a `*TCAnnotation` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from a tc_… function; -// - the memory referenced by the pointer must never be modified by C code; and -// - ownership transfers to the called function, and the value must not be used -// after the call returns. In fact, the value will be zeroed out to ensure this. -// -// TCAnnotations are not threadsafe. -typedef struct TCAnnotation { - // Time the annotation was made. Must be nonzero. - time_t entry; - // Content of the annotation. Must not be NULL. - TCString description; -} TCAnnotation; - -// Free a TCAnnotation instance. The instance, and the TCString it contains, must not be used -// after this call. -EXTERN_C void tc_annotation_free(struct TCAnnotation *tcann); - -// ***** TCAnnotationList ***** -// -// TCAnnotationList represents a list of annotations. -// -// The content of this struct must be treated as read-only. -typedef struct TCAnnotationList { - // number of annotations in items - size_t len; - // reserved - size_t _u1; - // Array of annotations. These remain owned by the TCAnnotationList instance and will be freed by - // tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. - struct TCAnnotation *items; -} TCAnnotationList; - -// Free a TCAnnotationList instance. The instance, and all TCAnnotations it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCAnnotationList. -EXTERN_C void tc_annotation_list_free(struct TCAnnotationList *tcanns); - -// ***** TCUda ***** -// -// TCUda contains the details of a UDA. -typedef struct TCUda { - // Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. - struct TCString ns; - // UDA key. Must not be NULL. - struct TCString key; - // Content of the UDA. Must not be NULL. - struct TCString value; -} TCUda; - -// Free a TCUda instance. The instance, and the TCStrings it contains, must not be used -// after this call. -EXTERN_C void tc_uda_free(struct TCUda *tcuda); - -// ***** TCUdaList ***** -// -// TCUdaList represents a list of UDAs. -// -// The content of this struct must be treated as read-only. -typedef struct TCUdaList { - // number of UDAs in items - size_t len; - // reserved - size_t _u1; - // Array of UDAs. These remain owned by the TCUdaList instance and will be freed by - // tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. - struct TCUda *items; -} TCUdaList; - -// Free a TCUdaList instance. The instance, and all TCUdas it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList. -EXTERN_C void tc_uda_list_free(struct TCUdaList *tcudas); - -// ***** TCKV ***** -// -// TCKV contains a key/value pair that is part of a task. -// -// Neither key nor value are ever NULL. They remain owned by the TCKV and -// will be freed when it is freed with tc_kv_list_free. -typedef struct TCKV { - struct TCString key; - struct TCString value; -} TCKV; - -// ***** TCKVList ***** -// -// TCKVList represents a list of key/value pairs. -// -// The content of this struct must be treated as read-only. -typedef struct TCKVList { - // number of key/value pairs in items - size_t len; - // reserved - size_t _u1; - // Array of TCKV's. These remain owned by the TCKVList instance and will be freed by - // tc_kv_list_free. This pointer is never NULL for a valid TCKVList. - struct TCKV *items; -} TCKVList; - -// Free a TCKVList instance. The instance, and all TCKVs it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCKVList. -EXTERN_C void tc_kv_list_free(struct TCKVList *tckvs); - -// ***** TCStatus ***** -// -// The status of a task, as defined by the task data model. -#ifdef __cplusplus -typedef enum TCStatus : int32_t { -#else // __cplusplus -typedef int32_t TCStatus; -enum TCStatus { -#endif // __cplusplus - TC_STATUS_PENDING = 0, - TC_STATUS_COMPLETED = 1, - TC_STATUS_DELETED = 2, - TC_STATUS_RECURRING = 3, - // Unknown signifies a status in the task DB that was not - // recognized. - TC_STATUS_UNKNOWN = -1, -#ifdef __cplusplus -} TCStatus; -#else // __cplusplus -}; -#endif // __cplusplus - -// ***** TCServer ***** -// -// TCServer represents an interface to a sync server. Aside from new and free, a server -// has no C-accessible API, but is designed to be passed to `tc_replica_sync`. -// -// ## Safety -// -// TCServer are not threadsafe, and must not be used with multiple replicas simultaneously. -typedef struct TCServer TCServer; - -// Create a new TCServer that operates locally (on-disk). See the TaskChampion docs for the -// description of the arguments. -// -// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -// returned. The caller must free this string. -// -// The server must be freed after it is used - tc_replica_sync does not automatically free it. -EXTERN_C struct TCServer *tc_server_new_local(struct TCString server_dir, struct TCString *error_out); - -// Create a new TCServer that connects to a remote server. See the TaskChampion docs for the -// description of the arguments. -// -// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -// returned. The caller must free this string. -// -// The server must be freed after it is used - tc_replica_sync does not automatically free it. -EXTERN_C struct TCServer *tc_server_new_sync(struct TCString origin, - struct TCUuid client_id, - struct TCString encryption_secret, - struct TCString *error_out); - -// Create a new TCServer that connects to the Google Cloud Platform. See the TaskChampion docs -// for the description of the arguments. -// -// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -// returned. The caller must free this string. -// -// The server must be freed after it is used - tc_replica_sync does not automatically free it. -EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket, - struct TCString credential_path, - struct TCString encryption_secret, - struct TCString *error_out); - -// Free a server. The server may not be used after this function returns and must not be freed -// more than once. -EXTERN_C void tc_server_free(struct TCServer *server); - -// ***** TCReplica ***** -// -// A replica represents an instance of a user's task data, providing an easy interface -// for querying and modifying that data. -// -// # Error Handling -// -// When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then -// `tc_replica_error` will return the error message. -// -// # Safety -// -// The `*TCReplica` returned from `tc_replica_new…` functions is owned by the caller and -// must later be freed to avoid a memory leak. -// -// Any function taking a `*TCReplica` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from a tc_… function; -// - the memory referenced by the pointer must never be modified by C code; and -// - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller. -// -// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. -// -// TCReplicas are not threadsafe. -typedef struct TCReplica TCReplica; - -// ***** TCReplicaOpType ***** -enum TCReplicaOpType -#ifdef __cplusplus - : uint32_t -#endif // __cplusplus -{ - Create = 0, - Delete = 1, - Update = 2, - UndoPoint = 3, -}; -#ifndef __cplusplus -typedef uint32_t TCReplicaOpType; -#endif // __cplusplus - -// ***** TCReplicaOp ***** -struct TCReplicaOp { - TCReplicaOpType operation_type; - void* inner; -}; - -typedef struct TCReplicaOp TCReplicaOp; - -// ***** TCReplicaOpList ***** -struct TCReplicaOpList { - struct TCReplicaOp *items; - size_t len; - size_t capacity; -}; - -typedef struct TCReplicaOpList TCReplicaOpList; - -// Create a new TCReplica with an in-memory database. The contents of the database will be -// lost when it is freed with tc_replica_free. -EXTERN_C struct TCReplica *tc_replica_new_in_memory(void); - -// Create a new TCReplica with an on-disk database having the given filename. On error, a string -// is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller -// must free this string. -EXTERN_C struct TCReplica *tc_replica_new_on_disk(struct TCString path, - bool create_if_missing, - struct TCString *error_out); - -// Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically -// when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already -// been created by this Replica, and may be useful when a Replica instance is held for a long time -// and used to apply more than one user-visible change. -EXTERN_C TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); - -// Get a list of all uuids for tasks in the replica. -// -// Returns a TCUuidList with a NULL items field on error. -// -// The caller must free the UUID list with `tc_uuid_list_free`. -EXTERN_C struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); - -// Get a list of all tasks in the replica. -// -// Returns a TCTaskList with a NULL items field on error. -EXTERN_C struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); - -// Undo local operations in storage. -// -// If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if -// there are no operations that can be done. -EXTERN_C TCResult tc_replica_commit_undo_ops(struct TCReplica *rep, TCReplicaOpList tc_undo_ops, int32_t *undone_out); - -// Get the latest error for a replica, or a string with NULL ptr if no error exists. Subsequent -// calls to this function will return NULL. The rep pointer must not be NULL. The caller must -// free the returned string. -EXTERN_C struct TCString tc_replica_error(struct TCReplica *rep); - -// Get an existing task by its UUID. -// -// Returns NULL when the task does not exist, and on error. Consult tc_replica_error -// to distinguish the two conditions. -EXTERN_C struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid); - -// Return undo local operations until the most recent UndoPoint. -EXTERN_C TCReplicaOpList tc_replica_get_undo_ops(struct TCReplica *rep); - -// Create a new task. The task must not already exist. -// -// Returns the task, or NULL on error. -EXTERN_C struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); - -// Create a new task. The task must not already exist. -// -// Returns the task, or NULL on error. -EXTERN_C struct TCTask *tc_replica_new_task(struct TCReplica *rep, - enum TCStatus status, - struct TCString description); - -// Get the number of local, un-synchronized operations (not including undo points), or -1 on -// error. -EXTERN_C int64_t tc_replica_num_local_operations(struct TCReplica *rep); - -// Get the number of undo points (number of undo calls possible), or -1 on error. -EXTERN_C int64_t tc_replica_num_undo_points(struct TCReplica *rep); - -// Rebuild this replica's working set, based on whether tasks are pending or not. If `renumber` -// is true, then existing tasks may be moved to new working-set indices; in any case, on -// completion all pending tasks are in the working set and all non- pending tasks are not. -EXTERN_C TCResult tc_replica_rebuild_working_set(struct TCReplica *rep, bool renumber); - -// Synchronize this replica with a server. -// -// The `server` argument remains owned by the caller, and must be freed explicitly. -EXTERN_C TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots); - -// Get the current working set for this replica. The resulting value must be freed -// with tc_working_set_free. -// -// Returns NULL on error. -EXTERN_C struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep); - -// Free a replica. The replica may not be used after this function returns and must not be freed -// more than once. -EXTERN_C void tc_replica_free(struct TCReplica *rep); - -// Return description field of old task field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_old_task_description(struct TCReplicaOp *op); - -// Return old value field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_old_value(struct TCReplicaOp *op); - -// Return property field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_property(struct TCReplicaOp *op); - -// Return timestamp field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_timestamp(struct TCReplicaOp *op); - -// Return uuid field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_uuid(struct TCReplicaOp *op); - -// Return value field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_value(struct TCReplicaOp *op); - -// Free a vector of ReplicaOp. The vector may not be used after this function returns and must not be freed -// more than once. -EXTERN_C void tc_replica_op_list_free(struct TCReplicaOpList *oplist); - -// ***** TCTask ***** -// -// A task, as publicly exposed by this library. -// -// A task begins in "immutable" mode. It must be converted to "mutable" mode -// to make any changes, and doing so requires exclusive access to the replica -// until the task is freed or converted back to immutable mode. -// -// An immutable task carries no reference to the replica that created it, and can be used until it -// is freed or converted to a TaskMut. A mutable task carries a reference to the replica and -// must be freed or made immutable before the replica is freed. -// -// All `tc_task_..` functions taking a task as an argument require that it not be NULL. -// -// When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then -// `tc_task_error` will return the error message. -// -// # Safety -// -// A task is an owned object, and must be freed with tc_task_free (or, if part of a list, -// with tc_task_list_free). -// -// Any function taking a `*TCTask` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from a tc_… function; -// - the memory referenced by the pointer must never be modified by C code; and -// - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller. -// -// Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again. -// -// TCTasks are not threadsafe. -typedef struct TCTask TCTask; - -// Get the annotations for the task. -// -// The caller must free the returned TCAnnotationList instance. The TCStringList instance does not -// reference the task and the two may be freed in any order. -EXTERN_C struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); - -// Get all dependencies for a task. -EXTERN_C struct TCUuidList tc_task_get_dependencies(struct TCTask *task); - -// Get a task's description. -EXTERN_C struct TCString tc_task_get_description(struct TCTask *task); - -// Get the entry timestamp for a task (when it was created), or 0 if not set. -EXTERN_C time_t tc_task_get_entry(struct TCTask *task); - -// Get the named legacy UDA from the task. -// -// Returns NULL if the UDA does not exist. -EXTERN_C struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); - -// Get all UDAs for this task. -// -// All TCUdas in this list have a NULL ns field. The entire UDA key is -// included in the key field. The caller must free the returned list. -EXTERN_C struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); - -// Get the modified timestamp for a task, or 0 if not set. -EXTERN_C time_t tc_task_get_modified(struct TCTask *task); - -// Get a task's status. -EXTERN_C enum TCStatus tc_task_get_status(struct TCTask *task); - -// Get the tags for the task. -// -// The caller must free the returned TCStringList instance. The TCStringList instance does not -// reference the task and the two may be freed in any order. -EXTERN_C struct TCStringList tc_task_get_tags(struct TCTask *task); - -// Get the underlying key/value pairs for this task. The returned TCKVList is -// a "snapshot" of the task and will not be updated if the task is subsequently -// modified. It is the caller's responsibility to free the TCKVList. -EXTERN_C struct TCKVList tc_task_get_taskmap(struct TCTask *task); - -// Get the named UDA from the task. -// -// Returns a TCString with NULL ptr field if the UDA does not exist. -EXTERN_C struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key); - -// Get all UDAs for this task. -// -// Legacy UDAs are represented with an empty string in the ns field. -EXTERN_C struct TCUdaList tc_task_get_udas(struct TCTask *task); - -// Get a task's UUID. -EXTERN_C struct TCUuid tc_task_get_uuid(struct TCTask *task); - -// Get a task property's value, or NULL if the task has no such property, (including if the -// property name is not valid utf-8). -EXTERN_C struct TCString tc_task_get_value(struct TCTask *task, struct TCString property); - -// Get the wait timestamp for a task, or 0 if not set. -EXTERN_C time_t tc_task_get_wait(struct TCTask *task); - -// Check if a task has the given tag. If the tag is invalid, this function will return false, as -// that (invalid) tag is not present. No error will be reported via `tc_task_error`. -EXTERN_C bool tc_task_has_tag(struct TCTask *task, struct TCString tag); - -// Check if a task is active (started and not stopped). -EXTERN_C bool tc_task_is_active(struct TCTask *task); - -// Check if a task is blocked (depends on at least one other task). -EXTERN_C bool tc_task_is_blocked(struct TCTask *task); - -// Check if a task is blocking (at least one other task depends on it). -EXTERN_C bool tc_task_is_blocking(struct TCTask *task); - -// Check if a task is waiting. -EXTERN_C bool tc_task_is_waiting(struct TCTask *task); - -// Convert an immutable task into a mutable task. -// -// The task must not be NULL. It is modified in-place, and becomes mutable. -// -// The replica must not be NULL. After this function returns, the replica _cannot be used at all_ -// until this task is made immutable again. This implies that it is not allowed for more than one -// task associated with a replica to be mutable at any time. -// -// Typically mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`: -// -// ```text -// tc_task_to_mut(task, rep); -// success = tc_task_done(task); -// tc_task_to_immut(task, rep); -// if (!success) { ... } -// ``` -EXTERN_C void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica); - -// Add an annotation to a mutable task. This call takes ownership of the -// passed annotation, which must not be used after the call returns. -EXTERN_C TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); - -// Add a dependency. -EXTERN_C TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep); - -// Add a tag to a mutable task. -EXTERN_C TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag); - -// Mark a task as deleted. -EXTERN_C TCResult tc_task_delete(struct TCTask *task); - -// Mark a task as done. -EXTERN_C TCResult tc_task_done(struct TCTask *task); - -// Remove an annotation from a mutable task. -EXTERN_C TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); - -// Remove a dependency. -EXTERN_C TCResult tc_task_remove_dependency(struct TCTask *task, struct TCUuid dep); - -// Remove a UDA fraom a mutable task. -EXTERN_C TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); - -// Remove a tag from a mutable task. -EXTERN_C TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag); - -// Remove a UDA fraom a mutable task. -EXTERN_C TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key); - -// Set a mutable task's description. -EXTERN_C TCResult tc_task_set_description(struct TCTask *task, struct TCString description); - -// Set a mutable task's entry (creation time). Pass entry=0 to unset -// the entry field. -EXTERN_C TCResult tc_task_set_entry(struct TCTask *task, time_t entry); - -// Set a legacy UDA on a mutable task. -EXTERN_C TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value); - -// Set a mutable task's modified timestamp. The value cannot be zero. -EXTERN_C TCResult tc_task_set_modified(struct TCTask *task, time_t modified); - -// Set a mutable task's status. -EXTERN_C TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); - -// Set a UDA on a mutable task. -EXTERN_C TCResult tc_task_set_uda(struct TCTask *task, - struct TCString ns, - struct TCString key, - struct TCString value); - -// Set a mutable task's property value by name. If value.ptr is NULL, the property is removed. -EXTERN_C TCResult tc_task_set_value(struct TCTask *task, struct TCString property, struct TCString value); - -// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. -EXTERN_C TCResult tc_task_set_wait(struct TCTask *task, time_t wait); - -// Start a task. -EXTERN_C TCResult tc_task_start(struct TCTask *task); - -// Stop a task. -EXTERN_C TCResult tc_task_stop(struct TCTask *task); - -// Convert a mutable task into an immutable task. -// -// The task must not be NULL. It is modified in-place, and becomes immutable. -// -// The replica passed to `tc_task_to_mut` may be used freely after this call. -EXTERN_C void tc_task_to_immut(struct TCTask *task); - -// Get the latest error for a task, or a string NULL ptr field if the last operation succeeded. -// Subsequent calls to this function will return NULL. The task pointer must not be NULL. The -// caller must free the returned string. -EXTERN_C struct TCString tc_task_error(struct TCTask *task); - -// Free a task. The given task must not be NULL. The task must not be used after this function -// returns, and must not be freed more than once. -// -// If the task is currently mutable, it will first be made immutable. -EXTERN_C void tc_task_free(struct TCTask *task); - -// ***** TCTaskList ***** -// -// TCTaskList represents a list of tasks. -// -// The content of this struct must be treated as read-only: no fields or anything they reference -// should be modified directly by C code. -// -// When an item is taken from this list, its pointer in `items` is set to NULL. -typedef struct TCTaskList { - // number of tasks in items - size_t len; - // reserved - size_t _u1; - // Array of pointers representing each task. These remain owned by the TCTaskList instance and - // will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList. - // Pointers in the array may be NULL after `tc_task_list_take`. - struct TCTask **items; -} TCTaskList; - -// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. -EXTERN_C void tc_task_list_free(struct TCTaskList *tasks); - -// Take an item from a TCTaskList. After this call, the indexed item is no longer associated -// with the list and becomes the caller's responsibility, just as if it had been returned from -// `tc_replica_get_task`. -// -// The corresponding element in the `items` array will be set to NULL. If that field is already -// NULL (that is, if the item has already been taken), this function will return NULL. If the -// index is out of bounds, this function will also return NULL. -// -// The passed TCTaskList remains owned by the caller. -EXTERN_C struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index); - -// ***** TCWorkingSet ***** -// -// A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically -// updated based on changes in the replica. Its lifetime is independent of the replica and it can -// be freed at any time. -// -// To iterate over a working set, search indexes 1 through largest_index. -// -// # Safety -// -// The `*TCWorkingSet` returned from `tc_replica_working_set` is owned by the caller and -// must later be freed to avoid a memory leak. Its lifetime is independent of the replica -// from which it was generated. -// -// Any function taking a `*TCWorkingSet` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from `tc_replica_working_set` -// - the memory referenced by the pointer must never be accessed by C code; and -// - except for `tc_replica_free`, ownership of a `*TCWorkingSet` remains with the caller. -// -// Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again. -// -// TCWorkingSet is not threadsafe. -typedef struct TCWorkingSet TCWorkingSet; - -// Get the UUID for the task at the given index. Returns true if the UUID exists in the working -// set. If not, returns false and does not change uuid_out. -EXTERN_C bool tc_working_set_by_index(struct TCWorkingSet *ws, size_t index, struct TCUuid *uuid_out); - -// Get the working set index for the task with the given UUID. Returns 0 if the task is not in -// the working set. -EXTERN_C size_t tc_working_set_by_uuid(struct TCWorkingSet *ws, struct TCUuid uuid); - -// Get the working set's largest index. -EXTERN_C size_t tc_working_set_largest_index(struct TCWorkingSet *ws); - -// Get the working set's length, or the number of UUIDs it contains. -EXTERN_C size_t tc_working_set_len(struct TCWorkingSet *ws); - -// Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this -// function returns, and must not be freed more than once. -EXTERN_C void tc_working_set_free(struct TCWorkingSet *ws); - -#endif /* TASKCHAMPION_H */ diff --git a/taskchampion/taskchampion/Cargo.toml b/taskchampion/taskchampion/Cargo.toml index ad0b55b68..4d4daf9d8 100644 --- a/taskchampion/taskchampion/Cargo.toml +++ b/taskchampion/taskchampion/Cargo.toml @@ -11,6 +11,9 @@ license = "MIT" edition = "2021" rust-version = "1.70.0" +[lib] +crate-type = ["staticlib", "rlib"] + [features] default = ["server-sync", "server-gcp"]