/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** UNIX AF_LOCAL network wrapper
 */


#include "tcn.h"
#include "apr_thread_mutex.h"
#include "apr_poll.h"

/* ### should be tossed in favor of APR */
#include <sys/stat.h>
#include <sys/un.h> /* for sockaddr_un */

#ifdef TCN_DO_STATISTICS
#include "apr_atomic.h"

static volatile apr_uint32_t uxp_created  = 0;
static volatile apr_uint32_t uxp_closed   = 0;
static volatile apr_uint32_t uxp_cleared  = 0;
static volatile apr_uint32_t uxp_accepted = 0;

void uxp_network_dump_statistics()
{
    fprintf(stderr, "NT Network Statistics ..\n");
    fprintf(stderr, "Sockets created         : %d\n", uxp_created);
    fprintf(stderr, "Sockets accepted        : %d\n", uxp_accepted);
    fprintf(stderr, "Sockets closed          : %d\n", uxp_closed);
    fprintf(stderr, "Sockets cleared         : %d\n", uxp_cleared);
}

#endif

#define DEFNAME     "/var/run/tomcatnativesock"
#define DEFNAME_FMT "/var/run/tomcatnativesock%08x%08x"
#define DEFSIZE     8192
#define DEFTIMEOUT  60000

#define TCN_UXP_UNKNOWN     0
#define TCN_UXP_CLIENT      1
#define TCN_UXP_ACCEPTED    2
#define TCN_UXP_SERVER      3

#define TCN_UNIX_MAXPATH    1024
typedef struct {
    apr_pool_t          *pool;
    apr_socket_t        *sock;               /* APR socket */
    int                 sd;
    struct sockaddr_un  uxaddr;
    int                 timeout;
    int                 mode;                 /* Client or server mode */
    char                name[TCN_UNIX_MAXPATH+1];
} tcn_uxp_conn_t;

static apr_status_t APR_THREAD_FUNC
uxp_socket_timeout_set(apr_socket_t *sock, apr_interval_time_t t)
{
    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
    if (t < 0)
        con->timeout = -1;
    else
        con->timeout = (int)(apr_time_as_msec(t));
    return APR_SUCCESS;
}

static apr_status_t APR_THREAD_FUNC
uxp_socket_timeout_get(apr_socket_t *sock, apr_interval_time_t *t)
{
    tcn_uxp_conn_t *con = (tcn_uxp_conn_t*)sock;
    if (con->timeout < 0)
        *t = -1;
    else
        *t = con->timeout * 1000;
    return APR_SUCCESS;
}

static APR_INLINE apr_status_t APR_THREAD_FUNC
uxp_socket_opt_set(apr_socket_t *sock, apr_int32_t opt, apr_int32_t on)
{
    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
    return apr_socket_opt_set(con->sock, opt, on);
}

static APR_INLINE apr_status_t APR_THREAD_FUNC
uxp_socket_opt_get(apr_socket_t *sock, apr_int32_t opt, apr_int32_t *on)
{
    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
    return apr_socket_opt_get(con->sock, opt, on);
}

static apr_status_t uxp_cleanup(void *data)
{
    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)data;

    if (con) {
        if (con->sock) {
            apr_socket_close(con->sock);
            con->sock = NULL;
        }
        if (con->mode == TCN_UXP_SERVER) {
            unlink(con->name);
            con->mode = TCN_UXP_UNKNOWN;
        }
    }

#ifdef TCN_DO_STATISTICS
    apr_atomic_inc32(&uxp_cleared);
#endif
    return APR_SUCCESS;
}

static apr_status_t APR_THREAD_FUNC
uxp_socket_shutdown(apr_socket_t *sock, apr_shutdown_how_e how)
{
    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
    return apr_socket_shutdown(con->sock, how);
}

static apr_status_t APR_THREAD_FUNC
uxp_socket_close(apr_socket_t *sock)
{
#ifdef TCN_DO_STATISTICS
    apr_atomic_inc32(&uxp_closed);
#endif
    return uxp_cleanup(sock);
}

static apr_status_t APR_THREAD_FUNC
uxp_socket_recv(apr_socket_t *sock, char *buf, apr_size_t *len)
{
    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
    return apr_socket_recv(con->sock, buf, len);
}


static apr_status_t APR_THREAD_FUNC
uxp_socket_send(apr_socket_t *sock, const char *buf,
                apr_size_t *len)
{
    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
    return apr_socket_send(con->sock, buf, len);
}

static apr_status_t APR_THREAD_FUNC
uxp_socket_sendv(apr_socket_t *sock,
                 const struct iovec *vec,
                 apr_int32_t nvec, apr_size_t *len)
{
    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
    return apr_socket_sendv(con->sock, vec, nvec, len);
}

static apr_status_t uxp_socket_cleanup(void *data)
{
    tcn_socket_t *s = (tcn_socket_t *)data;

    if (s->net->cleanup) {
        (*s->net->cleanup)(s->opaque);
        s->net->cleanup = NULL;
    }
#ifdef TCN_DO_STATISTICS
    apr_atomic_inc32(&uxp_cleared);
#endif
    return APR_SUCCESS;
}

static tcn_nlayer_t uxp_socket_layer = {
    TCN_SOCKET_UNIX,
    uxp_cleanup,
    uxp_socket_close,
    uxp_socket_shutdown,
    uxp_socket_opt_get,
    uxp_socket_opt_set,
    uxp_socket_timeout_get,
    uxp_socket_timeout_set,
    uxp_socket_send,
    uxp_socket_sendv,
    uxp_socket_recv
};

