#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

typedef enum op_t { ERROR, FETCH, STORE } op_t;
const char *file;
char *tmp_file;

void
do_fetch (int csock)
{
  int fd;
  int nread;
  char buf[1024];

  fd = open (file, O_RDONLY);
  if (fd < 0) {
    perror (file);
    return;
  }

  while ((nread = read (fd, buf, sizeof (buf))) > 0)
    write (csock, buf, nread);
}

void
do_store (int csock)
{
  int fd;
  int nread;
  char buf[1024];

  fd = open (tmp_file, O_CREAT|O_TRUNC|O_WRONLY, 0666);
  if (fd < 0) {
    perror (tmp_file);
    write (csock, "ERROR\n", 6);
    return;
  }

  while ((nread = read (csock, buf, sizeof (buf))) > 0)
    write (fd, buf, nread);
  fsync (fd);
  close (fd);
  rename (tmp_file, file);
  write (csock, "OK\n", 3);
}

int
fullread (int fd, char *buf, int len)
{
  while (len > 0) {
    int nread = read (fd, buf, len);
    if (nread < 0) {
      perror ("read");
      return -1;
    }
    if (nread == 0) {
      fprintf (stderr, "premature EOF\n");
      return -1;
    }
    len -= nread;
    buf += nread;
  }
  return 0;
}

op_t
get_command (int csock)
{
  char cmd[7];
  if (fullread (csock, cmd, 6) < 0)
    return ERROR;
  cmd[6] = '\0';
  if (!strncmp (cmd, "fetch", 5))
    return FETCH;
  if (!strncmp (cmd, "store", 5))
    return STORE;
  return ERROR;
}

void
service_client (int csock)
{
  switch (get_command (csock)) {
  case FETCH:
    do_fetch (csock);
    break;
  case STORE:
    do_store (csock);
    break;
  default:
    fprintf (stderr, "bad command\n");
    break;
  }
}

void
server_loop (int lsock)
{
  struct sockaddr_in sin;
  socklen_t sinlen;
  int csock;

  if (listen (lsock, 5) < 0) {
    perror ("listen");
    exit (1);
  }

  for (;;) {
    bzero (&sin, sizeof (sin));
    sinlen = sizeof (sin);
    csock = accept (lsock, (struct sockaddr *) &sin, &sinlen);
    if (csock < 0) {
      perror ("accept");
      exit (1);
    }

    printf ("servicing client from IP address %s, TCP port %d\n",
	    inet_ntoa (sin.sin_addr), ntohs (sin.sin_port));
    service_client (csock);
    close (csock);
  }
}

int
tcpsocket (int port)
{
  int fd;
  struct sockaddr_in sin;
  int n;

  if (port < 1 || port > 0xffff) {
    fprintf (stderr, "bad port number\n");
    exit (1);
  }

  fd = socket (AF_INET, SOCK_STREAM, 0);
  if (fd < 0) {
    perror ("socket");
    exit (1);
  }

  /* The following allows you to bind a TCP port when old connections
   * are still in TIME_WAIT.   */
  n = 1;
  setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof (n));

  bzero (&sin, sizeof (sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons (port);
  sin.sin_addr.s_addr = htonl (INADDR_ANY);
  if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
    perror ("bind");
    exit (1);
  }

  return fd;
}

int
main (int argc, char **argv)
{
  int lsock;

  if (argc != 3) {
    fprintf (stderr, "usage: %s port file\n", argv[0]);
    exit (1);
  }

  file = argv[2];
  tmp_file = malloc (strlen (file) + 1);
  sprintf (tmp_file, "%s~", file);

  lsock = tcpsocket (atoi (argv[1]));
  server_loop (lsock);

  return 0;
}
