fasttree-sys 0.2.0

Surowe bindingi FFI (extern "C") do biblioteki FastTree (Nim). Nie używaj bezpośrednio — patrz crate `fasttree`.
import std/[os, osproc, strformat, strutils]

type OverlayError* = object of CatchableError

type Overlay* = object
  lowerdir*: string
  upperdir*: string
  workdir*: string
  mountpoint*: string
  ephemeral*: bool
    ## true = upperdir/workdir żyją w tmpfs zamontowanym specjalnie dla tego
    ## overlaya (odmontowanie tmpfs = trwałe zniknięcie zmian);
    ## false = upperdir/workdir to zwykłe katalogi na dysku, przeżywają odmontowanie.
  tmpfsMount*: string
    ## puste dla trybu trwałego; dla ulotnego — punkt montowania tmpfs,
    ## który trzeba odmontować PO odmontowaniu samego overlaya (odwrotna
    ## kolejność niż montowanie).

proc requireOverlayfs() =
  ## OverlayFS jest wbudowane w większość jąder Linux (CONFIG_OVERLAY_FS),
  ## ale nie wszędzie — sprawdzamy przez /proc/filesystems zamiast na ślepo
  ## próbować mount i mylić "brak wsparcia" z innym błędem (złe ścieżki itp.).
  if not fileExists("/proc/filesystems"):
    return  # nie na Linuksie / brak /proc — zostawiamy błąd faktycznemu mount
  let content = readFile("/proc/filesystems")
  if "overlay" notin content:
    raise newException(OverlayError,
      "Jądro nie zgłasza wsparcia dla overlay w /proc/filesystems — " &
      "OverlayFS (CONFIG_OVERLAY_FS) wymagane dla `fasttree overlay`.")

proc doMountOverlay(lowerdir, upperdir, workdir, mountpoint: string) =
  createDir(mountpoint)
  let opts = &"lowerdir={lowerdir},upperdir={upperdir},workdir={workdir}"
  let (output, code) = execCmdEx(&"mount -t overlay overlay -o {opts} \"{mountpoint}\"")
  if code != 0:
    raise newException(OverlayError, "mount -t overlay nie powiodło się: " & output)

proc newPersistentOverlay*(lowerdir, ftRoot, name, mountpoint: string): Overlay =
  ## Trwały overlay: upperdir/workdir pod `<ftRoot>/overlays/<name>/`.
  ## Wołający jest odpowiedzialny za `unmountOverlay` przy zamykaniu, ale
  ## same pliki w upperdir PRZEŻYWAJĄ odmontowanie (w przeciwieństwie do
  ## `newTmpOverlay`) — to jest cecha, nie bug: `fasttree overlay create`
  ## bez `--ephemeral` ma dawać coś jak `/etc` w OSTree.
  requireOverlayfs()
  let base = ftRoot / "overlays" / name
  let upperdir = base / "upper"
  let workdir = base / "work"
  createDir(upperdir)
  createDir(workdir)
  doMountOverlay(lowerdir, upperdir, workdir, mountpoint)
  Overlay(lowerdir: lowerdir, upperdir: upperdir, workdir: workdir,
          mountpoint: mountpoint, ephemeral: false, tmpfsMount: "")

proc newTmpOverlay*(lowerdir, mountpoint: string, sizeMb = 512): Overlay =
  ## Ulotny overlay: upperdir/workdir w świeżo zamontowanym tmpfs pod
  ## `mountpoint & ".tmpfs"`. Cały stan znika, gdy `unmountOverlay` odmontuje
  ## tmpfs (albo po restarcie, jeśli proces się nie posprząta — tmpfs i tak
  ## nie przeżywa reboota).
  requireOverlayfs()
  let tmpfsMount = mountpoint & ".tmpfs"
  createDir(tmpfsMount)
  let (mtOut, mtCode) = execCmdEx(&"mount -t tmpfs -o size={sizeMb}m tmpfs \"{tmpfsMount}\"")
  if mtCode != 0:
    raise newException(OverlayError, "mount tmpfs (dla ulotnego upperdir) nie powiodło się: " & mtOut)

  let upperdir = tmpfsMount / "upper"
  let workdir = tmpfsMount / "work"
  createDir(upperdir)
  createDir(workdir)
  try:
    doMountOverlay(lowerdir, upperdir, workdir, mountpoint)
  except OverlayError:
    discard execCmdEx(&"umount \"{tmpfsMount}\"")
    raise
  Overlay(lowerdir: lowerdir, upperdir: upperdir, workdir: workdir,
          mountpoint: mountpoint, ephemeral: true, tmpfsMount: tmpfsMount)

