0]; if (!is_string($day)||!preg_match('/^raw-[0-9]{8}\.jsonl$/D',$day) ||!is_int($cursor['offset']??null)||$cursor['offset']<0 ||is_link($directory)||realpath($directory)!==$directory) { throw new \RuntimeException('Invalid archive cursor/directory'); } $cutoff='raw-'.gmdate('Ymd',$now-7*86400).'.jsonl'; $archive=$directory.'/archive'; if (is_link($archive)) throw new \RuntimeException('Archive symlink refused'); if (!is_dir($archive)&&!mkdir($archive,0700)) throw new \RuntimeException('Archive directory unavailable'); if ((fileperms($archive)&0007)!==0) throw new \RuntimeException('Archive must remain private'); $lockPath=$directory.'/.writer.lock'; if (is_link($lockPath)) throw new \RuntimeException('Writer lock symlink refused'); $lock=fopen($lockPath,'c+b'); if ($lock===false) throw new \RuntimeException('Archive lock unavailable'); $done=[]; try { if (!flock($lock,LOCK_EX|LOCK_NB)) return ['archived'=>0,'status'=>'writer_busy']; foreach (glob($directory.'/raw-*.jsonl')?:[] as $file) { $name=basename($file); if (!preg_match('/^raw-[0-9]{8}\.jsonl$/D',$name)||$name>=$day||$name>=$cutoff) continue; if (is_link($file)||realpath($file)!==$file||!is_file($file)) throw new \RuntimeException('Unexpected journal'); $before=stat($file); if (!$before||$before['size']>67108864) throw new \RuntimeException('Archive input bounds'); $digest=hash_file('sha256',$file); if (!is_string($digest)) throw new \RuntimeException('Cannot hash journal'); $target=$archive.'/'.$name.'.'.$digest.'.gz'; if (is_link($target)) throw new \RuntimeException('Archive target symlink'); if (!is_file($target)) { $tmp=tempnam($archive,'.pending-'); if ($tmp===false) throw new \RuntimeException('Cannot create archive'); try { chmod($tmp,0600);$in=fopen($file,'rb');$gz=gzopen($tmp,'wb6'); if ($in===false||$gz===false) {if(is_resource($in))fclose($in);if(is_resource($gz))gzclose($gz);throw new \RuntimeException('Cannot open archive streams');} try { while (!feof($in)) { $block=fread($in,65536); if ($block===false) throw new \RuntimeException('Archive read failed'); $offset=0; while($offset$before['size'])throw new \RuntimeException('Oversized decompressed archive');hash_update($hash,$block);}} finally {gzclose($gz);} clearstatcache(true,$file);$after=stat($file); if ($bytes!==$before['size']||hash_final($hash)!==$digest||!$after ||$after['size']!==$before['size']||$after['ino']!==$before['ino']||$after['mtime']!==$before['mtime'] ||hash_file('sha256',$file)!==$digest) throw new \RuntimeException('Archive mismatch; original retained'); $directoryHandle=fopen($archive,'r'); if ($directoryHandle===false) throw new \RuntimeException('Cannot sync archive directory'); try {if(!fsync($directoryHandle))throw new \RuntimeException('Archive directory sync failed');} finally {fclose($directoryHandle);} if (!unlink($file)) throw new \RuntimeException('Verified original could not be retired'); $done[]=$name; if (count($done)>=2) break; } } finally {flock($lock,LOCK_UN);fclose($lock);} return ['archived'=>count($done),'compressedOriginalsRetained'=>true,'unacknowledgedDaysTouched'=>false]; } }