TCN_IMPLEMENT_CALL(jlong, Local, create)(TCN_STDARGS, jstring name,
                                         jlong pool)
{
    apr_pool_t *p = J2P(pool, apr_pool_t *);
    tcn_socket_t   *s   = NULL;
    tcn_uxp_conn_t *con = NULL;
    int sd;
    TCN_ALLOC_CSTRING(name);

    UNREFERENCED(o);
    TCN_ASSERT(pool != 0);

    if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        tcn_ThrowAPRException(e, apr_get_netos_error());
        return 0;
    }
#ifdef TCN_DO_STATISTICS
    uxp_created++;
#endif
    con = (tcn_uxp_conn_t *)apr_pcalloc(p, sizeof(tcn_uxp_conn_t));
    con->pool = p;
    con->mode = TCN_UXP_UNKNOWN;
    con->timeout = DEFTIMEOUT;
    con->sd = sd;
    con->uxaddr.sun_family = AF_UNIX;
    if (J2S(name)) {
        strcpy(con->uxaddr.sun_path, J2S(name));
        TCN_FREE_CSTRING(name);
    }
    else
        strcpy(con->uxaddr.sun_path, DEFNAME);
    s = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t));
    s->pool   = p;
    s->net    = &uxp_socket_layer;
    s->opaque = con;
    apr_pool_cleanup_register(p, (const void *)s,
                              uxp_socket_cleanup,
                              apr_pool_cleanup_null);

    apr_os_sock_put(&(con->sock), &(con->sd), p);

    return P2J(s);

}

TCN_IMPLEMENT_CALL(jint, Local, bind)(TCN_STDARGS, jlong sock,
                                      jlong sa)
{
    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
    UNREFERENCED_STDARGS;
    UNREFERENCED(sa);
    TCN_ASSERT(sock != 0);
    if (s->net->type == TCN_SOCKET_UNIX) {
        int rc;
        tcn_uxp_conn_t *c = (tcn_uxp_conn_t *)s->opaque;
        c->mode = TCN_UXP_SERVER;
        rc = bind(c->sd, (struct sockaddr *)&(c->uxaddr), sizeof(c->uxaddr));
        if (rc < 0)
            return errno;
        else
            return APR_SUCCESS;
    }
    else
        return APR_EINVAL;
}

TCN_IMPLEMENT_CALL(jint, Local, listen)(TCN_STDARGS, jlong sock,
                                        jint backlog)
{
    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
    UNREFERENCED_STDARGS;

    TCN_ASSERT(sock != 0);
    if (s->net->type == TCN_SOCKET_UNIX) {
        tcn_uxp_conn_t *c = (tcn_uxp_conn_t *)s->opaque;
        c->mode = TCN_UXP_SERVER;
        return apr_socket_listen(c->sock, (apr_int32_t)backlog);
    }
    else
        return APR_EINVAL;
}

TCN_IMPLEMENT_CALL(jlong, Local, accept)(TCN_STDARGS, jlong sock)
{
    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
    apr_pool_t   *p = NULL;
    tcn_socket_t *a = NULL;
    tcn_uxp_conn_t *con = NULL;

    UNREFERENCED(o);
    TCN_ASSERT(sock != 0);

    TCN_THROW_IF_ERR(apr_pool_create(&p, s->pool), p);
    if (s->net->type == TCN_SOCKET_UNIX) {
        apr_socklen_t len;
        tcn_uxp_conn_t *c = (tcn_uxp_conn_t *)s->opaque;
        con = (tcn_uxp_conn_t *)apr_pcalloc(p, sizeof(tcn_uxp_conn_t));
        con->pool = p;
        con->mode = TCN_UXP_ACCEPTED;
        con->timeout = c->timeout;
        len = sizeof(c->uxaddr);
        /* Block until a client connects */
        con->sd = accept(c->sd, (struct sockaddr *)&(con->uxaddr), &len);
        if (con->sd < 0) {
            tcn_ThrowAPRException(e, apr_get_os_error());
            goto cleanup;
        }
    }
    else {
        tcn_ThrowAPRException(e, APR_ENOTIMPL);
        goto cleanup;
    }
    if (con) {
#ifdef TCN_DO_STATISTICS
        apr_atomic_inc32(&uxp_accepted);
#endif
        a = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t));
        a->pool   = p;
	    a->net    = &uxp_socket_layer;
        a->opaque = con;
        apr_pool_cleanup_register(p, (const void *)a,
                                  uxp_socket_cleanup,
                                  apr_pool_cleanup_null);
        apr_os_sock_put(&(con->sock), &(con->sd), p);
    }
    return P2J(a);
cleanup:
    if (p)
        apr_pool_destroy(p);
    return 0;
}

TCN_IMPLEMENT_CALL(jint, Local, connect)(TCN_STDARGS, jlong sock,
                                         jlong sa)
{
    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
    tcn_uxp_conn_t *con = NULL;
    int rc;

    UNREFERENCED(o);
    UNREFERENCED(sa);
    TCN_ASSERT(sock != 0);
    if (s->net->type != TCN_SOCKET_UNIX)
        return APR_ENOTSOCK;
    con = (tcn_uxp_conn_t *)s->opaque;
    if (con->mode != TCN_UXP_UNKNOWN)
        return APR_EINVAL;
    do {
        rc = connect(con->sd, (const struct sockaddr *)&(con->uxaddr),
                     sizeof(con->uxaddr));
    } while (rc == -1 && errno == EINTR);

    if (rc == -1 && errno != EISCONN)
        return errno;
    con->mode = TCN_UXP_CLIENT;

    return APR_SUCCESS;
}
