import os,ctypes,struct,time,json,select
libc=ctypes.CDLL('libc.so.6',use_errno=True)
FAN_CLASS_NOTIF=0; FAN_CLOEXEC=0x1; FAN_MARK_ADD=0x1; FAN_MARK_ONLYDIR=0x200; MASK=0x2|0x4|0x40|0x80|0x100|0x200|0x400|0x800
fd=libc.fanotify_init(FAN_CLASS_NOTIF|FAN_CLOEXEC,os.O_RDONLY|0x80000)
if fd<0: print(json.dumps({'available':False,'error':os.strerror(ctypes.get_errno())})); raise SystemExit
path=b'/run/graphiti-control'
rc=libc.fanotify_mark(fd,FAN_MARK_ADD|FAN_MARK_ONLYDIR,MASK,-1,path)
if rc<0: print(json.dumps({'available':False,'error':os.strerror(ctypes.get_errno())})); os.close(fd); raise SystemExit
start=time.time(); events=[]
while time.time()-start<240:
r,_,_=select.select([fd],[],[],1)
if not r: continue
data=os.read(fd,4096); off=0
while off+24<=len(data):
event_len,vers,res,meta_len,mask,efd,pid=struct.unpack_from('I B B H Q i i',data,off)
if event_len<24: break
off+=event_len
proc={}
if pid>0:
try: proc={'pid':pid,'ppid':int(open(f'/proc/{pid}/stat').read().split()[3]),'comm':open(f'/proc/{pid}/comm').read().strip(),'exe':os.readlink(f'/proc/{pid}/exe'),'uid':os.stat(f'/proc/{pid}').st_uid}
except Exception: proc={'pid':pid,'gone':True}
target=''
if efd>=0:
try: target=os.readlink(f'/proc/self/fd/{efd}')
except: pass
os.close(efd)
events.append({'utc':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'mask':mask,'target':target,'process':proc})
os.close(fd)
print(json.dumps({'available':True,'watch_path':'/run/graphiti-control','duration_seconds':round(time.time()-start,1),'events':events},ensure_ascii=False))