mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +02:00
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:
parent
ef9613e2d6
commit
94b3e301d1
157 changed files with 62 additions and 16265 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
17
src/tc/lib/Cargo.toml
Normal 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
2
src/tc/lib/Makefile
Normal file
|
@ -0,0 +1,2 @@
|
|||
taskchampion.h: cbindgen.toml ../target/debug/libtaskchampion.so
|
||||
cbindgen --config cbindgen.toml --crate taskchampion-lib --output $@
|
186
src/tc/lib/src/annotation.rs
Normal file
186
src/tc/lib/src/annotation.rs
Normal 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
34
src/tc/lib/src/atomic.rs
Normal 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
155
src/tc/lib/src/kv.rs
Normal 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
172
src/tc/lib/src/lib.rs
Normal 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
904
src/tc/lib/src/replica.rs
Normal 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
25
src/tc/lib/src/result.rs
Normal 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
234
src/tc/lib/src/server.rs
Normal 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
60
src/tc/lib/src/status.rs
Normal 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
773
src/tc/lib/src/string.rs
Normal 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
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
351
src/tc/lib/src/traits.rs
Normal 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
188
src/tc/lib/src/uda.rs
Normal 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
31
src/tc/lib/src/util.rs
Normal 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
239
src/tc/lib/src/uuid.rs
Normal 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);
|
||||
}
|
||||
}
|
141
src/tc/lib/src/workingset.rs
Normal file
141
src/tc/lib/src/workingset.rs
Normal 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
917
src/tc/lib/taskchampion.h
Normal 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 */
|
Loading…
Add table
Add a link
Reference in a new issue