From 9259506fcbcc68c164de1d69b7b89208bcd059ce Mon Sep 17 00:00:00 2001 From: nasr Date: Wed, 7 Jan 2026 14:00:22 +0100 Subject: [PATCH 01/59] checkpoint --- internal/dbusservices/manager.go | 79 ++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 9931950..eff1e78 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -76,18 +76,67 @@ func RunRetrieval( units := make([]*v2.LoadedUnit, 0, len(entries)) for _, e := range entries { - units = append(units, &v2.LoadedUnit{ - Name: e.Name, - Description: "", - LoadState: e.State, - SubState: "", - ActiveState: "", - DepUnit: "", - ObjectPath: "", - QueuedJob: 0, - JobType: "", - JobPath: "", - }) + for _, i := range entries { + + /** + + TODO(nasr): replace this with the proper enum + */ + + ch := make(chan []svctypes.LoadedUnit) + parse := make(chan []svctypes.LoadedUnit) + + go svc.GetLoadedUnits(obj, ch) + go dh.ParseLoadedUnits(ch, parse) + + loaded := <-parse + + if i.State == "enabled" { + + svc.GetLoadedUnits(obj, out) + + var luIdx int16 + var alIdx int16 + + for luIdx, lu := range <-out { + + } + + } + + units = append(units, &v2.LoadedUnit{ + Name: e.Name, + Description: "", + LoadState: e.State, + SubState: "", + ActiveState: "", + DepUnit: "", + ObjectPath: "", + QueuedJob: 0, + JobType: "", + JobPath: "", + }) + } + + loaded := <-parse + + units := make([]*v2.LoadedUnit, 0, len(loaded)) + for _, u := range loaded { + units = append(units, &v2.LoadedUnit{ + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + /*oops typo in queued job :)*/ + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), + }) + } + } return units, nil @@ -112,9 +161,9 @@ func RunRetrieval( DepUnit: u.DepUnit, ObjectPath: string(u.ObjectPath), /*oops typo in queued job :)*/ - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), }) } From f4e0d6fd13ab042c40c92c71d36253122d978585 Mon Sep 17 00:00:00 2001 From: nasr Date: Wed, 7 Jan 2026 15:06:30 +0100 Subject: [PATCH 02/59] feature: the total list of returned unit lists now return a complete list of units where the enabled units have complete properties --- internal/dbusservices/dbus/parse.go | 6 +- internal/dbusservices/manager.go | 139 +++++++++++----------------- 2 files changed, 56 insertions(+), 89 deletions(-) diff --git a/internal/dbusservices/dbus/parse.go b/internal/dbusservices/dbus/parse.go index fd31e91..5f77fe9 100644 --- a/internal/dbusservices/dbus/parse.go +++ b/internal/dbusservices/dbus/parse.go @@ -19,13 +19,11 @@ func ParseUnitFileEntries(in chan []svctypes.UnitFileEntry, out chan []svctypes. for i := range input { if input[i].State == "enabled" { - fmt.Println(input[i].Name) + continue } else { - - fmt.Println(input[i].Name) + out <- input } } - out <- input } diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index eff1e78..2a86ec0 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -1,3 +1,4 @@ + package dbus_services import ( @@ -57,6 +58,41 @@ func RunSymlinkAction(conn *dbus.Conn, sc svc.UnitFileAction, enableForRunTime b return nil } +/** +Helper function to retrieve all loaded units + +*/ +func getLoadedUnits(conn *dbus.Conn) []*v2.LoadedUnit { + + obj := dh.CreateSystemdObject(conn) + ch := make(chan []svctypes.LoadedUnit) + parse := make(chan []svctypes.LoadedUnit) + + go svc.GetLoadedUnits(obj, ch) + go dh.ParseLoadedUnits(ch, parse) + + loaded := <-parse + + units := make([]*v2.LoadedUnit, 0, len(loaded)) + for _, u := range loaded { + units = append(units, &v2.LoadedUnit{ + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + /*oops typo in queued job :)*/ + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), + }) + } + + return units +} + // @param, true for all on disk, false for loaded units func RunRetrieval( conn *dbus.Conn, @@ -64,7 +100,6 @@ func RunRetrieval( ) ([]*v2.LoadedUnit, error) { obj := dh.CreateSystemdObject(conn) - if all { ch := make(chan []svctypes.UnitFileEntry) parse := make(chan []svctypes.UnitFileEntry) @@ -76,96 +111,30 @@ func RunRetrieval( units := make([]*v2.LoadedUnit, 0, len(entries)) for _, e := range entries { - for _, i := range entries { - - /** - - TODO(nasr): replace this with the proper enum - */ - - ch := make(chan []svctypes.LoadedUnit) - parse := make(chan []svctypes.LoadedUnit) - - go svc.GetLoadedUnits(obj, ch) - go dh.ParseLoadedUnits(ch, parse) - - loaded := <-parse - - if i.State == "enabled" { - - svc.GetLoadedUnits(obj, out) - - var luIdx int16 - var alIdx int16 - - for luIdx, lu := range <-out { - - } - - } - - units = append(units, &v2.LoadedUnit{ - Name: e.Name, - Description: "", - LoadState: e.State, - SubState: "", - ActiveState: "", - DepUnit: "", - ObjectPath: "", - QueuedJob: 0, - JobType: "", - JobPath: "", - }) - } - - loaded := <-parse - - units := make([]*v2.LoadedUnit, 0, len(loaded)) - for _, u := range loaded { - units = append(units, &v2.LoadedUnit{ - Name: u.Name, - Description: u.Description, - LoadState: u.LoadState, - SubState: u.SubState, - ActiveState: u.ActiveState, - DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), - /*oops typo in queued job :)*/ - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), - }) - } + units = append(units, &v2.LoadedUnit{ + Name: e.Name, + Description: "", + LoadState: e.State, + SubState: "", + ActiveState: "", + DepUnit: "", + ObjectPath: "", + QueuedJob: 0, + JobType: "", + JobPath: "", + }) + } + loadedUnits := getLoadedUnits(conn) + for _, i := range loadedUnits { + + units = append(units, i) } return units, nil } - ch := make(chan []svctypes.LoadedUnit) - parse := make(chan []svctypes.LoadedUnit) - - go svc.GetLoadedUnits(obj, ch) - go dh.ParseLoadedUnits(ch, parse) - - loaded := <-parse - - units := make([]*v2.LoadedUnit, 0, len(loaded)) - for _, u := range loaded { - units = append(units, &v2.LoadedUnit{ - Name: u.Name, - Description: u.Description, - LoadState: u.LoadState, - SubState: u.SubState, - ActiveState: u.ActiveState, - DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), - /*oops typo in queued job :)*/ - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), - }) - } + units := getLoadedUnits(conn) return units, nil } From f4f7dc37b3cfc7509f114d45ac54ba5b353da688 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 13:26:53 +0100 Subject: [PATCH 03/59] feature: killing process and retrieving processes states protobuf --- ..._resources.proto => deviceresources.proto} | 0 proto/resources/v2/deviceresources.proto | 116 ++++++++++++++++++ 2 files changed, 116 insertions(+) rename proto/resources/v1/{device_resources.proto => deviceresources.proto} (100%) create mode 100644 proto/resources/v2/deviceresources.proto diff --git a/proto/resources/v1/device_resources.proto b/proto/resources/v1/deviceresources.proto similarity index 100% rename from proto/resources/v1/device_resources.proto rename to proto/resources/v1/deviceresources.proto diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto new file mode 100644 index 0000000..0272feb --- /dev/null +++ b/proto/resources/v2/deviceresources.proto @@ -0,0 +1,116 @@ +syntax = "proto3"; + +package resources.v2; + +option go_package = "paradigm-ehb/agent/gen/resources/v2;resourcespb"; + +service ResourcesService { + rpc GetSystemResources(GetSystemResourcesRequest) + returns (GetSystemResourcesResponse); +} + +message GetSystemResourcesRequest {} + +message GetSystemResourcesResponse { + SystemResources resources = 1; +} + +/** + * + * Get a complete system snapshots + * @return Cpu, Memory, Device, Disk ( partitions ) and processes + * + * */ +message SystemResources { + Cpu cpu = 1; + Memory memory = 2; + Device device = 3; + repeated Disk disks = 4; + repeated Process processes = 5; +} + +/** + * Cpu information + * + * @return vendor, model, frequency, cores + * */ +message Cpu { + string vendor = 1; + string model = 2; + string frequency = 3; + uint32 max_core = 4; +} + +/** + * Memory + * @return total memory on system, available memory on system + * */ +message Memory { + string total = 1; + string free = 2; +} + +/** + * Device specific information + * @return os version ( distro information ), and uptime in string format + * + * */ + +message Device { + string os_version = 1; + string uptime = 2; +} + +/** + * + * Disk information + * @disk a list of all available partitions + * + * */ + +message Disk { + repeated DiskPartition partitions = 1; +} + +/** + * Partition + * @return partition name, major, minor, blocks + * */ +message DiskPartition { + string name = 1; + uint32 major = 2; + uint32 minor = 3; + uint64 blocks = 4; +} + +enum ProcessState { + PROCESS_STATE_UNSPECIFIED = 0; + PROCESS_STATE_RUNNING = 1; + PROCESS_STATE_SLEEPING = 2; + PROCESS_STATE_DISK_SLEEPING = 3; + PROCESS_STATE_STOPPED = 4; + PROCESS_STATE_TRACING_STOPPED = 5; + PROCESS_STATE_ZOMBIE = 6; + PROCESS_STATE_DEAD = 7; +} + +/** + * Process information + * @return pid ( process identifier ), process name, the state, todo :) + * */ +message Process { + uint32 pid = 1; + string name = 2; + ProcessState state = 3; + uint64 utime = 4; + uint32 num_threads = 5; +} + +message KillProcessRequest { + uint32 pid = 1; +} + +message KillProcessReply { + + string succes = 1; +} From 8dfd98fd21a8fef3886e06b1bd2691b473cb11b6 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:40:57 +0100 Subject: [PATCH 04/59] refactor: added the new services and resources version --- test/setup_test.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/setup_test.go b/test/setup_test.go index e14c012..2b90e4d 100644 --- a/test/setup_test.go +++ b/test/setup_test.go @@ -1,17 +1,23 @@ package server_test +/** + +TODO(nasr): update tests for the new grpc handler services version +*/ import ( "context" "net" "google.golang.org/grpc" "google.golang.org/grpc/test/bufconn" - "paradigm-ehb/agent/pkg/grpc_handler" - respb "paradigm-ehb/agent/gen/resources/v1" + "paradigm-ehb/agent/pkg/grpchandler" + servicesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/services/v1" + servicesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/services/v2" + // respb "paradigm-ehb/agent/gen/resources/v1" + serpb_v1 "paradigm-ehb/agent/gen/services/v1" serpb_v2 "paradigm-ehb/agent/gen/services/v2" - greetpb "paradigm-ehb/agent/gen/greet" journalpb "paradigm-ehb/agent/gen/journal/v1" @@ -37,9 +43,9 @@ func init() { grpc_health_v1.RegisterHealthServer(server, healthServer) - respb.RegisterResourcesServiceServer(server, &grpc_handler.ResourcesService{}) - serpb_v1.RegisterHandlerServiceServer(server, &grpc_handler.HandlerService{}) - serpb_v2.RegisterHandlerServiceServer(server, &grpc_handler.HandlerServiceV2{}) + // respb.RegisterResourcesServiceServer(server, &grpc_handler.ResourcesService{}) + serpb_v1.RegisterHandlerServiceServer(server, &servicesHandlerV1.HandlerService{}) + serpb_v2.RegisterHandlerServiceServer(server, &servicesHandlerV2.HandlerServiceV2{}) greetpb.RegisterGreeterServer(server, &grpc_handler.GreeterServer{}) journalpb.RegisterJournalServiceServer(server, &grpc_handler.JournalService{}) From fe524d1184199817e2fa604cfb9ef5481957398c Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:41:26 +0100 Subject: [PATCH 05/59] feature: new grpcurl tests --- test/grpc_res_v2.sh | 114 +++++++++++++++++++++++++++++++++++++++ test/grpc_resources.sh | 27 ++++++++++ test/grpc_services.sh | 106 ++++++++++++++++++++++++++++++++++++ test/grpc_test_script.sh | 106 ------------------------------------ 4 files changed, 247 insertions(+), 106 deletions(-) create mode 100755 test/grpc_res_v2.sh create mode 100755 test/grpc_resources.sh create mode 100755 test/grpc_services.sh delete mode 100755 test/grpc_test_script.sh diff --git a/test/grpc_res_v2.sh b/test/grpc_res_v2.sh new file mode 100755 index 0000000..b372ae6 --- /dev/null +++ b/test/grpc_res_v2.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +# gRPCurl test script for ResourcesServiceV2 +# Make sure your gRPC server is running before executing these commands + +# Color codes for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Default server address +SERVER="localhost:5000" + +# Check if custom server address provided +if [ ! -z "$1" ]; then + SERVER="$1" +fi + +echo -e "${BLUE}Testing ResourcesServiceV2 on ${SERVER}${NC}\n" + +# List available services +echo -e "${GREEN}=== Listing available services ===${NC}" +grpcurl -plaintext ${SERVER} list +echo "" + +# List methods for ResourcesServiceV2 +echo -e "${GREEN}=== Listing methods for resources.v2.ResourcesService ===${NC}" +grpcurl -plaintext ${SERVER} list resources.v2.ResourcesService +echo "" + +# Describe the service +echo -e "${GREEN}=== Describing ResourcesService ===${NC}" +grpcurl -plaintext ${SERVER} describe resources.v2.ResourcesService +echo "" + +# Test GetSystemResources +echo -e "${GREEN}=== Getting System Resources ===${NC}" +grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources +echo "" + +# Test KillProcess (example with PID 12345) +# WARNING: Uncomment and modify PID carefully - this will kill a process! +# echo -e "${GREEN}=== Killing Process (PID: 12345) ===${NC}" +# grpcurl -plaintext \ +# -d '{"pid": 12345}' \ +# ${SERVER} \ +# resources.v2.ResourcesService/KillProcess +# echo "" + +# Get system resources with formatted output (using jq if available) +echo -e "${GREEN}=== Getting System Resources (formatted) ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.' +else + echo "jq not installed - skipping formatted output" + echo "Install jq for pretty JSON: sudo apt-get install jq" +fi +echo "" + +# Show CPU information only +echo -e "${GREEN}=== CPU Information ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.resources.cpu' +else + echo "jq not installed - install it to filter output" +fi +echo "" + +# Show Memory information only +echo -e "${GREEN}=== Memory Information ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.resources.memory' +else + echo "jq not installed - install it to filter output" +fi +echo "" + +# Show first 5 processes +echo -e "${GREEN}=== First 5 Processes ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.resources.processes[:5]' +else + echo "jq not installed - install it to filter output" +fi +echo "" + +# Count total processes +echo -e "${GREEN}=== Total Process Count ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.resources.processes | length' +else + echo "jq not installed - install it to filter output" +fi +echo "" + +echo -e "${BLUE}Tests completed!${NC}" diff --git a/test/grpc_resources.sh b/test/grpc_resources.sh new file mode 100755 index 0000000..5ea9c84 --- /dev/null +++ b/test/grpc_resources.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env sh +set -eu + +ADDR="${ADDR:-localhost:5000}" + +echo "== ResourcesService integration test ==" +echo "Target: $ADDR" +echo + +command -v grpcurl >/dev/null 2>&1 || { + echo "ERROR: grpcurl not found" + exit 1 +} + +echo "== Checking service availability ==" +grpcurl -plaintext "$ADDR" list resources.v1.ResourcesService >/dev/null +echo "OK" +echo + +echo "== Calling GetSystemResources ==" +grpcurl -plaintext \ + -format text \ + "$ADDR" \ + resources.v1.ResourcesService/GetSystemResources +echo + +echo "== Test completed ==" diff --git a/test/grpc_services.sh b/test/grpc_services.sh new file mode 100755 index 0000000..c2c13d3 --- /dev/null +++ b/test/grpc_services.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env sh +set -euo pipefail + +HOST="${HOST:-localhost}" +PORT="${PORT:-5000}" +ADDR="${HOST}:${PORT}" +SERVICE="services.v2.HandlerService" + +echo "== gRPC integration test ==" +echo "Target: ${ADDR}" +echo + +command -v grpcurl >/dev/null 2>&1 || { + echo "ERROR: grpcurl not found" + exit 1 +} + +echo "== Checking server availability ==" +grpcurl -plaintext "${ADDR}" list >/dev/null +echo "OK" +echo + +echo "== Listing all services ==" +grpcurl -plaintext "${ADDR}" list +echo + +echo "== Checking ${SERVICE} existence ==" +grpcurl -plaintext "${ADDR}" list "${SERVICE}" >/dev/null +echo "OK" +echo + + +echo "== Listing ${SERVICE} methods ==" +grpcurl -plaintext "${ADDR}" list "${SERVICE}" +echo + +echo "== GetAllUnits ==" +grpcurl -plaintext \ + -d '{}' \ + "${ADDR}" \ + "${SERVICE}/GetAllUnits" +echo + +echo "== GetLoadedUnits ==" +grpcurl -plaintext \ + -d '{}' \ + "${ADDR}" \ + "${SERVICE}/GetLoadedUnits" +echo + + echo "== GetUnitStatus (tailscaled.service) ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service" + }' \ + "${ADDR}" \ + "${SERVICE}/GetUnitStatus" + echo + + echo "== PerformUnitAction: START tailscaled.service ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_ACTION_START" + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitAction" + echo + + echo "== PerformUnitFileAction: ENABLE tailscaled.service (runtime=true, force=true) ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_FILE_ACTION_ENABLE", + "runtime": true, + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitFileAction" + echo + + echo "== PerformUnitAction: STOP tailscaled.service ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_ACTION_STOP", + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitAction" + echo + + + echo "== PerformUnitFileAction: DISABLE tailscaled.service ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_FILE_ACTION_DISABLE", + "runtime": true, + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitFileAction" + echo + +echo "== All gRPC v2 tests passed ==" diff --git a/test/grpc_test_script.sh b/test/grpc_test_script.sh deleted file mode 100755 index 07f19b8..0000000 --- a/test/grpc_test_script.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env sh -set -euo pipefail - -HOST="${HOST:-localhost}" -PORT="${PORT:-5000}" -ADDR="${HOST}:${PORT}" - -SERVICE="services.v2.HandlerService" - -echo "== gRPC integration test ==" -echo "Target: ${ADDR}" -echo - -command -v grpcurl >/dev/null 2>&1 || { - echo "ERROR: grpcurl not found" - exit 1 -} - -echo "== Checking server availability ==" -grpcurl -plaintext "${ADDR}" list >/dev/null -echo "OK" -echo - -echo "== Listing all services ==" -grpcurl -plaintext "${ADDR}" list -echo - -echo "== Checking ${SERVICE} existence ==" -grpcurl -plaintext "${ADDR}" list "${SERVICE}" >/dev/null -echo "OK" -echo - -echo "== Listing ${SERVICE} methods ==" -grpcurl -plaintext "${ADDR}" list "${SERVICE}" -echo - -echo "== GetAllUnits ==" -grpcurl -plaintext \ - -d '{}' \ - "${ADDR}" \ - "${SERVICE}/GetAllUnits" -echo - -echo "== GetLoadedUnits ==" -grpcurl -plaintext \ - -d '{}' \ - "${ADDR}" \ - "${SERVICE}/GetLoadedUnits" -echo - -echo "== GetUnitStatus (tailscaled.service) ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service" - }' \ - "${ADDR}" \ - "${SERVICE}/GetUnitStatus" -echo - -echo "== PerformUnitAction: START tailscaled.service ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service", - "action": "UNIT_ACTION_START" - }' \ - "${ADDR}" \ - "${SERVICE}/PerformUnitAction" -echo - -echo "== PerformUnitFileAction: ENABLE tailscaled.service (runtime=true, force=true) ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service", - "action": "UNIT_FILE_ACTION_ENABLE", - "runtime": true, - "force": true - }' \ - "${ADDR}" \ - "${SERVICE}/PerformUnitFileAction" -echo - -echo "== PerformUnitAction: STOP tailscaled.service ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service", - "action": "UNIT_ACTION_STOP", - "force": true - }' \ - "${ADDR}" \ - "${SERVICE}/PerformUnitAction" -echo - - -echo "== PerformUnitFileAction: DISABLE tailscaled.service ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service", - "action": "UNIT_FILE_ACTION_DISABLE", - "runtime": true, - "force": true - }' \ - "${ADDR}" \ - "${SERVICE}/PerformUnitFileAction" -echo - -echo "== All gRPC v2 tests passed ==" From e42d0b3629bf6ee461b9d64f373534b853195aa0 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:41:34 +0100 Subject: [PATCH 06/59] feature: v3 services --- proto/services/v3/services.proto | 124 +++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 proto/services/v3/services.proto diff --git a/proto/services/v3/services.proto b/proto/services/v3/services.proto new file mode 100644 index 0000000..1319324 --- /dev/null +++ b/proto/services/v3/services.proto @@ -0,0 +1,124 @@ +syntax = "proto3"; + +package services.v3; + +option go_package = "paradigm-ehb/agent/proto/services/v3"; + + +service HandlerService { + rpc PerformUnitAction (UnitActionRequest) returns (UnitActionReply); + rpc PerformUnitFileAction (UnitFileActionRequest) returns (UnitFileActionReply); + rpc GetAllUnits (GetUnitsRequest) returns (GetUnitsReply); + rpc GetLoadedUnits (GetUnitsRequest) returns (GetUnitsReply); + rpc GetFilteredUnits (GetUnitsRequest) returns (GetUnitsReply); + rpc GetUnitStatus (GetUnitStatusRequest) returns (GetUnitStatusReply); +} + + +message UnitActionRequest { + string unit_name = 1; + + enum UnitAction { + UNIT_ACTION_UNSPECIFIED = 0; + UNIT_ACTION_START = 1; + UNIT_ACTION_STOP = 2; + UNIT_ACTION_RESTART = 3; + } + + UnitAction action = 2; + bool force = 3; +} + +message UnitActionReply { + bytes status = 1; + bool success = 2; + string error_message = 3; +} + + +message UnitFileActionRequest { + string unit_name = 1; + + enum UnitFileAction { + UNIT_FILE_ACTION_UNSPECIFIED = 0; + UNIT_FILE_ACTION_ENABLE = 1; + UNIT_FILE_ACTION_DISABLE = 2; + } + + UnitFileAction action = 2; + bool runtime = 3; + bool force = 4; +} + +message UnitFileActionReply { + bytes status = 1; + bool success = 2; + string error_message = 3; +} + + +message LoadedUnit { + string name = 1; + string description = 2; + string load_state = 3; + string sub_state = 4; + string active_state = 5; + string dep_unit = 6; + + string object_path = 7; + + uint32 queued_job = 8; + string job_type = 9; + string job_path = 10; +} + +message Unit { + string name = 1; + string state = 2; +} + + +message GetUnitsRequest { + + enum UnitState { + UNIT_STATE_UNSPECIFIED = 0; + ENABLED = 1; + DISABLED = 2; + } + + string name = 1; + UnitState state = 2; +} + +message GetUnitsFilteredRequest { + + enum UnitFileState { + + UNIT_FILE_STATE_UNSPECIFIED = 0; + LOADED = 1; + NOT_FOUND = 2; + BAD_SETTING = 3; + ERROR = 4; + MASKED = 5; + } + + UnitFileState state = 1; + +} + +message GetUnitsReply { + repeated LoadedUnit units = 1; + bool success = 2; + string error_message = 3; +} + + +message GetUnitStatusRequest { + string unit_name = 1; +} + +message GetUnitStatusReply { + string state = 1; + bool success = 2; + string error_message = 3; +} From 16a3aba5280d4db961dcbd8236126efc5068b108 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:41:47 +0100 Subject: [PATCH 07/59] feature: resources services v2 and file restructure --- proto/resources/v1/deviceresources.proto | 1 + proto/resources/v2/deviceresources.proto | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/proto/resources/v1/deviceresources.proto b/proto/resources/v1/deviceresources.proto index 667c92a..63e6087 100644 --- a/proto/resources/v1/deviceresources.proto +++ b/proto/resources/v1/deviceresources.proto @@ -92,6 +92,7 @@ enum ProcessState { PROCESS_STATE_TRACING_STOPPED = 5; PROCESS_STATE_ZOMBIE = 6; PROCESS_STATE_DEAD = 7; + PROCESS_STATE_IDLE = 8; } /** diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 0272feb..55beeec 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -92,6 +92,8 @@ enum ProcessState { PROCESS_STATE_TRACING_STOPPED = 5; PROCESS_STATE_ZOMBIE = 6; PROCESS_STATE_DEAD = 7; + PROCESS_STATE_IDLE = 8; + PROCESS_STATE_UNDEFINED = 9; } /** @@ -99,7 +101,7 @@ enum ProcessState { * @return pid ( process identifier ), process name, the state, todo :) * */ message Process { - uint32 pid = 1; + int32 pid = 1; string name = 2; ProcessState state = 3; uint64 utime = 4; From 431c11376d449dd8cbb3401ef47b5401fad0c9d5 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:42:27 +0100 Subject: [PATCH 08/59] feature: adjusted the dbus manager for filtered services and better login objects handling still a lot of todos to handle --- internal/dbusservices/dbus/call.go | 33 +++++- internal/dbusservices/dbus/parse.go | 40 +++++-- internal/dbusservices/manager.go | 165 +++++++++++++++----------- internal/dbusservices/systemd/list.go | 40 +++++-- internal/dbusservices/types/types.go | 2 +- 5 files changed, 191 insertions(+), 89 deletions(-) diff --git a/internal/dbusservices/dbus/call.go b/internal/dbusservices/dbus/call.go index 9f9ac0b..5b0391a 100644 --- a/internal/dbusservices/dbus/call.go +++ b/internal/dbusservices/dbus/call.go @@ -1,6 +1,7 @@ package dbushandler import ( + "fmt" "github.com/godbus/dbus" ) @@ -9,7 +10,35 @@ import ( * used to handle services * @return BusObjcet */ -func CreateSystemdObject(conn *dbus.Conn) dbus.BusObject { +func CreateSystemdObject(conn *dbus.Conn) (dbus.BusObject, error) { + + obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") + if obj == nil { + + return nil, fmt.Errorf("failed to create a systemd object") + } + + return obj, nil +} + +/** + +* CreateLoginObject +* used to handle services +* @return BusObject + +*/ +func CreateLoginObject(conn *dbus.Conn) (dbus.BusObject, error) { + obj := conn.Object( + "org.freedesktop.login1", + "/org/freedesktop/login1", + ) + + if obj == nil { + + return nil, fmt.Errorf("failed to create a login object") + } + + return obj, nil - return conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") } diff --git a/internal/dbusservices/dbus/parse.go b/internal/dbusservices/dbus/parse.go index 5f77fe9..b8bf23f 100644 --- a/internal/dbusservices/dbus/parse.go +++ b/internal/dbusservices/dbus/parse.go @@ -2,40 +2,54 @@ package dbushandler import ( - "fmt" - svctypes "paradigm-ehb/agent/internal/dbusservices/types" + types "paradigm-ehb/agent/internal/dbusservices/types" + "strings" ) -// TODO: implement interfaces maybe +// TODO(nasr): implement interfaces maybe // Method // @param chan a(ss), chan a(ss) // @param chan UnitFileEntry, chan UnitFileEntry // @return nil -func ParseUnitFileEntries(in chan []svctypes.UnitFileEntry, out chan []svctypes.UnitFileEntry) { +func ParseUnits(in chan []types.Unit, out chan []types.Unit) { input := <-in - for i := range input { + /** + filter the units on services and remove devices etc + */ + buffer := make([]types.Unit, 0, len(input)) + for _, value := range input { - if input[i].State == "enabled" { - continue - } else { - out <- input + if strings.HasSuffix(".service", value.Name) { + buffer = append(buffer, value) } } + out <- buffer + + return + } -func ParseLoadedUnits(in chan []svctypes.LoadedUnit, out chan []svctypes.LoadedUnit) { +func ParseLoadedUnits(in chan []types.LoadedUnit, out chan []types.LoadedUnit) { input := <-in - for i := range input { + /** + filter the units on services and remove devices etc + */ + buffer := make([]types.LoadedUnit, 0, len(input)) + for _, value := range input { - fmt.Println(input[i]) + if strings.HasSuffix(".service", string(value.Name)) { + buffer = append(buffer, value) + } } - out <- input + out <- buffer + + return } diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 2a86ec0..e4eb8e3 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -1,21 +1,23 @@ - package dbus_services import ( "fmt" + "log" - v2 "paradigm-ehb/agent/gen/services/v2" - dh "paradigm-ehb/agent/internal/dbusservices/dbus" - svc "paradigm-ehb/agent/internal/dbusservices/systemd" - svctypes "paradigm-ehb/agent/internal/dbusservices/types" + dbushelper "paradigm-ehb/agent/internal/dbusservices/dbus" + systemd "paradigm-ehb/agent/internal/dbusservices/systemd" + types "paradigm-ehb/agent/internal/dbusservices/types" "github.com/godbus/dbus" ) // @param, action [start, stop, restart], symLinkAction [enable, disable], service name format "example.service" -func RunAction(conn *dbus.Conn, ac svc.UnitAction, service string) error { +func RunAction( + conn *dbus.Conn, + ac systemd.UnitAction, + service string) error { - obj := dh.CreateSystemdObject(conn) + obj, _ := dbushelper.CreateSystemdObject(conn) if !obj.Path().IsValid() { return fmt.Errorf("object path is invalid") } @@ -30,9 +32,14 @@ func RunAction(conn *dbus.Conn, ac svc.UnitAction, service string) error { } // @param, action [start, stop, restart], symLinkAction [enable, disable], service name format "example.service" -func RunSymlinkAction(conn *dbus.Conn, sc svc.UnitFileAction, enableForRunTime bool, enableForce bool, service []string) error { +func RunSymlinkAction( + conn *dbus.Conn, + sc systemd.UnitFileAction, + enableForRunTime bool, + enableForce bool, + service []string) error { - obj := dh.CreateSystemdObject(conn) + obj, _ := dbushelper.CreateSystemdObject(conn) if !obj.Path().IsValid() { fmt.Println("invalid systemd path") @@ -43,12 +50,12 @@ func RunSymlinkAction(conn *dbus.Conn, sc svc.UnitFileAction, enableForRunTime b switch sc { - case svc.UnitFileActionEnable: + case systemd.UnitFileActionEnable: call := obj.Call(string(sc), dbus.FlagAllowInteractiveAuthorization, service, enableForRunTime, enableForce) if call.Err != nil { return fmt.Errorf("error %v", call.Err) } - case svc.UnitFileActionDisable: + case systemd.UnitFileActionDisable: call := obj.Call(string(sc), dbus.FlagAllowInteractiveAuthorization, service, enableForRunTime) if call.Err != nil { return fmt.Errorf("something happened here %v", call.Err) @@ -58,85 +65,113 @@ func RunSymlinkAction(conn *dbus.Conn, sc svc.UnitFileAction, enableForRunTime b return nil } -/** -Helper function to retrieve all loaded units - -*/ -func getLoadedUnits(conn *dbus.Conn) []*v2.LoadedUnit { +// MapLoadedUnits /* +func MapLoadedUnits(conn *dbus.Conn) []*types.LoadedUnit { - obj := dh.CreateSystemdObject(conn) - ch := make(chan []svctypes.LoadedUnit) - parse := make(chan []svctypes.LoadedUnit) + obj, _ := dbushelper.CreateSystemdObject(conn) + ch := make(chan []types.LoadedUnit) + parse := make(chan []types.LoadedUnit) - go svc.GetLoadedUnits(obj, ch) - go dh.ParseLoadedUnits(ch, parse) + go systemd.GetLoadedUnits(obj, ch) + go dbushelper.ParseLoadedUnits(ch, parse) loaded := <-parse - units := make([]*v2.LoadedUnit, 0, len(loaded)) + units := make([]*types.LoadedUnit, 0, len(loaded)) + for _, u := range loaded { - units = append(units, &v2.LoadedUnit{ + units = append(units, &types.LoadedUnit{ Name: u.Name, Description: u.Description, LoadState: u.LoadState, SubState: u.SubState, ActiveState: u.ActiveState, DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), + ObjectPath: u.ObjectPath, /*oops typo in queued job :)*/ - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), + QueudJob: u.QueudJob, + JobType: u.JobType, + JobPath: u.JobPath, + }) + } + + return units +} + +func MapFilteredUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { + + /* + + dbushelper.CreateSystemdObject(conn) + TODO(nasr): map the filtered units to their appropriate object and pass + them to the grpc handler + **/ + + return nil, nil +} + +func MapUnits(conn *dbus.Conn) []*types.LoadedUnit { + + /** + TODO(nasr): fix the error handling + */ + obj, _ := dbushelper.CreateSystemdObject(conn) + + in := make(chan []types.Unit) + out := make(chan []types.Unit) + + go systemd.GetUnits(obj, in) + go dbushelper.ParseUnits(in, out) + + var entries []types.Unit + + units := make([]*types.LoadedUnit, 0, len(entries)) + + entries = <-out + + for _, e := range entries { + + units = append(units, &types.LoadedUnit{ + Name: e.Name, + Description: "Not available", + LoadState: e.State, + SubState: "Not Available", + ActiveState: "Not Available", + DepUnit: "Not Available", + ObjectPath: "Not Available", + QueudJob: 0, + JobType: "Not Available", + JobPath: "Not Available", }) } return units } -// @param, true for all on disk, false for loaded units +/* +* +* @param, true for all on disk, false for loaded units +* a loaded unit is a unit that has been activated before +* and is available in memoery for the server to start up +* or something like that +* @return []*types.LoadedUnit, error + */ func RunRetrieval( conn *dbus.Conn, - all bool, -) ([]*v2.LoadedUnit, error) { - - obj := dh.CreateSystemdObject(conn) - if all { - ch := make(chan []svctypes.UnitFileEntry) - parse := make(chan []svctypes.UnitFileEntry) - - go svc.GetAllUnits(obj, ch) - go dh.ParseUnitFileEntries(ch, parse) - - entries := <-parse - - units := make([]*v2.LoadedUnit, 0, len(entries)) - for _, e := range entries { - units = append(units, &v2.LoadedUnit{ - Name: e.Name, - Description: "", - LoadState: e.State, - SubState: "", - ActiveState: "", - DepUnit: "", - ObjectPath: "", - QueuedJob: 0, - JobType: "", - JobPath: "", - }) - } + requestAllUnitsOnDisk bool, +) ([]*types.LoadedUnit, error) { - loadedUnits := getLoadedUnits(conn) - for _, i := range loadedUnits { - - units = append(units, i) - } + conn, err := dbushelper.CreateSystemBus() - return units, nil + if err != nil { + log.Printf("failed to create a system bus connection for retrieving units %v", err) } - units := getLoadedUnits(conn) + if requestAllUnitsOnDisk { + return MapUnits(conn), nil + } - return units, nil + return MapLoadedUnits(conn), nil } func GetStatus(obj dbus.BusObject, name string) (string, error) { @@ -156,7 +191,5 @@ func GetStatus(obj dbus.BusObject, name string) (string, error) { return "call store: ", err } - fmt.Println("status:", result) - return result, nil } diff --git a/internal/dbusservices/systemd/list.go b/internal/dbusservices/systemd/list.go index bbf46dd..3361b88 100644 --- a/internal/dbusservices/systemd/list.go +++ b/internal/dbusservices/systemd/list.go @@ -2,7 +2,7 @@ package servicecontrol import ( "fmt" - svctypes "paradigm-ehb/agent/internal/dbusservices/types" + types "paradigm-ehb/agent/internal/dbusservices/types" "github.com/godbus/dbus" ) @@ -14,10 +14,11 @@ import ( // files (templates) cannot directly be loaded as units but need to be instantiated. // --------------------------------------------------------------------------------------- // Method returns an array of all currently loaded units, -// -func GetLoadedUnits(obj dbus.BusObject, out chan []svctypes.LoadedUnit) { +func GetLoadedUnits( + obj dbus.BusObject, + out chan []types.LoadedUnit) { - var result []svctypes.LoadedUnit + var result []types.LoadedUnit call := obj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0) if call.Err != nil { @@ -25,7 +26,6 @@ func GetLoadedUnits(obj dbus.BusObject, out chan []svctypes.LoadedUnit) { return } - err := call.Store(&result) if err != nil { @@ -36,12 +36,14 @@ func GetLoadedUnits(obj dbus.BusObject, out chan []svctypes.LoadedUnit) { } -func GetAllUnits(obj dbus.BusObject, out chan []svctypes.UnitFileEntry) { +func GetUnits( + obj dbus.BusObject, + out chan []types.Unit) { // ListUnitFiles(out a(ss) files); // an array of struct string string - var result []svctypes.UnitFileEntry + var result []types.Unit call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0) @@ -54,5 +56,29 @@ func GetAllUnits(obj dbus.BusObject, out chan []svctypes.UnitFileEntry) { if err != nil { return } + + out <- result +} + +func GetUnitsFiltered( + obj dbus.BusObject, + out chan []types.LoadedUnit, + states []string) { + + var result []types.LoadedUnit + + call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states) + + if call.Err != nil { + fmt.Println("failed to call filtered list of units") + return + } + + err := call.Store(&result) + if err != nil { + return + } + out <- result + } diff --git a/internal/dbusservices/types/types.go b/internal/dbusservices/types/types.go index 4f01975..83c2df1 100644 --- a/internal/dbusservices/types/types.go +++ b/internal/dbusservices/types/types.go @@ -27,7 +27,7 @@ type Service struct { } // type a(ss) -type UnitFileEntry struct { +type Unit struct { Name string State string } From 826b9934f806fc4ad6bd5c63529434cc9c790004 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:42:41 +0100 Subject: [PATCH 09/59] feature: v2 resources grpc handler --- pkg/grpchandler/resources/v1/resources.go | 192 ++++++++++++++++ pkg/grpchandler/resources/v2/resources.go | 264 ++++++++++++++++++++++ 2 files changed, 456 insertions(+) create mode 100644 pkg/grpchandler/resources/v1/resources.go create mode 100644 pkg/grpchandler/resources/v2/resources.go diff --git a/pkg/grpchandler/resources/v1/resources.go b/pkg/grpchandler/resources/v1/resources.go new file mode 100644 index 0000000..c9f9e01 --- /dev/null +++ b/pkg/grpchandler/resources/v1/resources.go @@ -0,0 +1,192 @@ +package grpc_handler + +import ( + "context" + + proto "paradigm-ehb/agent/gen/resources/v1" + res "paradigm-ehb/agent/internal/resources" + wr "paradigm-ehb/agent/pkg/cgowrap" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +/** + * ResourcesService implements the gRPC ResourcesServiceServer. + * + * It exposes endpoints that provide a snapshot of system-level resources + * such as CPU, memory, disks, and running processes. All data collection + * is delegated to the internal resources package and then mapped to + * protobuf-defined response types. + */ +type ResourcesService struct { + proto.UnimplementedResourcesServiceServer +} + +/** + * GetSystemResources returns a full snapshot of system resources. + * + * The method: + * - Respects context cancellation to avoid unnecessary work + * - Collects system information via the internal resources layer + * - Maps internal domain structures to protobuf response messages + * + * Errors: + * - Returns codes.Canceled if the request context is canceled + * - Returns codes.Internal if resource collection fails + */ +func (s *ResourcesService) GetSystemResources( + ctx context.Context, + req *proto.GetSystemResourcesRequest, +) (*proto.GetSystemResourcesResponse, error) { + + select { + case <-ctx.Done(): + return nil, status.Error(codes.Canceled, "request canceled") + default: + } + + snap, err := res.GetCompleteSystemResources() + if err != nil { + return nil, status.Errorf( + codes.Internal, + "failed to collect system resources: %v", + err, + ) + } + + return &proto.GetSystemResourcesResponse{ + Resources: mapSystemResources(snap), + }, nil +} + +/** + * mapSystemResources converts an internal SystemResources snapshot + * into its protobuf representation. + * + * This acts as the top-level aggregation mapper, delegating + * to more specific mapping functions per subsystem. + */ +func mapSystemResources(s *res.SystemResources) *proto.SystemResources { + return &proto.SystemResources{ + Cpu: mapCPU(s.CPU), + Memory: mapMemory(s.Memory), + Device: mapDevice(s.Device), + Disks: mapDisks(s.Disks), + Processes: mapProcesses(s.Procs), + } +} + +/** + * mapCPU maps CPU metadata from the cgo wrapper type + * into the protobuf Cpu message. + */ +func mapCPU(c wr.Cpu) *proto.Cpu { + return &proto.Cpu{ + Vendor: c.Vendor, + Model: c.Model, + Frequency: c.Frequency, + MaxCore: c.MaxCore, + } +} + +/** + * mapMemory maps RAM usage information into the protobuf Memory message. + * + * Values are expected to be raw byte counts as reported by the system. + */ +func mapMemory(m wr.Ram) *proto.Memory { + return &proto.Memory{ + Total: m.Total, + Free: m.Free, + } +} + +/** + * mapDevice maps general device and OS-level metadata + * into the protobuf Device message. + */ +func mapDevice(d wr.Device) *proto.Device { + return &proto.Device{ + OsVersion: d.OsVersion, + Uptime: d.Uptime, + } +} + +/** + * mapDisks maps a slice of disk descriptors into protobuf Disk messages. + * + * Each disk contains a list of partitions, which are also converted + * field-by-field into their protobuf equivalents. + */ +func mapDisks(disks []wr.Disk) []*proto.Disk { + out := make([]*proto.Disk, 0, len(disks)) + + for _, d := range disks { + parts := make([]*proto.DiskPartition, 0, len(d.Partitions)) + for _, p := range d.Partitions { + parts = append(parts, &proto.DiskPartition{ + Name: p.Name, + Major: p.Major, + Minor: p.Minor, + Blocks: p.Blocks, + }) + } + + out = append(out, &proto.Disk{ + Partitions: parts, + }) + } + + return out +} + +/** + * mapProcesses maps a slice of process descriptors into protobuf Process messages. + * + * Each process includes basic scheduling and accounting information + * such as PID, state, CPU time, and thread count. + */ +func mapProcesses(ps []wr.Process) []*proto.Process { + out := make([]*proto.Process, 0, len(ps)) + + for _, p := range ps { + out = append(out, &proto.Process{ + Pid: uint32(p.PID), + Name: p.Name, + State: mapProcessState(p.State), + Utime: p.UTime, + NumThreads: p.NumThreads, + }) + } + + return out +} + +/** + * mapProcessState converts an internal ProcessState enum + * into the corresponding protobuf ProcessState value. + * + * Unknown or unmapped states are converted to PROCESS_STATE_UNSPECIFIED + * to preserve forward compatibility. + */ +func mapProcessState(s wr.ProcessState) proto.ProcessState { + switch s { + case wr.ProcessRunning: + return proto.ProcessState_PROCESS_STATE_RUNNING + case wr.ProcessSleeping: + return proto.ProcessState_PROCESS_STATE_SLEEPING + case wr.ProcessDiskSleep: + return proto.ProcessState_PROCESS_STATE_DISK_SLEEPING + case wr.ProcessStopped: + return proto.ProcessState_PROCESS_STATE_STOPPED + case wr.ProcessTracingStopped: + return proto.ProcessState_PROCESS_STATE_TRACING_STOPPED + case wr.ProcessZombie: + return proto.ProcessState_PROCESS_STATE_ZOMBIE + case wr.ProcessDead: + return proto.ProcessState_PROCESS_STATE_DEAD + default: + return proto.ProcessState_PROCESS_STATE_UNSPECIFIED + } +} diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go new file mode 100644 index 0000000..e385559 --- /dev/null +++ b/pkg/grpchandler/resources/v2/resources.go @@ -0,0 +1,264 @@ +package grpc_handler + +import ( + "context" + + proto "paradigm-ehb/agent/gen/resources/v2" + "paradigm-ehb/agent/internal/resources" + cgo "paradigm-ehb/agent/pkg/cgowrap" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +/** + * ResourcesServiceV2 implements the gRPC ResourcesServiceServer. + * + * It exposes endpoints that provide a snapshot of system-level resources + * such as CPU, memory, disks, and running processes. All data collection + * is delegated to the internal resources package and then mapped to + * protobuf-defined response types. + */ +type ResourcesServiceV2 struct { + proto.UnimplementedResourcesServiceServer +} + +/** + * GetSystemResources returns a full snapshot of system resources. + * + * The method: + * - Respects context cancellation to avoid unnecessary work + * - Collects system information via the internal resources layer + * - Maps internal domain structures to protobuf response messages + * + * Errors: + * - Returns codes.Canceled if the request context is canceled + * - Returns codes.Internal if resource collection fails + */ +func (s *ResourcesServiceV2) GetSystemResources( + ctx context.Context, + req *proto.GetSystemResourcesRequest, +) (*proto.GetSystemResourcesResponse, error) { + + select { + case <-ctx.Done(): + return nil, status.Error(codes.Canceled, "request canceled") + default: + } + + snap, err := resources.GetCompleteSystemResources() + if err != nil { + return nil, status.Errorf( + codes.Internal, + "failed to collect system resources: %v", + err, + ) + } + + return &proto.GetSystemResourcesResponse{ + Resources: mapSystemResources(snap), + }, nil +} + +/** +* TODO(nasr): update individual resources + */ + +func (s *ResourcesServiceV2) KillProcess( + ctx context.Context, + req *proto.KillProcessRequest, +) (*proto.KillProcessReply, error) { + + err := cgo.KillProcess(int(req.Pid)) + if err != nil { + + return &proto.KillProcessReply{ + Succes: "false", + }, nil + } + + return &proto.KillProcessReply{ + Succes: "true", + }, nil +} + +/** + * mapSystemResources converts an internal SystemResources snapshot + * into its protobuf representation. + * + * This acts as the top-level aggregation mapper, delegating + * to more specific mapping functions per subsystem. + */ +func mapSystemResources(s *resources.SystemResources) *proto.SystemResources { + + + return &proto.SystemResources{ + Cpu: mapCPU(s.CPU), + Memory: mapMemory(s.Memory), + Device: mapDevice(s.Device), + Disks: mapDisks(s.Disks), + Processes: mapProcesses(s.Procs), + } +} + +/** + * mapCPU maps CPU metadata from the cgo wrapper type + * into the protobuf Cpu message. + */ +func mapCPU(c cgo.Cpu) *proto.Cpu { + + return &proto.Cpu{ + Vendor: c.Vendor, + Model: c.Model, + Frequency: c.Frequency, + MaxCore: c.MaxCore, + } + +} + +/** + * mapMemory maps RAM usage information into the protobuf Memory message. + * + * Values are expected to be raw byte counts as reported by the system. + */ +func mapMemory(m cgo.Ram) *proto.Memory { + + return &proto.Memory{ + Total: m.Total, + Free: m.Free, + } + +} + +/** + * mapDevice maps general device and OS-level metadata + * into the protobuf Device message. + */ +func mapDevice(d cgo.Device) *proto.Device { + + return &proto.Device{ + OsVersion: d.OsVersion, + Uptime: d.Uptime, + } + +} + +/** + * mapDisks maps a slice of disk descriptors into protobuf Disk messages. + * + * Each disk contains a list of partitions, which are also converted + * field-by-field into their protobuf equivalents. + */ +func mapDisks(disks []cgo.Disk) []*proto.Disk { + + out := make([]*proto.Disk, 0, len(disks)) + + for _, d := range disks { + + parts := make([]*proto.DiskPartition, 0, len(d.Partitions)) + + for _, p := range d.Partitions { + + parts = append(parts, &proto.DiskPartition{ + + Name: p.Name, + Major: p.Major, + Minor: p.Minor, + Blocks: p.Blocks, + }) + } + + out = append(out, &proto.Disk{ + Partitions: parts, + }) + } + + return out +} + +/** + * mapProcessState converts an internal ProcessState enum + * into the corresponding protobuf ProcessState value. + * + * Unknown or unmapped states are converted to PROCESS_STATE_UNSPECIFIED + * to preserve forward compatibility. + */ +func mapProcessState(s cgo.ProcessState) proto.ProcessState { + + switch s { + + + case cgo.ProcessRunning: + { + + return proto.ProcessState_PROCESS_STATE_RUNNING + } + case cgo.ProcessSleeping: + { + + return proto.ProcessState_PROCESS_STATE_SLEEPING + } + case cgo.ProcessDiskSleep: + { + + return proto.ProcessState_PROCESS_STATE_DISK_SLEEPING + } + case cgo.ProcessStopped: + { + + return proto.ProcessState_PROCESS_STATE_STOPPED + } + case cgo.ProcessTracingStopped: + { + + return proto.ProcessState_PROCESS_STATE_TRACING_STOPPED + } + case cgo.ProcessZombie: + { + + return proto.ProcessState_PROCESS_STATE_ZOMBIE + } + case cgo.ProcessDead: + { + + return proto.ProcessState_PROCESS_STATE_DEAD + } + case cgo.ProcessIdle: + { + return proto.ProcessState_PROCESS_STATE_IDLE + } + + case cgo.ProcessUndefined: + { + return proto.ProcessState_PROCESS_STATE_UNSPECIFIED + + } + default: + + return proto.ProcessState_PROCESS_STATE_UNSPECIFIED + } +} + +/** + * mapProcesses maps a slice of process descriptors into protobuf Process messages. + * + * Each process includes basic scheduling and accounting information + * such as PID, state, CPU time, and thread count. + */ +func mapProcesses(ps []cgo.Process) []*proto.Process { + + out := make([]*proto.Process, 0, len(ps)) + + for _, p := range ps { + out = append(out, &proto.Process{ + Pid: int32(p.PID), + Name: p.Name, + State: mapProcessState(p.State), + Utime: p.UTime, + NumThreads: p.NumThreads, + }) + } + + + return out +} From f3f4a12af5be76fccc3c1de61819a70ca6bc3511 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:42:54 +0100 Subject: [PATCH 10/59] feature: v3 grpc handler --- pkg/grpchandler/services/v1/services.go | 101 ++++++++++ pkg/grpchandler/services/v2/services.go | 211 +++++++++++++++++++++ pkg/grpchandler/services/v3/services.go | 240 ++++++++++++++++++++++++ 3 files changed, 552 insertions(+) create mode 100644 pkg/grpchandler/services/v1/services.go create mode 100644 pkg/grpchandler/services/v2/services.go create mode 100644 pkg/grpchandler/services/v3/services.go diff --git a/pkg/grpchandler/services/v1/services.go b/pkg/grpchandler/services/v1/services.go new file mode 100644 index 0000000..719e0ed --- /dev/null +++ b/pkg/grpchandler/services/v1/services.go @@ -0,0 +1,101 @@ +package grpc_handler + +import ( + "context" + "log" + + manager "paradigm-ehb/agent/internal/dbusservices" + + "paradigm-ehb/agent/gen/services/v1" + dh "paradigm-ehb/agent/internal/dbusservices/dbus" + "paradigm-ehb/agent/internal/dbusservices/systemd" + + "github.com/godbus/dbus" +) + +type HandlerService struct { + v1.UnimplementedHandlerServiceServer +} + +func (s *HandlerService) UnitAction(_ context.Context, in *v1.ServiceActionRequest) (*v1.ServiceActionReply, error) { + + var out string + conn, err := dh.CreateSystemBus() + if err != nil { + + log.Println("failed to create systembus") + out = "failed system bus" + } + + defer func(conn *dbus.Conn) { + if err != nil { + + } + }(conn) + + if in.GetUnitFileAction() == v1.ServiceActionRequest_UNIT_FILE_ACTION_ENABLE { + + err = manager.RunSymlinkAction(conn, servicecontrol.UnitFileActionEnable, true, true, []string{in.ServiceName}) + if err != nil { + + log.Println("failed to enable service") + out = "failed unit file action" + } + + } else if in.GetUnitFileAction() == v1.ServiceActionRequest_UNIT_FILE_ACTION_DISABLE { + + err = manager.RunSymlinkAction(conn, servicecontrol.UnitFileActionDisable, true, true, []string{in.ServiceName}) + if err != nil { + + log.Println("failed to disable service") + out = "failed unit file action" + } + + } else { + + log.Println("Bad input") + out = "Bad input" + + } + + if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_START { + + err = manager.RunAction(conn, servicecontrol.UnitActionStart, in.ServiceName) + if err != nil { + + log.Println("failed to run action") + out = "failed unit action" + } + + } else if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_STOP { + + err = manager.RunAction(conn, servicecontrol.UnitActionStop, in.ServiceName) + if err != nil { + + log.Println("failed to run action") + out = "failed unit action" + } + + } else if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_RESTART { + + err = manager.RunAction(conn, servicecontrol.UnitActionRestart, in.ServiceName) + if err != nil { + + log.Println("failed to run action") + out = "failed unit action" + } + + } else { + + log.Println("external bad input") + out = "external bad input" + } + + _, err = manager.RunRetrieval(conn, true) + if err != nil { + log.Println("failed to do everything") + out = "failed even more" + } + + return &v1.ServiceActionReply{Status: out}, nil +} diff --git a/pkg/grpchandler/services/v2/services.go b/pkg/grpchandler/services/v2/services.go new file mode 100644 index 0000000..3ded666 --- /dev/null +++ b/pkg/grpchandler/services/v2/services.go @@ -0,0 +1,211 @@ +package grpc_handler + +import ( + "context" + "fmt" + + v2 "paradigm-ehb/agent/gen/services/v2" + manager "paradigm-ehb/agent/internal/dbusservices" + dh "paradigm-ehb/agent/internal/dbusservices/dbus" + servicecontrol "paradigm-ehb/agent/internal/dbusservices/systemd" + +) + +type HandlerServiceV2 struct { + v2.UnimplementedHandlerServiceServer +} + + +func (s *HandlerServiceV2) PerformUnitAction( + _ context.Context, + in *v2.UnitActionRequest, +) (*v2.UnitActionReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.UnitActionReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + var action servicecontrol.UnitAction + var actionName string + + switch in.Action { + case v2.UnitActionRequest_UNIT_ACTION_START: + action = servicecontrol.UnitActionStart + actionName = "start" + case v2.UnitActionRequest_UNIT_ACTION_STOP: + action = servicecontrol.UnitActionStop + actionName = "stop" + case v2.UnitActionRequest_UNIT_ACTION_RESTART: + action = servicecontrol.UnitActionRestart + actionName = "restart" + default: + return &v2.UnitActionReply{ + Success: false, + ErrorMessage: "unspecified unit action", + }, nil + } + + err = manager.RunAction(conn, action, in.UnitName) + if err != nil { + return &v2.UnitActionReply{ + Status: []byte(fmt.Sprintf("failed to %s unit", actionName)), + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.UnitActionReply{ + Status: []byte("success"), + Success: true, + }, nil +} + + +func (s *HandlerServiceV2) PerformUnitFileAction( + _ context.Context, + in *v2.UnitFileActionRequest, +) (*v2.UnitFileActionReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.UnitFileActionReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + var action servicecontrol.UnitFileAction + + switch in.Action { + case v2.UnitFileActionRequest_UNIT_FILE_ACTION_ENABLE: + action = servicecontrol.UnitFileActionEnable + case v2.UnitFileActionRequest_UNIT_FILE_ACTION_DISABLE: + action = servicecontrol.UnitFileActionDisable + default: + return &v2.UnitFileActionReply{ + Success: false, + ErrorMessage: "unspecified unit file action", + }, nil + } + + err = manager.RunSymlinkAction( + conn, + action, + in.Runtime, + in.Force, + []string{in.UnitName}, + ) + + if err != nil { + return &v2.UnitFileActionReply{ + Status: []byte("unit file action failed"), + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.UnitFileActionReply{ + Status: []byte("success"), + Success: true, + }, nil +} + + +func (s *HandlerServiceV2) GetAllUnits( + _ context.Context, + _ *v2.GetUnitsRequest, +) (*v2.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + + TODO(nasr): fix the design issue and return the correct mapped types + from the correct namespace + + */ + _, err = manager.RunRetrieval(conn, true) + if err != nil { + return &v2.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.GetUnitsReply{ + Units: nil, + Success: true, + }, nil +} + +func (s *HandlerServiceV2) GetLoadedUnits( + _ context.Context, + _ *v2.GetUnitsRequest, +) (*v2.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + /** + + TODO(nasr): fix the design issue and return the correct mapped types + from the correct namespace + + */ + _, err = manager.RunRetrieval(conn, false) + if err != nil { + return &v2.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.GetUnitsReply{ + Units: nil, + Success: true, + }, nil +} + + +func (s *HandlerServiceV2) GetUnitStatus( + _ context.Context, + in *v2.GetUnitStatusRequest, +) (*v2.GetUnitStatusReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + obj, _ := dh.CreateSystemdObject(conn) + + state, err := manager.GetStatus(obj, in.UnitName) + if err != nil { + return &v2.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.GetUnitStatusReply{ + State: state, + Success: true, + }, nil +} diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go new file mode 100644 index 0000000..ae67b55 --- /dev/null +++ b/pkg/grpchandler/services/v3/services.go @@ -0,0 +1,240 @@ +package grpc_handler + +import ( + "context" + "fmt" + + v3 "paradigm-ehb/agent/gen/services/v3" + manager "paradigm-ehb/agent/internal/dbusservices" + dh "paradigm-ehb/agent/internal/dbusservices/dbus" + servicecontrol "paradigm-ehb/agent/internal/dbusservices/systemd" +) + +type HandlerServicev3 struct { + v3.UnimplementedHandlerServiceServer +} + +func (s *HandlerServicev3) PerformUnitAction( + _ context.Context, + in *v3.UnitActionRequest, +) (*v3.UnitActionReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v3.UnitActionReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + var action servicecontrol.UnitAction + var actionName string + + switch in.Action { + case v3.UnitActionRequest_UNIT_ACTION_START: + action = servicecontrol.UnitActionStart + actionName = "start" + case v3.UnitActionRequest_UNIT_ACTION_STOP: + action = servicecontrol.UnitActionStop + actionName = "stop" + case v3.UnitActionRequest_UNIT_ACTION_RESTART: + action = servicecontrol.UnitActionRestart + actionName = "restart" + default: + return &v3.UnitActionReply{ + Success: false, + ErrorMessage: "unspecified unit action", + }, nil + } + + err = manager.RunAction(conn, action, in.UnitName) + if err != nil { + return &v3.UnitActionReply{ + Status: []byte(fmt.Sprintf("failed to %s unit", actionName)), + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v3.UnitActionReply{ + Status: []byte("success"), + Success: true, + }, nil +} + +func (s *HandlerServicev3) PerformUnitFileAction( + _ context.Context, + in *v3.UnitFileActionRequest, +) (*v3.UnitFileActionReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v3.UnitFileActionReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + var action servicecontrol.UnitFileAction + + switch in.Action { + case v3.UnitFileActionRequest_UNIT_FILE_ACTION_ENABLE: + action = servicecontrol.UnitFileActionEnable + case v3.UnitFileActionRequest_UNIT_FILE_ACTION_DISABLE: + action = servicecontrol.UnitFileActionDisable + default: + return &v3.UnitFileActionReply{ + Success: false, + ErrorMessage: "unspecified unit file action", + }, nil + } + + err = manager.RunSymlinkAction( + conn, + action, + in.Runtime, + in.Force, + []string{in.UnitName}, + ) + + if err != nil { + return &v3.UnitFileActionReply{ + Status: []byte("unit file action failed"), + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v3.UnitFileActionReply{ + Status: []byte("success"), + Success: true, + }, nil +} + +func (s *HandlerServicev3) GetAllUnits( + _ context.Context, + _ *v3.GetUnitsRequest, +) (*v3.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + + Handle error hanlding + */ + _, err = manager.RunRetrieval(conn, true) + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + // mappedUnits := make(*v3.GetUnitsReply, 0, len(units)) + // mappedUnits = &units{} + + return &v3.GetUnitsReply{ + Units: nil, + Success: true, + }, nil +} + +func (s *HandlerServicev3) GetLoadedUnits( + _ context.Context, + _ *v3.GetUnitsRequest, +) (*v3.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + TODO(nasr): handle error + */ + _, err = manager.RunRetrieval(conn, false) + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v3.GetUnitsReply{ + Units: nil, + Success: true, + }, nil +} + +func (s *HandlerServicev3) GetUnitStatus( + _ context.Context, + in *v3.GetUnitStatusRequest, +) (*v3.GetUnitStatusReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v3.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + TODO(nasr): todo handle error + */ + obj, _ := dh.CreateSystemdObject(conn) + + state, err := manager.GetStatus(obj, in.UnitName) + if err != nil { + return &v3.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v3.GetUnitStatusReply{ + State: state, + Success: true, + }, nil +} + +func (s *HandlerServicev3) GetFilteredUnits( + _ context.Context, + in *v3.GetUnitsFilteredRequest, +) (*v3.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + + TODO(nasr): handle the correct types + + */ + _, err = manager.MapFilteredUnits(conn) + if err != nil { + + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, fmt.Errorf("failed to map filtered units %v: ", err) + } + + return &v3.GetUnitsReply{}, nil +} From f1b5c6bb135654f153fc4bf2037d55a214616491 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:43:05 +0100 Subject: [PATCH 11/59] refacor: folder restructure --- pkg/grpc_handler/resources.go | 192 ----------------- pkg/grpc_handler/services.go | 101 --------- pkg/grpc_handler/services_v2.go | 200 ------------------ .../deviceactions.go | 9 +- pkg/{grpc_handler => grpchandler}/greeter.go | 0 pkg/{grpc_handler => grpchandler}/journal.go | 0 6 files changed, 2 insertions(+), 500 deletions(-) delete mode 100644 pkg/grpc_handler/resources.go delete mode 100644 pkg/grpc_handler/services.go delete mode 100644 pkg/grpc_handler/services_v2.go rename pkg/{grpc_handler => grpchandler}/deviceactions.go (91%) rename pkg/{grpc_handler => grpchandler}/greeter.go (100%) rename pkg/{grpc_handler => grpchandler}/journal.go (100%) diff --git a/pkg/grpc_handler/resources.go b/pkg/grpc_handler/resources.go deleted file mode 100644 index 25f2117..0000000 --- a/pkg/grpc_handler/resources.go +++ /dev/null @@ -1,192 +0,0 @@ -package grpc_handler - -import ( - "context" - - proto "paradigm-ehb/agent/gen/resources/v1" - res "paradigm-ehb/agent/internal/resources" - wr "paradigm-ehb/agent/pkg/cgowrap" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -/** - * ResourcesService implements the gRPC ResourcesServiceServer. - * - * It exposes endpoints that provide a snapshot of system-level resources - * such as CPU, memory, disks, and running processes. All data collection - * is delegated to the internal resources package and then mapped to - * protobuf-defined response types. - */ -type ResourcesService struct { - proto.UnimplementedResourcesServiceServer -} - -/** - * GetSystemResources returns a full snapshot of system resources. - * - * The method: - * - Respects context cancellation to avoid unnecessary work - * - Collects system information via the internal resources layer - * - Maps internal domain structures to protobuf response messages - * - * Errors: - * - Returns codes.Canceled if the request context is canceled - * - Returns codes.Internal if resource collection fails - */ -func (s *ResourcesService) GetSystemResources( - ctx context.Context, - req *proto.GetSystemResourcesRequest, -) (*proto.GetSystemResourcesResponse, error) { - - select { - case <-ctx.Done(): - return nil, status.Error(codes.Canceled, "request canceled") - default: - } - - snap, err := res.GetCompleteSystemResources() - if err != nil { - return nil, status.Errorf( - codes.Internal, - "failed to collect system resources: %v", - err, - ) - } - - return &proto.GetSystemResourcesResponse{ - Resources: mapSystemResources(snap), - }, nil -} - -/** - * mapSystemResources converts an internal SystemResources snapshot - * into its protobuf representation. - * - * This acts as the top-level aggregation mapper, delegating - * to more specific mapping functions per subsystem. - */ -func mapSystemResources(s *res.SystemResources) *proto.SystemResources { - return &proto.SystemResources{ - Cpu: mapCPU(s.CPU), - Memory: mapMemory(s.Memory), - Device: mapDevice(s.Device), - Disks: mapDisks(s.Disks), - Processes: mapProcesses(s.Procs), - } -} - -/** - * mapCPU maps CPU metadata from the cgo wrapper type - * into the protobuf Cpu message. - */ -func mapCPU(c wr.Cpu) *proto.Cpu { - return &proto.Cpu{ - Vendor: c.Vendor, - Model: c.Model, - Frequency: c.Frequency, - MaxCore: c.MaxCore, - } -} - -/** - * mapMemory maps RAM usage information into the protobuf Memory message. - * - * Values are expected to be raw byte counts as reported by the system. - */ -func mapMemory(m wr.Ram) *proto.Memory { - return &proto.Memory{ - Total: m.Total, - Free: m.Free, - } -} - -/** - * mapDevice maps general device and OS-level metadata - * into the protobuf Device message. - */ -func mapDevice(d wr.Device) *proto.Device { - return &proto.Device{ - OsVersion: d.OsVersion, - Uptime: d.Uptime, - } -} - -/** - * mapDisks maps a slice of disk descriptors into protobuf Disk messages. - * - * Each disk contains a list of partitions, which are also converted - * field-by-field into their protobuf equivalents. - */ -func mapDisks(disks []wr.Disk) []*proto.Disk { - out := make([]*proto.Disk, 0, len(disks)) - - for _, d := range disks { - parts := make([]*proto.DiskPartition, 0, len(d.Partitions)) - for _, p := range d.Partitions { - parts = append(parts, &proto.DiskPartition{ - Name: p.Name, - Major: p.Major, - Minor: p.Minor, - Blocks: p.Blocks, - }) - } - - out = append(out, &proto.Disk{ - Partitions: parts, - }) - } - - return out -} - -/** - * mapProcesses maps a slice of process descriptors into protobuf Process messages. - * - * Each process includes basic scheduling and accounting information - * such as PID, state, CPU time, and thread count. - */ -func mapProcesses(ps []wr.Process) []*proto.Process { - out := make([]*proto.Process, 0, len(ps)) - - for _, p := range ps { - out = append(out, &proto.Process{ - Pid: p.PID, - Name: p.Name, - State: mapProcessState(p.State), - Utime: p.UTime, - NumThreads: p.NumThreads, - }) - } - - return out -} - -/** - * mapProcessState converts an internal ProcessState enum - * into the corresponding protobuf ProcessState value. - * - * Unknown or unmapped states are converted to PROCESS_STATE_UNSPECIFIED - * to preserve forward compatibility. - */ -func mapProcessState(s wr.ProcessState) proto.ProcessState { - switch s { - case wr.ProcessRunning: - return proto.ProcessState_PROCESS_STATE_RUNNING - case wr.ProcessSleeping: - return proto.ProcessState_PROCESS_STATE_SLEEPING - case wr.ProcessDiskSleep: - return proto.ProcessState_PROCESS_STATE_DISK_SLEEPING - case wr.ProcessStopped: - return proto.ProcessState_PROCESS_STATE_STOPPED - case wr.ProcessTracingStopped: - return proto.ProcessState_PROCESS_STATE_TRACING_STOPPED - case wr.ProcessZombie: - return proto.ProcessState_PROCESS_STATE_ZOMBIE - case wr.ProcessDead: - return proto.ProcessState_PROCESS_STATE_DEAD - default: - return proto.ProcessState_PROCESS_STATE_UNSPECIFIED - } -} diff --git a/pkg/grpc_handler/services.go b/pkg/grpc_handler/services.go deleted file mode 100644 index 719e0ed..0000000 --- a/pkg/grpc_handler/services.go +++ /dev/null @@ -1,101 +0,0 @@ -package grpc_handler - -import ( - "context" - "log" - - manager "paradigm-ehb/agent/internal/dbusservices" - - "paradigm-ehb/agent/gen/services/v1" - dh "paradigm-ehb/agent/internal/dbusservices/dbus" - "paradigm-ehb/agent/internal/dbusservices/systemd" - - "github.com/godbus/dbus" -) - -type HandlerService struct { - v1.UnimplementedHandlerServiceServer -} - -func (s *HandlerService) UnitAction(_ context.Context, in *v1.ServiceActionRequest) (*v1.ServiceActionReply, error) { - - var out string - conn, err := dh.CreateSystemBus() - if err != nil { - - log.Println("failed to create systembus") - out = "failed system bus" - } - - defer func(conn *dbus.Conn) { - if err != nil { - - } - }(conn) - - if in.GetUnitFileAction() == v1.ServiceActionRequest_UNIT_FILE_ACTION_ENABLE { - - err = manager.RunSymlinkAction(conn, servicecontrol.UnitFileActionEnable, true, true, []string{in.ServiceName}) - if err != nil { - - log.Println("failed to enable service") - out = "failed unit file action" - } - - } else if in.GetUnitFileAction() == v1.ServiceActionRequest_UNIT_FILE_ACTION_DISABLE { - - err = manager.RunSymlinkAction(conn, servicecontrol.UnitFileActionDisable, true, true, []string{in.ServiceName}) - if err != nil { - - log.Println("failed to disable service") - out = "failed unit file action" - } - - } else { - - log.Println("Bad input") - out = "Bad input" - - } - - if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_START { - - err = manager.RunAction(conn, servicecontrol.UnitActionStart, in.ServiceName) - if err != nil { - - log.Println("failed to run action") - out = "failed unit action" - } - - } else if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_STOP { - - err = manager.RunAction(conn, servicecontrol.UnitActionStop, in.ServiceName) - if err != nil { - - log.Println("failed to run action") - out = "failed unit action" - } - - } else if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_RESTART { - - err = manager.RunAction(conn, servicecontrol.UnitActionRestart, in.ServiceName) - if err != nil { - - log.Println("failed to run action") - out = "failed unit action" - } - - } else { - - log.Println("external bad input") - out = "external bad input" - } - - _, err = manager.RunRetrieval(conn, true) - if err != nil { - log.Println("failed to do everything") - out = "failed even more" - } - - return &v1.ServiceActionReply{Status: out}, nil -} diff --git a/pkg/grpc_handler/services_v2.go b/pkg/grpc_handler/services_v2.go deleted file mode 100644 index a7549a7..0000000 --- a/pkg/grpc_handler/services_v2.go +++ /dev/null @@ -1,200 +0,0 @@ -package grpc_handler - -import ( - "context" - "fmt" - - v2 "paradigm-ehb/agent/gen/services/v2" - manager "paradigm-ehb/agent/internal/dbusservices" - dh "paradigm-ehb/agent/internal/dbusservices/dbus" - servicecontrol "paradigm-ehb/agent/internal/dbusservices/systemd" - -) - -type HandlerServiceV2 struct { - v2.UnimplementedHandlerServiceServer -} - - -func (s *HandlerServiceV2) PerformUnitAction( - _ context.Context, - in *v2.UnitActionRequest, -) (*v2.UnitActionReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.UnitActionReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - var action servicecontrol.UnitAction - var actionName string - - switch in.Action { - case v2.UnitActionRequest_UNIT_ACTION_START: - action = servicecontrol.UnitActionStart - actionName = "start" - case v2.UnitActionRequest_UNIT_ACTION_STOP: - action = servicecontrol.UnitActionStop - actionName = "stop" - case v2.UnitActionRequest_UNIT_ACTION_RESTART: - action = servicecontrol.UnitActionRestart - actionName = "restart" - default: - return &v2.UnitActionReply{ - Success: false, - ErrorMessage: "unspecified unit action", - }, nil - } - - err = manager.RunAction(conn, action, in.UnitName) - if err != nil { - return &v2.UnitActionReply{ - Status: []byte(fmt.Sprintf("failed to %s unit", actionName)), - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.UnitActionReply{ - Status: []byte("success"), - Success: true, - }, nil -} - - -func (s *HandlerServiceV2) PerformUnitFileAction( - _ context.Context, - in *v2.UnitFileActionRequest, -) (*v2.UnitFileActionReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.UnitFileActionReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - var action servicecontrol.UnitFileAction - - switch in.Action { - case v2.UnitFileActionRequest_UNIT_FILE_ACTION_ENABLE: - action = servicecontrol.UnitFileActionEnable - case v2.UnitFileActionRequest_UNIT_FILE_ACTION_DISABLE: - action = servicecontrol.UnitFileActionDisable - default: - return &v2.UnitFileActionReply{ - Success: false, - ErrorMessage: "unspecified unit file action", - }, nil - } - - err = manager.RunSymlinkAction( - conn, - action, - in.Runtime, - in.Force, - []string{in.UnitName}, - ) - - if err != nil { - return &v2.UnitFileActionReply{ - Status: []byte("unit file action failed"), - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.UnitFileActionReply{ - Status: []byte("success"), - Success: true, - }, nil -} - - -func (s *HandlerServiceV2) GetAllUnits( - _ context.Context, - _ *v2.GetUnitsRequest, -) (*v2.GetUnitsReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - units, err := manager.RunRetrieval(conn, true) - if err != nil { - return &v2.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.GetUnitsReply{ - Units: units, - Success: true, - }, nil -} - -func (s *HandlerServiceV2) GetLoadedUnits( - _ context.Context, - _ *v2.GetUnitsRequest, -) (*v2.GetUnitsReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - units, err := manager.RunRetrieval(conn, false) - if err != nil { - return &v2.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.GetUnitsReply{ - Units: units, - Success: true, - }, nil -} - - -func (s *HandlerServiceV2) GetUnitStatus( - _ context.Context, - in *v2.GetUnitStatusRequest, -) (*v2.GetUnitStatusReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.GetUnitStatusReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - obj := dh.CreateSystemdObject(conn) - - state, err := manager.GetStatus(obj, in.UnitName) - if err != nil { - return &v2.GetUnitStatusReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.GetUnitStatusReply{ - State: state, - Success: true, - }, nil -} diff --git a/pkg/grpc_handler/deviceactions.go b/pkg/grpchandler/deviceactions.go similarity index 91% rename from pkg/grpc_handler/deviceactions.go rename to pkg/grpchandler/deviceactions.go index baf9b58..334b2eb 100644 --- a/pkg/grpc_handler/deviceactions.go +++ b/pkg/grpchandler/deviceactions.go @@ -25,18 +25,13 @@ func (s *DeviceActionsService) Action(ctx context.Context, req *actions.ActionRe } var out string - bus, err := dh.CreateSystemBus() + conn, err := dh.CreateSystemBus() if err != nil { log.Println("failed to create systembus") out = "failed system bus" } - - obj := bus.Object( - "org.freedesktop.login1", - "/org/freedesktop/login1", - ) - + obj, _ := dh.CreateLoginObject(conn) switch req.GetDeviceAction() { case actions.DeviceAction_DEVICE_ACTION_SHUTDOWN: out = string(da.PerformDeviceAction(obj, da.DeviceActionShutdown)) diff --git a/pkg/grpc_handler/greeter.go b/pkg/grpchandler/greeter.go similarity index 100% rename from pkg/grpc_handler/greeter.go rename to pkg/grpchandler/greeter.go diff --git a/pkg/grpc_handler/journal.go b/pkg/grpchandler/journal.go similarity index 100% rename from pkg/grpc_handler/journal.go rename to pkg/grpchandler/journal.go From d9de1dc1bddb62203af7306f5c3727828b8cb43d Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:43:17 +0100 Subject: [PATCH 12/59] debug: checkpoint fixing the process status --- pkg/cgowrap/types.go | 44 ---------------- pkg/cgowrap/wrapper.go | 113 ++++++++++++++++++++++++++++++++++------- 2 files changed, 95 insertions(+), 62 deletions(-) diff --git a/pkg/cgowrap/types.go b/pkg/cgowrap/types.go index 0b6a8e2..1d42440 100644 --- a/pkg/cgowrap/types.go +++ b/pkg/cgowrap/types.go @@ -33,48 +33,4 @@ type Device struct { Uptime string } -// ProcessState represents the state of a process. -type ProcessState uint32 -const ( - ProcessUndefined ProcessState = 0 - ProcessRunning ProcessState = 1 - ProcessSleeping ProcessState = 2 - ProcessDiskSleep ProcessState = 3 - ProcessStopped ProcessState = 4 - ProcessTracingStopped ProcessState = 5 - ProcessZombie ProcessState = 6 - ProcessDead ProcessState = 7 -) - -// String returns a human-readable representation of the process state. -func (ps ProcessState) String() string { - switch ps { - case ProcessRunning: - return "Running" - case ProcessSleeping: - return "Sleeping" - case ProcessDiskSleep: - return "Disk Sleep" - case ProcessStopped: - return "Stopped" - case ProcessTracingStopped: - return "Tracing Stopped" - case ProcessZombie: - return "Zombie" - case ProcessDead: - return "Dead" - default: - return "Undefined" - } -} - -// Process represents a single process with its attributes. -type Process struct { - PID uint32 - Name string - State ProcessState - UTime uint64 - STime uint64 - NumThreads uint32 -} diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index 4283da8..49b95a2 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -13,6 +13,56 @@ import ( "golang.org/x/sys/unix" ) +// ProcessState represents the state of a process. + +type ProcessState C.int32_t + +const ( + ProcessUndefined ProcessState = C.PROCESS_UNDEFINED + ProcessRunning ProcessState = C.PROCESS_RUNNING + ProcessSleeping ProcessState = C.PROCESS_SLEEPING + ProcessDiskSleep ProcessState = C.PROCESS_DISK_SLEEP + ProcessStopped ProcessState = C.PROCESS_STOPPED + ProcessTracingStopped ProcessState = C.PROCESS_TRACING_STOPPED + ProcessZombie ProcessState = C.PROCESS_ZOMBIE + ProcessDead ProcessState = C.PROCESS_DEAD + ProcessIdle ProcessState = C.PROCESS_IDLE +) + +// Process represents a single process with its attributes. +type Process struct { + PID int32 + State ProcessState + UTime uint64 + STime uint64 + NumThreads uint32 + Name string +} + +func (s ProcessState) String() string { + + switch s { + case ProcessRunning: + return "Running" + case ProcessSleeping: + return "Sleeping" + case ProcessDiskSleep: + return "Disk Sleep" + case ProcessStopped: + return "Stopped" + case ProcessTracingStopped: + return "Tracing Stopped" + case ProcessZombie: + return "Zombie" + case ProcessDead: + return "Dead" + case ProcessIdle: + return "Idle" + default: + return "Undefined" + } +} + // TODO(nasr): research this, interesting, alias vs true aliasing type Arena = C.mem_arena @@ -59,6 +109,14 @@ func AllocateArena(size uint64) (*C.mem_arena, error) { return arena, nil } +func PushArena(arena *C.mem_arena, size uint64) { + + if arena != nil { + C.arena_push(arena, C.ulong(size), 1) + } + +} + /* * DestroyArena unmaps and destroys the memory arena. @@ -334,37 +392,55 @@ Returns: - error: Error if device pointer is nil */ func ReadProcesses(device *C.Device) ([]Process, error) { + if device == nil { - return nil, fmt.Errorf("nil Device pointer") + return nil, fmt.Errorf("dvice null pointer") } - procs := make([]Process, 0, device.processes.count) + count := int(device.processes.count) items := device.processes.items - for i := C.size_t(0); i < device.processes.count; i++ { - p := (*C.Process)( - unsafe.Pointer( - uintptr(unsafe.Pointer(items)) + - uintptr(i)*unsafe.Sizeof(*items), - ), - ) + // slice := unsafe.Slice(items, count) - /** - Read detailed process information - Skip processes that can't be read - */ - if C.process_read(p.pid, p) != C.OK { - continue + procs := make([]Process, 0, count) + + // p := unsafe.Pointer(&items[i]) + + // p := (*C.Process)(unsafe.Pointer(items), uintptr(i)*unsafe.Sizeof(*items)) + + for i := 0; i < count; i++ { + // p := &slice[i] + + p := (*C.Process)(unsafe.Pointer(uintptr(unsafe.Pointer(items)) + uintptr(i)*unsafe.Sizeof(*items))) + + + err := C.process_read(p.pid, p) + if err != C.OK { + fmt.Errorf("failed reading processes") + } + + if p.state == C.PROCESS_UNDEFINED { + + C.process_read(p.pid, p) } + + fmt.Println(unsafe.Sizeof(C.Process{})) + + + fmt.Println("pid offset ", unsafe.Offsetof(p.pid)) + fmt.Println("state offset", unsafe.Offsetof(p.state)) + fmt.Println("utime offset", unsafe.Offsetof(p.utime)) + procs = append(procs, Process{ - PID: uint32(p.pid), - Name: C.GoString(&p.name[0]), - State: ProcessState(p.state), + PID: int32(p.pid), + State: ProcessState(p.state), //BREAKPOINT UTime: uint64(p.utime), STime: uint64(p.stime), NumThreads: uint32(p.num_threads), + Name: C.GoString(&p.name[0]), }) + } return procs, nil @@ -381,6 +457,7 @@ Returns: - error: Error if the process cannot be killed or doesn't exist */ func KillProcess(pid int) error { + if pid <= 0 { return fmt.Errorf("invalid PID: %d", pid) } From 94e33dfafbd8ceb93283fe3fc719c49193974072 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:43:25 +0100 Subject: [PATCH 13/59] feature: updated entry poitns for v2 and v3 --- cmd/agent/agent.go | 61 ++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 0ee0202..8e7bd6a 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -22,9 +22,12 @@ import ( devacpb "paradigm-ehb/agent/gen/actions/v1" "paradigm-ehb/agent/gen/greet" "paradigm-ehb/agent/gen/journal/v1" - "paradigm-ehb/agent/gen/resources/v1" - services_v1 "paradigm-ehb/agent/gen/services/v1" - services_v2 "paradigm-ehb/agent/gen/services/v2" + + servicesV1 "paradigm-ehb/agent/gen/services/v1" + servicesV2 "paradigm-ehb/agent/gen/services/v2" + + resourcesv1 "paradigm-ehb/agent/gen/resources/v1" + resourcesv2 "paradigm-ehb/agent/gen/resources/v2" "google.golang.org/grpc" "google.golang.org/grpc/health" @@ -33,7 +36,14 @@ import ( "paradigm-ehb/agent/internal/platform" - "paradigm-ehb/agent/pkg/grpc_handler" + "paradigm-ehb/agent/pkg/grpchandler" + + resourcesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/resources/v1" + resourcesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/resources/v2" + + servicesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/services/v1" + servicesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/services/v2" + "syscall" "time" ) @@ -132,29 +142,33 @@ func main() { */ - greet.RegisterGreeterServer(server, &grpc_handler.GreeterServer{}) - services_v1.RegisterHandlerServiceServer(server, &grpc_handler.HandlerService{}) - services_v2.RegisterHandlerServiceServer(server, &grpc_handler.HandlerServiceV2{}) - journal.RegisterJournalServiceServer(server, &grpc_handler.JournalService{}) - resourcespb.RegisterResourcesServiceServer(server, &grpc_handler.ResourcesService{}) - devacpb.RegisterActionServiceServer(server, &grpc_handler.DeviceActionsService{}) + greet.RegisterGreeterServer( + server, + &grpc_handler.GreeterServer{}) - /* - Diagnostics mode (disabled for now) + servicesV1.RegisterHandlerServiceServer( + server, + &servicesHandlerV1.HandlerService{}) - Problem: - - Running diagnostics synchronously introduces an infinite loop - that blocks gRPC reflection and request handling. + servicesV2.RegisterHandlerServiceServer( + server, + &servicesHandlerV2.HandlerServiceV2{}) - Important invariant: - - Diagnostics must run asynchronously and must never interfere - with server startup, reflection, or request processing. + journal.RegisterJournalServiceServer( + server, + &grpc_handler.JournalService{}) - TODO(nasr): - - Move diagnostics into a separate goroutine - - Introduce a proper shutdown context - - Ensure diagnostics respect server lifecycle - */ + resourcesv1.RegisterResourcesServiceServer( + server, + &resourcesHandlerV1.ResourcesService{}) + + resourcesv2.RegisterResourcesServiceServer( + server, + &resourcesHandlerV2.ResourcesServiceV2{}) + + devacpb.RegisterActionServiceServer( + server, + &grpc_handler.DeviceActionsService{}) /** Enable gRPC reflection unconditionally. @@ -171,7 +185,6 @@ func main() { } - /** Start serving requests. This call blocks until the server is stopped or encounters a fatal error. From 99ea9adb14f95ca90126459d86f7a941eabd46a1 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:44:34 +0100 Subject: [PATCH 14/59] checkpoint --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 5dc30a0..404f56e 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 5dc30a052160c8234393641db3521b59e26f4fb9 +Subproject commit 404f56e54c5e3373f529f0f5b59d6fed2fa241d8 From fa7c69777ba363152fb0337dd84b014136a0148d Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 13:35:44 +0100 Subject: [PATCH 15/59] feature: updated process kill from string to boolean --- pkg/grpchandler/resources/v2/resources.go | 4 ++-- proto/resources/v2/deviceresources.proto | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go index e385559..51dc7a8 100644 --- a/pkg/grpchandler/resources/v2/resources.go +++ b/pkg/grpchandler/resources/v2/resources.go @@ -73,12 +73,12 @@ func (s *ResourcesServiceV2) KillProcess( if err != nil { return &proto.KillProcessReply{ - Succes: "false", + Succes: false, }, nil } return &proto.KillProcessReply{ - Succes: "true", + Succes: true, }, nil } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 55beeec..7404d50 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -114,5 +114,5 @@ message KillProcessRequest { message KillProcessReply { - string succes = 1; + bool succes = 1; } From d1d454c234ed69cc9f6a993e372128950e6be7f2 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 13:41:11 +0100 Subject: [PATCH 16/59] bugfix: fixed process state --- pkg/agent-resources | 2 +- pkg/cgowrap/wrapper.go | 166 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 154 insertions(+), 14 deletions(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 404f56e..4358ac6 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 404f56e54c5e3373f529f0f5b59d6fed2fa241d8 +Subproject commit 4358ac69903d0897f09b19f87b78e2aa1cc1e0b7 diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index 49b95a2..b4c14ea 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -3,7 +3,138 @@ package wrapper /* #cgo CFLAGS: -I${SRCDIR}/../agent-resources #cgo LDFLAGS: -L${SRCDIR}/../agent-resources/build -lagent_resources + +#include +#include +#include #include "resources.h" + +int +process_read2(i32 pid, Process *out) +{ + char path[PATH_MAX_LEN]; + snprintf(path, sizeof(path), "/proc/%d/status", pid); + + FILE *fp = fopen(path, "r"); + if (!fp) + { + return ERR_IO; + } + + char buf[BUFFER_SIZE_LARGE]; + + out->pid = pid; + + while (fgets( + buf, + sizeof(buf), + fp)) + { + char *colon = strchr(buf, ':'); + if (!colon) + { + continue; + } + + char *val = colon + 1; + while (*val == ' ' || *val == '\t') + { + ++val; + } + + size_t len = strcspn(val, "\n"); + + printf("lenght: %lu", len); + + if (!strncmp(buf, "Name:", 5)) + { + + memcpy(out->name, val, len); + printf("\nout name %s\n", out->name); + } + printf("name: %s", out->name); + if (!strncmp(buf, "State:", 6)) + { + char state_char = 0; + for (char *p = val; *p; ++p) + { + if (*p >= 'A' && *p <= 'Z' || *p == 't') + { + state_char = *p; + break; + } + } + + + printf("\nstate char: %d\n", out->pid); + printf("\nstate char: %c\n", state_char); + switch (state_char) + { + case 'R': + { + out->state = PROCESS_RUNNING; + break; + } + case 'S': + { + out->state = PROCESS_SLEEPING; + break; + } + case 'D': + { + out->state = PROCESS_DISK_SLEEP; + break; + } + case 'T': + { + out->state = PROCESS_STOPPED; + break; + } + case 't': + { + out->state = PROCESS_TRACING_STOPPED; + break; + } + case 'Z': + { + out->state = PROCESS_ZOMBIE; + break; + } + case 'X': + { + out->state = PROCESS_DEAD; + break; + } + case 'I': + { + out->state = PROCESS_IDLE; + break; + } + default: + { + out->state = PROCESS_UNDEFINED; + break; + } + } + } + + if (!strncmp(buf, "Threads:", 8)) + { + out->num_threads = (u32)strtoul(val, 0, 10); + } + } + + printf("sizeof(Process) = %zu\n", sizeof(Process)); + + int error = fclose(fp); + if (error != 0) + { + return ERR_IO; + } + + printf("\n\n\n\nout state: %d\n", out->state); + return OK; +} */ import "C" import ( @@ -18,15 +149,15 @@ import ( type ProcessState C.int32_t const ( - ProcessUndefined ProcessState = C.PROCESS_UNDEFINED - ProcessRunning ProcessState = C.PROCESS_RUNNING - ProcessSleeping ProcessState = C.PROCESS_SLEEPING - ProcessDiskSleep ProcessState = C.PROCESS_DISK_SLEEP - ProcessStopped ProcessState = C.PROCESS_STOPPED - ProcessTracingStopped ProcessState = C.PROCESS_TRACING_STOPPED - ProcessZombie ProcessState = C.PROCESS_ZOMBIE - ProcessDead ProcessState = C.PROCESS_DEAD - ProcessIdle ProcessState = C.PROCESS_IDLE + ProcessUndefined ProcessState = ProcessState(C.PROCESS_UNDEFINED) + ProcessRunning ProcessState = ProcessState(C.PROCESS_RUNNING) + ProcessSleeping ProcessState = ProcessState(C.PROCESS_SLEEPING) + ProcessDiskSleep ProcessState = ProcessState(C.PROCESS_DISK_SLEEP) + ProcessStopped ProcessState = ProcessState(C.PROCESS_STOPPED) + ProcessTracingStopped ProcessState = ProcessState(C.PROCESS_TRACING_STOPPED) + ProcessZombie ProcessState = ProcessState(C.PROCESS_ZOMBIE) + ProcessDead ProcessState = ProcessState(C.PROCESS_DEAD) + ProcessIdle ProcessState = ProcessState(C.PROCESS_IDLE) ) // Process represents a single process with its attributes. @@ -393,6 +524,7 @@ Returns: */ func ReadProcesses(device *C.Device) ([]Process, error) { + if device == nil { return nil, fmt.Errorf("dvice null pointer") } @@ -411,20 +543,28 @@ func ReadProcesses(device *C.Device) ([]Process, error) { for i := 0; i < count; i++ { // p := &slice[i] + p := (*C.Process)(unsafe.Pointer(uintptr(unsafe.Pointer(items)) + uintptr(i)*unsafe.Sizeof(*items))) - err := C.process_read(p.pid, p) + err := C.process_read2(p.pid, p) if err != C.OK { fmt.Errorf("failed reading processes") } - if p.state == C.PROCESS_UNDEFINED { + fmt.Println("Reading processes...", device.processes) - C.process_read(p.pid, p) - } + fmt.Println("pid:",p.pid) + fmt.Println("state:", p.state) + + // if p.state == C.PROCESS_UNDEFINED { + // + // C.process_read(p.pid, p) + // } + // + fmt.Println("\t\t\t\tstate: ", C.GoString(&p.name[0])) fmt.Println(unsafe.Sizeof(C.Process{})) From 69bd5d83eaa7fed1bae4188c5fc99586fde484db Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 13:54:18 +0100 Subject: [PATCH 17/59] test: studyreflect --- pkg/grpchandler/services/v3/services.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index ae67b55..1814d57 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -225,6 +225,7 @@ func (s *HandlerServicev3) GetFilteredUnits( /** TODO(nasr): handle the correct types + doing now */ _, err = manager.MapFilteredUnits(conn) From b3f946f6ca19dddcdac0d589c7bed5b2373c5d97 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 15:11:02 +0100 Subject: [PATCH 18/59] feature: wrapper cleanup + process signal handling --- pkg/agent-resources | 2 +- pkg/cgowrap/wrapper.go | 42 +++-------------------- pkg/grpchandler/resources/v2/resources.go | 25 +++++++------- proto/resources/v2/deviceresources.proto | 7 ++-- 4 files changed, 22 insertions(+), 54 deletions(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 4358ac6..79d1ea9 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 4358ac69903d0897f09b19f87b78e2aa1cc1e0b7 +Subproject commit 79d1ea93aef1cabe101fd51f9d49587918b625a9 diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index b4c14ea..ff0c9cf 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -524,7 +524,6 @@ Returns: */ func ReadProcesses(device *C.Device) ([]Process, error) { - if device == nil { return nil, fmt.Errorf("dvice null pointer") } @@ -532,57 +531,26 @@ func ReadProcesses(device *C.Device) ([]Process, error) { count := int(device.processes.count) items := device.processes.items - // slice := unsafe.Slice(items, count) - procs := make([]Process, 0, count) - // p := unsafe.Pointer(&items[i]) - - // p := (*C.Process)(unsafe.Pointer(items), uintptr(i)*unsafe.Sizeof(*items)) - for i := 0; i < count; i++ { - // p := &slice[i] - - - p := (*C.Process)(unsafe.Pointer(uintptr(unsafe.Pointer(items)) + uintptr(i)*unsafe.Sizeof(*items))) + p := (*C.Process)(unsafe.Pointer(uintptr(unsafe.Pointer(items)) + uintptr(i)*unsafe.Sizeof(*items))) err := C.process_read2(p.pid, p) if err != C.OK { - fmt.Errorf("failed reading processes") + fmt.Printf("failed reading processes") } - fmt.Println("Reading processes...", device.processes) - - - fmt.Println("pid:",p.pid) - fmt.Println("state:", p.state) - - // if p.state == C.PROCESS_UNDEFINED { - // - // C.process_read(p.pid, p) - // } - // - - fmt.Println("\t\t\t\tstate: ", C.GoString(&p.name[0])) - fmt.Println(unsafe.Sizeof(C.Process{})) - - - fmt.Println("pid offset ", unsafe.Offsetof(p.pid)) - fmt.Println("state offset", unsafe.Offsetof(p.state)) - fmt.Println("utime offset", unsafe.Offsetof(p.utime)) - procs = append(procs, Process{ PID: int32(p.pid), - State: ProcessState(p.state), //BREAKPOINT + State: ProcessState(p.state), UTime: uint64(p.utime), STime: uint64(p.stime), NumThreads: uint32(p.num_threads), Name: C.GoString(&p.name[0]), }) - } - return procs, nil } @@ -596,13 +564,13 @@ Parameters: Returns: - error: Error if the process cannot be killed or doesn't exist */ -func KillProcess(pid int) error { +func ProcessAction(pid int, action unix.Signal) error { if pid <= 0 { return fmt.Errorf("invalid PID: %d", pid) } - if C.process_kill(C.int(pid), C.int(unix.SIGTERM)) != C.OK { + if C.process_kill(C.int(pid), C.int(action)) != C.OK { return fmt.Errorf("failed to kill process %d", pid) } return nil diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go index 51dc7a8..c59d02f 100644 --- a/pkg/grpchandler/resources/v2/resources.go +++ b/pkg/grpchandler/resources/v2/resources.go @@ -7,6 +7,8 @@ import ( "paradigm-ehb/agent/internal/resources" cgo "paradigm-ehb/agent/pkg/cgowrap" + "golang.org/x/sys/unix" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -66,18 +68,18 @@ func (s *ResourcesServiceV2) GetSystemResources( func (s *ResourcesServiceV2) KillProcess( ctx context.Context, - req *proto.KillProcessRequest, -) (*proto.KillProcessReply, error) { + req *proto.ProcessActionRequest, +) (*proto.ProcessActionReply, error) { - err := cgo.KillProcess(int(req.Pid)) + err := cgo.ProcessAction(int(req.Pid), unix.Signal(req.Signal)) if err != nil { - return &proto.KillProcessReply{ + return &proto.ProcessActionReply{ Succes: false, }, nil } - return &proto.KillProcessReply{ + return &proto.ProcessActionReply{ Succes: true, }, nil } @@ -91,12 +93,11 @@ func (s *ResourcesServiceV2) KillProcess( */ func mapSystemResources(s *resources.SystemResources) *proto.SystemResources { - return &proto.SystemResources{ - Cpu: mapCPU(s.CPU), - Memory: mapMemory(s.Memory), - Device: mapDevice(s.Device), - Disks: mapDisks(s.Disks), + Cpu: mapCPU(s.CPU), + Memory: mapMemory(s.Memory), + Device: mapDevice(s.Device), + Disks: mapDisks(s.Disks), Processes: mapProcesses(s.Procs), } } @@ -187,7 +188,6 @@ func mapProcessState(s cgo.ProcessState) proto.ProcessState { switch s { - case cgo.ProcessRunning: { @@ -231,7 +231,7 @@ func mapProcessState(s cgo.ProcessState) proto.ProcessState { case cgo.ProcessUndefined: { return proto.ProcessState_PROCESS_STATE_UNSPECIFIED - + } default: @@ -259,6 +259,5 @@ func mapProcesses(ps []cgo.Process) []*proto.Process { }) } - return out } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 7404d50..35dbd02 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -108,11 +108,12 @@ message Process { uint32 num_threads = 5; } -message KillProcessRequest { - uint32 pid = 1; +message ProcessActionRequest { + int32 pid = 1; + int32 signal = 2; } -message KillProcessReply { +message ProcessActionReply { bool succes = 1; } From 2e179f4215d50594e9190b9362b5ec6e0710293a Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 15:12:29 +0100 Subject: [PATCH 19/59] feature: refactored services grpc handler to better handle filtered and loaded units --- pkg/grpchandler/services/v3/services.go | 136 +++++++++++++++--------- 1 file changed, 88 insertions(+), 48 deletions(-) diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index 1814d57..3ca3f3c 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -5,15 +5,48 @@ import ( "fmt" v3 "paradigm-ehb/agent/gen/services/v3" + manager "paradigm-ehb/agent/internal/dbusservices" dh "paradigm-ehb/agent/internal/dbusservices/dbus" servicecontrol "paradigm-ehb/agent/internal/dbusservices/systemd" + types "paradigm-ehb/agent/internal/dbusservices/types" ) type HandlerServicev3 struct { v3.UnimplementedHandlerServiceServer } + +func mapLoadedUnit(u *types.LoadedUnit) *v3.LoadedUnit { + if u == nil { + return nil + } + + return &v3.LoadedUnit{ + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), + } +} + +func mapLoadedUnits(units []*types.LoadedUnit) []*v3.LoadedUnit { + out := make([]*v3.LoadedUnit, 0, len(units)) + for _, u := range units { + if pu := mapLoadedUnit(u); pu != nil { + out = append(out, pu) + } + } + return out +} + + func (s *HandlerServicev3) PerformUnitAction( _ context.Context, in *v3.UnitActionRequest, @@ -47,8 +80,7 @@ func (s *HandlerServicev3) PerformUnitAction( }, nil } - err = manager.RunAction(conn, action, in.UnitName) - if err != nil { + if err := manager.RunAction(conn, action, in.UnitName); err != nil { return &v3.UnitActionReply{ Status: []byte(fmt.Sprintf("failed to %s unit", actionName)), Success: false, @@ -89,15 +121,13 @@ func (s *HandlerServicev3) PerformUnitFileAction( }, nil } - err = manager.RunSymlinkAction( + if err := manager.RunSymlinkAction( conn, action, in.Runtime, in.Force, []string{in.UnitName}, - ) - - if err != nil { + ); err != nil { return &v3.UnitFileActionReply{ Status: []byte("unit file action failed"), Success: false, @@ -111,13 +141,13 @@ func (s *HandlerServicev3) PerformUnitFileAction( }, nil } + func (s *HandlerServicev3) GetAllUnits( _ context.Context, _ *v3.GetUnitsRequest, ) (*v3.GetUnitsReply, error) { conn, err := dh.CreateSystemBus() - if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -125,11 +155,7 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } - /** - - Handle error hanlding - */ - _, err = manager.RunRetrieval(conn, true) + units, err := manager.RunRetrieval(conn, true) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -137,11 +163,8 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } - // mappedUnits := make(*v3.GetUnitsReply, 0, len(units)) - // mappedUnits = &units{} - return &v3.GetUnitsReply{ - Units: nil, + Units: mapLoadedUnits(units), Success: true, }, nil } @@ -159,10 +182,7 @@ func (s *HandlerServicev3) GetLoadedUnits( }, nil } - /** - TODO(nasr): handle error - */ - _, err = manager.RunRetrieval(conn, false) + units, err := manager.RunRetrieval(conn, false) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -171,71 +191,91 @@ func (s *HandlerServicev3) GetLoadedUnits( } return &v3.GetUnitsReply{ - Units: nil, + Units: mapLoadedUnits(units), Success: true, }, nil } -func (s *HandlerServicev3) GetUnitStatus( +func (s *HandlerServicev3) GetFilteredUnits( _ context.Context, - in *v3.GetUnitStatusRequest, -) (*v3.GetUnitStatusReply, error) { + in *v3.GetUnitsFilteredRequest, +) (*v3.GetUnitsReply, error) { conn, err := dh.CreateSystemBus() if err != nil { - return &v3.GetUnitStatusReply{ + return &v3.GetUnitsReply{ Success: false, ErrorMessage: err.Error(), }, nil } - /** - TODO(nasr): todo handle error - */ - obj, _ := dh.CreateSystemdObject(conn) + var filters []string + + switch in.State { + case v3.GetUnitsFilteredRequest_LOADED: + filters = []string{"loaded"} + case v3.GetUnitsFilteredRequest_NOT_FOUND: + filters = []string{"not-found"} + case v3.GetUnitsFilteredRequest_BAD_SETTING: + filters = []string{"bad-setting"} + case v3.GetUnitsFilteredRequest_ERROR: + filters = []string{"error"} + case v3.GetUnitsFilteredRequest_MASKED: + filters = []string{"masked"} + default: + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: "unspecified filter state", + }, nil + } - state, err := manager.GetStatus(obj, in.UnitName) + units, err := manager.MapFilteredUnits(conn, filters) if err != nil { - return &v3.GetUnitStatusReply{ + return &v3.GetUnitsReply{ Success: false, ErrorMessage: err.Error(), }, nil } - return &v3.GetUnitStatusReply{ - State: state, + return &v3.GetUnitsReply{ + Units: mapLoadedUnits(units), Success: true, }, nil } -func (s *HandlerServicev3) GetFilteredUnits( + +func (s *HandlerServicev3) GetUnitStatus( _ context.Context, - in *v3.GetUnitsFilteredRequest, -) (*v3.GetUnitsReply, error) { + in *v3.GetUnitStatusRequest, +) (*v3.GetUnitStatusReply, error) { conn, err := dh.CreateSystemBus() - if err != nil { - return &v3.GetUnitsReply{ + return &v3.GetUnitStatusReply{ Success: false, ErrorMessage: err.Error(), }, nil } - /** - - TODO(nasr): handle the correct types - doing now - - */ - _, err = manager.MapFilteredUnits(conn) + obj, err := dh.CreateSystemdObject(conn) if err != nil { + return &v3.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, fmt.Errorf("failed to create systemd object") + } - return &v3.GetUnitsReply{ + state, err := manager.GetStatus(obj, in.UnitName) + if err != nil { + return &v3.GetUnitStatusReply{ Success: false, ErrorMessage: err.Error(), - }, fmt.Errorf("failed to map filtered units %v: ", err) + }, nil } - return &v3.GetUnitsReply{}, nil + return &v3.GetUnitStatusReply{ + State: state, + Success: true, + }, nil } +``` From d0384039e5577537c249e9145c62601e6fdcae10 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 15:13:44 +0100 Subject: [PATCH 20/59] debug: moved from ssh to https for the git submodule --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 3631d7d..3d20071 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "internal/resmanager/agent-resources"] path = pkg/agent-resources - url = git@github.com:paradigm-ehb/agent-resources.git + url = https://www.github.com/paradigm-ehb/agent-resources.git From 2a48c726711d2f54da13233ef74cbc2cd7219112 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 17:16:25 +0100 Subject: [PATCH 21/59] feature: v3 test services --- test/gprc_services_v3.sh | 114 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100755 test/gprc_services_v3.sh diff --git a/test/gprc_services_v3.sh b/test/gprc_services_v3.sh new file mode 100755 index 0000000..eab5361 --- /dev/null +++ b/test/gprc_services_v3.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env sh +set -euo pipefail + +HOST="${HOST:-localhost}" +PORT="${PORT:-5000}" +ADDR="${HOST}:${PORT}" +SERVICE="services.v3.HandlerService" + +echo "== gRPC v3 integration test ==" +echo "Target: ${ADDR}" +echo + +command -v grpcurl >/dev/null 2>&1 || { + echo "ERROR: grpcurl not found" + exit 1 +} + +echo "== Checking server availability ==" +grpcurl -plaintext "${ADDR}" list >/dev/null +echo "OK" +echo + +echo "== Listing all services ==" +grpcurl -plaintext "${ADDR}" list +echo + +echo "== Checking ${SERVICE} existence ==" +grpcurl -plaintext "${ADDR}" list "${SERVICE}" >/dev/null +echo "OK" +echo + +echo "== Listing ${SERVICE} methods ==" +grpcurl -plaintext "${ADDR}" list "${SERVICE}" +echo + +echo "== GetAllUnits ==" +grpcurl -plaintext \ + -d '{}' \ + "${ADDR}" \ + "${SERVICE}/GetAllUnits" +echo + +echo "== GetLoadedUnits ==" +grpcurl -plaintext \ + -d '{}' \ + "${ADDR}" \ + "${SERVICE}/GetLoadedUnits" +echo + +echo "== GetFilteredUnits (loaded, masked) ==" + +grpcurl -plaintext \ + -d '{ + "filters": ["LOADED", "MASKED"] + }' \ + localhost:5000 \ + services.v3.HandlerService/GetFilteredUnits + +echo + +echo "== GetUnitStatus (tailscaled.service) ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service" + }' \ + "${ADDR}" \ + "${SERVICE}/GetUnitStatus" +echo + +echo "== PerformUnitAction: START tailscaled.service ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_ACTION_START" + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitAction" +echo + +echo "== PerformUnitFileAction: ENABLE tailscaled.service (runtime=true, force=true) ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_FILE_ACTION_ENABLE", + "runtime": true, + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitFileAction" +echo + +echo "== PerformUnitAction: STOP tailscaled.service ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_ACTION_STOP" + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitAction" +echo + +echo "== PerformUnitFileAction: DISABLE tailscaled.service ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_FILE_ACTION_DISABLE", + "runtime": true, + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitFileAction" +echo + +echo "== All gRPC v3 tests passed ==" From 109573f4daf18f3cd443125581e34a31ecc7123b Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 17:16:51 +0100 Subject: [PATCH 22/59] feature: v3 services grpc and protobufs, still one issue but thats for tomorrow checkpoitn --- cmd/agent/agent.go | 9 ++++ internal/dbusservices/manager.go | 71 ++++++++++++++++--------- internal/dbusservices/systemd/list.go | 23 ++++++++ pkg/grpchandler/services/v2/services.go | 7 ++- pkg/grpchandler/services/v3/services.go | 64 +++++++++++----------- 5 files changed, 114 insertions(+), 60 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 8e7bd6a..87a2651 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -25,6 +25,7 @@ import ( servicesV1 "paradigm-ehb/agent/gen/services/v1" servicesV2 "paradigm-ehb/agent/gen/services/v2" + servicesV3 "paradigm-ehb/agent/gen/services/v3" resourcesv1 "paradigm-ehb/agent/gen/resources/v1" resourcesv2 "paradigm-ehb/agent/gen/resources/v2" @@ -43,6 +44,7 @@ import ( servicesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/services/v1" servicesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/services/v2" + servicesHandlerV3 "paradigm-ehb/agent/pkg/grpchandler/services/v3" "syscall" "time" @@ -154,6 +156,12 @@ func main() { server, &servicesHandlerV2.HandlerServiceV2{}) + servicesV3.RegisterHandlerServiceServer( + server, + &servicesHandlerV3.HandlerServicev3{}) + + + journal.RegisterJournalServiceServer( server, &grpc_handler.JournalService{}) @@ -170,6 +178,7 @@ func main() { server, &grpc_handler.DeviceActionsService{}) + /** Enable gRPC reflection unconditionally. diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index e4eb8e3..539be6c 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -98,24 +98,50 @@ func MapLoadedUnits(conn *dbus.Conn) []*types.LoadedUnit { return units } -func MapFilteredUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { +func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, error) { - /* + obj, err := dbushelper.CreateSystemdObject(conn) + if err != nil { + fmt.Errorf("failed to create systemd object") + } + + in := make(chan []types.LoadedUnit) + out := make(chan []types.LoadedUnit) + + go systemd.GetUnitsFiltered(obj, in, filters) + go dbushelper.ParseLoadedUnits(in, out) + + var entries []types.LoadedUnit + + units := make([]*types.LoadedUnit, 0, len(entries)) + + entries = <-out - dbushelper.CreateSystemdObject(conn) - TODO(nasr): map the filtered units to their appropriate object and pass - them to the grpc handler - **/ + for _, e := range entries { - return nil, nil + units = append(units, &types.LoadedUnit{ + Name: e.Name, + Description: "Not available", + LoadState: e.LoadState, + SubState: "Not Available", + ActiveState: "Not Available", + DepUnit: "Not Available", + ObjectPath: "Not Available", + QueudJob: 0, + JobType: "Not Available", + JobPath: "Not Available", + }) + } + + return units, nil } func MapUnits(conn *dbus.Conn) []*types.LoadedUnit { - /** - TODO(nasr): fix the error handling - */ - obj, _ := dbushelper.CreateSystemdObject(conn) + obj, err := dbushelper.CreateSystemdObject(conn) + if err != nil { + fmt.Errorf("failed to create systemd object") + } in := make(chan []types.Unit) out := make(chan []types.Unit) @@ -174,22 +200,15 @@ func RunRetrieval( return MapLoadedUnits(conn), nil } -func GetStatus(obj dbus.BusObject, name string) (string, error) { - var result string +func UnitStatus( + obj dbus.BusObject, + name string) (string, error) { - call := obj.Call( - "org.freedesktop.systemd1.Manager.GetUnitFileState", - 0, - name, - ) - - if call.Err != nil { - return "call error: ", call.Err - } + out, err := systemd.GetStatusCall(obj, name) + if err != nil { + return "Failed", fmt.Errorf("failed to execute status call ", err) + } - if err := call.Store(&result); err != nil { - return "call store: ", err - } + return out, nil - return result, nil } diff --git a/internal/dbusservices/systemd/list.go b/internal/dbusservices/systemd/list.go index 3361b88..0e6def2 100644 --- a/internal/dbusservices/systemd/list.go +++ b/internal/dbusservices/systemd/list.go @@ -82,3 +82,26 @@ func GetUnitsFiltered( out <- result } + + +func GetStatusCall(obj dbus.BusObject, name string) (string, error) { + var result string + + call := obj.Call( + "org.freedesktop.systemd1.Manager.GetUnitFileState", + 0, + name, + ) + + if call.Err != nil { + return "call error: ", call.Err + } + + if err := call.Store(&result); err != nil { + return "call store: ", err + } + + return result, nil +} + + diff --git a/pkg/grpchandler/services/v2/services.go b/pkg/grpchandler/services/v2/services.go index 3ded666..4a57da7 100644 --- a/pkg/grpchandler/services/v2/services.go +++ b/pkg/grpchandler/services/v2/services.go @@ -194,9 +194,12 @@ func (s *HandlerServiceV2) GetUnitStatus( }, nil } - obj, _ := dh.CreateSystemdObject(conn) + obj, err := dh.CreateSystemdObject(conn) + if err != nil { + return nil, fmt.Errorf("failed to create system object") + } - state, err := manager.GetStatus(obj, in.UnitName) + state, err := manager.UnitStatus(obj, in.UnitName) if err != nil { return &v2.GetUnitStatusReply{ Success: false, diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index 3ca3f3c..f9245bd 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -23,16 +23,16 @@ func mapLoadedUnit(u *types.LoadedUnit) *v3.LoadedUnit { } return &v3.LoadedUnit{ - Name: u.Name, - Description: u.Description, - LoadState: u.LoadState, - SubState: u.SubState, - ActiveState: u.ActiveState, - DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), } } @@ -209,24 +209,26 @@ func (s *HandlerServicev3) GetFilteredUnits( }, nil } - var filters []string - - switch in.State { - case v3.GetUnitsFilteredRequest_LOADED: - filters = []string{"loaded"} - case v3.GetUnitsFilteredRequest_NOT_FOUND: - filters = []string{"not-found"} - case v3.GetUnitsFilteredRequest_BAD_SETTING: - filters = []string{"bad-setting"} - case v3.GetUnitsFilteredRequest_ERROR: - filters = []string{"error"} - case v3.GetUnitsFilteredRequest_MASKED: - filters = []string{"masked"} - default: - return &v3.GetUnitsReply{ - Success: false, - ErrorMessage: "unspecified filter state", - }, nil + filters := make([]string, 0, len(in.Filters)) + + for _, f := range in.Filters { + switch f { + case v3.GetUnitsFilteredRequest_LOADED: + filters = append(filters, "loaded") + case v3.GetUnitsFilteredRequest_NOT_FOUND: + filters = append(filters, "not-found") + case v3.GetUnitsFilteredRequest_BAD_SETTING: + filters = append(filters, "bad-setting") + case v3.GetUnitsFilteredRequest_ERROR: + filters = append(filters, "error") + case v3.GetUnitsFilteredRequest_MASKED: + filters = append(filters, "masked") + default: + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: "unspecified filter state", + }, nil + } } units, err := manager.MapFilteredUnits(conn, filters) @@ -243,7 +245,6 @@ func (s *HandlerServicev3) GetFilteredUnits( }, nil } - func (s *HandlerServicev3) GetUnitStatus( _ context.Context, in *v3.GetUnitStatusRequest, @@ -262,10 +263,10 @@ func (s *HandlerServicev3) GetUnitStatus( return &v3.GetUnitStatusReply{ Success: false, ErrorMessage: err.Error(), - }, fmt.Errorf("failed to create systemd object") + }, nil } - state, err := manager.GetStatus(obj, in.UnitName) + state, err := manager.UnitStatus(obj, in.UnitName) if err != nil { return &v3.GetUnitStatusReply{ Success: false, @@ -278,4 +279,3 @@ func (s *HandlerServicev3) GetUnitStatus( Success: true, }, nil } -``` From 65f137f5ae267351f8136902c321cc96afa325e8 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 17:17:00 +0100 Subject: [PATCH 23/59] debug: array for file states --- proto/services/v3/services.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proto/services/v3/services.proto b/proto/services/v3/services.proto index 1319324..01e9755 100644 --- a/proto/services/v3/services.proto +++ b/proto/services/v3/services.proto @@ -10,7 +10,7 @@ service HandlerService { rpc PerformUnitFileAction (UnitFileActionRequest) returns (UnitFileActionReply); rpc GetAllUnits (GetUnitsRequest) returns (GetUnitsReply); rpc GetLoadedUnits (GetUnitsRequest) returns (GetUnitsReply); - rpc GetFilteredUnits (GetUnitsRequest) returns (GetUnitsReply); + rpc GetFilteredUnits (GetUnitsFilteredRequest) returns (GetUnitsReply); rpc GetUnitStatus (GetUnitStatusRequest) returns (GetUnitStatusReply); } @@ -102,7 +102,7 @@ message GetUnitsFilteredRequest { MASKED = 5; } - UnitFileState state = 1; + repeated UnitFileState filters = 1; } From f0c6eab7680fadfa083802e80550be0eed4b3b42 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:06:42 +0100 Subject: [PATCH 24/59] feature: refactor mapLoadedUnits to return error, update GetAllUnits and related handlers --- pkg/grpchandler/services/v3/services.go | 68 +++++++++++++++---------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index f9245bd..a2abed3 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -16,37 +16,39 @@ type HandlerServicev3 struct { v3.UnimplementedHandlerServiceServer } +func mapLoadedUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { -func mapLoadedUnit(u *types.LoadedUnit) *v3.LoadedUnit { - if u == nil { - return nil + if units == nil { + return nil, fmt.Errorf("passed input is nil") } - return &v3.LoadedUnit{ - Name: u.Name, - Description: u.Description, - LoadState: u.LoadState, - SubState: u.SubState, - ActiveState: u.ActiveState, - DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), - } -} + fmt.Println("input :", units) -func mapLoadedUnits(units []*types.LoadedUnit) []*v3.LoadedUnit { out := make([]*v3.LoadedUnit, 0, len(units)) + for _, u := range units { - if pu := mapLoadedUnit(u); pu != nil { - out = append(out, pu) + if u == nil { + continue } + + out = append(out, &v3.LoadedUnit{ + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), + }) } - return out -} + fmt.Println("out -> ", out) + return out, nil +} func (s *HandlerServicev3) PerformUnitAction( _ context.Context, in *v3.UnitActionRequest, @@ -141,7 +143,6 @@ func (s *HandlerServicev3) PerformUnitFileAction( }, nil } - func (s *HandlerServicev3) GetAllUnits( _ context.Context, _ *v3.GetUnitsRequest, @@ -163,8 +164,19 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } + /*** + units is empty here! + */ + mapped, err := mapLoadedUnits(units) + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + return &v3.GetUnitsReply{ - Units: mapLoadedUnits(units), + Units: mapped, Success: true, }, nil } @@ -174,7 +186,7 @@ func (s *HandlerServicev3) GetLoadedUnits( _ *v3.GetUnitsRequest, ) (*v3.GetUnitsReply, error) { - conn, err := dh.CreateSystemBus() + _, err := dh.CreateSystemBus() if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -182,7 +194,7 @@ func (s *HandlerServicev3) GetLoadedUnits( }, nil } - units, err := manager.RunRetrieval(conn, false) + // units, err := manager.RunRetrieval(conn, false) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -191,7 +203,7 @@ func (s *HandlerServicev3) GetLoadedUnits( } return &v3.GetUnitsReply{ - Units: mapLoadedUnits(units), + Units: nil, Success: true, }, nil } @@ -231,7 +243,7 @@ func (s *HandlerServicev3) GetFilteredUnits( } } - units, err := manager.MapFilteredUnits(conn, filters) + _, err = manager.MapFilteredUnits(conn, filters) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -240,7 +252,7 @@ func (s *HandlerServicev3) GetFilteredUnits( } return &v3.GetUnitsReply{ - Units: mapLoadedUnits(units), + Units: nil, Success: true, }, nil } From c351a4114d0d2b616638b6fd76afb9f7ccbfc3ab Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:06:52 +0100 Subject: [PATCH 25/59] refactor: update ParseUnits to accept slice input and return filtered slice with error --- internal/dbusservices/dbus/parse.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/internal/dbusservices/dbus/parse.go b/internal/dbusservices/dbus/parse.go index b8bf23f..8610d27 100644 --- a/internal/dbusservices/dbus/parse.go +++ b/internal/dbusservices/dbus/parse.go @@ -12,24 +12,20 @@ import ( // @param chan a(ss), chan a(ss) // @param chan UnitFileEntry, chan UnitFileEntry // @return nil -func ParseUnits(in chan []types.Unit, out chan []types.Unit) { - - input := <-in - +func ParseUnits(input []types.Unit) ([]types.Unit, error) { /** filter the units on services and remove devices etc */ + buffer := make([]types.Unit, 0, len(input)) for _, value := range input { - if strings.HasSuffix(".service", value.Name) { + if strings.HasSuffix(value.Name, ".service") { buffer = append(buffer, value) } } - out <- buffer - - return + return buffer, nil } From d5cd34c788b12cb38cfdaa06481a43962938b0fd Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:06:58 +0100 Subject: [PATCH 26/59] refactor: update MapUnits to return error, remove channels, improve error handling in GetAllUnits --- internal/dbusservices/manager.go | 54 +++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 539be6c..facf24e 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -2,7 +2,6 @@ package dbus_services import ( "fmt" - "log" dbushelper "paradigm-ehb/agent/internal/dbusservices/dbus" systemd "paradigm-ehb/agent/internal/dbusservices/systemd" @@ -136,26 +135,40 @@ func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, e return units, nil } -func MapUnits(conn *dbus.Conn) []*types.LoadedUnit { +func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { - fmt.Errorf("failed to create systemd object") + return nil, fmt.Errorf("failed to create systemd object") } - in := make(chan []types.Unit) - out := make(chan []types.Unit) + // in := make(chan []types.Unit) + // out := make(chan []types.Unit) - go systemd.GetUnits(obj, in) - go dbushelper.ParseUnits(in, out) + var result []types.Unit - var entries []types.Unit + result, err = systemd.GetUnits(obj) - units := make([]*types.LoadedUnit, 0, len(entries)) + if err != nil { + return nil, fmt.Errorf("failed in the Map Units function") + } - entries = <-out + /** + * TODO(nasr): remove channel impelmentetation + */ - for _, e := range entries { + parsedUnits, err := dbushelper.ParseUnits(result) + if err != nil { + return nil, fmt.Errorf("failed to parse units") + } + + // var entries []types.Unit + + units := make([]*types.LoadedUnit, 0, len(parsedUnits)) + + for _, e := range parsedUnits { + + fmt.Println("unit: -> ", e) units = append(units, &types.LoadedUnit{ Name: e.Name, @@ -171,7 +184,7 @@ func MapUnits(conn *dbus.Conn) []*types.LoadedUnit { }) } - return units + return units, nil } /* @@ -190,11 +203,14 @@ func RunRetrieval( conn, err := dbushelper.CreateSystemBus() if err != nil { - log.Printf("failed to create a system bus connection for retrieving units %v", err) + return nil, fmt.Errorf("failed to create a system bus connection for retrieving units %v", err) } if requestAllUnitsOnDisk { - return MapUnits(conn), nil + result, err := MapUnits(conn) + if err != nil { + return result, fmt.Errorf("failed to request stuff") + } } return MapLoadedUnits(conn), nil @@ -204,11 +220,11 @@ func UnitStatus( obj dbus.BusObject, name string) (string, error) { - out, err := systemd.GetStatusCall(obj, name) - if err != nil { - return "Failed", fmt.Errorf("failed to execute status call ", err) - } + out, err := systemd.GetStatusCall(obj, name) + if err != nil { + return "Failed", fmt.Errorf("failed to execute status call %v", err) + } - return out, nil + return out, nil } From c6c89cad355783362243304df9555a4f0de94574 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:07:06 +0100 Subject: [PATCH 27/59] refactor: update GetUnits to return slice and error instead of using channel --- internal/dbusservices/systemd/list.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/internal/dbusservices/systemd/list.go b/internal/dbusservices/systemd/list.go index 0e6def2..e4f22ef 100644 --- a/internal/dbusservices/systemd/list.go +++ b/internal/dbusservices/systemd/list.go @@ -38,7 +38,7 @@ func GetLoadedUnits( func GetUnits( obj dbus.BusObject, - out chan []types.Unit) { +) ([]types.Unit, error) { // ListUnitFiles(out a(ss) files); // an array of struct string string @@ -48,16 +48,26 @@ func GetUnits( call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0) if call.Err != nil { - fmt.Println("failed to call all units loaded on disk") - return + + return nil, fmt.Errorf("failed to call all units loaded on disk") } err := call.Store(&result) if err != nil { - return + return nil, fmt.Errorf("failed to store services ") } - out <- result + /** + * THIS IS WORKING + * fmt.Println("channel result: \n", result) + */ + + /** + * THIS ISNT + * out <- result + */ + + return result, nil } func GetUnitsFiltered( @@ -83,7 +93,6 @@ func GetUnitsFiltered( } - func GetStatusCall(obj dbus.BusObject, name string) (string, error) { var result string @@ -103,5 +112,3 @@ func GetStatusCall(obj dbus.BusObject, name string) (string, error) { return result, nil } - - From 3a7efeed44f2191c621718d007e1a11a601e647e Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:07:11 +0100 Subject: [PATCH 28/59] chore: update agent-resources submodule URL to use SSH --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 3d20071..3631d7d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "internal/resmanager/agent-resources"] path = pkg/agent-resources - url = https://www.github.com/paradigm-ehb/agent-resources.git + url = git@github.com:paradigm-ehb/agent-resources.git From c36504670a33a16913aa4ad0accc9138d97e4684 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:07:20 +0100 Subject: [PATCH 29/59] chore: set diagnostics flag default to false --- cmd/agent/agent.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 87a2651..47d1a6b 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -57,7 +57,7 @@ var ( NOTE: When enabled, diagnostics must never block the gRPC server. **/ - diagnostics = flag.Bool("diagnostics", true, "run runtime diagnostics") + diagnostics = flag.Bool("diagnostics", false, "run runtime diagnostics") /** portFlag is the preferred TCP port to bind the gRPC server to. @@ -159,8 +159,6 @@ func main() { servicesV3.RegisterHandlerServiceServer( server, &servicesHandlerV3.HandlerServicev3{}) - - journal.RegisterJournalServiceServer( server, @@ -178,7 +176,6 @@ func main() { server, &grpc_handler.DeviceActionsService{}) - /** Enable gRPC reflection unconditionally. From 06364f5d89a7fa9542904d738f2e4d2b9f6dcceb Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:22:49 +0100 Subject: [PATCH 30/59] checkpoint? --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 79d1ea9..f9e91ef 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 79d1ea93aef1cabe101fd51f9d49587918b625a9 +Subproject commit f9e91ef837924ba8ece0d2ed626f23747bbc9788 From d239cd3514b5558eb8e084bb7340c8aef531284a Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:26 +0100 Subject: [PATCH 31/59] refactor: update ParseUnits and ParseLoadedUnits to return slices and errors instead of using channels --- internal/dbusservices/dbus/parse.go | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/internal/dbusservices/dbus/parse.go b/internal/dbusservices/dbus/parse.go index 8610d27..6f9fce0 100644 --- a/internal/dbusservices/dbus/parse.go +++ b/internal/dbusservices/dbus/parse.go @@ -8,44 +8,34 @@ import ( // TODO(nasr): implement interfaces maybe -// Method -// @param chan a(ss), chan a(ss) -// @param chan UnitFileEntry, chan UnitFileEntry -// @return nil +// ParseUnits filters units to only include services +// @param input []types.Unit +// @return []types.Unit, error func ParseUnits(input []types.Unit) ([]types.Unit, error) { /** filter the units on services and remove devices etc */ - buffer := make([]types.Unit, 0, len(input)) for _, value := range input { - if strings.HasSuffix(value.Name, ".service") { buffer = append(buffer, value) } } - return buffer, nil - } -func ParseLoadedUnits(in chan []types.LoadedUnit, out chan []types.LoadedUnit) { - - input := <-in - +// ParseLoadedUnits filters loaded units to only include services +// @param input []types.LoadedUnit +// @return []types.LoadedUnit, error +func ParseLoadedUnits(input []types.LoadedUnit) ([]types.LoadedUnit, error) { /** filter the units on services and remove devices etc */ buffer := make([]types.LoadedUnit, 0, len(input)) for _, value := range input { - - if strings.HasSuffix(".service", string(value.Name)) { + if strings.HasSuffix(value.Name, ".service") { buffer = append(buffer, value) } } - - out <- buffer - - return - + return buffer, nil } From 40443b9aa480e723729f8b1a2e2c6d67212390f5 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:32 +0100 Subject: [PATCH 32/59] refactor: streamline unit retrieval and status functions, replacing channels with direct returns --- internal/dbusservices/manager.go | 167 ++++++++++++++----------------- 1 file changed, 76 insertions(+), 91 deletions(-) diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index facf24e..27fdebc 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -64,21 +64,40 @@ func RunSymlinkAction( return nil } -// MapLoadedUnits /* -func MapLoadedUnits(conn *dbus.Conn) []*types.LoadedUnit { - - obj, _ := dbushelper.CreateSystemdObject(conn) - ch := make(chan []types.LoadedUnit) - parse := make(chan []types.LoadedUnit) +func UnitStatus( + obj dbus.BusObject, + name string) (string, error) { - go systemd.GetLoadedUnits(obj, ch) - go dbushelper.ParseLoadedUnits(ch, parse) + out, err := systemd.GetStatusCall(obj, name) + if err != nil { + return "Failed", fmt.Errorf("failed to execute status call %v", err) + } - loaded := <-parse + return out, nil - units := make([]*types.LoadedUnit, 0, len(loaded)) +} - for _, u := range loaded { +/** + * Map to the correct types + */ +func MapLoadedUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { + obj, err := dbushelper.CreateSystemdObject(conn) + if err != nil { + return nil, fmt.Errorf("failed to create systemd object: %w", err) + } + + loaded, err := systemd.GetLoadedUnits(obj) + if err != nil { + return nil, fmt.Errorf("failed to get loaded units: %w", err) + } + + parsed, err := dbushelper.ParseLoadedUnits(loaded) + if err != nil { + return nil, fmt.Errorf("failed to parse loaded units: %w", err) + } + + units := make([]*types.LoadedUnit, 0, len(parsed)) + for _, u := range parsed { units = append(units, &types.LoadedUnit{ Name: u.Name, Description: u.Description, @@ -87,37 +106,32 @@ func MapLoadedUnits(conn *dbus.Conn) []*types.LoadedUnit { ActiveState: u.ActiveState, DepUnit: u.DepUnit, ObjectPath: u.ObjectPath, - /*oops typo in queued job :)*/ - QueudJob: u.QueudJob, - JobType: u.JobType, - JobPath: u.JobPath, + QueudJob: u.QueudJob, // Keep the typo for consistency + JobType: u.JobType, + JobPath: u.JobPath, }) } - - return units + return units, nil } func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, error) { - obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { - fmt.Errorf("failed to create systemd object") + return nil, fmt.Errorf("failed to create systemd object: %w", err) } - - in := make(chan []types.LoadedUnit) - out := make(chan []types.LoadedUnit) - - go systemd.GetUnitsFiltered(obj, in, filters) - go dbushelper.ParseLoadedUnits(in, out) - - var entries []types.LoadedUnit - - units := make([]*types.LoadedUnit, 0, len(entries)) - - entries = <-out - - for _, e := range entries { - + + entries, err := systemd.GetUnitsFiltered(obj, filters) + if err != nil { + return nil, fmt.Errorf("failed to get filtered units: %w", err) + } + + parsed, err := dbushelper.ParseLoadedUnits(entries) + if err != nil { + return nil, fmt.Errorf("failed to parse filtered units: %w", err) + } + + units := make([]*types.LoadedUnit, 0, len(parsed)) + for _, e := range parsed { units = append(units, &types.LoadedUnit{ Name: e.Name, Description: "Not available", @@ -131,45 +145,30 @@ func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, e JobPath: "Not Available", }) } - return units, nil } +/** + * Fill in proper unit types + */ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { - obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { - return nil, fmt.Errorf("failed to create systemd object") + return nil, fmt.Errorf("failed to create systemd object: %w", err) } - - // in := make(chan []types.Unit) - // out := make(chan []types.Unit) - - var result []types.Unit - - result, err = systemd.GetUnits(obj) - + + result, err := systemd.GetUnits(obj) if err != nil { - return nil, fmt.Errorf("failed in the Map Units function") + return nil, fmt.Errorf("failed to get units: %w", err) } - - /** - * TODO(nasr): remove channel impelmentetation - */ - + parsedUnits, err := dbushelper.ParseUnits(result) if err != nil { - return nil, fmt.Errorf("failed to parse units") + return nil, fmt.Errorf("failed to parse units: %w", err) } - - // var entries []types.Unit - + units := make([]*types.LoadedUnit, 0, len(parsedUnits)) - for _, e := range parsedUnits { - - fmt.Println("unit: -> ", e) - units = append(units, &types.LoadedUnit{ Name: e.Name, Description: "Not available", @@ -183,48 +182,34 @@ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { JobPath: "Not Available", }) } - return units, nil } -/* -* -* @param, true for all on disk, false for loaded units -* a loaded unit is a unit that has been activated before -* and is available in memoery for the server to start up -* or something like that -* @return []*types.LoadedUnit, error +/** + * @param requestAllUnitsOnDisk: true for all units on disk, false for loaded units + * A loaded unit is a unit that has been activated before and is available in memory + * for the server to start up + * @return []*types.LoadedUnit, error */ -func RunRetrieval( - conn *dbus.Conn, - requestAllUnitsOnDisk bool, -) ([]*types.LoadedUnit, error) { - +func RunRetrieval(requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { conn, err := dbushelper.CreateSystemBus() - if err != nil { - return nil, fmt.Errorf("failed to create a system bus connection for retrieving units %v", err) + return nil, fmt.Errorf("failed to create system bus connection: %w", err) } - + defer conn.Close() + if requestAllUnitsOnDisk { - result, err := MapUnits(conn) - if err != nil { - return result, fmt.Errorf("failed to request stuff") - } + return MapUnits(conn) } - - return MapLoadedUnits(conn), nil + + return MapLoadedUnits(conn) } -func UnitStatus( - obj dbus.BusObject, - name string) (string, error) { - - out, err := systemd.GetStatusCall(obj, name) - if err != nil { - return "Failed", fmt.Errorf("failed to execute status call %v", err) +func RunRetrievalDeprecated(conn *dbus.Conn, requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { + + if requestAllUnitsOnDisk { + return MapUnits(conn) } - - return out, nil - + + return MapLoadedUnits(conn) } From 6b16881dafb2a5a1dc4eb228f63e9d32258a0794 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:36 +0100 Subject: [PATCH 33/59] refactor: update unit retrieval functions to return slices and errors instead of using channels --- internal/dbusservices/systemd/list.go | 65 +++++++-------------------- 1 file changed, 17 insertions(+), 48 deletions(-) diff --git a/internal/dbusservices/systemd/list.go b/internal/dbusservices/systemd/list.go index e4f22ef..f509f56 100644 --- a/internal/dbusservices/systemd/list.go +++ b/internal/dbusservices/systemd/list.go @@ -14,83 +14,52 @@ import ( // files (templates) cannot directly be loaded as units but need to be instantiated. // --------------------------------------------------------------------------------------- // Method returns an array of all currently loaded units, -func GetLoadedUnits( - obj dbus.BusObject, - out chan []types.LoadedUnit) { +func GetLoadedUnits(obj dbus.BusObject) ([]types.LoadedUnit, error) { var result []types.LoadedUnit - call := obj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0) if call.Err != nil { - fmt.Printf("failed to list unit files that are loaded in memory %v", call.Err) - return + return nil, fmt.Errorf("failed to list unit files that are loaded in memory: %w", call.Err) } - + err := call.Store(&result) - if err != nil { - return + return nil, fmt.Errorf("failed to store loaded units: %w", err) } - - out <- result - + + return result, nil } -func GetUnits( - obj dbus.BusObject, -) ([]types.Unit, error) { - +func GetUnits(obj dbus.BusObject) ([]types.Unit, error) { // ListUnitFiles(out a(ss) files); // an array of struct string string - var result []types.Unit - call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0) - if call.Err != nil { - - return nil, fmt.Errorf("failed to call all units loaded on disk") + return nil, fmt.Errorf("failed to call all units loaded on disk: %w", call.Err) } - + err := call.Store(&result) if err != nil { - return nil, fmt.Errorf("failed to store services ") + return nil, fmt.Errorf("failed to store services: %w", err) } - - /** - * THIS IS WORKING - * fmt.Println("channel result: \n", result) - */ - - /** - * THIS ISNT - * out <- result - */ - + return result, nil } -func GetUnitsFiltered( - obj dbus.BusObject, - out chan []types.LoadedUnit, - states []string) { - +func GetUnitsFiltered(obj dbus.BusObject, states []string) ([]types.LoadedUnit, error) { var result []types.LoadedUnit - call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states) - if call.Err != nil { - fmt.Println("failed to call filtered list of units") - return + return nil, fmt.Errorf("failed to call filtered list of units: %w", call.Err) } - + err := call.Store(&result) if err != nil { - return + return nil, fmt.Errorf("failed to store filtered units: %w", err) } - - out <- result - + + return result, nil } func GetStatusCall(obj dbus.BusObject, name string) (string, error) { From bee5856646ebc298e6b1626c155960a41812b5ef Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:41 +0100 Subject: [PATCH 34/59] refactor: implement runtime diagnostics using bubbletea for improved UI and interactivity --- internal/platform/platform.go | 148 +++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 45 deletions(-) diff --git a/internal/platform/platform.go b/internal/platform/platform.go index 9323a61..68748f4 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -6,6 +6,9 @@ import ( "runtime" "runtime/debug" "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" ) func AssertLinux() error { @@ -15,65 +18,120 @@ func AssertLinux() error { return nil } -func RunRuntimeDiagnostics(interval time.Duration, ip string, port int) { - ticker := time.NewTicker(interval) - defer ticker.Stop() +type tickMsg time.Time - for range ticker.C { +type model struct { + ip string + port int + interval time.Duration +} - // clear screen - fmt.Print("\033[2J") - fmt.Print("\033[H") +func (m model) Init() tea.Cmd { + return tick(m.interval) +} - var m runtime.MemStats - runtime.ReadMemStats(&m) +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "q", "ctrl+c": + return m, tea.Quit + } + case tickMsg: + return m, tick(m.interval) + } + return m, nil +} - fmt.Println("runtime diagnostics") - fmt.Println("-------------------") +func (m model) View() string { + var mem runtime.MemStats + runtime.ReadMemStats(&mem) - fmt.Printf("%-20s : %s\n", "go version", runtime.Version()) - fmt.Printf("%-20s : %s\n", "os", runtime.GOOS) - fmt.Printf("%-20s : %s\n", "architecture", runtime.GOARCH) - fmt.Printf("%-20s : %s\n", "compiler", runtime.Compiler) + // Styles + titleStyle := lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("39")). + MarginBottom(1) - fmt.Printf("%-20s : %d\n", "cpu cores", runtime.NumCPU()) - fmt.Printf("%-20s : %d\n", "gomaxprocs", runtime.GOMAXPROCS(0)) - fmt.Printf("%-20s : %d\n", "goroutines", runtime.NumGoroutine()) + sectionStyle := lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("86")). + MarginTop(1) - fmt.Println() - fmt.Println("memory") - fmt.Println("-----------------") + labelStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color("241")) - fmt.Printf("%-20s : %d KB\n", "heap alloc", m.HeapAlloc/1024) - fmt.Printf("%-20s : %d KB\n", "heap sys", m.HeapSys/1024) - fmt.Printf("%-20s : %d KB\n", "heap in use", m.HeapInuse/1024) - fmt.Printf("%-20s : %d KB\n", "heap idle", m.HeapIdle/1024) - fmt.Printf("%-20s : %d KB\n", "stack in use", m.StackInuse/1024) - fmt.Printf("%-20s : %d KB\n", "stack sys", m.StackSys/1024) + valueStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color("white")) - fmt.Printf("%-20s : %d\n", "gc cycles", m.NumGC) - fmt.Printf("%-20s : %d ms\n", "gc pause total", m.PauseTotalNs/1e6) + helpStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color("241")). + MarginTop(1) - fmt.Printf("%-20s : %d\n", "gc next", m.NextGC/1024) + // Helper function to format rows + row := func(label, value string) string { + return fmt.Sprintf("%s %s", + labelStyle.Width(20).Render(label+":"), + valueStyle.Render(value)) + } - fmt.Println() - fmt.Println("server info") - fmt.Println("-----------------") - fmt.Printf("port listening on : %d\n", port) - fmt.Printf("ip address : %s\n", ip) + // Build the view + var s string + + s += titleStyle.Render("Runtime Diagnostics") + "\n" + + s += sectionStyle.Render("System") + "\n" + s += row("Go Version", runtime.Version()) + "\n" + s += row("OS", runtime.GOOS) + "\n" + s += row("Architecture", runtime.GOARCH) + "\n" + s += row("Compiler", runtime.Compiler) + "\n" + s += row("CPU Cores", fmt.Sprintf("%d", runtime.NumCPU())) + "\n" + s += row("GOMAXPROCS", fmt.Sprintf("%d", runtime.GOMAXPROCS(0))) + "\n" + s += row("Goroutines", fmt.Sprintf("%d", runtime.NumGoroutine())) + "\n" + + s += sectionStyle.Render("Memory") + "\n" + s += row("Heap Alloc", fmt.Sprintf("%d KB", mem.HeapAlloc/1024)) + "\n" + s += row("Heap Sys", fmt.Sprintf("%d KB", mem.HeapSys/1024)) + "\n" + s += row("Heap In Use", fmt.Sprintf("%d KB", mem.HeapInuse/1024)) + "\n" + s += row("Heap Idle", fmt.Sprintf("%d KB", mem.HeapIdle/1024)) + "\n" + s += row("Stack In Use", fmt.Sprintf("%d KB", mem.StackInuse/1024)) + "\n" + s += row("Stack Sys", fmt.Sprintf("%d KB", mem.StackSys/1024)) + "\n" + s += row("GC Cycles", fmt.Sprintf("%d", mem.NumGC)) + "\n" + s += row("GC Pause Total", fmt.Sprintf("%d ms", mem.PauseTotalNs/1e6)) + "\n" + s += row("GC Next", fmt.Sprintf("%d KB", mem.NextGC/1024)) + "\n" + + s += sectionStyle.Render("Server Info") + "\n" + s += row("IP Address", m.ip) + "\n" + s += row("Port", fmt.Sprintf("%d", m.port)) + "\n" + + if info, ok := debug.ReadBuildInfo(); ok { + s += sectionStyle.Render("Build Info") + "\n" + s += row("Module", info.Path) + "\n" + s += row("Go Version", info.GoVersion) + "\n" + if info.Main.Version != "(devel)" { + s += row("Version", info.Main.Version) + "\n" + } + } - if info, ok := debug.ReadBuildInfo(); ok { - fmt.Println() - fmt.Println("information") - fmt.Println("-----------------") + s += helpStyle.Render("\nPress 'q' or 'ctrl+c' to quit") - fmt.Printf("%-20s : %s\n", "module", info.Path) - fmt.Printf("%-20s : %s\n", "go version", info.GoVersion) + return s +} - if info.Main.Version != "(devel)" { - fmt.Printf("%-20s : %s\n", "version", info.Main.Version) - } +func tick(interval time.Duration) tea.Cmd { + return tea.Tick(interval, func(t time.Time) tea.Msg { + return tickMsg(t) + }) +} - } +func RunRuntimeDiagnostics(interval time.Duration, ip string, port int) { + p := tea.NewProgram(model{ + ip: ip, + port: port, + interval: interval, + }) + + if _, err := p.Run(); err != nil { + fmt.Printf("Error running diagnostics: %v\n", err) } } From 120e7d0fda255c77c65b59e9c31cffe03b702349 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:47 +0100 Subject: [PATCH 35/59] refactor: update go.mod and go.sum to include additional indirect dependencies for enhanced functionality --- go.mod | 20 ++++++++++++++++++++ go.sum | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/go.mod b/go.mod index 8fb194a..34322c6 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,26 @@ require ( google.golang.org/protobuf v1.36.10 ) +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.10.1 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect +) + require ( github.com/godbus/dbus v4.1.0+incompatible golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect diff --git a/go.sum b/go.sum index 2391d25..c15576e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,21 @@ +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= +github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -12,6 +28,25 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -26,6 +61,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= From 86ea77be63ad5308fc01f97e6e7e5573ae371146 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:53 +0100 Subject: [PATCH 36/59] refactor: update UnitAction to use RunRetrievalDeprecated for improved compatibility --- pkg/grpchandler/services/v1/services.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/grpchandler/services/v1/services.go b/pkg/grpchandler/services/v1/services.go index 719e0ed..ef1e423 100644 --- a/pkg/grpchandler/services/v1/services.go +++ b/pkg/grpchandler/services/v1/services.go @@ -91,7 +91,7 @@ func (s *HandlerService) UnitAction(_ context.Context, in *v1.ServiceActionReque out = "external bad input" } - _, err = manager.RunRetrieval(conn, true) + _, err = manager.RunRetrievalDeprecated(conn, true) if err != nil { log.Println("failed to do everything") out = "failed even more" From 5889aabfe8f714233b5453922f02650ce3699e65 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:55:04 +0100 Subject: [PATCH 37/59] refactor: update service methods to use deprecated retrieval and improve unit handling --- pkg/grpchandler/services/v2/services.go | 4 +- pkg/grpchandler/services/v3/services.go | 52 ++--- proto/resources/v2/deviceresources.proto | 3 + test/test-all.sh | 254 +++++++++++++++++++++++ 4 files changed, 286 insertions(+), 27 deletions(-) create mode 100755 test/test-all.sh diff --git a/pkg/grpchandler/services/v2/services.go b/pkg/grpchandler/services/v2/services.go index 4a57da7..6ede873 100644 --- a/pkg/grpchandler/services/v2/services.go +++ b/pkg/grpchandler/services/v2/services.go @@ -134,7 +134,7 @@ func (s *HandlerServiceV2) GetAllUnits( from the correct namespace */ - _, err = manager.RunRetrieval(conn, true) + _, err = manager.RunRetrievalDeprecated(conn, true) if err != nil { return &v2.GetUnitsReply{ Success: false, @@ -166,7 +166,7 @@ func (s *HandlerServiceV2) GetLoadedUnits( from the correct namespace */ - _, err = manager.RunRetrieval(conn, false) + _, err = manager.RunRetrievalDeprecated(conn, false) if err != nil { return &v2.GetUnitsReply{ Success: false, diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index a2abed3..48f0c63 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -16,7 +16,7 @@ type HandlerServicev3 struct { v3.UnimplementedHandlerServiceServer } -func mapLoadedUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { +func toGrpcUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { if units == nil { return nil, fmt.Errorf("passed input is nil") @@ -148,15 +148,7 @@ func (s *HandlerServicev3) GetAllUnits( _ *v3.GetUnitsRequest, ) (*v3.GetUnitsReply, error) { - conn, err := dh.CreateSystemBus() - if err != nil { - return &v3.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - units, err := manager.RunRetrieval(conn, true) + units, err := manager.RunRetrieval(true) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -164,10 +156,7 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } - /*** - units is empty here! - */ - mapped, err := mapLoadedUnits(units) + mapped, err := toGrpcUnits(units) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -186,24 +175,28 @@ func (s *HandlerServicev3) GetLoadedUnits( _ *v3.GetUnitsRequest, ) (*v3.GetUnitsReply, error) { - _, err := dh.CreateSystemBus() + in, err := manager.RunRetrieval(false) + if err != nil { + return &v3.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil + Units: nil, + Success: true, + }, fmt.Errorf("failed to run retrieval") } - // units, err := manager.RunRetrieval(conn, false) + out, err := toGrpcUnits(in) + if err != nil { + return &v3.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil + Units: nil, + Success: true, + }, fmt.Errorf("failed to map to grpc types") } return &v3.GetUnitsReply{ - Units: nil, + Units: out, Success: true, }, nil } @@ -243,7 +236,7 @@ func (s *HandlerServicev3) GetFilteredUnits( } } - _, err = manager.MapFilteredUnits(conn, filters) + units, err := manager.MapFilteredUnits(conn, filters) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -251,8 +244,17 @@ func (s *HandlerServicev3) GetFilteredUnits( }, nil } + result, err := toGrpcUnits(units) + + if err != nil { + return &v3.GetUnitsReply{ + Units: nil, + Success: false, + }, fmt.Errorf("failed to parse to grpc units") + } + return &v3.GetUnitsReply{ - Units: nil, + Units: result, Success: true, }, nil } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 35dbd02..6175536 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -7,6 +7,9 @@ option go_package = "paradigm-ehb/agent/gen/resources/v2;resourcespb"; service ResourcesService { rpc GetSystemResources(GetSystemResourcesRequest) returns (GetSystemResourcesResponse); + rpc ProcessAction(ProcessActionRequest) + returns (ProcessActionReply); + } message GetSystemResourcesRequest {} diff --git a/test/test-all.sh b/test/test-all.sh new file mode 100755 index 0000000..c20a1fe --- /dev/null +++ b/test/test-all.sh @@ -0,0 +1,254 @@ +#!/bin/bash +# gRPCurl test commands for all services +# Make sure your gRPC server is running on localhost:5000 (adjust port as needed) +# Install grpcurl: go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest + +SERVER="localhost:5000" + +echo "======================================" +echo "ResourcesServiceV2 Tests" +echo "======================================" + +# Get System Resources +echo -e "\n[1] GetSystemResources" +grpcurl -plaintext \ + -d '{}' \ + $SERVER \ + resources.v2.ResourcesService/GetSystemResources + +# Kill Process (replace with actual PID) +echo -e "\n[2] KillProcess (SIGTERM - signal 15)" +grpcurl -plaintext \ + -d '{ + "pid": 12345, + "signal": 15 + }' \ + $SERVER \ + resources.v2.ResourcesService/KillProcess + +echo -e "\n[3] KillProcess (SIGKILL - signal 9)" +grpcurl -plaintext \ + -d '{ + "pid": 12345, + "signal": 9 + }' \ + $SERVER \ + resources.v2.ResourcesService/KillProcess + +echo "======================================" +echo "HandlerServicev3 Tests (Systemd)" +echo "======================================" + +# Get All Units +echo -e "\n[4] GetAllUnits" +grpcurl -plaintext \ + -d '{}' \ + $SERVER \ + services.v3.HandlerService/GetAllUnits + +# Get Loaded Units +echo -e "\n[5] GetLoadedUnits" +grpcurl -plaintext \ + -d '{}' \ + $SERVER \ + services.v3.HandlerService/GetLoadedUnits + +# Get Filtered Units +echo -e "\n[6] GetFilteredUnits - LOADED" +grpcurl -plaintext \ + -d '{ + "filters": ["LOADED"] + }' \ + $SERVER \ + services.v3.HandlerService/GetFilteredUnits + +echo -e "\n[7] GetFilteredUnits - ERROR and MASKED" +grpcurl -plaintext \ + -d '{ + "filters": ["ERROR", "MASKED"] + }' \ + $SERVER \ + services.v3.HandlerService/GetFilteredUnits + +# Get Unit Status +echo -e "\n[8] GetUnitStatus - nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service" + }' \ + $SERVER \ + services.v3.HandlerService/GetUnitStatus + +echo -e "\n[9] GetUnitStatus - sshd.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "sshd.service" + }' \ + $SERVER \ + services.v3.HandlerService/GetUnitStatus + +# Perform Unit Action - Start +echo -e "\n[10] PerformUnitAction - START nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_ACTION_START" + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitAction + +# Perform Unit Action - Stop +echo -e "\n[11] PerformUnitAction - STOP nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_ACTION_STOP" + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitAction + +# Perform Unit Action - Restart +echo -e "\n[12] PerformUnitAction - RESTART nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_ACTION_RESTART" + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitAction + +# Perform Unit File Action - Enable +echo -e "\n[13] PerformUnitFileAction - ENABLE nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_FILE_ACTION_ENABLE", + "runtime": false, + "force": false + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitFileAction + +# Perform Unit File Action - Disable +echo -e "\n[14] PerformUnitFileAction - DISABLE nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_FILE_ACTION_DISABLE", + "runtime": false, + "force": true + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitFileAction + +echo "======================================" +echo "DeviceActionsService Tests" +echo "======================================" + +# Shutdown (WARNING: Will shutdown the system!) +echo -e "\n[15] Action - SHUTDOWN (commented out for safety)" +# grpcurl -plaintext \ +# -d '{ +# "device_action": "DEVICE_ACTION_SHUTDOWN" +# }' \ +# $SERVER \ +# actions.v1.ActionService/Action + +# Reboot (WARNING: Will reboot the system!) +echo -e "\n[16] Action - REBOOT (commented out for safety)" +# grpcurl -plaintext \ +# -d '{ +# "device_action": "DEVICE_ACTION_REBOOT" +# }' \ +# $SERVER \ +# actions.v1.ActionService/Action + +# Suspend +echo -e "\n[17] Action - SUSPEND" +grpcurl -plaintext \ + -d '{ + "device_action": "DEVICE_ACTION_SUSPEND" + }' \ + $SERVER \ + actions.v1.ActionService/Action + +# Hibernate +echo -e "\n[18] Action - HIBERNATE" +grpcurl -plaintext \ + -d '{ + "device_action": "DEVICE_ACTION_HIBERNATE" + }' \ + $SERVER \ + actions.v1.ActionService/Action + +echo "======================================" +echo "JournalService Tests (Server Streaming)" +echo "======================================" + +# Journal by Systemd Unit +echo -e "\n[19] Action - Get journal for nginx.service" +grpcurl -plaintext \ + -d '{ + "field": 0, + "value": "nginx.service", + "num_from_tail": 100, + "cursor": "", + "path": "" + }' \ + $SERVER \ + journal.v1.JournalService/Action + +# Journal by PID +echo -e "\n[20] Action - Get journal for PID 1" +grpcurl -plaintext \ + -d '{ + "field": 1, + "value": "1", + "num_from_tail": 50, + "cursor": "", + "path": "" + }' \ + $SERVER \ + journal.v1.JournalService/Action + +# Journal by UID +echo -e "\n[21] Action - Get journal for UID 0 (root)" +grpcurl -plaintext \ + -d '{ + "field": 2, + "value": "0", + "num_from_tail": 100, + "cursor": "", + "path": "" + }' \ + $SERVER \ + journal.v1.JournalService/Action + +# Journal by GID +echo -e "\n[22] Action - Get journal for GID 0 (root)" +grpcurl -plaintext \ + -d '{ + "field": 3, + "value": "0", + "num_from_tail": 100, + "cursor": "", + "path": "" + }' \ + $SERVER \ + journal.v1.JournalService/Action + +echo -e "\n======================================" +echo "List Available Services" +echo "======================================" + +grpcurl -plaintext $SERVER list + +echo -e "\n======================================" +echo "Describe a Service" +echo "======================================" + +grpcurl -plaintext $SERVER describe resources.v2.ResourcesService + +echo -e "\n======================================" +echo "Tests Complete!" +echo "======================================" From ae8dc06d846630fccf11f95b76059311124ee144 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 18:42:19 +0100 Subject: [PATCH 38/59] debug: connection closed errror --- internal/dbusservices/manager.go | 2 +- pkg/grpchandler/services/v3/services.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 27fdebc..8d0d27f 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -193,10 +193,10 @@ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { */ func RunRetrieval(requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { conn, err := dbushelper.CreateSystemBus() + if err != nil { return nil, fmt.Errorf("failed to create system bus connection: %w", err) } - defer conn.Close() if requestAllUnitsOnDisk { return MapUnits(conn) diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index 48f0c63..d569141 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -22,7 +22,6 @@ func toGrpcUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { return nil, fmt.Errorf("passed input is nil") } - fmt.Println("input :", units) out := make([]*v3.LoadedUnit, 0, len(units)) @@ -45,7 +44,6 @@ func toGrpcUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { }) } - fmt.Println("out -> ", out) return out, nil } @@ -156,6 +154,7 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } + mapped, err := toGrpcUnits(units) if err != nil { return &v3.GetUnitsReply{ @@ -164,6 +163,7 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } + return &v3.GetUnitsReply{ Units: mapped, Success: true, @@ -246,6 +246,8 @@ func (s *HandlerServicev3) GetFilteredUnits( result, err := toGrpcUnits(units) + fmt.Print("result: ", result) + if err != nil { return &v3.GetUnitsReply{ Units: nil, From e7913de35773bb1e11fe25dfa84353eb859e3143 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 19:57:06 +0100 Subject: [PATCH 39/59] checkpoint: played around with build scripts --- compile.sh | 132 +++-------------------------------------------------- 1 file changed, 6 insertions(+), 126 deletions(-) diff --git a/compile.sh b/compile.sh index d19a4f5..456bd75 100755 --- a/compile.sh +++ b/compile.sh @@ -1,129 +1,9 @@ #!/bin/sh -# echo ------------------------------------------------------------------------- -# echo Description: build go binary with flags -# echo ------------------------------------------------------------------------- -VERSION=1.0 -prod() { - go build \ - -ldflags="-s -w -X main.version=${VERSION}" \ - -o agent \ - ./cmd/agent/agent.go -} +go clean -cache -debug() { - go build \ - -race \ - -gcflags="all=-N -l" \ - -o debug \ - ./cmd/agent/agent.go -} - -valgrind() { - echo "Building with debug symbols for valgrind..." - CGO_CFLAGS="-g -O0" \ - CGO_LDFLAGS="-g" \ - go build \ - -gcflags="all=-N -l" \ - -o debug_valgrind \ - ./cmd/agent/agent.go - - echo "Running valgrind..." - valgrind \ - --leak-check=full \ - --show-leak-kinds=all \ - --track-origins=yes \ - --verbose \ - --log-file=valgrind-out.txt \ - ./debug_valgrind - - echo "Valgrind output saved to valgrind-out.txt" -} - -gdb() { - echo "Building with debug symbols for gdb..." - CGO_CFLAGS="-g -O0" \ - CGO_LDFLAGS="-g" \ - go build \ - -gcflags="all=-N -l" \ - -o debug_gdb \ - ./cmd/agent/agent.go - - echo "Starting gdb..." - gdb ./debug_gdb -} - -core() { - echo "Building with debug symbols..." - CGO_CFLAGS="-g -O0" \ - CGO_LDFLAGS="-g" \ - go build \ - -gcflags="all=-N -l" \ - -o debug_core \ - ./cmd/agent/agent.go - - echo "Enabling core dumps..." - ulimit -c unlimited - - echo "Running program (will generate core dump on crash)..." - ./debug_core - - # Check if core dump was created - if [ -f core ]; then - echo "Core dump generated. Starting gdb..." - gdb ./debug_core core - elif [ -f core.* ]; then - CORE_FILE=$(ls -t core.* | head -n1) - echo "Core dump generated: $CORE_FILE. Starting gdb..." - gdb ./debug_core "$CORE_FILE" - else - echo "No core dump found. Program may have exited normally." - echo "Core dumps might be in: /var/lib/systemd/coredump/ or /var/crash/" - echo "Check with: coredumpctl list" - fi -} - -sanitize() { - echo "Building with address sanitizer..." - CGO_CFLAGS="-fsanitize=address -g -O0" \ - CGO_LDFLAGS="-fsanitize=address" \ - go build \ - -gcflags="all=-N -l" \ - -o debug_asan \ - ./cmd/agent/agent.go - - echo "Running with address sanitizer..." - ASAN_OPTIONS=detect_leaks=1:halt_on_error=0 ./debug_asan -} - -case "$1" in - prod) - prod - ;; - debug) - debug - ;; - valgrind) - valgrind - ;; - gdb) - gdb - ;; - core) - core - ;; - sanitize|asan) - sanitize - ;; - *) - echo "Usage: $0 [prod|debug|valgrind|gdb|core|sanitize]" - echo "" - echo " prod - Production build (stripped, optimized)" - echo " debug - Debug build with race detector" - echo " valgrind - Build and run with valgrind memory checker" - echo " gdb - Build and start gdb debugger" - echo " core - Build, enable core dumps, run and analyze crash" - echo " sanitize - Build and run with address sanitizer (ASAN)" - exit 1 - ;; -esac +if CGO_ENABLED=1 go build -race -gcflags="all=-N -l" -o debug ./cmd/agent/agent.go; then + exec ./debug +else + exit 1 +fi From 439a70063b32324f4f356b25973cd19d9b4fe026 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 19:58:06 +0100 Subject: [PATCH 40/59] feature: implemented total time and idle time in protobbufs and cgo wrapper --- cmd/agent/agent.go | 1 + pkg/agent-resources | 2 +- pkg/cgowrap/types.go | 2 ++ pkg/cgowrap/wrapper.go | 3 +++ proto/resources/v2/deviceresources.proto | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 47d1a6b..1c8df7a 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -39,6 +39,7 @@ import ( "paradigm-ehb/agent/pkg/grpchandler" + resourcesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/resources/v1" resourcesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/resources/v2" diff --git a/pkg/agent-resources b/pkg/agent-resources index f9e91ef..f9aa0f4 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit f9e91ef837924ba8ece0d2ed626f23747bbc9788 +Subproject commit f9aa0f4c68b0ed5771077c308107a731f43f2da8 diff --git a/pkg/cgowrap/types.go b/pkg/cgowrap/types.go index 1d42440..2e2d769 100644 --- a/pkg/cgowrap/types.go +++ b/pkg/cgowrap/types.go @@ -6,6 +6,8 @@ type Cpu struct { Model string Frequency string MaxCore uint32 + TotalTime uint64 + IdleTime uint64 } // Ram represents RAM information including total and free memory. diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index ff0c9cf..d4d1147 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -321,6 +321,9 @@ func CpuRead(c *C.Cpu) (Cpu, error) { Model: C.GoString(&c.model[0]), Frequency: C.GoString(&c.frequency[0]), MaxCore: uint32(c.cores), + TotalTime: uint64(c.total_time), + IdleTime: uint64(c.idle_time), + } return cpu, nil } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 6175536..f811a98 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -42,6 +42,8 @@ message Cpu { string model = 2; string frequency = 3; uint32 max_core = 4; + uint64 total_time = 5; + uint64 idle_time = 6; } /** From a8d8ebc7f1158556c42ddccc16ccad6ffa6da544 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 21:47:00 +0100 Subject: [PATCH 41/59] feature: updated scripts for correct versiona and correct namings --- test/grpc_resources.sh | 4 ++-- test/test-all.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/grpc_resources.sh b/test/grpc_resources.sh index 5ea9c84..4e7b93b 100755 --- a/test/grpc_resources.sh +++ b/test/grpc_resources.sh @@ -13,7 +13,7 @@ command -v grpcurl >/dev/null 2>&1 || { } echo "== Checking service availability ==" -grpcurl -plaintext "$ADDR" list resources.v1.ResourcesService >/dev/null +grpcurl -plaintext "$ADDR" list resources.v2.ResourcesService >/dev/null echo "OK" echo @@ -21,7 +21,7 @@ echo "== Calling GetSystemResources ==" grpcurl -plaintext \ -format text \ "$ADDR" \ - resources.v1.ResourcesService/GetSystemResources + resources.v2.ResourcesService/GetSystemResources echo echo "== Test completed ==" diff --git a/test/test-all.sh b/test/test-all.sh index c20a1fe..a787032 100755 --- a/test/test-all.sh +++ b/test/test-all.sh @@ -191,7 +191,7 @@ grpcurl -plaintext \ -d '{ "field": 0, "value": "nginx.service", - "num_from_tail": 100, + "numFromTail": 100, "cursor": "", "path": "" }' \ From 9095621492f8bff823eff374dab470e4bb0f28f5 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 21:47:39 +0100 Subject: [PATCH 42/59] bugfix: fixed memory issue --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index f9aa0f4..f654d6e 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit f9aa0f4c68b0ed5771077c308107a731f43f2da8 +Subproject commit f654d6e475b562ced341ba57108c55e1ca37f81b From 83541a1f30ca10df09b269435a56c3559c0dd82d Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 21:48:03 +0100 Subject: [PATCH 43/59] feature: added idle time and total time to cpu resources --- cmd/agent/agent.go | 4 +++- internal/dbusservices/manager.go | 7 +++++-- pkg/cgowrap/wrapper.go | 7 +++++-- pkg/grpchandler/resources/v2/resources.go | 2 ++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 1c8df7a..f42057f 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -23,6 +23,8 @@ import ( "paradigm-ehb/agent/gen/greet" "paradigm-ehb/agent/gen/journal/v1" + "log" + servicesV1 "paradigm-ehb/agent/gen/services/v1" servicesV2 "paradigm-ehb/agent/gen/services/v2" servicesV3 "paradigm-ehb/agent/gen/services/v3" @@ -116,7 +118,7 @@ func main() { /** Any error other than "address already in use" is fatal. */ - fmt.Println("failed to listen:", err) + log.Printf("failed to listen:", err) return } break diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 8d0d27f..48cee6a 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -38,10 +38,13 @@ func RunSymlinkAction( enableForce bool, service []string) error { - obj, _ := dbushelper.CreateSystemdObject(conn) + obj, err := dbushelper.CreateSystemdObject(conn) + if err != nil { + return fmt.Errorf("failed to create systemd object %v", err) + } if !obj.Path().IsValid() { - fmt.Println("invalid systemd path") + return fmt.Errorf("invalid systemd path") } /** EnableUnitFiles(in as files, in b runtime, in b force, out b carries_install_info, out a(sss) changes); */ diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index d4d1147..ff7e648 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -316,14 +316,17 @@ func CpuRead(c *C.Cpu) (Cpu, error) { return Cpu{}, fmt.Errorf("failed to read CPU information") } + if C.cpu_read_usage(c) != C.OK { + return Cpu{}, fmt.Errorf("failed to read CPU information") + } + cpu := Cpu{ Vendor: C.GoString(&c.vendor[0]), Model: C.GoString(&c.model[0]), Frequency: C.GoString(&c.frequency[0]), MaxCore: uint32(c.cores), TotalTime: uint64(c.total_time), - IdleTime: uint64(c.idle_time), - + IdleTime: uint64(c.idle_time), } return cpu, nil } diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go index c59d02f..69e3c10 100644 --- a/pkg/grpchandler/resources/v2/resources.go +++ b/pkg/grpchandler/resources/v2/resources.go @@ -113,6 +113,8 @@ func mapCPU(c cgo.Cpu) *proto.Cpu { Model: c.Model, Frequency: c.Frequency, MaxCore: c.MaxCore, + TotalTime: c.TotalTime, + IdleTime: c.IdleTime, } } From 057326279f474d428b75092763630168935aafae Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:17:04 +0100 Subject: [PATCH 44/59] refactore: improved documentation and todos in certain features --- cmd/agent/agent.go | 170 ++++++++++++++++++--------- internal/dbusservices/manager.go | 195 ++++++++++++++++++++++++------- internal/journal/journal.go | 158 ++++++++++++++++--------- pkg/cgowrap/wrapper.go | 9 +- pkg/grpchandler/journal.go | 57 +++++++-- 5 files changed, 421 insertions(+), 168 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index f42057f..a72b10b 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -1,16 +1,18 @@ -// Package main boots the Paradigm Agent gRPC server. -// -// Responsibilities: -// - Parse runtime flags (IP, port, diagnostics) -// - Bind a TCP listener with automatic port fallback -// - Initialize and register all gRPC services -// - Expose health and reflection endpoints -// -// Design notes: -// - The server must be able to start even if the preferred port is occupied. -// - Reflection is enabled by default for debugging and introspection. -// - Diagnostics are intentionally decoupled from server startup to avoid -// blocking gRPC reflection and request handling. +/* +Package main boots the Paradigm Agent gRPC server. + +Responsibilities: +- Parse runtime flags (IP, port, diagnostics) +- Bind a TCP listener with automatic port fallback +- Initialize and register all gRPC services +- Expose health and reflection endpoints + +Design notes: +- The server must be able to start even if the preferred port is occupied. +- Reflection is enabled by default for debugging and introspection. +- Diagnostics are intentionally decoupled from server startup to avoid + blocking gRPC reflection and request handling. +*/ package main import ( @@ -41,7 +43,6 @@ import ( "paradigm-ehb/agent/pkg/grpchandler" - resourcesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/resources/v1" resourcesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/resources/v2" @@ -54,48 +55,68 @@ import ( ) var ( - /** - diagnostics enables periodic runtime diagnostics such as - resource usage, process health, and connectivity checks. - NOTE: When enabled, diagnostics must never block the gRPC server. - **/ + /* + diagnostics enables periodic runtime diagnostics such as + resource usage, process health, and connectivity checks. - diagnostics = flag.Bool("diagnostics", false, "run runtime diagnostics") + TODO: + - Add structured configuration for diagnostics intervals. + - Allow diagnostics to be toggled or reconfigured at runtime. + - Ensure diagnostics respect context cancellation on shutdown. - /** - portFlag is the preferred TCP port to bind the gRPC server to. - If unavailable, the server will increment the port until a free - one is found. + NOTE: + When enabled, diagnostics must never block the gRPC server. */ + diagnostics = flag.Bool("diagnostics", true, "run runtime diagnostics") + + /* + portFlag is the preferred TCP port to bind the gRPC server to. + + If unavailable: + - The server increments the port until a free one is found. + TODO: + - Log the final selected port explicitly. + - Optionally expose the selected port via diagnostics or metadata. + */ portFlag = flag.Int("port", 5000, "port to listen on") - /** - ipFlag defines the IP address diagnostics may use when reporting - or exposing runtime information. + /* + ipFlag defines the IP address diagnostics may use when reporting + or exposing runtime information. + + TODO: + - Validate IP format early. + - Clarify distinction between bind address vs diagnostics address. */ ipFlag = flag.String("ip", "0.0.0.0", "ip addr") ) func main() { - /** + /* Parse command-line flags before any runtime behavior. - */ + TODO: + - Add validation for flag combinations. + - Print effective configuration at startup. + */ flag.Parse() - /** + /* Attempt to bind a TCP listener. Strategy: - - Start with the requested port - - If EADDRINUSE is encountered, increment the port and retry - - Fail hard on any other error + - Start with the requested port + - If EADDRINUSE is encountered, increment the port and retry + - Fail hard on any other error This guarantees the agent can always start, even in constrained or multi-agent environments. + TODO: + - Add an upper bound to port scanning. + - Support IPv6 or configurable network protocols. */ var lis net.Listener var err error @@ -115,90 +136,125 @@ func main() { } } - /** + /* Any error other than "address already in use" is fatal. + + TODO: + - Emit structured logs. + - Exit with non-zero status code. */ - log.Printf("failed to listen:", err) + log.Printf("failed to listen: %v", err) return } break } - /** + /* Create the gRPC server instance. + + TODO: + - Configure server options (timeouts, interceptors, limits). + - Add graceful shutdown handling. */ server := grpc.NewServer() - /** + /* Health server is used by orchestration systems (systemd, Kubernetes, external monitors) to determine liveness and readiness. + + TODO: + - Set explicit serving statuses per service. */ healthServer := health.NewServer() grpc_health_v1.RegisterHealthServer(server, healthServer) - /** - Register all application services. + /* + Register all application services. - Each service implements a distinct responsibility: - - Greeter: connectivity / handshake testing - - HandlerService: service lifecycle and orchestration - - JournalService: event and state journaling - - ResourcesService: system resource inspection and reporting + Each service implements a distinct responsibility: + - Greeter: connectivity / handshake testing + - HandlerService: service lifecycle and orchestration + - JournalService: event and state journaling + - ResourcesService: system resource inspection and reporting + TODO: + - Centralize service registration. + - Version-gate deprecated service versions. */ greet.RegisterGreeterServer( server, - &grpc_handler.GreeterServer{}) + &grpc_handler.GreeterServer{}, + ) servicesV1.RegisterHandlerServiceServer( server, - &servicesHandlerV1.HandlerService{}) + &servicesHandlerV1.HandlerService{}, + ) servicesV2.RegisterHandlerServiceServer( server, - &servicesHandlerV2.HandlerServiceV2{}) + &servicesHandlerV2.HandlerServiceV2{}, + ) servicesV3.RegisterHandlerServiceServer( server, - &servicesHandlerV3.HandlerServicev3{}) + &servicesHandlerV3.HandlerServicev3{}, + ) journal.RegisterJournalServiceServer( server, - &grpc_handler.JournalService{}) + &grpc_handler.JournalService{}, + ) resourcesv1.RegisterResourcesServiceServer( server, - &resourcesHandlerV1.ResourcesService{}) + &resourcesHandlerV1.ResourcesService{}, + ) resourcesv2.RegisterResourcesServiceServer( server, - &resourcesHandlerV2.ResourcesServiceV2{}) + &resourcesHandlerV2.ResourcesServiceV2{}, + ) devacpb.RegisterActionServiceServer( server, - &grpc_handler.DeviceActionsService{}) + &grpc_handler.DeviceActionsService{}, + ) - /** + /* Enable gRPC reflection unconditionally. This allows tools such as grpcurl and gRPC UI to inspect services and message schemas at runtime. + + TODO: + - Make reflection configurable for production environments. */ reflection.Register(server) - fmt.Printf("\nserver listening at %v\n", lis.Addr()) + + log.Printf("\nserver listening at %v\n", lis.Addr()) if *diagnostics { + /* + TODO: + - Tie diagnostics lifecycle to server context. + - Ensure diagnostics terminate on server shutdown. + */ go platform.RunRuntimeDiagnostics(time.Second*2, *ipFlag, *portFlag) - } - /** + /* Start serving requests. + This call blocks until the server is stopped or encounters a fatal error. + + TODO: + - Implement graceful shutdown (signals, context). + - Flush diagnostics and logs before exit. */ if err := server.Serve(lis); err != nil { - fmt.Println("failed to serve:", err) + log.Printf("failed to serve: %v", err) } } diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 48cee6a..c801519 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -10,13 +10,30 @@ import ( "github.com/godbus/dbus" ) -// @param, action [start, stop, restart], symLinkAction [enable, disable], service name format "example.service" +/* +RunAction executes a systemd unit action (start, stop, restart). + +Parameters: +- conn: + Active D-Bus connection. +- ac: + Unit action to execute (start, stop, restart). +- service: + Unit name in systemd format (e.g. "example.service"). + +TODO: +- Validate service name format before invoking D-Bus. +- Propagate context / cancellation support. +- Replace raw string conversion with strongly typed D-Bus method mapping. +*/ func RunAction( conn *dbus.Conn, ac systemd.UnitAction, - service string) error { + service string, +) error { obj, _ := dbushelper.CreateSystemdObject(conn) + if !obj.Path().IsValid() { return fmt.Errorf("object path is invalid") } @@ -24,19 +41,39 @@ func RunAction( call := obj.Call(string(ac), 0, service, "replace") if call.Err != nil { - return fmt.Errorf("failed to execute object on in unit action, %v", call.Err) + return fmt.Errorf("failed to execute object on unit action, %v", call.Err) } return nil } -// @param, action [start, stop, restart], symLinkAction [enable, disable], service name format "example.service" +/* +RunSymlinkAction executes systemd unit file actions (enable / disable). + +Parameters: +- conn: + Active D-Bus connection. +- sc: + Unit file action (enable or disable). +- enableForRunTime: + Whether the action applies only at runtime. +- enableForce: + Whether to force-enable units (only relevant for enable). +- service: + Slice of unit names. + +TODO: +- Validate service slice is non-empty. +- Clarify runtime vs persistent semantics in API naming. +- Normalize error messages. +*/ func RunSymlinkAction( conn *dbus.Conn, sc systemd.UnitFileAction, enableForRunTime bool, enableForce bool, - service []string) error { + service []string, +) error { obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { @@ -47,18 +84,43 @@ func RunSymlinkAction( return fmt.Errorf("invalid systemd path") } - /** EnableUnitFiles(in as files, in b runtime, in b force, out b carries_install_info, out a(sss) changes); */ - /** DisableUnitFiles(in as files, in b runtime, out a(sss) changes); */ + /* + EnableUnitFiles( + in as files, + in b runtime, + in b force, + out b carries_install_info, + out a(sss) changes + ) + + DisableUnitFiles( + in as files, + in b runtime, + out a(sss) changes + ) + */ switch sc { case systemd.UnitFileActionEnable: - call := obj.Call(string(sc), dbus.FlagAllowInteractiveAuthorization, service, enableForRunTime, enableForce) + call := obj.Call( + string(sc), + dbus.FlagAllowInteractiveAuthorization, + service, + enableForRunTime, + enableForce, + ) if call.Err != nil { return fmt.Errorf("error %v", call.Err) } + case systemd.UnitFileActionDisable: - call := obj.Call(string(sc), dbus.FlagAllowInteractiveAuthorization, service, enableForRunTime) + call := obj.Call( + string(sc), + dbus.FlagAllowInteractiveAuthorization, + service, + enableForRunTime, + ) if call.Err != nil { return fmt.Errorf("something happened here %v", call.Err) } @@ -67,9 +129,23 @@ func RunSymlinkAction( return nil } +/* +UnitStatus retrieves the status of a single systemd unit. + +Parameters: +- obj: + Systemd D-Bus object. +- name: + Unit name. + +TODO: +- Replace string status with structured state representation. +- Normalize error return values. +*/ func UnitStatus( obj dbus.BusObject, - name string) (string, error) { + name string, +) (string, error) { out, err := systemd.GetStatusCall(obj, name) if err != nil { @@ -77,28 +153,32 @@ func UnitStatus( } return out, nil - } -/** - * Map to the correct types - */ +/* +MapLoadedUnits maps loaded systemd units to internal LoadedUnit types. + +TODO: +- Remove duplicated mapping logic across unit retrieval functions. +- Fix QueudJob typo once wire format compatibility is resolved. +*/ func MapLoadedUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { + obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { return nil, fmt.Errorf("failed to create systemd object: %w", err) } - + loaded, err := systemd.GetLoadedUnits(obj) if err != nil { return nil, fmt.Errorf("failed to get loaded units: %w", err) } - + parsed, err := dbushelper.ParseLoadedUnits(loaded) if err != nil { return nil, fmt.Errorf("failed to parse loaded units: %w", err) } - + units := make([]*types.LoadedUnit, 0, len(parsed)) for _, u := range parsed { units = append(units, &types.LoadedUnit{ @@ -109,30 +189,42 @@ func MapLoadedUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { ActiveState: u.ActiveState, DepUnit: u.DepUnit, ObjectPath: u.ObjectPath, - QueudJob: u.QueudJob, // Keep the typo for consistency + QueudJob: u.QueudJob, /* keep typo for consistency */ JobType: u.JobType, JobPath: u.JobPath, }) } + return units, nil } -func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, error) { +/* +MapFilteredUnits retrieves and maps filtered systemd units. + +TODO: +- Replace placeholder "Not Available" strings with optional fields. +- Clarify which fields are guaranteed by GetUnitsFiltered. +*/ +func MapFilteredUnits( + conn *dbus.Conn, + filters []string, +) ([]*types.LoadedUnit, error) { + obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { return nil, fmt.Errorf("failed to create systemd object: %w", err) } - + entries, err := systemd.GetUnitsFiltered(obj, filters) if err != nil { return nil, fmt.Errorf("failed to get filtered units: %w", err) } - + parsed, err := dbushelper.ParseLoadedUnits(entries) if err != nil { return nil, fmt.Errorf("failed to parse filtered units: %w", err) } - + units := make([]*types.LoadedUnit, 0, len(parsed)) for _, e := range parsed { units = append(units, &types.LoadedUnit{ @@ -148,28 +240,34 @@ func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, e JobPath: "Not Available", }) } + return units, nil } -/** - * Fill in proper unit types - */ +/* +MapUnits retrieves and maps all systemd units. + +TODO: +- Distinguish unit-on-disk vs loaded semantics at the type level. +- Avoid repeating placeholder field values. +*/ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { + obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { return nil, fmt.Errorf("failed to create systemd object: %w", err) } - + result, err := systemd.GetUnits(obj) if err != nil { return nil, fmt.Errorf("failed to get units: %w", err) } - + parsedUnits, err := dbushelper.ParseUnits(result) if err != nil { return nil, fmt.Errorf("failed to parse units: %w", err) } - + units := make([]*types.LoadedUnit, 0, len(parsedUnits)) for _, e := range parsedUnits { units = append(units, &types.LoadedUnit{ @@ -185,34 +283,51 @@ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { JobPath: "Not Available", }) } + return units, nil } -/** - * @param requestAllUnitsOnDisk: true for all units on disk, false for loaded units - * A loaded unit is a unit that has been activated before and is available in memory - * for the server to start up - * @return []*types.LoadedUnit, error - */ +/* +RunRetrieval retrieves units using an internally created system bus connection. + +Parameters: +- requestAllUnitsOnDisk: + true -> retrieve all units on disk + false -> retrieve only loaded units + +TODO: +- Close system bus connection explicitly. +- Propagate context. +*/ func RunRetrieval(requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { - conn, err := dbushelper.CreateSystemBus() + conn, err := dbushelper.CreateSystemBus() if err != nil { return nil, fmt.Errorf("failed to create system bus connection: %w", err) } - + if requestAllUnitsOnDisk { return MapUnits(conn) } - + return MapLoadedUnits(conn) } -func RunRetrievalDeprecated(conn *dbus.Conn, requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { - +/* +RunRetrievalDeprecated performs unit retrieval using a caller-provided connection. + +TODO: +- Remove once all call sites migrate to RunRetrieval. +- Clearly document ownership of conn lifecycle. +*/ +func RunRetrievalDeprecated( + conn *dbus.Conn, + requestAllUnitsOnDisk bool, +) ([]*types.LoadedUnit, error) { + if requestAllUnitsOnDisk { return MapUnits(conn) } - + return MapLoadedUnits(conn) } diff --git a/internal/journal/journal.go b/internal/journal/journal.go index e963cf2..dcbb9b0 100644 --- a/internal/journal/journal.go +++ b/internal/journal/journal.go @@ -4,34 +4,38 @@ import ( "io" "time" - "fmt" + "log" - jrnl "github.com/coreos/go-systemd/v22/journal" - sdj "github.com/coreos/go-systemd/v22/sdjournal" + "github.com/coreos/go-systemd/v22/journal" + "github.com/coreos/go-systemd/v22/sdjournal" ) -// checkJournal reports whether systemd’s journal is available and enabled -// on the current system. -// -// It is a lightweight capability check and does not open or read the journal. -// Internally, this relies on libsystemd to detect whether journald is usable -// (for example, not present on non-systemd systems). +/* +checkJournal reports whether systemd’s journal is available and enabled +on the current system. + +It is a lightweight capability check and does not open or read the journal. +Internally, this relies on libsystemd to detect whether journald is usable +(for example, not present on non-systemd systems). +*/ func checkJournal() bool { - return jrnl.Enabled() + return journal.Enabled() } -// systemdID returns the boot ID associated with the currently running system. -// -// The boot ID uniquely identifies the current boot session and is useful for -// correlating journal entries to a specific system start. The function opens -// the journal, queries the boot ID, and then closes the journal handle. -// -// If the journal cannot be opened or the boot ID cannot be retrieved, the -// returned string may be empty. +/* +systemdID returns the boot ID associated with the currently running system. + +The boot ID uniquely identifies the current boot session and is useful for +correlating journal entries to a specific system start. The function opens +the journal, queries the boot ID, and then closes the journal handle. + +If the journal cannot be opened or the boot ID cannot be retrieved, the +returned string may be empty. +*/ func systemdID() (string, error) { - j, err := sdj.NewJournal() + j, err := sdjournal.NewJournal() if err != nil { return "not available", err } @@ -43,35 +47,68 @@ func systemdID() (string, error) { } return bid, nil - } -// TODO(nasr): checkout formatters - -// GetJournalInformation GetJournaldInformation reads entries from the systemd journal and returns -// them as a single concatenated string. -// -// The journal reader is configured through the provided parameters: -// - since: limits entries to those newer than the given duration -// relative now -// - numFromTail: limits the number of entries read from the end of the journal -// - cursor: reserved for future cursor-based positioning (currently unused) -// - matches: filters entries using systemd journal match rules -// - path: optionally specifies a custom journal path -// -// Internally, this function uses a JournalReader and performs sequential reads -// into a fixed-size buffer until no more data is available or an error occurs. -// The caller receives raw journal output as text, without further parsing or -// field-level decoding. -// -// Example Matches: []sdj.Match{{Field: "_SYSTEMD_UNIT", Value: "ssh.service"}}} -func GetJournalInformation(since time.Duration, numFromTail uint64, cursor string, matches []sdj.Match, path string, out chan []byte) { - +/* +TODO(nasr): +- Evaluate and implement journal output formatters. +- Decide whether formatting should be: + - raw text (current behavior), + - structured (map / proto), + - or selectable via configuration. +*/ + +/* +GetJournalInformation reads entries from the systemd journal and streams them +through the provided output channel. + +Parameters: +- since: + Duration relative to "now" used to limit journal entries. + (Currently not implemented.) +- numFromTail: + Limits the number of entries read from the end of the journal. +- cursor: + Reserved for cursor-based positioning (currently unused). +- matches: + Journal match filters (field/value pairs). +- path: + Optional custom journal path. +- out: + Output channel receiving raw journal byte slices. + +Behavior: +- Opens a JournalReader with the provided configuration. +- Reads sequentially into a fixed-size buffer. +- Streams raw journal output without parsing or decoding fields. +- Closes the output channel before returning. + +Example: + []sdj.Match{{Field: "_SYSTEMD_UNIT", Value: "ssh.service"}} +*/ +func GetJournalInformation( + since time.Duration, + numFromTail uint64, + cursor string, + matches []sdjournal.Match, + path string, + out chan []byte, +) { + + /* + TODO(nasr): + - Define ownership and lifecycle rules for `out`. + - Document that this function is responsible for closing the channel. + */ defer close(out) - config := sdj.JournalReaderConfig{ - // TODO(nasr): fix time imlementation - //Since: since, + config := sdjournal.JournalReaderConfig{ + /* + TODO(nasr): + - Implement time-based filtering using `since`. + - Decide whether `since` should override cursor semantics. + */ + // Since: since, NumFromTail: numFromTail, Cursor: cursor, Matches: matches, @@ -79,10 +116,14 @@ func GetJournalInformation(since time.Duration, numFromTail uint64, cursor strin Formatter: nil, } - reader, err := sdj.NewJournalReader(config) - + reader, err := sdjournal.NewJournalReader(config) if err != nil { - fmt.Println("failed to open the journal reader") + /* + TODO(nasr): + - Replace stdout logging with structured error propagation. + - Decide whether to terminate early or send error markers via channel. + */ + } defer reader.Close() @@ -93,23 +134,36 @@ func GetJournalInformation(since time.Duration, numFromTail uint64, cursor strin c, err := reader.Read(b) if err == io.EOF { - fmt.Println("End of journal") + /* + TODO(nasr): + - Decide whether EOF should be silent. + - Avoid stdout logging in library code. + */ + log.Printf("end of journal %v, ", err) break } if c == 0 { - fmt.Println(" data") + /* + TODO(nasr): + - Clarify whether zero-length reads are expected. + - Remove noisy logging or replace with debug-level tracing. + */ continue } if err != nil { + /* + TODO(nasr): + - Avoid sending placeholder data on error. + - Define a structured error signaling mechanism. + - Consider context cancellation or error channels. + */ out <- []byte("nothing in here") - fmt.Println("failed to read from the journal reader", err) + log.Printf("no more data to read %v, ", err) break } - out <- b[:c] - } } diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index ff7e648..b6e84be 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -44,15 +44,12 @@ process_read2(i32 pid, Process *out) size_t len = strcspn(val, "\n"); - printf("lenght: %lu", len); if (!strncmp(buf, "Name:", 5)) { memcpy(out->name, val, len); - printf("\nout name %s\n", out->name); } - printf("name: %s", out->name); if (!strncmp(buf, "State:", 6)) { char state_char = 0; @@ -66,8 +63,6 @@ process_read2(i32 pid, Process *out) } - printf("\nstate char: %d\n", out->pid); - printf("\nstate char: %c\n", state_char); switch (state_char) { case 'R': @@ -124,7 +119,6 @@ process_read2(i32 pid, Process *out) } } - printf("sizeof(Process) = %zu\n", sizeof(Process)); int error = fclose(fp); if (error != 0) @@ -132,7 +126,6 @@ process_read2(i32 pid, Process *out) return ERR_IO; } - printf("\n\n\n\nout state: %d\n", out->state); return OK; } */ @@ -545,7 +538,7 @@ func ReadProcesses(device *C.Device) ([]Process, error) { err := C.process_read2(p.pid, p) if err != C.OK { - fmt.Printf("failed reading processes") + return nil, fmt.Errorf("failed reading processes %v", err) } procs = append(procs, Process{ diff --git a/pkg/grpchandler/journal.go b/pkg/grpchandler/journal.go index 8f60f58..7bf9801 100644 --- a/pkg/grpchandler/journal.go +++ b/pkg/grpchandler/journal.go @@ -16,7 +16,15 @@ type JournalService struct { func (s *JournalService) Action(in *journal.JournalRequest, srv journal.JournalService_ActionServer) error { var val string - // TODO(nasr): remove the magic number enums, horrible code practice + /* + TODO(nasr): + - Remove magic-number–based enum handling. + - Replace `switch in.Field` with: + - a typed protobuf enum, or + - a map[JournalField]string lookup, or + - explicit constants with semantic names. + - Ensure invalid enum values are handled (default case + error). + */ switch in.Field { case 0: @@ -31,30 +39,51 @@ func (s *JournalService) Action(in *journal.JournalRequest, srv journal.JournalS m := []sdjournal.Match{{Field: val, Value: in.Value}} - // Generated time. testing issue - // TODO(nasr): fix the time - //sinceTime, err := time.Parse(time.RFC3339, "0") - // - //if err != nil { - // fmt.Println("error parsing time:", err) - // return nil, nil - //} - // - //duration := time.Since(sinceTime) + /* + TODO(nasr): + - Implement proper time filtering support. + - Decide on API semantics: + - absolute timestamp vs relative duration + - server-side vs client-provided time window + - Validate time parsing errors and propagate them via gRPC status. + - Remove dead/commented-out code once design is finalized. + */ var wg sync.WaitGroup ch := make(chan []byte) + /* + TODO(nasr): + - Define clear channel ownership: + - who closes `ch` and when. + - Consider making channel buffered to avoid producer/consumer blocking. + - Document lifetime guarantees between goroutines. + */ + wg.Add(2) go func() { defer wg.Done() + /* + TODO(nasr): + - Propagate context cancellation into GetJournalInformation. + - Return errors instead of silent failure. + - Clarify meaning of leading `0` argument. + */ j.GetJournalInformation(0, in.NumFromTail, in.Cursor, m, in.Path, ch) }() go func() { defer wg.Done() + /* + TODO(nasr): + - Fix channel consumption logic: + current code ranges over `ch` but also performs `<-ch` again. + - Avoid double reads from the same channel. + - Handle channel close explicitly. + - Propagate send errors via context cancellation or status return. + */ for range ch { resp := journal.JournalChunk{Reply: <-ch} if err := srv.Send(&resp); err != nil { @@ -63,6 +92,12 @@ func (s *JournalService) Action(in *journal.JournalRequest, srv journal.JournalS } }() + /* + TODO(nasr): + - Consider early exit on client disconnect. + - Avoid waiting indefinitely if goroutines deadlock. + - Evaluate replacing WaitGroup + channels with errgroup + context. + */ wg.Wait() return nil } From 96ef9bc73d05c9cb258e49dff1d3c4cf9b73c4ad Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:24:50 +0100 Subject: [PATCH 45/59] refactor: test names --- test/{grpc_resources.sh => grpc_resources_test.sh} | 0 test/{grpc_res_v2.sh => grpc_resources_v2_test.sh} | 0 test/{grpc_services.sh => grpc_services_test.sh} | 0 test/{gprc_services_v3.sh => grpc_services_v3_test.sh} | 0 test/{setup_test.go => integration_setup_test.go} | 0 test/{action_test.go => journal_action_test.go} | 0 test/{journal_test.go => journal_service_test.go} | 0 test/{resources_test.go => resources_service_test.go} | 0 test/{test-all.sh => run_all_tests.sh} | 5 ++--- .../{services_v1_test.go => services_v1_integration_test.go} | 0 .../{services_v2_test.go => services_v2_integration_test.go} | 0 11 files changed, 2 insertions(+), 3 deletions(-) rename test/{grpc_resources.sh => grpc_resources_test.sh} (100%) rename test/{grpc_res_v2.sh => grpc_resources_v2_test.sh} (100%) rename test/{grpc_services.sh => grpc_services_test.sh} (100%) rename test/{gprc_services_v3.sh => grpc_services_v3_test.sh} (100%) rename test/{setup_test.go => integration_setup_test.go} (100%) rename test/{action_test.go => journal_action_test.go} (100%) rename test/{journal_test.go => journal_service_test.go} (100%) rename test/{resources_test.go => resources_service_test.go} (100%) rename test/{test-all.sh => run_all_tests.sh} (98%) rename test/{services_v1_test.go => services_v1_integration_test.go} (100%) rename test/{services_v2_test.go => services_v2_integration_test.go} (100%) diff --git a/test/grpc_resources.sh b/test/grpc_resources_test.sh similarity index 100% rename from test/grpc_resources.sh rename to test/grpc_resources_test.sh diff --git a/test/grpc_res_v2.sh b/test/grpc_resources_v2_test.sh similarity index 100% rename from test/grpc_res_v2.sh rename to test/grpc_resources_v2_test.sh diff --git a/test/grpc_services.sh b/test/grpc_services_test.sh similarity index 100% rename from test/grpc_services.sh rename to test/grpc_services_test.sh diff --git a/test/gprc_services_v3.sh b/test/grpc_services_v3_test.sh similarity index 100% rename from test/gprc_services_v3.sh rename to test/grpc_services_v3_test.sh diff --git a/test/setup_test.go b/test/integration_setup_test.go similarity index 100% rename from test/setup_test.go rename to test/integration_setup_test.go diff --git a/test/action_test.go b/test/journal_action_test.go similarity index 100% rename from test/action_test.go rename to test/journal_action_test.go diff --git a/test/journal_test.go b/test/journal_service_test.go similarity index 100% rename from test/journal_test.go rename to test/journal_service_test.go diff --git a/test/resources_test.go b/test/resources_service_test.go similarity index 100% rename from test/resources_test.go rename to test/resources_service_test.go diff --git a/test/test-all.sh b/test/run_all_tests.sh similarity index 98% rename from test/test-all.sh rename to test/run_all_tests.sh index a787032..25ad4ce 100755 --- a/test/test-all.sh +++ b/test/run_all_tests.sh @@ -204,7 +204,6 @@ grpcurl -plaintext \ -d '{ "field": 1, "value": "1", - "num_from_tail": 50, "cursor": "", "path": "" }' \ @@ -217,7 +216,7 @@ grpcurl -plaintext \ -d '{ "field": 2, "value": "0", - "num_from_tail": 100, + "numFromTail": 100, "cursor": "", "path": "" }' \ @@ -230,7 +229,7 @@ grpcurl -plaintext \ -d '{ "field": 3, "value": "0", - "num_from_tail": 100, + "numFromTail": 100, "cursor": "", "path": "" }' \ diff --git a/test/services_v1_test.go b/test/services_v1_integration_test.go similarity index 100% rename from test/services_v1_test.go rename to test/services_v1_integration_test.go diff --git a/test/services_v2_test.go b/test/services_v2_integration_test.go similarity index 100% rename from test/services_v2_test.go rename to test/services_v2_integration_test.go From 0270ac973f8e719369a1b3aec6581cfd1b8aa5a4 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:42:50 +0100 Subject: [PATCH 46/59] checkpoint: refactor --- internal/platform/platform.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/platform/platform.go b/internal/platform/platform.go index 68748f4..54fb879 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -47,7 +47,6 @@ func (m model) View() string { var mem runtime.MemStats runtime.ReadMemStats(&mem) - // Styles titleStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("39")). @@ -68,14 +67,12 @@ func (m model) View() string { Foreground(lipgloss.Color("241")). MarginTop(1) - // Helper function to format rows row := func(label, value string) string { return fmt.Sprintf("%s %s", labelStyle.Width(20).Render(label+":"), valueStyle.Render(value)) } - // Build the view var s string s += titleStyle.Render("Runtime Diagnostics") + "\n" From 88c8d730ccada4160ae2836a2ab7bf69086c236c Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:50:39 +0100 Subject: [PATCH 47/59] debug: removed deprecated test --- test/services_v1_integration_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/services_v1_integration_test.go b/test/services_v1_integration_test.go index 69dc9c8..844c030 100644 --- a/test/services_v1_integration_test.go +++ b/test/services_v1_integration_test.go @@ -1,5 +1,6 @@ package server_test +/* import ( "context" "testing" @@ -49,3 +50,4 @@ func TestService_Test(t *testing.T) { t.Fatalf("unexpected response: %q", resp) } } +*/ From 1751bb5f446ebe42be88383db1e1b7c184befb34 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:54:22 +0100 Subject: [PATCH 48/59] debug: removed deprecated test --- test/journal_action_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/journal_action_test.go b/test/journal_action_test.go index 635c423..302b846 100644 --- a/test/journal_action_test.go +++ b/test/journal_action_test.go @@ -1,5 +1,6 @@ package server_test +/* import ( "context" "testing" @@ -13,9 +14,8 @@ import ( "google.golang.org/grpc/credentials/insecure" ) -/** TODO(nasr): write test for reboot and shutdown, what would be a proper way of doing this -*/ + func TestActions_all(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -45,3 +45,4 @@ func TestActions_all(t *testing.T) { t.Fatalf("unexpected response: %q", resp) } } +*/ From 96ed346a4d7879b2866b28d7e0fe592869fba176 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:58:03 +0100 Subject: [PATCH 49/59] debug: removed deprecated tests --- test/journal_service_test.go | 86 +++++++++++++++++----------------- test/resources_service_test.go | 86 +++++++++++++++++----------------- 2 files changed, 86 insertions(+), 86 deletions(-) diff --git a/test/journal_service_test.go b/test/journal_service_test.go index 5db71ba..3dcd0c8 100644 --- a/test/journal_service_test.go +++ b/test/journal_service_test.go @@ -1,44 +1,44 @@ package server_test - -import ( - "context" - "testing" - "time" - - pb "paradigm-ehb/agent/gen/journal/v1" - - "google.golang.org/grpc/resolver" - - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - - -func TestJournal_all(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - resolver.SetDefaultScheme("passthrough") - - clientConn, err := grpc.NewClient( - "bufnet", - grpc.WithContextDialer(BufDialer), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - - if err != nil { - t.Fatalf("failed to create client: %v", err) - } - defer clientConn.Close() - - client := pb.NewJournalServiceClient(clientConn) - - resp, err := client.Action(ctx, &pb.JournalRequest{}) - if err != nil { - t.Fatalf("rpc failed: %v", err) - } - - if resp == nil { - t.Fatalf("unexpected response: %q", resp) - } -} +// +// import ( +// "context" +// "testing" +// "time" +// +// pb "paradigm-ehb/agent/gen/journal/v1" +// +// "google.golang.org/grpc/resolver" +// +// "google.golang.org/grpc" +// "google.golang.org/grpc/credentials/insecure" +// ) +// +// +// func TestJournal_all(t *testing.T) { +// ctx, cancel := context.WithTimeout(context.Background(), time.Second) +// defer cancel() +// +// resolver.SetDefaultScheme("passthrough") +// +// clientConn, err := grpc.NewClient( +// "bufnet", +// grpc.WithContextDialer(BufDialer), +// grpc.WithTransportCredentials(insecure.NewCredentials()), +// ) +// +// if err != nil { +// t.Fatalf("failed to create client: %v", err) +// } +// defer clientConn.Close() +// +// client := pb.NewJournalServiceClient(clientConn) +// +// resp, err := client.Action(ctx, &pb.JournalRequest{}) +// if err != nil { +// t.Fatalf("rpc failed: %v", err) +// } +// +// if resp == nil { +// t.Fatalf("unexpected response: %q", resp) +// } +// } diff --git a/test/resources_service_test.go b/test/resources_service_test.go index 9fc92fe..6bf5cf3 100644 --- a/test/resources_service_test.go +++ b/test/resources_service_test.go @@ -1,44 +1,44 @@ package server_test - -import ( - "context" - "testing" - "time" - - pb "paradigm-ehb/agent/gen/resources/v1" - - "google.golang.org/grpc/resolver" - - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - - -func TestResources_All(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - resolver.SetDefaultScheme("passthrough") - - clientConn, err := grpc.NewClient( - "bufnet", - grpc.WithContextDialer(BufDialer), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - - if err != nil { - t.Fatalf("failed to create client: %v", err) - } - defer clientConn.Close() - - client := pb.NewResourcesServiceClient(clientConn) - - resp, err := client.GetSystemResources(ctx, &pb.GetSystemResourcesRequest{}) - if err != nil { - t.Fatalf("rpc failed: %v", err) - } - - if resp == nil { - t.Fatalf("unexpected response: %q", resp) - } -} +// +// import ( +// "context" +// "testing" +// "time" +// +// pb "paradigm-ehb/agent/gen/resources/v1" +// +// "google.golang.org/grpc/resolver" +// +// "google.golang.org/grpc" +// "google.golang.org/grpc/credentials/insecure" +// ) +// +// +// func TestResources_All(t *testing.T) { +// ctx, cancel := context.WithTimeout(context.Background(), time.Second) +// defer cancel() +// +// resolver.SetDefaultScheme("passthrough") +// +// clientConn, err := grpc.NewClient( +// "bufnet", +// grpc.WithContextDialer(BufDialer), +// grpc.WithTransportCredentials(insecure.NewCredentials()), +// ) +// +// if err != nil { +// t.Fatalf("failed to create client: %v", err) +// } +// defer clientConn.Close() +// +// client := pb.NewResourcesServiceClient(clientConn) +// +// resp, err := client.GetSystemResources(ctx, &pb.GetSystemResourcesRequest{}) +// if err != nil { +// t.Fatalf("rpc failed: %v", err) +// } +// +// if resp == nil { +// t.Fatalf("unexpected response: %q", resp) +// } +// } From 82065666afaa6edab2b65b7b0070bc7f6104de1f Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 12:03:08 +0100 Subject: [PATCH 50/59] bugfix: grpc process action call was wrongly named --- pkg/grpchandler/resources/v2/resources.go | 2 +- test/process_action.sh | 11 +++++++++++ test/run_all_tests.sh | 8 ++++---- 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100755 test/process_action.sh diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go index 69e3c10..5da3509 100644 --- a/pkg/grpchandler/resources/v2/resources.go +++ b/pkg/grpchandler/resources/v2/resources.go @@ -66,7 +66,7 @@ func (s *ResourcesServiceV2) GetSystemResources( * TODO(nasr): update individual resources */ -func (s *ResourcesServiceV2) KillProcess( +func (s *ResourcesServiceV2) ProcessAction( ctx context.Context, req *proto.ProcessActionRequest, ) (*proto.ProcessActionReply, error) { diff --git a/test/process_action.sh b/test/process_action.sh new file mode 100755 index 0000000..569d500 --- /dev/null +++ b/test/process_action.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -eu + +grpcurl -plaintext \ + -d '{ + "pid": 2497, + "signal": 15 + }' \ + localhost:5000 \ + resources.v2.ResourcesService/ProcessAction diff --git a/test/run_all_tests.sh b/test/run_all_tests.sh index 25ad4ce..d35c7db 100755 --- a/test/run_all_tests.sh +++ b/test/run_all_tests.sh @@ -17,23 +17,23 @@ grpcurl -plaintext \ resources.v2.ResourcesService/GetSystemResources # Kill Process (replace with actual PID) -echo -e "\n[2] KillProcess (SIGTERM - signal 15)" +echo -e "\n[2] ProcessAction (SIGTERM - signal 15)" grpcurl -plaintext \ -d '{ "pid": 12345, "signal": 15 }' \ $SERVER \ - resources.v2.ResourcesService/KillProcess + resources.v2.ResourcesService/ProcessAction -echo -e "\n[3] KillProcess (SIGKILL - signal 9)" +echo -e "\n[3] ProcessAction (SIGKILL - signal 9)" grpcurl -plaintext \ -d '{ "pid": 12345, "signal": 9 }' \ $SERVER \ - resources.v2.ResourcesService/KillProcess + resources.v2.ResourcesService/ProcessAction echo "======================================" echo "HandlerServicev3 Tests (Systemd)" From 0fad65c718b1072ef62c149a575de9536ad95e6a Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 12:04:22 +0100 Subject: [PATCH 51/59] checkpoint: agent-resources update --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index f654d6e..a69ede2 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit f654d6e475b562ced341ba57108c55e1ca37f81b +Subproject commit a69ede2a36da7d7d198ffe17f68c61d9995552cf From 5dc145313d5ef7471bcb442ca8f8fc9c956d8ca1 Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 14:01:08 +0100 Subject: [PATCH 52/59] checkpoint: package manager --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index a69ede2..4c63c83 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit a69ede2a36da7d7d198ffe17f68c61d9995552cf +Subproject commit 4c63c830b566512af8b35715479ba6afcc836c13 From 0cc80985728715cad438c4ebadc49ae0942b9378 Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 15:37:09 +0100 Subject: [PATCH 53/59] feature: disk resources --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 4c63c83..01f4420 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 4c63c830b566512af8b35715479ba6afcc836c13 +Subproject commit 01f44207854e794f162bb23953bb4dcf45f1b154 From aa168433c35554a6f20e466620612d2b7aa2210a Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 15:08:41 +0100 Subject: [PATCH 54/59] feature: converted ram strings to int --- cmd/agent/agent.go | 2 +- pkg/cgowrap/types.go | 4 ++-- pkg/cgowrap/wrapper.go | 5 +++-- proto/resources/v2/deviceresources.proto | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index a72b10b..0bf0755 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -67,7 +67,7 @@ var ( NOTE: When enabled, diagnostics must never block the gRPC server. */ - diagnostics = flag.Bool("diagnostics", true, "run runtime diagnostics") + diagnostics = flag.Bool("diagnostics", false, "run runtime diagnostics") /* portFlag is the preferred TCP port to bind the gRPC server to. diff --git a/pkg/cgowrap/types.go b/pkg/cgowrap/types.go index 2e2d769..30d8492 100644 --- a/pkg/cgowrap/types.go +++ b/pkg/cgowrap/types.go @@ -12,8 +12,8 @@ type Cpu struct { // Ram represents RAM information including total and free memory. type Ram struct { - Total string - Free string + Total uint64 + Free uint64 } // DiskPartition represents a single disk partition with device identifiers and block count. diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index b6e84be..f0b9993 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -364,9 +364,10 @@ func RamRead(ram *C.Ram) (Ram, error) { } r := Ram{ - Total: C.GoString(&ram.total[0]), - Free: C.GoString(&ram.free[0]), + Total: uint64(ram.total), + Free: uint64(ram.free), } + return r, nil } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index f811a98..13d2712 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -51,8 +51,8 @@ message Cpu { * @return total memory on system, available memory on system * */ message Memory { - string total = 1; - string free = 2; + uint64 total = 1; + uint64 free = 2; } /** From ccdcdb46a5f9481df6bb9d370d936175277860e7 Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 15:36:46 +0100 Subject: [PATCH 55/59] feature: fixed compile script to handle parsing function --- build.sh | 7 ++++++- pkg/cgowrap/wrapper.go | 4 ++-- pkg/grpchandler/resources/v1/resources.go | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/build.sh b/build.sh index 0eabe9e..dcc7473 100755 --- a/build.sh +++ b/build.sh @@ -92,9 +92,11 @@ OUT_DIR="$AGENT_RES_DIR/build" SRC_RES="$AGENT_RES_DIR/resources.c" SRC_ARENA="$AGENT_RES_DIR/arena.c" +SRC_BASE="$AGENT_RES_DIR/base.c" OUT_OBJ_RES="$OUT_DIR/resources.o" OUT_OBJ_ARENA="$OUT_DIR/arena.o" +OUT_OBJ_BASE="$OUT_DIR/base.o" OUT_LIB="$OUT_DIR/libagent_resources.a" @@ -133,10 +135,13 @@ $CC $CFLAGS -c "$SRC_RES" -o "$OUT_OBJ_RES" echo "Compiling arena.c..." $CC $CFLAGS -c "$SRC_ARENA" -o "$OUT_OBJ_ARENA" +echo "Compiling base.c..." +$CC $CFLAGS -c "$SRC_BASE" -o "$OUT_OBJ_BASE" + echo "Creating static library..." # $AR rcs "$OUT_LIB" "$OUT_OBJ" -$AR rcs "$OUT_LIB" "$OUT_OBJ_RES" "$OUT_OBJ_ARENA" +$AR rcs "$OUT_LIB" "$OUT_OBJ_RES" "$OUT_OBJ_ARENA" "$OUT_OBJ_BASE" echo "Done: $OUT_LIB" diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index f0b9993..034d9a0 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -413,10 +413,10 @@ func DiskRead(disk *C.Disk) (Disk, error) { } d := Disk{ - Partitions: make([]DiskPartition, 0, disk.count), + Partitions: make([]DiskPartition, 0, disk.part_count), } - for i := C.size_t(0); i < disk.count; i++ { + for i := C.size_t(0); i < disk.part_count; i++ { part := (*C.Partition)( unsafe.Pointer( uintptr(unsafe.Pointer(disk.partitions)) + diff --git a/pkg/grpchandler/resources/v1/resources.go b/pkg/grpchandler/resources/v1/resources.go index c9f9e01..0a60456 100644 --- a/pkg/grpchandler/resources/v1/resources.go +++ b/pkg/grpchandler/resources/v1/resources.go @@ -97,8 +97,8 @@ func mapCPU(c wr.Cpu) *proto.Cpu { */ func mapMemory(m wr.Ram) *proto.Memory { return &proto.Memory{ - Total: m.Total, - Free: m.Free, + Total: string(m.Total), + Free: string(m.Free), } } From 4e6352915131964f9cf064423fb03699425fbb69 Mon Sep 17 00:00:00 2001 From: nasr on the server Date: Thu, 15 Jan 2026 12:09:59 +0100 Subject: [PATCH 56/59] feature: removed the submodule and made it a subdirectory doing this because were doing a rewrite in cpp --- .gitmodules | 3 --- build.sh | 1 - 2 files changed, 4 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 3631d7d..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "internal/resmanager/agent-resources"] - path = pkg/agent-resources - url = git@github.com:paradigm-ehb/agent-resources.git diff --git a/build.sh b/build.sh index dcc7473..df8b8a7 100755 --- a/build.sh +++ b/build.sh @@ -118,7 +118,6 @@ CFLAGS=" -Wmisleading-indentation -Wunused -Wuninitialized --Werror -Wdouble-promotion -Wstrict-overflow=2 -D_POSIX_C_SOURCE=200809L From 1db826adbc21039b70b0d2200055e525dbe19d6a Mon Sep 17 00:00:00 2001 From: nasr on the server Date: Thu, 15 Jan 2026 12:13:25 +0100 Subject: [PATCH 57/59] feature: removed the gitsubmodule --- pkg/agent-resources | 1 - pkg/resources/.clang-format | 104 ++ pkg/resources/.clangd | 68 ++ .../.gitea/workflows/main-build.yaml | 11 + pkg/resources/.github/workflows/main.yml | 24 + pkg/resources/.gitignore | 67 ++ pkg/resources/LICENSE | 21 + pkg/resources/README.md | 15 + pkg/resources/arena.c | 75 ++ pkg/resources/arena.h | 75 ++ pkg/resources/base.c | 25 + pkg/resources/base.h | 48 + pkg/resources/build-pkgm.sh | 11 + pkg/resources/build-pkgmanager.sh | 30 + pkg/resources/build.sh | 46 + pkg/resources/build_check_distro.sh | 5 + pkg/resources/compile.sh | 61 ++ pkg/resources/configure.c | 64 ++ pkg/resources/nob | Bin 0 -> 74032 bytes pkg/resources/pkgm.cpp | 87 ++ pkg/resources/pkgm.hpp | 68 ++ pkg/resources/pkgm_main.cpp | 11 + pkg/resources/resources.c | 890 ++++++++++++++++++ pkg/resources/resources.h | 156 +++ pkg/resources/resources_main.c | 47 + 25 files changed, 2009 insertions(+), 1 deletion(-) delete mode 160000 pkg/agent-resources create mode 100644 pkg/resources/.clang-format create mode 100644 pkg/resources/.clangd create mode 100644 pkg/resources/.gitea/workflows/main-build.yaml create mode 100644 pkg/resources/.github/workflows/main.yml create mode 100644 pkg/resources/.gitignore create mode 100644 pkg/resources/LICENSE create mode 100644 pkg/resources/README.md create mode 100644 pkg/resources/arena.c create mode 100644 pkg/resources/arena.h create mode 100644 pkg/resources/base.c create mode 100644 pkg/resources/base.h create mode 100644 pkg/resources/build-pkgm.sh create mode 100755 pkg/resources/build-pkgmanager.sh create mode 100755 pkg/resources/build.sh create mode 100755 pkg/resources/build_check_distro.sh create mode 100755 pkg/resources/compile.sh create mode 100644 pkg/resources/configure.c create mode 100755 pkg/resources/nob create mode 100644 pkg/resources/pkgm.cpp create mode 100644 pkg/resources/pkgm.hpp create mode 100644 pkg/resources/pkgm_main.cpp create mode 100644 pkg/resources/resources.c create mode 100644 pkg/resources/resources.h create mode 100644 pkg/resources/resources_main.c diff --git a/pkg/agent-resources b/pkg/agent-resources deleted file mode 160000 index 01f4420..0000000 --- a/pkg/agent-resources +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 01f44207854e794f162bb23953bb4dcf45f1b154 diff --git a/pkg/resources/.clang-format b/pkg/resources/.clang-format new file mode 100644 index 0000000..a086b22 --- /dev/null +++ b/pkg/resources/.clang-format @@ -0,0 +1,104 @@ +BasedOnStyle: LLVM +Language: Cpp + +# ------------------------------------------------------------------- +# Indentation & layout +# ------------------------------------------------------------------- +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +ContinuationIndentWidth: 2 + +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: NoIndent + +# ------------------------------------------------------------------- +# Line breaking +# ------------------------------------------------------------------- +ColumnLimit: 0 + +AllowAllParametersOfDeclarationOnNextLine: false +AllowAllArgumentsOnNextLine: false + +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false + +AlwaysBreakAfterReturnType: All +AlwaysBreakTemplateDeclarations: No # harmless for C + +BreakBeforeBinaryOperators: None + +# ------------------------------------------------------------------- +# Braces (Allman style) +# ------------------------------------------------------------------- +BreakBeforeBraces: Allman + +BraceWrapping: + AfterCaseLabel: true + BeforeElse: true + BeforeCatch: true + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true + +# NOTE: +# AfterControlStatement / AfterFunction / AfterStruct / AfterEnum / AfterUnion +# are IMPLIED by Allman and must NOT be redundantly specified. + +# ------------------------------------------------------------------- +# Spacing +# ------------------------------------------------------------------- +SpaceBeforeParens: ControlStatements +SpaceBeforeAssignmentOperators: true +SpaceBeforeRangeBasedForLoopColon: true # + +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpacesInAngles: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 + +PointerAlignment: Right +DerivePointerAlignment: false + +# ------------------------------------------------------------------- +# Alignment (explicitly disabled) +# ------------------------------------------------------------------- +AlignAfterOpenBracket: DontAlign +AlignOperands: false +AlignTrailingComments: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign + +# ------------------------------------------------------------------- +# Comments +# ------------------------------------------------------------------- +ReflowComments: false +CommentPragmas: '^ dont touch:' +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 + +# ------------------------------------------------------------------- +# Includes +# ------------------------------------------------------------------- +SortIncludes: Never +IncludeBlocks: Preserve + +# ------------------------------------------------------------------- +# Macros & preprocessor +# ------------------------------------------------------------------- +MacroBlockBegin: '' +MacroBlockEnd: '' +SpaceAfterCStyleCast: false + +# ------------------------------------------------------------------- +# C-specific +# ------------------------------------------------------------------- +Cpp11BracedListStyle: false +DisableFormat: false diff --git a/pkg/resources/.clangd b/pkg/resources/.clangd new file mode 100644 index 0000000..b8bec03 --- /dev/null +++ b/pkg/resources/.clangd @@ -0,0 +1,68 @@ +CompileFlags: + Add: + - -Iinclude + + Remove: + - -std=* + - -O* + - -march=* + - -mtune=* + +# C configuration +If: + Language: C +Then: + CompileFlags: + Add: + - -std=c99 + - -xc + - -Wall + - -Wextra + - -Wpedantic + - -Wshadow + - -Wconversion + - -Wsign-conversion + - -Wmissing-declarations + - -Wundef + - -Wpointer-arith + - -Wcast-align + - -Wcast-qual + - -Wwrite-strings + - -Wswitch-enum + - -Wformat=2 + - -Wstrict-aliasing=2 + - -Werror=implicit-function-declaration + - -Werror=implicit-int + - -Werror=incompatible-pointer-types + - -Werror=return-type + - -Wformat-security + - -Wnull-dereference + - -Wmisleading-indentation + - -Wunused + - -Wuninitialized + - -Wdouble-promotion + - -Wstrict-overflow=2 + - -D_POSIX_C_SOURCE=200809L + - -ldnf5 + +# C++ configuration +If: + Language: Cpp +Then: + CompileFlags: + Add: + - -std=c++20 + - -Wall + - -Wextra + - -Wpedantic + - -Wshadow + - -Wconversion + - -Wsign-conversion + - -Wundef + - -Wpointer-arith + - -Wcast-align + - -Wcast-qual + - -Wformat=2 + - -Wformat-security + - -Wnull-dereference + - -Wmisleading-indentation diff --git a/pkg/resources/.gitea/workflows/main-build.yaml b/pkg/resources/.gitea/workflows/main-build.yaml new file mode 100644 index 0000000..0da5604 --- /dev/null +++ b/pkg/resources/.gitea/workflows/main-build.yaml @@ -0,0 +1,11 @@ +name: main-build +run-name: compile-build + +on: [push] + +jobs: + main-compile: + runs-on: ubuntu-latest + steps: + - name: Run Build step + - run: echo "Workflow is running" diff --git a/pkg/resources/.github/workflows/main.yml b/pkg/resources/.github/workflows/main.yml new file mode 100644 index 0000000..a712a5f --- /dev/null +++ b/pkg/resources/.github/workflows/main.yml @@ -0,0 +1,24 @@ +name: C CI + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y clang libproc2-dev + + - name: Make build script executable + run: chmod +x build.sh + + - name: Build + run: ./build.sh diff --git a/pkg/resources/.gitignore b/pkg/resources/.gitignore new file mode 100644 index 0000000..2c4c8cd --- /dev/null +++ b/pkg/resources/.gitignore @@ -0,0 +1,67 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Build files +build/ + +# Config files +.idea +.vscode + +# Junk +.DS_Store +.cache +source/main +tmp +/.vs +*.json diff --git a/pkg/resources/LICENSE b/pkg/resources/LICENSE new file mode 100644 index 0000000..d48e6ad --- /dev/null +++ b/pkg/resources/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Abdellah El Morabit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pkg/resources/README.md b/pkg/resources/README.md new file mode 100644 index 0000000..b658d57 --- /dev/null +++ b/pkg/resources/README.md @@ -0,0 +1,15 @@ +# Resources gathering tool + +Tool for gathering system resources on linux + + +### credits + +https://www.gingerbill.org/series/memory-allocation-strategies/ +https://www.youtube.com/watch?v=jgiMagdjA1s +https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator +https://github.com/EpicGamesExt/raddebugger/blob/master/src/base/base_arena.c +https://www.geeksforgeeks.org/c/inline-function-in-c/ + +https://libdnf.readthedocs.io/en/dnf-5-devel/tutorial/install-build-deps.html +https://man.archlinux.org/man/libalpm.3#Topics diff --git a/pkg/resources/arena.c b/pkg/resources/arena.c new file mode 100644 index 0000000..2104d7d --- /dev/null +++ b/pkg/resources/arena.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arena.h" + +mem_arena * +arena_create(u64 capacity) +{ + mem_arena *arena = mmap(0, capacity, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (arena == MAP_FAILED) + { + assert(0); + } + + arena->capacity = capacity; + arena->pos = ARENA_BASE_POS; + + return arena; +} + +// make it a void pointer to allow implicit conversion +void +arena_destroy(mem_arena *arena) +{ + munmap(arena, arena->capacity); +} + +void * +arena_push(mem_arena *arena, u64 size, b32 non_zero) +{ + u64 pos_aligned = ALIGN_UP_POW2(arena->pos, ARENA_ALIGN); + u64 new_pos = pos_aligned + size; + + if (new_pos > arena->capacity) + { + assert(0); + return NULL; + } + + arena->pos = new_pos; + // cast to u8 to be able to do pointer arithemtic + u8 *out = (u8 *)arena + pos_aligned; + + if (!non_zero) + { + memset(out, 0, size); + } + return out; +} +void +arena_pop(mem_arena *arena, u64 size) +{ + size = MIN(size, arena->pos - ARENA_BASE_POS); + arena->pos -= size; +} + +void +arena_pop_to(mem_arena *arena, u64 pos) +{ + u64 size = pos < arena->pos ? arena->pos - pos : 0; + arena_pop(arena, size); +} + +void +arena_clear(mem_arena *arena) +{ + arena_pop_to(arena, ARENA_BASE_POS); +} diff --git a/pkg/resources/arena.h b/pkg/resources/arena.h new file mode 100644 index 0000000..ca39ca0 --- /dev/null +++ b/pkg/resources/arena.h @@ -0,0 +1,75 @@ +#ifndef ARENA_H +#define ARENA_H + +#include "base.h" + +/** + * Arena Helper macro's + * */ + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define ALIGN_UP_POW2(n, p) (((u64)(n) + ((u64)(p) - 1)) & (~((u64)(p) - 1))) + +#define PUSH_STRUCT(arena, T) (T *)arena_push((arena), sizeof(T), 0) +#define PUSH_STRUCT_NZ(arena, T) (T *)arena_push((arena), sizeof(T), 1) +#define PUSH_ARRAY(arena, T, n) (T *)arena_push((arena), sizeof(T) * (n), 0) +#define PUSH_ARRAY_NZ(arena, T, n) (T *)arena_push((arena), sizeof(T) * (n), 1) + + + + +/* + * Represents a disk partition with major/minor device numbers and block count. + */ + +/** + * replacing malloc/free with arena allocaters + * + * */ + +#define ARENA_BASE_POS (sizeof(mem_arena)) +// void * for the size of a pointer on the machine, 64/32bit comp +#define ARENA_ALIGN (sizeof(void *)) + + +static inline u64 KiB(u64 n) { return n << 10; } +static inline u64 MiB(u64 n) { return n << 20; } +static inline u64 GiB(u64 n) { return n << 30; } + +typedef struct mem_arena mem_arena; + + +struct mem_arena +{ + u64 capacity; + u64 pos; +} ; + +// arena prototypes +mem_arena * +arena_create(u64 capacity); +// make it a void pointer to allow implicit conversion +void +arena_destroy(mem_arena *arena); + +void * +arena_push(mem_arena *arena, u64 size, b32 non_zero); + +void +arena_pop(mem_arena *arena, u64 size); + +void +arena_pop_to(mem_arena *arena, u64 pos); + +void +arena_clear(mem_arena *arena); + + +#define PUSH_STRUCT(arena, T) (T *)arena_push((arena), sizeof(T), 0) +#define PUSH_STRUCT_NZ(arena, T) (T *)arena_push((arena), sizeof(T), 1) +#define PUSH_ARRAY(arena, T, n) (T *)arena_push((arena), sizeof(T) * (n), 0) +#define PUSH_ARRAY_NZ(arena, T, n) (T *)arena_push((arena), sizeof(T) * (n), 1) + + +#endif diff --git a/pkg/resources/base.c b/pkg/resources/base.c new file mode 100644 index 0000000..7dacd30 --- /dev/null +++ b/pkg/resources/base.c @@ -0,0 +1,25 @@ +#include "base.h" + +/** + * Helper function to parse strings to int using ascii codes + * */ +u64 +parse_u64(char *buf, size_t len) +{ + u64 value = 0; + + for ( + size_t buffer_idx = 0; + buffer_idx < len; + ++buffer_idx) + { + char c = buf[buffer_idx]; + if (c < '0' || c > '9') + { + break; + } + value = value * 10 + (c - '0'); + } + + return value; +} diff --git a/pkg/resources/base.h b/pkg/resources/base.h new file mode 100644 index 0000000..988d33a --- /dev/null +++ b/pkg/resources/base.h @@ -0,0 +1,48 @@ +#ifndef BASE_H +#define BASE_H + +#include +#include + +#define OK 0 +#define ERR_IO 1 +#define ERR_PARSE 2 +#define ERR_PERM 3 +#define ERR_INVALID 4 + +#define internal static +#define local_persist static +#define global_variable static + +enum +{ + BUFFER_SIZE_SMALL = 128, + BUFFER_SIZE_DEFAULT = 256, + BUFFER_SIZE_LARGE = 512, + PATH_MAX_LEN = 4096 +}; + +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; + +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; + +typedef float f32; +typedef double f64; + +typedef i32 b32; +typedef i16 b16; +typedef u8 b8; + +#define TRUE 1 +#define FALSE 0 + +u64 +parse_u64(char *buf, size_t len); + +#endif diff --git a/pkg/resources/build-pkgm.sh b/pkg/resources/build-pkgm.sh new file mode 100644 index 0000000..ea25767 --- /dev/null +++ b/pkg/resources/build-pkgm.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -eu + +# Compile C files with clang (C compiler) +clang -c arena.c -o arena.o +clang -c resources.c -o resources.o + +# Compile C++ files and link with C objects +clang configure.c arena.o resources.o -ldnf5 -o nob + +./nob diff --git a/pkg/resources/build-pkgmanager.sh b/pkg/resources/build-pkgmanager.sh new file mode 100755 index 0000000..16356db --- /dev/null +++ b/pkg/resources/build-pkgmanager.sh @@ -0,0 +1,30 @@ +#!/bin/sh +set -eu + +CXX=c++ +SRC=libdnf_handler.cpp +OUT=build/pkgmanager + +CXXFLAGS=" +-std=c++20 +-Wall -Wextra -Wpedantic +-Wshadow -Wconversion +-Wundef +-Wpointer-arith -Wcast-align -Wcast-qual +-Wformat=2 -Wformat-security +-Wnull-dereference +-Wmisleading-indentation +-Wunused -Wuninitialized +-fexceptions +-fno-rtti +-ldnf5 +-lfmt +" + +mkdir -p build + +echo "Compiling..." +$CXX $CXXFLAGS "$SRC" -o "$OUT" + +echo "Running..." +"$OUT" diff --git a/pkg/resources/build.sh b/pkg/resources/build.sh new file mode 100755 index 0000000..b30970b --- /dev/null +++ b/pkg/resources/build.sh @@ -0,0 +1,46 @@ +#!/bin/sh +set -eu + +CC=cc +AR=ar + +SRC=resources.c +OUT_DIR=build +OUT_OBJ=$OUT_DIR/resources.o +OUT_LIB=$OUT_DIR/libagent_resources.a + +CFLAGS=" +-std=c99 +-Wall +-Wextra +-Wpedantic +-Wshadow +-Wconversion +-Wundef +-Wpointer-arith +-Wcast-align +-Wcast-qual +-Wwrite-strings +-Wformat=2 +-Wformat-security +-Wnull-dereference +-Wmisleading-indentation +-Wunused +-Wuninitialized +-Werror +-Wdouble-promotion +-Wstrict-overflow=2 +-D_POSIX_C_SOURCE=200809L +" + +# TODO(nasr): compile the package manager libs, static bin +mkdir -p "$OUT_DIR" + +echo "Compiling object..." +$CC $CFLAGS -c "$SRC" -o "$OUT_OBJ" + +echo "Creating static library..." +$AR rcs "$OUT_LIB" "$OUT_OBJ" + +echo "Done:" +echo " $OUT_LIB" diff --git a/pkg/resources/build_check_distro.sh b/pkg/resources/build_check_distro.sh new file mode 100755 index 0000000..a952f51 --- /dev/null +++ b/pkg/resources/build_check_distro.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -xeu + +clang configure.cpp -o -Iarena -Ibase check diff --git a/pkg/resources/compile.sh b/pkg/resources/compile.sh new file mode 100755 index 0000000..d3368e4 --- /dev/null +++ b/pkg/resources/compile.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -eu + +CC=clang +SRC=resources_main.c +OUT_DIR=build +OUT_BIN=$OUT_DIR/agent_resources + +CFLAGS=" +-std=c99 +-Wall +-Wextra +-Wpedantic +-Wshadow +-Wconversion +-Wundef +-Wpointer-arith +-Wcast-align +-Wcast-qual +-Wwrite-strings +-Wformat=2 +-D_POSIX_C_SOURCE=200809L +" + +sep() { + printf '%s\n' "============================================================" +} + +header() { + sep + printf ' %s\n' "$1" + sep +} + +info() { + printf ' • %s\n' "$1" +} + +ok() { + printf ' ✓ %s\n' "$1" +} + +mkdir -p "$OUT_DIR" + +header "BUILD" +info "Compiler : $CC" +info "Source : $SRC" +info "Output : $OUT_BIN" + +printf '\n' +info "Compiling…" +$CC $CFLAGS "$SRC" -o "$OUT_BIN" +ok "Compilation finished" + +printf '\n' +header "RUN" +info "Executing binary" +sep +"$OUT_BIN" +sep +ok "Execution finished" diff --git a/pkg/resources/configure.c b/pkg/resources/configure.c new file mode 100644 index 0000000..d45a4e9 --- /dev/null +++ b/pkg/resources/configure.c @@ -0,0 +1,64 @@ +#include "arena.h" +#include "resources.h" +#include "pkgm.hpp" +#include +#include "stdio.h" + +internal enum LinuxDistro +find_lxd_pkgm(mem_arena *arena) +{ + Device *device = device_create(arena); + device_read(device); + enum LinuxDistro lxd; + + i32 len = sizeof(device->os_version); + char *distro_unparsed = (char *)PUSH_ARRAY(arena, char, len); + + memcpy(distro_unparsed, device->os_version, len); + + i32 word_idx = 0; + char *buffer = (char *)arena_push(arena, len, 1); + + while (distro_unparsed[word_idx] != '\0') + { + /* TODO: + * + * Convert character to lowercase using ASCII ordering. + * + * - Check whether the character is an uppercase letter. + * - Uppercase ASCII range: 'A' .. 'Z'. + * - If the character falls within this range: + * - Convert it to lowercase by applying the ASCII offset. + * - Rationale: + * - Lowercase letters appear after uppercase letters in ASCII. + * - Case conversion can be done via direct numeric comparison, + * without locale or library calls. + */ + /* + Calculate the lower-case upper-case differnce + */ + i8 lwc_diff = 'a' - 'A'; + if (distro_unparsed[word_idx] != ' ') + { + if (distro_unparsed[word_idx] < 'a') + { + distro_unparsed[word_idx] += lwc_diff; + } + buffer[word_idx] = distro_unparsed[word_idx]; + } + + ++word_idx; + } + + buffer[word_idx] = '\0'; + + return buffer; +} + +int +main() +{ + mem_arena *arena = arena_create(GiB(1)); + printf("distro name %s", find_lxd_pkgm(arena)); + arena_destroy(arena); +} diff --git a/pkg/resources/nob b/pkg/resources/nob new file mode 100755 index 0000000000000000000000000000000000000000..eff7a02b751e8c3c41c52921f6b19681b295de07 GIT binary patch literal 74032 zcmeHueRNdEm1lLg1T8EJVPk_0Ms2{EmI6Wu@o6V+Nqh(|U`19O$3y&Rbw5dM`b*s{ z2Lj&6;AFi?W|rr$8Bc<}GWMPowzI>`A!9V-j4@<4WY?3$ILb=Jo*KAnpcDb0m-0XEmwSme2C)s}) zOWsl739uDx3Jb6rHiu0DR~deiFCgw@+2BSaE?Ii*D)XQuxxJ!&uRDMm)dN(pS^RII zl)Y0~F52uOAZm7bxyi1|lJU{lf63NUV|M$gv1WcF%Sti`SG6dm9GORQSs|Ada#h{|KZ)7=N*eOdX_bJHH_o$Fw{knVMuP)d8 zpeI@Oi;ai=_uqc1=lh@igGJFu*P>{6VKfr&-?lIqOoe)vHPxpR^^0wof7n)mY{#H1pU(`=>NF{{hbo@FP5M$ z0Nsb5^ZGgn6Zw}J>pM3ZVKZg+MA8{E)w!`Xnuwd7!LF#u45KHOh#TolFqJV3n{`6Q z?jAFfX6bk`6^UoMS$E0|h9MnHr%gz92P07yiYC%#IFe$q{&*~yWZj9R8E5HCDvBbQ zNkr&AlupJWhj|+ky~ctOqD0b((8`rYcRCb|ceAwUha`GaY-=PMWz;39@8%t`m>El( znSx~6mZVOJ3zC^cf0Wol$?ae>Q>l2uh$ce8Oe6t=x)P}ji;U!Z<=##l-`%i;Hmun)mtWfCcxB?1&ilV1Yq2Hv?+ZB4HLVrY| z->lHLDD*0Y-mB23D)gj6m%gOtZ3=yw!atzU)%p62Lcdkv-=olJJ(SmT3jH=oL^-I? zZ&&C;3Vnt`Kd8`WD)hq&{SJkGM4{iQ(2py0xhJFM6AFE{!hcGk&r#_2&-1)Dw&TqM zV;*MxF}8VQJKh?46SN~9SvXD3+irnq5rk0&sFLVpx&a?H=*98 z)H_h$rPLoreZNwF6!oJ@{V~+fD)lbZ-JU}K-Kfu1>W`z|qSWK4ciDB-*K}{p_q=x( z8#p-!zY8Ar${9~!^z1xl{S0w}<~{60qvb(c8GgUW<^Je<_>s&f7@JNpp~jUOQzM8W z_TjqG0Aq*#LoPRi;!MVnS|VBQaSeJbDjCZA8M}0ZvCcD)zdO5bGzj_29@i@jamZuU z!GAYn|8wrOFTUGc`P5hMzSjKpx4?fEvMra_jdpQacV5=E^{@CG_|;8p^j7HF@@(tM z0mhcUk8%0fK*yG6Ju828Q($y#$I<-!pz)r?7;?MWJ7YUs2gX_vi@*cc)07AAk;v#*u4f?~q&;?d(fk@1ic~F7#0b z8(r4m?wYHEyN#>gDr0*GJ-GK6SD%=~_IiB-9Yee49HzDdyI(l$hb^urE53CWeLPNm z-3ohd4UE1B{{3jbA9mzw*f82L ztyJ5*Pq99qhjlt-ykBzlJ>atGAKZT=FH_;F_^RI(Sm||5xykQhD~sgN=0)2@VEqC5 zu60cTui&W(413Gjv@C4?8{nP=54R68HQ*WH{#kt>*Xpt{Q~W}ho&l!7`<1g8=RAx9 zv3tt})ZhCJ{NVxSh;iuuQ|t-&Sh&AV*IBUd#NFrXtSwWZ3wvZgz zPW=$}TqiIYI`-J%EMrT_M>MYe=YyZEQ#DfgIwJ$1lb@G8I@0`z}rmjMl>L z+GcizY}$`G%>9i%$Y$(Wrf0#!#|9pv@pi%pIz9j&>F`4b;^h+An4c@hXx_OGjA8E3 zoREET3^;$qCAYvp;wQ#-DT}dTOEx_V|IvavB zEW!wLdT{q+S1)3&dT+JoJI&)bY$87pw!MsfhxGlP)JgH-R{G#ru`3aQ)d*y$Z;DT8 zZv}L_kxLqd-$^fz8N@#20)Ag%<9G%><8qLH6?!7jUsuko$Kksv&{O;5ly5P_tQ+@p zgubyI%Dinw3@JXLe9-H*^ADe+lsn#HeV1e`!EeOly}sZc7|lc>r?_b7bpl!Vl*_8%ZH_w#-80smuV zeD3ppz4i8Zdh4yt&_n*Xo-Yu8l+O=_GWFIz@V^9`Ms_pnZmgSb-@wWL0-BsdrTjVf zwt|kYf6mNKD!*d_CWzi3V zKI3rBT%97b7zlLE#NJZ+zOYY~d(2XC?Y>L+=d}jwJ@}ROEPb@5p!A6{ zOLNz18U8MEIQ6HlfkpZ8nD-r811VNZ_E#hNo4(i@Mt?lEb}qL4jo9i1?MCzYtfFHG z{Dc?nS7=VV0s|eNr*Tx+Ign`0lLtEfkZ6u|@>k>sn!B`Si22OtBE`T{tnV%97sRaO zxeA$le%#h%Z6UpsGpTRNv4<90bFp@J6J6}5_&x(V&H~5Hpp(6M{?m)C7Vtj`I_4AK zf64hz>nq7T=#=p`TG-p3+&E6gYe|_Fr;NXm+Z6y^+C?^WqR#t6ERkLR72~74@;LFq z*STZf;+*VQJ1I6w`MT>K;p_HB>yxApyq`r(1zh(0U87ht`MwHgejXDv9y=x~t{W5NXUs#spZf2>th5hroG<1!3jB{RvA&J| zxesSH<$bsrv>W9M_mYB+ec&(n(9RcILEGY$KD6`2F3^5w`2v3CanasnxiQxb=*DAe z`>cgI&f79g)<)_RxkSwu?V!>8ya-ImpS13i{rUVc(8P1W1K{&bpmPQ3^x%949ki$5 z`N0zyT_yG6Ue)^-a{hC<8_$34Yme>ge{(%ww^s4_O6Mc8rBu#nxmV7&CTlTl;Qn5) zH1F?`e}cdH`RcKfI_BP2(BXrQ{Wv#`oU5=#YB1NZ=Qy*R4gVeVoLL?i9>F}K_770! zaUpn#?*jETNyf!wYh(r+rLzp3i+_x^vzSY*K4z^BFpKnaKPH!27j}EBDd^W*&HA>8 zHRJ@|nmFn3MC zhX06m!k>P`|0L=ogsI34v{0GW@K8^_2F7SD?o?Q_N@3nz1kRlI`;_ws{5Ye~EEX-AnsI z${Em){rV`K4XDpr>k~56{tOP~PF=!yfW~ z(RgPe&d5$0b9R}vANz;*h%WYoG=49}#`>6b2C`?dp8r7LLUb?aa*fZg5mFypZXJcb zx4_S;?6}RniD$de<2*aHc^qe_a#!K`3*R$fZya&;{X*s~NqfiD_m+c}MIZFsk7Gf6 zG=9vd3xw~RIH!5ouy>lPkIA!{2j`)CTz!L+1+G+I@9I0^R_YsFeIsi9Q{b2TBH|A^ z^uboFf3}~f9d?qwTJk9{t)O^;PyL8@zd~yhw0R1xSJ3cYEie0wpy6F!o;DuFcG<@Y`9UJ_m_vZicP0H6qtH8a;nU|&< zed;7{qczvj_U|VTpzR0Wq_vx{;LmW7m(Kz3^D>`ZU2f$tR%gD%bDQF4UE`l=6=C7t zB5ZG3Vf`HX_}KQX$m979!c**%dc_(@_QKDH$j`LCT>+2k^Ls(3I^QpauxIrRbi7Qq zmW!N8G{XDz3Z9?7O>JdjAB(nTylXi`G_jT;?s#8z?y}F=w5Ac?E5t|Z4{&)#Jfo(2 z@qCLvx6gVgua;fu68d_KAm#v`=?@R#*`(LS`tZF-C+T&@(5@cghmTfRU!s1%4;zR75cl3Pw)iQr4|tU7 z4EYD~sJwS7^69SLg02+!H%ghCOUulS6l6^BJMG)Aw0{omcH9--Lza4fOW)C`R~TP8 zr4qh)FlPGhNwWqPOquaueTcpUGWfl(ju%DQ67fSXU^kI^LKJYMAw`S`6M|5C|A^Y*~|SL@i77XY$PWq>G!I&KnO_$ymxzF;2!m z2=jOKcQ5dzBVRBR-E?2Oz}KCu&3C|oDeArmvS2J+Fw7ntOpnC76YL3m#Fa=Hkuc)} z;of9u^lfbW0xt>XbWumr@S-mI)4?87w%A?ZTel2X!S0Fn`>ylj+Ec+8_piSr81vP^ zf7}NM4sPZ~GuD~N1fy*06o&h?@Clbf3~sf>R^yJGuY{9SlEgQ65&9M_U2I9?(xqv; zAFhi!G}CE(GM6TIMWbdY<8K$l4Iq|Kk8q-)_NUn46{E;WLOubUl?G8d+AS16()76q?rjVN~9O2%%~Ymn`~3_#xk(XL7lxQSL+e8p_T1K;lOzkE8q$<%)B;+;nUrwxGNZ z<>2{TE{t*}Hh()&u15JX$|HCfegfsSpXYMdP+q;5%lYvFG>ijtJIYSH5ZQ_H1j?6D z_Wm9CQSL=~mD(@oa`UlS8bG-LWfL~<+fZ&nIf!!SN4eZ_s^h`!M<`z#&E;leQ`+#) zxm+8{YLVnzU+7?E+vd1tRn~ACeY17`G;F8#=E-yBqo8j@tA7GKIBj8bRrT}k7hEmx z|D=jFFC-c+($|TfAAAi&z(urP{8od;_2HiM?Zht%+6t7=&|KwvzU&20%h%m)@Bido zp_3Mzm+{*PJ}>c!OSRjz)3%#*l5LNGm%e*utE+r%m3LLY;C{X=Q(5C{t77l}1Z2_~ zz>jqDv8=ADZpTM?e$XBf^d|To^6gUIGuu^J(=IjG_CF1lrzw6=s(rbWh0e&P*YGnyNwJbr}cEclMtd;!_Fd#JJ|Ai5^oui-aTTiLBlFpaGdjj*C z;#|eq9WDUP#}B^w*<9|q@j3^bI;>PE@j8c`IyZpVf9b|LGvHeQ zzC+`6?sMuK1n(i_tn1lH7##=SVerkyxPVb%zLmN2^NldN48GTp^UkAm;QqEpi4oE{ zyA1gg`H%8^kRwUaDasMiKHJg$55LJJh;+~$pn{e2zliTX@Y-X=;}c*IRMXWQo%^+9nnUDPLwa)v06PY&=qNgoh(Ij%OrS0PH-|8H{k zc-jSjPSl0D?6Rm=i*_OY|E$Og-2ZPo*fS--MgD&+X>jXWTUYt~>o$G5*0*?RePjJ% zUt>e#(#5!Z{th$jYYS#X!@{Q8QrwLba4)T8p=dDP<9otPr6Y+r^wuw~U*YqwHN%Ni zP*fK-)pv)Q8f#he!>gK-iyK!qv5tpVrTXLX2t<2AA*WTW<>6Jqcs$V+iBH(P?lY?z zwk;1YYgoFh(HHEBEQ}{IW_^e~)V^v7d#H8QV%EBL)nc_}?S@qiYJFWZnA+E`YGMzq zS=GRTEvwR*U_2a5g;|mENIa5(Vy;fyb5-KLh!+|%kkErg}ouliFi(lwhynvBXis~5&IMAk$LZHb#|W<@i38F zUlZj-JW`J=|D`6V=`|S z)64Vuy_i0UO_UcsY%-Jiw3y$^WL_?&S1_5si|JFC^l>r$CMNS-F}*S$TgCL7ne4%o9hEAE5G+3k_Q$|M4!e^G)+Xmg~@eRRpw#0GP%AM)2FkE^1p}O#*T~i zu$cdLCfCVg`V2P0T2vCh_pq7l{DgG5D<~H6Fu5CW(r2-(Ll3Q&W$a3>nA4vBjE(n; zy$`5lhs6Ara`t*&&GtHQw&~+>CJSAxK=%nnHvf44(!3>mj+E$k$kFe7a9?j+GEZ@} z`15i6+|V!Sa=&8tJ3gLm`uO-I~566e9XfKwI9*hf z;uhQ!zl=AUpL9h^@NXCTAGtli`L3Z(*VhGo@YVpY?-cac1wA9^3wSGIM?tT~{B`F4 zA98-dq!c5dPjk;0$Nvz{b7a5V3*IMWJ_Nr{k(ak#;&+eFlT}=g!|JSO_}`UAYbr$xVy3;KK!CrRGQ*yjcPjgQ&<8wCHCIbAR* z#UGcTzasQp75Vmvkoist{{L2j{(cGi6`_B@tN_>ioUju~jL%ai4ppE}yz z3abQte+l|`iS91!_vHHf9_O!QZ-_XbjrgPM13^DBGr;SYg*~H!erm2wKM&mLnp94B zR7 z_!S3zBK{{pr*U-(9HN5%OM))<56=ktvz#s}Y7t6h7S|`({B6uIKA#FEjaV=eXY<3T z;*p1u3HC6sM8FaXMx(~(&EQs}J5IDX(NdW%vnN8gVKarhbjC~>nV1oZCgNrqO2P@F zCz|LAMvZVLkxCoE{%tIjh$W+D#thdtOw3_)h7$4aNKb!?9@VUiv@qxj8_{iHBe}IF#)2s`9z;vZOegwNAv2A8jKYkE zF$Tj}+tIvnjj?9aYQtd0x(yGuG;c5-T)Xz6HJwIhbIXP`psxPxrsj?7TfvM+UB;R= zA=9?H!&Yu26G_&cNSg7$x((}FS`9o2Th3y!U{bAzlKn=HnK42MSb{+YyP{?ol z(nh>LW~L$`7BgdMGo$LH;b34ru|(X~hOu=6t+dJ%j-~~W zl9@`y69yid;qh6bh!g#XlG|0)P!|uyiu^^xB(ETFf?>zFsQ2C?&x z(bb<0_Q>erE}@7wBI!hEyZsut7f$+N|>@bq>bA-@j$y5SUDQ#?xM5Flt zW_)hut2~5biLe&u!LNvLB%j0eLiA#YRnIGc=c6h7fDH7QQ;T+~QL%)B*G zq$5nQXw(d43T6ajVe~64R(umS;fDlr4W9<84WLJhr?WwGNhZAFnPi0138NQ}d1+d$ ze-MU+BXOf26ASp-^EB@S8v`-LU<#kl(MVTFggqtWydg`P7^_cjk7a^gC=oKe?3H)? z>2y!LzrL$K5)Chmgjqf2s+mf%dZP9Q5jpkY?Qv+Z%S_5{5l_{NDhApBZIA@ST{4;> zjS>9KnA`A|k~G>9Jk;yWUXhl2!;qCVR?p39Zdt#OmJ%*+XKPzt-lLmf47s4bCjrg) zQ_s7zRU^T%dMp|-tOcxIJhJDCLC=r4KA6d*B3=C%tULeei{87@yDG8m$UnbL3c`Fx z4|2VhJ&hRbD<% z-y`Hls8PA9m3zAVGJdMOJjV}I3dT926h)x8%D#qrk$j!aF;HhS$~ci?PWzt~@)Cdf z9RE2Xe^l(xrM$#X*1zeHm(Tk*)5O9x-!Xn?|3ly*7TLdiFEFr(q#0`$actKL{*(1T z!wtDw%FFi$Cy;~b^6|fgKhFM-ps5N!IZXMU;nbKd;~cN7%kpioO;zOO`-qX>QvMWV zDfVRl^1Ve?$d3s8Bn+}&8AoRzODs}ezTY@5-XQqUre0EBmOn$IDlgxIWQF`yRT%eD zPL_Y~keBaEM!43(yp?LiaAcMJgU~7Mm*?#Bzoq;?iT-7tk&31KGm;=m*}p73xS@Ne zeAH!g)Jnz@^0%N}q#Dj4pEEt@wgp!^ + +/** + * TODO(nasr): pacman impelmentation of package manager + * */ + +#elif defined(DISTRO_FEDORAH) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +libdnf5::Base * +setup() +{ + libdnf5::Base *base = new libdnf5::Base; + base->setup(); + + auto repo_sack = base->get_repo_sack(); + + repo_sack->create_repos_from_system_configuration(); + repo_sack->load_repos(); + + return base; +} + +std::vector +query(libdnf5::Base *base, std::string package_name) +{ + std::vector packages; + + libdnf5::rpm::PackageQuery query(*base); + query.filter_name(package_name); + + for (const auto &pkg : query) + { + packages.push_back(pkg.get_nevra()); + } + + return packages; +} + +void +run_transaction(libdnf5::Base *base, std::string package_name) +{ + libdnf5::Goal goal(*base); + goal.add_rpm_install(package_name); + + auto transaction = goal.resolve(); + + libdnf5::repo::PackageDownloader downloader(*base); + + for (auto &tspkg : transaction.get_transaction_packages()) + { + if (libdnf5::transaction::transaction_item_action_is_inbound(tspkg.get_action())) + { + downloader.add(tspkg.get_package()); + downloader.download(); + } + } + + downloader.download(); + + transaction.set_callbacks(std::unique_ptr()); + transaction.set_description("installing a package for the first time"); + + transaction.run(); + + return; +} + +#elif defined(DISTRO_DEBIAN) + +/** + * TODO(nasr): apt impelmentation of package manager + * */ + +#endif diff --git a/pkg/resources/pkgm.hpp b/pkg/resources/pkgm.hpp new file mode 100644 index 0000000..1fa6e7e --- /dev/null +++ b/pkg/resources/pkgm.hpp @@ -0,0 +1,68 @@ +#ifndef PKGMANAGER_H +#define PKGMANAGER_H + +#include "arena.h" +#include "base.h" + +enum LinuxDistro +{ + DISTRO_UNKNOWN = 0, + + DISTRO_ARCH_LINUX, + DISTRO_MANJARO, + DISTRO_ENDEAVOUROS, + DISTRO_ARCO, + DISTRO_GARUDA, + + DISTRO_DEBIAN, + DISTRO_UBUNTU, + DISTRO_LINUX_MINT, + DISTRO_POP_OS, + + DISTRO_FEDORA, + DISTRO_RHEL, + DISTRO_CENTOS_STREAM, + DISTRO_ROCKY, + DISTRO_ALMA, + + DISTRO_GENTOO, + DISTRO_NIXOS, + DISTRO_OPENSUSE, + + DISTRO_COUNT + +}; + +struct DistroStack +{ + const char *name; + enum LinuxDistro linux_distro; +}; + +internal const struct DistroStack distro_map[] = { + { "arch", DISTRO_ARCH_LINUX }, + { "manjaro", DISTRO_MANJARO }, + { "endeavouros", DISTRO_ENDEAVOUROS }, + { "arco", DISTRO_ARCO }, + { "garuda", DISTRO_GARUDA }, + + { "debian", DISTRO_DEBIAN }, + { "ubuntu", DISTRO_UBUNTU }, + { "mint", DISTRO_LINUX_MINT }, + { "pop", DISTRO_POP_OS }, + + { "fedora", DISTRO_FEDORA }, + { "rhel", DISTRO_RHEL }, + { "centos", DISTRO_CENTOS_STREAM }, + { "rocky", DISTRO_ROCKY }, + { "alma", DISTRO_ALMA }, + + { "gentoo", DISTRO_GENTOO }, + { "nixos", DISTRO_NIXOS }, + { "opensuse", DISTRO_OPENSUSE }, +}; + +internal enum LinuxDistro +find_lxd_pkgm(mem_arena *arena); + +#endif diff --git a/pkg/resources/pkgm_main.cpp b/pkg/resources/pkgm_main.cpp new file mode 100644 index 0000000..a9c3c8c --- /dev/null +++ b/pkg/resources/pkgm_main.cpp @@ -0,0 +1,11 @@ +/* ======================================================================== + $File: pkgm_main.cpp $ + $Date: 2026-01-12 $ + $Revision: $ + $Author: nsrddyn@gmail.com $ + $Notice: (C) Copyright 2026 $ + ======================================================================== */ + +#include "arena.h" +#include "pkgm.hpp" +#include "stdio.h" diff --git a/pkg/resources/resources.c b/pkg/resources/resources.c new file mode 100644 index 0000000..1201437 --- /dev/null +++ b/pkg/resources/resources.c @@ -0,0 +1,890 @@ +/* + * name: Abdellah El Morabit + * organization: Paradigm-Ehb + * year: 2025-2026 + * description: resources gathering library + * + */ + +#include "base.h" +#define _POSIX_C_SOURCE 200809L + +#include "resources.h" +#include "arena.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * disk_push_partition - Add a partition to the disk structure + * @d: Pointer to the Disk structure + * @p: Partition to add + * + * Dynamically grows the partition array if needed. Doubles capacity when + * full. If realloc fails, the partition is not added and function returns + * silently. + */ + +void +disk_push_partition(Disk *d, Partition p, mem_arena *arena) +{ + if (d->part_count == d->part_capacity) + { + size_t new_cap = d->part_capacity ? d->part_capacity * 2 : 8; + + Partition *np = PUSH_ARRAY_NZ(arena, Partition, new_cap); + if (!np) + return; + + if (d->partitions && d->part_count > 0) + { + memcpy(np, d->partitions, d->part_count * sizeof(Partition)); + } + + d->partitions = np; + d->part_capacity = new_cap; + } + + d->partitions[d->part_count++] = p; +} + +/* + * is_numeric - Check if a string contains only digits + * @s: String to check + * + * Return: 1 if string contains only numeric characters, 0 otherwise + */ +int +is_numeric(const char *s) +{ + for (; *s; ++s) + { + if (*s < '0' || *s > '9') + { + return 0; + } + } + return 1; +} + +/* + * cpu_create - Allocate and initialize a new Cpu structure + * + * Return: Pointer to newly allocated Cpu, or NULL on allocation failure + */ +Cpu * +cpu_create(mem_arena *m) +{ + return arena_push(m, sizeof(Cpu), 1); +} + +/* + * cpu_read - Read CPU information from /proc/cpuinfo + * @out: Pointer to Cpu structure to populate + * + * Reads vendor_id, model name, cpu MHz, and cpu cores from /proc/cpuinfo. + * The function reads the first occurrence of each field. + * + * Return: OK on success, AGENT_ERR_INVALID if out is NULL, + * ERR_IO if /proc/cpuinfo cannot be opened + */ + +/** + * get a read of the enabled cpu cores to get a view of the folder structure + * before starting to read every single of of them + * + * call + * + * */ +int +cpu_read_enabled_core_cpu_frequency(Cpu *out, int enabled_cpu_count) +{ + if (!out) + { + assert(0); + return ERR_INVALID; + } + + char path[PATH_MAX_LEN]; + for (i8 i = 0; i <= enabled_cpu_count; i++) + { + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i); + } + FILE *fp = fopen(path, "r"); + // TODO(nasr): do a **cores see each frequency individually together with the utilization + if (!fp) + { + assert(fp); + return ERR_IO; + } + + u64 freq = 0; + if (fscanf(fp, "%lu", &freq) != 1) + { + fclose(fp); + assert(0); + return ERR_PARSE; + } + + fclose(fp); + + snprintf(out->frequency, sizeof(out->frequency), "%lu", freq); + return OK; +} + +int +cpu_read_cpu_model_name_arm64(Cpu *out) +{ + FILE *of = fopen("/proc/device-tree/model", "rb"); + if (!of) + { + assert(0); + return ERR_IO; + } + + u8 buffer[BUFFER_SIZE_DEFAULT]; + + /** + * + * note to self + * fread returns the amount of bytes read + * fwrite returns the amount of bytes written + * + */ + + size_t n = fread(buffer, 1, sizeof(buffer), of); + if (n == 0) + { + assert(0); + return ERR_IO; + } + + size_t len = 0; + while (len < n && buffer[len] != 0) + { + ++len; + } + + memcpy(out->model, buffer, len); + out->model[len] = '\0'; + + fclose(of); + + return OK; +} + +int +cpu_get_cores_enabled_arm(Cpu *out) +{ + assert(out); + + FILE *fp = fopen("/sys/devices/system/cpu/enabled", "r"); + assert(fp); + + char buf[BUFFER_SIZE_DEFAULT]; + assert(fgets(buf, sizeof(buf), fp)); + fclose(fp); + + int max_cpu = 0; + char *p = buf; + + while (*p) + { + if (*p >= '0' && *p <= '9') + { + int v = 0; + while (*p >= '0' && *p <= '9') + { + v = v * 10 + (*p++ - '0'); + } + + if (v > max_cpu) + { + max_cpu = v; + } + } + else + { + ++p; + } + } + + out->cores = (u32)max_cpu; + return OK; +} + +int +cpu_read_arm64(Cpu *out) +{ + if (!out) + { + assert(0); + return ERR_INVALID; + } + + cpu_get_cores_enabled_arm(out); + cpu_read_cpu_model_name_arm64(out); + cpu_read_enabled_core_cpu_frequency(out, (int)out->cores); + + return OK; +} + +int +cpu_read_amd64(Cpu *out) +{ + if (!out) + { + assert(0); + return ERR_INVALID; + } + + FILE *f = fopen("/proc/cpuinfo", "r"); + if (!f) + { + assert(0); + return ERR_IO; + } + + char buf[BUFFER_SIZE_LARGE]; + while (fgets(buf, sizeof(buf), f)) + { + char *colon = strchr(buf, ':'); + if (!colon) + continue; + + char *val = colon + 1; + while (*val == ' ') + val++; + + size_t len = strcspn(val, "\n"); + + if (!strncmp(buf, "vendor_id", 9)) + { + memcpy(out->vendor, val, len); + } + if (!strncmp(buf, "model name", 10)) + { + memcpy(out->model, val, len); + } + if (!strncmp(buf, "cpu MHz", 7)) + { + memcpy(out->frequency, val, len); + } + if (!strncmp(buf, "cpu cores", 9)) + { + out->cores = (u32)atoi(buf); + } + } + + fclose(f); + return OK; +} + +int +cpu_read(Cpu *out) +{ + if (!out) + { + assert(0); + return ERR_INVALID; + } + +#if defined(__arm__) || defined(__aarch64__) + if (cpu_read_arm64(out) != OK) + { + /** + * Debugging!! + * + * */ + assert(0); + } + +#elif defined(__i386__) || defined(__x86_64__) + if (cpu_read_amd64(out) != OK) + { + /** + * Debugging!! + * + * */ + assert(0); + } + +#else +#error "Unsupported architecture" +#endif + return OK; +} + +int +cpu_read_usage(Cpu *out) +{ + if (!out) + { + assert(0); + return ERR_INVALID; + } + + FILE *f = fopen("/proc/stat", "r"); + if (!f) + { + assert(0); + return ERR_IO; + } + + char buf[BUFFER_SIZE_LARGE]; + while (fgets(buf, sizeof(buf), f)) + { + if (strncmp(buf, "cpu ", 4) == 0) + { + unsigned long user, nice, system, idle, iowait, irq, softirq, steal; + if (sscanf( + buf + 4, + "%lu %lu %lu %lu %lu %lu %lu %lu", + &user, + &nice, + &system, + &idle, + &iowait, + &irq, + &softirq, + &steal) == 8) + { + u64 idleAll = idle + iowait; + u64 total = user + nice + system + idle + iowait + irq + softirq + steal; + out->idle_time = idleAll; + out->total_time = total; + break; + } + } + } + + fclose(f); + return OK; +} + +/* + * ram_create - Allocate and initialize a new Ram structure + * + * Return: Pointer to newly allocated Ram, or NULL on allocation failure + */ +Ram * +ram_create(mem_arena *m) +{ + return arena_push(m, sizeof(Ram), 1); +} + +/* + * ram_read - Read RAM information from /proc/meminfo + * @out: Pointer to Ram structure to populate + * + * Reads MemTotal and MemFree from /proc/meminfo in kilobytes. + * + * Return: OK on success, AGENT_ERR_INVALID if out is NULL, + * ERR_IO if /proc/meminfo cannot be opened + */ +int +ram_read(Ram *out) +{ + if (!out) + { + assert(0); + return ERR_INVALID; + } + + FILE *f = fopen("/proc/meminfo", "r"); + if (!f) + { + assert(0); + return ERR_IO; + } + + mem_arena *temp_arena = arena_create(KiB(8)); + + size_t total_len; + size_t free_len; + + char *total_buffer; + char *free_buffer; + + char buf[BUFFER_SIZE_SMALL]; + while (fgets(buf, sizeof(buf), f)) + { + char *colon = strchr(buf, ':'); + if (!colon) + { + continue; + } + + char *val = colon + 1; + while (*val == ' ') + { + val++; + } + + if (!strncmp(buf, "MemTotal", 8)) + { + total_len = strcspn(val, "k\n"); + total_buffer = PUSH_ARRAY(temp_arena, char, total_len); + + memcpy(total_buffer, val, total_len); + } + + if (!strncmp(buf, "MemFree", 7)) + { + free_len = strcspn(val, "k\n"); + free_buffer = PUSH_ARRAY(temp_arena, char, free_len); + + memcpy(free_buffer, val, free_len); + } + } + + out->total = parse_u64(total_buffer, total_len); + out->free = parse_u64(free_buffer, free_len); + + fclose(f); + return OK; +} + +/* + * disk_create - Allocate and initialize a new Disk structure + * + * Return: Pointer to newly allocated Disk, or NULL on allocation failure + */ +Disk * +disk_create(mem_arena *m) +{ + return arena_push(m, sizeof(Disk), 1); +} + +/* + * disk_read - Read disk partition information from /proc/partitions + * @out: Pointer to Disk structure to populate + * + * Reads all partitions from /proc/partitions, storing major/minor device + * numbers, block count, and device name for each partition. Skips the header + * line and any malformed entries. + * + * Return: OK on success, AGENT_ERR_INVALID if out is NULL, + * ERR_IO if /proc/partitions cannot be opened + */ + +int +disk_read(Disk *out, mem_arena *arena) +{ + if (!out) + { + assert(0); + return ERR_INVALID; + } + + FILE *f = fopen("/proc/partitions", "r"); + if (!f) + { + assert(0); + return ERR_IO; + } + + char buf[BUFFER_SIZE_DEFAULT]; + + while (fgets(buf, sizeof(buf), f)) + { + Partition p = { 0 }; + char name[BUFFER_SIZE_DEFAULT]; + + if (sscanf(buf, + "%lu %lu %lu %255s", + &p.major, + &p.minor, + &p.blocks, + name) != 4) + { + continue; + } + + size_t len = strlen(name); + if (len >= sizeof(p.name)) + len = sizeof(p.name) - 1; + + memcpy(p.name, name, len); + p.name[len] = 0; + + disk_push_partition(out, p, arena); + } + + fclose(f); + return OK; +} + +int +fs_usage(char *path, Disk *disk) +{ + struct statfs s; + if (statfs(path, &s) != 0) + { + return ERR_IO; + } + + i64 block_size = s.f_bsize; + + u64 blocks = (u64)s.f_blocks; + u64 bfree = (u64)s.f_bfree; + u64 bavail = (u64)s.f_bavail; + u64 bsize = (u64)block_size; + + disk->disk_usage.total = blocks * bsize; + disk->disk_usage.free = bfree * bsize; + disk->disk_usage.available = bavail * bsize; + disk->disk_usage.used = (blocks - bfree) * bsize; + + return OK; +} + +/* + * device_create - Allocate and initialize a new Device structure + * + * Return: Pointer to newly allocated Device, or NULL on allocation failure + */ +Device * +device_create(mem_arena *m) +{ + return arena_push(m, sizeof(Device), 1); +} + +/* + * collect_processes - Collect all running process IDs from /proc + * @dev: Pointer to Device structure to populate with process IDs + * + * Scans /proc directory for numeric entries (process IDs) and stores them + * as strings in the Device structure. Dynamically grows the process array + * as needed. + */ + +int +process_list_collect(Process_List *list, mem_arena *arena) +{ + DIR *d = opendir("/proc"); + if (!d) + { + assert(0); + return ERR_IO; + } + + struct dirent *e = 0; + + if (!list->items) + { + list->capacity = 8; + list->count = 0; + list->items = PUSH_ARRAY_NZ(arena, Process, list->capacity); + if (!list->items) + { + closedir(d); + assert(0); + return ERR_IO; + } + } + + while ((e = readdir(d))) + { + if (!is_numeric(e->d_name)) + continue; + + if (list->count == list->capacity) + { + size_t new_cap = list->capacity * 2; + Process *np = PUSH_ARRAY_NZ(arena, Process, new_cap); + if (!np) + break; + + memcpy(np, list->items, sizeof(Process) * list->capacity); + list->items = np; + list->capacity = new_cap; + } + + Process *p = &list->items[list->count++]; + + p->pid = atoi(e->d_name); + p->state = PROCESS_UNDEFINED; + p->utime = 0; + p->stime = 0; + p->num_threads = 0; + p->name[0] = 0; + } + + closedir(d); + return OK; +} + +/** + * +struct Proces { + char *pid; + char *name; + char *state; + char *utime; + char *num_threads; +}; + +*/ + +int +process_read(i32 pid, Process *out) +{ + char path[PATH_MAX_LEN]; + snprintf(path, sizeof(path), "/proc/%d/status", pid); + + FILE *fp = fopen(path, "r"); + if (!fp) + { + return ERR_IO; + } + + char buf[BUFFER_SIZE_LARGE]; + + /* initialize */ + out->pid = pid; + + while (fgets( + buf, + sizeof(buf), + fp)) + { + char *colon = strchr(buf, ':'); + if (!colon) + { + continue; + } + + char *val = colon + 1; + while (*val == ' ' || *val == '\t') + { + ++val; + } + + size_t len = strcspn(val, "\n"); + + if (!strncmp(buf, "Name:", 5)) + { + memcpy(out->name, val, len); + out->name[len] = 0; + } + if (!strncmp(buf, "State:", 6)) + { + char state_char = 0; + for (char *p = val; *p; ++p) + { + if ((*p >= 'A' && *p <= 'Z') || *p == 't') + { + state_char = *p; + break; + } + } + + switch (state_char) + { + case 'R': + { + out->state = PROCESS_RUNNING; + break; + } + case 'S': + { + out->state = PROCESS_SLEEPING; + break; + } + case 'D': + { + out->state = PROCESS_DISK_SLEEP; + break; + } + case 'T': + { + out->state = PROCESS_STOPPED; + break; + } + case 't': + { + out->state = PROCESS_TRACING_STOPPED; + break; + } + case 'Z': + { + out->state = PROCESS_ZOMBIE; + break; + } + case 'X': + { + out->state = PROCESS_DEAD; + break; + } + case 'I': + { + out->state = PROCESS_IDLE; + break; + } + default: + { + out->state = PROCESS_UNDEFINED; + break; + } + } + } + + if (!strncmp(buf, "Threads:", 8)) + { + out->num_threads = (u32)strtoul(val, 0, 10); + } + } + + int error = fclose(fp); + if (error != 0) + { + return ERR_IO; + } + + return OK; +} + +int +device_up_time(Device *out) +{ + if (!out) + { + assert(0); + return ERR_INVALID; + } + + FILE *f = fopen("/proc/uptime", "r"); + if (!f) + { + assert(0); + return ERR_IO; + } + + i64 s; + int result = fscanf(f, "%ld", &s); + fclose(f); + + if (result != 1) + { + assert(0); + return ERR_IO; + } + + i64 day = s / 86400; + i64 hour = s % 86400 / 3600; + i64 min = s % 3600 / 60; + sprintf(out->uptime, "%ldd %ldh %ldm", day, hour, min); + return OK; +} + +/* + * device_read - Read device information including OS version, uptime, and + * processes + * @out: Pointer to Device structure to populate + * + * Reads OS version from /proc/version, uptime from /proc/uptime, and collects + * all running process IDs from /proc directory. + * + * Return: OK on success, AGENT_ERR_INVALID if out is NULL, + * ERR_IO if required files cannot be opened + */ +int +device_read(Device *out) +{ + if (!out) + { + assert(0); + return ERR_INVALID; + } + + FILE *version = fopen("/etc/os-release", "r"); + if (!version) + { + if (version) + { + fclose(version); + } + return ERR_IO; + } + + char buffer[BUFFER_SIZE_DEFAULT]; + while (fgets(buffer, sizeof(out->os_version), version)) + { + if (!strncmp(buffer, "NAME=", 5)) + { + char *start = strchr(buffer, '"'); + if (start) + { + start++; + char *end = strchr(start, '"'); + if (end) + { + i64 len = end - start; + if ((u64)len < sizeof(out->os_version)) + { + memcpy(out->os_version, start, (u64)len); + out->os_version[len] = '\0'; + } + } + } + } + } + + fclose(version); + device_up_time(out); + + return OK; +} + +/* + * process_kill - Send a signal to a process + * @pid: Process ID to signal + * @signal: Signal number to send (e.g., SIGTERM, SIGKILL) + * + * Wrapper around kill(2) system call with error handling. + * + * Return: OK on success, + * ERR_INVALID if pid is invalid or process not found, + * ERR_PERM if permission denied, + * ERR_IO for other errors + */ +int +process_kill(pid_t pid, int signal) +{ + if (pid <= 0) + { + assert(0); + return ERR_INVALID; + } + + if (kill(pid, signal) == -1) + { + if (errno == EPERM) + { + assert(0); + return ERR_PERM; + } + if (errno == ESRCH) + { + assert(0); + return ERR_INVALID; + } + + assert(0); + return ERR_IO; + } + return OK; +} diff --git a/pkg/resources/resources.h b/pkg/resources/resources.h new file mode 100644 index 0000000..d76c4f1 --- /dev/null +++ b/pkg/resources/resources.h @@ -0,0 +1,156 @@ +#ifndef RESOURCES_H +#define RESOURCES_H + +#include +#include +#include "base.h" +#include "arena.h" + +#define RESOURCES_API_VERSION 1 + +typedef struct Cpu Cpu; +typedef struct Ram Ram; +typedef struct Disk Disk; +typedef struct Device Device; + +typedef struct Partition Partition; +typedef struct Process Process; +typedef struct Process_List Process_List; + +/** + * TODO(nasr): + * + * Heh? what are you doing with the types here, + * you mismatched the namings + * check for what you are doing in the cgo wrapper + * this could be a big issue + * + * */ +typedef int32_t ProcessState; + +typedef enum Process_State +{ + PROCESS_UNDEFINED = 0, + PROCESS_RUNNING = 1, + PROCESS_SLEEPING = 2, + PROCESS_DISK_SLEEP = 3, + PROCESS_STOPPED = 4, + PROCESS_TRACING_STOPPED = 5, + PROCESS_ZOMBIE = 6, + PROCESS_DEAD = 7, + PROCESS_IDLE = 8, + +} Process_State; + +struct Process +{ + i32 pid; + Process_State state; + u64 utime; + u64 stime; + u32 num_threads; + char name[BUFFER_SIZE_SMALL]; +}; + +struct Process_List +{ + Process *items; + size_t count; + size_t capacity; +}; + +struct Partition +{ + u64 major; + u64 minor; + u64 blocks; + char name[BUFFER_SIZE_SMALL]; +}; + +struct Cpu +{ + char vendor[BUFFER_SIZE_DEFAULT]; + char model[BUFFER_SIZE_DEFAULT]; + char frequency[BUFFER_SIZE_SMALL]; + u64 total_time; + u64 idle_time; + u32 cores; +}; + +struct Ram +{ + u64 total; + u64 free; +}; + +struct DiskUsage +{ + u64 total; + u64 free; + u64 available; + u64 used; +}; + +struct Disk +{ + Partition *partitions; + + size_t part_count; + size_t part_capacity; + + struct DiskUsage disk_usage; +}; + +struct Device +{ + char os_version[BUFFER_SIZE_DEFAULT]; + char uptime[BUFFER_SIZE_DEFAULT]; + Process_List processes; +}; + +mem_arena * +arena_create(u64 capacity); +/** + * TODO(nasr): add error handling for both the destroy and the clear + * */ +void +arena_destroy(mem_arena *arena); +void +arena_clear(mem_arena *arena); + +Cpu * +cpu_create(mem_arena *arena); +Ram * +ram_create(mem_arena *arena); +Disk * +disk_create(mem_arena *arena); +Device * +device_create(mem_arena *arena); + +int +cpu_read(Cpu *cpu); +int +cpu_read_usage(Cpu *cpu); +int +ram_read(Ram *ram); +int +disk_read(Disk *disk, mem_arena *arena); +int +device_read(Device *device); + +// TODO(nasr): add a function that updates certain values incrementally +// instead of neading to update the entire cpu struct + +int +process_list_collect(Process_List *list, mem_arena *arena); + +int +process_read(i32 pid, Process *out); + +int +process_read2(i32 pid, Process *out); + +int +process_kill(i32 pid, i32 signal); + +#endif /* RESOURCES_H */ diff --git a/pkg/resources/resources_main.c b/pkg/resources/resources_main.c new file mode 100644 index 0000000..c59aca2 --- /dev/null +++ b/pkg/resources/resources_main.c @@ -0,0 +1,47 @@ +#include "base.h" +#include "resources.h" +#include "stdio.h" +#include "arena.h" +#include "arena.c" +#include "resources.c" +#include + +int +main(void) +{ + mem_arena *arena = arena_create(MiB(1)); + + Ram *ram = ram_create(arena); + Device *device = device_create(arena); + + ram_read(ram); + process_list_collect(&device->processes, arena); + + for (size_t proc_idx = 0; + proc_idx < device->processes.count; + ++proc_idx) + { + Process *proc = (Process *)arena_push(arena, + sizeof(Process), + 1); + + i32 error = process_read( + device->processes + .items[proc_idx] + .pid, + proc); + + printf( + "[total] total=%6s free=%3s\n", + ram->total, + ram->free + ); + + if (error != OK) + { + assert(0); + } + } + + return 0; +} From b14b99c09d202a7d69919519e728282a9c14961a Mon Sep 17 00:00:00 2001 From: nasr on the server Date: Thu, 15 Jan 2026 12:20:51 +0100 Subject: [PATCH 58/59] debug: fixed linking error --- build.sh | 7 ++----- pkg/cgowrap/wrapper.go | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/build.sh b/build.sh index df8b8a7..fc3a710 100755 --- a/build.sh +++ b/build.sh @@ -83,11 +83,8 @@ echo "-------------------------------------------------------------------------" CC=cc AR=ar -AGENT_RES_DIR="pkg/agent-resources" -# SRC="$AGENT_RES_DIR/resources.c" +AGENT_RES_DIR="pkg/resources" OUT_DIR="$AGENT_RES_DIR/build" -# OUT_OBJ="$OUT_DIR/resources.o" -# OUT_LIB="$OUT_DIR/libagent_resources.a" SRC_RES="$AGENT_RES_DIR/resources.c" @@ -98,7 +95,7 @@ OUT_OBJ_RES="$OUT_DIR/resources.o" OUT_OBJ_ARENA="$OUT_DIR/arena.o" OUT_OBJ_BASE="$OUT_DIR/base.o" -OUT_LIB="$OUT_DIR/libagent_resources.a" +OUT_LIB="$OUT_DIR/libresources.a" CFLAGS=" -std=c99 diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index 034d9a0..bb54842 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -1,8 +1,8 @@ package wrapper /* -#cgo CFLAGS: -I${SRCDIR}/../agent-resources -#cgo LDFLAGS: -L${SRCDIR}/../agent-resources/build -lagent_resources +#cgo CFLAGS: -I${SRCDIR}/../resources +#cgo LDFLAGS: -L${SRCDIR}/../resources/build -lresources #include #include From 134f8409078e9f725a6bbea1786638b174248702 Mon Sep 17 00:00:00 2001 From: nasr on the server Date: Thu, 15 Jan 2026 12:24:00 +0100 Subject: [PATCH 59/59] debug: fixed arithmatic warning --- pkg/resources/base.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/resources/base.c b/pkg/resources/base.c index 7dacd30..27cfc7f 100644 --- a/pkg/resources/base.c +++ b/pkg/resources/base.c @@ -18,7 +18,8 @@ parse_u64(char *buf, size_t len) { break; } - value = value * 10 + (c - '0'); + u64 digit = (u64)(c - '0'); + value = value * 10 + digit; } return value;