/* pshm_ucase_bounce.c
   Adapted from SHM_OPEN(3) in Linux Programmer's Manual
   Licensed under GNU General Public License v2 or later */

#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/ctype.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/s.h"
#include "libc/sysv/consts/sig.h"
#include "libc/thread/semaphore.h"

#define SHM_PATH    "/fc7261622dd420d8"
#define STRING_SEND "hello"
#define STRING_RECV "HELLO"

struct shmbuf {
  sem_t sem1;    /* POSIX unnamed semaphore */
  sem_t sem2;    /* POSIX unnamed semaphore */
  size_t cnt;    /* Number of bytes used in 'buf' */
  char buf[256]; /* Data being transferred */
};

atomic_bool *ready;

wontreturn void Bouncer(void) {

  /* Create shared memory object and set its size to the size
     of our structure. */
  int fd = shm_open(SHM_PATH, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);
  if (fd == -1) {
    perror("shm_open(bouncer)");
    exit(1);
  }
  if (ftruncate(fd, sizeof(struct shmbuf)) == -1) {
    perror("ftruncate");
    exit(1);
  }

  /* Map the object into the caller's address space. */
  struct shmbuf *shmp =
      mmap(NULL, sizeof(*shmp), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (shmp == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }

  /* Initialize semaphores as process-shared, with value 0. */
  if (sem_init(&shmp->sem1, 1, 0) == -1) {
    perror("sem_init-sem1");
    exit(1);
  }
  if (sem_init(&shmp->sem2, 1, 0) == -1) {
    perror("sem_init-sem2");
    exit(1);
  }

  /* Let other process know it's ok to open. */
  *ready = true;

  /* Wait for 'sem1' to be posted by peer before touching
     shared memory. */
  if (sem_wait(&shmp->sem1) == -1) {
    perror("sem_wait");
    exit(1);
  }

  /* Convert data in shared memory into upper case. */
  for (int j = 0; j < shmp->cnt; j++) {
    shmp->buf[j] = toupper((unsigned char)shmp->buf[j]);
  }

  /* Post 'sem2' to tell the peer that it can now
     access the modified data in shared memory. */
  if (sem_post(&shmp->sem2) == -1) {
    perror("sem_post");
    exit(1);
  }

  exit(0);
}

wontreturn void Sender(void) {

  /* Wait for file to exist. */
  while (!*ready)
    donothing;

  /* Open the existing shared memory object and map it
     into the caller's address space. */
  int fd = shm_open(SHM_PATH, O_RDWR, 0);
  if (fd == -1) {
    perror("shm_open(sender)");
    exit(1);
  }

  struct shmbuf *shmp =
      mmap(NULL, sizeof(*shmp), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (shmp == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }

  /* Copy data into the shared memory object. */
  shmp->cnt = strlen(STRING_SEND);
  strcpy(shmp->buf, STRING_SEND);

  /* Tell peer that it can now access shared memory. */
  if (sem_post(&shmp->sem1) == -1) {
    perror("sem_post");
    exit(1);
  }

  /* Wait until peer says that it has finished accessing
     the shared memory. */
  if (sem_wait(&shmp->sem2) == -1) {
    perror("sem_wait");
    exit(1);
  }

  /* Write modified data in shared memory to standard output. */
  if (strcmp(shmp->buf, STRING_RECV)) {
    tinyprint(2, program_invocation_name,
              ": received wrong string: ", shmp->buf, "\n", NULL);
    exit(1);
  }

  /* Unlink the shared memory object. Even if the peer process
     is still using the object, this is okay. The object will
     be removed only after all open references are closed. */
  if (shm_unlink(SHM_PATH)) {
    if (IsWindows() && errno == EACCES) {
      // TODO(jart): Make unlink() work better on Windows.
    } else {
      perror("shm_unlink");
      exit(1);
    }
  }

  exit(0);
}

int pid1;
int pid2;

void OnExit(void) {
  kill(pid1, SIGKILL);
  kill(pid2, SIGKILL);
  shm_unlink(SHM_PATH);
}

void OnTimeout(int sig) {
  tinyprint(2, program_invocation_name, ": test timed out!\n", NULL);
  exit(1);
}

int main(int argc, char *argv[]) {

  // create synchronization object
  ready = _mapshared(1);

  // create bouncer
  pid1 = fork();
  if (pid1 == -1) {
    perror("fork");
    exit(1);
  }
  if (!pid1) {
    Bouncer();
  }

  // create sender
  pid2 = fork();
  if (pid1 == -1) {
    perror("fork");
    kill(pid1, SIGKILL);
    exit(1);
  }
  if (!pid2) {
    Sender();
  }

  // set limit
  atexit(OnExit);
  signal(SIGALRM, OnTimeout);
  alarm(1);

  // wait for children
  for (;;) {
    int child, status;
    child = wait(&status);
    if (child == -1) {
      if (errno == ECHILD) {
        break;
      }
      perror("wait");
      exit(1);
    }
    if (status) {
      if (WIFEXITED(status)) {
        exit(WEXITSTATUS(status));
      } else {
        raise(WTERMSIG(status));
        exit(128 + WTERMSIG(status));
      }
    }
  }

  // report success
  exit(0);
}
