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}