labfy-investigation/docs/tickets/open/TICKET-038.md
2026-07-17 16:47:46 +02:00

20 KiB
Raw Blame History

TICKET-038 — Exécution dun outil externe dans une BackgroundTask

Statut

À faire

Priorité

Haute

Objectif

Créer une couche dintégration permettant dexécuter un outil externe dans une BackgroundTask, puis de suivre cette exécution avec le TaskManager et le panneau dactivité.

Ce ticket doit relier proprement les modules déjà validés :

ToolRegistry
ToolProcess
BackgroundTask
TaskManager
TaskPanel

Le résultat attendu est une abstraction réutilisable capable de :

  • sélectionner un outil enregistré ;
  • vérifier sa disponibilité ;
  • préparer une exécution structurée ;
  • lancer cette exécution dans un thread secondaire ;
  • transmettre lannulation ;
  • publier un statut compréhensible ;
  • conserver le résultat brut de loutil ;
  • signaler proprement les erreurs ;
  • rester indépendante de GTK.

Contexte

Les tickets précédents ont fourni :

Ticket #034

BackgroundTask

pour exécuter un travail asynchrone, publier une progression et gérer lannulation.

Ticket #035

TaskManager
TaskPanel

pour conserver, afficher et annuler les tâches.

Ticket #036

ToolRegistry
ToolInfo

pour détecter les exécutables disponibles sur la machine.

Ticket #037

ToolProcess
ToolProcessResult

pour lancer un exécutable sans shell, capturer ses sorties et gérer son annulation.

Il manque maintenant une couche métier qui assemble ces briques sans obliger Application, les futurs adaptateurs OSINT ou les widgets GTK à connaître tous les détails de leur fonctionnement interne.


Nom proposé du module

ToolTask

Fichiers attendus :

include/core/tool_task.h
src/core/tool_task.c
tests/test_tool_task.c

Le nom peut être ajusté avant implémentation sil existe une meilleure proposition cohérente avec le projet.


Responsabilité du module

ToolTask doit représenter une exécution externe préparée pour être lancée dans une BackgroundTask.

Le module devra :

  1. recevoir un ToolRegistry ;
  2. rechercher un outil par identifiant ;
  3. vérifier que loutil est disponible ;
  4. copier les arguments reçus ;
  5. copier le dossier de travail facultatif ;
  6. créer une BackgroundTask ;
  7. utiliser ToolProcess dans son worker ;
  8. transmettre le GCancellable fourni par BackgroundTask ;
  9. publier un statut avant le lancement ;
  10. publier un statut après la fin ;
  11. transférer un résultat structuré au propriétaire de la tâche ;
  12. convertir les erreurs de préparation en erreurs ToolTask ;
  13. laisser les erreurs dexécution de loutil disponibles dans le résultat ou dans la tâche selon leur nature.

Hors périmètre

Ce ticket ne doit pas encore :

  • ajouter un outil OSINT réel à linterface ;
  • créer un adaptateur DNS ;
  • analyser la sortie dun outil ;
  • enregistrer une preuve ;
  • écrire les sorties dans SQLite ;
  • sauvegarder les résultats bruts sur disque ;
  • afficher directement les sorties dans GTK ;
  • gérer une limite de temps ;
  • gérer stdin ;
  • relancer automatiquement une commande ;
  • gérer un pipeline entre plusieurs outils ;
  • déterminer automatiquement les arguments dun outil ;
  • modifier le ToolRegistry.

Le premier adaptateur OSINT concret viendra après ce ticket.


Contraintes générales

  • C17.
  • GLib et GIO.
  • Aucune dépendance GTK.
  • Compilation stricte :
-Wall -Wextra -Wpedantic -Werror
  • Aucun shell.
  • Aucune concaténation de commande.
  • Les arguments doivent rester séparés.
  • Les structures publiques doivent être opaques.
  • Les fonctions doivent être préfixées par :
tool_task_
tool_task_result_
  • Aucun état global mutable.
  • Les règles de propriété doivent être documentées.
  • Le module doit être testable sans dépendre dun outil réellement installé.

Modèle public

ToolTaskResult

Créer une structure opaque :

typedef struct ToolTaskResult ToolTaskResult;

Cette structure doit conserver au minimum :

tool_identifier
executable_path
arguments
working_directory
process_result

