Added more entries to gitignore

This commit is contained in:
nocturn9x 2021-12-05 20:53:44 +01:00
parent 539035f3a5
commit 3f3403170f
7 changed files with 157 additions and 79 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ packervm
test test
initramfs-linux.img initramfs-linux.img
vmlinuz-linux vmlinuz-linux
debian*

View File

@ -350,9 +350,44 @@ proc createSymlinks*(logger: Logger) =
createDir(sym.dest.splitPath().head) createDir(sym.dest.splitPath().head)
createSymlink(sym.source, sym.dest) createSymlink(sym.source, sym.dest)
except: except:
logger.warning(&"Failed to create symbolic link from {sym.dest} to {sym.source}: {getCurrentExceptionMsg()}") logger.error(&"Failed to create symbolic link from {sym.dest} to {sym.source}: {getCurrentExceptionMsg()}")
proc createDirectories*(logger: Logger) = proc createDirectories*(logger: Logger) =
## Creates standard directories that ## Creates standard directories that
## Linux software expects to be present. ## Linux software expects to be present.
## Note that this has to run after the
## filesystem has been initialized.
## If a chmod binary is found, it is used
## to set directory permissions as specified
## in their config. Note that the entire path
## of the directory is created if it does not
## exist yet
var hasChmod = false
try:
if findExe("chmod").isEmptyOrWhitespace():
logger.warning("Could not find chmod binary, directory permissions will default to OS configuration")
hasChmod = true
except:
logger.error(&"Failed to search for chmod binary: {getCurrentExceptionMsg()}")
for dir in directories:
try:
if exists(dir.path):
if dirExists(dir.path):
logger.warning(&"Creation of directory {dir.path} skipped: directory already exists")
elif fileExists(dir.path):
logger.warning(&"Creation of directory {dir.path} skipped: path is a file")
elif symlinkExists(dir.path):
logger.warning(&"Creation of directory {dir.path} skipped: path is a symlink to {expandSymlink(dir.path)}")
else:
# Catch-all
logger.warning(&"Creation of directory {dir.path} skipped: destination already exists")
else:
createDir(dir.path)
logger.debug(&"Created new directory at {dir.path}")
if hasChmod:
logger.debug(&"Setting permissions to {dir.permissions} for {dir.path}")
if (let code = execShellCmd(&"chmod -R {dir.permissions} {dir.path}"); code) != 0:
logger.warning(&"Command 'chmod -R {dir.permissions}' exited non-zero status code {code}")
except:
logger.error(&"Failed to create directory at {dir.path}: {getCurrentExceptionMsg()}")

View File

