Aligned log messages, minor general fixes, unified virtual and real filesystems and started to work on the API for parsing NimD's config (also added empty networking module)

This commit is contained in:
nocturn9x 2021-12-06 13:47:11 +01:00
parent 2ba8cfa5be
commit 92850e8d70
9 changed files with 168 additions and 94 deletions

View File

@ -28,7 +28,7 @@ NimD assumes that the standard file descriptors 0, 1 and 2 (stdin, stdout and st
know how to check for a proper set of file descriptors and connect them manually, please make a PR, I'd love to hear how to do that. know how to check for a proper set of file descriptors and connect them manually, please make a PR, I'd love to hear how to do that.
When mounting the filesystem, NimD is at least somewhat smart: When mounting the filesystem, NimD is at least somewhat smart:
- First, it'll try to mount the standard POSIX virtual filesystems (/proc, /sys, etc) if they're not mounted already - First, it'll try to mount the standard POSIX virtual filesystems (/proc, /sys, etc) if they're not mounted already (you specify which)
- Then, it'll parse /etc/fstab and mount all the disks from there as well (unless they are already mounted, of course). - Then, it'll parse /etc/fstab and mount all the disks from there as well (unless they are already mounted, of course).
Drive IDs/UUIDs, LABELs and PARTUUIDs are also supported and are automatically resolved to their respective /dev/disk/by-XXX symlink Drive IDs/UUIDs, LABELs and PARTUUIDs are also supported and are automatically resolved to their respective /dev/disk/by-XXX symlink

30
src/core/config.nim Normal file
View File

@ -0,0 +1,30 @@
# 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 parsecfg
import ../util/logging
import services
import fs
type
Config* = ref object
## Configuration object
# The desired log verbosity of nimD
logLevel*: LogLevel
logDir*: Directory
services*: tuple[runlevel: RunLevel, services: seq[Service]]
filesystems*: seq[Filesystem]

13
src/core/control.nim Normal file
View File

@ -0,0 +1,13 @@
# 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.

View File

@ -56,11 +56,12 @@ type
data: string data: string
dump: uint8 dump: uint8
pass: uint8 pass: uint8
virtual: bool # Is this a virtual filesystem?
proc newFilesystem*(source, target, fstype: string, mountflags: uint64 = 0, data: string = "", dump: uint8 = 0, pass: uint8 = 0): Filesystem = proc newFilesystem*(source, target, fstype: string, mountflags: uint64 = 0, data: string = "", dump: uint8 = 0, pass: uint8 = 0, virtual: bool = false): Filesystem =
## Initializes a new filesystem object ## Initializes a new filesystem object
result = Filesystem(source: source, target: target, fstype: fstype, mountflags: mountflags, data: data, dump: dump, pass: pass) result = Filesystem(source: source, target: target, fstype: fstype, mountflags: mountflags, data: data, dump: dump, pass: pass, virtual: virtual)
proc newSymlink*(source, dest: string): Symlink = proc newSymlink*(source, dest: string): Symlink =
@ -73,10 +74,13 @@ proc newDirectory*(path: string, permissions: uint64): Directory =
result = Directory(path: path, permissions: permissions) result = Directory(path: path, permissions: permissions)
# Stores VFS entries to be mounted upon boot (usually /proc, /sys, etc). You could # Stores filesystem entries to be mounted upon boot. You could do this with a oneshot
# do this with a oneshot service, but it's a simple enough feature to have it built-in # service, but it's a simple enough feature to have it built-in into the init itself,
# into the init itself (especially since it makes error handling a heck of a lot easier) # especially since it makes error handling a heck of a lot easier.
var virtualFileSystems: seq[Filesystem] = @[] # Note this has to be used only for stuff that's not already in /etc/fstab, like virtual
# filesystems (/proc, /sys, etc) if your kernel doesn't already mount them upon startup
# (which it most likely does)
var fileSystems: seq[Filesystem] = @[]
# Since creating symlinks is a pretty typical operation for an init, NimD # Since creating symlinks is a pretty typical operation for an init, NimD
# provides a straightforward way to create them on boot without creating # provides a straightforward way to create them on boot without creating
# full fledged oneshot services # full fledged oneshot services
@ -86,38 +90,38 @@ var symlinks: seq[Symlink ] = @[]
var directories: seq[Directory] = @[] var directories: seq[Directory] = @[]
proc addVFS*(filesystem: FileSystem) = proc addFS*(filesystem: FileSystem) =
## Adds a virtual filesystem to be mounted upon boot ## Adds a filesystem to be mounted upon boot
virtualFileSystems.add(filesystem) filesystems.add(filesystem)
proc removeVFS*(filesystem: Filesystem) = proc removeFS*(filesystem: Filesystem) =
## Removes a virtual filesystem. Note ## Unregisters a filesystem. Note
## this has no effect if executed after ## this has no effect if executed after
## the VFSs have been mounted (i.e. after ## the filesystems have been mounted (i.e. after
## a call to mountVirtualDisks) ## a call to mountDisks)
for i, f in virtualFileSystems: for i, f in filesystems:
if f == filesystem: if f == filesystem:
virtualFileSystems.del(i) filesystems.del(i)
iterator getAllVFSPaths: string = iterator getAllFSPaths: string =
## Yields all of the mount points of ## Yields all of the mount points of
## the currently registered virtual ## the currently registered
## filesystems ## filesystems
for vfs in virtualFileSystems: for fs in filesystems:
yield vfs.target yield fs.target
iterator getAllVFSNames: string = iterator getAllFSNames: string =
## This is similar to what ## This is similar to what
## getAllVFSPaths does, except ## getAllVFSPaths does, except
## it yields the VFS' source ## it yields the VFS' source
## instead of the mount point ## instead of the mount point
## (which in this case is just ## (which in this case is just
## an alias, hence the "names" part) ## an alias, hence the "names" part)
for vfs in virtualFileSystems: for fs in filesystems:
yield vfs.source yield fs.source
proc addSymlink*(symlink: Symlink) = proc addSymlink*(symlink: Symlink) =
@ -231,12 +235,13 @@ proc checkDiskIsMounted(search: Filesystem, expand: bool = false): bool =
return false return false
proc mountRealDisks*(logger: Logger, fstab: string = "/etc/fstab") = proc mountDisks*(logger: Logger, fstab: string = "/etc/fstab") =
## Mounts real disks from /etc/fstab ## Mounts disks from /etc/fstab as well as the ones registered
## via addFS (these are mounted first)
var retcode = 0 var retcode = 0
try: try:
logger.debug(&"Reading disk entries from {fstab}") logger.debug(&"Reading disk entries from {fstab} (mounting custom filesystems first!)")
for entry in parseFileSystemTable(readFile(fstab)): for entry in filesystems & parseFileSystemTable(readFile(fstab)):
if checkDiskIsMounted(entry, expand=true): if checkDiskIsMounted(entry, expand=true):
logger.debug(&"Skipping mounting filesystem {entry.source} ({entry.fstype}) at {entry.target}: already mounted") logger.debug(&"Skipping mounting filesystem {entry.source} ({entry.fstype}) at {entry.target}: already mounted")
continue continue
@ -255,28 +260,6 @@ proc mountRealDisks*(logger: Logger, fstab: string = "/etc/fstab") =
nimDExit(logger, 131) nimDExit(logger, 131)
proc mountVirtualDisks*(logger: Logger) =
## Mounts POSIX virtual filesystems/partitions,
## such as /proc and /sys
var retcode = 0
for entry in virtualFileSystems:
if checkDiskIsMounted(entry):
logger.debug(&"Skipping mounting filesystem {entry.source} ({entry.fstype}) at {entry.target}: already mounted")
continue
logger.debug(&"Mounting filesystem {entry.source} ({entry.fstype}) at {entry.target} with mount option(s) {entry.data}")
logger.trace(&"Calling mount('{entry.source}', '{entry.target}', '{entry.fstype}', {entry.mountflags}, '{entry.data}')")
retcode = mount(entry.source, entry.target, entry.fstype, entry.mountflags, entry.data)
logger.trace(&"mount('{entry.source}', '{entry.target}', '{entry.fstype}', {entry.mountflags}, '{entry.data}') returned {retcode}")
if retcode == -1:
logger.error(&"Mounting disk {entry.source} at {entry.target} has failed with error {posix.errno}: {posix.strerror(posix.errno)}")
# Resets the error code
posix.errno = cint(0)
logger.fatal("Failed mounting vital system disk partition, system is likely corrupted, booting cannot continue")
nimDExit(logger, 131) # ENOTRECOVERABLE - State not recoverable
else:
logger.debug(&"Mounted {entry.source} at {entry.target}")
proc unmountAllDisks*(logger: Logger, code: int) = proc unmountAllDisks*(logger: Logger, code: int) =
## Unmounts all currently mounted disks, including the ones that ## Unmounts all currently mounted disks, including the ones that
## were not mounted trough fstab but excluding virtual filesystems ## were not mounted trough fstab but excluding virtual filesystems
@ -293,13 +276,16 @@ proc unmountAllDisks*(logger: Logger, code: int) =
# as they are critical system components (especially /proc): maybe we should use stat() # as they are critical system components (especially /proc): maybe we should use stat()
# instead and make a generic check, but adding a system call into the mix seems overkill given # instead and make a generic check, but adding a system call into the mix seems overkill given
# we alredy have all the info we need # we alredy have all the info we need
if entry in virtualFileSystems: if entry.virtual:
# Detects VFS manually
continue continue
for source in getAllVFSNames(): for source in getAllFSNames():
# Detects VFS by name
if entry.source == source: if entry.source == source:
isVFS = true isVFS = true
break break
for path in getAllVFSPaths(): for path in getAllFSPaths():
# Detects VFS by mount point
if entry.target.startswith(path): if entry.target.startswith(path):
isVFS = true isVFS = true
break break

View File

@ -12,6 +12,7 @@
# 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 strformat import strformat
import strutils
import cpuinfo import cpuinfo
import tables import tables
import osproc import osproc
@ -112,8 +113,10 @@ proc supervisorWorker(logger: Logger, service: Service, pid: int) =
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.switchToFile() logger.switchToFile()
while true: while true:
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
@ -121,6 +124,7 @@ proc supervisorWorker(logger: Logger, service: Service, pid: int) =
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}")
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")
@ -165,7 +169,9 @@ proc startService(logger: Logger, service: Service) =
## Starts a single service (this is called by ## Starts a single service (this is called by
## startServices below until all services have ## startServices below until all services have
## been started). This function is supposed to ## been started). This function is supposed to
## be called from a forked process! ## be called from a forked process and it itself
## forks to call supervisorWorker if the service
## is a supervised one
var process: Process var process: Process
try: try:
var split = shlex(service.exec) var split = shlex(service.exec)
@ -179,6 +185,7 @@ proc startService(logger: Logger, service: Service) =
if service.supervised: if service.supervised:
var pid = posix.fork() var pid = posix.fork()
if pid == 0: if pid == 0:
logger.trace(&"New child has been spawned")
supervisorWorker(logger, service, process.processID) supervisorWorker(logger, service, process.processID)
# If the service is unsupervised we just exit # If the service is unsupervised we just exit
except: except:
@ -192,7 +199,7 @@ proc startServices*(logger: Logger, level: RunLevel, workers: int = 1) =
## Starts the registered services in the ## Starts the registered services in the
## given runlevel ## given runlevel
if workers > cpuinfo.countProcessors(): if workers > cpuinfo.countProcessors():
logger.warning(&"The configured number of workers ({workers}) is greater than the recommended one ({cpuinfo.countProcessors()}), performance may degrade") logger.warning(&"The configured number of workers ({workers}) is greater than the number of CPU cores ({cpuinfo.countProcessors()}), performance may degrade")
var workerCount: int = 0 var workerCount: int = 0
var status: cint var status: cint
var pid: int = posix.fork() var pid: int = posix.fork()
@ -206,7 +213,10 @@ proc startServices*(logger: Logger, level: RunLevel, workers: int = 1) =
servicesCopy.add(service) servicesCopy.add(service)
while servicesCopy.len() > 0: while servicesCopy.len() > 0:
if workerCount == workers: if workerCount == workers:
discard waitPid(cint(pid), status, WUNTRACED) logger.debug(&"Worker queue full, waiting for some worker to exit...")
logger.trace(&"Calling waitpid() on {pid}")
var returnCode = waitPid(cint(pid), status, WUNTRACED)
logger.trace(&"Call to waitpid() set status to {status} and returned {returnCode}")
dec(workerCount) dec(workerCount)
pid = posix.fork() pid = posix.fork()
if pid == -1: if pid == -1:
@ -225,4 +235,7 @@ proc startServices*(logger: Logger, level: RunLevel, workers: int = 1) =
servicesCopy.del(0) servicesCopy.del(0)
quit(0) quit(0)
else: else:
discard waitPid(cint(pid), status, WUNTRACED) 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() set status to {status} and returned {returnCode}")

