main.c

/* This is free software.  See LICENSE for terms.
 * Copyright 2004 - 2015, Patricia Kirk.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <sys/time.h>
#include <signal.h>
#include <netinet/in.h>
#include <sys/resource.h>

#if defined(__linux__)
#include <time.h>
#endif

#include "proto.h"

struct global g;

char **cmdline=NULL;

extern struct lineopts *linelist;

char smalbuf[SMALBUF];

void freeLine(struct lineopts *line) {

    if (line->prev)
	line->prev->next = line->next;
    if (line->next)
	line->next->prev = line->prev;
    if (line == linelist)
	linelist = line->next;
    FREE(line->type);
    FREE(line->argument);
    FREE(line->command);
    FREE(line->status);
    FREE(line->value);
    FREE(line->buffer);
    FREE(line->setting);
    FREE(line);
}

void freeLines() {
    struct lineopts *line, *next;

    if ((line = linelist)) {
	while (line) {
	    next = line->next;
	    freeLine(line);
	    line = next;
	}
	linelist = NULL;
    }
}

/* Set count as minus whatever to
 * force a specific refresh rate.
 */
void setRefresh(int count) {

    if (!g.refresh) {
	if (g.ctlport) {
	    g.refresh = 1;			/* so samcon can find it */
	} else if (count == g.linecount) {	/* all display lines */
	    /* Only call Redraw() on XEvent. */
	    g.refresh = (g.pixmap == 1) ? 10 : FALSE;
	    setpriority(PRIO_PROCESS, 0, 20);
	} else if (count == -1) {		/* calendar */
	    g.refresh = (g.pixmap == 1) ? 10 : 60;
	    setpriority(PRIO_PROCESS, 0, 10);
	} else if (g.moongraphic) {
	    g.refresh = (g.pixmap == 1) ? 10 : 300;
	    setpriority(PRIO_PROCESS, 0, 10);
	} else {
	    g.refresh = 1;
	}
    }
}

/* Set lines and panels, return if four panel calendar.
 * Insure that the lines are sequential in each panel.
 * Set up lines for phasecalc, one panel calendar and
 * volume control.
 */
