// host_block_swapper.c - host side of the directfs mount.
// It creates a persistent regular target and block swapdev, then waits until
// the guest has primed/cached target as S_IFREG before beginning atomic swaps.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
#ifndef RENAME_EXCHANGE
#define RENAME_EXCHANGE (1U << 1)
#endif
static int exchange(int dfd,const char*a,const char*b){return syscall(SYS_renameat2,dfd,a,dfd,b,RENAME_EXCHANGE);}
int main(int argc,char**argv){
 if(argc<5){fprintf(stderr,"usage: %s <dir> <seconds> <major> <minor>\n",argv[0]);return 2;}
 int dfd=open(argv[1],O_RDONLY|O_DIRECTORY|O_CLOEXEC),secs=atoi(argv[2]);
 unsigned maj=(unsigned)strtoul(argv[3],0,0),min=(unsigned)strtoul(argv[4],0,0);
 if(dfd<0){perror("open directory");return 2;}
 unlinkat(dfd,"target",0);unlinkat(dfd,"swapdev",0);unlinkat(dfd,"guest-ready",0);unlinkat(dfd,"host-go",0);
 int fd=openat(dfd,"target",O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC,0666);
 if(fd<0){perror("open target");return 2;} if(write(fd,"x",1)!=1){perror("write target");return 2;} close(fd);
 if(mknodat(dfd,"swapdev",S_IFBLK|0666,makedev(maj,min))){perror("mknod block swapdev");return 2;}
 // Do not swap until the guest confirms it resolved target as a regular file.
 struct stat st; int waited=0;
 while(fstatat(dfd,"guest-ready",&st,0)!=0){if(errno!=ENOENT){perror("guest-ready");return 2;}usleep(1000);if(++waited>30000){fprintf(stderr,"guest-ready timeout\n");return 2;}}
 fd=openat(dfd,"host-go",O_WRONLY|O_CREAT|O_CLOEXEC,0666);if(fd<0){perror("host-go");return 2;}close(fd);
 struct timespec start,now={0};clock_gettime(CLOCK_MONOTONIC,&start);unsigned long count=0;
 do{if(exchange(dfd,"target","swapdev")){perror("renameat2");return 2;}count++;if((count&0xfff)==0)clock_gettime(CLOCK_MONOTONIC,&now);}while(now.tv_sec-start.tv_sec<secs);
 printf("BLOCK_SWAPPER_DONE exchanges=%lu device=%u:%u\n",count,maj,min);return 0;
}
