/* $Id: FvwmCommandS.c,v 1.27.2.1 2006/12/09 09:17:44 griph Exp $
* $Source: /home/cvs/fvwm/fvwm/modules/FvwmCommand/FvwmCommandS.c,v $
*
* Fvwm command input interface.
*
* Copyright 1997, Toshi Isogai. No guarantees or warantees or anything
* are provided. Use this program at your own risk. Permission to use
* this program for any purpose is given,
* as long as the copyright is kept intact.
*
*/
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "FvwmCommand.h"
#include "libs/fvwmlib.h"
#include "libs/fvwmsignal.h"
#define MYNAME "FvwmCommandS"
static int Fd[2]; /* pipes to fvwm */
static int FfdC; /* command fifo file discriptors */
static int FfdM; /* message fifo file discriptors */
static struct stat stat_buf;
static char *FfdC_name, *FfdM_name; /* fifo names */
static ino_t FfdC_ino, FfdM_ino; /* fifo inode numbers */
int open_fifos(const char *f_stem);
void close_fifos(void);
void close_pipes(void);
void err_msg(const char *msg);
void err_quit(const char *msg) __attribute__((noreturn));
void process_message(unsigned long type,unsigned long *body);
void relay_packet(unsigned long, unsigned long, unsigned long *);
void server(char *);
static RETSIGTYPE sig_handler(int);
static char *bugger_off = "killme\n";
/* a queue of messages for FvwmCommand to receive */
typedef struct Q
{
unsigned long length;
unsigned long sent;
char *body;
struct Q *next;
} Q;
/* head and tail of the queue */
static Q *Qstart = NULL;
static Q *Qlast = NULL;
/* flag to indicate new queue items available */
static Bool queueing = False;
int main(int argc, char *argv[])
{
char *fifoname;
if (argc < FARGS)
{
fprintf(stderr, "%s version %s should only be executed by fvwm!\n",
MYNAME, VERSION);
exit(1);
}
if (argc == FARGS + 1)
{
fifoname = argv[FARGS];
}
else
{
fifoname = NULL;
}
#ifdef HAVE_SIGACTION
{
struct sigaction sigact;
sigemptyset(&sigact.sa_mask);
sigaddset(&sigact.sa_mask, SIGINT);
sigaddset(&sigact.sa_mask, SIGHUP);
sigaddset(&sigact.sa_mask, SIGQUIT);
sigaddset(&sigact.sa_mask, SIGTERM);
sigaddset(&sigact.sa_mask, SIGPIPE);
#ifdef SA_RESTART
sigact.sa_flags = SA_RESTART;
#else
sigact.sa_flags = 0;
#endif
sigact.sa_handler = sig_handler;
sigaction(SIGINT, &sigact, NULL);
sigaction(SIGHUP, &sigact, NULL);
sigaction(SIGQUIT, &sigact, NULL);
sigaction(SIGTERM, &sigact, NULL);
sigaction(SIGPIPE, &sigact, NULL);
}
#else
#ifdef USE_BSD_SIGNALS
fvwmSetSignalMask(sigmask(SIGINT) | sigmask(SIGHUP) | sigmask(SIGQUIT)
| sigmask(SIGTERM) | sigmask(SIGPIPE));
#endif
signal(SIGPIPE, sig_handler);
signal(SIGINT, sig_handler);
signal(SIGQUIT, sig_handler);
signal(SIGHUP, sig_handler);
signal(SIGTERM, sig_handler);
#ifdef HAVE_SIGINTERRUPT
siginterrupt(SIGINT, 0);
siginterrupt(SIGHUP, 0);
siginterrupt(SIGQUIT, 0);
siginterrupt(SIGTERM, 0);
siginterrupt(SIGPIPE, 0);
#endif
#endif
Fd[0] = atoi(argv[1]);
Fd[1] = atoi(argv[2]);
server(fifoname);
close_pipes();
return 1;
}
/*
* Signal handler
*/
static RETSIGTYPE
sig_handler(int signo)
{
fvwmSetTerminate(signo);
}
/*
* setup server and communicate with fvwm and the client
*/
void server (char *name)
{
char *f_stem;
int len;
fd_set fdrset, fdwset;
char buf[MAX_MODULE_INPUT_TEXT_LEN + 1]; /* command receiving buffer */
char cmd[MAX_MODULE_INPUT_TEXT_LEN + 1];
int ix,cix;
struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
if (name == NULL)
{
if ((f_stem = fifos_get_default_name()) == NULL)
{
exit(-1);
}
}
else
{
f_stem = name;
}
if (open_fifos(f_stem) < 0)
{
exit (-1);
}
cix = 0;
/* tell fvwm we're running */
SendFinishedStartupNotification(Fd);
while (!isTerminated)
{
int ret;
FD_ZERO(&fdrset);
FD_ZERO(&fdwset);
FD_SET(FfdC, &fdrset);
FD_SET(Fd[1], &fdrset);
if (queueing)
FD_SET(FfdM, &fdwset);
ret = fvwmSelect(FD_SETSIZE, &fdrset, &fdwset, 0, queueing ? &tv : NULL);
if (ret < 0)
continue;
if (queueing && ret == 0) {
/* a timeout has occurred, this means the pipe to FvwmCommand is full
* dump any messages in the queue that have not been partially sent */
Q *q1, *q2;
#ifdef PARANOMIA
/* do nothing if there are no messages (should never happen) */
if (!Qstart) {
queueing = False;
continue;
}
#endif
q1 = Qstart->next;
/* if the first message has been partially sent don't remove it */
if (!Qstart->sent) {
free(Qstart->body);
free(Qstart);
Qstart = NULL;
} else
fprintf(stderr, "FvwmCommandS: leaving a partially sent message in the queue\n");
/* now remove the rest of the message queue */
while ((q2 = q1) != NULL) {
q1 = q1->next;
free(q2->body);
free(q2);
}
/* there is either one message left (partially complete) or none */
Qlast = Qstart;
queueing = False;
continue;
}
if (FD_ISSET(Fd[1], &fdrset))
{
FvwmPacket* packet = ReadFvwmPacket(Fd[1]);
if (packet == NULL)
{
break;
}
process_message(packet->type, packet->body);
}
if (FD_ISSET(FfdC, &fdrset))
{
/*
* This descriptor is non-blocking, hence we should never
* block here! Also, the select() call should guarantee that
* there is something here to read, and the signal handling
* should be restarting interrupted reads. Together, these
* should severely restrict the types of errors that we can see.
*/
len = read(FfdC, buf, MAX_MODULE_INPUT_TEXT_LEN);
if (len <= 0)
{
if ((len < 0) && (errno == EAGAIN))
{
/*
* This probably won't happen - the select() has told us that
* there's something to read. The only possible thing that could
* cause this is if somebody else read the data first ... (who?)
*/
continue;
}
/*
* Any other error, such as an invalid descriptor (?) or someone
* closing the remote end of our pipe, and we give up!
*/
err_quit("reading fifo");
}
/* in case of multiple long lines */
for (ix = 0; ix < len; ix++)
{
cmd[cix] = buf[ix];
if (cmd[cix] == '\n')
{
cmd[cix] = '\0';
cix = 0;
if (StrHasPrefix(bugger_off, cmd))
/* fvwm will close our pipes when it has processed this */
SendQuitNotification(Fd);
else
SendText(Fd, cmd, 0);
}
else if (cix >= MAX_MODULE_INPUT_TEXT_LEN)
{
err_msg("command too long");
cix = 0;
}
else
{
cix++;
}
} /* for */
} /* FD_ISSET */
if (queueing && FD_ISSET(FfdM, &fdwset))
{
int sent;
Q *q = Qstart;
/* send one packet to FvwmCommand, try to send it all but cope
* with partial success */
sent = write(FfdM, q->body + q->sent, q->length - q->sent);
if (sent == q->length - q->sent) {
Qstart = q->next;
free(q->body);
free(q);
if (Qstart == NULL) {
Qlast = NULL;
queueing = False;
}
} else if (sent >= 0)
q->sent += sent;
}
} /* while */
}
/*
* close fifos and pipes
*/
void close_pipes(void)
{
static char is_closed = 0;
/* prevent that this is executed twice */
if (!is_closed)
{
is_closed = 1;
close(Fd[0]);
close(Fd[1]);
close_fifos();
}
}
void close_fifos(void)
{
struct stat stat_buf;
/* only unlink this file if it is the same as FfdC */
/* I know there's a race here between the stat and the unlink, if you know
* of a way to unlink from a fildes use it here */
if ((stat(FfdC_name, &stat_buf) == 0) && (stat_buf.st_ino == FfdC_ino))
unlink(FfdC_name);
/* only unlink this file if it is the same as FfdM */
if ((stat(FfdM_name, &stat_buf) == 0) && (stat_buf.st_ino == FfdM_ino))
unlink(FfdM_name);
/* construct the name of the lock file and remove it */
FfdM_name[strlen(FfdM_name)-1] = 'R';
unlink(FfdM_name);
close(FfdM);
close(FfdC);
FfdC = -1;
FfdM = -1;
}
/*
* open fifos
*/
int open_fifos(const char *f_stem)
{
/* create 2 fifos */
FfdC_name = malloc(strlen(f_stem) + 2);
if (FfdC_name == NULL)
{
err_msg( "allocating command" );
return -1;
}
FfdM_name = malloc(strlen(f_stem) + 2);
if (FfdM_name == NULL)
{
err_msg("allocating message");
return -1;
}
strcpy(FfdC_name, f_stem);
strcpy(FfdM_name, f_stem);
strcat(FfdC_name, "C");
strcat(FfdM_name, "M");
/* check to see if another FvwmCommandS is running by trying to talk to it */
FfdC = open(FfdC_name, O_RDWR | O_NONBLOCK | O_NOFOLLOW);
/* remove the fifo's if they exist */
unlink(FfdM_name);
unlink(FfdC_name);
/* If a previous FvwmCommandS is lingering tell it to go away
* It will check that it owns the fifo's before removing them so no
* there is no need to wait before creating our own */
if (FfdC > 0)
{
write(FfdC, bugger_off, strlen(bugger_off));
close(FfdC);
}
/* now we can create the fifo's knowing that they don't already exist
* and any lingering FvwmCommandS will not share them or remove them */
if (mkfifo(FfdM_name, S_IRUSR | S_IWUSR) < 0)
{
err_msg(FfdM_name);
return -1;
}
if (mkfifo(FfdC_name, S_IRUSR | S_IWUSR) < 0)
{
err_msg(FfdC_name);
return -1;
}
if ((FfdM = open(FfdM_name, O_RDWR | O_NONBLOCK | O_NOFOLLOW)) < 0)
{
err_msg("opening message fifo");
return -1;
}
if ((FfdC = open(FfdC_name, O_RDWR | O_NONBLOCK | O_NOFOLLOW)) < 0)
{
err_msg("opening command fifo");
return -1;
}
/* get the inode numbers for use when quiting */
if (fstat(FfdC, &stat_buf) != 0)
{
err_msg("stat()ing command fifo");
return -1;
}
else
{
FfdC_ino = stat_buf.st_ino;
if (!(S_IFIFO & stat_buf.st_mode))
{
err_msg("command fifo is not a fifo");
return -1;
}
if (stat_buf.st_mode & (S_IRWXG | S_IRWXO))
{
err_msg("command fifo is too permissive");
return -1;
}
}
if (fstat(FfdM, &stat_buf) != 0)
{
err_msg("stat()ing message fifo");
return -1;
}
else
{
FfdM_ino = stat_buf.st_ino;
if (!(S_IFIFO & stat_buf.st_mode))
{
err_msg("message fifo is not a fifo");
return -1;
}
if (stat_buf.st_mode & (S_IRWXG | S_IRWXO))
{
err_msg("message fifo is too permissive");
return -1;
}
}
return 0;
}
/*
* Process window list messages
*/
void process_message(unsigned long type,unsigned long *body)
{
int msglen;
switch (type)
{
case M_ADD_WINDOW:
case M_CONFIGURE_WINDOW:
relay_packet(type, sizeof(struct ConfigWinPacket) * SOL, body);
break;
case M_ICON_LOCATION:
case M_ICONIFY:
relay_packet(type, 7 * SOL, body);
break;
case M_MINI_ICON:
relay_packet(type, 6 * SOL, body);
break;
case M_NEW_PAGE:
relay_packet(type, 5 * SOL, body);
break;
case M_NEW_DESK:
relay_packet(type, 1 * SOL, body);
break;
case M_END_WINDOWLIST:
case M_END_CONFIG_INFO:
relay_packet(type, 0 * SOL, body);
break;
case M_WINDOW_NAME:
case M_ICON_NAME:
case M_RES_CLASS:
case M_RES_NAME:
case M_ICON_FILE:
case M_DEFAULTICON:
case M_ERROR:
case M_STRING:
case M_CONFIG_INFO:
msglen = strlen((char *)&body[3]);
relay_packet(type, msglen + 1 + 3 * SOL, body);
break;
default:
relay_packet(type, 4 * SOL, body);
}
}
/*
* print error message on stderr and exit
*/
void err_msg(const char *msg)
{
fprintf(stderr, "%s server error in %s, %s\n", MYNAME, msg, strerror(errno));
}
void err_quit(const char *msg)
{
err_msg(msg);
close_pipes();
exit(1);
}
/*
* relay packet to front-end
* this is now implemented by simply adding the packet to a queue
* and letting the main select loop handle sending from the queue to
* the front end. In this way FvwmCommandS is always responsive to commands
* from the input pipes. (it will also die a lot faster when fvwm2 quits)
*/
void relay_packet(unsigned long type, unsigned long length,
unsigned long *body)
{
Q *new;
if (!length || !body)
return;
new = (Q *)safemalloc(sizeof(Q));
new->length = length + 2 * SOL;
new->sent = 0L;
new->body = safemalloc(new->length);
memcpy(new->body, &type, SOL);
memcpy(new->body + SOL, &length, SOL);
memcpy(new->body + 2 * SOL, body, length);
new->next = NULL;
if (Qlast)
Qlast->next = new;
Qlast = new;
if (!Qstart)
Qstart = Qlast;
queueing = True;
}
syntax highlighted by Code2HTML, v. 0.9.1