Le champ process_result doit contenir le ToolProcessResult produit par ToolProcess.

Le résultat doit être indépendant du ToolRegistry après sa création.

ToolTask

Deux approches sont possibles.

Approche recommandée

Ne pas créer une structure persistante ToolTask.

Créer directement une BackgroundTask configurée à partir dune requête.

Exemple :

BackgroundTask *tool_task_create(
    const ToolRegistry *tool_registry,
    const char *tool_identifier,
    const char *task_title,
    const char *const arguments[],
    const char *working_directory,
    GError **error
);

Le BackgroundTask retourné nest pas encore démarré.

Une seconde fonction démarre la tâche :

gboolean tool_task_start(
    BackgroundTask *background_task,
    BackgroundTaskCompletedCallback completed_callback,
    gpointer completed_user_data,
    GDestroyNotify completed_user_data_destroy,
    GError **error
);

Approche alternative

Créer une structure opaque ToolTask possédant sa BackgroundTask.

Cette approche nest acceptable que si elle simplifie clairement la propriété et les tests.


Domaine derreur

Créer :

#define TOOL_TASK_ERROR \
    tool_task_error_quark()

Énumération minimale :

typedef enum
{
    TOOL_TASK_ERROR_INVALID_ARGUMENT,
    TOOL_TASK_ERROR_TOOL_NOT_FOUND,
    TOOL_TASK_ERROR_TOOL_NOT_CHECKED,
    TOOL_TASK_ERROR_TOOL_MISSING,
    TOOL_TASK_ERROR_TASK_CREATION,
    TOOL_TASK_ERROR_TASK_START,
    TOOL_TASK_ERROR_PROCESS
} ToolTaskError;

Fonction :

GQuark tool_task_error_quark(void);

Règles de préparation

Outil inconnu

Si lidentifiant nexiste pas dans le registre :

retour : NULL
erreur : TOOL_TASK_ERROR_TOOL_NOT_FOUND

Outil non vérifié

Si létat vaut :

TOOL_AVAILABILITY_UNKNOWN

la création doit échouer avec :

TOOL_TASK_ERROR_TOOL_NOT_CHECKED

Le module ne doit pas appeler automatiquement tool_registry_refresh().

Cette décision garde les responsabilités séparées.

Outil absent

Si létat vaut :

TOOL_AVAILABILITY_MISSING

la création doit échouer avec :

TOOL_TASK_ERROR_TOOL_MISSING

Outil disponible sans chemin

Un outil marqué disponible mais sans chemin résolu représente un état incohérent.

La création doit échouer.

Arguments

Le tableau darguments :

const char *const arguments[]

peut être NULL.

Lorsquil est fourni :

  • chaque chaîne doit être non NULL jusquau terminateur ;
  • les chaînes doivent être dupliquées ;
  • leur ordre doit être conservé ;
  • aucune interprétation ne doit être faite ;
  • le tableau interne doit se terminer par NULL.

Titre

Le titre de la tâche doit être non vide.

Exemple :

Interrogation DNS de example.org

Le titre ne doit pas être fabriqué automatiquement par le module.


Cycle de vie recommandé

Création

BackgroundTask *tool_task_create(
    const ToolRegistry *tool_registry,
    const char *tool_identifier,
    const char *task_title,
    const char *const arguments[],
    const char *working_directory,
    GError **error
);

Comportement :

  1. valider les arguments ;
  2. rechercher ToolInfo ;
  3. vérifier létat ;
  4. récupérer le chemin résolu ;
  5. dupliquer :
    • identifiant ;
    • chemin ;
    • arguments ;
    • dossier de travail ;
  6. créer les données du worker ;
  7. créer une BackgroundTask ;
  8. attacher les données au futur lancement.

Démarrage

gboolean tool_task_start(
    BackgroundTask *background_task,
    BackgroundTaskCompletedCallback completed_callback,
    gpointer completed_user_data,
    GDestroyNotify completed_user_data_destroy,
    GError **error
);

Cette fonction doit :

  1. vérifier que la tâche provient bien de ToolTask ;
  2. appeler background_task_start() ;
  3. transmettre :
    • le worker ;
    • les données du worker ;
    • leur destructeur ;
    • le destructeur du résultat ;
    • le callback de fin ;
    • ses données utilisateur.

