/*
This is a simple server that users io_uring in blocking mode.
It demonstrates the bare minimum needed to use io_uring.
It uses liburing for simplicity.
*/


#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <liburing.h>

extern int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

struct io_uring ring;

char data[256];
struct iovec iov = { data, 256 };
struct msghdr msg = { (void *)"", 0, &iov, 1, NULL, 0, 0 };
static int async_read(int sock) {
	int ret;

	/* get an sqe and fill in a READ operation */
      struct io_uring_sqe * sqe = io_uring_get_sqe(&ring);
      io_uring_prep_recvmsg(sqe, sock, &msg, 0);
      sqe->user_data = 0;

      /* tell the kernel we have an sqe ready for consumption */
      ret = io_uring_submit(&ring);
      assert(ret == 1);

	struct io_uring_cqe * cqe;
	ret = io_uring_wait_cqe( &ring, &cqe );

	if( ret < 0 ) {
		printf( "Main Loop Error : %s\n", strerror(-ret) );
		close( sock );
		exit( EXIT_FAILURE );
	}

	// Problem ?
	if(cqe->user_data != 0) {
		printf("Unexpected user data : %llu", cqe->user_data);
		exit( EXIT_FAILURE );
	}

	int res = cqe->res;
	io_uring_cqe_seen( &ring, cqe );

	if(res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
		printf("io_uring returned EAGAIN\n");
	}

	return res;
}

static int do_read(int sock) {
	int res = recvmsg(sock, &msg, 0);

	if(res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
		printf("Had EAGAIN\n");
		return async_read(sock);
	}

	return res;
}

int main(int argc, char *argv[]) {
	if(argc != 2) {
            printf("usage:    %s portnumber\n", argv[0]);
            exit( EXIT_FAILURE );
      }
      int port = atoi(argv[1]);
      if(port < 1) {
            printf("Invalid port : %d (from %s)\n", port, argv[1]);
            exit( EXIT_FAILURE );
      }

	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0) {
		perror( "socket" );
		exit( EXIT_FAILURE );
	}

	struct sockaddr_in serv_addr;
      memset(&serv_addr, 0, sizeof(serv_addr));
      serv_addr.sin_family = AF_INET;
      serv_addr.sin_addr.s_addr = INADDR_ANY;
      serv_addr.sin_port = htons(port);

	int ret = bind(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
	if(ret < 0) {
		perror( "bind" );
		exit( EXIT_FAILURE );
	}

     	listen(sock,1);

	struct sockaddr_in cli_addr;
     	__socklen_t clilen = sizeof(cli_addr);
	int newsock = accept4(sock, (struct sockaddr *) &cli_addr, &clilen, SOCK_NONBLOCK);
     	if (newsock < 0) {
		perror( "accept" );
		exit( EXIT_FAILURE );
	}

	io_uring_queue_init( 16, &ring, 0 );

	while(1) {
		int res = do_read(newsock);

		// Did we get an error
		if( res < 0 ) {
			perror( "Main Loop Error" );
			close( sock );
			exit( EXIT_FAILURE );
		}

		// If it is the end of file we are done
		if( res == 0 ) {
			goto END;
		}

		// just echo the data
		printf("'%.*s'\n", res - 1, data);
	}
END:

	io_uring_queue_exit( &ring );

	ret = close(newsock);
      if(ret < 0) {
            perror( "close new" );
            exit( EXIT_FAILURE );
      }

	ret = close(sock);
      if(ret < 0) {
            perror( "close old" );
            exit( EXIT_FAILURE );
      }

	return 0;
}