Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
madonuko committed May 14, 2024
0 parents commit fef30fe
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dive
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Madonuko, Ultramarine Project, Fyra Labs, Et al.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# dive

A chroot utility (just like `arch-chroot`).
15 changes: 15 additions & 0 deletions dive.nimble
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Package

version = "0.1.0"
author = "madomado"
description = "A chroot utility"
license = "MIT"
srcDir = "src"
bin = @["dive"]


# Dependencies

requires "nim >= 2.1.1"
requires "cligen"
requires "sweet"
96 changes: 96 additions & 0 deletions src/dive.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import std/[strutils, strformat, paths, os, dirs, files, osproc]
import asyncdispatch
import cligen, sweet
import log, mounts

let mounteds = getMounts()

proc is_mountpoint(root: Path): bool =
for mount in mounteds:
if root == mount.mountpoint.Path:
return true

proc mkdir(dir: Path) =
try: dir.createDir
except OSError:
error "Cannot create dir: " & dir.string
quit 1

proc run(cmd: string) {.async.} =
info "Running command: "&cmd
let p = startProcess("sh", args=["-c", cmd], options={poParentStreams})
while p.running:
await sleepAsync 10
let rc = p.waitForExit
if !!rc:
fatal "Fail to execute command: "&cmd
fatal "Command returned exit code: " & $rc
quit 1

proc force_mountpoint(root: Path) {.async.} =
if root.is_mountpoint: return
warn fmt"{root.string} is not a mountpoint, bind-mounting…"
await run fmt"mount --bind {root.string.quoteShell} {root.string.quoteShell}"

proc mount(path: Path, mountargs: string) {.async.} =
if !path.is_mountpoint:
mkdir path
await run "mount "&mountargs&" "&path.string

proc mount_dirs(root: Path) {.async.} =
await mount(root/"proc".Path, "-t proc proc") and
mount(root/"sys".Path, "-t sysfs sys") and
mount(root/"dev".Path, "-o bind /dev") and
mount(root/"dev/pts".Path, "-o bind /dev/pts")

proc cp_resolv(root: Path) {.async.} =
if !"/etc/resolv.conf".Path.fileExists:
warn "/etc/resolv.conf does not exist"
return
let dest = root/"etc/resolv.conf".Path
if !dest.fileExists:
warn "Refusing to copy resolv.conf because it doesn't exist inside chroot"
return
await run "mount -c --bind /etc/resolv.conf "&dest.string

proc umount(path: string) {.async.} =
try: await run fmt"umount {path}"
finally: discard

proc umount_all(root: string) {.async.} =
await umount(fmt"{root}/proc") and
umount(fmt"{root}/sys") and
umount(fmt"{root}/dev {root}/dev/pts") and
umount(fmt"{root}/etc/resolv.conf")

proc find_shell(root: Path): string =
if fileExists root/"bin/fish".Path:
return "/bin/fish"
if fileExists root/"bin/zsh".Path:
return "/bin/zsh"
if fileExists root/"bin/bash".Path:
return "/bin/bash"
if fileExists root/"bin/sh".Path:
return "/bin/sh"
warn "Cannot detect any shell in the chroot… falling back to /bin/sh"
"/bin/sh"

proc dive(args: seq[string], verbosity = lvlNotice, keepresolv = false) =
## A chroot utility
if !args.len:
fatal "You must provide an argument for the root directory"
quit 1
let root = args[0].Path
let cp_resolv_fut = cp_resolv root
waitFor (force_mountpoint root) and (mount_dirs root) and cp_resolv_fut
waitFor cp_resolv_fut
let shell = find_shell root
let str_args = args[1..^1].join(" ")
waitFor run fmt"SHELL={shell} chroot {root.string} {str_args}"
waitFor umount_all(root.string)


dispatch dive, help = {
"args": "<root directory> [shell command]",
"verbosity": "set the logging verbosity: {lvlAll, lvlDebug, lvlInfo, lvlNotice, lvlWarn, lvlError, lvlFatal, lvlNone}",
}, short = {"keepresolv": 'r'}
6 changes: 6 additions & 0 deletions src/log.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import std/logging

var logger = newConsoleLogger(fmtStr="mkfstab: $levelname: ", useStderr=true)
addHandler logger

export debug, error, fatal, info, log, notice, warn, logging
25 changes: 25 additions & 0 deletions src/mounts.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import std/[streams, strformat, strscans]
import log

type Mount* = tuple[device: string, mountpoint: string, fstype: string, mountopts: string]

proc notSpace(input: string, match: var string, start: int): int =
result = 0
while input[start+result] != ' ': inc result
match = input[start..start+result-1]


proc getMounts*(): seq[Mount] =
var fdmounts = newFileStream("/proc/mounts")
if fdmounts == nil:
# cannot open file
return @[]
defer: fdmounts.close()
var linenum = 0
var line, dev, mp, fstype, mountopts: string
while fdmounts.readLine line:
inc linenum
if scanf(line, "${notSpace} ${notSpace} ${notSpace} ${notSpace} 0 0$.", dev, mp, fstype, mountopts):
result.add (device: dev, mountpoint: mp, fstype: fstype, mountopts: mountopts)
else:
error fmt"Fail to parse /proc/mounts:{linenum} : {line}"

0 comments on commit fef30fe

Please sign in to comment.