taskchampion_lib/
task.rs

1use crate::traits::*;
2use crate::types::*;
3use crate::util::err_to_ruststring;
4use crate::TCKV;
5use std::convert::TryFrom;
6use std::ops::Deref;
7use std::ptr::NonNull;
8use std::str::FromStr;
9use taskchampion::{utc_timestamp, Annotation, Tag, Task, TaskMut, Uuid};
10
11#[ffizz_header::item]
12#[ffizz(order = 1000)]
13/// ***** TCTask *****
14///
15/// A task, as publicly exposed by this library.
16///
17/// A task begins in "immutable" mode.  It must be converted to "mutable" mode
18/// to make any changes, and doing so requires exclusive access to the replica
19/// until the task is freed or converted back to immutable mode.
20///
21/// An immutable task carries no reference to the replica that created it, and can be used until it
22/// is freed or converted to a TaskMut.  A mutable task carries a reference to the replica and
23/// must be freed or made immutable before the replica is freed.
24///
25/// All `tc_task_..` functions taking a task as an argument require that it not be NULL.
26///
27/// When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then
28/// `tc_task_error` will return the error message.
29///
30/// # Safety
31///
32/// A task is an owned object, and must be freed with tc_task_free (or, if part of a list,
33/// with tc_task_list_free).
34///
35/// Any function taking a `*TCTask` requires:
36///  - the pointer must not be NUL;
37///  - the pointer must be one previously returned from a tc_… function;
38///  - the memory referenced by the pointer must never be modified by C code; and
39///  - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller.
40///
41/// Once passed to tc_task_free, a `*TCTask` becomes  invalid and must not be used again.
42///
43/// TCTasks are not threadsafe.
44///
45/// ```c
46/// typedef struct TCTask TCTask;
47/// ```
48pub struct TCTask {
49    /// The wrapped Task or TaskMut
50    inner: Inner,
51
52    /// The error from the most recent operation, if any
53    error: Option<RustString<'static>>,
54}
55
56enum Inner {
57    /// A regular, immutable task
58    Immutable(Task),
59
60    /// A mutable task, together with the replica to which it holds an exclusive
61    /// reference.
62    Mutable(TaskMut<'static>, *mut TCReplica),
63
64    /// A transitional state for a TCTask as it goes from mutable to immutable and back.  A task
65    /// can only be in this state outside of [`to_mut`] and [`to_immut`] if a panic occurs during
66    /// one of those methods.
67    Invalid,
68}
69
70impl PassByPointer for TCTask {}
71
72impl TCTask {
73    /// Make an immutable TCTask into a mutable TCTask.  Does nothing if the task
74    /// is already mutable.
75    ///
76    /// # Safety
77    ///
78    /// The tcreplica pointer must not be NULL, and the replica it points to must not
79    /// be freed before TCTask.to_immut completes.
80    unsafe fn to_mut(&mut self, tcreplica: *mut TCReplica) {
81        self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) {
82            Inner::Immutable(task) => {
83                // SAFETY:
84                //  - tcreplica is not null (promised by caller)
85                //  - tcreplica outlives the pointer in this variant (promised by caller)
86                let tcreplica_ref: &mut TCReplica =
87                    unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) };
88                let rep_ref = tcreplica_ref.borrow_mut();
89                Inner::Mutable(task.into_mut(rep_ref), tcreplica)
90            }
91            Inner::Mutable(task, tcreplica) => Inner::Mutable(task, tcreplica),
92            Inner::Invalid => unreachable!(),
93        }
94    }
95
96    /// Make an mutable TCTask into a immutable TCTask.  Does nothing if the task
97    /// is already immutable.
98    #[allow(clippy::wrong_self_convention)] // to_immut_mut is not better!
99    fn to_immut(&mut self) {
100        self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) {
101            Inner::Immutable(task) => Inner::Immutable(task),
102            Inner::Mutable(task, tcreplica) => {
103                // SAFETY:
104                //  - tcreplica is not null (promised by caller of to_mut, which created this
105                //    variant)
106                //  - tcreplica is still alive (promised by caller of to_mut)
107                let tcreplica_ref: &mut TCReplica =
108                    unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) };
109                tcreplica_ref.release_borrow();
110                Inner::Immutable(task.into_immut())
111            }
112            Inner::Invalid => unreachable!(),
113        }
114    }
115}
116
117impl From<Task> for TCTask {
118    fn from(task: Task) -> TCTask {
119        TCTask {
120            inner: Inner::Immutable(task),
121            error: None,
122        }
123    }
124}
125
126/// Utility function to get a shared reference to the underlying Task.  All Task getters
127/// are error-free, so this does not handle errors.
128fn wrap<T, F>(task: *mut TCTask, f: F) -> T
129where
130    F: FnOnce(&Task) -> T,
131{
132    // SAFETY:
133    //  - task is not null (promised by caller)
134    //  - task outlives this function (promised by caller)
135    let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
136    let task: &Task = match &tctask.inner {
137        Inner::Immutable(t) => t,
138        Inner::Mutable(t, _) => t.deref(),
139        Inner::Invalid => unreachable!(),
140    };
141    tctask.error = None;
142    f(task)
143}
144
145/// Utility function to get a mutable reference to the underlying Task.  The
146/// TCTask must be mutable.  The inner function may use `?` syntax to return an
147/// error, which will be represented with the `err_value` returned to C.
148fn wrap_mut<T, F>(task: *mut TCTask, f: F, err_value: T) -> T
149where
150    F: FnOnce(&mut TaskMut) -> anyhow::Result<T>,
151{
152    // SAFETY:
153    //  - task is not null (promised by caller)
154    //  - task outlives this function (promised by caller)
155    let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
156    let task: &mut TaskMut = match tctask.inner {
157        Inner::Immutable(_) => panic!("Task is immutable"),
158        Inner::Mutable(ref mut t, _) => t,
159        Inner::Invalid => unreachable!(),
160    };
161    tctask.error = None;
162    match f(task) {
163        Ok(rv) => rv,
164        Err(e) => {
165            tctask.error = Some(err_to_ruststring(e));
166            err_value
167        }
168    }
169}
170
171impl TryFrom<RustString<'static>> for Tag {
172    type Error = anyhow::Error;
173
174    fn try_from(mut rstring: RustString) -> Result<Tag, anyhow::Error> {
175        let tagstr = rstring.as_str()?;
176        Tag::from_str(tagstr)
177    }
178}
179
180#[ffizz_header::item]
181#[ffizz(order = 1010)]
182/// ***** TCTaskList *****
183///
184/// TCTaskList represents a list of tasks.
185///
186/// The content of this struct must be treated as read-only: no fields or anything they reference
187/// should be modified directly by C code.
188///
189/// When an item is taken from this list, its pointer in `items` is set to NULL.
190///
191/// ```c
192/// typedef struct TCTaskList {
193///   // number of tasks in items
194///   size_t len;
195///   // reserved
196///   size_t _u1;
197///   // Array of pointers representing each task. These remain owned by the TCTaskList instance and
198///   // will be freed by tc_task_list_free.  This pointer is never NULL for a valid TCTaskList.
199///   // Pointers in the array may be NULL after `tc_task_list_take`.
200///   struct TCTask **items;
201/// } TCTaskList;
202/// ```
203#[repr(C)]
204pub struct TCTaskList {
205    /// number of tasks in items
206    len: libc::size_t,
207
208    /// total size of items (internal use only)
209    capacity: libc::size_t,
210
211    /// Array of pointers representing each task. These remain owned by the TCTaskList instance and
212    /// will be freed by tc_task_list_free.  This pointer is never NULL for a valid TCTaskList.
213    /// Pointers in the array may be NULL after `tc_task_list_take`.
214    items: *mut Option<NonNull<TCTask>>,
215}
216
217impl CList for TCTaskList {
218    type Element = Option<NonNull<TCTask>>;
219
220    unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self {
221        TCTaskList {
222            len,
223            capacity: cap,
224            items,
225        }
226    }
227
228    fn slice(&mut self) -> &mut [Self::Element] {
229        // SAFETY:
230        //  - because we have &mut self, we have read/write access to items[0..len]
231        //  - all items are properly initialized Element's
232        //  - return value lifetime is equal to &mmut self's, so access is exclusive
233        //  - items and len came from Vec, so total size is < isize::MAX
234        unsafe { std::slice::from_raw_parts_mut(self.items, self.len) }
235    }
236
237    fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) {
238        (self.items, self.len, self.capacity)
239    }
240}
241
242#[ffizz_header::item]
243#[ffizz(order = 1001)]
244/// Get a task's UUID.
245///
246/// ```c
247/// EXTERN_C struct TCUuid tc_task_get_uuid(struct TCTask *task);
248/// ```
249#[no_mangle]
250pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid {
251    wrap(task, |task| {
252        // SAFETY:
253        // - value is not allocated and need not be freed
254        unsafe { TCUuid::return_val(task.get_uuid()) }
255    })
256}
257
258#[ffizz_header::item]
259#[ffizz(order = 1001)]
260/// Get a task's status.
261///
262/// ```c
263/// EXTERN_C enum TCStatus tc_task_get_status(struct TCTask *task);
264/// ```
265#[no_mangle]
266pub unsafe extern "C" fn tc_task_get_status(task: *mut TCTask) -> TCStatus {
267    wrap(task, |task| task.get_status().into())
268}
269
270#[ffizz_header::item]
271#[ffizz(order = 1001)]
272/// Get the underlying key/value pairs for this task.  The returned TCKVList is
273/// a "snapshot" of the task and will not be updated if the task is subsequently
274/// modified.  It is the caller's responsibility to free the TCKVList.
275///
276/// ```c
277/// EXTERN_C struct TCKVList tc_task_get_taskmap(struct TCTask *task);
278/// ```
279#[no_mangle]
280pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList {
281    wrap(task, |task| {
282        let vec: Vec<TCKV> = task
283            .get_taskmap()
284            .iter()
285            .map(|(k, v)| {
286                let key = RustString::from(k.as_ref());
287                let value = RustString::from(v.as_ref());
288                TCKV::as_ctype((key, value))
289            })
290            .collect();
291        // SAFETY:
292        //  - caller will free this list
293        unsafe { TCKVList::return_val(vec) }
294    })
295}
296
297#[ffizz_header::item]
298#[ffizz(order = 1001)]
299/// Get a task's description.
300///
301/// ```c
302/// EXTERN_C struct TCString tc_task_get_description(struct TCTask *task);
303/// ```
304#[no_mangle]
305pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> TCString {
306    wrap(task, |task| {
307        let descr = task.get_description();
308        // SAFETY:
309        //  - caller promises to free this string
310        unsafe { TCString::return_val(descr.into()) }
311    })
312}
313
314#[ffizz_header::item]
315#[ffizz(order = 1001)]
316/// Get a task property's value, or NULL if the task has no such property, (including if the
317/// property name is not valid utf-8).
318///
319/// ```c
320/// EXTERN_C struct TCString tc_task_get_value(struct TCTask *task, struct TCString property);
321/// ```
322#[no_mangle]
323pub unsafe extern "C" fn tc_task_get_value(task: *mut TCTask, property: TCString) -> TCString {
324    // SAFETY:
325    //  - property is valid (promised by caller)
326    //  - caller will not use property after this call (convention)
327    let mut property = unsafe { TCString::val_from_arg(property) };
328    wrap(task, |task| {
329        if let Ok(property) = property.as_str() {
330            let value = task.get_value(property);
331            if let Some(value) = value {
332                // SAFETY:
333                //  - caller promises to free this string
334                return unsafe { TCString::return_val(value.into()) };
335            }
336        }
337        TCString::default() // null value
338    })
339}
340
341#[ffizz_header::item]
342#[ffizz(order = 1001)]
343/// Get the entry timestamp for a task (when it was created), or 0 if not set.
344///
345/// ```c
346/// EXTERN_C time_t tc_task_get_entry(struct TCTask *task);
347/// ```
348#[no_mangle]
349pub unsafe extern "C" fn tc_task_get_entry(task: *mut TCTask) -> libc::time_t {
350    wrap(task, |task| libc::time_t::as_ctype(task.get_entry()))
351}
352
353#[ffizz_header::item]
354#[ffizz(order = 1001)]
355/// Get the wait timestamp for a task, or 0 if not set.
356///
357/// ```c
358/// EXTERN_C time_t tc_task_get_wait(struct TCTask *task);
359/// ```
360#[no_mangle]
361pub unsafe extern "C" fn tc_task_get_wait(task: *mut TCTask) -> libc::time_t {
362    wrap(task, |task| libc::time_t::as_ctype(task.get_wait()))
363}
364
365#[ffizz_header::item]
366#[ffizz(order = 1001)]
367/// Get the modified timestamp for a task, or 0 if not set.
368///
369/// ```c
370/// EXTERN_C time_t tc_task_get_modified(struct TCTask *task);
371/// ```
372#[no_mangle]
373pub unsafe extern "C" fn tc_task_get_modified(task: *mut TCTask) -> libc::time_t {
374    wrap(task, |task| libc::time_t::as_ctype(task.get_modified()))
375}
376
377#[ffizz_header::item]
378#[ffizz(order = 1001)]
379/// Check if a task is waiting.
380///
381/// ```c
382/// EXTERN_C bool tc_task_is_waiting(struct TCTask *task);
383/// ```
384#[no_mangle]
385pub unsafe extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool {
386    wrap(task, |task| task.is_waiting())
387}
388
389#[ffizz_header::item]
390#[ffizz(order = 1001)]
391/// Check if a task is active (started and not stopped).
392///
393/// ```c
394/// EXTERN_C bool tc_task_is_active(struct TCTask *task);
395/// ```
396#[no_mangle]
397pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool {
398    wrap(task, |task| task.is_active())
399}
400
401#[ffizz_header::item]
402#[ffizz(order = 1001)]
403/// Check if a task is blocked (depends on at least one other task).
404///
405/// ```c
406/// EXTERN_C bool tc_task_is_blocked(struct TCTask *task);
407/// ```
408#[no_mangle]
409pub unsafe extern "C" fn tc_task_is_blocked(task: *mut TCTask) -> bool {
410    wrap(task, |task| task.is_blocked())
411}
412
413#[ffizz_header::item]
414#[ffizz(order = 1001)]
415/// Check if a task is blocking (at least one other task depends on it).
416///
417/// ```c
418/// EXTERN_C bool tc_task_is_blocking(struct TCTask *task);
419/// ```
420#[no_mangle]
421pub unsafe extern "C" fn tc_task_is_blocking(task: *mut TCTask) -> bool {
422    wrap(task, |task| task.is_blocking())
423}
424
425#[ffizz_header::item]
426#[ffizz(order = 1001)]
427/// Check if a task has the given tag.  If the tag is invalid, this function will return false, as
428/// that (invalid) tag is not present. No error will be reported via `tc_task_error`.
429///
430/// ```c
431/// EXTERN_C bool tc_task_has_tag(struct TCTask *task, struct TCString tag);
432/// ```
433#[no_mangle]
434pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: TCString) -> bool {
435    // SAFETY:
436    //  - tag is valid (promised by caller)
437    //  - caller will not use tag after this call (convention)
438    let tcstring = unsafe { TCString::val_from_arg(tag) };
439    wrap(task, |task| {
440        if let Ok(tag) = Tag::try_from(tcstring) {
441            task.has_tag(&tag)
442        } else {
443            false
444        }
445    })
446}
447
448#[ffizz_header::item]
449#[ffizz(order = 1001)]
450/// Get the tags for the task.
451///
452/// The caller must free the returned TCStringList instance.  The TCStringList instance does not
453/// reference the task and the two may be freed in any order.
454///
455/// ```c
456/// EXTERN_C struct TCStringList tc_task_get_tags(struct TCTask *task);
457/// ```
458#[no_mangle]
459pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList {
460    wrap(task, |task| {
461        let vec: Vec<TCString> = task
462            .get_tags()
463            .map(|t| {
464                // SAFETY:
465                //  - this TCString will be freed via tc_string_list_free.
466                unsafe { TCString::return_val(t.as_ref().into()) }
467            })
468            .collect();
469        // SAFETY:
470        //  - caller will free the list
471        unsafe { TCStringList::return_val(vec) }
472    })
473}
474
475#[ffizz_header::item]
476#[ffizz(order = 1001)]
477/// Get the annotations for the task.
478///
479/// The caller must free the returned TCAnnotationList instance.  The TCStringList instance does not
480/// reference the task and the two may be freed in any order.
481///
482/// ```c
483/// EXTERN_C struct TCAnnotationList tc_task_get_annotations(struct TCTask *task);
484/// ```
485#[no_mangle]
486pub unsafe extern "C" fn tc_task_get_annotations(task: *mut TCTask) -> TCAnnotationList {
487    wrap(task, |task| {
488        let vec: Vec<TCAnnotation> = task
489            .get_annotations()
490            .map(|a| {
491                let description = RustString::from(a.description);
492                TCAnnotation::as_ctype((a.entry, description))
493            })
494            .collect();
495        // SAFETY:
496        //  - caller will free the list
497        unsafe { TCAnnotationList::return_val(vec) }
498    })
499}
500
501#[ffizz_header::item]
502#[ffizz(order = 1001)]
503/// Get the named UDA from the task.
504///
505/// Returns a TCString with NULL ptr field if the UDA does not exist.
506///
507/// ```c
508/// EXTERN_C struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key);
509/// ```
510#[no_mangle]
511pub unsafe extern "C" fn tc_task_get_uda(
512    task: *mut TCTask,
513    ns: TCString,
514    key: TCString,
515) -> TCString {
516    wrap(task, |task| {
517        // SAFETY:
518        //  - ns is valid (promised by caller)
519        //  - caller will not use ns after this call (convention)
520        if let Ok(ns) = unsafe { TCString::val_from_arg(ns) }.as_str() {
521            // SAFETY: same
522            if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() {
523                if let Some(value) = task.get_uda(ns, key) {
524                    // SAFETY:
525                    // - caller will free this string (caller promises)
526                    return unsafe { TCString::return_val(value.into()) };
527                }
528            }
529        }
530        TCString::default()
531    })
532}
533
534#[ffizz_header::item]
535#[ffizz(order = 1001)]
536/// Get the named legacy UDA from the task.
537///
538/// Returns NULL if the UDA does not exist.
539///
540/// ```c
541/// EXTERN_C struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key);
542/// ```
543#[no_mangle]
544pub unsafe extern "C" fn tc_task_get_legacy_uda(task: *mut TCTask, key: TCString) -> TCString {
545    wrap(task, |task| {
546        // SAFETY:
547        //  - key is valid (promised by caller)
548        //  - caller will not use key after this call (convention)
549        if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() {
550            if let Some(value) = task.get_legacy_uda(key) {
551                // SAFETY:
552                // - caller will free this string (caller promises)
553                return unsafe { TCString::return_val(value.into()) };
554            }
555        }
556        TCString::default()
557    })
558}
559
560#[ffizz_header::item]
561#[ffizz(order = 1001)]
562/// Get all UDAs for this task.
563///
564/// Legacy UDAs are represented with an empty string in the ns field.
565///
566/// ```c
567/// EXTERN_C struct TCUdaList tc_task_get_udas(struct TCTask *task);
568/// ```
569#[no_mangle]
570pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUdaList {
571    wrap(task, |task| {
572        let vec: Vec<TCUda> = task
573            .get_udas()
574            .map(|((ns, key), value)| {
575                // SAFETY:
576                //  - will be freed by tc_uda_list_free
577                unsafe {
578                    TCUda::return_val(Uda {
579                        ns: Some(ns.into()),
580                        key: key.into(),
581                        value: value.into(),
582                    })
583                }
584            })
585            .collect();
586        // SAFETY:
587        //  - caller will free this list
588        unsafe { TCUdaList::return_val(vec) }
589    })
590}
591
592#[ffizz_header::item]
593#[ffizz(order = 1001)]
594/// Get all UDAs for this task.
595///
596/// All TCUdas in this list have a NULL ns field.  The entire UDA key is
597/// included in the key field.  The caller must free the returned list.
598///
599/// ```c
600/// EXTERN_C struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task);
601/// ```
602#[no_mangle]
603pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUdaList {
604    wrap(task, |task| {
605        let vec: Vec<TCUda> = task
606            .get_legacy_udas()
607            .map(|(key, value)| {
608                // SAFETY:
609                //  - will be freed by tc_uda_list_free
610                unsafe {
611                    TCUda::return_val(Uda {
612                        ns: None,
613                        key: key.into(),
614                        value: value.into(),
615                    })
616                }
617            })
618            .collect();
619        // SAFETY:
620        //  - caller will free this list
621        unsafe { TCUdaList::return_val(vec) }
622    })
623}
624
625#[ffizz_header::item]
626#[ffizz(order = 1001)]
627/// Get all dependencies for a task.
628///
629/// ```c
630/// EXTERN_C struct TCUuidList tc_task_get_dependencies(struct TCTask *task);
631/// ```
632#[no_mangle]
633pub unsafe extern "C" fn tc_task_get_dependencies(task: *mut TCTask) -> TCUuidList {
634    wrap(task, |task| {
635        let vec: Vec<TCUuid> = task
636            .get_dependencies()
637            .map(|u| {
638                // SAFETY:
639                //  - value is not allocated
640                unsafe { TCUuid::return_val(u) }
641            })
642            .collect();
643        // SAFETY:
644        //  - caller will free this list
645        unsafe { TCUuidList::return_val(vec) }
646    })
647}
648
649#[ffizz_header::item]
650#[ffizz(order = 1002)]
651/// Convert an immutable task into a mutable task.
652///
653/// The task must not be NULL. It is modified in-place, and becomes mutable.
654///
655/// The replica must not be NULL. After this function returns, the replica _cannot be used at all_
656/// until this task is made immutable again.  This implies that it is not allowed for more than one
657/// task associated with a replica to be mutable at any time.
658///
659/// Typically mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`:
660///
661/// ```text
662///     tc_task_to_mut(task, rep);
663///     success = tc_task_done(task);
664///     tc_task_to_immut(task, rep);
665///     if (!success) { ... }
666/// ```
667///
668/// ```c
669/// EXTERN_C void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica);
670/// ```
671#[no_mangle]
672pub unsafe extern "C" fn tc_task_to_mut(task: *mut TCTask, tcreplica: *mut TCReplica) {
673    // SAFETY:
674    //  - task is not null (promised by caller)
675    //  - task outlives 'a (promised by caller)
676    let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
677    // SAFETY:
678    //  - tcreplica is not NULL (promised by caller)
679    //  - tcreplica lives until later call to to_immut via tc_task_to_immut (promised by caller,
680    //    who cannot call tc_replica_free during this time)
681    unsafe { tctask.to_mut(tcreplica) };
682}
683
684#[ffizz_header::item]
685#[ffizz(order = 1003)]
686/// Set a mutable task's status.
687///
688/// ```c
689/// EXTERN_C TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status);
690/// ```
691#[no_mangle]
692pub unsafe extern "C" fn tc_task_set_status(task: *mut TCTask, status: TCStatus) -> TCResult {
693    wrap_mut(
694        task,
695        |task| {
696            task.set_status(status.into())?;
697            Ok(TCResult::Ok)
698        },
699        TCResult::Error,
700    )
701}
702
703#[ffizz_header::item]
704#[ffizz(order = 1003)]
705/// Set a mutable task's property value by name.  If value.ptr is NULL, the property is removed.
706///
707/// ```c
708/// EXTERN_C TCResult tc_task_set_value(struct TCTask *task, struct TCString property, struct TCString value);
709/// ```
710#[no_mangle]
711pub unsafe extern "C" fn tc_task_set_value(
712    task: *mut TCTask,
713    property: TCString,
714    value: TCString,
715) -> TCResult {
716    // SAFETY:
717    //  - property is valid (promised by caller)
718    //  - caller will not use property after this call (convention)
719    let mut property = unsafe { TCString::val_from_arg(property) };
720    let value = if value.is_null() {
721        None
722    } else {
723        // SAFETY:
724        //  - value is valid (promised by caller, after NULL check)
725        //  - caller will not use value after this call (convention)
726        Some(unsafe { TCString::val_from_arg(value) })
727    };
728    wrap_mut(
729        task,
730        |task| {
731            let value_str = if let Some(mut v) = value {
732                Some(v.as_str()?.to_string())
733            } else {
734                None
735            };
736            task.set_value(property.as_str()?.to_string(), value_str)?;
737            Ok(TCResult::Ok)
738        },
739        TCResult::Error,
740    )
741}
742
743#[ffizz_header::item]
744#[ffizz(order = 1003)]
745/// Set a mutable task's description.
746///
747/// ```c
748/// EXTERN_C TCResult tc_task_set_description(struct TCTask *task, struct TCString description);
749/// ```
750#[no_mangle]
751pub unsafe extern "C" fn tc_task_set_description(
752    task: *mut TCTask,
753    description: TCString,
754) -> TCResult {
755    // SAFETY:
756    //  - description is valid (promised by caller)
757    //  - caller will not use description after this call (convention)
758    let mut description = unsafe { TCString::val_from_arg(description) };
759    wrap_mut(
760        task,
761        |task| {
762            task.set_description(description.as_str()?.to_string())?;
763            Ok(TCResult::Ok)
764        },
765        TCResult::Error,
766    )
767}
768
769#[ffizz_header::item]
770#[ffizz(order = 1003)]
771/// Set a mutable task's entry (creation time).  Pass entry=0 to unset
772/// the entry field.
773///
774/// ```c
775/// EXTERN_C TCResult tc_task_set_entry(struct TCTask *task, time_t entry);
776/// ```
777#[no_mangle]
778pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult {
779    wrap_mut(
780        task,
781        |task| {
782            // SAFETY: any time_t value is a valid timestamp
783            task.set_entry(unsafe { entry.from_ctype() })?;
784            Ok(TCResult::Ok)
785        },
786        TCResult::Error,
787    )
788}
789
790#[ffizz_header::item]
791#[ffizz(order = 1003)]
792/// Set a mutable task's wait timestamp.  Pass wait=0 to unset the wait field.
793///
794/// ```c
795/// EXTERN_C TCResult tc_task_set_wait(struct TCTask *task, time_t wait);
796/// ```
797#[no_mangle]
798pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult {
799    wrap_mut(
800        task,
801        |task| {
802            // SAFETY: any time_t value is a valid timestamp
803            task.set_wait(unsafe { wait.from_ctype() })?;
804            Ok(TCResult::Ok)
805        },
806        TCResult::Error,
807    )
808}
809
810#[ffizz_header::item]
811#[ffizz(order = 1003)]
812/// Set a mutable task's modified timestamp.  The value cannot be zero.
813///
814/// ```c
815/// EXTERN_C TCResult tc_task_set_modified(struct TCTask *task, time_t modified);
816/// ```
817#[no_mangle]
818pub unsafe extern "C" fn tc_task_set_modified(
819    task: *mut TCTask,
820    modified: libc::time_t,
821) -> TCResult {
822    wrap_mut(
823        task,
824        |task| {
825            task.set_modified(
826                // SAFETY: any time_t value is a valid timestamp
827                unsafe { modified.from_ctype() }
828                    .ok_or_else(|| anyhow::anyhow!("modified cannot be zero"))?,
829            )?;
830            Ok(TCResult::Ok)
831        },
832        TCResult::Error,
833    )
834}
835
836#[ffizz_header::item]
837#[ffizz(order = 1003)]
838/// Start a task.
839///
840/// ```c
841/// EXTERN_C TCResult tc_task_start(struct TCTask *task);
842/// ```
843#[no_mangle]
844pub unsafe extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult {
845    wrap_mut(
846        task,
847        |task| {
848            task.start()?;
849            Ok(TCResult::Ok)
850        },
851        TCResult::Error,
852    )
853}
854
855#[ffizz_header::item]
856#[ffizz(order = 1003)]
857/// Stop a task.
858///
859/// ```c
860/// EXTERN_C TCResult tc_task_stop(struct TCTask *task);
861/// ```
862#[no_mangle]
863pub unsafe extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult {
864    wrap_mut(
865        task,
866        |task| {
867            task.stop()?;
868            Ok(TCResult::Ok)
869        },
870        TCResult::Error,
871    )
872}
873
874#[ffizz_header::item]
875#[ffizz(order = 1003)]
876/// Mark a task as done.
877///
878/// ```c
879/// EXTERN_C TCResult tc_task_done(struct TCTask *task);
880/// ```
881#[no_mangle]
882pub unsafe extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult {
883    wrap_mut(
884        task,
885        |task| {
886            task.done()?;
887            Ok(TCResult::Ok)
888        },
889        TCResult::Error,
890    )
891}
892
893#[ffizz_header::item]
894#[ffizz(order = 1003)]
895/// Mark a task as deleted.
896///
897/// ```c
898/// EXTERN_C TCResult tc_task_delete(struct TCTask *task);
899/// ```
900#[no_mangle]
901pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult {
902    wrap_mut(
903        task,
904        |task| {
905            task.delete()?;
906            Ok(TCResult::Ok)
907        },
908        TCResult::Error,
909    )
910}
911
912#[ffizz_header::item]
913#[ffizz(order = 1003)]
914/// Add a tag to a mutable task.
915///
916/// ```c
917/// EXTERN_C TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag);
918/// ```
919#[no_mangle]
920pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: TCString) -> TCResult {
921    // SAFETY:
922    //  - tag is valid (promised by caller)
923    //  - caller will not use tag after this call (convention)
924    let tcstring = unsafe { TCString::val_from_arg(tag) };
925    wrap_mut(
926        task,
927        |task| {
928            let tag = Tag::try_from(tcstring)?;
929            task.add_tag(&tag)?;
930            Ok(TCResult::Ok)
931        },
932        TCResult::Error,
933    )
934}
935
936#[ffizz_header::item]
937#[ffizz(order = 1003)]
938/// Remove a tag from a mutable task.
939///
940/// ```c
941/// EXTERN_C TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag);
942/// ```
943#[no_mangle]
944pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: TCString) -> TCResult {
945    // SAFETY:
946    //  - tag is valid (promised by caller)
947    //  - caller will not use tag after this call (convention)
948    let tcstring = unsafe { TCString::val_from_arg(tag) };
949    wrap_mut(
950        task,
951        |task| {
952            let tag = Tag::try_from(tcstring)?;
953            task.remove_tag(&tag)?;
954            Ok(TCResult::Ok)
955        },
956        TCResult::Error,
957    )
958}
959
960#[ffizz_header::item]
961#[ffizz(order = 1003)]
962/// Add an annotation to a mutable task.  This call takes ownership of the
963/// passed annotation, which must not be used after the call returns.
964///
965/// ```c
966/// EXTERN_C TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation);
967/// ```
968#[no_mangle]
969pub unsafe extern "C" fn tc_task_add_annotation(
970    task: *mut TCTask,
971    annotation: *mut TCAnnotation,
972) -> TCResult {
973    // SAFETY:
974    //  - annotation is not NULL (promised by caller)
975    //  - annotation is return from a tc_string_.. so is valid
976    //  - caller will not use annotation after this call
977    let (entry, description) =
978        unsafe { TCAnnotation::take_val_from_arg(annotation, TCAnnotation::default()) };
979    wrap_mut(
980        task,
981        |task| {
982            let description = description.into_string()?;
983            task.add_annotation(Annotation { entry, description })?;
984            Ok(TCResult::Ok)
985        },
986        TCResult::Error,
987    )
988}
989
990#[ffizz_header::item]
991#[ffizz(order = 1003)]
992/// Remove an annotation from a mutable task.
993///
994/// ```c
995/// EXTERN_C TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry);
996/// ```
997#[no_mangle]
998pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64) -> TCResult {
999    wrap_mut(
1000        task,
1001        |task| {
1002            task.remove_annotation(utc_timestamp(entry))?;
1003            Ok(TCResult::Ok)
1004        },
1005        TCResult::Error,
1006    )
1007}
1008
1009#[ffizz_header::item]
1010#[ffizz(order = 1003)]
1011/// Set a UDA on a mutable task.
1012///
1013/// ```c
1014/// EXTERN_C TCResult tc_task_set_uda(struct TCTask *task,
1015///                          struct TCString ns,
1016///                          struct TCString key,
1017///                          struct TCString value);
1018/// ```
1019#[no_mangle]
1020pub unsafe extern "C" fn tc_task_set_uda(
1021    task: *mut TCTask,
1022    ns: TCString,
1023    key: TCString,
1024    value: TCString,
1025) -> TCResult {
1026    // safety:
1027    //  - ns is valid (promised by caller)
1028    //  - caller will not use ns after this call (convention)
1029    let mut ns = unsafe { TCString::val_from_arg(ns) };
1030    // SAFETY: same
1031    let mut key = unsafe { TCString::val_from_arg(key) };
1032    // SAFETY: same
1033    let mut value = unsafe { TCString::val_from_arg(value) };
1034    wrap_mut(
1035        task,
1036        |task| {
1037            task.set_uda(ns.as_str()?, key.as_str()?, value.as_str()?.to_string())?;
1038            Ok(TCResult::Ok)
1039        },
1040        TCResult::Error,
1041    )
1042}
1043
1044#[ffizz_header::item]
1045#[ffizz(order = 1003)]
1046/// Remove a UDA fraom a mutable task.
1047///
1048/// ```c
1049/// EXTERN_C TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key);
1050/// ```
1051#[no_mangle]
1052pub unsafe extern "C" fn tc_task_remove_uda(
1053    task: *mut TCTask,
1054    ns: TCString,
1055    key: TCString,
1056) -> TCResult {
1057    // safety:
1058    //  - ns is valid (promised by caller)
1059    //  - caller will not use ns after this call (convention)
1060    let mut ns = unsafe { TCString::val_from_arg(ns) };
1061    // SAFETY: same
1062    let mut key = unsafe { TCString::val_from_arg(key) };
1063    wrap_mut(
1064        task,
1065        |task| {
1066            task.remove_uda(ns.as_str()?, key.as_str()?)?;
1067            Ok(TCResult::Ok)
1068        },
1069        TCResult::Error,
1070    )
1071}
1072
1073#[ffizz_header::item]
1074#[ffizz(order = 1003)]
1075/// Set a legacy UDA on a mutable task.
1076///
1077/// ```c
1078/// EXTERN_C TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value);
1079/// ```
1080#[no_mangle]
1081pub unsafe extern "C" fn tc_task_set_legacy_uda(
1082    task: *mut TCTask,
1083    key: TCString,
1084    value: TCString,
1085) -> TCResult {
1086    // safety:
1087    //  - key is valid (promised by caller)
1088    //  - caller will not use key after this call (convention)
1089    let mut key = unsafe { TCString::val_from_arg(key) };
1090    // SAFETY: same
1091    let mut value = unsafe { TCString::val_from_arg(value) };
1092    wrap_mut(
1093        task,
1094        |task| {
1095            task.set_legacy_uda(key.as_str()?.to_string(), value.as_str()?.to_string())?;
1096            Ok(TCResult::Ok)
1097        },
1098        TCResult::Error,
1099    )
1100}
1101
1102#[ffizz_header::item]
1103#[ffizz(order = 1003)]
1104/// Remove a UDA fraom a mutable task.
1105///
1106/// ```c
1107/// EXTERN_C TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key);
1108/// ```
1109#[no_mangle]
1110pub unsafe extern "C" fn tc_task_remove_legacy_uda(task: *mut TCTask, key: TCString) -> TCResult {
1111    // safety:
1112    //  - key is valid (promised by caller)
1113    //  - caller will not use key after this call (convention)
1114    let mut key = unsafe { TCString::val_from_arg(key) };
1115    wrap_mut(
1116        task,
1117        |task| {
1118            task.remove_legacy_uda(key.as_str()?.to_string())?;
1119            Ok(TCResult::Ok)
1120        },
1121        TCResult::Error,
1122    )
1123}
1124
1125#[ffizz_header::item]
1126#[ffizz(order = 1003)]
1127/// Add a dependency.
1128///
1129/// ```c
1130/// EXTERN_C TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep);
1131/// ```
1132#[no_mangle]
1133pub unsafe extern "C" fn tc_task_add_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult {
1134    // SAFETY:
1135    //  - tcuuid is a valid TCUuid (all byte patterns are valid)
1136    let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) };
1137    wrap_mut(
1138        task,
1139        |task| {
1140            task.add_dependency(dep)?;
1141            Ok(TCResult::Ok)
1142        },
1143        TCResult::Error,
1144    )
1145}
1146
1147#[ffizz_header::item]
1148#[ffizz(order = 1003)]
1149/// Remove a dependency.
1150///
1151/// ```c
1152/// EXTERN_C TCResult tc_task_remove_dependency(struct TCTask *task, struct TCUuid dep);
1153/// ```
1154#[no_mangle]
1155pub unsafe extern "C" fn tc_task_remove_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult {
1156    // SAFETY:
1157    //  - tcuuid is a valid TCUuid (all byte patterns are valid)
1158    let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) };
1159    wrap_mut(
1160        task,
1161        |task| {
1162            task.remove_dependency(dep)?;
1163            Ok(TCResult::Ok)
1164        },
1165        TCResult::Error,
1166    )
1167}
1168
1169#[ffizz_header::item]
1170#[ffizz(order = 1004)]
1171/// Convert a mutable task into an immutable task.
1172///
1173/// The task must not be NULL.  It is modified in-place, and becomes immutable.
1174///
1175/// The replica passed to `tc_task_to_mut` may be used freely after this call.
1176///
1177/// ```c
1178/// EXTERN_C void tc_task_to_immut(struct TCTask *task);
1179/// ```
1180#[no_mangle]
1181pub unsafe extern "C" fn tc_task_to_immut(task: *mut TCTask) {
1182    // SAFETY:
1183    //  - task is not null (promised by caller)
1184    //  - task outlives 'a (promised by caller)
1185    let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
1186    tctask.to_immut();
1187}
1188
1189#[ffizz_header::item]
1190#[ffizz(order = 1005)]
1191/// Get the latest error for a task, or a string NULL ptr field if the last operation succeeded.
1192/// Subsequent calls to this function will return NULL.  The task pointer must not be NULL.  The
1193/// caller must free the returned string.
1194///
1195/// ```c
1196/// EXTERN_C struct TCString tc_task_error(struct TCTask *task);
1197/// ```
1198#[no_mangle]
1199pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> TCString {
1200    // SAFETY:
1201    //  - task is not null (promised by caller)
1202    //  - task outlives 'a (promised by caller)
1203    let task: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
1204    if let Some(rstring) = task.error.take() {
1205        // SAFETY:
1206        //  - caller promises to free this value
1207        unsafe { TCString::return_val(rstring) }
1208    } else {
1209        TCString::default()
1210    }
1211}
1212
1213#[ffizz_header::item]
1214#[ffizz(order = 1006)]
1215/// Free a task.  The given task must not be NULL.  The task must not be used after this function
1216/// returns, and must not be freed more than once.
1217///
1218/// If the task is currently mutable, it will first be made immutable.
1219///
1220/// ```c
1221/// EXTERN_C void tc_task_free(struct TCTask *task);
1222/// ```
1223#[no_mangle]
1224pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) {
1225    // SAFETY:
1226    //  - task is not NULL (promised by caller)
1227    //  - caller will not use the TCTask after this (promised by caller)
1228    let mut tctask = unsafe { TCTask::take_from_ptr_arg(task) };
1229
1230    // convert to immut if it was mutable
1231    tctask.to_immut();
1232
1233    drop(tctask);
1234}
1235
1236#[ffizz_header::item]
1237#[ffizz(order = 1011)]
1238/// Take an item from a TCTaskList.  After this call, the indexed item is no longer associated
1239/// with the list and becomes the caller's responsibility, just as if it had been returned from
1240/// `tc_replica_get_task`.
1241///
1242/// The corresponding element in the `items` array will be set to NULL.  If that field is already
1243/// NULL (that is, if the item has already been taken), this function will return NULL.  If the
1244/// index is out of bounds, this function will also return NULL.
1245///
1246/// The passed TCTaskList remains owned by the caller.
1247///
1248/// ```c
1249/// EXTERN_C struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index);
1250/// ```
1251#[no_mangle]
1252pub unsafe extern "C" fn tc_task_list_take(tasks: *mut TCTaskList, index: usize) -> *mut TCTask {
1253    // SAFETY:
1254    //  - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to
1255    //    modify the list directly, and tc_task_list_take leaves the list valid)
1256    let p = unsafe { take_optional_pointer_list_item(tasks, index) };
1257    if let Some(p) = p {
1258        p.as_ptr()
1259    } else {
1260        std::ptr::null_mut()
1261    }
1262}
1263
1264#[ffizz_header::item]
1265#[ffizz(order = 1011)]
1266/// Free a TCTaskList instance.  The instance, and all TCTaskList it contains, must not be used after
1267/// this call.
1268///
1269/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList.
1270///
1271/// ```c
1272/// EXTERN_C void tc_task_list_free(struct TCTaskList *tasks);
1273/// ```
1274#[no_mangle]
1275pub unsafe extern "C" fn tc_task_list_free(tasks: *mut TCTaskList) {
1276    // SAFETY:
1277    //  - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to
1278    //    modify the list directly, and tc_task_list_take leaves the list valid)
1279    //  - caller promises not to use the value after return
1280    unsafe { drop_optional_pointer_list(tasks) };
1281}
1282
1283#[cfg(test)]
1284mod test {
1285    use super::*;
1286
1287    #[test]
1288    fn empty_list_has_non_null_pointer() {
1289        let tasks = unsafe { TCTaskList::return_val(Vec::new()) };
1290        assert!(!tasks.items.is_null());
1291        assert_eq!(tasks.len, 0);
1292        assert_eq!(tasks.capacity, 0);
1293    }
1294
1295    #[test]
1296    fn free_sets_null_pointer() {
1297        let mut tasks = unsafe { TCTaskList::return_val(Vec::new()) };
1298        // SAFETY: testing expected behavior
1299        unsafe { tc_task_list_free(&mut tasks) };
1300        assert!(tasks.items.is_null());
1301        assert_eq!(tasks.len, 0);
1302        assert_eq!(tasks.capacity, 0);
1303    }
1304}