/* -*-c-*- */ /* * 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 "config.h" #include "FvwmCommand.h" #include "libs/fvwmlib.h" #include "libs/fvwmsignal.h" #include "libs/Strings.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 = NULL, *FfdM_name = NULL; /* 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); SIGNAL_RETURN; } /* * 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; /*Accept reply-messages */ SetMessageMask(Fd, MX_REPLY); /* 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 fifos knowing that they don't already exist * and any lingering FvwmCommandS will not share them or remove them */ if (mkfifo(FfdM_name, FVWM_S_IRUSR | FVWM_S_IWUSR) < 0) { err_msg(FfdM_name); return -1; } if (mkfifo(FfdC_name, FVWM_S_IRUSR | FVWM_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: case MX_REPLY: 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 fvwm quits) */ void relay_packet(unsigned long type, unsigned long length, unsigned long *body) { Q *new; if (!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; }