La fonction ne doit pas ajouter elle-même la tâche dans un TaskManager.

Cette responsabilité reste à lappelant.


Worker

Le worker doit respecter la signature actuelle de BackgroundTask.

Comportement attendu :

  1. publier :
Préparation de lexécution
  1. publier :
Exécution de <nom de loutil>
  1. appeler tool_process_run() avec :
    • le chemin détecté ;
    • les arguments copiés ;
    • le dossier de travail ;
    • le GCancellable reçu ;
  2. si lappel échoue :
    • propager une erreur exploitable ;
    • ne produire aucun résultat ;
  3. si lappel réussit :
    • créer ToolTaskResult ;
    • y transférer ToolProcessResult ;
    • publier un statut final ;
  4. placer le résultat dans :
*result

Progression

Une commande externe ne fournit pas toujours une progression mesurable.

Pour ce ticket, utiliser une progression qualitative :

0.0  Préparation
0.1  Lancement
0.9  Exécution terminée, traitement du résultat
1.0  Terminé

Il ne faut pas simuler une progression régulière avec un minuteur.


Code de sortie non nul

Un programme lancé correctement mais terminant avec un code non nul ne doit pas faire échouer la BackgroundTask.

Dans ce cas :

BackgroundTask : COMPLETED
ToolTaskResult : disponible
ToolProcessResult : is_success == FALSE

Le résultat doit conserver :

  • le code de sortie ;
  • stdout ;
  • stderr.

Cette distinction est essentielle pour les futurs adaptateurs.

Exemple :

dig retourne 9

La tâche technique est terminée, mais le résultat fonctionnel signale un échec.


Annulation

Le GCancellable reçu par le worker doit être transmis directement à :

tool_process_run()

Si ToolProcess retourne :

TOOL_PROCESS_ERROR_CANCELLED

le worker doit permettre à BackgroundTask daboutir à létat :

BACKGROUND_TASK_STATE_CANCELLED

Il ne doit pas transformer lannulation en erreur métier générique.

Aucun processus enfant ne doit rester actif.


Résultat

Cycle de vie

void tool_task_result_free(
    ToolTaskResult *result
);

La fonction doit accepter NULL.

Identifiant

const char *tool_task_result_get_tool_identifier(
    const ToolTaskResult *result
);

Chaîne empruntée.

Chemin de lexécutable

const char *tool_task_result_get_executable_path(
    const ToolTaskResult *result
);

Chaîne empruntée.

Arguments

gsize tool_task_result_get_argument_count(
    const ToolTaskResult *result
);
const char *tool_task_result_get_argument(
    const ToolTaskResult *result,
    gsize index
);

Chaînes empruntées.

Dossier de travail

const char *tool_task_result_get_working_directory(
    const ToolTaskResult *result
);

Retourne NULL si aucun dossier na été défini.

Résultat du processus

const ToolProcessResult *tool_task_result_get_process_result(
    const ToolTaskResult *result
);

Pointeur emprunté.

Une fonction de transfert ou de référence nest pas nécessaire pour ce ticket.


Propriété des données

Données de préparation

Le module doit copier toutes les données nécessaires avant le démarrage.

Il ne doit pas dépendre de la durée de vie :

  • du ToolRegistry ;
  • du ToolInfo ;
  • du tableau darguments dorigine ;
  • du titre dorigine ;
  • du dossier de travail dorigine.

Résultat

ToolTaskResult devient propriétaire de :

  • lidentifiant copié ;
  • le chemin copié ;
  • la copie des arguments ;
  • la copie du dossier de travail ;
  • ToolProcessResult.

Le destructeur doit tout libérer.

BackgroundTask

La propriété de la BackgroundTask reste conforme au ticket #034.


API publique suggérée

typedef struct ToolTaskResult ToolTaskResult;

typedef enum
{
    TOOL_TASK_ERROR_INVALID_ARGUMENT,
    TOOL_TASK_ERROR_TOOL_NOT_FOUND,
    TOOL_TASK_ERROR_TOOL_NOT_CHECKED,
    TOOL_TASK_ERROR_TOOL_MISSING,
    TOOL_TASK_ERROR_TASK_CREATION,
    TOOL_TASK_ERROR_TASK_START,
    TOOL_TASK_ERROR_PROCESS
} ToolTaskError;