View File

@ -40,12 +40,12 @@ 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
# Adds virtual filesystems (Update: apparently the kernel already mounts this stuff!) # Adds virtual filesystems (Update: apparently the kernel already mounts this stuff!)
#[ #[
addVFS(newFilesystem(source="proc", target="/proc", fstype="proc", mountflags=0u64, data="nosuid,noexec,nodev", dump=0u8, pass=0u8)) addFS(newFilesystem(source="proc", target="/proc", fstype="proc", mountflags=0u64, data="nosuid,noexec,nodev", dump=0u8, pass=0u8))
addVFS(newFilesystem(source="sys", target="/sys", fstype="sysfs", mountflags=0u64, data="nosuid,noexec,nodev", dump=0u8, pass=0u8)) addFS(newFilesystem(source="sys", target="/sys", fstype="sysfs", mountflags=0u64, data="nosuid,noexec,nodev", dump=0u8, pass=0u8))
addVFS(newFilesystem(source="run", target="/run", fstype="tmpfs", mountflags=0u64, data="mode=0755,nosuid,nodev", dump=0u8, pass=0u8)) addFS(newFilesystem(source="run", target="/run", fstype="tmpfs", mountflags=0u64, data="mode=0755,nosuid,nodev", dump=0u8, pass=0u8))
addVFS(newFilesystem(source="dev", target="/dev", fstype="devtmpfs", mountflags=0u64, data="mode=0755,nosuid", dump=0u8, pass=0u8)) addFS(newFilesystem(source="dev", target="/dev", fstype="devtmpfs", mountflags=0u64, data="mode=0755,nosuid", dump=0u8, pass=0u8))
addVFS(newFilesystem(source="devpts", target="/dev/pts", fstype="devpts", mountflags=0u64, data="mode=0620,gid=5,nosuid,noexec", dump=0u8, pass=0u8)) addFS(newFilesystem(source="devpts", target="/dev/pts", fstype="devpts", mountflags=0u64, data="mode=0620,gid=5,nosuid,noexec", dump=0u8, pass=0u8))
addVFS(newFilesystem(source="shm", target="/dev/shm", fstype="tmpfs", mountflags=0u64, data="mode=1777,nosuid,nodev", dump=0u8, pass=0u8)) addFS(newFilesystem(source="shm", target="/dev/shm", fstype="tmpfs", mountflags=0u64, data="mode=1777,nosuid,nodev", dump=0u8, pass=0u8))
]# ]#
addDirectory(newDirectory("test", 777)) # Should create a directory addDirectory(newDirectory("test", 777)) # Should create a directory
addDirectory(newDirectory("/dev/disk", 123)) # Should say directory already exists addDirectory(newDirectory("/dev/disk", 123)) # Should say directory already exists
@ -72,7 +72,7 @@ proc addStuff =
proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab") = proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab") =
## NimD's entry point and setup ## NimD's entry point and setup
## function ## function
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()")
let pid = getCurrentProcessId() let pid = getCurrentProcessId()
@ -84,15 +84,12 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab")
logger.trace(&"getuid() returned {uid}") logger.trace(&"getuid() returned {uid}")
if uid != 0: if uid != 0:
logger.fatal(&"NimD must run as root, but current user id is {uid}") logger.fatal(&"NimD must run as root, but current user id is {uid}")
nimDExit(logger, EPERM) # 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. Must find # a noconv procedure, so we use getDefaultLogger() instead
# a better solution long-term because we need the configuration from
# our own logger object (otherwise we'd always create a new one and
# never switch our logs to file once booting is completed)
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
onSignal(SIGCHLD): onSignal(SIGCHLD):
# One of the key features of an init system is reaping child # One of the key features of an init system is reaping child
@ -102,10 +99,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab")
try: try:
if mountDisks: if mountDisks:
logger.info("Mounting filesystem") logger.info("Mounting filesystem")
logger.info("Mounting virtual disks") mountDisks(logger, fstab)
mountVirtualDisks(logger)
logger.info("Mounting real disks")
mountRealDisks(logger, 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")

View File

@ -38,8 +38,8 @@ type
level*: LogLevel level*: LogLevel
handlers*: seq[LogHandler] handlers*: seq[LogHandler]
const defaultLevel = LogLevel.Info var defaultLevel = LogLevel.Info
const logFile = "/var/log/nimd" var logFile = "/var/log/nimd"
var logToFile: bool = false var logToFile: bool = false
@ -58,8 +58,12 @@ proc fatal*(self: Logger, message: string) = self.log(LogLevel.Fatal, message)
proc newLogger*(level: LogLevel = defaultLevel, handlers: seq[LogHandler] = @[]): Logger = Logger(level: level, handlers: handlers) proc newLogger*(level: LogLevel = defaultLevel, handlers: seq[LogHandler] = @[]): Logger = Logger(level: level, handlers: handlers)
proc setLevel*(self: Logger, level: LogLevel) = self.level = level proc setLevel*(self: Logger, level: LogLevel) = self.level = level
proc getLevel*(self: Logger): LogLevel = self.level proc getLevel*(self: Logger): LogLevel = self.level
proc setLogFile*(filename: string) = logFile = filename
proc getLogFile*: string = logFile
proc setDefaultLevel*(level: LogLevel) = defaultLevel = level
proc getDefaultLevel*: LogLevel = defaultLevel
proc createHandler*(procedure: proc (self: LogHandler, logger: Logger, message: string), level: LogLevel): LogHandler = LogHandler(code: procedure, level: level) proc createHandler*(procedure: proc (self: LogHandler, logger: Logger, message: string), level: LogLevel): LogHandler = LogHandler(code: procedure, level: level)
proc createStreamHandler*(procedure: proc (self: LogHandler, logger: Logger, message: string), level: LogLevel, filename: string): StreamHandler = StreamHandler(code: procedure, level: level, filename: filename, file: open(filename, fmWrite)) proc createStreamHandler*(procedure: proc (self: LogHandler, logger: Logger, message: string), level: LogLevel, filename: string): StreamHandler = StreamHandler(code: procedure, level: level, filename: filename, file: open(filename, fmAppend))
proc addHandler*(self: Logger, handler: LogHandler) = self.handlers.add(handler) proc addHandler*(self: Logger, handler: LogHandler) = self.handlers.add(handler)
proc removeHandler*(self: Logger, handler: LogHandler) = self.handlers.delete(self.handlers.find(handler)) proc removeHandler*(self: Logger, handler: LogHandler) = self.handlers.delete(self.handlers.find(handler))
@ -71,84 +75,99 @@ proc log(self: Logger, level: LogLevel = defaultLevel, message: string) =
handler.code(handler, self, message) handler.code(handler, self, message)
# Note: Log messages have been *carefully* hand tuned to be perfectly aligned both in the console and in log files.
# Do NOT touch the alignment offsets or your console output and logs will look like trash
proc logTraceStderr(self: LogHandler, logger: Logger, message: string) = proc logTraceStderr(self: LogHandler, logger: Logger, message: string) =
setForegroundColor(fgMagenta) setForegroundColor(fgMagenta)
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE ({posix.getpid()})] {message}""") stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
stderr.flushFile() stderr.flushFile()
setForegroundColor(fgDefault) setForegroundColor(fgDefault)
proc logDebugStderr(self: LogHandler, logger: Logger, message: string) = proc logDebugStderr(self: LogHandler, logger: Logger, message: string) =
setForegroundColor(fgCyan) setForegroundColor(fgCyan)
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - DEBUG ({posix.getpid()})] {message}""") stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
stderr.flushFile() stderr.flushFile()
setForegroundColor(fgDefault) setForegroundColor(fgDefault)
proc logInfoStderr(self: LogHandler, logger: Logger, message: string) = proc logInfoStderr(self: LogHandler, logger: Logger, message: string) =
setForegroundColor(fgGreen) setForegroundColor(fgGreen)
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - INFO ({posix.getpid()})] {message}""") stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
stderr.flushFile() stderr.flushFile()
setForegroundColor(fgDefault) setForegroundColor(fgDefault)
proc logWarningStderr(self: LogHandler, logger: Logger, message: string) = proc logWarningStderr(self: LogHandler, logger: Logger, message: string) =
setForegroundColor(fgYellow) setForegroundColor(fgYellow)
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - WARNING ({posix.getpid()})] {message}""") stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
stderr.flushFile() stderr.flushFile()
setForegroundColor(fgDefault) setForegroundColor(fgDefault)
proc logErrorStderr(self: LogHandler, logger: Logger, message: string) = proc logErrorStderr(self: LogHandler, logger: Logger, message: string) =
setForegroundColor(fgRed) setForegroundColor(fgRed)
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - ERROR ({posix.getpid()})] {message}""") stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
stderr.flushFile() stderr.flushFile()
setForegroundColor(fgDefault) setForegroundColor(fgDefault)
proc logCriticalStderr(self: LogHandler, logger: Logger, message: string) = proc logCriticalStderr(self: LogHandler, logger: Logger, message: string) =
setForegroundColor(fgRed) setForegroundColor(fgYellow)
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - CRITICAL ({posix.getpid()})] {message}""") setBackgroundColor(bgRed)
stderr.flushFile() stderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})]""")
setBackgroundColor(bgDefault)
stderr.writeLine(&""" {message}""")
setForegroundColor(fgDefault) setForegroundColor(fgDefault)
stderr.flushFile()
proc logFatalStderr(self: LogHandler, logger: Logger, message: string) = proc logFatalStderr(self: LogHandler, logger: Logger, message: string) =
setForegroundColor(fgBlack) setForegroundColor(fgBlack)
setBackgroundColor(bgRed) setBackgroundColor(bgRed)
stderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - FATAL ({posix.getpid()})]""") stderr.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}") stderr.writeline(&""" {message}""")
setForegroundColor(fgDefault) setForegroundColor(fgDefault)
stderr.flushFile() stderr.flushFile()
proc logTraceFile(self: LogHandler, logger: Logger, message: string) = proc logTraceFile(self: LogHandler, logger: Logger, message: string) =
StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE ({posix.getpid()})] {message}""") StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
StreamHandler(self).file.flushFile()
proc logDebugFile(self: LogHandler, logger: Logger, message: string) = proc logDebugFile(self: LogHandler, logger: Logger, message: string) =
StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE ({posix.getpid()})] {message}""") StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
StreamHandler(self).file.flushFile()
proc logInfoFile(self: LogHandler, logger: Logger, message: string) = proc logInfoFile(self: LogHandler, logger: Logger, message: string) =
StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE ({posix.getpid()})] {message}""") StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
StreamHandler(self).file.flushFile()
proc logWarningFile(self: LogHandler, logger: Logger, message: string) = proc logWarningFile(self: LogHandler, logger: Logger, message: string) =
StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE ({posix.getpid()})] {message}""") StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
StreamHandler(self).file.flushFile()
proc logErrorFile(self: LogHandler, logger: Logger, message: string) = proc logErrorFile(self: LogHandler, logger: Logger, message: string) =
StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE ({posix.getpid()})] {message}""") StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
StreamHandler(self).file.flushFile()
proc logCriticalFile(self: LogHandler, logger: Logger, message: string) = proc logCriticalFile(self: LogHandler, logger: Logger, message: string) =
StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE ({posix.getpid()})] {message}""") StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})] {message}""")
StreamHandler(self).file.flushFile()
proc logFatalFile(self: LogHandler, logger: Logger, message: string) = proc logFatalFile(self: LogHandler, logger: Logger, message: string) =
StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE ({posix.getpid()})] {message}""") StreamHandler(self).file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})] {message}""")
StreamHandler(self).file.flushFile()
proc switchToFile*(self: Logger) = proc switchToFile*(self: Logger) =
@ -158,7 +177,7 @@ proc switchToFile*(self: Logger) =
if logToFile: if logToFile:
return return
logToFile = true logToFile = true
self.handlers = @[] self.handlers = @[] # Don't you love it when you can just let the GC manage memory for you?
self.addHandler(createStreamHandler(logTraceFile, LogLevel.Trace, logFile)) self.addHandler(createStreamHandler(logTraceFile, LogLevel.Trace, logFile))
self.addHandler(createStreamHandler(logDebugFile, LogLevel.Debug, logFile)) self.addHandler(createStreamHandler(logDebugFile, LogLevel.Debug, logFile))
self.addHandler(createStreamHandler(logInfoFile, LogLevel.Info, logFile)) self.addHandler(createStreamHandler(logInfoFile, LogLevel.Info, logFile))
@ -168,6 +187,23 @@ proc switchToFile*(self: Logger) =
self.addHandler(createStreamHandler(logFatalFile, LogLevel.Fatal, logFile)) self.addHandler(createStreamHandler(logFatalFile, LogLevel.Fatal, logFile))
proc switchToConsole*(self: Logger) =
## Switches logging to the console and
## changes the behavior of getDefaultLogger
## accordingly
if not logToFile:
return
logToFile = false
self.handlers = @[]
self.addHandler(createHandler(logTraceStderr, LogLevel.Trace))
self.addHandler(createHandler(logDebugStderr, LogLevel.Debug))
self.addHandler(createHandler(logInfoStderr, LogLevel.Info))
self.addHandler(createHandler(logWarningStderr, LogLevel.Warning))
self.addHandler(createHandler(logErrorStderr, LogLevel.Error))
self.addHandler(createHandler(logCriticalStderr, LogLevel.Critical))
self.addHandler(createHandler(logFatalStderr, LogLevel.Fatal))
proc getDefaultLogger*(): Logger = proc getDefaultLogger*(): Logger =
## Gets a simple logger with level set ## Gets a simple logger with level set
## to LogLevel.Info and one handler per ## to LogLevel.Info and one handler per

View File

@ -78,7 +78,9 @@ proc reapProcess*(logger: Logger) =
## and restarts them as needed ## and restarts them as needed
logger.debug("Handling SIGCHLD") logger.debug("Handling SIGCHLD")
var status: cint var status: cint
discard posix.waitPid(-1, status, WNOHANG) # This doesn't hang, which is what we want logger.trace("Calling waitpid() on -1")
var returnCode = posix.waitPid(-1, status, WNOHANG) # This doesn't hang, which is what we want
logger.trace(&"Call to waitpid() set status to {status} and returned {returnCode}")
proc exists*(p: string): bool = proc exists*(p: string): bool =

BIN
vm.qcow2 Normal file

Binary file not shown.