#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static const int backlog = 10;

static void handle_client(int fd);

int main(int argc, char **argv) {
  struct addrinfo *ai;
  struct addrinfo hints = {.ai_family = AF_UNSPEC,
                           .ai_socktype = SOCK_STREAM,
                           .ai_flags = AI_PASSIVE};
  int res, fd;

  if (argc != 2) errx(1, "usage: %s port", argv[0]);

  res = getaddrinfo(NULL, argv[1], &hints, &ai);
  if (res) errx(EXIT_FAILURE, "getaddrinfo: %s", gai_strerror(res));

  fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
  if (fd < 0) err(EXIT_FAILURE, "socket");

  res = 1;
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res)))
    err(EXIT_FAILURE, "setsockopt");

  if (bind(fd, ai->ai_addr, ai->ai_addrlen)) err(EXIT_FAILURE, "bind");

  if (listen(fd, backlog)) err(EXIT_FAILURE, "listen");

  for (;;) {
    struct sockaddr_storage addr;
    socklen_t addrlen = sizeof(addr);
    char ip[128] = {0};
    int newfd;

    /* accept(fd, NULL, NULL) */
    newfd = accept(fd, (struct sockaddr *)&addr, &addrlen);
    if (newfd < 0) err(EXIT_FAILURE, "accept");

    getnameinfo((struct sockaddr *)&addr, addrlen, ip, sizeof(ip), NULL, 0,
                NI_NUMERICHOST);
    fprintf(stderr, "new connection from %s\n", ip);

    handle_client(newfd);

    fprintf(stderr, "close connection\n");
    close(newfd);
  }

  close(fd);
  freeaddrinfo(ai);
}

static void handle_client(int fd) {
  char buf[64];

  for (;;) {
    size_t count = read(fd, buf, sizeof(buf)), offset = 0;

    if (count < 0) {
      if (errno == EAGAIN || errno == EINTR) continue;
      err(EXIT_FAILURE, "read");
    }
    if (count == 0) break;

    while (offset < count) {
      ssize_t res = write(fd, buf + offset, count - offset);

      if (res < 0) {
        if (errno == EAGAIN || errno == EINTR) continue;
        err(EXIT_FAILURE, "write");
      }
      offset += res;
    }
  }
}