Major code cleanup. Added initial (VERY broken) support for services

This commit is contained in:
Nocturn9x 2021-12-04 13:47:06 +01:00
parent bd7d4e1974
commit 5777e1a715
8 changed files with 747 additions and 335 deletions

View File

@ -6,7 +6,7 @@ WORKDIR /code
# Removes any already existing binary so that when compilation fails the container stops # Removes any already existing binary so that when compilation fails the container stops
RUN rm -f /code/nimd RUN rm -f /code/nimd
RUN rm -f /code/main RUN rm -f /code/main
RUN nimble install syscall -y RUN nimble install syscall glob shlex -y
RUN nim -d:release --opt:size --passL:"-static" --gc:orc -d:useMalloc c -o:nimd src/main RUN nim -d:release --opt:size --passL:"-static" --gc:orc -d:useMalloc c -o:nimd src/main
RUN cp /code/nimd /sbin/nimd RUN cp /code/nimd /sbin/nimd

359
src/core/fs.nim Normal file
View File

@ -0,0 +1,359 @@
# 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 strutils
import sequtils
import strformat
import posix
import os
import ../util/[logging, misc]
import shutdown
# Nim wrappers around C functionality in sys/mount.h on Linux
proc mount*(source: cstring, target: cstring, fstype: cstring,
mountflags: culong, data: pointer): cint {.header: "sys/mount.h", importc.}
# Since cstrings are weak references, we need to convert nim strings to cstrings only
# when we're ready to use them and only when we're sure the underlying nim string is
# in scope, otherwise garbage collection madness happens
proc mount*(source, target, fstype: string, mountflags: uint64, data: string): int = int(mount(cstring(source), cstring(target), cstring(fstype), culong(mountflags), cstring(data)))
proc umount*(target: cstring): cint {.header: "sys/mount.h", importc.}
proc umount2*(target: cstring, flags: cint): cint {.header: "sys/mount.h", importc.}
# These 2 wrappers silent the CStringConv warning
# (implicit conversion to 'cstring' from a non-const location)
proc umount*(target: string): int = int(umount(cstring(target)))
proc umount2*(target: string, flags: int): int = int(umount2(cstring(target), cint(flags)))
## Our Nim API
type
Directory* = ref object
path: string
permissions: uint64
Symlink* = ref object
## A symbolic link
source: string
dest: string
Filesystem* = ref object
## A filesystem
## (real or virtual)
source: string
target: string
fstype: string
mountflags: uint64
data: string
dump: uint8
pass: uint8
proc newFilesystem*(source, target, fstype: string, mountflags: uint64 = 0, data: string = "", dump: uint8 = 0, pass: uint8 = 0): Filesystem =
## Initializes a new filesystem object
result = Filesystem(source: source, target: target, fstype: fstype, mountflags: mountflags, data: data, dump: dump, pass: pass)
proc newSymlink*(source, dest: string): Symlink =
## Initializes a new symlink object
result = Symlink(source: source, dest: dest)
proc newDirectory*(path: string, permissions: uint64): Directory =
## Initializes a new directory object
result = Directory(path: path, permissions: permissions)
# Stores VFS entries to be mounted upon boot (usually /proc, /sys, etc). You could
# do this with a oneshot service, but it's a simple enough feature to have it built-in
# into the init itself (especially since it makes error handling a heck of a lot easier)
var virtualFileSystems: seq[Filesystem] = @[]
# Since creating symlinks is a pretty typical operation for an init, NimD
# provides a straightforward way to create them on boot without creating
# full fledged oneshot services
var symlinks: seq[Symlink ] = @[]
# Stores directories to be created on boot. Again, this is achievable trough oneshots,
# but having a builtin API is a nice option IMHO
var directories: seq[Directory] = @[]
proc addVFS*(filesystem: FileSystem) =
## Adds a virtual filesystem to be mounted upon boot
virtualFileSystems.add(filesystem)
proc removeVFS*(filesystem: Filesystem) =
## Removes a virtual filesystem. Note
## this has no effect if executed after
## the VFSs have been mounted (i.e. after
## a call to mountVirtualDisks)
for i, f in virtualFileSystems:
if f == filesystem:
virtualFileSystems.del(i)
iterator getAllVFSPaths: string =
## Yields all of the mount points of
## the currently registered virtual
## filesystems
for vfs in virtualFileSystems:
yield vfs.target
iterator getAllVFSNames: string =
## This is similar to what
## getAllVFSPaths does, except
## it yields the VFS' source
## instead of the mount point
## (which in this case is just
## an alias, hence the "names" part)
for vfs in virtualFileSystems:
yield vfs.source
proc addSymlink*(symlink: Symlink) =
## Adds a symlink to be created
## upon boot (check createSymlinks)
symlinks.add(symlink)
proc removeSymlink*(symlink: Symlink) =
## Removes a symlink. This has no
## effect after createSymlinks has
## been executed
for i, sym in symlinks:
if sym == symlink:
symlinks.del(i)
proc addDirectory*(directory: Directory) =
## Adds a directory to be created upon
## boot (check createDirectories)
directories.add(directory)
proc removeDirectory*(directory: Directory) =
## Removes a directory. This has no
## effect after createDirectories has
## been executed
for i, dir in directories:
if dir == directory:
directories.del(i)
proc parseFileSystemTable*(fstab: string): seq[Filesystem] =
## Parses the contents of the given filesystem table and returns a Filesystem object.
## An improperly formatted or semantically invalid fstab will cause this function to
## error out with a ValueError exception that should be caught by the caller.
## No other checks other than very basic syntax are performed, as that job
## is delegated to the operating system. Missing dump/pass entries are interpreted
## as if they were set to 0, following the way Linux does it. Note that this function
## automatically converts UUID/LABEL/PARTUUID/ID directives to their corresponding
## /dev/disk/by-XXX/YYY symlink just like the mount command would do on a Linux system.
var temp: seq[string] = @[]
var dump: int
var pass: int
var line: string = ""
var s: seq[string] = @[]
for l in fstab.splitlines():
line = l.strip().replace("\t", " ")
if line.startswith("#") or line.isEmptyOrWhitespace():
continue
# This madness will make sure we only get (hopefully) 6 entries
# in our temporary list
temp = line.split().filterIt(it != "").join(" ").split(maxsplit=6)
if len(temp) < 6:
if len(temp) < 4:
# Not enough columns!
raise newException(ValueError, "improperly formatted filesystem table")
elif len(temp) == 4:
dump = 0
pass = 0
elif len(temp) == 5:
dump = 0
else:
try:
dump = parseInt(temp[4])
except ValueError:
raise newException(ValueError, &"improperly formatted filesystem table -> invalid value ({dump}) for dump")
try:
pass = parseInt(temp[5])
except ValueError:
raise newException(ValueError, &"improperly formatted filesystem table -> invalid value ({pass}) for pass")
if dump notin 0..1:
raise newException(ValueError, &"invalid value in filesystem table -> invalid value ({dump}) for dump")
if pass < 0:
raise newException(ValueError, &"invalid value in filesystem table -> invalid value ({pass}) for pass")
s = temp[0].split("=", maxsplit=2)
if temp[0].toLowerAscii().startswith("id="):
if len(s) < 2:
raise newException(ValueError, "improperly formatted filesystem table")
temp[0] = &"""/dev/disk/by-id/{s[1]}"""
if temp[0].toLowerAscii().startswith("label="):
if len(s) < 2:
raise newException(ValueError, "improperly formatted filesystem table")
temp[0] = &"""/dev/disk/by-label/{s[1]}"""
if temp[0].toLowerAscii().startswith("uuid="):
if len(s) < 2:
raise newException(ValueError, "improperly formatted filesystem table")
temp[0] = &"""/dev/disk/by-uuid/{s[1]}"""
if temp[0].toLowerAscii().startswith("partuuid="):
if len(s) < 2:
raise newException(ValueError, "improperly formatted filesystem table")
temp[0] = &"""/dev/disk/by-partuuid/{s[1]}"""
result.add(newFilesystem(source=temp[0], target=temp[1], fstype=temp[2], mountflags=0u64, data=temp[3], dump=uint8(dump), pass=uint8(pass)))
proc checkDiskIsMounted(search: Filesystem, expand: bool = false): bool =
## Returns true if a disk is already mounted. If expand is true,
## symlinks are expanded and checked instead of doing a simple
## string comparison of the source entry point. This should be
## true when mounting real filesystems. Returns false if
## /proc/mounts does not exist (usually happens when /proc has
## not been mounted yet)
if not fileExists("/proc/mounts"):
return false
for entry in parseFileSystemTable(readFile("/proc/mounts")):
if expand:
if exists(entry.source) and exists(search.source) and sameFile(entry.source, search.source):
return true
elif entry.source == search.source:
return true
return false
proc mountRealDisks*(logger: Logger, fstab: string = "/etc/fstab") =
## Mounts real disks from /etc/fstab
var retcode = 0
try:
logger.debug(&"Reading disk entries from {fstab}")
for entry in parseFileSystemTable(readFile(fstab)):
if checkDiskIsMounted(entry, expand=true):
logger.debug(&"Skipping mounting filesystem {entry.source} ({entry.fstype}) at {entry.target}: already mounted")
continue
logger.debug(&"fsck returned status code {retcode}")
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 {entry.source} at {entry.target} has failed with error {posix.errno}: {posix.strerror(posix.errno)}")
# Resets the error code
posix.errno = cint(0)
else:
logger.debug(&"Mounted {entry.source} at {entry.target}")
except ValueError: # Check parseFileSystemTable for more info on this catch block
logger.fatal("Improperly formatted fstab, exiting")
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) =
## Unmounts all currently mounted disks, including the ones that
## were not mounted trough fstab but excluding virtual filesystems
var isVFS: bool = false
var retcode = 0
try:
logger.info("Detaching real filesystems")
logger.debug(&"Reading disk entries from /proc/mounts")
for entry in parseFileSystemTable(readFile("/proc/mounts")):
# We don't detach the virtual filesystems because they are a software-level abstraction
# that exists purely in memory, and unmounting them while keeping the system stable during
# shutdown is a headache I don't wanna deal with.
# All of these checks seem excessive, but they make absolutely sure we don't unmount them,
# 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
# we alredy have all the info we need
if entry in virtualFileSystems:
continue
for source in getAllVFSNames():
if entry.source == source:
isVFS = true
break
for path in getAllVFSPaths():
if entry.target.startswith(path):
isVFS = true
break
if isVFS:
isVFS = false
logger.trace(&"Skipping unmounting filesystem {entry.source} ({entry.fstype}) from {entry.target} as it is a virtual filesystem")
continue
if not checkDiskIsMounted(entry):
logger.trace(&"Skipping unmounting filesystem {entry.source} ({entry.fstype}) from {entry.target}: not mounted")
continue
logger.debug(&"Unmounting filesystem {entry.source} ({entry.fstype}) from {entry.target}")
logger.trace(&"Calling umount2('{entry.source}', MNT_DETACH)")
retcode = umount2(entry.source, 2) # 2 = MNT_DETACH - Since we're shutting down, we need the disks to be *gone*!
logger.trace(&"umount2('{entry.source}', MNT_DETACH) returned {retcode}")
if retcode == -1:
logger.error(&"Unmounting disk {entry.source} from {entry.target} has failed with error {posix.errno}: {posix.strerror(posix.errno)}")
# Resets the error code
posix.errno = cint(0)
else:
logger.debug(&"Unmounted {entry.source} from {entry.target}")
except ValueError: # Check parseFileSystemTable for more info on this catch block
logger.fatal(&"A fatal error occurred while unmounting disks: {getCurrentExceptionMsg()}")
nimDExit(logger, 131)
proc createSymlinks*(logger: Logger) =
## Creates a set of symlinks needed
## by stuff like Linux ports of BSD
## software. Non-existing directories
## are created until the path to the
## symlink is valid. Already existing
## sources and non-existent destinations
## cause the symlink creation to be skipped
for sym in symlinks:
try:
if not exists(sym.source):
logger.warning(&"Skipping creation of symbolic link from {sym.dest} to {sym.source}: destination does not exist")
continue
elif exists(sym.dest):
if symlinkExists(sym.dest) and sameFile(expandSymlink(sym.dest), sym.source):
logger.debug(&"Skipping creation of symbolic link from {sym.dest} to {sym.source}: link already exists")
elif symlinkExists(sym.dest) and not sameFile(expandSymlink(sym.dest), sym.source):
logger.warning(&"Attempted to create symbolic link from {sym.dest} to {sym.source}, but link already exists and points to {expandSymlink(sym.dest)}")
else:
logger.warning(&"Attempted to create symbolic link from {sym.dest} to {sym.source}, but destination already exists and is not a symlink")
continue
logger.debug(&"Creating symbolic link from {sym.dest} to {sym.source}")
createDir(sym.dest.splitPath().head)
createSymlink(sym.source, sym.dest)
except:
logger.warning(&"Failed to create symbolic link from {sym.dest} to {sym.source}: {getCurrentExceptionMsg()}")
proc createDirectories*(logger: Logger) =
## Creates standard directories that
## Linux software expects to be present.