proc unmountOverlay*(ov: Overlay) =
  ## Kolejność MA znaczenie dla trybu ulotnego: najpierw odmontuj sam
  ## overlay (który ma upperdir/workdir WEWNĄTRZ tmpfs), dopiero potem
  ## odmontuj tmpfs — w odwrotnej kolejności jądro odmówi (busy).
  let (out1, c1) = execCmdEx(&"umount \"{ov.mountpoint}\"")
  if c1 != 0:
    raise newException(OverlayError, "umount overlay nie powiodło się: " & out1)
  if ov.ephemeral and ov.tmpfsMount.len > 0:
    let (out2, c2) = execCmdEx(&"umount \"{ov.tmpfsMount}\"")
    if c2 != 0:
      raise newException(OverlayError, "umount tmpfs (upperdir ulotny) nie powiodło się: " & out2)

type OverlayInfo* = object
  name*: string
  mountpoint*: string
  upperdir*: string
  ephemeral*: bool
  mounted*: bool
    ## true jeśli `mountpoint` faktycznie pojawia się jako aktywny mount
    ## overlay w /proc/mounts (a nie tylko: katalog istnieje na dysku).

proc isMounted(mountpoint: string): bool =
  ## Sprawdza /proc/mounts zamiast ufać samej obecności katalogu —
  ## katalog mountpointu przeżywa `umount`, więc sama jego obecność nic
  ## nie mówi o tym, czy overlay jest faktycznie aktywny.
  if not fileExists("/proc/mounts"): return false
  let target = mountpoint.absolutePath
  for line in readFile("/proc/mounts").splitLines:
    let parts = line.splitWhitespace()
    if parts.len >= 3 and parts[2] == "overlay" and parts[1] == target:
      return true
  false

proc listActiveOverlays*(ftRoot: string): seq[OverlayInfo] =
  ## `fasttree overlay list` — przegląda `<ftRoot>/overlay-mounts/` (każdy
  ## wpis to nazwa overlaya utworzonego przez `cmdOverlayCreate`) i dla
  ## każdego sprawdza w /proc/mounts, czy jest faktycznie zamontowany.
  ## Typ (ulotny/trwały) rozpoznajemy po obecności `<mountpoint>.tmpfs`
  ## (patrz `newTmpOverlay`) — dokładnie tak samo jak `cmdOverlayRemove`.
  result = @[]
  let mountsDir = ftRoot / "overlay-mounts"
  if not dirExists(mountsDir): return
  for kind, path in walkDir(mountsDir):
    if kind != pcDir: continue
    let name = path.extractFilename
    if name.endsWith(".tmpfs"): continue  # to jest wewnętrzny katalog tmpfs, nie osobny overlay
    let ephemeral = dirExists(path & ".tmpfs")
    let upperdir =
      if ephemeral: path & ".tmpfs" / "upper"
      else: ftRoot / "overlays" / name / "upper"
    result.add OverlayInfo(name: name, mountpoint: path, upperdir: upperdir,
                            ephemeral: ephemeral, mounted: isMounted(path))

proc listChanges*(ov: Overlay): seq[string] =
  ## Lista ścieżek (względnych) zmienionych względem `lowerdir` — czyli
  ## zawartość `upperdir`. OverlayFS oznacza usunięte pliki "whiteoutami"
  ## (character device 0:0) i katalogi przesłonięte "opaque" xattr-em
  ## `trusted.overlay.opaque` — tu nie rozróżniamy tych przypadków od
  ## zwykłych nowych plików (wystarczy do przeglądu "co się zmieniło",
  ## niekoniecznie do precyzyjnego diff jak manifest.diff() dla FastTree).
  result = @[]
  if not dirExists(ov.upperdir): return
  for path in walkDirRec(ov.upperdir):
    result.add path.relativePath(ov.upperdir)