38 lines
1.6 KiB
Python
38 lines
1.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Root-owned, read-only pstore snapshot; never clears kernel/archive records."""
|
|
from datetime import datetime
|
|
import grp
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import tempfile
|
|
|
|
folder = Path('/var/lib/vdi-observer')
|
|
folder.mkdir(mode=0o750, exist_ok=True)
|
|
if folder.is_symlink() or folder.stat().st_uid != 0:
|
|
raise RuntimeError('Unsafe pstore snapshot directory')
|
|
gid = grp.getgrnam('systemd-journal').gr_gid
|
|
os.chown(folder, 0, gid); os.chmod(folder, 0o750)
|
|
data = {'captured_at': datetime.now().astimezone().isoformat(),
|
|
'boot_id': Path('/proc/sys/kernel/random/boot_id').read_text().strip(), 'sources': {}}
|
|
for root in (Path('/sys/fs/pstore'), Path('/var/lib/systemd/pstore')):
|
|
try:
|
|
entries = {}
|
|
for p in sorted(root.iterdir())[:8]:
|
|
if p.is_symlink() or not p.is_file(): continue
|
|
with p.open('rb') as stream: raw = stream.read(32768)
|
|
entries[p.name] = {'mtime': p.stat().st_mtime, 'text': raw.decode(errors='replace'), 'truncated': p.stat().st_size > 32768}
|
|
data['sources'][str(root)] = entries
|
|
except OSError as e: data['sources'][str(root)] = {'error': str(e)}
|
|
name = None
|
|
try:
|
|
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', dir=folder, delete=False) as f:
|
|
name = f.name; os.fchown(f.fileno(), 0, gid); os.fchmod(f.fileno(), 0o640)
|
|
json.dump(data, f, ensure_ascii=False); f.flush(); os.fsync(f.fileno())
|
|
os.replace(name, folder/'pstore.json'); name = None
|
|
fd = os.open(folder, os.O_RDONLY | os.O_DIRECTORY)
|
|
try: os.fsync(fd)
|
|
finally: os.close(fd)
|
|
finally:
|
|
if name: os.unlink(name)
|