View File

@ -13,38 +13,17 @@
# limitations under the License. # limitations under the License.
import segfaults # Makes us catch segfaults as NilAccessDefect exceptions! import segfaults # Makes us catch segfaults as NilAccessDefect exceptions!
import strformat import strformat
import os
import ../util/[logging, disks, misc] import ../util/[logging, misc]
import services
proc mainLoop*(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab") =
proc mainLoop*(logger: Logger) =
## NimD's main execution loop ## NimD's main execution loop
try:
addShutdownHandler(unmountAllDisks, logger)
if mountDisks:
logger.info("Mounting filesystem")
logger.info("Mounting virtual disks")
mountVirtualDisks(logger)
logger.info("Mounting real disks")
mountRealDisks(logger, fstab)
else:
logger.info("Skipping disk mounting, did we restart after a critical error?")
except:
logger.fatal(&"A fatal error has occurred while preparing filesystem, booting cannot continue. Error -> {getCurrentExceptionMsg()}")
nimDExit(logger, 131)
logger.info("Disks mounted")
logger.debug("Calling sync() just in case")
doSync(logger)
logger.info("Setting hostname")
logger.debug(&"Hostname was set to '{setHostname(logger)}'")
logger.info("Creating symlinks")
createSymlinks(logger)
logger.info("Processing boot runlevel")
# TODO
logger.info("Processing default runlevel") logger.info("Processing default runlevel")
# TODO startServices(logger, workers=1, level=Default)
logger.info("System initialization complete, going idle") logger.info("System initialization complete, going idle")
while true: while true:
try: try:
@ -53,4 +32,4 @@ proc mainLoop*(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fs
except: except:
logger.critical(&"A critical error has occurred while running, restarting the mainloop! Error -> {getCurrentExceptionMsg()}") logger.critical(&"A critical error has occurred while running, restarting the mainloop! Error -> {getCurrentExceptionMsg()}")
# We *absolutely* cannot die # We *absolutely* cannot die
mainLoop(logger, mountDisks=false) mainLoop(logger)

192
src/core/services.nim Normal file
View File

@ -0,0 +1,192 @@
# 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 strformat
import cpuinfo
import tables
import osproc
import posix
import shlex
import ../util/logging
import ../util/misc
type
RunLevel* = enum
## Enum of possible runlevels
Boot, Default, Shutdown
ServiceKind* = enum
## Enumerates all service
## types
Oneshot, Simple
Service* = ref object of RootObj
## A service object
name: string
description: string
kind: ServiceKind
workDir: string
runlevel: RunLevel
exec: string
supervised: bool
restartOnFailure: bool
restartDelay: int
proc newService*(name, description: string, kind: ServiceKind, workDir: string, runlevel: RunLevel, exec: string, supervised, restartOnFailure: bool, restartDelay: int): Service =
## Creates a new service object
result = Service(name: name, description: description, kind: kind, workDir: workDir, runLevel: runLevel,
exec: exec, supervised: supervised, restartOnFailure: restartOnFailure, restartDelay: restartDelay)
var services: seq[Service] = @[]
var processIDs: TableRef[int, Service] = newTable[int, Service]()
proc isManagedProcess*(pid: int): bool =
## Returns true if the given process
## id is associated to a supervised
## NimD service
result = pid in processIDs
proc getManagedProcess*(pid: int): Service =
## Returns a managed process by its PID.
## Returns nil if the given pid doesn't
## belong to a managed process
result = if pid.isManagedProcess(): processIDs[pid] else: nil
proc removeManagedProcess*(pid: int) =
## Removes a managed process entry
## from the table
if pid.isManagedProcess():
processIDs.del(pid)
proc addManagedProcess*(pid: int, service: Service) =
## Adds a managed process to the
## table
processIDs[pid] = service
proc addService*(service: Service) =
## Adds a service to be started when
## its runlevel is processed
services.add(service)
proc removeService*(service: Service) =
## Unregisters a service from being
## started (has no effect after services
## have already been started)
for i, serv in services:
if serv == service:
services.del(i)
break
proc supervisorWorker(logger: Logger, service: Service, pid: int) =
## This is the actual worker that supervises the service process
var pid = pid
var status: cint
var returnCode: int
var sig: int
while true:
returnCode = posix.waitPid(Pid(pid), status, WUNTRACED)
if WIFEXITED(status):
sig = 0
elif WIFSIGNALED(status):
sig = WTERMSIG(status)
else:
sig = -1
if service.restartOnFailure and sig > 0:
logger.info(&"Service {service.name} has exited with return code {returnCode} (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it")
removeManagedProcess(pid)
sleepSeconds(service.restartDelay)
var split = shlex(service.exec)
if split.error:
logger.error(&"Error while starting service {service.name}: invalid exec syntax")
return
var arguments = split.words
let progName = arguments[0]
arguments = arguments[1..^1]
pid = startProcess(progName, workingDir=service.workDir, args=arguments, options={poParentStreams}).processID()
else:
logger.info(&"Service {service.name} has exited with return code {returnCode}), shutting down controlling process")
break
proc startService(logger: Logger, service: Service) =
## Starts a single service (this is called by
## startServices below until all services have
## been started)
var split = shlex(service.exec)
if split.error:
logger.error(&"Error while starting service {service.name}: invalid exec syntax")
return
var arguments = split.words
let progName = arguments[0]
arguments = arguments[1..^1]
try:
var process = startProcess(progName, workingDir=service.workDir, args=arguments, options={poParentStreams, })
if service.supervised:
supervisorWorker(logger, service, process.processID)
except OSError:
logger.error(&"Error while starting service {service.name}: {getCurrentExceptionMsg()}")
quit(0)
proc startServices*(logger: Logger, workers: int = 1, level: RunLevel) =
## Starts the services in the given
## runlevel. The workers parameter
## configures parallelism and allows
## for faster boot times by starting
## services concurrently rather than
## sequentially (1 to disable parallelism).
## Note this function immediately returns to
## the caller and forks in the background
echo posix.getpid()
discard readLine(stdin)
var pid: int = posix.fork()
if pid == -1:
logger.fatal(&"Could not fork(): {posix.strerror(posix.errno)}")
return
elif pid == 0:
quit(0)
var servicesCopy: seq[Service] = @[]
echo servicesCopy.len(), " ", posix.getpid()
for service in services:
if service.runlevel == level:
servicesCopy.add(service)
echo servicesCopy.len(), " ", posix.getpid()
if workers > cpuinfo.countProcessors() * 2 - 1:
logger.warning(&"The configured workers count is beyond the recommended threshold ({workers} > {cpuinfo.countProcessors() * 2 - 1}), performance may degrade")
while servicesCopy.len() > 0:
echo servicesCopy.len(), " ", posix.getpid()
sleepSeconds(5)
for i in 1..workers:
pid = posix.fork()
if pid == -1:
logger.error(&"An error occurred while forking to spawn services, trying again: {posix.strerror(posix.errno)}")
elif pid == 0:
logger.info(&"Starting service {servicesCopy[0].name}")
startService(logger, servicesCopy[0])
else:
if servicesCopy.len() > 0 and servicesCopy[0].supervised:
addManagedProcess(pid, servicesCopy[0])

110
src/core/shutdown.nim Normal file
View File

@ -0,0 +1,110 @@
# 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 os
import posix
import glob
import strutils
import strformat
import times
import ../util/logging
import ../util/misc
type ShutdownHandler* = ref object
## A shutdown handler (internal to NimD)
body*: proc (logger: Logger, code: int)
proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler =
result = ShutdownHandler(body: body)
var shutdownHandlers: seq[ShutdownHandler] = @[]
var sigTermDelay: float = 10.0
proc addShutdownHandler*(handler: ShutdownHandler) =
## Registers a shutdown handler to be executed
## upon a call of NimDExit
shutdownHandlers.add(handler)
proc removeShutdownHandler*(handler: ShutdownHandler) =
## Unregisters a shutdown handler
for i, hndlr in shutdownHandlers:
if hndlr == handler:
shutdownHandlers.del(i)
break
proc anyUserlandProcessLeft: bool =
## Returns true if there's any
## userland processes running.
## A userland process is one
## whose pid is higher than 2
## or whose /proc/<pid>/cmdline
## file is empty. This function
## assumes /proc is mounted and
## readable and returns false in
## the event of any I/O exceptions
try:
for dir in walkGlob("/proc/[0-9]"):
if dir.lastPathPart.parseInt() > 2 or readFile(dir.joinPath("/cmdline")).len() == 0:
# PID > 2 or empty cmdline file means it's a kernel process so we ignore
# it (not that we'd have the right to send those processes a signal anyway)
continue
else:
return true # There is at least one userland process running
except OSError:
return false
except IOError:
return false
return false
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.warning("The system is shutting down")
logger.info("Processing shutdown runlevel")
# TODO
logger.info("Running shutdown handlers")
try:
for handler in shutdownHandlers:
handler.body(logger, code)
except:
logger.error(&"An error has occurred while calling shutdown handlers. Error -> {getCurrentExceptionMsg()}")
# Note: continues calling handlers!
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")
discard execShellCmd("/bin/sh") # TODO: Is this fine? maybe use execProcess
else:
logger.info("Terminating child processes with SIGTERM")
logger.debug(&"Waiting {sigTermDelay} seconds for the kernel to deliver signals")
discard posix.kill(SIGTERM, -1) # The kernel handles this for us asynchronously
var t = cpuTime()
# We wait some time for the signals to propagate
while anyUserlandProcessLeft():
sleepSeconds(0.25)
if cpuTime() - t >= sigTermDelay:
break
if anyUserlandProcessLeft():
logger.info("Terminating child processes with SIGKILL")
discard posix.kill(SIGKILL, -1)
logger.warning("Shutdown procedure complete, sending final termination signal")
quit(code)

View File

@ -18,13 +18,15 @@ import os
# NimD's own stuff # NimD's own stuff
import util/[logging, constants, misc] import util/[logging, constants, misc]
import core/mainloop import core/[mainloop, fs, shutdown, services]
proc main(logger: Logger) =
## NimD's entry point proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab") =
## NimD's entry point and setup
## 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()
logger.trace(&"getCurrentProcessId() returned {pid}") logger.trace(&"getCurrentProcessId() returned {pid}")
@ -35,17 +37,66 @@ proc main(logger: Logger) =
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}")
quit(EPERM) # EPERM - Operation not permitted nimDExit(logger, EPERM) # EPERM - Operation not permitted
logger.debug("Setting up dummy 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, SIGTSTP,
SIGTRAP, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, 6, SIGFPE, SIGBUS, SIGURG, SIGINT): # 6 is SIGIOT SIGTRAP, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, 6, SIGFPE, SIGBUS, SIGURG, 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 # a noconv procedure, so we use getDefaultLogger() instead. Must find
# 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
logger.debug("Setting up SIGCHLD signal handler")
onSignal(SIGCHLD): onSignal(SIGCHLD):
# One of the key features of an init system is reaping child
# processes!
reapProcess(getDefaultLogger()) reapProcess(getDefaultLogger())
logger.debug("Starting uninterruptible mainloop") addSymlink(newSymlink(dest="/dev/fd", source="/proc/self/fd"))
addSymlink(newSymlink(dest="/dev/fd/0", source="/proc/self/fd/0"))
addSymlink(newSymlink(dest="/dev/fd/1", source="/proc/self/fd/1"))
addSymlink(newSymlink(dest="/dev/fd/2", source="/proc/self/fd/2"))
addSymlink(newSymlink(dest="/dev/std/in", source="/proc/self/fd/0"))
addSymlink(newSymlink(dest="/dev/std/out", source="/proc/self/fd/1"))
addSymlink(newSymlink(dest="/dev/std/err", source="/proc/self/fd/2"))
# Tests here. Check logging output (debug) to see if
# they work as intended
addSymlink(newSymlink(dest="/dev/std/err", source="/")) # Should say link already exists and points to /proc/self/fd/2
addSymlink(newSymlink(dest="/dev/std/in", source="/does/not/exist")) # Shuld say destination does not exist
addSymlink(newSymlink(dest="/dev/std/in", source="/proc/self/fd/0")) # Should say link already exists
# Adds virtual filesystems
addVFS(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))
addVFS(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))
addVFS(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))
addShutdownHandler(newShutdownHandler(unmountAllDisks))
try:
if mountDisks:
logger.info("Mounting filesystem")
logger.info("Mounting virtual disks")
mountVirtualDisks(logger)
logger.info("Mounting real disks")
mountRealDisks(logger, fstab)
else:
logger.info("Skipping disk mounting, assuming this has already been done")
except:
logger.fatal(&"A fatal error has occurred while preparing filesystem, booting cannot continue. Error -> {getCurrentExceptionMsg()}")
nimDExit(logger, 131, emerg=false)
logger.info("Disks mounted")
logger.debug("Calling sync() just in case")
doSync(logger)
logger.info("Setting hostname")
logger.debug(&"Hostname was set to '{setHostname(logger)}'")
logger.info("Creating symlinks")
createSymlinks(logger)
createDirectories(logger)
logger.info("Processing boot runlevel")
addService(newService(name="Test", description="prints owo", exec="/bin/echo owo",
runlevel=Boot, kind=Oneshot, workDir=getCurrentDir(),
supervised=false, restartOnFailure=false, restartDelay=0))
startServices(logger, workers=1, level=Boot)
logger.debug("Starting main loop")
mainLoop(logger) mainLoop(logger)
@ -93,6 +144,7 @@ when isMainModule:
try: try:
main(logger) main(logger)
except: except:
raise
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
# This will almost certainly cause the kernel to crash with an error the likes of "Kernel not syncing, attempted to kill init!", # This will almost certainly cause the kernel to crash with an error the likes of "Kernel not syncing, attempted to kill init!",

