From d34434778263505dbf6219d11b25dfccb96a3310 Mon Sep 17 00:00:00 2001 From: EnumDev Date: Wed, 27 Aug 2025 12:51:55 +0300 Subject: [PATCH 1/6] Switch service start, stop and restart commands to JSON --- src/ectl/main.go | 87 +++++++++----------- src/esvm/socket.go | 198 +++++++++++++++++++++++++++++++-------------- 2 files changed, 176 insertions(+), 109 deletions(-) diff --git a/src/ectl/main.go b/src/ectl/main.go index d6b1589..3af2f70 100644 --- a/src/ectl/main.go +++ b/src/ectl/main.go @@ -1,9 +1,10 @@ package main import ( + "encoding/json" "flag" "fmt" - "gopkg.in/yaml.v3" + "io" "log" "net" "os" @@ -12,6 +13,8 @@ import ( "strings" "syscall" "time" + + "gopkg.in/yaml.v3" ) // Build-time variables @@ -19,7 +22,7 @@ var version = "dev" var sysconfdir = "/etc/" var runstatedir = "/var/run/" -var socket net.Conn +var conn net.Conn func main() { @@ -29,7 +32,7 @@ func main() { // Dial esvm socket dialSocket() - defer socket.Close() + defer conn.Close() if flag.NArg() < 1 { printUsage() @@ -85,66 +88,54 @@ func main() { } else if len(flag.Args()) <= 2 { fmt.Printf("Usage: ectl service %s \n", flag.Args()[1]) return - } else if flag.Args()[1] == "start" { - if _, err := os.Stat(path.Join(runstatedir, "esvm", flag.Args()[2])); err != nil { - log.Fatalf("Could not start service! Error: %s\n", err) + } else if flag.Arg(1) == "start" || flag.Arg(1) == "stop" || flag.Arg(1) == "restart" { + type ServiceCommandJsonStruct struct { + Command string `json:"command"` + Service string `json:"service"` + } + serviceCommandJson := ServiceCommandJsonStruct{ + Command: flag.Arg(1), + Service: flag.Arg(2), } - _, err := socket.Write([]byte("start " + flag.Args()[2])) + // Encode struct to json string + jsonData, err := json.Marshal(serviceCommandJson) if err != nil { - log.Fatalf("Could not start service! Error: %s\n", err) + log.Fatalf("Could not encode JSON data! Error: %s\n", err) } - buf := make([]byte, 1024) - n, err := socket.Read(buf) + _, err = conn.Write(jsonData) if err != nil { - log.Fatalf("Could not start service! Error: %s\n", err) - } - if string(buf[:n]) != "ok" { - log.Fatalf("Could not start service! Error: expcted 'ok' got '%s'\n", string(buf)) + log.Fatalf("Could not write JSON data to socket! Error: %s\n", err) } - fmt.Println("Service started successfully!") - return - } else if flag.Args()[1] == "stop" { - if _, err := os.Stat(path.Join(runstatedir, "esvm", flag.Args()[2])); err != nil { - log.Fatalf("Could not stop service! Error: %s\n", err) - } + // Create a buffer for incoming data. + buf := make([]byte, 4096) - _, err := socket.Write([]byte("stop " + flag.Args()[2])) + // Read data from the connection. + n, err := conn.Read(buf) + if err == io.EOF { + return + } if err != nil { - log.Fatalf("Could not stop service! Error: %s\n", err) + return } - buf := make([]byte, 1024) - n, err := socket.Read(buf) + // Decoode JSON data + var returnedJsonData map[string]any + err = json.Unmarshal(buf[:n], &returnedJsonData) if err != nil { - log.Fatalf("Could not stop service! Error: %s\n", err) - } - if string(buf[:n]) != "ok" { - log.Fatalf("Could not stop service! Error: expcted 'ok' got '%s'\n", string(buf)) - } - fmt.Println("Service stopped successfully!") - return - } else if flag.Args()[1] == "restart" || flag.Args()[1] == "reload" { - if _, err := os.Stat(path.Join(runstatedir, "esvm", flag.Args()[2])); err != nil { - log.Fatalf("Could not restart service! Error: %s\n", err) + log.Fatalf("Could not decode JSON data from connection!") } - _, err := socket.Write([]byte("restart " + flag.Args()[2])) - if err != nil { - log.Fatalf("Could not restart service! Error: %s\n", err) + if err, ok := returnedJsonData["error"]; ok { + log.Fatal(err) + } else if msg, ok := returnedJsonData["success"]; ok { + fmt.Println(msg) + } else { + log.Fatal("Connection returned empty string!") } - buf := make([]byte, 1024) - n, err := socket.Read(buf) - if err != nil { - log.Fatalf("Could not restart service! Error: %s\n", err) - } - if string(buf[:n]) != "ok" { - log.Fatalf("Could not restart service! Error: expcted 'ok' got '%s'\n", string(buf)) - } - fmt.Println("Service restarted successfully!") return } else if flag.Args()[1] == "enable" { // Check if service exists @@ -329,12 +320,12 @@ func dialSocket() { } var err error - socket, err = net.Dial("unix", path.Join(runstatedir, "esvm/esvm.sock")) + conn, err = net.Dial("unix", path.Join(runstatedir, "esvm/esvm.sock")) if err != nil { log.Fatalf("Failed to connect to esvm.sock! Error: %s\n", err) } - if err := socket.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { + if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { log.Fatalf("Failed to set write deadline! Error: %s\n", err) } } diff --git a/src/esvm/socket.go b/src/esvm/socket.go index a55392a..f409c3d 100644 --- a/src/esvm/socket.go +++ b/src/esvm/socket.go @@ -1,18 +1,26 @@ package main import ( + "encoding/json" + "fmt" "io" "net" "path" - "strings" ) +var commandHandlers = make(map[string]func(conn net.Conn, jsonData map[string]any)) + func initSocket() (socket net.Listener, err error) { socket, err = net.Listen("unix", path.Join(runtimeServiceDir, "esvm.sock")) if err != nil { return nil, err } + // Register command handlers + commandHandlers["start"] = handleStartServiceCommand + commandHandlers["stop"] = handleStopServiceCommand + commandHandlers["restart"] = handleRestartServiceCommand + return socket, nil } @@ -35,68 +43,136 @@ func listenToSocket() { return } if err != nil { - logger.Fatal(err) + return } - command := string(buf[:n]) - commandSplit := strings.Split(command, " ") - - if len(commandSplit) >= 2 { - if commandSplit[0] == "start" { - service := GetServiceByName(commandSplit[1]) - if service == nil { - _, err := conn.Write([]byte("service not found")) - if err != nil { - return - } - } - if err := service.StartService(); err != nil { - _, err := conn.Write([]byte("could not start service")) - if err != nil { - return - } - } - _, err := conn.Write([]byte("ok")) - if err != nil { - return - } - } else if commandSplit[0] == "stop" { - service := GetServiceByName(commandSplit[1]) - if service == nil { - _, err := conn.Write([]byte("service not found")) - if err != nil { - return - } - } - if err := service.StopService(); err != nil { - _, err := conn.Write([]byte("could not stop service")) - if err != nil { - return - } - } - _, err := conn.Write([]byte("ok")) - if err != nil { - return - } - } else if commandSplit[0] == "restart" { - service := GetServiceByName(commandSplit[1]) - if service == nil { - _, err := conn.Write([]byte("service not found")) - if err != nil { - return - } - } - if err := service.RestartService(); err != nil { - _, err := conn.Write([]byte("could not restart service")) - if err != nil { - return - } - } - _, err := conn.Write([]byte("ok")) - if err != nil { - return - } - } + // Decoode JSON data + var jsonData map[string]any + err = json.Unmarshal(buf[:n], &jsonData) + if err != nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Invalid JSON"))) + return } + + // Get command to execute + command, ok := jsonData["command"] + if !ok { + conn.Write(wrapErrorInJson(fmt.Errorf("'command' field missing"))) + return + } + + // Get command handler + commandHandler, ok := commandHandlers[command.(string)] + if !ok { + conn.Write(wrapErrorInJson(fmt.Errorf("command (%s) has not been implemented", command.(string)))) + return + } + commandHandler(conn, jsonData) }(conn) } + +func handleStartServiceCommand(conn net.Conn, jsonData map[string]any) { + // Get service name from json data + serviceName, ok := jsonData["service"] + if !ok { + conn.Write(wrapErrorInJson(fmt.Errorf("'service' field missing"))) + return + } + + // Ensure service exists + service := GetServiceByName(serviceName.(string)) + if service == nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Service (%s) not found", serviceName.(string)))) + return + } + + // Start the service + if err := service.StartService(); err != nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Service (%s) could not be started", serviceName.(string)))) + return + } + + conn.Write(wrapSuccessMsgInJson(fmt.Sprintf("Service (%s) has started sucessfully", serviceName.(string)))) +} + +func handleStopServiceCommand(conn net.Conn, jsonData map[string]any) { + // Get service name from json data + serviceName, ok := jsonData["service"] + if !ok { + conn.Write(wrapErrorInJson(fmt.Errorf("'service' field missing"))) + return + } + + // Ensure service exists + service := GetServiceByName(serviceName.(string)) + if service == nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Service (%s) not found", serviceName.(string)))) + return + } + + // Stop the service + if err := service.StopService(); err != nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Service (%s) could not be stopped", serviceName.(string)))) + return + } + + conn.Write(wrapSuccessMsgInJson(fmt.Sprintf("Service (%s) has stopped sucessfully", serviceName.(string)))) +} + +func handleRestartServiceCommand(conn net.Conn, jsonData map[string]any) { + // Get service name from json data + serviceName, ok := jsonData["service"] + if !ok { + conn.Write(wrapErrorInJson(fmt.Errorf("'service' field missing"))) + return + } + + // Ensure service exists + service := GetServiceByName(serviceName.(string)) + if service == nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Service (%s) not found", serviceName.(string)))) + return + } + + // Restart the service + if err := service.RestartService(); err != nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Service (%s) could not be restarted", serviceName.(string)))) + return + } + + conn.Write(wrapSuccessMsgInJson(fmt.Sprintf("Service (%s) has restarted sucessfully", serviceName.(string)))) +} + +func wrapErrorInJson(err error) []byte { + // Wrap error in struct + type jsonErrorStruct struct { + Error string `json:"error"` + } + jsonError := jsonErrorStruct{ + Error: err.Error(), + } + + // Encode struct to json string + jsonData, _err := json.Marshal(jsonError) + if _err != nil { + return nil + } + return jsonData +} + +func wrapSuccessMsgInJson(msg string) []byte { + // Wrap message in struct + type jsonSuccessStruct struct { + Success string `json:"success"` + } + jsonSuccess := jsonSuccessStruct{ + Success: msg, + } + + // Encode struct to json string + jsonData, _err := json.Marshal(jsonSuccess) + if _err != nil { + return nil + } + return jsonData +} From 0df715dda16f91f5d4a7ef1658a8461b41f2c242 Mon Sep 17 00:00:00 2001 From: EnumDev Date: Wed, 27 Aug 2025 15:57:56 +0300 Subject: [PATCH 2/6] Move service enable/disable functionality to esvm --- src/ectl/go.mod | 2 - src/ectl/go.sum | 4 -- src/ectl/main.go | 100 +------------------------------------------- src/esvm/service.go | 62 +++++++++++++++++++++++++++ src/esvm/socket.go | 64 ++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 105 deletions(-) diff --git a/src/ectl/go.mod b/src/ectl/go.mod index 28b0013..56db4d6 100644 --- a/src/ectl/go.mod +++ b/src/ectl/go.mod @@ -1,5 +1,3 @@ module ectl go 1.23.4 - -require gopkg.in/yaml.v3 v3.0.1 diff --git a/src/ectl/go.sum b/src/ectl/go.sum index a62c313..e69de29 100644 --- a/src/ectl/go.sum +++ b/src/ectl/go.sum @@ -1,4 +0,0 @@ -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/ectl/main.go b/src/ectl/main.go index 3af2f70..27a7fda 100644 --- a/src/ectl/main.go +++ b/src/ectl/main.go @@ -13,8 +13,6 @@ import ( "strings" "syscall" "time" - - "gopkg.in/yaml.v3" ) // Build-time variables @@ -88,7 +86,7 @@ func main() { } else if len(flag.Args()) <= 2 { fmt.Printf("Usage: ectl service %s \n", flag.Args()[1]) return - } else if flag.Arg(1) == "start" || flag.Arg(1) == "stop" || flag.Arg(1) == "restart" { + } else if flag.Arg(1) == "start" || flag.Arg(1) == "stop" || flag.Arg(1) == "restart" || flag.Arg(1) == "enable" || flag.Arg(1) == "disable" { type ServiceCommandJsonStruct struct { Command string `json:"command"` Service string `json:"service"` @@ -136,102 +134,6 @@ func main() { log.Fatal("Connection returned empty string!") } - return - } else if flag.Args()[1] == "enable" { - // Check if service exists - found := false - entries, err := os.ReadDir(path.Join(sysconfdir, "esvm/services/")) - if err != nil { - log.Fatalf("Could not enable service! Error: %s\n", err) - } - type minimalServiceStruct struct { - Name string `yaml:"name"` - } - for _, entry := range entries { - if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".esv") { - continue - } - - bytes, err := os.ReadFile(path.Join(sysconfdir, "esvm/services", entry.Name())) - if err != nil { - log.Fatalf("Could not enable service! Error: %s\n", err) - } - - sv := minimalServiceStruct{Name: ""} - err = yaml.Unmarshal(bytes, &sv) - if err != nil { - log.Fatalf("Could not enable service! Error: %s\n", err) - } - - if sv.Name == flag.Args()[2] { - found = true - break - } - } - if !found { - log.Fatalf("Service does not exist!") - } - - if _, err := os.Stat(path.Join(sysconfdir, "esvm/enabled_services")); err != nil { - err := os.WriteFile(path.Join(sysconfdir, "esvm/enabled_services"), []byte(flag.Args()[2]+"\n"), 0644) - if err != nil { - log.Fatalf("Could not enable service! Error: %s\n", err) - } - return - } - - file, err := os.ReadFile(path.Join(sysconfdir, "esvm/enabled_services")) - if err != nil { - log.Fatalf("Could not enable service! Error: %s\n", err) - } - for _, line := range strings.Split(string(file), "\n") { - if strings.TrimSpace(line) == flag.Args()[2] { - fmt.Println("Service is already enabled!") - return - } - } - - err = os.WriteFile(path.Join(sysconfdir, "esvm/enabled_services"), []byte(string(file)+flag.Args()[2]+"\n"), 0644) - if err != nil { - log.Fatalf("Could not enable service! Error: %s\n", err) - } - - fmt.Printf("Service (%s) has been enabled!\n", flag.Args()[2]) - return - } else if flag.Args()[1] == "disable" { - if _, err := os.Stat(path.Join(sysconfdir, "esvm/enabled_services")); err != nil { - fmt.Println("Service is already disabled!") - return - } - - file, err := os.ReadFile(path.Join(sysconfdir, "esvm/enabled_services")) - if err != nil { - log.Fatalf("Could not disable service! Error: %s\n", err) - } - - lines := strings.Split(string(file), "\n") - found := false - for i := len(lines) - 1; i >= 0; i-- { - line := strings.TrimSpace(lines[i]) - if strings.TrimSpace(line) == flag.Args()[2] { - lines = append(lines[:i], lines[i+1:]...) - found = true - } else if strings.TrimSpace(line) == "" { - lines = append(lines[:i], lines[i+1:]...) - } - } - - if !found { - fmt.Println("Service is already disabled!") - return - } - - err = os.WriteFile(path.Join(sysconfdir, "esvm/enabled_services"), []byte(strings.Join(lines, "\n")+"\n"), 0644) - if err != nil { - log.Fatalf("Could not disable service! Error: %s\n", err) - } - - fmt.Printf("Service (%s) has been disabled!\n", flag.Args()[2]) return } else if flag.Args()[1] == "status" { if _, err := os.Stat(path.Join(runstatedir, "esvm", flag.Args()[2])); err != nil { diff --git a/src/esvm/service.go b/src/esvm/service.go index b67c2ec..a89dd3f 100644 --- a/src/esvm/service.go +++ b/src/esvm/service.go @@ -1,6 +1,7 @@ package main import ( + "io" "os" "os/exec" "path" @@ -308,3 +309,64 @@ func (service *EnitService) RestartService() error { return nil } + +// Functions will be rewritten at some point to allow enabling unloaded services + +func (service *EnitService) isEnabled() bool { + contents, err := os.ReadFile(path.Join(serviceConfigDir, "enabled_services")) + if err != nil { + return false + } + + for _, line := range strings.Split(string(contents), "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + + if line == service.Name { + return true + } + } + + return false +} + +func (service *EnitService) SetEnabled(isEnabled bool) error { + // Return if service is already in correct state + if service.isEnabled() == isEnabled { + return nil + } + + // Create or open enabled_services file + file, err := os.OpenFile(path.Join(serviceConfigDir, "enabled_services"), os.O_CREATE|os.O_RDWR, 0644) + if err != nil { + return err + } + defer file.Close() + + // Get enabled_services file contents + contents, err := io.ReadAll(file) + if err != nil { + return err + } + + // Modify contents + strContents := string(contents) + if isEnabled { + strContents += service.Name + "\n" + } else { + strContents = strings.ReplaceAll(strContents, service.Name+"\n", "") + } + + // Write new contents to file + file.Truncate(0) + file.Seek(0, 0) + _, err = file.WriteString(strContents) + if err != nil { + return err + } + file.Sync() + + return nil +} diff --git a/src/esvm/socket.go b/src/esvm/socket.go index f409c3d..8c94a55 100644 --- a/src/esvm/socket.go +++ b/src/esvm/socket.go @@ -20,6 +20,8 @@ func initSocket() (socket net.Listener, err error) { commandHandlers["start"] = handleStartServiceCommand commandHandlers["stop"] = handleStopServiceCommand commandHandlers["restart"] = handleRestartServiceCommand + commandHandlers["enable"] = handleEnableServiceCommand + commandHandlers["disable"] = handleDisableServiceCommand return socket, nil } @@ -143,6 +145,68 @@ func handleRestartServiceCommand(conn net.Conn, jsonData map[string]any) { conn.Write(wrapSuccessMsgInJson(fmt.Sprintf("Service (%s) has restarted sucessfully", serviceName.(string)))) } +func handleEnableServiceCommand(conn net.Conn, jsonData map[string]any) { + // Get service name from json data + serviceName, ok := jsonData["service"] + if !ok { + conn.Write(wrapErrorInJson(fmt.Errorf("'service' field missing"))) + return + } + + // Ensure service exists + service := GetServiceByName(serviceName.(string)) + if service == nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Service (%s) not found", serviceName.(string)))) + return + } + + // Check if service is already enabled + if service.isEnabled() { + conn.Write(wrapSuccessMsgInJson(fmt.Sprintf("Service (%s) is already enabled", serviceName.(string)))) + return + } + + // Enable service + err := service.SetEnabled(true) + if err != nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Could not enable service! Error: %s", err))) + return + } + + conn.Write(wrapSuccessMsgInJson(fmt.Sprintf("Service (%s) was enabled sucessfully", serviceName.(string)))) +} + +func handleDisableServiceCommand(conn net.Conn, jsonData map[string]any) { + // Get service name from json data + serviceName, ok := jsonData["service"] + if !ok { + conn.Write(wrapErrorInJson(fmt.Errorf("'service' field missing"))) + return + } + + // Ensure service exists + service := GetServiceByName(serviceName.(string)) + if service == nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Service (%s) not found", serviceName.(string)))) + return + } + + // Check if service is already disabled + if !service.isEnabled() { + conn.Write(wrapSuccessMsgInJson(fmt.Sprintf("Service (%s) is already disabled", serviceName.(string)))) + return + } + + // Disable service + err := service.SetEnabled(false) + if err != nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Could not disable service! Error: %s", err))) + return + } + + conn.Write(wrapSuccessMsgInJson(fmt.Sprintf("Service (%s) was disabled sucessfully", serviceName.(string)))) +} + func wrapErrorInJson(err error) []byte { // Wrap error in struct type jsonErrorStruct struct { From ba51f2ec4daff8c127a4af882813bff0fd1b2dda Mon Sep 17 00:00:00 2001 From: EnumDev Date: Wed, 27 Aug 2025 16:17:39 +0300 Subject: [PATCH 3/6] Use new isEnabled function in ESVM initialization --- src/esvm/main.go | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/esvm/main.go b/src/esvm/main.go index 01502d0..62a1941 100644 --- a/src/esvm/main.go +++ b/src/esvm/main.go @@ -8,7 +8,6 @@ import ( "os" "os/signal" "path" - "slices" "strings" "syscall" "time" @@ -23,7 +22,6 @@ var runtimeServiceDir string var serviceConfigDir string var Services = make([]EnitService, 0) -var EnabledServices = make([]string, 0) var logger *log.Logger var socket net.Listener @@ -203,23 +201,10 @@ func Init() { } } - // Get enabled services - if _, err := os.Stat(path.Join(serviceConfigDir, "enabled_services")); err == nil { - file, err := os.ReadFile(path.Join(serviceConfigDir, "enabled_services")) - if err != nil { - return - } - for _, line := range strings.Split(string(file), "\n") { - if line != "" { - EnabledServices = append(EnabledServices, line) - } - } - } - // Get enabled services that meet their dependencies servicesWithMetDepends := make([]EnitService, 0) for _, service := range Services { - if slices.Contains(EnabledServices, service.Name) && len(service.GetUnmetDependencies()) == 0 { + if service.isEnabled() && len(service.GetUnmetDependencies()) == 0 { servicesWithMetDepends = append(servicesWithMetDepends, service) } } From 1e35b0a000caddeb1ca309fa3a7f1d0e71b9818d Mon Sep 17 00:00:00 2001 From: EnumDev Date: Wed, 27 Aug 2025 20:23:15 +0300 Subject: [PATCH 4/6] Move service status functionality to esvm --- src/ectl/main.go | 127 +++++++++++++++++--------------------------- src/esvm/service.go | 9 ++++ src/esvm/socket.go | 31 +++++++++++ 3 files changed, 88 insertions(+), 79 deletions(-) diff --git a/src/ectl/main.go b/src/ectl/main.go index 27a7fda..a3645bf 100644 --- a/src/ectl/main.go +++ b/src/ectl/main.go @@ -9,8 +9,6 @@ import ( "net" "os" "path" - "strconv" - "strings" "syscall" "time" ) @@ -60,28 +58,7 @@ func main() { fmt.Println("Usage: ectl service [service]") return } else if flag.Args()[1] == "list" { - if _, err := os.Stat(path.Join(runstatedir, "esvm")); err != nil { - log.Fatalf("Could not list services! Error: %s\n", err) - } - - entries, err := os.ReadDir(path.Join(runstatedir, "esvm")) - if err != nil { - log.Fatalf("Could not list services! Error: %s\n", err) - } - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - state := getServiceState(entry.Name()) - enabled := strconv.FormatBool(isServiceEnabled(entry.Name())) - enabled = strings.ToUpper(enabled[:1]) + strings.ToLower(enabled[1:]) - - fmt.Println("Service name: " + entry.Name()) - fmt.Printf(" State: %s\n", state) - fmt.Printf(" Enabled: %s\n", enabled) - } + fmt.Println("To be impemented") return } else if len(flag.Args()) <= 2 { fmt.Printf("Usage: ectl service %s \n", flag.Args()[1]) @@ -136,17 +113,56 @@ func main() { return } else if flag.Args()[1] == "status" { - if _, err := os.Stat(path.Join(runstatedir, "esvm", flag.Args()[2])); err != nil { - log.Fatalf("Could not get service status! Error: %s\n", err) + type ServiceCommandJsonStruct struct { + Command string `json:"command"` + Service string `json:"service"` + } + serviceCommandJson := ServiceCommandJsonStruct{ + Command: flag.Arg(1), + Service: flag.Arg(2), } - state := getServiceState(flag.Args()[2]) - enabled := strconv.FormatBool(isServiceEnabled(flag.Args()[2])) - enabled = strings.ToUpper(enabled[:1]) + strings.ToLower(enabled[1:]) + // Encode struct to json string + jsonData, err := json.Marshal(serviceCommandJson) + if err != nil { + log.Fatalf("Could not encode JSON data! Error: %s\n", err) + } + + _, err = conn.Write(jsonData) + if err != nil { + log.Fatalf("Could not write JSON data to socket! Error: %s\n", err) + } + + // Create a buffer for incoming data. + buf := make([]byte, 4096) + + // Read data from the connection. + n, err := conn.Read(buf) + if err == io.EOF { + return + } + if err != nil { + return + } + + // Decoode JSON data + var returnedJsonData map[string]any + err = json.Unmarshal(buf[:n], &returnedJsonData) + if err != nil { + log.Fatalf("Could not decode JSON data from connection!") + } + + if err, ok := returnedJsonData["error"]; ok { + log.Fatal(err) + } + + serviceState := returnedJsonData["state"].(string) + serviceEnabled := returnedJsonData["is_enabled"].(bool) + + fmt.Printf("Name: %s\n", flag.Arg(2)) + fmt.Printf("State: %s\n", serviceState) + fmt.Printf("Enabled: %t\n", serviceEnabled) - fmt.Println("Service name: " + flag.Args()[2]) - fmt.Printf(" State: %s\n", state) - fmt.Printf(" Enabled: %s\n", enabled) return } } @@ -155,53 +171,6 @@ func main() { os.Exit(1) } -func getServiceState(serviceName string) string { - if _, err := os.Stat(path.Join(runstatedir, "esvm", serviceName)); err != nil { - return "" - } - - var state uint64 - bytes, err := os.ReadFile(path.Join(runstatedir, "esvm", serviceName, "state")) - if err != nil { - state = 0 - } - state, err = strconv.ParseUint(string(bytes), 10, 8) - - switch state { - case 1: - return "Unloaded" - case 2: - return "Running" - case 3: - return "Stopped" - case 4: - return "Crashed" - case 5: - return "Completed" - default: - return "Unknown" - } -} - -func isServiceEnabled(serviceName string) bool { - if _, err := os.Stat(path.Join(sysconfdir, "esvm/enabled_services")); err != nil { - return false - } - - file, err := os.ReadFile(path.Join(sysconfdir, "esvm/enabled_services")) - if err != nil { - return false - } - - for _, line := range strings.Split(string(file), "\n") { - if strings.TrimSpace(line) == serviceName { - return true - } - } - - return false -} - func printUsage() { fmt.Println("Available sucommands:") fmt.Println("ectl version | Show enit version") diff --git a/src/esvm/service.go b/src/esvm/service.go index a89dd3f..828cbd4 100644 --- a/src/esvm/service.go +++ b/src/esvm/service.go @@ -22,6 +22,15 @@ const ( EnitServiceCompleted ) +var EnitServiceStateNames map[EnitServiceState]string = map[EnitServiceState]string{ + EnitServiceUnknown: "unknown", + EnitServiceUnloaded: "unloaded", + EnitServiceRunning: "running", + EnitServiceStopped: "stopped", + EnitServiceCrashed: "crashed", + EnitServiceCompleted: "completed", +} + type EnitService struct { Name string `yaml:"name"` Description string `yaml:"description,omitempty"` diff --git a/src/esvm/socket.go b/src/esvm/socket.go index 8c94a55..26e429f 100644 --- a/src/esvm/socket.go +++ b/src/esvm/socket.go @@ -22,6 +22,7 @@ func initSocket() (socket net.Listener, err error) { commandHandlers["restart"] = handleRestartServiceCommand commandHandlers["enable"] = handleEnableServiceCommand commandHandlers["disable"] = handleDisableServiceCommand + commandHandlers["status"] = handleStatusServiceCommand return socket, nil } @@ -207,6 +208,36 @@ func handleDisableServiceCommand(conn net.Conn, jsonData map[string]any) { conn.Write(wrapSuccessMsgInJson(fmt.Sprintf("Service (%s) was disabled sucessfully", serviceName.(string)))) } +func handleStatusServiceCommand(conn net.Conn, jsonData map[string]any) { + // Get service name from json data + serviceName, ok := jsonData["service"] + if !ok { + conn.Write(wrapErrorInJson(fmt.Errorf("'service' field missing"))) + return + } + + // Ensure service exists + service := GetServiceByName(serviceName.(string)) + if service == nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Service (%s) not found", serviceName.(string)))) + return + } + + statusMap := make(map[string]any) + statusMap["name"] = service.Name + statusMap["state"] = EnitServiceStateNames[service.GetCurrentState()] + statusMap["is_enabled"] = service.isEnabled() + + // Encode map to json string + newJsonData, err := json.Marshal(statusMap) + if err != nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Could not encode JSON data"))) + return + } + + conn.Write(newJsonData) +} + func wrapErrorInJson(err error) []byte { // Wrap error in struct type jsonErrorStruct struct { From 71435bc676ba7b39d47f032da4125126b4c2ff48 Mon Sep 17 00:00:00 2001 From: EnumDev Date: Thu, 28 Aug 2025 11:09:00 +0300 Subject: [PATCH 5/6] Move service list functionality to esvm --- src/ectl/main.go | 72 ++++++++++++++++++++++++++++++++++++++++++---- src/esvm/socket.go | 24 ++++++++++++++++ 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/ectl/main.go b/src/ectl/main.go index a3645bf..c06a509 100644 --- a/src/ectl/main.go +++ b/src/ectl/main.go @@ -57,13 +57,13 @@ func main() { if len(flag.Args()) <= 1 { fmt.Println("Usage: ectl service [service]") return - } else if flag.Args()[1] == "list" { - fmt.Println("To be impemented") - return - } else if len(flag.Args()) <= 2 { - fmt.Printf("Usage: ectl service %s \n", flag.Args()[1]) - return } else if flag.Arg(1) == "start" || flag.Arg(1) == "stop" || flag.Arg(1) == "restart" || flag.Arg(1) == "enable" || flag.Arg(1) == "disable" { + // Ensure service name argument has been set + if len(flag.Args()) <= 2 { + fmt.Printf("Usage: ectl service %s \n", flag.Args()[1]) + return + } + type ServiceCommandJsonStruct struct { Command string `json:"command"` Service string `json:"service"` @@ -113,6 +113,12 @@ func main() { return } else if flag.Args()[1] == "status" { + // Ensure service name argument has been set + if len(flag.Args()) <= 2 { + fmt.Printf("Usage: ectl service %s \n", flag.Args()[1]) + return + } + type ServiceCommandJsonStruct struct { Command string `json:"command"` Service string `json:"service"` @@ -163,6 +169,60 @@ func main() { fmt.Printf("State: %s\n", serviceState) fmt.Printf("Enabled: %t\n", serviceEnabled) + return + } else if flag.Arg(1) == "list" { + type ServiceCommandJsonStruct struct { + Command string `json:"command"` + } + serviceCommandJson := ServiceCommandJsonStruct{ + Command: flag.Arg(1), + } + + // Encode struct to json string + jsonData, err := json.Marshal(serviceCommandJson) + if err != nil { + log.Fatalf("Could not encode JSON data! Error: %s\n", err) + } + + _, err = conn.Write(jsonData) + if err != nil { + log.Fatalf("Could not write JSON data to socket! Error: %s\n", err) + } + + // Create a buffer for incoming data. + buf := make([]byte, 4096) + + // Read data from the connection. + n, err := conn.Read(buf) + if err == io.EOF { + return + } + if err != nil { + return + } + + // Decoode JSON data + var returnedJsonData map[string]any + err = json.Unmarshal(buf[:n], &returnedJsonData) + if err != nil { + log.Fatalf("Could not decode JSON data from connection!") + } + + if err, ok := returnedJsonData["error"]; ok { + log.Fatal(err) + } + + for _, serviceMap := range returnedJsonData["services"].([]any) { + serviceName := serviceMap.(map[string]any)["name"].(string) + serviceState := serviceMap.(map[string]any)["state"].(string) + serviceEnabled := serviceMap.(map[string]any)["is_enabled"].(bool) + + fmt.Printf("Name: %s\n", serviceName) + fmt.Printf("State: %s\n", serviceState) + fmt.Printf("Enabled: %t\n", serviceEnabled) + fmt.Println() + } + return } } diff --git a/src/esvm/socket.go b/src/esvm/socket.go index 26e429f..edde9cf 100644 --- a/src/esvm/socket.go +++ b/src/esvm/socket.go @@ -23,6 +23,7 @@ func initSocket() (socket net.Listener, err error) { commandHandlers["enable"] = handleEnableServiceCommand commandHandlers["disable"] = handleDisableServiceCommand commandHandlers["status"] = handleStatusServiceCommand + commandHandlers["list"] = handleListServicesCommand return socket, nil } @@ -238,6 +239,29 @@ func handleStatusServiceCommand(conn net.Conn, jsonData map[string]any) { conn.Write(newJsonData) } +func handleListServicesCommand(conn net.Conn, _ map[string]any) { + servicesMap := make(map[string]any) + servicesMap["services"] = make([]map[string]any, 0) + + // Loop through each service + for _, service := range Services { + statusMap := make(map[string]any) + statusMap["name"] = service.Name + statusMap["state"] = EnitServiceStateNames[service.GetCurrentState()] + statusMap["is_enabled"] = service.isEnabled() + servicesMap["services"] = append(servicesMap["services"].([]map[string]any), statusMap) + } + + // Encode map to json string + newJsonData, err := json.Marshal(servicesMap) + if err != nil { + conn.Write(wrapErrorInJson(fmt.Errorf("Could not encode JSON data"))) + return + } + + conn.Write(newJsonData) +} + func wrapErrorInJson(err error) []byte { // Wrap error in struct type jsonErrorStruct struct { From c58802301ae1729f3cd4b1d97709ee9c3fb3654d Mon Sep 17 00:00:00 2001 From: EnumDev Date: Thu, 28 Aug 2025 11:10:04 +0300 Subject: [PATCH 6/6] Add --json flag that prints out output in json format --- src/ectl/main.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/ectl/main.go b/src/ectl/main.go index c06a509..e13e8c8 100644 --- a/src/ectl/main.go +++ b/src/ectl/main.go @@ -24,6 +24,7 @@ func main() { // Set and parse flags printVersion := flag.Bool("version", false, "print version and exit") + printJson := flag.Bool("json", false, "print output in json format") flag.Parse() // Dial esvm socket @@ -96,6 +97,12 @@ func main() { return } + // Print json data if flag is set + if *printJson { + fmt.Println(string(buf[:n])) + return + } + // Decoode JSON data var returnedJsonData map[string]any err = json.Unmarshal(buf[:n], &returnedJsonData) @@ -151,6 +158,12 @@ func main() { return } + // Print json data if flag is set + if *printJson { + fmt.Println(string(buf[:n])) + return + } + // Decoode JSON data var returnedJsonData map[string]any err = json.Unmarshal(buf[:n], &returnedJsonData) @@ -200,6 +213,12 @@ func main() { if err != nil { return } + + // Print json data if flag is set + if *printJson { + fmt.Println(string(buf[:n])) + return + } // Decoode JSON data var returnedJsonData map[string]any