void initLines() {
    struct lineopts *line, *nline;
    int calendar=0, panel=0, linenum=0, color=0;
    int i, j=1, k=1, curpan=0, p[]={1, 3, 2, 4};
    char *utc="";

    if (g.linecount == 1) {	/* No lines were defined. */
	parseLine("format=%l:%M|%p|%Z_linenum=1");
	parseLine("load1_linenum=2");
	parseLine("memfree_linenum=3");
	parseLine("swapfree_linenum=4");
	parseLine("processes_linenum=5");
	parseLine("updays_linenum=6");
    } else {
	for (line = linelist; line; line = line->next) {
	    if (line->index == getIndex("volume")) {
		/* Only allow one volume line.  Don`t
		 * allow user to change mixer def.
		 */
		if (g.mixer) {
		    freeLine(line);
		    continue;
		}
		if (findExecutable(smalbuf, SMALBUF, MIXER)) {
		    STRDUP(g.mixer, smalbuf);
		}
		STRDUP(line->status, "connect");
		for (i = 0; i < 2; i++) {
		    CALLOC(nline, 1, sizeof(struct lineopts), "initLines");
		    STRDUP(nline->type, line->type);
		    nline->index = line->index;
		    nline->color = line->color;
		    nline->panel = line->panel;
		    if (line->linenum)
			nline->linenum = line->linenum + 1;
		    snprintf(smalbuf, SMALBUF, "%d", i);
		    STRDUP(nline->value, smalbuf);
		    STRDUP(nline->status, "volume");
		    nline->next = line->next;
		    nline->prev = line;
		    if (line->next)
			line->next->prev = nline;
		    line->next = nline;
		    line = nline;
		}
	    }
	}
    }
    if (g.linecount > 25)
	gdayMate("initLines() too many lines defined", NULL);
    if (g.linecount > 7)
	g.panel = 1;
    bzero(&g.lines, sizeof(g.lines));
    for (line = linelist; line; line = line->next) {
	if (line->index == getIndex("calendar")) {
	    if (!calendar) {
		panel = (line->panel + !line->panel);
		linenum = (line->linenum + !line->linenum);
		color = line->color;
		if (g.calendar == 2)
		    utc = "UTC";
		if (line->setting && !strcmp(line->setting, "4")) {
		    freeLines();
		    linenum = 1 - (linenum + ((panel - 1) * 6));
		    for (j = 0; j < 4; j++) {
			g.lines[j][0] = 6;
			for (i = 1; i < 7; i++, linenum++) {
			    snprintf(smalbuf, SMALBUF,
				    "calendar_panel=%d_linenum=%d_setting=%d",
				    p[j], i, linenum);
			    line = parseLine(smalbuf);
			    snprintf(smalbuf, SMALBUF, "%s%%a|%%b|%%d", utc);
			    STRDUP(line->argument, smalbuf);
			    if (color && !linenum)
				line->color = color;
			}
		    }
		    setRefresh(-1);	/* Mark this as four panel calendar. */
		    return;
		}
		calendar = 1;
	    }
	    freeLine(line);
	    continue;
	}
	if ((i = line->panel)) {
	    g.lines[i - 1][0]++;
	    if (g.lines[i - 1][0] > 6)
		goto nope;
	} else {
	    line->panel = curpan + 1;
	    if (line->index == getIndex("volume")) {
		g.lines[curpan][0] += 3;
		if (g.lines[curpan][0] > 6) {
nope:
		    snprintf(smalbuf, SMALBUF, "initLines() too many lines in panel #%d",
			    line->panel);
		    gdayMate(smalbuf, NULL);
		}
		line = line->next;
		line->panel = curpan + 1;
		line = line->next;
		line->panel = curpan + 1;
	    } else {
		g.lines[curpan][0]++;
	    }
	}
	if (g.lines[curpan][0] == 6)
	    curpan++;
	if (line->linenum) {
	    i = line->panel - 1;
	    j = line->linenum - 1;
	    k = 1 << j;
	    if (g.lines[i][1] & k) {
		snprintf(smalbuf, SMALBUF,
			"initLines() second line numbered %d in panel %d, type \"%s\"",
			line->linenum, line->panel, line->type);
		gdayMate(smalbuf, NULL);
	    }
	    g.lines[i][1] |= k;
	} else {
	    j = line->panel - 1;
	    for (i = 0; i < 6; i++) {
		if (!(g.lines[j][1] & (1 << i))) {
		    line->linenum = i + 1;
		    g.lines[j][1] |= (1 << i);
		    break;
		}
	    }
	}
    }
    for (i = 0; i < 4; i++) {
	if (g.lines[i][1] != (1 << (g.lines[i][0] + 1)) - 1) {
	    j = 1;
	    for (line = linelist; line; line = line->next) {
		if (line->panel == i + 1)
		    line->linenum = j++;
	    }
	}
    }
    if (calendar) {
	if (g.panel) {
	    panel = 0;
	    for (i = 0; i < 4; i++) {
		if (!g.lines[i][0]) {
		    panel = i + 1;
		    break;
		}
	    }
	    if (!panel)
		goto getout;
	} else {
	    panel = 1;
	    i = 0;
	}
	g.lines[i][0] += 6;
	if (g.lines[i][0] > 6)
	    goto getout;
	linenum = 1 - linenum;
	for (i = 1; i < 7; i++, linenum++) {
	    snprintf(smalbuf, SMALBUF, "calendar_panel=%d_linenum=%d_setting=%d",
		    panel, i, linenum);
	    line = parseLine(smalbuf);
	    snprintf(smalbuf, SMALBUF, "%s%%a|%%b|%%d", utc);
	    STRDUP(line->argument, smalbuf);
	    if (!linenum)
		line->color = color;
	}
    }
    if (g.phasecalc) {
	if (g.panel) {
	    panel = 0;
	    for (i = 0; i < 4; i++) {
		if (!g.lines[i][0]) {
		    panel = i + 1;
		    g.lines[i][0] = 5;
		    break;
		}
	    }
	    if (!panel) {
getout:
		snprintf(smalbuf, SMALBUF, "initLines() the %s function requires"
			" an empty panel in four panel mode", line->type);
		gdayMate(smalbuf, NULL);
	    }
	} else {
	    panel = 1;
	    g.lines[0][0] = 5;
	}
	if (g.phasecalc == 2)	/* UTC */
	    utc = "UTC";
	else
	    utc = "";
	snprintf(smalbuf, SMALBUF, "setting=|Current|_linenum=1_panel=%d", panel);
	line = parseLine(smalbuf);
	STRDUP(line->status, "phasecalc");
	snprintf(smalbuf, SMALBUF, "moonphase_linenum=2_panel=%d", panel);
	line = parseLine(smalbuf);
	STRDUP(line->status, "phasecalc");
	snprintf(smalbuf, SMALBUF, "format=%s%%l:%%M|%%p|%%Z_linenum=3_panel=%d", utc, panel);
	line = parseLine(smalbuf);
	STRDUP(line->status, "phasecalc");
	snprintf(smalbuf, SMALBUF, "format=%s|%%A|_linenum=4_panel=%d", utc, panel);
	line = parseLine(smalbuf);
	STRDUP(line->status, "phasecalc");
	snprintf(smalbuf, SMALBUF, "format=%s|%%b|%%e,|%%Y|_linenum=5_panel=%d", utc, panel);
	line = parseLine(smalbuf);
	STRDUP(line->status, "phasecalc");
    }
}

