diff --git a/src/core/mainloop.nim b/src/core/mainloop.nim index c58115b..5a60237 100644 --- a/src/core/mainloop.nim +++ b/src/core/mainloop.nim @@ -35,6 +35,8 @@ proc mainLoop*(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fs logger.fatal(&"A fatal error has occurred while preparing filesystem, booting cannot continue. Error -> {getCurrentExceptionMsg()}") nimDExit(logger, 131) logger.info("Disks mounted") + logger.info("Setting hostname") + logger.debug(&"Hostname was set to '{setHostname(logger)}'") logger.info("Processing boot runlevel") # TODO logger.info("Processing default runlevel") @@ -45,8 +47,8 @@ proc mainLoop*(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fs # TODO sleepSeconds(5) except CtrlCException: - logger.warning("Main process received SIGINT: exiting") - nimDExit(logger, 130) # 130 - Interrupted by SIGINT + logger.warning("Main process received SIGINT: exiting") # TODO: Ignore this once we stop testing on our local machines lol + nimDExit(logger, 130, emerg=false) # 130 - Interrupted by SIGINT except: logger.critical(&"A critical error has occurred while running, restarting the mainloop! Error -> {getCurrentExceptionMsg()}") # We *absolutely* cannot die diff --git a/src/main.nim b/src/main.nim index ed470de..017fb4d 100644 --- a/src/main.nim +++ b/src/main.nim @@ -36,6 +36,12 @@ proc main(logger: Logger) = if uid != 0: logger.fatal(&"NimD must run as root, but current user id is {uid}") quit(EPERM) # EPERM - Operation not permitted + logger.debug("Setting up dummy signal handlers") + onSignal(SIGABRT, SIGALRM, SIGHUP, SIGILL, SIGKILL, SIGQUIT, SIGSTOP, SIGSEGV, + SIGTRAP, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, 6, SIGFPE, SIGBUS, SIGURG, SIGINT): # 6 is SIGIOT + # Can't capture local variables because this implicitly generates + # a noconv procedure + getDefaultLogger().warning(&"Ignoring signal {sig} ({strsignal(sig)})") # Nim injects the variable "sig" into the scope. Gotta love those macros logger.debug("Starting uninterruptible mainloop") mainLoop(logger) diff --git a/src/util/disks.nim b/src/util/disks.nim index e7380fc..b33b6ea 100644 --- a/src/util/disks.nim +++ b/src/util/disks.nim @@ -145,6 +145,7 @@ proc checkDisksIsMounted(search: tuple[source, target, filesystemtype: string, m 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)): @@ -153,7 +154,7 @@ proc mountRealDisks*(logger: Logger, fstab: string = "/etc/fstab") = 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}')") - var retcode = 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)}") @@ -169,13 +170,15 @@ proc mountRealDisks*(logger: Logger, fstab: string = "/etc/fstab") = 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}')") - var retcode = 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)}") @@ -189,26 +192,28 @@ proc mountVirtualDisks*(logger: Logger) = proc unmountAllDisks*(logger: Logger, code: int) = ## Unmounts all currently mounted disks, including the ones that - ## were not mounted trough fstab and virtual filesystems + ## 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")): - flag = false if entry.source in ["proc", "sys", "run", "dev", "devpts", "shm"]: - continue # We cannot detach the vfs just yet, we'll do it later + 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.target}', MNT_DETACH)") - var retcode = umount2(entry.target, 2) # MNT_DETACH - Since we're shutting down, we need the disks to be *gone*! + # var retcode = umount2(entry.target, 2) # 2 = MNT_DETACH - Since we're shutting down, we need the disks to be *gone*! logger.trace(&"umount2('{entry.target}', 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)}") @@ -216,21 +221,6 @@ proc unmountAllDisks*(logger: Logger, code: int) = posix.errno = cint(0) else: logger.debug(&"Unmounted {entry.source} from {entry.target}") - logger.info("Detaching virtual filesystems") - for entry in virtualFileSystems: - 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.target}', MNT_DETACH)") - var retcode = umount2(entry.target, 2) # MNT_DETACH - Since we're shutting down, we need the disks to be *gone*! - logger.trace(&"umount('{entry.target}') 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("Improperly formatted /etc/mtab, exiting") + logger.fatal(&"A fatal error occurred while unmounting disks: {getCurrentExceptionMsg()}") nimDExit(logger, 131) diff --git a/src/util/logging.nim b/src/util/logging.nim index 45ee54a..1341aa8 100644 --- a/src/util/logging.nim +++ b/src/util/logging.nim @@ -13,6 +13,7 @@ # limitations under the License. # A simple logging module inspired by python's own logging facility +import terminal import strformat import times @@ -72,28 +73,45 @@ proc getDefaultLogger*(): Logger = ## level that writes the given message to the ## standard error with some basic info like the ## current date and time and the log level + + setStdIoUnbuffered() # Just in case proc logTrace(self: LogHandler, logger: Logger, message: string) = + setForegroundColor(fgMagenta) stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - TRACE] {message}""") + setForegroundColor(fgDefault) proc logDebug(self: LogHandler, logger: Logger, message: string) = + setForegroundColor(fgCyan) stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - DEBUG] {message}""") - + setForegroundColor(fgDefault) + proc logInfo(self: LogHandler, logger: Logger, message: string) = + setForegroundColor(fgGreen) stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - INFO] {message}""") + setForegroundColor(fgDefault) proc logWarning(self: LogHandler, logger: Logger, message: string) = + setForegroundColor(fgYellow) stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - WARNING] {message}""") + setForegroundColor(fgDefault) proc logError(self: LogHandler, logger: Logger, message: string) = + setForegroundColor(fgRed) stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - ERROR] {message}""") + setForegroundColor(fgDefault) proc logCritical(self: LogHandler, logger: Logger, message: string) = + setForegroundColor(fgRed) stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - CRITICAL] {message}""") + setForegroundColor(fgDefault) proc logFatal(self: LogHandler, logger: Logger, message: string) = + setForegroundColor(fgBlack) + setBackgroundColor(bgRed) stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss")} - FATAL] {message}""") - + setForegroundColor(fgDefault) + setBackgroundColor(bgDefault) result = newLogger() result.addHandler(createHandler(logTrace, LogLevel.Trace)) diff --git a/src/util/misc.nim b/src/util/misc.nim index cb4d876..617e3a2 100644 --- a/src/util/misc.nim +++ b/src/util/misc.nim @@ -16,6 +16,7 @@ ## to allow a clean shutdown of NimD import os import strformat +import strutils import logging @@ -36,8 +37,8 @@ proc removeShutdownHandler*(handler: proc (logger: Logger, code: int)) = shutdownHandlers.delete(i) -proc nimDExit*(logger: Logger, code: int) = - logger.warning("The system is being shut down!") +proc nimDExit*(logger: Logger, code: int, emerg: bool = true) = + logger.warning("The system is shutting down") # TODO logger.info("Processing shutdown runlevel") # TODO @@ -48,13 +49,38 @@ proc nimDExit*(logger: Logger, code: int) = except: logger.error(&"An error has occurred while calling shutdown handlers. Error -> {getCurrentExceptionMsg()}") # Note: continues calling handlers! - 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) # Replace with syscall(REBOOT, ...) + 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 = + ## Sets the machine's hostname. Returns + ## the hostname that has been set or an + ## empty string if an error occurs. If + ## /etc/hostname doesn't exist, the hostname + ## defaults to localhost + var hostname: string + try: + if not fileExists("/etc/hostname"): + logger.warning("/etc/hostname doesn't exist, defaulting to 'localhost'") + hostname = "localhost" + else: + hostname = readFile("/etc/hostname").strip(chars={'\n'}) + writeFile("/proc/sys/kernel/hostname", hostname) + except: + logger.error(&"An error occurred while setting hostname -> {getCurrentExceptionMsg()}") + return "" + return hostname proc sleepSeconds*(amount: SomeInteger) = sleep(amount * 1000) @@ -62,3 +88,6 @@ proc sleepSeconds*(amount: SomeInteger) = sleep(amount * 1000) proc handleControlC* {.noconv.} = raise newException(CtrlCException, "Interrupted by Ctrl+C") + + +proc strsignal*(sig: cint): cstring {.header:"string.h", importc.}