/*
** $Id: watchcat.c 2703 2008-08-27 20:27:34Z andre.dig $
** libwcat - Watchcat Client Library
** See copyright notice in distro's COPYRIGHT file
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include "watchcat.h"


static char DEVICE[CAT_DEVICE_SIZE] = "/var/run/watchcat.socket";

void cat_set_device(const char *dev)
{
    strncpy(DEVICE, dev, CAT_DEVICE_SIZE);
    DEVICE[CAT_DEVICE_SIZE] = '\0';
}

static ssize_t safe_write(int fd, const void *buf, size_t count)
{
    struct sigaction act, oact;
    ssize_t r;
    int e, s;

#ifdef SCM_CREDS
    union {
        struct cmsghdr hdr; /* for alignment */
        char cred[CMSG_SPACE(sizeof(struct cmsgcred))];
    } cmsg;
    struct iovec iov[1];
    struct msghdr msg;

   iov[0].iov_base = (void *)buf;
   iov[0].iov_len = count;

   memset(&msg, 0, sizeof(msg));
   msg.msg_iov = iov;
   msg.msg_iovlen = 1;

   msg.msg_control = (caddr_t)&cmsg;
   msg.msg_controllen = CMSG_SPACE(sizeof(struct cmsgcred));
   memset(&cmsg, 0, sizeof(cmsg));
   cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(struct cmsgcred));
   cmsg.hdr.cmsg_level = SOL_SOCKET;
   cmsg.hdr.cmsg_type = SCM_CREDS;
#endif

    /* Ignore SIGPIPE. */
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    while ((s = sigaction(SIGPIPE, &act, &oact)) == -1 &&
           errno == EINTR)
        ;
    if (s != 0)
        return -1;
    
    do {
#ifdef SCM_CREDS
        r = sendmsg(fd, &msg, 0);
#else
        r = write(fd, buf, count);
#endif
    } while(r == -1 && errno == EINTR);
    e = errno;

    /* Restore SIGPIPE handler. */
    while ((s = sigaction(SIGPIPE, &oact, NULL)) == -1 &&
           errno == EINTR)
        ;
    if (s != 0)
        abort();

    errno = e;
    return r;
}

static ssize_t safe_read(int fd, void *buf, size_t count)
{
    ssize_t r;
    do {
        r = read(fd, buf, count);
    } while(r == -1 && errno == EINTR);
    return r;
}

/* FIXME What errno I must use? */
#define cat_assert(exp) if (!(exp)) { \
    close(cat); \
    errno = EPERM; \
    return -1; \
} (void)0

#define cat_assert_sys(exp) if (!(exp)) { \
    int e = errno; \
    close(cat); \
    errno = e; \
    return -1; \
} (void)0

#define CAT_BUFSIZE 256

int cat_open1(int timeout, int signal, const char *info)
{
    char buf[CAT_BUFSIZE];
    int cat;
    struct sockaddr_un addr;
    int count;
    ssize_t r;

    /* Create socket. */
    cat = socket(PF_UNIX, SOCK_STREAM, 0);
    if (cat < 0) return -1;
    cat_assert_sys(fcntl(cat, F_SETFD, FD_CLOEXEC) == 0);
    
    /* Connect socket. */
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, DEVICE);
    cat_assert_sys(connect(cat, (struct sockaddr *)&addr, SUN_LEN(&addr)) == 0);
    
    /* Fill buf with cat header. */
    if (info) {
        char linfo[121], *p;
        strncpy(linfo, info, sizeof(linfo)-1);
        linfo[sizeof(linfo)-1] = '\0';
        for (p = linfo; *p; p++)
            if (*p == '\n')
                *p = '_';
        count = snprintf(buf, CAT_BUFSIZE,
#ifdef SCM_CREDS
                         " "  /* XXX extra byte */
#endif
                         "version: 1\ntimeout: %i\nsignal: %i\n"
                         "info: %s\n\n", timeout, signal, linfo);
    }
    else {
        count = snprintf(buf, CAT_BUFSIZE,
#ifdef SCM_CREDS
                         " "  /* XXX extra byte */
#endif
                         "version: 1\ntimeout: %i\nsignal: %i\n\n",
                         timeout, signal);
    }
    cat_assert(count > 0 && count < CAT_BUFSIZE);

    /* Send cat header. */
    r = safe_write(cat, buf, count);
    cat_assert_sys(r != -1);
    cat_assert(r == count);

    /* Get feedback. */
    r = safe_read(cat, buf, CAT_BUFSIZE);
    cat_assert_sys(r != -1);
    if (r > 0 && strncmp(buf, "ok\n", 3) == 0)
        return cat;

    close(cat);
    /* FIXME What errno I must use?
     * buf can contain: "connect\n", "timeout\n", "error\n".
     */
    errno = EPERM;
    return -1;
}

int cat_open(void)
{
    return cat_open1(60, SIGKILL, NULL);
}

int cat_heartbeat(int cat)
{
    return (safe_write(cat, ".", 1) == 1) ? 0 : -1;
}

int cat_close(int cat)
{
    int r;
    do {
        r = close(cat);
    } while (r == -1 && errno == EINTR);
    return r;
}