@ -17,10 +17,13 @@ import tables
import osproc import osproc
import posix import posix
import shlex import shlex
import os
proc strsignal(sig: cint): cstring {.header: "string.h", importc.}
import ../util/logging import ../util/logging
import ../util/misc
type type
@ -31,6 +34,9 @@ type
## Enumerates all service ## Enumerates all service
## types ## types
Oneshot, Simple Oneshot, Simple
RestartKind* = enum
## Enum of possible restart modes
Always, OnFailure, Never
Service* = ref object of RootObj Service* = ref object of RootObj
## A service object ## A service object
name: string name: string
@ -40,14 +46,14 @@ type
runlevel: RunLevel runlevel: RunLevel
exec: string exec: string
supervised: bool supervised: bool
restartOnFailure: bool restart: RestartKind
restartDelay: int restartDelay: int
proc newService*(name, description: string, kind: ServiceKind, workDir: string, runlevel: RunLevel, exec: string, supervised, restartOnFailure: bool, restartDelay: int): Service = proc newService*(name, description: string, kind: ServiceKind, workDir: string, runlevel: RunLevel, exec: string, supervised: bool, restart: RestartKind, restartDelay: int): Service =
## Creates a new service object ## Creates a new service object
result = Service(name: name, description: description, kind: kind, workDir: workDir, runLevel: runLevel, result = Service(name: name, description: description, kind: kind, workDir: workDir, runLevel: runLevel,
exec: exec, supervised: supervised, restartOnFailure: restartOnFailure, restartDelay: restartDelay) exec: exec, supervised: supervised, restart: restart, restartDelay: restartDelay)
var services: seq[Service] = @[] var services: seq[Service] = @[]
@ -114,25 +120,42 @@ proc supervisorWorker(logger: Logger, service: Service, pid: int) =
sig = WTERMSIG(status) sig = WTERMSIG(status)
else: else:
sig = -1 sig = -1
if sig > 0 and service.restartOnFailure: case service.restart:
logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it") of Never:
removeManagedProcess(pid) logger.info(&"Service '{service.name}' ({returnCode}) has exited, shutting down controlling process")
sleepSeconds(service.restartDelay)
var split = shlex(service.exec)
if split.error:
logger.error(&"Error while restarting service '{service.name}': invalid exec syntax")
break break
var arguments = split.words of Always:
let progName = arguments[0] if sig > 0:
arguments = arguments[1..^1] logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it")
process = startProcess(progName, workingDir=service.workDir, args=arguments) elif sig == 0:
pid = process.processID() logger.info(&"Service '{service.name}' has exited gracefully, sleeping {service.restartDelay} seconds before restarting it")
elif sig > 0: else:
logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), shutting down controlling process") logger.info(&"Service '{service.name}' has exited, sleeping {service.restartDelay} seconds before restarting it")
break removeManagedProcess(pid)
else: sleep(service.restartDelay * 1000)
logger.info(&"Service '{service.name}' ({returnCode}) has exited, shutting down controlling process") var split = shlex(service.exec)
break if split.error:
logger.error(&"Error while restarting service '{service.name}': invalid exec syntax")
break
var arguments = split.words
let progName = arguments[0]
arguments = arguments[1..^1]
process = startProcess(progName, workingDir=service.workDir, args=arguments)
pid = process.processID()
of OnFailure:
if sig > 0:
logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it")
removeManagedProcess(pid)
sleep(service.restartDelay * 1000)
var split = shlex(service.exec)
if split.error:
logger.error(&"Error while restarting service '{service.name}': invalid exec syntax")
break
var arguments = split.words
let progName = arguments[0]
arguments = arguments[1..^1]
process = startProcess(progName, workingDir=service.workDir, args=arguments)
pid = process.processID()
if process != nil: if process != nil:
process.close() process.close()
@ -140,7 +163,8 @@ proc supervisorWorker(logger: Logger, service: Service, pid: int) =
proc startService(logger: Logger, service: Service) = 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) ## been started). This function is supposed to
## be called from a forked process!
var process: Process var process: Process
try: try:
var split = shlex(service.exec) var split = shlex(service.exec)
@ -152,7 +176,9 @@ proc startService(logger: Logger, service: Service) =
arguments = arguments[1..^1] arguments = arguments[1..^1]
process = startProcess(progName, workingDir=service.workDir, args=arguments) process = startProcess(progName, workingDir=service.workDir, args=arguments)
if service.supervised: if service.supervised:
supervisorWorker(logger, service, process.processID) var pid = posix.fork()
if pid == 0:
supervisorWorker(logger, service, process.processID)
# If the service is unsupervised we just exit # If the service is unsupervised we just exit
except: except:
logger.error(&"Error while starting service {service.name}: {getCurrentExceptionMsg()}") logger.error(&"Error while starting service {service.name}: {getCurrentExceptionMsg()}")

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 os import os
import osproc
import posix import posix
import glob import glob
import strutils import strutils
@ -20,6 +21,7 @@ import times
import ../util/logging import ../util/logging
import services
type ShutdownHandler* = ref object type ShutdownHandler* = ref object
@ -79,17 +81,15 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
## 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
if emerg: if emerg:
var status: cint
# 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 posix.kill(SIGKILL, -1) discard execCmd("/bin/sh") # TODO: Is this fine? maybe use execProcess
discard posix.waitPid(-1, status, 0) discard posix.kill(-1, SIGKILL)
discard execShellCmd("/bin/sh") # TODO: Is this fine? maybe use execProcess
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")
# TODO startServices(logger, Shutdown)
logger.info("Running shutdown handlers") logger.info("Running shutdown handlers")
try: try:
for handler in shutdownHandlers: for handler in shutdownHandlers:
@ -99,13 +99,13 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
# Note: continues calling handlers! # Note: continues calling handlers!
logger.info("Terminating child processes with SIGTERM") logger.info("Terminating child processes with SIGTERM")
logger.debug(&"Waiting up to {sigTermDelay} seconds for the kernel to deliver signals") logger.debug(&"Waiting up to {sigTermDelay} seconds for the kernel to deliver signals")
discard posix.kill(SIGTERM, -1) # The kernel handles this for us asynchronously discard posix.kill(-1, SIGTERM) # The kernel handles this for us asynchronously
var t = cpuTime() var t = cpuTime()
# We wait some time for the signals to propagate # We wait some time for the signals to propagate
while anyUserlandProcessLeft() or cpuTime() - t >= sigTermDelay: while anyUserlandProcessLeft() or cpuTime() - t >= sigTermDelay:
sleep(int(0.25 * 1000)) sleep(int(0.25 * 1000))
if anyUserlandProcessLeft(): if anyUserlandProcessLeft():
logger.info("Terminating child processes with SIGKILL") logger.info("Terminating child processes with SIGKILL")
discard posix.kill(SIGKILL, -1) discard posix.kill(-1, SIGKILL)
logger.warning("Shutdown procedure complete, sending final termination signal") logger.warning("Shutdown procedure complete, sending final termination signal")
quit(code) quit(code)