#define TOOL_TASK_ERROR \
    tool_task_error_quark()

GQuark tool_task_error_quark(void);

BackgroundTask *tool_task_create(
    const ToolRegistry *tool_registry,
    const char *tool_identifier,
    const char *task_title,
    const char *const arguments[],
    const char *working_directory,
    GError **error
);

gboolean tool_task_start(
    BackgroundTask *background_task,
    BackgroundTaskCompletedCallback completed_callback,
    gpointer completed_user_data,
    GDestroyNotify completed_user_data_destroy,
    GError **error
);

void tool_task_result_free(
    ToolTaskResult *result
);

const char *tool_task_result_get_tool_identifier(
    const ToolTaskResult *result
);

const char *tool_task_result_get_executable_path(
    const ToolTaskResult *result
);

gsize tool_task_result_get_argument_count(
    const ToolTaskResult *result
);

const char *tool_task_result_get_argument(
    const ToolTaskResult *result,
    gsize index
);

const char *tool_task_result_get_working_directory(
    const ToolTaskResult *result
);

const ToolProcessResult *tool_task_result_get_process_result(
    const ToolTaskResult *result
);

LAPI peut évoluer si limplémentation de BackgroundTask rend une autre forme plus sûre.


Marquage dune BackgroundTask

tool_task_start() doit pouvoir vérifier que la tâche reçue a été créée par tool_task_create().

Approches possibles :

Structure privée associée

Conserver les données du worker dans une structure privée connue uniquement de ToolTask.

Extension de BackgroundTask

Ajouter un pointeur de contexte privé à BackgroundTask uniquement si cela reste générique et justifié.

Wrapper opaque

Créer un ToolTask opaque contenant la BackgroundTask.

Cette solution peut être préférable si la vérification devient fragile.

Le choix final doit privilégier :

  • sécurité de propriété ;
  • lisibilité ;
  • testabilité ;
  • absence de cast dangereux.

Tests unitaires obligatoires

Les tests doivent créer un faux outil temporaire.

Ils ne doivent pas dépendre dun outil réel.


1. Arguments invalides

Tester au minimum :

  • registre NULL ;
  • identifiant NULL ;
  • identifiant vide ;
  • titre NULL ;
  • titre vide ;
  • dossier de travail vide ;
  • GError déjà initialisé si la convention est vérifiée.

Résultat attendu :

TOOL_TASK_ERROR_INVALID_ARGUMENT

2. Outil inconnu

Registre valide mais identifiant absent.

Résultat :

TOOL_TASK_ERROR_TOOL_NOT_FOUND

3. Outil non vérifié

Enregistrer un outil sans appeler tool_registry_refresh().

Résultat :

TOOL_TASK_ERROR_TOOL_NOT_CHECKED

4. Outil absent

Enregistrer un faux outil absent, puis rafraîchir.

Résultat :

TOOL_TASK_ERROR_TOOL_MISSING

5. Création valide

Créer un faux outil dans un PATH temporaire.

Vérifier :

  • registre rafraîchi ;
  • création de la BackgroundTask ;
  • titre correct ;
  • état initial PENDING.

6. Copie des arguments

Créer les arguments avec des chaînes dynamiques.

Créer la tâche, puis libérer les chaînes originales.

Démarrer la tâche.

Le résultat doit toujours contenir les bonnes valeurs.


7. Exécution réussie

Le faux outil écrit dans stdout et retourne zéro.

Vérifier :

  • tâche terminée ;
  • état COMPLETED ;
  • résultat non NULL ;
  • identifiant correct ;
  • chemin correct ;
  • arguments corrects ;
  • ToolProcessResult disponible ;
  • is_success == TRUE.

8. Code de sortie non nul

Le faux outil retourne :

exit 7

Vérifier :

  • tâche COMPLETED ;
  • résultat disponible ;
  • code 7 ;
  • succès fonctionnel FALSE.

9. Erreur de lancement

Créer la tâche avec un exécutable détecté, puis supprimer le fichier avant le démarrage.

Vérifier :

  • tâche FAILED ;
  • erreur exploitable ;
  • aucun résultat.

10. Annulation

Créer un faux outil long.

Démarrer la tâche puis lannuler.

Vérifier :

  • état final CANCELLED ;
  • aucun résultat ;
  • aucun processus enfant actif.

