/* $Id: inndcomm.c 7414 2005-10-09 03:58:19Z eagle $
**
** Library routines to let other programs control innd.
*/
#include "config.h"
#include "clibrary.h"
#include "portable/time.h"
#include "portable/socket.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
/* Needed on AIX 4.1 to get fd_set and friends. */
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#ifdef HAVE_UNIX_DOMAIN_SOCKETS
# include <sys/un.h>
#endif
#include "inn/innconf.h"
#include "inndcomm.h"
#include "libinn.h"
#include "paths.h"
static char *ICCsockname = NULL;
#ifdef HAVE_UNIX_DOMAIN_SOCKETS
static struct sockaddr_un ICCserv;
static struct sockaddr_un ICCclient;
#endif
static int ICCfd;
static int ICCtimeout;
const char *ICCfailure;
/*
** Set the timeout.
*/
void
ICCsettimeout(int i)
{
ICCtimeout = i;
}
/*
** Get ready to talk to the server.
*/
int
ICCopen(void)
{
int mask, oerrno, fd;
int size = 65535;
if (innconf == NULL) {
if (!innconf_read(NULL)) {
ICCfailure = "innconf";
return -1;
}
}
/* Create a temporary name. mkstemp is complete overkill here and is used
only because it's convenient. We don't use it properly, since we
actually need to create a socket or named pipe, so there is a race
condition here. It doesn't matter, since pathrun isn't world-writable
(conceivably two processes could end up using the same temporary name
at the same time, but the worst that will happen is that one process
will delete the other's temporary socket). */
if (ICCsockname == NULL)
ICCsockname = concatpath(innconf->pathrun, _PATH_TEMPSOCK);
fd = mkstemp(ICCsockname);
if (fd < 0) {
ICCfailure = "mkstemp";
return -1;
}
close(fd);
if (unlink(ICCsockname) < 0 && errno != ENOENT) {
ICCfailure = "unlink";
return -1;
}
#ifdef HAVE_UNIX_DOMAIN_SOCKETS
/* Make a socket and give it the name. */
if ((ICCfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
ICCfailure = "socket";
return -1;
}
/* Adjust the socket buffer size to accomodate large responses. Ignore
failure; the message may fit anyway, and if not, we'll fail below. */
setsockopt(ICCfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
memset(&ICCclient, 0, sizeof ICCclient);
ICCclient.sun_family = AF_UNIX;
strlcpy(ICCclient.sun_path, ICCsockname, sizeof(ICCclient.sun_path));
mask = umask(0);
if (bind(ICCfd, (struct sockaddr *) &ICCclient, SUN_LEN(&ICCclient)) < 0) {
oerrno = errno;
umask(mask);
errno = oerrno;
ICCfailure = "bind";
return -1;
}
umask(mask);
/* Name the server's socket. */
memset(&ICCserv, 0, sizeof ICCserv);
ICCserv.sun_family = AF_UNIX;
strlcpy(ICCserv.sun_path, innconf->pathrun, sizeof(ICCserv.sun_path));
strlcat(ICCserv.sun_path, "/", sizeof(ICCserv.sun_path));
strlcat(ICCserv.sun_path, _PATH_NEWSCONTROL, sizeof(ICCserv.sun_path));
#else /* !HAVE_UNIX_DOMAIN_SOCKETS */
/* Make a named pipe and open it. */
mask = umask(0);
if (mkfifo(ICCsockname, 0666) < 0) {
oerrno = errno;
umask(mask);
errno = oerrno;
ICCfailure = "mkfifo";
return -1;
}
umask(mask);
if ((ICCfd = open(ICCsockname, O_RDWR)) < 0) {
ICCfailure = "open";
return -1;
}
#endif /* !HAVE_UNIX_DOMAIN_SOCKETS */
ICCfailure = NULL;
return 0;
}
/*
** Close down.
*/
int
ICCclose(void)
{
int i;
ICCfailure = NULL;
i = 0;
if (close(ICCfd) < 0) {
ICCfailure = "close";
i = -1;
}
if (unlink(ICCsockname) < 0 && errno != ENOENT) {
ICCfailure = "unlink";
i = -1;
}
return i;
}
/*
** Get the server's pid.
*/
static pid_t
ICCserverpid(void)
{
pid_t pid;
FILE *F;
char *path;
char buff[SMBUF];
pid = 1;
path = concatpath(innconf->pathrun, _PATH_SERVERPID);
F = fopen(path, "r");
free(path);
if (F != NULL) {
if (fgets(buff, sizeof buff, F) != NULL)
pid = atol(buff);
fclose(F);
}
return pid;
}
/*
** See if the server is still there. When in doubt, assume yes. Cache the
** PID since a rebooted server won't know about our pending message.
*/
static bool
ICCserveralive(pid_t pid)
{
if (kill(pid, 0) > 0 || errno != ESRCH)
return true;
return false;
}
/*
** Send an arbitrary command to the server.
**
** There is a protocol version (one-byte) on the front of the message,
** followed by a two byte length count. The length includes the protocol
** byte and the length itself. This differs from the protocol in much
** earlier versions of INN.
*/
int
ICCcommand(char cmd, const char *argv[], char **replyp)
{
char *buff;
char *p;
const char *q;
char save;
int i ;
#ifndef HAVE_UNIX_DOMAIN_SOCKETS
int fd;
char *path;
#endif
int len;
fd_set Rmask;
struct timeval T;
pid_t pid;
ICC_MSGLENTYPE rlen;
ICC_PROTOCOLTYPE protocol;
size_t bufsiz = 64 * 1024 - 1;
/* Is server there? */
pid = ICCserverpid();
if (!ICCserveralive(pid)) {
ICCfailure = "dead server";
return -1;
}
/* Get the length of the buffer. */
buff = xmalloc(bufsiz);
if (replyp)
*replyp = NULL;
/* Advance to leave space for length + protocol version info. */
buff += HEADER_SIZE;
bufsiz -= HEADER_SIZE;
/* Format the message. */
snprintf(buff, bufsiz, "%s%c%c", ICCsockname, SC_SEP, cmd);
for (p = buff + strlen(buff), i = 0; (q = argv[i]) != NULL; i++) {
*p++ = SC_SEP;
*p = '\0';
strlcat(buff, q, bufsiz);
p += strlen(q);
}
/* Send message. */
ICCfailure = NULL;
len = p - buff + HEADER_SIZE;
rlen = htons(len);
/* now stick in the protocol version and the length. */
buff -= HEADER_SIZE;
protocol = ICC_PROTOCOL_1;
memcpy(buff, &protocol, sizeof(protocol));
memcpy(buff + sizeof(protocol), &rlen, sizeof(rlen));
#ifdef HAVE_UNIX_DOMAIN_SOCKETS
if (sendto(ICCfd, buff, len, 0,(struct sockaddr *) &ICCserv,
SUN_LEN(&ICCserv)) < 0) {
free(buff);
ICCfailure = "sendto";
return -1;
}
#else /* !HAVE_UNIX_DOMAIN_SOCKETS */
path = concatpath(innconf->pathrun, _PATH_NEWSCONTROL);
fd = open(path, O_WRONLY);
free(path);
if (fd < 0) {
free(buff);
ICCfailure = "open";
return -1;
}
if (write(fd, buff, len) != len) {
i = errno;
free(buff);
close(fd);
errno = i;
ICCfailure = "write";
return -1;
}
close(fd);
#endif /* !HAVE_UNIX_DOMAIN_SOCKETS */
/* Possibly get a reply. */
switch (cmd) {
default:
if (ICCtimeout >= 0)
break;
/* FALLTHROUGH */
case SC_SHUTDOWN:
case SC_XABORT:
case SC_XEXEC:
free(buff);
return 0;
}
/* Wait for the reply. */
for ( ; ; ) {
FD_ZERO(&Rmask);
FD_SET(ICCfd, &Rmask);
T.tv_sec = ICCtimeout ? ICCtimeout : 120;
T.tv_usec = 0;
i = select(ICCfd + 1, &Rmask, NULL, NULL, &T);
if (i < 0) {
free(buff);
ICCfailure = "select";
return -1;
}
if (i > 0 && FD_ISSET(ICCfd, &Rmask))
/* Server reply is there; go handle it. */
break;
/* No data -- if we timed out, return. */
if (ICCtimeout) {
free(buff);
errno = ETIMEDOUT;
ICCfailure = "timeout";
return -1;
}
if (!ICCserveralive(pid)) {
free(buff);
ICCfailure = "dead server";
return -1;
}
}
#ifdef HAVE_UNIX_DOMAIN_SOCKETS
/* Read the reply. */
i = RECVorREAD(ICCfd, buff, bufsiz);
if ((unsigned int) i < HEADER_SIZE) {
free(buff);
ICCfailure = "read";
return -1;
}
memcpy(&protocol, buff, sizeof(protocol));
memcpy(&rlen, buff + sizeof(protocol), sizeof(rlen));
rlen = ntohs(rlen);
if (i != rlen) {
free(buff);
ICCfailure = "short read";
return -1;
}
if (protocol != ICC_PROTOCOL_1) {
free(buff);
ICCfailure = "protocol mismatch";
return -1;
}
memmove(buff, buff + HEADER_SIZE, rlen - HEADER_SIZE);
i -= HEADER_SIZE;
buff[i] = '\0';
#else /* !HAVE_UNIX_DOMAIN_SOCKETS */
i = RECVorREAD(ICCfd, buff, HEADER_SIZE);
if (i != HEADER_SIZE)
return -1;
memcpy(&protocol, buff, sizeof(protocol));
memcpy(&rlen, buff + sizeof(protocol), sizeof(rlen));
rlen = ntohs(rlen) - HEADER_SIZE;
i = RECVorREAD(ICCfd, buff, rlen);
if (i != rlen) {
ICCfailure = "short read";
return -1;
}
buff[i] = '\0';
if (protocol != ICC_PROTOCOL_1) {
ICCfailure = "protocol mismatch";
return -1;
}
#endif /* !HAVE_UNIX_DOMAIN_SOCKETS */
/* Parse the rest of the reply; expected to be like
<exitcode><space><text>" */
i = 0;
if (CTYPE(isdigit, buff[0])) {
for (p = buff; *p && CTYPE(isdigit, *p); p++)
continue;
if (*p) {
save = *p;
*p = '\0';
i = atoi(buff);
*p = save;
}
}
if (replyp)
*replyp = buff;
else
free(buff);
return i;
}
/*
** Send a "cancel" command.
*/
int
ICCcancel(const char *msgid)
{
const char *args[2];
args[0] = msgid;
args[1] = NULL;
return ICCcommand(SC_CANCEL, args, NULL);
}
/*
** Send a "go" command.
*/
int
ICCgo(const char *why)
{
const char *args[2];
args[0] = why;
args[1] = NULL;
return ICCcommand(SC_GO, args, NULL);
}
/*
** Send a "pause" command.
*/
int
ICCpause(const char *why)
{
const char *args[2];
args[0] = why;
args[1] = NULL;
return ICCcommand(SC_PAUSE, args, NULL);
}
/*
** Send a "reserve" command.
*/
int
ICCreserve(const char *why)
{
const char *args[2];
args[0] = why;
args[1] = NULL;
return ICCcommand(SC_RESERVE, args, NULL);
}
syntax highlighted by Code2HTML, v. 0.9.1