diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..28b3ea8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM nimlang/nim AS builder + +COPY . /code +WORKDIR /code + +RUN nim c -o:nimd --passL:"-static" src/main.nim +RUN cp /code/nimd /sbin/nimd + +FROM alpine:latest + +COPY --from=builder /code/nimd /sbin/nimd +# ENTRYPOINT ["/bin/sh", "-l"] +ENTRYPOINT [ "/sbin/nimd", "--extra"] diff --git a/src/main.nim b/src/main.nim index 5c0a3c2..21f9730 100644 --- a/src/main.nim +++ b/src/main.nim @@ -11,33 +11,111 @@ # 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 parseopt import strformat import posix import os -import util/logger as log +import util/[logging, constants, mount] -const NimdVersion*: tuple[major, minor, patch: int] = (major: 0, minor: 0, patch: 1) +proc sleepSeconds(amount: SomeInteger) = sleep(amount * 1000) + + +proc handleControlC {.noconv.} = + getDefaultLogger().warning("Main process received SIGINT: exiting") # TODO: Call exit point + quit(0) + + +proc mainLoop(logger: Logger) = + ## NimD's main execution loop + try: + logger.info("Reading disk entries from /etc/fstab") + for entry in parseFileSystemTable(readFile("/etc/fstab")): + logger.debug(&"Mounting filesystem {entry.source} of type {entry.filesystemtype} at {entry.target} with mountflags {entry.mountflags} and mount options {entry.data}") + logger.trace("Calling mount()") + var retcode = mount(entry.source, entry.target, entry.filesystemtype, entry.mountflags, entry.data) + logger.trace(&"mount() returned {retcode}") + if retcode == -1: + logger.warning(&"Mounting disk {entry.source} has failed with error {posix.errno}: {posix.strerror(posix.errno)}") + else: + logger.debug(&"Mounted {entry.source} at {entry.target}") + posix.errno = cint(0) + except IndexDefect: + logger.fatal("Improperly formatted /etc/fstab, exiting") + quit(131) + logger.info("Disks mounted") + while true: + logger.info("NimD is running") + sleepSeconds(5) proc main(logger: Logger) = ## NimD entry point + 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...") - if posix.getuid() != 0: - logger.error("NimD must run as root") - quit(1) # EPERM - Operation not permitted - if (let pid = getCurrentProcessId(); pid) != 1: + logger.trace("Calling getCurrentProcessId()") + let pid = getCurrentProcessId() + logger.trace(&"getCurrentProcessId() returned {pid}") + if pid != 1: logger.warning(&"Expecting to run as PID 1, but current process ID is {pid}") - + logger.trace("Calling getuid()") + let uid = posix.getuid() + logger.trace(&"getuid() returned {uid}") + if uid != 0: + logger.fatal(&"NimD must run as root, but current user id is {uid}") + quit(EPERM) # EPERM - Operation not permitted + logger.debug("Starting uninterruptible mainloop") + mainLoop(logger) + when isMainModule: + setControlCHook(handleControlC) var logger = getDefaultLogger() + var optParser = initOptParser(commandLineParams()) + for kind, key, value in optParser.getopt(): + case kind: + of cmdArgument: + discard + of cmdLongOption: + case key: + of "help": + echo helpMessage + quit(0) + of "version": + echo &"NimD version {NimdVersion.major}.{NimdVersion.minor}.{NimdVersion.patch} ({CompileDate}, {CompileTime}, {hostOS}, {hostCPU}) compiled with Nim {NimVersion}" + quit(0) + of "verbose": + logger.setLevel(LogLevel.Debug) + of "extra": + logger.setLevel(LogLevel.Trace) + else: + logger.error(&"Unkown command-line long option '{key}'") + quit(EINVAL) # EINVAL - Invalid argument + of cmdShortOption: + case key: + of "h": + echo helpMessage + quit(0) + of "v": + echo &"NimD version {NimdVersion.major}.{NimdVersion.minor}.{NimdVersion.patch} ({CompileDate}, {CompileTime}, {hostOS}, {hostCPU}) compiled with Nim {NimVersion}" + quit(0) + of "V": + logger.setLevel(LogLevel.Debug) + of "X": + logger.setLevel(LogLevel.Trace) + else: + logger.error(&"Unkown command-line short option '{key}'") + quit(EINVAL) # EINVAL - Invalid argument + else: + echo "Usage: nimd [options]" + quit(EINVAL) # EINVAL - Invalid argument + logger.debug("Calling NimD entry point") try: main(logger) except: - logger.fatal(&"A fatal exception has occurred during startup and NimD cannot continue: {getCurrentExceptionMsg()}") + logger.fatal(&"A fatal unrecoverable error has occurred during startup and NimD cannot continue: {getCurrentExceptionMsg()}") quit(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!", - # but there isn't much we can do if we can't even initialize *ourselves*, after all, is there? \ No newline at end of file + # but, after all, there isn't much we can do if we can't even initialize *ourselves* is there? \ No newline at end of file diff --git a/src/util/constants.nim b/src/util/constants.nim new file mode 100644 index 0000000..864d99f --- /dev/null +++ b/src/util/constants.nim @@ -0,0 +1,27 @@ +# 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. + +const NimdVersion*: tuple[major, minor, patch: int] = (major: 0, minor: 0, patch: 1) +const helpMessage* = """The NimD init system, Copyright (C) 2021 Mattia Giambirtone & All contributors + +This program is free software, see the license distributed with this program or check +http://www.apache.org/licenses/LICENSE-2.0 for more info. + + +Command-line options +-------------------- +-h, --help -> Shows this help text and exit +-v, --version -> Prints the NimD version number and exits +-V, --verbose -> Enables debug output +-X, --extra -> Enables extra verbose output (hint: you probably don't need it)""" \ No newline at end of file diff --git a/src/util/logger.nim b/src/util/logging.nim similarity index 78% rename from src/util/logger.nim rename to src/util/logging.nim index 4975254..45ee54a 100644 --- a/src/util/logger.nim +++ b/src/util/logging.nim @@ -14,6 +14,7 @@ # A simple logging module inspired by python's own logging facility import strformat +import times type @@ -61,7 +62,7 @@ proc removeHandler*(self: Logger, handler: LogHandler) = self.handlers.delete(se proc log(self: Logger, level: LogLevel = defaultLevel, message: string) = ## Generic utility for logging on any level for handler in self.handlers: - if handler.level == level: + if handler.level == level and self.level <= level: handler.code(handler, self, message) @@ -69,28 +70,29 @@ proc getDefaultLogger*(): Logger = ## Gets a simple logger with level set ## to LogLevel.Info and one handler per ## level that writes the given message to the - ## standard error + ## standard error with some basic info like the + ## current date and time and the log level proc logTrace(self: LogHandler, logger: Logger, message: string) = - stderr.write(&"[TRACE] {message}\n") + stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE] {message}""") proc logDebug(self: LogHandler, logger: Logger, message: string) = - stderr.write(&"[DEBUG] {message}\n") + stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - DEBUG] {message}""") proc logInfo(self: LogHandler, logger: Logger, message: string) = - stderr.write(&"[INFO] {message}\n") + stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - INFO] {message}""") proc logWarning(self: LogHandler, logger: Logger, message: string) = - stderr.write(&"[WARNING] {message}\n") + stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - WARNING] {message}""") proc logError(self: LogHandler, logger: Logger, message: string) = - stderr.write(&"[ERROR] {message}\n") + stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - ERROR] {message}""") proc logCritical(self: LogHandler, logger: Logger, message: string) = - stderr.write(&"[CRITICAL] {message}\n") + stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - CRITICAL] {message}""") proc logFatal(self: LogHandler, logger: Logger, message: string) = - stderr.write(&"[FATAL] {message}\n") + stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - FATAL] {message}""") result = newLogger() diff --git a/src/util/mount.nim b/src/util/mount.nim new file mode 100644 index 0000000..54f3bff --- /dev/null +++ b/src/util/mount.nim @@ -0,0 +1,40 @@ +# 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 + + +proc parseFileSystemTable*(fstab: string): seq[tuple[source: cstring, target: cstring, filesystemtype: cstring, mountflags: culong, data: cstring]] = + ## Parses the contents of the given file (the contents of /etc/fstab) + ## and returns a sequence of tuples with elements source, target, + ## filesystemtype, mountflags and data as required by mount in sys/mount.h + ## which is wrapped below. An improperly formatted fstab will cause this + ## function to error out with an IndexDefect exception (when an fstab 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. + var temp: seq[string] = @[] + var line: string + for l in fstab.splitlines(): + if l.strip().startswith("#"): + continue + if l.strip().len() == 0: + continue + line = l.filterIt(it != ' ').join("") + temp = line.split(maxsplit=6) + result.add((source: cstring(temp[0]), target: cstring(temp[1]), filesystemtype: cstring(temp[2]), mountflags: culong(0), data: cstring(temp[3]))) + + +proc mount*(source: cstring, target: cstring, filesystemtype: cstring, + mountflags: culong, data: pointer): cint {.header: "sys/mount.h", importc.} diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..bcff935 --- /dev/null +++ b/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash +docker build -t nimd:latest . +docker run --rm -it nimd \ No newline at end of file