View File

@ -21,6 +21,50 @@ import util/[logging, constants, misc]
import core/[mainloop, fs, shutdown, services] import core/[mainloop, fs, shutdown, services]
proc addStuff =
## Adds stuff to test NimD. This is
## a temporary procedure
# Adds symlinks
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))
addDirectory(newDirectory("test", 777)) # Should create a directory
addDirectory(newDirectory("/dev/disk", 123)) # Should say directory already exists
addDirectory(newDirectory("/dev/test/owo", 000)) # Should say path does not exist
# Shutdown handler to unmount disks
addShutdownHandler(newShutdownHandler(unmountAllDisks))
# Adds test services
addService(newService(name="echoer", description="prints owo", exec="/bin/echo owo",
runlevel=Boot, kind=Oneshot, workDir=getCurrentDir(),
supervised=false, restart=Never, restartDelay=0))
addService(newService(name="errorer", description="la mamma di gavd",
exec="/bin/false", supervised=true, restart=OnFailure,
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple))
addService(newService(name="exiter", description="la mamma di licenziat",
exec="/bin/true", supervised=true, restart=Always,
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple))
addService(newService(name="sleeper", description="la mamma di danieloz",
exec="/usr/bin/sleep", supervised=true, restart=Always,
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple))
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
@ -53,26 +97,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab")
onSignal(SIGINT): onSignal(SIGINT):
# Temporary # Temporary
nimDExit(getDefaultLogger(), 131, emerg=true) nimDExit(getDefaultLogger(), 131, emerg=true)
addSymlink(newSymlink(dest="/dev/fd", source="/proc/self/fd")) addStuff()
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: try:
if mountDisks: if mountDisks:
logger.info("Mounting filesystem") logger.info("Mounting filesystem")
@ -82,31 +107,22 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab")
mountRealDisks(logger, fstab) 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")
createSymlinks(logger)
logger.info("Creating directories")
createDirectories(logger)
logger.info("Filesystem preparation complete")
logger.debug("Calling sync() just in case")
doSync(logger)
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)
logger.info("Disks mounted")
logger.debug("Calling sync() just in case")
doSync(logger)
logger.info("Setting hostname") logger.info("Setting hostname")
logger.debug(&"Hostname was set to '{setHostname(logger)}'") logger.debug(&"Hostname was set to '{setHostname(logger)}'")
logger.info("Creating symlinks")
createSymlinks(logger)
logger.info("Creating directories")
createDirectories(logger)
logger.debug("Entering critical fork() section: blocking signals") logger.debug("Entering critical fork() section: blocking signals")
blockSignals(logger) blockSignals(logger) # They are later unblocked in mainLoop
logger.info("Processing boot runlevel") logger.info("Processing boot runlevel")
addService(newService(name="echoer", description="prints owo", exec="/bin/echo owo", startServices(logger, workers=1, level=Boot)
runlevel=Boot, kind=Oneshot, workDir=getCurrentDir(),
supervised=false, restartOnFailure=false, restartDelay=0))
addService(newService(name="sleeper", description="la mamma di licenziato",
exec="/usr/bin/sleep 10", supervised=true, restartOnFailure=true,
restartDelay=5, runlevel=Boot, workDir="/home", kind=Simple))
addService(newService(name="errorer", description="la mamma di gavd",
exec="/bin/false", supervised=true, restartOnFailure=true,
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple))
startServices(logger, workers=2, level=Boot)
logger.debug("Starting main loop") logger.debug("Starting main loop")
mainLoop(logger) mainLoop(logger)
@ -117,7 +133,8 @@ when isMainModule:
for kind, key, value in optParser.getopt(): for kind, key, value in optParser.getopt():
case kind: case kind:
of cmdArgument: of cmdArgument:
discard echo "Error: unexpected argument"
quit(EINVAL)
of cmdLongOption: of cmdLongOption:
case key: case key:
of "help": of "help":
@ -131,7 +148,7 @@ when isMainModule:
of "extra": of "extra":
logger.setLevel(LogLevel.Trace) logger.setLevel(LogLevel.Trace)
else: else:
logger.error(&"Unkown command-line long option '{key}'") echo &"Unkown command-line long option '{key}'"
quit(EINVAL) # EINVAL - Invalid argument quit(EINVAL) # EINVAL - Invalid argument
of cmdShortOption: of cmdShortOption:
case key: case key:
@ -146,7 +163,9 @@ when isMainModule:
of "X": of "X":
logger.setLevel(LogLevel.Trace) logger.setLevel(LogLevel.Trace)
else: else:
logger.error(&"Unkown command-line short option '{key}'") echo &"Unkown command-line short option '{key}'"
echo "Usage: nimd [options]"
echo "Try nimd --help for more info"
quit(EINVAL) # EINVAL - Invalid argument quit(EINVAL) # EINVAL - Invalid argument
else: else:
echo "Usage: nimd [options]" echo "Usage: nimd [options]"
@ -158,4 +177,4 @@ when isMainModule:
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!",
# but, after all, there isn't much we can do if we can't even initialize *ourselves* is there? # but, after all, there isn't much we can do if we can't even initialize *ourselves* is there?

View File

@ -75,7 +75,7 @@ proc getDefaultLogger*(): Logger =
## standard error with some basic info like the ## standard error with some basic info like the
## current date and time and the log level ## current date and time and the log level
setStdIoUnbuffered() # Just in case setStdIoUnbuffered() # Doesn't work otherwise!
proc logTrace(self: LogHandler, logger: Logger, message: string) = proc logTrace(self: LogHandler, logger: Logger, message: string) =
setForegroundColor(fgMagenta) setForegroundColor(fgMagenta)

View File

@ -1,3 +0,0 @@
#!/bin/bash
docker build -t nimd:latest .
docker run --rm -it nimd