runv-server

server tooling for runv.club
Log | Files | Refs | README

runv_mount.py (2741B)


      1 """
      2 Descoberta de montagens para quotas runv — partilhado por starthere, create_runv_user, del-user.
      3 
      4 O ponto de verdade para «em que disco estão as homes» é o path físico (tipicamente /home):
      5 o mesmo algoritmo deve usar findmnt/proc em todos os scripts.
      6 """
      7 
      8 from __future__ import annotations
      9 
     10 import json
     11 import subprocess
     12 from pathlib import Path
     13 
     14 
     15 class MountLookupError(RuntimeError):
     16     """Não foi possível resolver o mount para um path."""
     17 
     18 
     19 def find_mount_triple(path: Path) -> tuple[str, str, str]:
     20     """
     21     Retorna (target, fstype, options_csv) do filesystem que contém ``path``.
     22 
     23     ``target`` é o mountpoint canónico (ex.: ``/`` se /home está na raiz, ou ``/home``
     24     se /home é um ponto de montagem separado).
     25     """
     26     try:
     27         r = subprocess.run(
     28             ["findmnt", "-J", "-T", str(path)],
     29             capture_output=True,
     30             text=True,
     31             timeout=30,
     32         )
     33         if r.returncode == 0 and r.stdout.strip():
     34             data = json.loads(r.stdout)
     35             fss = data.get("filesystems") or []
     36             if fss:
     37                 e = fss[0]
     38                 tgt = str(e.get("target", ""))
     39                 fst = str(e.get("fstype", ""))
     40                 opts = str(e.get("options", ""))
     41                 if tgt and fst:
     42                     return tgt, fst, opts
     43     except (FileNotFoundError, json.JSONDecodeError, subprocess.TimeoutExpired):
     44         pass
     45 
     46     try:
     47         resolved = path.resolve()
     48         rpath = str(resolved)
     49         best: tuple[str, str, str, int] = ("", "", "", -1)
     50         with open("/proc/mounts", encoding="utf-8") as f:
     51             for line in f:
     52                 parts = line.split()
     53                 if len(parts) < 4:
     54                     continue
     55                 _dev, mountpoint, fstype, opts = parts[0], parts[1], parts[2], parts[3]
     56                 mp = mountpoint.replace("\\040", " ")
     57                 if rpath == mp or rpath.startswith(mp.rstrip("/") + "/") or rpath.startswith(mp + "/"):
     58                     ln = len(mp)
     59                     if ln > best[3]:
     60                         best = (mp, fstype, opts, ln)
     61         if best[3] >= 0:
     62             return best[0], best[1], best[2]
     63     except OSError:
     64         pass
     65 
     66     raise MountLookupError(
     67         f"não foi possível determinar o mountpoint do filesystem para {path} "
     68         "(findmnt e /proc/mounts falharam)"
     69     )
     70 
     71 
     72 def quota_opts_allow_user(options: str) -> bool:
     73     """True se usrquota ou usrjquota= está ativo nas opções de mount."""
     74     if not options:
     75         return False
     76     for raw in options.split(","):
     77         opt = raw.strip()
     78         if opt == "usrquota":
     79             return True
     80         if opt.startswith("usrjquota="):
     81             return True
     82     return False