Solved merge conflict

This commit is contained in:
Nocturn9x 2022-01-14 08:31:56 +01:00
commit 93b73de32a
13 changed files with 385 additions and 191 deletions

View File

@ -58,7 +58,6 @@ want to make sure your disks are mounted and that your network has been set up.
``` ```
[Service] [Service]
name = ssh # The name of the service name = ssh # The name of the service
description = Secure Shell Server # A short description description = Secure Shell Server # A short description
type = simple # Other option: oneshot (i.e. runs only once, implies supervised=false) type = simple # Other option: oneshot (i.e. runs only once, implies supervised=false)
@ -71,7 +70,6 @@ supervised = true # This is the default. Disable it if you d
workDir = /usr/bin # The service's working directory workDir = /usr/bin # The service's working directory
[Logging] [Logging]
stderr = /var/log/sshd # Path of the stderr log for the service stderr = /var/log/sshd # Path of the stderr log for the service
stdout = /var/log/sshd # Path of the stdout 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 stdin = /dev/null # Path of the stdin fd for the service
@ -100,20 +98,22 @@ INI-like structure), but the options are obviously different. An example config
``` ```
[Logging] [Logging]
level = info # Levels are: trace, debug, info, warning, error, critical, fatal level = info # Levels are: trace, debug, info, warning, error, critical, fatal
logFile = /var/log/nimd # Path to log file logFile = /var/log/nimd # Path to log file
[Filesystem] [Filesystem]
autoMount = true # Automatically parses /etc/fstab and mounts disks
autoMount = true # Automatically parses /etc/fstab and mounts disks autoUnmount = true # Automatically parses /proc/mounts and unmounts everything on shutdown
autoUnmount = true # Automatically parses /proc/mounts and unmounts everything on shutdown fstabPath = /etc/fstab # Path to your system's fstab (defaults to /etc/fstab)
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
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
createSymlinks = /path/to/symlink:/path/to/dest, ... # Creates these symlinks on boot. Empty to disable
[Misc] [Misc]
controlSocket = /var/run/nimd.sock # Path to the Unix domain socket to create for IPC 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
``` ```

View File

@ -1,12 +1,11 @@
from macros import error from macros import error
from ospaths import splitFile, `/` from os import splitFile, `/`
# -d:musl # -d:musl
when defined(musl): when defined(musl):
var muslGccPath: string var muslGccPath: string
echo " [-d:musl] Building a static binary using musl .." echo " [-d:musl] Building a static binary using musl .."
muslGccPath = findExe("musl-gcc") muslGccPath = findExe("musl-gcc")
# echo "debug: " & muslGccPath
if muslGccPath == "": if muslGccPath == "":
error("'musl-gcc' binary was not found in PATH.") error("'musl-gcc' binary was not found in PATH.")
switch("gcc.exe", muslGccPath) switch("gcc.exe", muslGccPath)
@ -15,7 +14,7 @@ when defined(musl):
proc binOptimize(binFile: string) = proc binOptimize(binFile: string) =
## Optimize size of the ``binFile`` binary. ## Optimize size of the binFile binary.
echo "" echo ""
if findExe("strip") != "": if findExe("strip") != "":
echo "Running 'strip -s' .." echo "Running 'strip -s' .."
@ -36,11 +35,10 @@ task musl, "Builds an optimized static binary using musl":
"\n Usage Example: nim musl FILE.nim.") "\n Usage Example: nim musl FILE.nim.")
let 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) (dirName, baseName, _) = splitFile(nimFile)
binFile = dirName / baseName # Save the binary in the same dir as the nim file binFile = dirName / baseName # Save the binary in the same dir as the nim file
nimArgs = "c -d:musl -d:release --opt:size " & nimFile nimArgs = "c -d:musl -d:release --opt:size --gc:orc" & nimFile
# echo "[debug] nimFile = " & nimFile & ", binFile = " & binFile
# Build binary # Build binary
echo "\nRunning 'nim " & nimArgs & "' .." echo "\nRunning 'nim " & nimArgs & "' .."

