Remove taskchampion source from this repo (#3427)

* move taskchampion-lib to src/tc/lib, remove the rest
* update references to taskchampion
* Use a top-level Cargo.toml so everything is consistent
* apply comments from ryneeverett
This commit is contained in:
Dustin J. Mitchell 2024-05-01 22:45:11 -04:00 committed by GitHub
parent ef9613e2d6
commit 94b3e301d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
157 changed files with 62 additions and 16265 deletions

View file

@ -2,10 +2,10 @@ cmake_minimum_required (VERSION 3.22)
include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/tc/lib
${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS})
add_library (task STATIC CLI2.cpp CLI2.h

View file

@ -2,10 +2,10 @@ cmake_minimum_required (VERSION 3.22)
include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/tc/lib
${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS})
set (columns_SRCS Column.cpp Column.h

View file

@ -2,10 +2,10 @@ cmake_minimum_required (VERSION 3.22)
include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/tc/lib
${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS})
set (commands_SRCS Command.cpp Command.h

View file

@ -8,15 +8,18 @@ corrosion_import_crate(
LOCKED
CRATES "taskchampion-lib")
# TODO(#3425): figure out how to create taskchampion.h
include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/tc/lib
${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS})
set (tc_SRCS
ffi.h
lib/taskchampion.h
util.cpp util.h
Replica.cpp Replica.h
Server.cpp Server.h

17
src/tc/lib/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "taskchampion-lib"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
crate-type = ["staticlib", "rlib"]
[dependencies]
libc.workspace = true
anyhow.workspace = true
ffizz-header.workspace = true
taskchampion.workspace = true
[dev-dependencies]
pretty_assertions.workspace = true

2
src/tc/lib/Makefile Normal file
View file

@ -0,0 +1,2 @@
taskchampion.h: cbindgen.toml ../target/debug/libtaskchampion.so
cbindgen --config cbindgen.toml --crate taskchampion-lib --output $@

View file

@ -0,0 +1,186 @@
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<Utc>, 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);
}
}

34
src/tc/lib/src/atomic.rs Normal file
View file

@ -0,0 +1,34 @@
//! 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<DateTime<Utc>> to a libc::time_t, or zero if not set.
impl PassByValue for libc::time_t {
type RustType = Option<DateTime<Utc>>;
unsafe fn from_ctype(self) -> Option<DateTime<Utc>> {
if self != 0 {
return Some(utc_timestamp(self));
}
None
}
fn as_ctype(arg: Option<DateTime<Utc>>) -> libc::time_t {
arg.map(|ts| ts.timestamp() as libc::time_t)
.unwrap_or(0 as libc::time_t)
}
}

155
src/tc/lib/src/kv.rs Normal file
View file

@ -0,0 +1,155 @@
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);
}
}

172
src/tc/lib/src/lib.rs Normal file
View file

@ -0,0 +1,172 @@
#![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 <stdbool.h>
/// #include <stdint.h>
/// #include <time.h>
///
/// #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()
}

904
src/tc/lib/src/replica.rs Normal file
View file

@ -0,0 +1,904 @@
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<RustString<'static>>,
}
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<Replica> 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<T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T
where
F: FnOnce(&mut Replica) -> anyhow::Result<T>,
{
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<T, F>(f: F, error_out: *mut TCString, err_value: T) -> T
where
F: FnOnce() -> anyhow::Result<T>,
{
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<ReplicaOp>,
}
impl From<ReplicaOp> 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<ReplicaOp> = 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);
}
}

25
src/tc/lib/src/result.rs Normal file
View file

@ -0,0 +1,25 @@
#[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,
}

234
src/tc/lib/src/server.rs Normal file
View file

@ -0,0 +1,234 @@
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<dyn Server>);
impl PassByPointer for TCServer {}
impl From<Box<dyn Server>> for TCServer {
fn from(server: Box<dyn Server>) -> TCServer {
TCServer(server)
}
}
impl AsMut<Box<dyn Server>> for TCServer {
fn as_mut(&mut self) -> &mut Box<dyn Server> {
&mut self.0
}
}
/// Utility function to allow using `?` notation to return an error value.
fn wrap<T, F>(f: F, error_out: *mut TCString, err_value: T) -> T
where
F: FnOnce() -> anyhow::Result<T>,
{
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);
}

60
src/tc/lib/src/status.rs Normal file
View file

@ -0,0 +1,60 @@
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<TCStatus> 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<Status> 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,
}
}
}

773
src/tc/lib/src/string.rs Normal file
View file

@ -0,0 +1,773 @@
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<u8>),
}
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<String, std::str::Utf8Error> {
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<PathBuf, std::str::Utf8Error> {
#[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<String> 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<T, F>(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
}
}

1304
src/tc/lib/src/task.rs Normal file

File diff suppressed because it is too large Load diff

351
src/tc/lib/src/traits.rs Normal file
View file

@ -0,0 +1,351 @@
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<Element>`.
///
/// The element type can be PassByValue or PassByPointer. If the latter, it should use either
/// `NonNull<T>` or `Option<NonNull<T>>` 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<CL, T>(list: *mut CL)
where
CL: CList<Element = T>,
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<CL, T>(list: *mut CL)
where
CL: CList<Element = NonNull<T>>,
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<CL, T>(list: *mut CL)
where
CL: CList<Element = Option<NonNull<T>>>,
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<CL, T>(
list: *mut CL,
index: usize,
) -> Option<NonNull<T>>
where
CL: CList<Element = Option<NonNull<T>>>,
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<A> PassByValue for A
where
A: CList,
{
type RustType = Vec<A::Element>;
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) }
}
}

188
src/tc/lib/src/uda.rs Normal file
View file

@ -0,0 +1,188 @@
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<RustString<'static>>,
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);
}
}

31
src/tc/lib/src/util.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::string::RustString;
pub(crate) fn err_to_ruststring(e: anyhow::Error) -> RustString<'static> {
// The default `to_string` representation of `anyhow::Error` only shows the "outermost"
// context, e.g., "Could not connect to server", and omits the juicy details about what
// actually went wrong. So, join all of those contexts with `: ` for presentation to the C++
// layer.
let entire_msg = e
.chain()
.skip(1)
.fold(e.to_string(), |a, b| format!("{}: {}", a, b));
RustString::from(entire_msg)
}
/// An implementation of Vec::into_raw_parts, which is still unstable. Returns ptr, len, cap.
pub(crate) fn vec_into_raw_parts<T>(vec: Vec<T>) -> (*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())
}

239
src/tc/lib/src/uuid.rs Normal file
View file

@ -0,0 +1,239 @@
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);
}
}

View file

@ -0,0 +1,141 @@
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<WorkingSet> for TCWorkingSet {
fn from(ws: WorkingSet) -> TCWorkingSet {
TCWorkingSet(ws)
}
}
/// Utility function to get a shared reference to the underlying WorkingSet.
fn wrap<T, F>(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);
}

917
src/tc/lib/taskchampion.h Normal file
View file

@ -0,0 +1,917 @@
// 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 <stdbool.h>
#include <stdint.h>
#include <time.h>
#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 */