/* Copyright (c) 1995-1999 NEC USA, Inc.  All rights reserved.               */
/*                                                                           */
/* The redistribution, use and modification in source or binary forms of     */
/* this software is subject to the conditions set forth in the copyright     */
/* document ("Copyright") included with this distribution.                   */

/*
 * $Id: sema.c,v 1.20.4.3 1999/02/03 22:35:42 steve Exp $
 */

#include "socks5p.h"
#include "sema.h"
#include "log.h"

#ifdef USE_SYSTEM_SEMAPHORE
#ifdef HAVE_SYS_IPC_H
#include <sys/ipc.h>
#endif

#ifdef HAVE_SYS_SEM_H
#include <sys/sem.h>
#endif

#ifndef HAVE_UNION_SEMUN
union semun { int val; struct semid_ds *buf; u_short *array; };
#endif

int semacquire(void *sem) {
    struct sembuf sb = { 0, -1, SEM_UNDO };

    if (sem && semop(*(int *)sem, &sb, 1) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Semacquire: semop failed: %m");
	return -1;
    }

    return 0;
}

int semrelease(void *sem) {
    struct sembuf sb = { 0, 1, SEM_UNDO };

    if (sem && semop(*(int *)sem, &sb, 1) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Semrelease: semop failed: %m");
	return -1;
    }

    return 0;
}

int semreset(void *sem, int val) {
    /* I think semaphores with UNDO won't need resetting...                  */
    return 0;
}

void semdestroy(void *sem) {
    union semun u;
    u.val = 1;

    semctl(*(int *)sem, 0, IPC_RMID, u);
}

void *semcreate(int val) {
    int *semid = (int *)malloc(sizeof(int));
    union semun u;

    u.val = val;

    if (semid == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Semcreate: malloc failed");
	return NULL;
    }

    if ((*semid = semget(IPC_PRIVATE, 1, 0666)) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Semcreate: semget failed: %m");
	return NULL;
    }

    if (semctl(*semid, 0, SETVAL, u) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Semcreate: semctl failed: %m");
	return NULL;
    }

    return (void *)semid;
}
#elif defined(USE_SEMAPHORES)

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif

static int semstart(S5IOHandle *fd, int count) {
    if (pipe(fd) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Semstart: pipe failed: %m");
	return -1;
    }
    
    while (count-- > 0) semrelease((void *)fd);
    return 0;
}

/* Lock (using the semaphore pipe, semfd) control of the signaling and pipes */
/* (pipe reads and writes are atomic, so the pipe can act as a semaphore.)   */
int semacquire(void *sem) {
    S5IOHandle *fd = (int *)sem;
    char c;

    if (sem && RECVSOCKET(fd[0], &c, 1, 0) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Semacquire: read failed: %m");
	return -1;
    }

    return 0;
}

/* Unlock (using the semaphore pipe) control of the signaling and pipes      */
/* (pipe reads and writes are atomic, so the pipe can act as a semaphore.)   */
int semrelease(void *sem) {
    S5IOHandle *fd = (int *)sem;

    if (sem && SENDSOCKET(fd[1], " ", 1, 0) != 1) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Semrelease: write failed: %m");
	return -1;
    }

    return 0;
}

int semreset(void *sem, int val) {
    S5IOHandle *fd = (int *)sem;
    int nq;

    /* ok? */
    if (sem == NULL) return 0;

#ifdef FIONREAD
    ioctl(fd[0], FIONREAD, (char *)&nq);
    if (nq == val) return 0;
#else
    /* on some OSs fstat works on pipes, if it doesn't, it will return -1,   */
    /* and we'll be ok.                                                      */
    {
      struct stat sb;
      if (fstat(fd[0], &sb) == 0 && sb.st_size == val) return 0;
    }
#endif

    /* Need to reset it, really...                                           */
    close(fd[0]);
    close(fd[1]);

    return semstart((int *)sem, val);

    /* Since this only gets called on HUPs, we can deal with recreating the  */
    /* pipe -- It doesn't happen that often, and the old pipe should be      */
    /* gone or will be as soon as the last child that has it open exits.     */

    /* while (nq-- > val) semacquire(sem); while (nq++ < val)                */
    /* semrelease(sem);                                                      */
}

void semdestroy(void *sem) {
    S5IOHandle *fd = (int *)sem;

    if (!sem) return;
    close(fd[0]);
    close(fd[1]);
    free(sem);
}

void *semcreate(int count) {
    S5IOHandle *fd;

    if ((fd  = (int *)malloc(2 * sizeof(int)))== NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Semcreate: malloc failed");
	return NULL;
    }

    semstart(fd, count);
    return (void *)fd;
}

#endif /* not __svr4__ */