21
nimd.conf Normal file
View File

@ -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
"""

View File

@ -11,3 +11,26 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # 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()))

View File

@ -13,26 +13,62 @@
# limitations under the License. # limitations under the License.
import strformat import strformat
import os import os
import net
import ../util/[logging, misc] import ../util/[logging, misc, config]
import services import services
import control
import shutdown
proc mainLoop*(logger: Logger) = proc mainLoop*(logger: Logger, config: NimDConfig, startServices: bool = true) =
## NimD's main execution loop ## NimD's main execution loop
logger.info("Processing default runlevel") if startServices:
startServices(logger, workers=1, level=Default) logger.info("Processing default runlevel")
logger.debug(&"Unblocking signals") startServices(logger, workers=config.workers, level=Default)
unblockSignals(logger) logger.debug(&"Unblocking signals")
logger.info("System initialization complete, going idle") unblockSignals(logger)
logger.switchToFile() logger.info("System initialization complete, idling on control socket")
var opType: string
try: try:
logger.trace("Calling initControlSocket()")
var serverSocket = initControlSocket(logger)
serverSocket.listen(5)
var clientSocket = newSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
logger.switchToFile()
logger.debug("Entering accept() loop")
while true: while true:
sleepSeconds(30) 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 connections")
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 "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: except:
logger.critical(&"A critical error has occurred while running, restarting the mainloop in 30 seconds! Error -> {getCurrentExceptionMsg()}") logger.critical(&"A critical error has occurred while running, restarting the mainloop in {config.restartDelay} seconds! Error -> {getCurrentExceptionMsg()}")
sleepSeconds(30) sleepSeconds(config.restartDelay)
# We *absolutely* cannot die # We *absolutely* cannot die
mainLoop(logger) mainLoop(logger, config, startServices=false)

View File

@ -223,18 +223,17 @@ proc removeService*(service: Service) =
break 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 ## Captures the output of a given process and relays it
## in a formatted manner into our logging system ## in a formatted manner into our logging system
try: try:
logger.debug("Switching logs to file") logger.debug("Switching logs to file")
logger.switchToFile() logger.switchToFile()
var line: string = "" var line: string = ""
var stream = process.outputStream
while stream.readLine(line): while stream.readLine(line):
logger.info(&"{service.name}: {line}") logger.info(&"{service.name}: {line}")
except: except:
logger.error(&"An error occurred in loggerWorker: {getCurrentExceptionMsg()}") logger.error(&"An error occurred in streamLoggerWorker: {getCurrentExceptionMsg()}")
quit(-1) quit(-1)
@ -248,63 +247,63 @@ proc supervisorWorker(logger: Logger, service: Service, process: Process) =
logger.error(&"Error, cannot fork: {posix.strerror(posix.errno)}") logger.error(&"Error, cannot fork: {posix.strerror(posix.errno)}")
elif p == 0: elif p == 0:
logger.trace(&"New child has been spawned") logger.trace(&"New child has been spawned")
loggerWorker(logger, service, process) streamLoggerWorker(logger, service, process.outputStream)
else: var pid = process.processID
var pid = process.processID var status: cint
var status: cint var returnCode: int
var returnCode: int var sig: int
var sig: int var process: Process
var process: Process logger.debug("Switching logs to file")
logger.debug("Switching logs to file") logger.switchToFile()
logger.switchToFile() while true:
while true: logger.trace(&"Calling waitpid() on {pid}")
logger.trace(&"Calling waitpid() on {pid}") returnCode = posix.waitPid(cint(pid), status, WUNTRACED)
returnCode = posix.waitPid(cint(pid), status, WUNTRACED) if WIFEXITED(status):
if WIFEXITED(status): sig = 0
sig = 0 elif WIFSIGNALED(status):
elif WIFSIGNALED(status): sig = WTERMSIG(status)
sig = WTERMSIG(status) else:
else: sig = -1
sig = -1 logger.trace(&"Call to waitpid() set status to {status} and returned {returnCode}, setting sig to {sig}")
logger.trace(&"Call to waitpid() set status to {status} and returned {returnCode}, setting sig to {sig}") process.close()
case service.restart: case service.restart:
of Never: of Never:
logger.info(&"Service '{service.name}' ({returnCode}) has exited, shutting down controlling process") 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 break
of Always: var arguments = split.words
if sig > 0: let progName = arguments[0]
logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it") arguments = arguments[1..^1]
elif sig == 0: process = startProcess(progName, workingDir=service.workDir, args=arguments)
logger.info(&"Service '{service.name}' has exited gracefully, sleeping {service.restartDelay} seconds before restarting it") pid = process.processID()
else: of OnFailure:
logger.info(&"Service '{service.name}' has exited, sleeping {service.restartDelay} seconds before restarting it") if sig > 0:
removeManagedProcess(pid) logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it")
sleep(service.restartDelay * 1000) removeManagedProcess(pid)
var split = shlex(service.exec) sleep(service.restartDelay * 1000)
if split.error: var split = shlex(service.exec)
logger.error(&"Error while restarting service '{service.name}': invalid exec syntax") if split.error:
break logger.error(&"Error while restarting service '{service.name}': invalid exec syntax")
var arguments = split.words break
let progName = arguments[0] var arguments = split.words
arguments = arguments[1..^1] let progName = arguments[0]
process = startProcess(progName, workingDir=service.workDir, args=arguments) arguments = arguments[1..^1]
pid = process.processID() process = startProcess(progName, workingDir=service.workDir, args=arguments)
of OnFailure: pid = process.processID()
if sig > 0: if process != nil:
logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it") process.close()
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) = proc startService(logger: Logger, service: Service) =
@ -323,7 +322,7 @@ proc startService(logger: Logger, service: Service) =
var arguments = split.words var arguments = split.words
let progName = arguments[0] let progName = arguments[0]
arguments = arguments[1..^1] 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: if service.supervised or service.kind != Oneshot:
var pid = posix.fork() var pid = posix.fork()
if pid == -1: if pid == -1:
@ -331,8 +330,9 @@ proc startService(logger: Logger, service: Service) =
elif pid == 0: elif pid == 0:
logger.trace(&"New child has been spawned") logger.trace(&"New child has been spawned")
supervisorWorker(logger, service, process) supervisorWorker(logger, service, process)
# If the service is unsupervised we just spawn the logger worker # If the service is unsupervised we just spawn the logger worker (assuming it doesn't use poParentStreams)
loggerWorker(logger, service, process) if not service.useParentStreams:
streamLoggerWorker(logger, service, process.outputStream)
except: except:
logger.error(&"Error while starting service '{service.name}': {getCurrentExceptionMsg()}") logger.error(&"Error while starting service '{service.name}': {getCurrentExceptionMsg()}")
@ -374,7 +374,7 @@ proc startServices*(logger: Logger, level: RunLevel, workers: int = 1) =
if service.supervised: if service.supervised:
addManagedProcess(pid, service) addManagedProcess(pid, service)
if len(pids) == workers: 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: for i, pid in pids:
logger.trace(&"Calling waitpid() on {pid}") logger.trace(&"Calling waitpid() on {pid}")
var returnCode = waitPid(cint(pid), status, WUNTRACED) var returnCode = waitPid(cint(pid), status, WUNTRACED)
@ -385,4 +385,4 @@ proc startServices*(logger: Logger, level: RunLevel, workers: int = 1) =
logger.debug(&"Waiting for completion of service spawning in runlevel {($level).toLowerAscii()}") logger.debug(&"Waiting for completion of service spawning in runlevel {($level).toLowerAscii()}")
logger.trace(&"Calling waitpid() on {pid}") logger.trace(&"Calling waitpid() on {pid}")
var returnCode = waitPid(cint(pid), status, WUNTRACED) var returnCode = waitPid(cint(pid), status, WUNTRACED)
logger.trace(&"Call to waitpid() on {pid} set status to {status} and returned {returnCode}") logger.trace(&"Call to waitpid() on {pid} set status to {status} and returned {returnCode}")

View File

@ -18,6 +18,8 @@ import glob
import strutils import strutils
import strformat import strformat
import times import times
import tables
import syscall
import ../util/logging import ../util/logging
@ -29,12 +31,20 @@ type ShutdownHandler* = ref object
body*: proc (logger: Logger, code: int) body*: proc (logger: Logger, code: int)
const reboot_codes = {"poweroff": 0x4321fedc'i64, "reboot": 0x01234567'i64, "halt": 0xcdef0123}.toTable()
proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler = proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler =
result = ShutdownHandler(body: body) result = ShutdownHandler(body: body)
var shutdownHandlers: seq[ShutdownHandler] = @[] 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) = proc addShutdownHandler*(handler: ShutdownHandler) =
@ -80,16 +90,19 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
## NimD's exit point. This function tries to shut down ## NimD's exit point. This function tries to shut down
## as cleanly as possible. When emerg equals true, it will ## as cleanly as possible. When emerg equals true, it will
## try to spawn a root shell and exit ## 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: if emerg:
# We're in emergency mode: do not crash the kernel, spawn a shell and exit # 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.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") 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) discard posix.kill(-1, SIGKILL)
quit(-1) quit(-1)
logger.warning("The system is shutting down") logger.warning("The system is shutting down")
logger.info("Processing shutdown runlevel") logger.info("Processing shutdown runlevel")
startServices(logger, Shutdown) startServices(logger, RunLevel.Shutdown)
logger.info("Running shutdown handlers") logger.info("Running shutdown handlers")
try: try:
for handler in shutdownHandlers: for handler in shutdownHandlers:
@ -107,5 +120,31 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
if anyUserlandProcessLeft(): if anyUserlandProcessLeft():
logger.info("Terminating child processes with SIGKILL") logger.info("Terminating child processes with SIGKILL")
discard posix.kill(-1, SIGKILL) discard posix.kill(-1, SIGKILL)
logger.warning("Shutdown procedure complete, sending final termination signal") logger.warning("Shutdown procedure complete, NimD is exiting")
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"])

View File

@ -17,7 +17,7 @@ import posix
import os import os
# NimD's own stuff # NimD's own stuff
import util/[logging, constants, misc] import util/[logging, constants, misc, config]
import core/[mainloop, fs, shutdown, services] 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 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("test", 764)) # Should create a directory
addDirectory(newDirectory("/dev/disk", 123)) # Should say directory already exists addDirectory(newDirectory("/dev/disk", 123)) # Should say directory already exists
# Shutdown handler to unmount disks
addShutdownHandler(newShutdownHandler(unmountAllDisks))
# Adds test services # Adds test services
var echoer = newService(name="echoer", description="prints owo", exec="/bin/echo owoooooooooo", var echoer = newService(name="echoer", description="prints owo", exec="/bin/echo owoooooooooo",
runlevel=Boot, kind=Oneshot, workDir=getCurrentDir(), runlevel=Boot, kind=Oneshot, workDir=getCurrentDir(),
@ -52,11 +51,6 @@ proc addStuff =
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple, restartDelay=5, runlevel=Boot, workDir="/", kind=Simple,
depends=(@[newDependency(Other, echoer)]), provides=(@[]), depends=(@[newDependency(Other, echoer)]), provides=(@[]),
stdin="/dev/null", stderr="", stdout="") stdin="/dev/null", stderr="", stdout="")
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=(@[]),
stdin="/dev/null", stderr="", stdout="")
var exiter = newService(name="exiter", description="la mamma di licenziat", var exiter = newService(name="exiter", description="la mamma di licenziat",
exec="/bin/true", supervised=true, restart=Always, exec="/bin/true", supervised=true, restart=Always,
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple, restartDelay=5, runlevel=Boot, workDir="/", kind=Simple,
@ -70,15 +64,15 @@ proc addStuff =
addService(errorer) addService(errorer)
addService(echoer) addService(echoer)
addService(exiter) addService(exiter)
addService(test)
addService(shell) addService(shell)
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 ## NimD's entry point and setup
## function ## 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.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.info(&"NimD version {NimdVersion.major}.{NimdVersion.minor}.{NimdVersion.patch} is starting up!")
logger.trace("Calling getCurrentProcessId()") logger.trace("Calling getCurrentProcessId()")
@ -94,7 +88,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
nimDExit(logger, EPERM, emerg=false) # EPERM - Operation not permitted nimDExit(logger, EPERM, emerg=false) # EPERM - Operation not permitted
logger.trace("Setting up signal handlers") logger.trace("Setting up signal handlers")
onSignal(SIGABRT, SIGALRM, SIGHUP, SIGILL, SIGKILL, SIGQUIT, SIGSTOP, SIGSEGV, SIGTSTP, 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 # Can't capture local variables because this implicitly generates
# a noconv procedure, so we use getDefaultLogger() instead # 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 getDefaultLogger().warning(&"Ignoring signal {sig} ({strsignal(sig)})") # Nim injects the variable "sig" into the scope. Gotta love those macros
@ -102,11 +96,15 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
# One of the key features of an init system is reaping child # One of the key features of an init system is reaping child
# processes! # processes!
reapProcess(getDefaultLogger()) reapProcess(getDefaultLogger())
if config.autoUnmount:
logger.debug("Setting up shutdown handler for automatic disk unmounting")
# Shutdown handler to unmount disks
addShutdownHandler(newShutdownHandler(unmountAllDisks))
addStuff() addStuff()
try: try:
if mountDisks: if config.autoMount:
logger.info("Mounting filesystem") logger.info("Mounting filesystem")
mountDisks(logger, fstab) mountDisks(logger, config.fstab)
else: else:
logger.info("Skipping disk mounting, assuming this has already been done") logger.info("Skipping disk mounting, assuming this has already been done")
logger.info("Creating symlinks") logger.info("Creating symlinks")
@ -119,7 +117,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
except: except:
logger.fatal(&"A fatal error has occurred while preparing filesystem, booting cannot continue. Error -> {getCurrentExceptionMsg()}") logger.fatal(&"A fatal error has occurred while preparing filesystem, booting cannot continue. Error -> {getCurrentExceptionMsg()}")
nimDExit(logger, 131, emerg=false) nimDExit(logger, 131, emerg=false)
if setHostname: if config.setHostname:
logger.info("Setting hostname") logger.info("Setting hostname")
logger.debug(&"Hostname was set to '{misc.setHostname(logger)}'") logger.debug(&"Hostname was set to '{misc.setHostname(logger)}'")
else: else:
@ -127,9 +125,9 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
logger.debug("Entering critical fork() section: blocking signals") logger.debug("Entering critical fork() section: blocking signals")
blockSignals(logger) # They are later unblocked in mainLoop blockSignals(logger) # They are later unblocked in mainLoop
logger.info("Processing boot runlevel") logger.info("Processing boot runlevel")
startServices(logger, workers=workerCount, level=Boot) startServices(logger, workers=config.workers, level=Boot)
logger.debug("Starting main loop") logger.debug("Starting main loop")
mainLoop(logger) mainLoop(logger, config)
when isMainModule: when isMainModule:
@ -175,9 +173,10 @@ when isMainModule:
else: else:
echo "Usage: nimd [options]" echo "Usage: nimd [options]"
quit(EINVAL) # EINVAL - Invalid argument quit(EINVAL) # EINVAL - Invalid argument
logger.debug("Calling NimD entry point")
setStdIoUnbuffered() # Colors don't work otherwise!
try: try:
main(logger) main(logger, parseConfig(logger, "/etc/nimd/nimd.conf"))
except: except:
logger.fatal(&"A fatal unrecoverable error has occurred during startup and NimD cannot continue: {getCurrentExceptionMsg()}") logger.fatal(&"A fatal unrecoverable error has occurred during startup and NimD cannot continue: {getCurrentExceptionMsg()}")
nimDExit(logger, 131) # ENOTRECOVERABLE - State not recoverable nimDExit(logger, 131) # ENOTRECOVERABLE - State not recoverable

25
src/programs/halt.nim Normal file
View File

@ -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()

25
src/programs/poweroff.nim Normal file
View File

@ -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()

25
src/programs/reboot.nim Normal file
View File

@ -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()

View File

@ -18,37 +18,42 @@ import strutils
import strformat import strformat
import ../util/logging import logging
import services import ../core/fs
import fs
type type
NimDConfig* = ref object NimDConfig* = ref object
## Configuration object ## Configuration object
logLevel*: LogLevel logLevel*: LogLevel
logDir*: Directory logFile*: string
services*: tuple[runlevel: RunLevel, services: seq[Service]] workers*: int
directories*: seq[Directory] directories*: seq[Directory]
symlinks*: seq[Symlink] symlinks*: seq[Symlink]
filesystems*: seq[Filesystem] filesystems*: seq[Filesystem]
restartDelay*: int restartDelay*: int
sigtermDelay*: int
autoMount*: bool autoMount*: bool
autoUnmount*: bool autoUnmount*: bool
fstab*: string fstab*: string
sock*: string sock*: string
onDependencyConflict*: string
setHostname*: bool
const defaultSections = ["Misc", "Logging", "Filesystem"] const defaultSections* = ["Misc", "Logging", "Filesystem"]
const defaultConfig = { const defaultConfig* = {
"Logging": { "Logging": {
"stderr": "/var/log/nimd", "logFile": "/var/log/nimd",
"stdout": "/var/log/nimd",
"level": "info" "level": "info"
}.toTable(), }.toTable(),
"Misc": { "Misc": {
"controlSocket": "/var/run/nimd.sock", "controlSocket": "/var/run/nimd.sock",
"onDependencyConflict": "skip" "onDependencyConflict": "skip",
"setHostname": "true",
"workers": "1",
"sigtermDelay": "90",
"restartDelay": "30"
}.toTable(), }.toTable(),
"Filesystem": { "Filesystem": {
"autoMount": "true", "autoMount": "true",
@ -59,9 +64,16 @@ const defaultConfig = {
"virtualDisks": "" "virtualDisks": ""
}.toTable() }.toTable()
}.toTable() }.toTable()
const levels = {"trace": Trace, "debug": Debug, "info": Info,
"warning": Warning, "error": Error, "critical": Critical, const levels = {
"fatal": Fatal}.toTable() "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 = 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: elif section notin defaultSections:
logger.warning(&"Unknown section '{section}' found in config file, skipping it") logger.warning(&"Unknown section '{section}' found in config file, skipping it")
else: else:
if not data.hasKey(section):
data[section] = newTable[string, string]()
for key in defaultConfig[section].keys(): for key in defaultConfig[section].keys():
data[section][key] = cfgObject.getSectionValue(section, key, defaultConfig[section][key]) data[section][key] = cfgObject.getSectionValue(section, key, defaultConfig[section][key])
for dirInfo in data["Filesystem"]["createDirs"].split(","): for dirInfo in data["Filesystem"]["createDirs"].split(","):
temp = dirInfo.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(","): for symInfo in data["Filesystem"]["createSymlinks"].split(","):
temp = symInfo.split(":") temp = symInfo.split(":")
symlinks.add(newSymlink(temp[0], temp[1])) symlinks.add(newSymlink(temp[0], temp[1]))
if data["Logging"]["level"] notin levels: 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"] data["Logging"]["level"] = defaultConfig["Logging"]["level"]
result = NimDConfig(logLevel: levels[data["Logging"]["level"]], result = NimDConfig(logLevel: levels[data["Logging"]["level"]],
filesystems: filesystems) 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"])
)

View File

@ -38,11 +38,28 @@ type
level*: LogLevel level*: LogLevel
handlers*: seq[LogHandler] handlers*: seq[LogHandler]
proc dup3(a1, a2, a3: cint): cint {.importc.}
var defaultLevel = LogLevel.Info var defaultLevel = LogLevel.Info
var logFile = "/var/log/nimd" var logFile = "/var/log/nimd"
var logToFileOnly: bool = false var logToFileOnly: bool = false
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)
var customStderr: File
discard open(customStderr, customStderrFd, fmAppend)
proc log(self: Logger, level: LogLevel = defaultLevel, message: string) # Forward declaration proc log(self: Logger, level: LogLevel = defaultLevel, message: string) # Forward declaration
# Simple one-line procedures # Simple one-line procedures
@ -79,132 +96,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 # 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) = proc logTraceStderr(self: LogHandler, logger: Logger, message: string) =
logger.lockFile(stderr)
setForegroundColor(fgMagenta) 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) setForegroundColor(fgDefault)
logger.unlockFile(stderr)
proc logDebugStderr(self: LogHandler, logger: Logger, message: string) = proc logDebugStderr(self: LogHandler, logger: Logger, message: string) =
logger.lockFile(stderr)
setForegroundColor(fgCyan) 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) setForegroundColor(fgDefault)
logger.unlockFile(stderr)
proc logInfoStderr(self: LogHandler, logger: Logger, message: string) = proc logInfoStderr(self: LogHandler, logger: Logger, message: string) =
logger.lockFile(stderr)
setForegroundColor(fgGreen) 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) setForegroundColor(fgDefault)
logger.unlockFile(stderr)
proc logWarningStderr(self: LogHandler, logger: Logger, message: string) = proc logWarningStderr(self: LogHandler, logger: Logger, message: string) =
logger.lockFile(stderr)
setForegroundColor(fgYellow) 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) setForegroundColor(fgDefault)
logger.unlockFile(stderr)
proc logErrorStderr(self: LogHandler, logger: Logger, message: string) = proc logErrorStderr(self: LogHandler, logger: Logger, message: string) =
logger.lockFile(stderr)
setForegroundColor(fgRed) 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) setForegroundColor(fgDefault)
logger.unlockFile(stderr)
proc logCriticalStderr(self: LogHandler, logger: Logger, message: string) = proc logCriticalStderr(self: LogHandler, logger: Logger, message: string) =
logger.lockFile(stderr)
setForegroundColor(fgYellow) setForegroundColor(fgYellow)
setBackgroundColor(bgRed) 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) setBackgroundColor(bgDefault)
stderr.writeLine(&""" {message}""") customStderr.writeLine(&""" {message}""")
setForegroundColor(fgDefault) setForegroundColor(fgDefault)
logger.unlockFile(stderr)
proc logFatalStderr(self: LogHandler, logger: Logger, message: string) = proc logFatalStderr(self: LogHandler, logger: Logger, message: string) =
logger.lockFile(stderr)
setForegroundColor(fgBlack) setForegroundColor(fgBlack)
setBackgroundColor(bgRed) 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) setForegroundColor(fgRed)
setBackgroundColor(bgDefault) setBackgroundColor(bgDefault)
stderr.writeline(&""" {message}""") customStderr.writeline(&""" {message}""")
setForegroundColor(fgDefault) setForegroundColor(fgDefault)
logger.unlockFile(stderr)
proc logTraceFile(self: LogHandler, logger: Logger, message: string) = proc logTraceFile(self: LogHandler, logger: Logger, message: string) =
var self = StreamHandler(self) 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}""") 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) = proc logDebugFile(self: LogHandler, logger: Logger, message: string) =
var self = StreamHandler(self) 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}""") 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) = proc logInfoFile(self: LogHandler, logger: Logger, message: string) =
var self = StreamHandler(self) 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}""") 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) = proc logWarningFile(self: LogHandler, logger: Logger, message: string) =
var self = StreamHandler(self) 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}""") 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) = proc logErrorFile(self: LogHandler, logger: Logger, message: string) =
var self = StreamHandler(self) 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}""") 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) = proc logCriticalFile(self: LogHandler, logger: Logger, message: string) =
var self = StreamHandler(self) 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}""") 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) = proc logFatalFile(self: LogHandler, logger: Logger, message: string) =
var self = StreamHandler(self) 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}""") 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) = proc switchToFile*(self: Logger) =