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