View File

@ -1,215 +0,0 @@
# 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 strutils
import sequtils
import strformat
import posix
import os
import logging
import misc
const virtualFileSystems: array[6, tuple[source: string, target: string, filesystemtype: string, mountflags: uint64, data: string, dump, pass: uint8]] = [
# ALl the standard POSIX filesystems needed by the OS to work properly are listed here
(source: "proc", target: "/proc", filesystemtype: "proc", mountflags: 0u64, data: "nosuid,noexec,nodev", dump: 0u8, pass: 0u8),
(source: "sys", target: "/sys", filesystemtype: "sysfs", mountflags: 0u64, data: "nosuid,noexec,nodev", dump: 0u8, pass: 0u8),
(source: "run", target: "/run", filesystemtype: "tmpfs", mountflags: 0u64, data: "mode=0755,nosuid,nodev", dump: 0u8, pass: 0u8),
(source: "dev", target: "/dev", filesystemtype: "devtmpfs", mountflags: 0u64, data: "mode=0755,nosuid", dump: 0u8, pass: 0u8),
(source: "devpts", target: "/dev/pts", filesystemtype: "devpts", mountflags: 0u64, data: "mode=0620,gid=5,nosuid,noexec", dump: 0u8, pass: 0u8),
(source: "shm", target: "/dev/shm", filesystemtype: "tmpfs", mountflags: 0u64, data: "mode=1777,nosuid,nodev", dump: 0u8, pass: 0u8),
]
proc parseFileSystemTable*(fstab: string): seq[tuple[source, target, filesystemtype: string, mountflags: uint64, data: string, dump, pass: uint8]] =
## Parses the contents of the given file (the contents of /etc/fstab or /etc/mtab
## most of the time, but this is not enforced in any way) and returns a sequence
## of tuples with elements source, target, filesystemtype, mountflags, data, dump
## and pass as required by mount/umount/umount2 in sys/mount.h which are wrapped below.
## The types of these arguments are Nim types to make the garbage collector happy
## and avoid freeing the underlying string object.
## An improperly formatted fstab will cause this function to error out with a
## ValueError exception (when an entry is incomplete) that should be caught by
## the caller. No other checks other than very basic syntax are performed, as
## that job is delegated to the operating system. Missing dump/pass entries are
## interpreted as if they were set to 0, following the way Linux does it.
## Note that this function automatically converts UUID/LABEL/PARTUUID/ID directives
## to their corresponding /dev/disk symlink just like the mount command would do
## on a Linux system.
var temp: seq[string] = @[]
var dump: int
var pass: int
var line: string = ""
for l in fstab.splitlines():
line = l.strip().replace("\t", " ")
if line.startswith("#") or line.isEmptyOrWhitespace():
continue
# This madness will make sure we only get (hopefully) 6 entries
# in our temporary list
temp = line.split().filterIt(it != "").join(" ").split(maxsplit=6)
if len(temp) < 6:
if len(temp) < 4:
# Not enough columns!
raise newException(ValueError, "improperly formatted filesystem table")
elif len(temp) == 4:
dump = 0
pass = 0
elif len(temp) == 5:
dump = 0
else:
try:
dump = parseInt(temp[4])
except ValueError:
raise newException(ValueError, &"improperly formatted filesystem table -> invalid value ({dump}) for dump")
try:
pass = parseInt(temp[5])
except ValueError:
raise newException(ValueError, &"improperly formatted filesystem table -> invalid value ({pass}) for pass")
if dump notin 0..1:
raise newException(ValueError, &"invalid value in filesystem table -> invalid value ({dump}) for dump")
if pass notin 0..2:
raise newException(ValueError, &"invalid value in filesystem table -> invalid value ({pass}) for pass")
if temp[0].toLowerAscii().startswith("id="):
if (let s = temp[0].split("=", maxsplit=2); len(s) < 2):
raise newException(ValueError, "improperly formatted filesystem table")
temp[0] = &"""/dev/disk/by-id/{temp[0].split("=", maxsplit=2)[1]}"""
if temp[0].toLowerAscii().startswith("label="):
if (let s = temp[0].split("=", maxsplit=2); len(s) < 2):
raise newException(ValueError, "improperly formatted filesystem table")
temp[0] = &"""/dev/disk/by-label/{temp[0].split("=", maxsplit=2)[1]}"""
if temp[0].toLowerAscii().startswith("uuid="):
if (let s = temp[0].split("=", maxsplit=2); len(s) < 2):
raise newException(ValueError, "improperly formatted filesystem table")
temp[0] = &"""/dev/disk/by-uuid/{temp[0].split("=", maxsplit=2)[1]}"""
if temp[0].toLowerAscii().startswith("partuuid="):
if (let s = temp[0].split("=", maxsplit=2); len(s) < 2):
raise newException(ValueError, "improperly formatted filesystem table")
temp[0] = &"""/dev/disk/by-partuuid/{temp[0].split("=", maxsplit=2)[1]}"""
result.add((source: temp[0], target: temp[1], filesystemtype: temp[2], mountflags: 0u64, data: temp[3], dump: uint8(dump), pass: uint8(pass)))
# Nim wrappers around C functionality in sys/mount.h on Linux
proc mount*(source: cstring, target: cstring, filesystemtype: cstring,
mountflags: culong, data: pointer): cint {.header: "sys/mount.h", importc.}
# Since cstrings are weak references, we need to convert nim strings to cstrings only
# when we're ready to use them and only when we're sure the underlying nim string is
# in scope, otherwise garbage collection madness happens
proc mount*(source, target, filesystemtype: string, mountflags: uint64, data: string): int = int(mount(cstring(source), cstring(target), cstring(filesystemtype), culong(mountflags), cstring(data)))
proc umount*(target: cstring): cint {.header: "sys/mount.h", importc.}
proc umount2*(target: cstring, flags: cint): cint {.header: "sys/mount.h", importc.}
# These 2 wrappers silent the CStringConv warning
# (implicit conversion to 'cstring' from a non-const location)
proc umount*(target: string): int = int(umount(cstring(target)))
proc umount2*(target: string, flags: int): int = int(umount2(cstring(target), cint(flags)))
proc checkDisksIsMounted(search: tuple[source, target, filesystemtype: string, mountflags: uint64, data: string, dump, pass: uint8], expand: bool = false): bool =
## Returns true if a disk is already mounted. If expand is true,
## symlinks are expanded and checked instead of doing a simple
## string comparison of the source entry point. This should be
## true when mounting real filesystems. This returns false if
## /proc/mounts does not exist (usually happens when /proc has
## not been mounted yet)
if not fileExists("/proc/mounts"):
return false
for entry in parseFileSystemTable(readFile("/proc/mounts")):
if expand:
if exists(entry.source) and exists(search.source) and sameFile(entry.source, search.source):
return true
elif entry.source == search.source:
return true
return false
proc mountRealDisks*(logger: Logger, fstab: string = "/etc/fstab") =
## Mounts real disks from /etc/fstab
var retcode = 0
try:
logger.debug(&"Reading disk entries from {fstab}")
for entry in parseFileSystemTable(readFile(fstab)):
if checkDisksIsMounted(entry, expand=true):
logger.debug(&"Skipping mounting filesystem {entry.source} ({entry.filesystemtype}) at {entry.target}: already mounted")
continue
logger.debug(&"Mounting filesystem {entry.source} ({entry.filesystemtype}) at {entry.target} with mount option(s) {entry.data}")
logger.trace(&"Calling mount('{entry.source}', '{entry.target}', '{entry.filesystemtype}', {entry.mountflags}, '{entry.data}')")
retcode = mount(entry.source, entry.target, entry.filesystemtype, entry.mountflags, entry.data)
logger.trace(&"mount('{entry.source}', '{entry.target}', '{entry.filesystemtype}', {entry.mountflags}, '{entry.data}') returned {retcode}")
if retcode == -1:
logger.error(&"Mounting {entry.source} at {entry.target} has failed with error {posix.errno}: {posix.strerror(posix.errno)}")
# Resets the error code
posix.errno = cint(0)
else:
logger.debug(&"Mounted {entry.source} at {entry.target}")
except ValueError: # Check parseFileSystemTable for more info on this catch block
logger.fatal("Improperly formatted fstab, exiting")
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 checkDisksIsMounted(entry):
logger.debug(&"Skipping mounting filesystem {entry.source} ({entry.filesystemtype}) at {entry.target}: already mounted")
continue
logger.debug(&"Mounting filesystem {entry.source} ({entry.filesystemtype}) at {entry.target} with mount option(s) {entry.data}")
logger.trace(&"Calling mount('{entry.source}', '{entry.target}', '{entry.filesystemtype}', {entry.mountflags}, '{entry.data}')")
retcode = mount(entry.source, entry.target, entry.filesystemtype, entry.mountflags, entry.data)
logger.trace(&"mount('{entry.source}', '{entry.target}', '{entry.filesystemtype}', {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) =
## Unmounts all currently mounted disks, including the ones that
## were not mounted trough fstab but excluding virtual filesystems
var flag: bool = false
var retcode = 0
try:
logger.info("Detaching real filesystems")
logger.debug(&"Reading disk entries from /proc/mounts")
for entry in parseFileSystemTable(readFile("/proc/mounts")):
if entry.source in ["proc", "sys", "run", "dev", "devpts", "shm"]:
flag = true # We don't detach the vfs
for path in ["/proc", "/sys", "/run", "/dev", "/dev/pts", "/dev/shm"]:
if entry.target.startswith(path):
flag = true
if flag:
flag = false
logger.debug(&"Skipping unmounting filesystem {entry.source} ({entry.filesystemtype}) from {entry.target} as it is a virtual filesystem")
continue
if not checkDisksIsMounted(entry):
logger.debug(&"Skipping unmounting filesystem {entry.source} ({entry.filesystemtype}) from {entry.target}: not mounted")
continue
logger.debug(&"Unmounting filesystem {entry.source} ({entry.filesystemtype}) from {entry.target}")
logger.trace(&"Calling umount2('{entry.source}', MNT_DETACH)")
retcode = umount2(entry.source, 2) # 2 = MNT_DETACH - Since we're shutting down, we need the disks to be *gone*!
logger.trace(&"umount2('{entry.source}', MNT_DETACH) returned {retcode}")
if retcode == -1:
logger.error(&"Unmounting disk {entry.source} from {entry.target} has failed with error {posix.errno}: {posix.strerror(posix.errno)}")
# Resets the error code
posix.errno = cint(0)
else:
logger.debug(&"Unmounted {entry.source} from {entry.target}")
except ValueError: # Check parseFileSystemTable for more info on this catch block
logger.fatal(&"A fatal error occurred while unmounting disks: {getCurrentExceptionMsg()}")
nimDExit(logger, 131)

View File

@ -15,40 +15,41 @@
## Default signal handlers, exit procedures and helpers ## Default signal handlers, exit procedures and helpers
## to allow a clean shutdown of NimD ## to allow a clean shutdown of NimD
import os import os
import strformat
import strutils import strutils
import syscall import syscall
import strformat
import posix
import logging import logging
# Note: This will work reliably only with absolute paths. Use with care
const symlinks: array[7, tuple[dest, source: string]] = [
(dest: "/dev/fd", source: "/proc/self/fd"),
(dest: "/dev/fd/0", source: "/proc/self/fd/0"),
(dest: "/dev/fd/1", source: "/proc/self/fd/1"),
(dest: "/dev/fd/2", source: "/proc/self/fd/2"),
(dest: "/dev/std/in", source: "/proc/self/fd/0"),
(dest: "/dev/std/out", source: "/proc/self/fd/1"),
(dest: "/dev/std/err", source: "/proc/self/fd/2"),
]
proc sleepSeconds*(amount: SomeNumber) = sleep(int(amount * 1000))
var shutdownHandlers: seq[proc (logger: Logger, code: int)] = @[] proc strsignal*(sig: cint): cstring {.header: "string.h", importc.}
proc doSync*(logger: Logger) = proc doSync*(logger: Logger) =
## Performs a sync() system call
logger.debug(&"Calling sync() syscall has returned {syscall(SYNC)}") logger.debug(&"Calling sync() syscall has returned {syscall(SYNC)}")
proc reapProcess*(logger: Logger) = proc reapProcess*(logger: Logger) =
## Reaps zombie processes. Note: This does not
## handle restarting crashed service processes,
## it simply makes sure that there's no dead
## process entries in the kernel's ptable.
## When (supervised) services are started,
## they are spawned by a controlling subprocess
## of PID 1 which listens for changes in them
## and restarts them as needed
logger.debug("Handling SIGCHLD") logger.debug("Handling SIGCHLD")
# TODO var status: cint
discard posix.waitPid(-1, status, WNOHANG) # This doesn't hang, which is what we want
proc exists*(p: string): bool = proc exists*(p: string): bool =
# Checks if a path exists. Thanks ## Returns true if a path exists,
# araq :) ## false otherwise
try: try:
discard getFileInfo(p) discard getFileInfo(p)
result = true result = true
@ -56,42 +57,6 @@ proc exists*(p: string): bool =
result = false result = false
proc addShutdownHandler*(handler: proc (logger: Logger, code: int), logger: Logger) =
shutdownHandlers.add(handler)
proc removeShutdownHandler*(handler: proc (logger: Logger, code: int)) =
for i, h in shutdownHandlers:
if h == handler:
shutdownHandlers.delete(i)
proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
logger.warning("The system is shutting down")
# TODO
logger.info("Processing shutdown runlevel")
# TODO
logger.info("Running shutdown handlers")
try:
for handler in shutdownHandlers:
handler(logger, code)
except:
logger.error(&"An error has occurred while calling shutdown handlers. Error -> {getCurrentExceptionMsg()}")
# Note: continues calling handlers!
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")
discard execShellCmd("/bin/sh") # TODO: Is this fine? maybe use execProcess
else:
logger.info("Terminating child processes with SIGINT")
# TODO
logger.info("Terminating child processes with SIGKILL")
# TODO
logger.warning("Shutdown procedure complete, sending final termination signal")
# TODO
quit(code)
proc setHostname*(logger: Logger): string = proc setHostname*(logger: Logger): string =
## Sets the machine's hostname. Returns ## Sets the machine's hostname. Returns
## the hostname that has been set or an ## the hostname that has been set or an
@ -110,33 +75,3 @@ proc setHostname*(logger: Logger): string =
logger.error(&"An error occurred while setting hostname -> {getCurrentExceptionMsg()}") logger.error(&"An error occurred while setting hostname -> {getCurrentExceptionMsg()}")
return "" return ""
return hostname return hostname
proc createSymlinks*(logger: Logger) =
## Creates a set of symlinks needed
## by stuff like Linux ports of BSD
## software
for sym in symlinks:
try:
if not exists(sym.source):
logger.warning(&"Skipping creation of symbolic link from {sym.dest} to {sym.source}: destination does not exist")
continue
elif exists(sym.dest):
if symlinkExists(sym.dest) and sameFile(expandSymlink(sym.dest), sym.source):
logger.debug(&"Skipping creation of symbolic link from {sym.dest} to {sym.source}: link already exists")
elif symlinkExists(sym.dest) and not sameFile(expandSymlink(sym.dest), sym.source):
logger.warning(&"Attempted to create symbolic link from {sym.dest} to {sym.source}, but link already exists and points to {expandSymlink(sym.dest)}")
continue
logger.debug(&"Creating symbolic link from {sym.source} to {sym.dest}")
createSymlink(sym.dest, sym.source)
except:
logger.error(&"Failed to create symbolic link from {sym.dest} to {sym.source}: {getCurrentExceptionMsg()}")
proc createDirectories*(logger: Logger) =
## Creates
proc sleepSeconds*(amount: SomeInteger) = sleep(amount * 1000)
proc strsignal*(sig: cint): cstring {.header: "string.h", importc.}