/* Set upper, lower, left and right event
 * limits.  Set line->type specific values.
 */
void spaceLines() {
    int i, j, k=0, index, display=0;
    struct lineopts *line;
    char **args, *ptr=NULL;

    for (i = 0; i <= (g.panel * 3); i++) {
	j = 60 - (g.lines[i][0] * CHARHEIGHT);			/* j=number of unused one pixel rows */
	k = j / (g.lines[i][0] + 1);				/* k=height of one blank line */
	j = ((k + (k != 4)) * g.xmag) + g.psize * (i > 1);	/* j=location of top info line */
	k = (CHARHEIGHT + 1 + k) * g.xmag;
	for (line = linelist; line; line = line->next) {
	    if (line->panel == i + 1) {
		line->upper = j + ((line->linenum - 1) * k);	/* used for clicks and by lineToDraw */
		line->lower = line->upper + ((CHARHEIGHT + 1) * g.xmag);
		line->left = (i % 2) * g.psize;
		line->right = line->left + g.psize;
	    }
	}
    }
    for (line = linelist; line; line = line->next) {
	index = line->index;
	if (index == getIndex("display")) {
	    display++;
	    if (!line->setting) {
		STRDUP(line->setting, "|Display|");
	    }
	} else if (index == getIndex("powerstate")) {
	    if (!line->warn || !line->crit)
		gdayMate("spaceLines() warning and critical levels must"
			" be set for powerstate", strerror(EINVAL));
	    if (line->warn < line->crit) {
		snprintf(smalbuf, SMALBUF, "spaceLines() warning level %d"
			" < critical level %d for powerstate",
			line->warn, line->crit);
		logMessage(smalbuf);
	    }
	    goto putcolors;
	} else if (index == getIndex("thermal")) {
	    if (!line->warn || !line->crit) {
		snprintf(smalbuf, SMALBUF,
			"spaceLines() warning and critical levels must be"
			" set for thermal zone #%s",
			line->argument);
		gdayMate(smalbuf, strerror(EINVAL));
	    }
	    if (line->warn > line->crit) {
		snprintf(smalbuf, SMALBUF,
			"spaceLines() warning level %d > critical level %d"
			" for thermal zone #%s",
			line->warn, line->crit, line->argument);
		logMessage(smalbuf);
	    }
putcolors:
	    if (!line->color1)
		line->color1 = pixAlloc("#00ff00");
	    if (!line->color2)
		line->color2 = pixAlloc("#ffff00");
	    if (!line->color3)
		line->color3 = pixAlloc("#ff0000");
	    STRDUP(line->status, "0");
	} else if (index == getIndex("format") && line->setting) {
	    /* Set up talking clock times. */
	    if (!g.talker) {
		if (findExecutable(smalbuf, SMALBUF, TALKER)) {
		    STRDUP(g.talker, smalbuf);
		}
	    }
	    if (*line->setting == '~' || *line->setting == ':') {
		snprintf(smalbuf, SMALBUF, line->setting);
		args = readArgs(smalbuf, "return", 0);
		if (!args || parseStrtol(args[0], TRUE, "spaceLines") != 2)
		    gdayMate("spaceLines() talking clock label", strerror(EINVAL));
		ptr = args[1];
		while (*ptr == '-')
		    ptr++;
		STRDUP(line->setting, ptr);
		FREE(args);
	    } else {
		k = parseStrtol(line->setting, TRUE, "spaceLines");
		if (k != 1 && k != 5 && k != 10 && k != 15 && k != 30 && k != 60)
		    gdayMate("spaceLines() talking clock interval", strerror(EINVAL));
	    }
	} else if (index == getIndex("timer")) {
	    if (line->setting) {
		STRDUP(line->buffer, line->setting);
		snprintf(smalbuf, SMALBUF, "%d", (int)getDuration(line));
		STRDUP(line->setting, smalbuf);
		FREE(line->buffer);
	    }
	    if (line->value) {
		STRDUP(line->buffer, line->value);
		k = (int)getDuration(line);
		snprintf(smalbuf, SMALBUF, "%d", k);
		STRDUP(line->value, smalbuf);
		snprintf(smalbuf, SMALBUF, "%d", (int)(k + time(NULL)));
		STRDUP(line->buffer, smalbuf);
		STRDUP(line->status, "running");
	    }
	} else if (index == getIndex("alarm") || index == getIndex("reset")) {
	    if (line->setting) {
		if (index == getIndex("reset")) {
		    if (sscanf(line->setting, "%d:%d", &i, &j) != 2)
			goto badset;
		    k = (i * 3600) + (j * 60);
		    if (!k) {
badset:
			snprintf(smalbuf, SMALBUF,
				"spaceLines() resetting alarm interval [HH:MM] %s",
				line->setting);
			gdayMate(smalbuf, strerror(EINVAL));
		    }
		} else {
		    STRDUP(line->buffer, line->setting);
		    k = (int)getExpiration(line);
		}
		snprintf(smalbuf, SMALBUF, "%d", k);
		STRDUP(line->setting, smalbuf);
		FREE(line->buffer);
	    }
	    if (line->value) {
		STRDUP(line->buffer, line->value);
		snprintf(smalbuf, SMALBUF, "%d", (int)getExpiration(line));
		STRDUP(line->value, smalbuf);
		STRDUP(line->buffer, smalbuf);
		STRDUP(line->status, "running");
	    }
	} else if (line->argument && index >= getIndex("user") &&
		index <= getIndex("idle")) {
	    i = parseStrtol(line->argument, TRUE, "initLines");
	    if (!i) {
		FREE(line->argument);
	    } else if (i > g.ncpu) {
		snprintf(smalbuf, SMALBUF, "initLines() cpu \"%d\" for line=\"%s\""
			" is greater than #cpu's = \"%d\"", i, line->type, g.ncpu);
		gdayMate(smalbuf, NULL);
	    } else {
		g.split = 1;
	    }
	}
    }
    setRefresh(display);
}
#if WITH_REMEXEC || WITH_TIMESYNC
void bindPort(int *port, int *sock, const char *portname) {
    struct sockaddr_in sockaddr;
    struct timeval tv;
    char *buffer=NULL;
    int i, temp=0;

    CALLOC(buffer, BUFSIZE, sizeof(char), "bindPort");
    if ((*sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
	snprintf(buffer, BUFSIZE, "bindPort() failed to open %s socket", portname);
	gdayMate(buffer, strerror(errno));
    }
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    setsockopt(*sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
    setsockopt(*sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    bzero(&sockaddr, sizeof(sockaddr));
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(port[0]);
    sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    temp = port[0];
    if (temp < 0) {
	/* No number given, try to find a usable port. */
	srandom(time(NULL));
	i = 0;
	for (;; i++) {
	    if (i > MAXTRIES) {
		snprintf(buffer, BUFSIZE,
			"bindPort() exceeded maximum tries to find usable %s port in range %d - %d",
			portname, LODYNAMIC, HIDYNAMIC);
		gdayMate(buffer, strerror(EAGAIN));
	    }
	    temp = random();
	    if (temp >= LODYNAMIC && temp <= HIDYNAMIC) {
		sockaddr.sin_port = htons(temp);
		if (bind(*sock, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) == -1) {
		    close(*sock);
		    continue;
		}
		snprintf(buffer, BUFSIZE, "%s started on port %d", portname, temp);
		logMessage(buffer);
		port[0] = temp;
		break;
	    }
	}
    } else if (bind(*sock, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) == -1) {
	snprintf(buffer, BUFSIZE, "bindPort() failed to bind %s socket #%d on port %d",
		portname, *sock, temp);
	gdayMate(buffer, strerror(errno));
    }
    if (listen(*sock, 1) == -1) {
	snprintf(buffer, BUFSIZE, "bindPort() failed to set listen on %s socket #%d",
		portname, *sock);
	gdayMate(buffer, strerror(errno));
    }
    if (fcntl(*sock, F_SETFL, O_NONBLOCK) == -1) {
	snprintf(buffer, BUFSIZE, "bindPort() failed to set non-block on %s socket #%d",
		portname, *sock);
	gdayMate(buffer, strerror(errno));
    }
    FREE(buffer);
}
#endif	/* WITH_REMEXEC || WITH_TIMESYNC */

int main(int argc, char **argv) {
    struct itimerval itv;
#if WITH_STEPEXEC
    struct sockaddr_un sockaddr;
#endif	/* WITH_STEPEXEC */

    bzero(&g, sizeof(struct global));
    cmdline = argv;
    if (!(g.effectiveuid = geteuid()))
	g.pingsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    g.realuid = getuid();
    seteuid(g.realuid);
    parseArgs(argc, argv);
    g.running = 1;
    g.ppid = getpid();
#if WITH_REMEXEC
    if (g.execport)
	bindPort(&g.execport, &g.execsock, "remote execute");
#endif	/* WITH_REMEXEC */
#if WITH_TIMESYNC
    if (g.timeserver)
	/* Only do this on the master server. */
	bindPort(&g.timeport, &g.timesock, "time server");
    else if (g.settime)
	setClock();
#endif	/* WITH_TIMESYNC */
    if (g.daemonize) {
	if (daemon(1, 0))
	    /* Detach from the controlling terminal.
	     * Close stdin, stdout and stderr.
	     */
	    gdayMate("main() daemon() failed", strerror(errno));
	/* The parent process now has a new PID. */
	g.ppid = getpid();
	if (g.ctlport) {
	    bzero(&itv, sizeof(itv));
	    itv.it_interval.tv_sec = itv.it_value.tv_sec = 1;
	    setitimer(ITIMER_REAL, &itv, NULL);
	    signal(SIGALRM, checkCtl);
	}
	do {
	    if ((g.cpid = fork()) == -1)
		gdayMate("main() fork() failed", strerror(errno));
	    waitpid(g.cpid, NULL, 0);
	} while (getpid() == g.ppid);
	if (g.ctlport) {
	    bzero(&itv, sizeof(itv));
	    setitimer(ITIMER_REAL, &itv, NULL);
	}
    }
    initLines();
    getOSVals();
    initWindow(argc, argv);
    spaceLines();
    if (!g.moongraphic)
	initPixlist();
#if WITH_STEPEXEC
    if (g.stepfile) {
	if ((g.stepsock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
	    gdayMate("main() unable to create afterstep socket", strerror(errno));
	bzero(&sockaddr, sizeof(sockaddr));
	sockaddr.sun_family = AF_UNIX;
	if (stringcpy(sockaddr.sun_path, g.stepfile, sizeof(sockaddr.sun_path)) < 0)
	    gdayMate("main() afterstep socket path too long", strerror(errno));
	if (connect(g.stepsock, (struct sockaddr *)&sockaddr, sizeof(struct sockaddr_un)))
	    gdayMate("main() unable to connect to afterstep socket", strerror(errno));
	/* Initialize the connection. */
	execFunc(NULL);
    }
#endif	/* WITH_STEPEXEC */
    for (;;) {
	/* Update ten times per second. */
	Update();
	usleep(100000);
    }
}
* * o * *