mirror of https://github.com/nocturn9x/nimd.git
Initial ground work on the init
This commit is contained in:
parent
d242a72a89
commit
c6f6fcb54e
|
@ -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"]
|
96
src/main.nim
96
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?
|
||||
# but, after all, there isn't much we can do if we can't even initialize *ourselves* is there?
|
|
@ -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)"""
|
|
@ -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()
|
|
@ -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.}
|
Loading…
Reference in New Issue