From 2f107f7f288b3d29831c5b457cf349d48e76e83f Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Mon, 27 Dec 2021 11:17:24 +0100 Subject: [PATCH 1/7] Added VERY experimental (untested) networking code --- src/core/control.nim | 23 +++++++++++++++++++++++ src/core/mainloop.nim | 40 ++++++++++++++++++++++++++++++++++++++-- src/core/services.nim | 5 +++-- src/core/shutdown.nim | 36 ++++++++++++++++++++++++++++++++++-- 4 files changed, 98 insertions(+), 6 deletions(-) diff --git a/src/core/control.nim b/src/core/control.nim index a19ac77..3e17a2c 100644 --- a/src/core/control.nim +++ b/src/core/control.nim @@ -11,3 +11,26 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import os +import net +import strformat +import shutdown + + +import ../util/logging +import ../util/misc + + +proc initControlSocket*(logger: Logger, path: string = "/var/run/nimd.sock"): Socket = + ## Initializes NimD's control socket (an unbuffered + ## TCP Unix Domain Socket) binding it to the given + ## path (defaults to /var/run/nimd.sock) + try: + logger.info(&"Initializing control socket at '{path}'") + if exists(path): + removeFile(path) + result = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP, buffered=false) + bindUnix(result, path) + except OSError: + logger.error(&"Error when binding unix socket at '{path}': {getCurrentExceptionMsg()}") + nimDExit(logger, code=int(osLastError())) \ No newline at end of file diff --git a/src/core/mainloop.nim b/src/core/mainloop.nim index 214362c..b968141 100644 --- a/src/core/mainloop.nim +++ b/src/core/mainloop.nim @@ -13,10 +13,13 @@ # limitations under the License. import strformat import os +import net import ../util/[logging, misc] import services +import control +import shutdown @@ -27,10 +30,43 @@ proc mainLoop*(logger: Logger) = logger.debug(&"Unblocking signals") unblockSignals(logger) logger.info("System initialization complete, going idle") - logger.switchToFile() + var opType: string try: + logger.trace("Calling initControlSocket()") + var serverSocket = initControlSocket(logger) + serverSocket.listen(5) + var clientSocket = newSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) + # logger.switchToFile() while true: - sleepSeconds(30) + serverSocket.accept(clientSocket) + if clientSocket.recv(opType, size=1) == 0: + logger.debug(&"Client has disconnected, waiting for new connection") + continue + logger.debug(&"Received operation type '{opType}' via control socket") + # The operation type is a single byte: + # - 'p' -> poweroff + # - 'r' -> reboot + # - 'h' -> halt + # - 's' -> Services-related operations (start, stop, get status, etc.) + case opType: + of "": + logger.debug(&"Empty read from control socket: did the client disconnect?") + continue + of "p": + logger.info("Received shutdown request") + shutdown(logger) + of "r": + logger.info("Received reboot request") + reboot(logger) + of "h": + logger.info("Received halt request") + halt(logger) + of "s": + discard # TODO + else: + logger.warning(&"Received unknown operation type '{opType}' via control socket, ignoring it") + discard + clientSocket.close() except: logger.critical(&"A critical error has occurred while running, restarting the mainloop in 30 seconds! Error -> {getCurrentExceptionMsg()}") sleepSeconds(30) diff --git a/src/core/services.nim b/src/core/services.nim index e4fb00d..cd3fb07 100644 --- a/src/core/services.nim +++ b/src/core/services.nim @@ -327,8 +327,9 @@ proc startService(logger: Logger, service: Service) = elif pid == 0: logger.trace(&"New child has been spawned") supervisorWorker(logger, service, process) - # If the service is unsupervised we just spawn the logger worker - loggerWorker(logger, service, process) + # If the service is unsupervised we just spawn the logger worker (assuming it doesn't use poParentStreams) + if not service.useParentStreams: + loggerWorker(logger, service, process) except: logger.error(&"Error while starting service '{service.name}': {getCurrentExceptionMsg()}") diff --git a/src/core/shutdown.nim b/src/core/shutdown.nim index cb2bd3f..e51cf13 100644 --- a/src/core/shutdown.nim +++ b/src/core/shutdown.nim @@ -18,6 +18,8 @@ import glob import strutils import strformat import times +import tables +import syscall import ../util/logging @@ -29,6 +31,9 @@ type ShutdownHandler* = ref object body*: proc (logger: Logger, code: int) +const reboot_codes = {"poweroff": 0x4321fedc'i64, "restart": 0x01234567'i64, "halt": 0xcdef0123}.toTable() + + proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler = result = ShutdownHandler(body: body) @@ -80,6 +85,7 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) = ## NimD's exit point. This function tries to shut down ## as cleanly as possible. When emerg equals true, it will ## try to spawn a root shell and exit + logger.switchToConsole() if emerg: # We're in emergency mode: do not crash the kernel, spawn a shell and exit logger.fatal("NimD has entered emergency mode and cannot continue. You will be now (hopefully) dropped in a root shell: you're on your own. May the force be with you") @@ -89,7 +95,7 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) = quit(-1) logger.warning("The system is shutting down") logger.info("Processing shutdown runlevel") - startServices(logger, Shutdown) + startServices(logger, RunLevel.Shutdown) logger.info("Running shutdown handlers") try: for handler in shutdownHandlers: @@ -108,4 +114,30 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) = logger.info("Terminating child processes with SIGKILL") discard posix.kill(-1, SIGKILL) logger.warning("Shutdown procedure complete, sending final termination signal") - quit(code) + + +proc reboot*(logger: Logger) = + ## Reboots the system + logger.debug("Switching logs to console") + logger.switchToConsole() + logger.info("The system is rebooting") + nimDExit(logger, 0, emerg=false) + discard syscall(REBOOT, 0xfee1dead, 537993216, reboot_codes["reboot"]) + + +proc shutdown*(logger: Logger) = + ## Shuts the system off + logger.debug("Switching logs to console") + logger.switchToConsole() + logger.info("The system is powering off") + nimDExit(logger, 0, emerg=false) + discard syscall(REBOOT, 0xfee1dead, 537993216, reboot_codes["poweroff"]) + + +proc halt*(logger: Logger) = + ## Halts the system + logger.debug("Switching logs to console") + logger.switchToConsole() + logger.info("The system is halting") + nimDExit(logger, 0, emerg=false) + discard syscall(REBOOT, 0xfee1dead, 537993216, reboot_codes["halt"]) From d6c46b35430d633e41d659375731940d4d630ad3 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Mon, 27 Dec 2021 11:18:42 +0100 Subject: [PATCH 2/7] Minor changes to mainloop.nim --- src/core/mainloop.nim | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/core/mainloop.nim b/src/core/mainloop.nim index b968141..91482cb 100644 --- a/src/core/mainloop.nim +++ b/src/core/mainloop.nim @@ -29,14 +29,14 @@ proc mainLoop*(logger: Logger) = startServices(logger, workers=1, level=Default) logger.debug(&"Unblocking signals") unblockSignals(logger) - logger.info("System initialization complete, going idle") + logger.info("System initialization complete, idling on control socket") var opType: string try: logger.trace("Calling initControlSocket()") var serverSocket = initControlSocket(logger) serverSocket.listen(5) var clientSocket = newSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) - # logger.switchToFile() + logger.switchToFile() while true: serverSocket.accept(clientSocket) if clientSocket.recv(opType, size=1) == 0: @@ -49,9 +49,6 @@ proc mainLoop*(logger: Logger) = # - 'h' -> halt # - 's' -> Services-related operations (start, stop, get status, etc.) case opType: - of "": - logger.debug(&"Empty read from control socket: did the client disconnect?") - continue of "p": logger.info("Received shutdown request") shutdown(logger) From b68b6f5e747b3481742c6bc9364b9ed8a89e6244 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Mon, 27 Dec 2021 15:08:57 +0100 Subject: [PATCH 3/7] Failed attempts to fix missing stderr from supervised processes --- src/core/mainloop.nim | 4 +- src/core/services.nim | 120 +++++++++++++++++++++--------------------- src/main.nim | 27 ++++------ 3 files changed, 72 insertions(+), 79 deletions(-) diff --git a/src/core/mainloop.nim b/src/core/mainloop.nim index 91482cb..601e6c8 100644 --- a/src/core/mainloop.nim +++ b/src/core/mainloop.nim @@ -23,10 +23,10 @@ import shutdown -proc mainLoop*(logger: Logger) = +proc mainLoop*(logger: Logger, workers: int = 1) = ## NimD's main execution loop logger.info("Processing default runlevel") - startServices(logger, workers=1, level=Default) + startServices(logger, workers=workers, level=Default) logger.debug(&"Unblocking signals") unblockSignals(logger) logger.info("System initialization complete, idling on control socket") diff --git a/src/core/services.nim b/src/core/services.nim index cd3fb07..610e693 100644 --- a/src/core/services.nim +++ b/src/core/services.nim @@ -219,14 +219,13 @@ proc removeService*(service: Service) = break -proc loggerWorker(logger: Logger, service: Service, process: Process) = +proc streamLoggerWorker(logger: Logger, service: Service, stream: Stream) = ## Captures the output of a given process and relays it ## in a formatted manner into our logging system try: logger.debug("Switching logs to file") - logger.switchToFile() + # logger.switchToFile() var line: string = "" - var stream = process.outputStream while stream.readLine(line): logger.info(&"{service.name}: {line}") except: @@ -244,63 +243,62 @@ proc supervisorWorker(logger: Logger, service: Service, process: Process) = logger.error(&"Error, cannot fork: {posix.strerror(posix.errno)}") elif p == 0: logger.trace(&"New child has been spawned") - loggerWorker(logger, service, process) - else: - var pid = process.processID - var status: cint - var returnCode: int - var sig: int - var process: Process - logger.debug("Switching logs to file") - logger.switchToFile() - while true: - logger.trace(&"Calling waitpid() on {pid}") - returnCode = posix.waitPid(cint(pid), status, WUNTRACED) - if WIFEXITED(status): - sig = 0 - elif WIFSIGNALED(status): - sig = WTERMSIG(status) - else: - sig = -1 - logger.trace(&"Call to waitpid() set status to {status} and returned {returnCode}, setting sig to {sig}") - case service.restart: - of Never: - logger.info(&"Service '{service.name}' ({returnCode}) has exited, shutting down controlling process") + streamLoggerWorker(logger, service, process.outputStream) + var pid = process.processID + var status: cint + var returnCode: int + var sig: int + var process: Process + logger.debug("Switching logs to file") + logger.switchToFile() + while true: + logger.trace(&"Calling waitpid() on {pid}") + returnCode = posix.waitPid(cint(pid), status, WUNTRACED) + if WIFEXITED(status): + sig = 0 + elif WIFSIGNALED(status): + sig = WTERMSIG(status) + else: + sig = -1 + logger.trace(&"Call to waitpid() set status to {status} and returned {returnCode}, setting sig to {sig}") + case service.restart: + of Never: + logger.info(&"Service '{service.name}' ({returnCode}) has exited, shutting down controlling process") + break + of Always: + if sig > 0: + logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it") + elif sig == 0: + logger.info(&"Service '{service.name}' has exited gracefully, sleeping {service.restartDelay} seconds before restarting it") + else: + logger.info(&"Service '{service.name}' has exited, sleeping {service.restartDelay} seconds before restarting it") + removeManagedProcess(pid) + sleep(service.restartDelay * 1000) + var split = shlex(service.exec) + if split.error: + logger.error(&"Error while restarting service '{service.name}': invalid exec syntax") break - of Always: - if sig > 0: - logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it") - elif sig == 0: - logger.info(&"Service '{service.name}' has exited gracefully, sleeping {service.restartDelay} seconds before restarting it") - else: - logger.info(&"Service '{service.name}' has exited, sleeping {service.restartDelay} seconds before restarting it") - removeManagedProcess(pid) - sleep(service.restartDelay * 1000) - var split = shlex(service.exec) - if split.error: - logger.error(&"Error while restarting service '{service.name}': invalid exec syntax") - break - var arguments = split.words - let progName = arguments[0] - arguments = arguments[1..^1] - process = startProcess(progName, workingDir=service.workDir, args=arguments) - pid = process.processID() - of OnFailure: - if sig > 0: - logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it") - removeManagedProcess(pid) - sleep(service.restartDelay * 1000) - var split = shlex(service.exec) - if split.error: - logger.error(&"Error while restarting service '{service.name}': invalid exec syntax") - break - var arguments = split.words - let progName = arguments[0] - arguments = arguments[1..^1] - process = startProcess(progName, workingDir=service.workDir, args=arguments) - pid = process.processID() - if process != nil: - process.close() + var arguments = split.words + let progName = arguments[0] + arguments = arguments[1..^1] + process = startProcess(progName, workingDir=service.workDir, args=arguments) + pid = process.processID() + of OnFailure: + if sig > 0: + logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it") + removeManagedProcess(pid) + sleep(service.restartDelay * 1000) + var split = shlex(service.exec) + if split.error: + logger.error(&"Error while restarting service '{service.name}': invalid exec syntax") + break + var arguments = split.words + let progName = arguments[0] + arguments = arguments[1..^1] + process = startProcess(progName, workingDir=service.workDir, args=arguments) + pid = process.processID() + if process != nil: + process.close() proc startService(logger: Logger, service: Service) = @@ -319,7 +317,7 @@ proc startService(logger: Logger, service: Service) = var arguments = split.words let progName = arguments[0] arguments = arguments[1..^1] - process = startProcess(progName, workingDir=service.workDir, args=arguments, options=if service.useParentStreams: {poParentStreams} else: {poUsePath, poDaemon, poStdErrToStdOut}) + process = startProcess(progName, workingDir=service.workDir, args=arguments, options=if service.useParentStreams: {poParentStreams, poStdErrToStdOut} else: {poUsePath, poDaemon, poStdErrToStdOut}) if service.supervised or service.kind != Oneshot: var pid = posix.fork() if pid == -1: @@ -329,7 +327,7 @@ proc startService(logger: Logger, service: Service) = supervisorWorker(logger, service, process) # If the service is unsupervised we just spawn the logger worker (assuming it doesn't use poParentStreams) if not service.useParentStreams: - loggerWorker(logger, service, process) + streamLoggerWorker(logger, service, process.outputStream) except: logger.error(&"Error while starting service '{service.name}': {getCurrentExceptionMsg()}") diff --git a/src/main.nim b/src/main.nim index ce0685d..4c9d980 100644 --- a/src/main.nim +++ b/src/main.nim @@ -44,30 +44,25 @@ proc addStuff = addShutdownHandler(newShutdownHandler(unmountAllDisks)) # Adds test services var echoer = newService(name="echoer", description="prints owo", exec="/bin/echo owoooooooooo", - runlevel=Boot, kind=Oneshot, workDir=getCurrentDir(), - supervised=false, restart=Never, restartDelay=0, + runlevel=Boot, kind=Simple, workDir=getCurrentDir(), + supervised=false, restart=Always, restartDelay=5, depends=(@[]), provides=(@[])) var errorer = newService(name="errorer", description="la mamma di gavd", - exec="/bin/false", supervised=true, restart=OnFailure, - restartDelay=5, runlevel=Boot, workDir="/", kind=Simple, - depends=(@[newDependency(Other, echoer)]), provides=(@[])) - var test = newService(name="broken", description="", exec="/bin/echo owo", - runlevel=Boot, kind=Oneshot, workDir=getCurrentDir(), - supervised=false, restart=Never, restartDelay=0, - depends=(@[newDependency(Other, echoer)]), provides=(@[])) + exec="/bin/false", supervised=true, restart=OnFailure, + restartDelay=5, runlevel=Boot, workDir="/", kind=Simple, + depends=(@[]), provides=(@[])) var exiter = newService(name="exiter", description="la mamma di licenziat", - exec="/bin/true", supervised=true, restart=Always, - restartDelay=5, runlevel=Boot, workDir="/", kind=Simple, - depends=(@[newDependency(Other, errorer)]), provides=(@[])) + exec="/bin/true", supervised=true, restart=Always, + restartDelay=5, runlevel=Boot, workDir="/", kind=Simple, + depends=(@[newDependency(Other, errorer)]), provides=(@[])) var shell = newService(name="login", description="A simple login shell", kind=Simple, - getCurrentDir(), runlevel=Boot, exec="/bin/login -f root", - supervised=true, restart=Always, restartDelay=0, depends=(@[]), provides=(@[]), + getCurrentDir(), runlevel=Default, exec="/bin/login -f root", + supervised=true, restart=Always, restartDelay=5, depends=(@[]), provides=(@[]), useParentStreams=true ) addService(errorer) addService(echoer) addService(exiter) - addService(test) addService(shell) @@ -126,7 +121,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab", logger.info("Processing boot runlevel") startServices(logger, workers=workerCount, level=Boot) logger.debug("Starting main loop") - mainLoop(logger) + mainLoop(logger, workers=workerCount) when isMainModule: From a93c3c6fd09bb19b1548ccf7ceaeb203758e3c6c Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Mon, 27 Dec 2021 18:21:07 +0100 Subject: [PATCH 4/7] Failed attempts to fix missing stderr from supervised processes --- src/core/services.nim | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/services.nim b/src/core/services.nim index 610e693..6810daa 100644 --- a/src/core/services.nim +++ b/src/core/services.nim @@ -224,12 +224,12 @@ proc streamLoggerWorker(logger: Logger, service: Service, stream: Stream) = ## in a formatted manner into our logging system try: logger.debug("Switching logs to file") - # logger.switchToFile() + logger.switchToFile() var line: string = "" while stream.readLine(line): logger.info(&"{service.name}: {line}") except: - logger.error(&"An error occurred in loggerWorker: {getCurrentExceptionMsg()}") + logger.error(&"An error occurred in streamLoggerWorker: {getCurrentExceptionMsg()}") quit(-1) @@ -261,6 +261,7 @@ proc supervisorWorker(logger: Logger, service: Service, process: Process) = else: sig = -1 logger.trace(&"Call to waitpid() set status to {status} and returned {returnCode}, setting sig to {sig}") + process.close() case service.restart: of Never: logger.info(&"Service '{service.name}' ({returnCode}) has exited, shutting down controlling process") @@ -369,7 +370,7 @@ proc startServices*(logger: Logger, level: RunLevel, workers: int = 1) = if service.supervised: addManagedProcess(pid, service) if len(pids) == workers: - logger.debug(&"""Worker queue full, waiting for some worker{(if workers > 1: "s" else: "")} to exit...""") + logger.debug(&"""Worker queue full, waiting for some worker{(if workers > 1: "s" else: "")} to exit""") for i, pid in pids: logger.trace(&"Calling waitpid() on {pid}") var returnCode = waitPid(cint(pid), status, WUNTRACED) @@ -380,4 +381,4 @@ proc startServices*(logger: Logger, level: RunLevel, workers: int = 1) = logger.debug(&"Waiting for completion of service spawning in runlevel {($level).toLowerAscii()}") logger.trace(&"Calling waitpid() on {pid}") var returnCode = waitPid(cint(pid), status, WUNTRACED) - logger.trace(&"Call to waitpid() on {pid} set status to {status} and returned {returnCode}") \ No newline at end of file + logger.trace(&"Call to waitpid() on {pid} set status to {status} and returned {returnCode}") From d41e67f4138f40bff9298f7a0a9dd189a63a6cae Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Tue, 4 Jan 2022 12:00:18 +0100 Subject: [PATCH 5/7] Fixed issues with fcntl and replaced file locking with a duplicate of stderr in O_APPEND mode. Added basic utilities for shutdown/reboot/halt --- src/core/mainloop.nim | 17 ++++---- src/core/shutdown.nim | 2 +- src/programs/halt.nim | 25 ++++++++++++ src/programs/poweroff.nim | 25 ++++++++++++ src/programs/reboot.nim | 25 ++++++++++++ src/util/logging.nim | 86 +++++++++++++-------------------------- 6 files changed, 114 insertions(+), 66 deletions(-) create mode 100644 src/programs/halt.nim create mode 100644 src/programs/poweroff.nim create mode 100644 src/programs/reboot.nim diff --git a/src/core/mainloop.nim b/src/core/mainloop.nim index 601e6c8..fa4b861 100644 --- a/src/core/mainloop.nim +++ b/src/core/mainloop.nim @@ -23,12 +23,13 @@ import shutdown -proc mainLoop*(logger: Logger, workers: int = 1) = +proc mainLoop*(logger: Logger, workers: int = 1, startServices: bool = true) = ## NimD's main execution loop - logger.info("Processing default runlevel") - startServices(logger, workers=workers, level=Default) - logger.debug(&"Unblocking signals") - unblockSignals(logger) + if startServices: + logger.info("Processing default runlevel") + startServices(logger, workers=workers, level=Default) + logger.debug(&"Unblocking signals") + unblockSignals(logger) logger.info("System initialization complete, idling on control socket") var opType: string try: @@ -37,10 +38,12 @@ proc mainLoop*(logger: Logger, workers: int = 1) = serverSocket.listen(5) var clientSocket = newSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) logger.switchToFile() + logger.debug("Entering accept() loop") while true: serverSocket.accept(clientSocket) + logger.debug(&"Received connection on control socket") if clientSocket.recv(opType, size=1) == 0: - logger.debug(&"Client has disconnected, waiting for new connection") + logger.debug(&"Client has disconnected, waiting for new connections") continue logger.debug(&"Received operation type '{opType}' via control socket") # The operation type is a single byte: @@ -68,4 +71,4 @@ proc mainLoop*(logger: Logger, workers: int = 1) = logger.critical(&"A critical error has occurred while running, restarting the mainloop in 30 seconds! Error -> {getCurrentExceptionMsg()}") sleepSeconds(30) # We *absolutely* cannot die - mainLoop(logger) + mainLoop(logger, startServices=false) diff --git a/src/core/shutdown.nim b/src/core/shutdown.nim index e51cf13..fa8e08a 100644 --- a/src/core/shutdown.nim +++ b/src/core/shutdown.nim @@ -31,7 +31,7 @@ type ShutdownHandler* = ref object body*: proc (logger: Logger, code: int) -const reboot_codes = {"poweroff": 0x4321fedc'i64, "restart": 0x01234567'i64, "halt": 0xcdef0123}.toTable() +const reboot_codes = {"poweroff": 0x4321fedc'i64, "reboot": 0x01234567'i64, "halt": 0xcdef0123}.toTable() proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler = diff --git a/src/programs/halt.nim b/src/programs/halt.nim new file mode 100644 index 0000000..2e9c211 --- /dev/null +++ b/src/programs/halt.nim @@ -0,0 +1,25 @@ +# Copyright 2021 Mattia Giambirtone & All Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import net + + +when isMainModule: + var sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) + try: + sock.connectUnix("/var/run/nimd.sock") + except OSError: + echo getCurrentExceptionMsg() + quit(-1) + echo sock.trySend("r") + sock.close() diff --git a/src/programs/poweroff.nim b/src/programs/poweroff.nim new file mode 100644 index 0000000..16f4ec6 --- /dev/null +++ b/src/programs/poweroff.nim @@ -0,0 +1,25 @@ +# Copyright 2021 Mattia Giambirtone & All Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import net + + +when isMainModule: + var sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) + try: + sock.connectUnix("/var/run/nimd.sock") + except OSError: + echo getCurrentExceptionMsg() + quit(-1) + echo sock.trySend("p") + sock.close() diff --git a/src/programs/reboot.nim b/src/programs/reboot.nim new file mode 100644 index 0000000..2e9c211 --- /dev/null +++ b/src/programs/reboot.nim @@ -0,0 +1,25 @@ +# Copyright 2021 Mattia Giambirtone & All Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import net + + +when isMainModule: + var sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) + try: + sock.connectUnix("/var/run/nimd.sock") + except OSError: + echo getCurrentExceptionMsg() + quit(-1) + echo sock.trySend("r") + sock.close() diff --git a/src/util/logging.nim b/src/util/logging.nim index 0e1ef8d..d1d3788 100644 --- a/src/util/logging.nim +++ b/src/util/logging.nim @@ -38,10 +38,21 @@ type level*: LogLevel handlers*: seq[LogHandler] + +proc dup3(a1, a2, a3: cint): cint {.importc.} + + var defaultLevel = LogLevel.Info var logFile = "/var/log/nimd" var logToFileOnly: bool = false +## This mess is needed to make sure stderr writes are mostly atomic. Sort of +## No error handling yet. Deal with it +var customStderrFd = dup(stderr.getFileHandle()) +discard dup3(stderr.getFileHandle(), customStderrFd, O_APPEND) +var customStderr: File +discard open(customStderr, customStderrFd, fmAppend) + proc log(self: Logger, level: LogLevel = defaultLevel, message: string) # Forward declaration @@ -79,132 +90,91 @@ proc log(self: Logger, level: LogLevel = defaultLevel, message: string) = # Do NOT touch the alignment offsets or your console output and logs will look like trash -proc lockFile(logger: Logger, handle: File) = - ## Locks the given file across the whole system for writing using fcntl() - if fcntl(handle.getFileHandle(), F_WRLCK) == -1: - setForegroundColor(fgRed) - stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] Error while locking handle (code {posix.errno}, {posix.strerror(posix.errno)}): output may be mangled""") - setForegroundColor(fgDefault) - - -proc unlockFile(logger: Logger, handle: File) = - ## Unlocks the given file across the whole system for writing using fcntl() - if fcntl(handle.getFileHandle(), F_UNLCK) == -1: - setForegroundColor(fgRed) - stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] Error while unlocking handle (code {posix.errno}, {posix.strerror(posix.errno)}): output may be missing""") - setForegroundColor(fgDefault) - proc logTraceStderr(self: LogHandler, logger: Logger, message: string) = - logger.lockFile(stderr) setForegroundColor(fgMagenta) - stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""") + customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""") setForegroundColor(fgDefault) - logger.unlockFile(stderr) proc logDebugStderr(self: LogHandler, logger: Logger, message: string) = - logger.lockFile(stderr) setForegroundColor(fgCyan) - stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""") + customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""") setForegroundColor(fgDefault) - logger.unlockFile(stderr) proc logInfoStderr(self: LogHandler, logger: Logger, message: string) = - logger.lockFile(stderr) setForegroundColor(fgGreen) - stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""") + customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""") setForegroundColor(fgDefault) - logger.unlockFile(stderr) proc logWarningStderr(self: LogHandler, logger: Logger, message: string) = - logger.lockFile(stderr) setForegroundColor(fgYellow) - stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""") + customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""") setForegroundColor(fgDefault) - logger.unlockFile(stderr) proc logErrorStderr(self: LogHandler, logger: Logger, message: string) = - logger.lockFile(stderr) setForegroundColor(fgRed) - stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""") + customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""") setForegroundColor(fgDefault) - logger.unlockFile(stderr) - + proc logCriticalStderr(self: LogHandler, logger: Logger, message: string) = - logger.lockFile(stderr) setForegroundColor(fgYellow) setBackgroundColor(bgRed) - stderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})]""") + customStderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})]""") setBackgroundColor(bgDefault) - stderr.writeLine(&""" {message}""") + customStderr.writeLine(&""" {message}""") setForegroundColor(fgDefault) - logger.unlockFile(stderr) - + proc logFatalStderr(self: LogHandler, logger: Logger, message: string) = - logger.lockFile(stderr) setForegroundColor(fgBlack) setBackgroundColor(bgRed) - stderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})]""") + customStderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})]""") setForegroundColor(fgRed) setBackgroundColor(bgDefault) - stderr.writeline(&""" {message}""") + customStderr.writeline(&""" {message}""") setForegroundColor(fgDefault) - logger.unlockFile(stderr) proc logTraceFile(self: LogHandler, logger: Logger, message: string) = var self = StreamHandler(self) - logger.lockFile(self.file) self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""") - logger.unlockFile(self.file) - + proc logDebugFile(self: LogHandler, logger: Logger, message: string) = var self = StreamHandler(self) - logger.lockFile(self.file) self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""") - logger.unlockFile(self.file) - + proc logInfoFile(self: LogHandler, logger: Logger, message: string) = var self = StreamHandler(self) - logger.lockFile(self.file) self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""") - logger.unlockFile(self.file) - + proc logWarningFile(self: LogHandler, logger: Logger, message: string) = var self = StreamHandler(self) - logger.lockFile(self.file) self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""") - logger.unlockFile(self.file) proc logErrorFile(self: LogHandler, logger: Logger, message: string) = var self = StreamHandler(self) - logger.lockFile(self.file) self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""") - logger.unlockFile(self.file) + proc logCriticalFile(self: LogHandler, logger: Logger, message: string) = var self = StreamHandler(self) - logger.lockFile(self.file) self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})] {message}""") - logger.unlockFile(self.file) - + proc logFatalFile(self: LogHandler, logger: Logger, message: string) = var self = StreamHandler(self) - logger.lockFile(self.file) self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})] {message}""") - logger.unlockFile(self.file) + proc switchToFile*(self: Logger) = From 76f2df19696f29d3a2c16e6614b9cb7b609a11d2 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Sun, 9 Jan 2022 22:46:25 +0100 Subject: [PATCH 6/7] Fixed config parsing module (sorta) and added example config file --- README.md | 22 ++++++------- config.nims | 10 +++--- nimd.conf | 21 +++++++++++++ src/core/mainloop.nim | 12 +++---- src/core/shutdown.nim | 11 +++++-- src/main.nim | 31 ++++++++++-------- src/{core => util}/config.nim | 59 +++++++++++++++++++++++++---------- src/util/logging.nim | 8 ++++- 8 files changed, 118 insertions(+), 56 deletions(-) create mode 100644 nimd.conf rename src/{core => util}/config.nim (62%) diff --git a/README.md b/README.md index 55c8432..f7d8063 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,6 @@ want to make sure your disks are mounted and that your network has been set up. ``` [Service] - name = ssh # The name of the service description = Secure Shell Server # A short description type = simple # Other option: oneshot (i.e. runs only once, implies supervised=false) @@ -70,7 +69,6 @@ restartDelay = 10 # NimD will wait this many seconds before supervised = true # This is the default. Disable it if you don't need NimD to watch for it [Logging] - stderr = /var/log/sshd # Path of the stderr log for the service stdout = /var/log/sshd # Path of the stdout log for the service stdin = /dev/null # Path of the stdin fd for the service @@ -99,20 +97,22 @@ INI-like structure), but the options are obviously different. An example config ``` [Logging] - level = info # Levels are: trace, debug, info, warning, error, critical, fatal logFile = /var/log/nimd # Path to log file [Filesystem] - -autoMount = true # Automatically parses /etc/fstab and mounts disks -autoUnmount = true # Automatically parses /proc/mounts and unmounts everything on shutdown -fstabPath = /etc/fstab # Path to your system's fstab (defaults to /etc/fstab) -createDirs = /path/to/dir1, /path/to/dir2 # Creates these directories on boot. Empty to disable -createSymlinks = /path/to/symlink:/path/to/dest, ... # Creates these symlinks on boot. Empty to disable +autoMount = true # Automatically parses /etc/fstab and mounts disks +autoUnmount = true # Automatically parses /proc/mounts and unmounts everything on shutdown +fstabPath = /etc/fstab # Path to your system's fstab (defaults to /etc/fstab) +createDirs = "/path/to/dir:perms," # Creates these directories with the given permissions on boot. Empty to disable +createSymlinks = "/path/to/symlink:/path/to/dest," # Creates these symlinks on boot. Empty to disable [Misc] - controlSocket = /var/run/nimd.sock # Path to the Unix domain socket to create for IPC -onDependencyConflict = skip # Other option: warn, error +setHostname = true # Set to true to set the machine's hostname automatically from /etc/hostname +onDependencyConflict = skip # Other option: warn, error +workers = 1 # Number of worker processes to use when spawning services +restartDelay = 10 # Delay (seconds) that nimd will wait before restarting itself after crashes +sigtermDelay = 90 # Delay (seconds) that nimd will wait before terminating child processes with + # SIGKILL after sending a more gentle SIGTERM upon shutdown or exit ``` \ No newline at end of file diff --git a/config.nims b/config.nims index bdc1869..e29ba61 100644 --- a/config.nims +++ b/config.nims @@ -1,12 +1,11 @@ from macros import error -from ospaths import splitFile, `/` +from os import splitFile, `/` # -d:musl when defined(musl): var muslGccPath: string echo " [-d:musl] Building a static binary using musl .." muslGccPath = findExe("musl-gcc") - # echo "debug: " & muslGccPath if muslGccPath == "": error("'musl-gcc' binary was not found in PATH.") switch("gcc.exe", muslGccPath) @@ -15,7 +14,7 @@ when defined(musl): proc binOptimize(binFile: string) = - ## Optimize size of the ``binFile`` binary. + ## Optimize size of the binFile binary. echo "" if findExe("strip") != "": echo "Running 'strip -s' .." @@ -36,11 +35,10 @@ task musl, "Builds an optimized static binary using musl": "\n Usage Example: nim musl FILE.nim.") let - nimFile = paramStr(numParams) ## The nim file name *must* be the last. + nimFile = paramStr(numParams) ## The nim file name *must* be the last (dirName, baseName, _) = splitFile(nimFile) binFile = dirName / baseName # Save the binary in the same dir as the nim file - nimArgs = "c -d:musl -d:release --opt:size " & nimFile - # echo "[debug] nimFile = " & nimFile & ", binFile = " & binFile + nimArgs = "c -d:musl -d:release --opt:size --gc:orc" & nimFile # Build binary echo "\nRunning 'nim " & nimArgs & "' .." diff --git a/nimd.conf b/nimd.conf new file mode 100644 index 0000000..3627f6c --- /dev/null +++ b/nimd.conf @@ -0,0 +1,21 @@ +[Misc] +controlSocket = /var/run/nimd.sock +setHostname = true +onDependencyConflict = skip +workers = 1 +restartDelay = 10 +sigtermDelay = 90 + +[Logging] +level = info +logFile = /var/log/nimd + +[Filesystem] +autoMount = true +autoUnmount = true +fstabPath = /etc/fstab +createDirs = "test:764,/dev/disk:123" +createSymlinks = """/dev/fd:/proc/self/fd,/dev/fd/0:/proc/self/fd/0,/dev/fd/1:/proc/self/fd/1,/dev/fd/2:/proc/self/fd/2, + /dev/std/in:/proc/self/fd/0,/dev/std/out:/proc/self/fd/1,/dev/std/err:/proc/self/fd/2, + /dev/std/in:/,/dev/std/out:/does/not/exist,/dev/std/err:/proc/self/fd/2 + """ diff --git a/src/core/mainloop.nim b/src/core/mainloop.nim index fa4b861..381961b 100644 --- a/src/core/mainloop.nim +++ b/src/core/mainloop.nim @@ -16,18 +16,18 @@ import os import net -import ../util/[logging, misc] +import ../util/[logging, misc, config] import services import control import shutdown -proc mainLoop*(logger: Logger, workers: int = 1, startServices: bool = true) = +proc mainLoop*(logger: Logger, config: NimDConfig, startServices: bool = true) = ## NimD's main execution loop if startServices: logger.info("Processing default runlevel") - startServices(logger, workers=workers, level=Default) + startServices(logger, workers=config.workers, level=Default) logger.debug(&"Unblocking signals") unblockSignals(logger) logger.info("System initialization complete, idling on control socket") @@ -68,7 +68,7 @@ proc mainLoop*(logger: Logger, workers: int = 1, startServices: bool = true) = discard clientSocket.close() except: - logger.critical(&"A critical error has occurred while running, restarting the mainloop in 30 seconds! Error -> {getCurrentExceptionMsg()}") - sleepSeconds(30) + logger.critical(&"A critical error has occurred while running, restarting the mainloop in {config.restartDelay} seconds! Error -> {getCurrentExceptionMsg()}") + sleepSeconds(config.restartDelay) # We *absolutely* cannot die - mainLoop(logger, startServices=false) + mainLoop(logger, config, startServices=false) diff --git a/src/core/shutdown.nim b/src/core/shutdown.nim index fa8e08a..4f5134e 100644 --- a/src/core/shutdown.nim +++ b/src/core/shutdown.nim @@ -39,7 +39,12 @@ proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandle var shutdownHandlers: seq[ShutdownHandler] = @[] -var sigTermDelay: float = 10.0 +var sigTermDelay: float = 90 + + +proc setSigTermDelay*(delay: int = 90) = + # Sets the sigtermDelay variable + sigTermDelay = float(delay) proc addShutdownHandler*(handler: ShutdownHandler) = @@ -90,7 +95,7 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) = # We're in emergency mode: do not crash the kernel, spawn a shell and exit logger.fatal("NimD has entered emergency mode and cannot continue. You will be now (hopefully) dropped in a root shell: you're on your own. May the force be with you") logger.info("Terminating child processes with SIGKILL") - discard execCmd("/bin/sh") # TODO: Is this fine? maybe use execProcess + discard execCmd(os.getEnv("SHELL", "/bin/sh")) # TODO: Is this fine? maybe use execProcess discard posix.kill(-1, SIGKILL) quit(-1) logger.warning("The system is shutting down") @@ -113,7 +118,7 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) = if anyUserlandProcessLeft(): logger.info("Terminating child processes with SIGKILL") discard posix.kill(-1, SIGKILL) - logger.warning("Shutdown procedure complete, sending final termination signal") + logger.warning("Shutdown procedure complete, NimD is exiting") proc reboot*(logger: Logger) = diff --git a/src/main.nim b/src/main.nim index 4c9d980..d38e0d1 100644 --- a/src/main.nim +++ b/src/main.nim @@ -17,7 +17,7 @@ import posix import os # NimD's own stuff -import util/[logging, constants, misc] +import util/[logging, constants, misc, config] import core/[mainloop, fs, shutdown, services] @@ -40,8 +40,7 @@ proc addStuff = addSymlink(newSymlink(dest="/dev/std/in", source="/proc/self/fd/0")) # Should say link already exists addDirectory(newDirectory("test", 764)) # Should create a directory addDirectory(newDirectory("/dev/disk", 123)) # Should say directory already exists - # Shutdown handler to unmount disks - addShutdownHandler(newShutdownHandler(unmountAllDisks)) + # Adds test services var echoer = newService(name="echoer", description="prints owo", exec="/bin/echo owoooooooooo", runlevel=Boot, kind=Simple, workDir=getCurrentDir(), @@ -67,10 +66,11 @@ proc addStuff = -proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab", setHostname: bool = true, workerCount: int = 1) = +proc main(logger: Logger, config: NimDConfig) = ## NimD's entry point and setup ## function - setStdIoUnbuffered() # Colors and output synchronization don't work otherwise + logger.debug(&"Setting log file to '{config.logFile}'") + setLogFile(file=config.logFile) logger.debug("Starting NimD: A minimal, self-contained, dependency-based Linux init system written in Nim") logger.info(&"NimD version {NimdVersion.major}.{NimdVersion.minor}.{NimdVersion.patch} is starting up!") logger.trace("Calling getCurrentProcessId()") @@ -86,7 +86,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab", nimDExit(logger, EPERM, emerg=false) # EPERM - Operation not permitted logger.trace("Setting up signal handlers") onSignal(SIGABRT, SIGALRM, SIGHUP, SIGILL, SIGKILL, SIGQUIT, SIGSTOP, SIGSEGV, SIGTSTP, - SIGTRAP, SIGPIPE, SIGUSR1, SIGUSR2, 6, SIGFPE, SIGBUS, SIGURG, SIGTERM, SIGINT): # 6 is SIGIOT + SIGTRAP, SIGPIPE, SIGUSR1, SIGUSR2, 6, SIGFPE, SIGBUS, SIGURG, SIGTERM, SIGINT): # 6 is SIGIOT # Can't capture local variables because this implicitly generates # a noconv procedure, so we use getDefaultLogger() instead getDefaultLogger().warning(&"Ignoring signal {sig} ({strsignal(sig)})") # Nim injects the variable "sig" into the scope. Gotta love those macros @@ -94,11 +94,15 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab", # One of the key features of an init system is reaping child # processes! reapProcess(getDefaultLogger()) + if config.autoUnmount: + logger.debug("Setting up shutdown handler for automatic disk unmounting") + # Shutdown handler to unmount disks + addShutdownHandler(newShutdownHandler(unmountAllDisks)) addStuff() try: - if mountDisks: + if config.autoMount: logger.info("Mounting filesystem") - mountDisks(logger, fstab) + mountDisks(logger, config.fstab) else: logger.info("Skipping disk mounting, assuming this has already been done") logger.info("Creating symlinks") @@ -111,7 +115,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab", except: logger.fatal(&"A fatal error has occurred while preparing filesystem, booting cannot continue. Error -> {getCurrentExceptionMsg()}") nimDExit(logger, 131, emerg=false) - if setHostname: + if config.setHostname: logger.info("Setting hostname") logger.debug(&"Hostname was set to '{misc.setHostname(logger)}'") else: @@ -119,9 +123,9 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab", logger.debug("Entering critical fork() section: blocking signals") blockSignals(logger) # They are later unblocked in mainLoop logger.info("Processing boot runlevel") - startServices(logger, workers=workerCount, level=Boot) + startServices(logger, workers=config.workers, level=Boot) logger.debug("Starting main loop") - mainLoop(logger, workers=workerCount) + mainLoop(logger, config) when isMainModule: @@ -167,9 +171,10 @@ when isMainModule: else: echo "Usage: nimd [options]" quit(EINVAL) # EINVAL - Invalid argument - logger.debug("Calling NimD entry point") + + setStdIoUnbuffered() # Colors don't work otherwise! try: - main(logger) + main(logger, parseConfig(logger, "/etc/nimd/nimd.conf")) except: logger.fatal(&"A fatal unrecoverable error has occurred during startup and NimD cannot continue: {getCurrentExceptionMsg()}") nimDExit(logger, 131) # ENOTRECOVERABLE - State not recoverable diff --git a/src/core/config.nim b/src/util/config.nim similarity index 62% rename from src/core/config.nim rename to src/util/config.nim index fc3bf72..7561fbc 100644 --- a/src/core/config.nim +++ b/src/util/config.nim @@ -18,37 +18,42 @@ import strutils import strformat -import ../util/logging -import services -import fs +import logging +import ../core/fs type NimDConfig* = ref object ## Configuration object logLevel*: LogLevel - logDir*: Directory - services*: tuple[runlevel: RunLevel, services: seq[Service]] + logFile*: string + workers*: int directories*: seq[Directory] symlinks*: seq[Symlink] filesystems*: seq[Filesystem] restartDelay*: int + sigtermDelay*: int autoMount*: bool autoUnmount*: bool fstab*: string sock*: string + onDependencyConflict*: string + setHostname*: bool -const defaultSections = ["Misc", "Logging", "Filesystem"] -const defaultConfig = { +const defaultSections* = ["Misc", "Logging", "Filesystem"] +const defaultConfig* = { "Logging": { - "stderr": "/var/log/nimd", - "stdout": "/var/log/nimd", + "logFile": "/var/log/nimd", "level": "info" }.toTable(), "Misc": { "controlSocket": "/var/run/nimd.sock", - "onDependencyConflict": "skip" + "onDependencyConflict": "skip", + "setHostname": "true", + "workers": "1", + "sigtermDelay": "90", + "restartDelay": "30" }.toTable(), "Filesystem": { "autoMount": "true", @@ -59,9 +64,16 @@ const defaultConfig = { "virtualDisks": "" }.toTable() }.toTable() -const levels = {"trace": Trace, "debug": Debug, "info": Info, - "warning": Warning, "error": Error, "critical": Critical, - "fatal": Fatal}.toTable() + +const levels = { + "trace": Trace, + "debug": Debug, + "info": Info, + "warning": Warning, + "error": Error, + "critical": Critical, + "fatal": Fatal + }.toTable() proc parseConfig*(logger: Logger, file: string = "/etc/nimd/nimd.conf"): NimDConfig = @@ -89,16 +101,31 @@ proc parseConfig*(logger: Logger, file: string = "/etc/nimd/nimd.conf"): NimDCon elif section notin defaultSections: logger.warning(&"Unknown section '{section}' found in config file, skipping it") else: + if not data.hasKey(section): + data[section] = newTable[string, string]() for key in defaultConfig[section].keys(): data[section][key] = cfgObject.getSectionValue(section, key, defaultConfig[section][key]) for dirInfo in data["Filesystem"]["createDirs"].split(","): temp = dirInfo.split(":") - directories.add(newDirectory(temp[0], uint64(parseInt(temp[1])))) + directories.add(newDirectory(temp[0].strip(), uint64(parseInt(temp[1].strip())))) for symInfo in data["Filesystem"]["createSymlinks"].split(","): temp = symInfo.split(":") symlinks.add(newSymlink(temp[0], temp[1])) if data["Logging"]["level"] notin levels: - logger.warning(&"""Unknown logging level '{data["Logging"]["level"]}', defaulting to {defaultConfig["Logging"]["level"]}""") + logger.warning(&"""Unknown logging level '{data["Logging"]["level"]}', defaulting to '{defaultConfig["Logging"]["level"]}'""") data["Logging"]["level"] = defaultConfig["Logging"]["level"] result = NimDConfig(logLevel: levels[data["Logging"]["level"]], - filesystems: filesystems) \ No newline at end of file + logFile: data["Logging"]["logFile"], + filesystems: filesystems, + symlinks: symlinks, + directories: directories, + autoMount: parseBool(data["Filesystem"]["autoMount"].toLowerAscii()), + autoUnmount: parseBool(data["Filesystem"]["autoUnmount"].toLowerAscii()), + fstab: data["Filesystem"]["fstabPath"], + sock: data["Misc"]["controlSocket"], + onDependencyConflict: data["Misc"]["onDependencyConflict"].toLowerAscii(), + restartDelay: parseInt(data["Misc"]["restartDelay"]), + sigtermDelay: parseInt(data["Misc"]["sigtermDelay"]), + workers: parseInt(data["Misc"]["workers"]), + setHostname: parseBool(data["Misc"]["setHostname"]) + ) diff --git a/src/util/logging.nim b/src/util/logging.nim index d1d3788..a53667b 100644 --- a/src/util/logging.nim +++ b/src/util/logging.nim @@ -46,7 +46,13 @@ var defaultLevel = LogLevel.Info var logFile = "/var/log/nimd" var logToFileOnly: bool = false -## This mess is needed to make sure stderr writes are mostly atomic. Sort of + +proc setLogFile*(file: string) = + # Sets the log file + logFile = file + + +## This mess is needed to make sure stderr writes are mostly atomic. Sort of. ## No error handling yet. Deal with it var customStderrFd = dup(stderr.getFileHandle()) discard dup3(stderr.getFileHandle(), customStderrFd, O_APPEND) From c25867245cc3ec8de046d88b12cd70183f284a06 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Sun, 9 Jan 2022 23:00:17 +0100 Subject: [PATCH 7/7] NimD now calls sync() before shutting down --- src/core/shutdown.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/shutdown.nim b/src/core/shutdown.nim index 4f5134e..b74f24d 100644 --- a/src/core/shutdown.nim +++ b/src/core/shutdown.nim @@ -91,6 +91,8 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) = ## as cleanly as possible. When emerg equals true, it will ## try to spawn a root shell and exit logger.switchToConsole() + logger.info("Syncing file systems") + logger.debug(&"Calling sync() syscall has returned {syscall(SYNC)}") if emerg: # We're in emergency mode: do not crash the kernel, spawn a shell and exit logger.fatal("NimD has entered emergency mode and cannot continue. You will be now (hopefully) dropped in a root shell: you're on your own. May the force be with you")