| /* |
| * Copyright 1999-2004 The Apache Software Foundation |
| * |
| * Licensed 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. |
| */ |
| |
| /** |
| * Channel using 'plain' TCP sockets or UNIX sockets. |
| * Based on jk_sockbuf. It uses a an APR-based mechanism. |
| * The UNIX sockets are not yet in APR (the code has to been written). |
| * |
| * Properties: |
| * - host/filename |
| * - port |
| * - ndelay (Where the hell we set it?) |
| * |
| * This channel should 'live' as much as the workerenv. It is stateless. |
| * It allocates memory for endpoint private data ( using endpoint's pool ). |
| * |
| * @author: Gal Shachor <shachor@il.ibm.com> |
| * @author: Costin Manolache |
| * @author: Jean-Frederic Clere <jfrederic.clere@fujitsu-siemens.com> |
| */ |
| |
| #include "jk_map.h" |
| #include "jk_env.h" |
| #include "jk_channel.h" |
| #include "jk_global.h" |
| |
| #include <string.h> |
| #include <fcntl.h> |
| #include "jk_registry.h" |
| |
| #ifdef HAVE_UNIXSOCKETS |
| |
| /** Information specific for the socket channel |
| */ |
| typedef struct jk_channel_un_private |
| { |
| int ndelay; |
| struct sockaddr_un unix_addr; |
| char *file; |
| |
| int l_onoff; /* Nonzero to linger on close. */ |
| int l_linger; /* Time to linger. */ |
| |
| int backlog; |
| |
| int listenSocket; |
| } jk_channel_un_private_t; |
| |
| #ifndef SUN_LEN |
| /* actual length of an initialized sockaddr_un */ |
| #define SUN_LEN(su) \ |
| (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) |
| #endif |
| |
| static int JK_METHOD jk2_channel_un_close(jk_env_t *env, jk_channel_t *ch, |
| jk_endpoint_t *endpoint); |
| |
| static char *jk2_channel_un_multiValueInfo[] = { "group", NULL }; |
| static char *jk2_channel_un_setAttributeInfo[] = |
| { "file", "soLinger", "listen", |
| "level", NULL |
| }; |
| static char *jk2_channel_un_getAttributeInfo[] = { "file", "soLinger", |
| "listen", NULL |
| }; |
| |
| static int JK_METHOD jk2_channel_un_setAttribute(jk_env_t *env, |
| jk_bean_t *mbean, |
| char *name, void *valueP) |
| { |
| jk_channel_t *ch = (jk_channel_t *)mbean->object; |
| char *value = valueP; |
| jk_channel_un_private_t *socketInfo = |
| (jk_channel_un_private_t *) (ch->_privatePtr); |
| |
| if (strcmp("file", name) == 0) { |
| socketInfo->file = value; |
| } |
| else if (strcmp("soLinger", name) == 0) { |
| socketInfo->l_linger = atoi(value); |
| } |
| else if (strcmp("listen", name) == 0) { |
| socketInfo->backlog = atoi(value); |
| ch->serverSide = JK_TRUE; |
| } |
| else { |
| return jk2_channel_setAttribute(env, mbean, name, valueP); |
| } |
| return JK_OK; |
| } |
| |
| static void *JK_METHOD jk2_channel_un_getAttribute(jk_env_t *env, |
| jk_bean_t *mbean, |
| char *name) |
| { |
| jk_channel_t *ch = (jk_channel_t *)mbean->object; |
| jk_channel_un_private_t *socketInfo = |
| (jk_channel_un_private_t *) (ch->_privatePtr); |
| |
| if (strcmp("file", name) == 0) { |
| return socketInfo->file; |
| } |
| else if (strcmp("soLinger", name) == 0) { |
| return jk2_env_itoa(env, socketInfo->l_linger); |
| } |
| else if (strcmp("listen", name) == 0) { |
| return jk2_env_itoa(env, socketInfo->backlog); |
| } |
| return NULL; |
| } |
| |
| /** resolve the host IP ( jk_resolve ) and initialize the channel. |
| */ |
| static int JK_METHOD jk2_channel_un_init(jk_env_t *env, jk_bean_t *chB) |
| { |
| jk_channel_t *ch = chB->object; |
| jk_channel_un_private_t *socketInfo = |
| (jk_channel_un_private_t *) (ch->_privatePtr); |
| int rc = JK_OK; |
| int omask; |
| |
| env->l->jkLog(env, env->l, JK_LOG_INFO, "channelUn.init(): init \n"); |
| if (socketInfo->file == NULL) { |
| char *localName = ch->mbean->localName; |
| jk_config_t *cfg = ch->workerEnv->config; |
| |
| /* Set the 'name' property |
| */ |
| localName = |
| jk2_config_replaceProperties(env, cfg->map, cfg->map->pool, |
| localName); |
| |
| /* env->l->jkLog(env, env->l, JK_LOG_INFO, */ |
| /* "channelUn.init(): use name %s\n", localName ); */ |
| |
| if (localName[0] == '/') { |
| ch->mbean->setAttribute(env, ch->mbean, "file", localName); |
| } |
| env->l->jkLog(env, env->l, JK_LOG_INFO, |
| "channelUn.init(): extracted file from name %s\n", |
| socketInfo->file); |
| } |
| |
| if (socketInfo->file != NULL && socketInfo->file[0] == '/') { |
| memset(&socketInfo->unix_addr, 0, sizeof(struct sockaddr_un)); |
| socketInfo->unix_addr.sun_family = AF_UNIX; |
| strcpy(socketInfo->unix_addr.sun_path, socketInfo->file); |
| |
| if (ch->mbean->debug > 0) |
| env->l->jkLog(env, env->l, JK_LOG_DEBUG, |
| "channelUn.init(): create AF_UNIX %s\n", |
| socketInfo->file); |
| } |
| else { |
| env->l->jkLog(env, env->l, JK_LOG_ERROR, "channelUn.init(): " |
| "can't init %s errno=%d\n", socketInfo->file, errno); |
| } |
| |
| if (ch->serverSide == JK_TRUE) { |
| |
| socketInfo->listenSocket = socket(AF_UNIX, SOCK_STREAM, 0); |
| if (socketInfo->listenSocket < 0) { |
| return JK_ERR; |
| } |
| |
| omask = umask(0117); /* so that only Apache can use socket */ |
| |
| rc = bind(socketInfo->listenSocket, |
| (struct sockaddr *)&socketInfo->unix_addr, |
| SUN_LEN(&(socketInfo->unix_addr))); |
| |
| umask(omask); /* can't fail, so can't clobber errno */ |
| |
| if (rc < 0) |
| return -errno; |
| |
| listen(socketInfo->listenSocket, socketInfo->backlog); |
| |
| if (ch->mbean->debug > 0) |
| env->l->jkLog(env, env->l, JK_LOG_DEBUG, |
| "Unix socket listening on %d \n", |
| socketInfo->listenSocket); |
| /* |
| { |
| struct linger { |
| int l_onoff; |
| int l_linger; |
| } lin; |
| int rc; |
| |
| lin.l_onoff = l_onoff; |
| lin.l_linger = l_linger; |
| rc=setsockopt(sd, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin)); |
| if( rc < 0) { |
| return -errno; |
| } |
| } |
| */ |
| |
| } |
| |
| return rc; |
| } |
| |
| |
| /* |
| * Wait input event on socket for timeout ms |
| */ |
| static int JK_METHOD jk2_channel_un_hasinput(jk_env_t *env, |
| jk_channel_t *ch, |
| jk_endpoint_t *endpoint, |
| int timeout) |
| { |
| /* |
| * Should implements the select/poll for UN here |
| */ |
| return (JK_TRUE); |
| } |
| |
| /** connect to Tomcat (jk_open_socket) |
| */ |
| static int JK_METHOD jk2_channel_un_open(jk_env_t *env, |
| jk_channel_t *ch, |
| jk_endpoint_t *endpoint) |
| { |
| jk_channel_un_private_t *socketInfo = |
| (jk_channel_un_private_t *) (ch->_privatePtr); |
| int unixsock; |
| struct sockaddr_un client; |
| int clientlen; |
| |
| if (ch->serverSide) { |
| while (1) { |
| clientlen = sizeof(client); |
| |
| unixsock = |
| accept(socketInfo->listenSocket, (struct sockaddr *)&client, |
| &clientlen); |
| |
| if (ch->mbean->debug > 0) |
| env->l->jkLog(env, env->l, JK_LOG_DEBUG, |
| "channelUn.open(): accept %d %d\n", unixsock, |
| errno); |
| |
| /* XXX Should we return EINTR ? This would allow us to stop |
| */ |
| if (unixsock < 0) { |
| if (errno == EINTR) { |
| if (ch->mbean->debug > 0) |
| env->l->jkLog(env, env->l, JK_LOG_DEBUG, |
| "channelUn.open(): accept EINTR %d %d\n", |
| unixsock, errno); |
| continue; |
| } |
| else { |
| env->l->jkLog(env, env->l, JK_LOG_DEBUG, |
| "channelUn.open(): accept error %d %d %s\n", |
| unixsock, errno, strerror(errno)); |
| return -errno; |
| } |
| } |
| break; |
| } |
| } |
| else { |
| unixsock = socket(AF_UNIX, SOCK_STREAM, 0); |
| if (unixsock < 0) { |
| env->l->jkLog(env, env->l, JK_LOG_ERROR, |
| "channelUn.open(): can't create socket %d %s\n", |
| errno, strerror(errno)); |
| return JK_ERR; |
| } |
| |
| if (ch->mbean->debug > 0) |
| env->l->jkLog(env, env->l, JK_LOG_DEBUG, |
| "channelUn.open(): create unix socket %s %d\n", |
| socketInfo->file, unixsock); |
| |
| if (connect(unixsock, (struct sockaddr *)&(socketInfo->unix_addr), |
| sizeof(struct sockaddr_un)) < 0) { |
| close(unixsock); |
| env->l->jkLog(env, env->l, JK_LOG_ERROR, |
| "channelUn.connect() connect failed %d %s\n", |
| errno, strerror(errno)); |
| return JK_ERR; |
| } |
| } |
| |
| #if defined(F_SETFD) && defined(FD_CLOEXEC) |
| /* Protect the socket so that it will not be inherited by child processes */ |
| fcntl(unixsock, F_SETFD, FD_CLOEXEC); |
| #endif |
| |
| if (ch->mbean->debug > 0) |
| env->l->jkLog(env, env->l, JK_LOG_DEBUG, |
| "channelUn.open(): connect unix socket %d %s\n", |
| unixsock, socketInfo->file); |
| /* store the channel information */ |
| endpoint->sd = unixsock; |
| return JK_OK; |
| } |
| |
| /** close the socket ( was: jk2_close_socket ) |
| */ |
| static int JK_METHOD jk2_channel_un_close(jk_env_t *env, jk_channel_t *ch, |
| jk_endpoint_t *endpoint) |
| { |
| env->l->jkLog(env, env->l, JK_LOG_INFO, |
| "channelUn.close(): close unix socket %d \n", endpoint->sd); |
| close(endpoint->sd); |
| endpoint->sd = -1; |
| return JK_OK; |
| } |
| |
| |
| /** send a long message |
| * @param sd opened socket. |
| * @param b buffer containing the data. |
| * @param len length to send. |
| * @return -2: send returned 0 ? what this that ? |
| * -3: send failed. |
| * >0: total size send. |
| * @bug this fails on Unixes if len is too big for the underlying |
| * protocol. |
| * @was: jk_tcp_socket_sendfull |
| */ |
| static int JK_METHOD jk2_channel_un_send(jk_env_t *env, jk_channel_t *ch, |
| jk_endpoint_t *endpoint, |
| jk_msg_t *msg) |
| { |
| unsigned char *b; |
| int len; |
| int sent = 0; |
| int this_time; |
| int unixsock; |
| |
| msg->end(env, msg); |
| len = msg->len; |
| b = msg->buf; |
| |
| unixsock = endpoint->sd; |
| if (unixsock < 0) { |
| env->l->jkLog(env, env->l, JK_LOG_INFO, |
| "channel.apr:send() not connected %d\n", unixsock); |
| return JK_ERR; |
| } |
| |
| while (sent < len) { |
| errno = 0; |
| this_time = write(unixsock, (char *)b + sent, len - sent); |
| |
| if (ch->mbean->debug > 0) |
| env->l->jkLog(env, env->l, JK_LOG_DEBUG, |
| "channel.apr:send() write() %d %d %s\n", this_time, |
| errno, strerror(errno)); |
| if (0 == this_time) { |
| return -2; |
| } |
| if (this_time < 0) { |
| return -3; |
| } |
| sent += this_time; |
| } |
| /* return sent; */ |
| return JK_OK; |
| } |
| |
| |
| /** receive len bytes. |
| * @param sd opened socket. |
| * @param b buffer to store the data. |
| * @param len length to receive |
| * @return -1: receive failed or connection closed. |
| * >0: length of the received data. |
| * Was: tcp_socket_recvfull |
| */ |
| static int JK_METHOD jk2_channel_un_readN(jk_env_t *env, |
| jk_channel_t *ch, |
| jk_endpoint_t *endpoint, |
| unsigned char *b, int len) |
| { |
| int sd; |
| int rdlen; |
| |
| sd = endpoint->sd; |
| |
| if (sd < 0) { |
| env->l->jkLog(env, env->l, JK_LOG_INFO, |
| "channel.apr:readN() not connected %d\n", sd); |
| return -3; |
| } |
| |
| rdlen = 0; |
| |
| while (rdlen < len) { |
| int this_time = recv(sd, (char *)b + rdlen, |
| len - rdlen, 0); |
| if (this_time < 0) { |
| if (EAGAIN == errno) { |
| continue; |
| } |
| return -2; |
| } |
| if (0 == this_time) { |
| return -1; |
| } |
| rdlen += this_time; |
| } |
| return rdlen; |
| } |
| |
| |
| /** receive len bytes. |
| * @param sd opened socket. |
| * @param b buffer to store the data. |
| * @param len length to receive. |
| * @return -1: receive failed or connection closed. |
| * >0: length of the received data. |
| * Was: tcp_socket_recvfull |
| */ |
| static int JK_METHOD jk2_channel_un_recv(jk_env_t *env, jk_channel_t *ch, |
| jk_endpoint_t *endpoint, |
| jk_msg_t *msg) |
| { |
| int hlen = msg->headerLength; |
| int blen; |
| int rc = JK_OK; |
| |
| |
| blen = jk2_channel_un_readN(env, ch, endpoint, msg->buf, hlen); |
| if (blen <= 0) { |
| env->l->jkLog(env, env->l, JK_LOG_ERROR, |
| "channelUn.receive(): error receiving %d %d %s %#lx %d\n", |
| blen, errno, strerror(errno), endpoint, endpoint->sd); |
| return JK_ERR; |
| } |
| |
| blen = msg->checkHeader(env, msg, endpoint); |
| if (blen < 0) { |
| env->l->jkLog(env, env->l, JK_LOG_ERROR, |
| "channelUn.receive(): Bad header\n"); |
| return JK_ERR; |
| } |
| |
| rc = jk2_channel_un_readN(env, ch, endpoint, msg->buf + hlen, blen); |
| |
| if (rc < 0) { |
| env->l->jkLog(env, env->l, JK_LOG_ERROR, |
| "channelUn.receive(): Error receiving message body %d %d\n", |
| rc, errno); |
| return JK_ERR; |
| } |
| |
| if (ch->mbean->debug > 0) |
| env->l->jkLog(env, env->l, JK_LOG_DEBUG, |
| "channelUn.receive(): Received len=%d type=%d\n", |
| blen, (int)msg->buf[hlen]); |
| return JK_OK; |
| |
| } |
| |
| |
| int JK_METHOD jk2_channel_un_factory(jk_env_t *env, |
| jk_pool_t *pool, |
| jk_bean_t *result, |
| const char *type, const char *name) |
| { |
| jk_channel_t *ch; |
| |
| ch = (jk_channel_t *)pool->calloc(env, pool, sizeof(jk_channel_t)); |
| |
| ch->_privatePtr = (jk_channel_un_private_t *) |
| pool->calloc(env, pool, sizeof(jk_channel_un_private_t)); |
| |
| ch->recv = jk2_channel_un_recv; |
| ch->send = jk2_channel_un_send; |
| ch->open = jk2_channel_un_open; |
| ch->close = jk2_channel_un_close; |
| ch->hasinput = jk2_channel_un_hasinput; |
| ch->is_stream = JK_TRUE; |
| ch->serverSide = JK_FALSE; |
| |
| result->setAttribute = jk2_channel_un_setAttribute; |
| result->getAttribute = jk2_channel_un_getAttribute; |
| result->multiValueInfo = jk2_channel_un_multiValueInfo; |
| result->setAttributeInfo = jk2_channel_un_setAttributeInfo; |
| result->getAttributeInfo = jk2_channel_un_getAttributeInfo; |
| result->invoke = jk2_channel_invoke; |
| |
| ch->mbean = result; |
| result->object = ch; |
| result->init = jk2_channel_un_init; |
| |
| ch->workerEnv = env->getByName(env, "workerEnv"); |
| ch->workerEnv->addChannel(env, ch->workerEnv, ch); |
| |
| return JK_OK; |
| } |
| |
| #else |
| |
| int JK_METHOD jk2_channel_un_factory(jk_env_t *env, |
| jk_pool_t *pool, |
| jk_bean_t *result, |
| const char *type, const char *name) |
| { |
| env->l->jkLog(env, env->l, JK_LOG_ERROR, |
| "channelUn.factory(): Support for unix sockets is disabled, " |
| "you need to set HAVE_UNIXSOCKETS at compile time\n"); |
| result->disabled = 1; |
| return JK_FALSE; |
| } |
| #endif |