11. Dossier de travail

Le faux outil affiche son dossier courant.

Vérifier que le résultat correspond au dossier demandé.


12. Plusieurs tâches simultanées

Créer deux tâches à partir du même outil avec des arguments différents.

Les lancer simultanément.

Vérifier :

  • deux résultats indépendants ;
  • aucun mélange des arguments ;
  • aucun partage incorrect des sorties ;
  • aucun état global mutable.

13. Destruction avant démarrage

Créer une tâche puis la libérer sans la lancer.

Vérifier :

  • aucune fuite ;
  • destruction des copies darguments ;
  • destruction des données privées.

14. Destruction après exécution

Exécuter une tâche, lire le résultat, puis libérer la tâche.

Vérifier la libération complète.


Noms de tests suggérés

/tool_task/invalid_arguments
/tool_task/tool_not_found
/tool_task/tool_not_checked
/tool_task/tool_missing
/tool_task/create
/tool_task/argument_ownership
/tool_task/success
/tool_task/nonzero_exit
/tool_task/spawn_failure
/tool_task/cancellation
/tool_task/working_directory
/tool_task/concurrent_tasks
/tool_task/free_before_start
/tool_task/free_after_completion

Synchronisation des tests

Les tests doivent utiliser une GMainLoop pour attendre la fin des BackgroundTask.

Ils ne doivent pas utiliser une attente active infinie.

Prévoir un timeout de sécurité afin quun test défectueux ne bloque pas toute la suite.

Exemple :

5 secondes maximum pour un test normal

Le test dannulation doit se terminer rapidement.


Makefile

Ajouter :

TEST_TOOL_TASK := tests/test_tool_task

Règle attendue :

$(TEST_TOOL_TASK): \
	tests/test_tool_task.c \
	src/core/tool_task.c \
	src/core/tool_registry.c \
	src/core/tool_process.c \
	src/core/background_task.c
	$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)

Ajouter la cible à :

make test
make clean

Vérifications manuelles

make clean
make
make tests/test_tool_task
./tests/test_tool_task
make test

Vérification mémoire

G_DEBUG=gc-friendly \
G_SLICE=always-malloc \
valgrind \
    --leak-check=full \
    --show-leak-kinds=all \
    ./tests/test_tool_task

Surveiller particulièrement :

  • création sans démarrage ;
  • échec de démarrage ;
  • annulation ;
  • résultat avec code non nul ;
  • tâches simultanées.

Critères dacceptation

Le ticket est validé lorsque :

  • le module compile en C17 strict ;
  • aucune dépendance GTK nest ajoutée ;
  • un outil est recherché par identifiant dans ToolRegistry ;
  • les états UNKNOWN et MISSING sont correctement refusés ;
  • le chemin résolu est copié ;
  • les arguments sont copiés ;
  • le dossier de travail est copié ;
  • la tâche utilise ToolProcess dans un thread secondaire ;
  • le GCancellable est transmis ;
  • lannulation aboutit à CANCELLED ;
  • un code de sortie non nul produit une tâche COMPLETED ;
  • le résultat brut est accessible ;
  • les résultats sont indépendants du registre après création ;
  • plusieurs tâches peuvent fonctionner simultanément ;
  • aucun shell nest utilisé ;
  • aucun processus enfant nest abandonné ;
  • aucune fuite mémoire nest détectée ;
  • tous les tests passent ;
  • le test est intégré au Makefile.

Démonstration finale prévue

Après validation du module, remplacer temporairement le bouton actuel :

Tâche de test

par une vraie démonstration basée sur un faux outil ou un outil stable détecté.

Cette démonstration devra passer par :

ToolRegistry
→ ToolTask
→ BackgroundTask
→ TaskManager
→ TaskPanel

Le bouton temporaire sera supprimé dès que le premier adaptateur OSINT réel sera disponible.


Suite prévue

Après ce ticket :

  1. ticket #039 — catalogue initial des outils externes ;
  2. interrogation de leurs versions ;
  3. ticket dadaptateur DNS ;
  4. conservation structurée des exécutions ;
  5. stockage des sorties brutes ;
  6. création dobservations et de preuves ;
  7. affichage dans le workspace ;
  8. packaging Ubuntu avec dépendances optionnelles.