--- /dev/null
+
+all Involves metad meta
+
+metad Cload metad.o mainloop.o classes.o commands.o control.o daemon.o stream.o read_config.o service.o version.o skip.o strlistdup.o error.o strsplit.o strccmp.o broadcast.o ports.o sendlist.o loadstate.o recvlist.o StrDup
+
+meta Cload meta.o sendcmd.o broadcast.o dlink.o args.o version.o skip.o ports.o recvlist.o prtime.o StrDup
+
+StrDup Involves/ultrix strdup.o
+StrDup Involves/!ultrix
+
+#AutoBuilder
+args.o C args.c
+args.c Includes args.h
+broadcast.o C broadcast.c
+classes.o C classes.c
+classes.c Includes metad.h
+commands.o C commands.c
+commands.c Includes metad.h skip.h
+control.o C control.c
+control.c Includes metad.h
+daemon.o C daemon.c
+daemon.c Includes metad.h
+dlink.o C dlink.c
+dlink.c Includes dlink.h
+error.o C error.c
+error.c Includes metad.h
+loadstate.o C loadstate.c
+loadstate.c Includes metad.h skip.h
+mainloop.o C mainloop.c
+mainloop.c Includes metad.h skip.h
+meta.o C meta.c
+meta.c Includes metad.h args.h dlink.h
+metad.o C metad.c
+metad.c Includes metad.h
+nargs.o C nargs.c
+nargs.c Includes args.h
+ports.o C ports.c
+prtime.o C prtime.c
+read_config.o C read_config.c
+read_config.c Includes metad.h
+recvlist.o C recvlist.c
+sendcmd.o C sendcmd.c
+sendcmd.c Includes skip.h metad.h
+sendlist.o C sendlist.c
+sendlist.c Includes metad.h skip.h
+service.o C service.c
+service.c Includes metad.h skip.h
+skip.o C skip.c
+strccmp.o C strccmp.c
+strdup.o C strdup.c
+stream.o C stream.c
+stream.c Includes metad.h
+strlistdup.o C strlistdup.c
+strsplit.o C strsplit.c
+version.o C version.c
--- /dev/null
+list
+list <service>
+version
+broad <message>
+die
+disable <service>
+enable <service>
+kick <service> [<arg>]
+run <service> [<arg>]
+kill <signum/name> <service>
+kill <signum/name> <process-id>
+reread
+# need leading number<space> for backwardsness
+
+config file - parsed by strsplit
+
+ service type options /path/of/prog args
+
+options may be independant
+ max= crash= dir= user= enabled=
+or type dependant
+ daemon:
+ min= period=
+ stream:
+ port=
+ listen:
+ port=
\ No newline at end of file
--- /dev/null
+
+Be careful not to miss a child exit (it has happened) - use non-block wait
+log stuff.
+when=time for daemon
+
+optionally connect pipe to stdout/stderr and wait until pipe closed.
+optionally have pidfile from which pid may be read if child exits but pipe still open
\ No newline at end of file
--- /dev/null
+
+/*
+ * args - a getopt link argument processor
+ *
+ * void *handle = args_init(argc, argv, "argctl");
+ *
+ * args_next(handle, &pos, &inv, &optarg) -> BAD_FLAG/END_ARGS/NO_FLAG/opt
+ *
+ * argctl = {<optchar><typechar>}*
+ * typechar == ? -f or -~f
+ * + -f arg or -~f
+ * - -f or -~f arg
+ * : -f arg only
+ *
+ *
+ */
+
+#define IN_ARGS
+#include "args.h"
+void *malloc(int);
+struct handle
+{
+ int argc;
+ char **argv;
+ char *ctl;
+ int nextarg, nextchar;
+};
+#define NULLH ((struct handle *)0)
+
+struct handle *args_init(int argc, char **argv, char *ctl)
+{
+ /* if ctl is bad, return NULL, else a handle */
+ int i;
+ struct handle *h;
+ for (i=0 ; ctl[i]; i+= 2)
+ if (ctl[i+1] != '?' && ctl[i+1] != '-' && ctl[i+1] != '+' && ctl[i+1] != ':')
+ break;
+ if (ctl[i])
+ return NULLH;
+
+ h = (struct handle*)malloc(sizeof(struct handle));
+ if (h == NULLH)
+ return h;
+ h->argc = argc;
+ h->argv = argv;
+ h->ctl = ctl;
+ h->nextarg = 1;
+ h->nextchar = 0;
+ return h;
+}
+
+int args_next(struct handle *h, int *pos, int *inv, char **opt)
+{
+ int invc = 0;
+ int invfound = 0;
+ int i;
+ char *a;
+ if (h->nextarg >= h->argc)
+ return END_ARGS;
+ if (h->nextchar == 0)
+ {
+ if (h->argv[h->nextarg][0] != '-')
+ {
+ if (pos) *pos = h->nextarg;
+ if (inv) *inv = 0;
+ if (opt) *opt = h->argv[h->nextarg];
+ h->nextarg++;
+ return NO_FLAG;
+ }
+ h->nextchar++;
+ }
+ a = h->argv[h->nextarg];
+ while (a[h->nextchar] == '~')
+ {
+ invc = !invc;
+ invfound = 1;
+ h->nextchar++;
+ }
+ for (i=0 ; h->ctl[i] ; i+=2)
+ if (h->ctl[i] == a[h->nextchar])
+ break;
+ if (h->ctl[i] == 0)
+ {
+ h->nextchar = 0;
+ h->nextarg ++;
+ return BAD_FLAG;
+ }
+ h->nextchar++;
+ if (pos) *pos = h->nextarg;
+ if (inv) *inv = invc;
+ if (a[h->nextchar] == 0)
+ {
+ h->nextchar = 0;
+ h->nextarg ++;
+ }
+ switch(h->ctl[i+1])
+ {
+ case '?':
+ return h->ctl[i];
+ case '+':
+ if (inv)
+ return h->ctl[i];
+ break;
+ case '-':
+ if (!inv)
+ return h->ctl[i];
+ break;
+ case ':':
+ break;
+ }
+ /* need optarg */
+ if (h->nextarg >= h->argc)
+ return BAD_FLAG;
+ if (opt)
+ *opt = h->argv[h->nextarg] + h->nextchar;
+ h->nextarg++;
+ h->nextchar=0;
+ return h->ctl[i];
+}
--- /dev/null
+
+#ifndef IN_ARGS
+void *args_init(int argc, char **argv, char *ctl);
+int args_next(void *handle, int *pos, int *inv, char **opt);
+#endif
+
+#define END_ARGS 0
+#define BAD_FLAG (-1)
+#define NO_FLAG (-2)
--- /dev/null
+
+/* broadcast a string on all interfaces, to udp_port(); */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#ifdef SOLARIS
+#include <sys/sockio.h>
+#endif
+#include <netinet/in.h>
+#include <net/if.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <memory.h>
+
+int udp_port(void);
+
+static struct ifconf iflist;
+static int interfaces;
+static int sock;
+static char *packet;
+static struct servent *sv;
+
+static int ifconfinit()
+{
+
+ static char buf[2048];
+
+ iflist.ifc_len = sizeof(buf);
+ iflist.ifc_buf = buf;
+/* printf("ifc_len = %d\n", iflist.ifc_len); /**/
+ if (ioctl(sock, SIOCGIFCONF, (char *)&iflist)< 0) return -1;
+/* printf("ifc_len = %d\n", iflist.ifc_len); /**/
+ interfaces = iflist.ifc_len / sizeof(struct ifreq);
+/* printf("interfaces = %d\n",interfaces); /**/
+/* for (in=0; in<interfaces ; in++) if (iflist.ifc_req[in].ifr_name[0]=='\0') break;
+ interfaces = in;
+/* printf("interfaces = %d\n",interfaces); /**/
+ return 0;
+}
+
+static void sendaddr(struct sockaddr_in addr, char *packet)
+{
+ addr.sin_port = udp_port();
+ addr.sin_family = AF_INET;
+
+ sendto(sock, packet, strlen(packet), 0, (struct sockaddr *)&addr, sizeof(addr));
+}
+
+static void sendinter(int n, char *packet)
+{
+ struct ifreq ifr;
+
+ ifr = iflist.ifc_req[n];
+/* printf("interface = %s\n", ifr.ifr_name); */
+ ioctl(sock, SIOCGIFBRDADDR, &ifr);
+ sendaddr(*(struct sockaddr_in*)&ifr.ifr_broadaddr, packet);
+}
+
+void broadcast(char *packet)
+{
+
+ int n;
+ int port;
+ struct sockaddr_in myaddr;
+ int a = -1;
+
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&a, 4);
+ memset(&myaddr, 0, sizeof(myaddr));
+ myaddr.sin_family = AF_INET;
+ if (geteuid()==0)
+ port = 1023;
+ else
+ port = 10230;
+ myaddr.sin_port = htons(port);
+ while (bind(sock, (struct sockaddr *)&myaddr, sizeof(myaddr))== -1) myaddr.sin_port = htons(--port);
+ if (ifconfinit()!= -1) /* gets list of interfaces */
+ for (n = 0 ; n < interfaces ; n++)
+ sendinter(n, packet);
+}
--- /dev/null
+
+#include "metad.h"
+
+extern struct class daemon_class;
+extern struct class stream_class;
+static struct class *classes[] = {
+ &daemon_class, &stream_class, NULL
+ };
+
+class_t find_class(char *name)
+{
+ int c;
+ for (c=0 ; classes[c] ; c++)
+ if (strccmp(name, classes[c]->class)==0)
+ return classes[c];
+ return NULL;
+}
--- /dev/null
+
+#include "metad.h"
+#include "skip.h"
+
+/* the commands are handled here */
+
+static char *gather_arg(char **args)
+{
+ char *line;
+ int len = 0;
+ int a;
+ for (a=0 ; args[a] ; a++) len += strlen(args[a])+1;
+ line = (char*)malloc(len+1);
+ len = 0;
+ for (a=0 ; args[a] ; a++)
+ {
+ strcpy(line+len, args[a]);
+ len += strlen(args[a]);
+ line[len++] = ' ';
+ }
+ if (len>0)
+ line[len-1] = '\0';
+ else
+ line[len] = 0;
+ return line;
+}
+
+static void do_version(char **args, char *host, void *con)
+{
+ char *rv;
+
+ if (con)
+ {
+ extern char version[];
+ rv = (char*)malloc(strlen(version)+2);
+ strcpy(rv+1, version);
+ rv[0] = 2;
+ set_reply(con, rv, strlen(version)+2);
+ }
+
+}
+
+
+static void do_broad(char **args, char *host, void *con)
+{
+ /* remaining args are packaged up and broadcast on all interfaces */
+ char *line;
+ line = gather_arg(args+1);
+ log(LOG_INFO, "Broadcast request from %s for %s", host, line);
+ broadcast(line);
+ free(line);
+}
+
+static void do_die(char **args, char *host, void *con)
+{
+ log(LOG_WARNING, "Die request from %s", host);
+ exit(1); /* FIXME */
+}
+
+static void do_disable(char **args, char *host, void *con)
+{
+ service_t sv;
+ if (args[1] == NULL)
+ return_error(con, "No service given for disabling");
+ else if ((sv=find_service(args[1]))==NULL)
+ return_error(con, "Cannot find service %s to disable it", args[1]);
+ else for ( ; sv ; sv=find_service(NULL))
+ if (sv->enabled)
+ {
+ log(LOG_INFO, "Disable request from %s for %s", host, sv->service);
+ (sv->class->disable_service)(sv);
+ sv->enabled = 0;
+ }
+}
+
+static void do_enable(char **args, char *host, void *con)
+{
+ service_t sv;
+ if (args[1] == NULL)
+ return_error(con, "No service given for enabling");
+ else if ((sv=find_service(args[1]))==NULL)
+ return_error(con, "Cannot find service %s to enable it", args[1]);
+ else for ( ; sv ; sv=find_service(NULL))
+ if (!sv->enabled)
+ {
+ log(LOG_INFO, "Enable request from %s for %s", host, sv->service);
+ (sv->class->register_service)(sv);
+ sv->enabled = 1;
+ }
+}
+
+
+static void do_run(char **args, char *host, void *con)
+{
+ /* attempt to run service called args[1] with METAD_ARG set to remaining args and METAD_HOST set to host
+ */
+ service_t sv;
+ if (args[1] == NULL)
+ return_error(con, "No service given to run");
+ else if ((sv=find_service(args[1]))==NULL)
+ return_error(con, "Cannot find service %s to run", args[1]);
+ else
+ {
+ char *env[3];
+ char *arg = gather_arg(args+2);
+ env[0] = strcat(strcpy((char*)malloc(20+strlen(host)), "METAD_REASON=run:"), host);
+ env[1] = strcat(strcpy((char*)malloc(11+strlen(arg)), "METAD_ARG="), arg);
+ env[2] = NULL;
+ for ( ; sv ; sv = find_service(NULL))
+ {
+ /* first clear all hold-times */
+ proc_t *pp;
+ for (pp=skip_first(sv->proc_list) ; pp ; pp=skip_next(pp))
+ if ((*pp)->hold_time != 0)
+ (*pp)->hold_time = 1;
+ sv->next_hold = 2;
+ log(LOG_INFO,"starting %s for %s : arg = %s", sv->service, host, arg);
+ new_proc(sv, env);
+ }
+ free(arg);
+ free(env[1]);
+ free(env[0]);
+ }
+}
+
+static void do_kill(char **args, char *host, void *con)
+{
+ /* args[1] = signal
+ * args[2] = pid or service name
+ */
+ int sig;
+ pid_t pid;
+ service_t sv;
+ if (args[1] == NULL)
+ return_error(con, "No signal number given for kill");
+ else if ((sig=atoi(args[1]))<= 0)
+ return_error(con, "Bad signal number given for kill: %s", args[1]);
+ else if (args[2] == NULL)
+ return_error(con, "No process id or service name given for kill");
+ else if ((pid = atoi(args[2]))>0)
+ {
+ proc_t *pp = skip_search(allprocs, &pid);
+ if (pp)
+ {
+ log(LOG_INFO, "killing %s for %s", args[2], host);
+ if ((*pp)->exit_time == 0)
+ kill((*pp)->pid, sig);
+ else if ((*pp)->it_forked > 1)
+ kill((*pp)->it_forked, sig);
+ }
+ else
+ return_error(con, "Cannot find process %s to kill", args[2]);
+ }
+ else if ((sv = find_service(args[2]))!= NULL)
+ {
+ for ( ; sv ; sv = find_service(NULL))
+ {
+ proc_t *pp;
+ for (pp = skip_first(sv->proc_list) ; pp ; pp = skip_next(pp))
+ if ((*pp)->exit_time == 0 || (*pp)->it_forked)
+ {
+ log(LOG_INFO,
+ "signalling %s:%d with %d for %s", sv->service,
+ (*pp)->exit_time?(*pp)->it_forked:(*pp)->pid,
+ sig, host);
+ if ((*pp)->exit_time == 0)
+ kill((*pp)->pid, sig);
+ else if ((*pp)->it_forked > 1)
+ kill((*pp)->it_forked, sig);
+ }
+ }
+ }
+ else
+ return_error(con, "Cannot find service %s to kill", args[2]);
+}
+
+
+static void do_reread(char **args, char *host, void *con)
+{
+ char *errs = NULL;
+ int old;
+ log(LOG_INFO, "Rereading config file for %s", host);
+ old= errors_to(ERROR_STRING, &errs);
+ if (read_config(services, NULL) != 0)
+ return_error(con, "%s", errs);
+ errors_to(old, NULL);
+ if (errs) free(errs);
+}
+
+static void do_list(char **args, char *host, void *con)
+{
+ service_t sv, *svp;
+ init_return();
+ send_byte(3); /* listing */
+ if (args[1] == NULL)
+ {
+ for (svp = skip_first(services) ; svp ; svp = skip_next(svp))
+ {
+ send_service(*svp);
+ }
+ send_byte(0); /* finished */
+ do_send(con);
+ }
+ else if ((sv=find_service(args[1])) != NULL)
+ {
+ for ( ; sv ; sv = find_service(NULL))
+ send_service(sv);
+ send_byte(0);
+ do_send(con);
+ }
+ else
+ return_error(con, "Cannot find service %s to list", args[1]);
+}
+
+void do_restart(char **args, char *host, void *con)
+{
+ log(LOG_INFO, "About to restart for %s", host);
+ control_close();
+ prepare_restart();
+ restart();
+ exit(0);
+}
+
+
+static struct commands cmds[] = {
+ { "list", do_list },
+ { "version",do_version },
+ { "broad", do_broad },
+ { "die", do_die },
+ { "disable",do_disable},
+ { "enable", do_enable},
+ { "kick", do_run},
+ { "run", do_run},
+ { "kill", do_kill},
+ { "reread", do_reread},
+ { "restart", do_restart},
+ { NULL, NULL}
+ };
+
+int do_command(char **args, char *host, void *con)
+{
+ int cmd = 0;
+ for (cmd = 0; cmds[cmd].name ; cmd++)
+ if (strccmp(cmds[cmd].name, args[0])==0)
+ break;
+ if (cmds[cmd].name)
+ {
+ (cmds[cmd].proc)(args, host, con);
+ return 1;
+ }
+ else
+ return 0;
+}
--- /dev/null
+
+#define IN_CONTROL
+#include <stdio.h>
+#include "metad.h"
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#ifdef SOLARIS
+#include <sys/fcntl.h>
+#else
+#include <sys/file.h>
+#endif
+
+#include <netdb.h>
+#include <ctype.h>
+
+/* control.c - handle the control ports
+ * listen on udp port for packets with command to obey
+ * listen on tcp port for connections.
+ * when get connection, listen for command, and possibly return data
+ *
+ */
+
+/* only allow one active tcp connection for now,
+ * but make sure it times out
+ */
+
+static int udp_sock;
+static int tcp_listen;
+
+static struct tcpcon {
+ int sock;
+ char buf[1024]; /*for incoming command */
+ char host[1024]; /* host connection is from */
+ int buflen; /* how much has been read */
+ char *outbuf; /* outgoing data */
+ int outlen; /* size of outgoing data */
+ int outpos; /* how much sent so far */
+ time_t connect_time;/* when the connection was established */
+} tcpcon;
+
+
+
+
+void return_error(struct tcpcon *con, char *fmt, char *a, char *b, char *c)
+{
+ char buf[1024];
+ char *rv;
+ extern char version[];
+ if (con)
+ {
+ sprintf(buf, fmt, a, b, c);
+ sprintf(buf+strlen(buf), " (metad version %s)", version);
+ rv = (char*)malloc(strlen(buf)+2);
+ strcpy(rv+1, buf);
+ rv[0]= 1;
+ con->outbuf = rv;
+ con->outlen = strlen(buf)+2;
+ con->outpos = 0;
+ }
+}
+
+static int address_ok(struct sockaddr_in *sa, char *host)
+{
+ struct hostent *he;
+ int len;
+ int a;
+ static char *tail = ".cse.unsw.edu.au";
+
+ if (ntohs(sa->sin_port) >= 1024 && geteuid() == 0)
+ return 0;
+ if (sa->sin_addr.s_addr == htonl(0x7f000001))
+ {
+ strcpy(host, "localhost");
+ return 1; /* localhost */
+ }
+ he = gethostbyaddr((char*)&sa->sin_addr, 4, AF_INET);
+ if (he == NULL)
+ return 0;
+ strcpy(host, he->h_name);
+ he = gethostbyname(host);
+ if (he == NULL)
+ return 0;
+ for (a=0; he->h_addr_list[a] ; a++)
+ if (memcmp(&sa->sin_addr, he->h_addr_list[a], 4)==0)
+ {
+ /* well, we have a believeable name */
+
+ len = strlen(host);
+ if (len > strlen(tail) && strccmp(tail, host+len - strlen(tail))== 0)
+ return 1;
+ return 0;
+ }
+ return 0;
+}
+
+static void run_command(char *buf, char *host, struct tcpcon *con)
+{
+ char *cp;
+ char **words, **wp;
+
+ for (cp= buf; *cp ; cp++)
+ {
+ if (*cp == '\r' || *cp == '\n') *cp = 0;
+ }
+ wp = words = strsplit(buf, " ");
+ if (isdigit(wp[0][0]))
+ wp++; /* old gossip put a port number at the start for return info */
+ if (!do_command(wp, host, con))
+ {
+ /* possibly return error */
+ if (con)
+ return_error(con, "unknown command %s", wp[0], NULL, NULL);
+ }
+}
+
+void nodelay(int socket)
+{
+ int f;
+ f = fcntl(socket, F_GETFL, 0);
+ fcntl(socket, F_SETFL, f|O_NDELAY);
+ fcntl(socket, F_SETFD, 1); /* set close-on-exec */
+}
+
+int control_init()
+{
+ udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (udp_sock >= 0)
+ {
+ struct sockaddr_in sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_port = udp_port();
+ nodelay(udp_sock);
+ if (bind(udp_sock, (struct sockaddr *)&sa, sizeof(sa)) != 0)
+ {
+ error("cannot bind udp port");
+ return -1;
+ }
+ }
+ else
+ {
+ error("cannot create udp socket");
+ return -1;
+ }
+ tcp_listen = socket(AF_INET, SOCK_STREAM, 0);
+ if (tcp_listen >= 0)
+ {
+ struct sockaddr_in sa;
+ int i = 1;
+ nodelay(tcp_listen);
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_port = tcp_port();
+ setsockopt(tcp_listen, SOL_SOCKET, SO_REUSEADDR, (char*)&i, 4);
+ if (bind(tcp_listen, (struct sockaddr *)&sa, sizeof(sa)) != 0)
+ {
+ error("cannot bind tcp port");
+ return -1;
+ }
+ listen(tcp_listen, 5);
+ }
+ else
+ {
+ error("Cannot create tcp socket");
+ return -1;
+ }
+ tcpcon.sock = -1;
+ return 0;
+}
+
+void control_close(void)
+{
+ close(tcp_listen);
+ close(udp_sock);
+ close(tcpcon.sock);
+}
+
+void check_control(void)
+{
+ /* first check udp */
+ if (readyon(udp_sock))
+ {
+ char buf[1024];
+ char host[1024];
+ int n;
+ struct sockaddr_in sa;
+ int salen = sizeof(sa);
+ n = recvfrom(udp_sock, buf, sizeof(buf)-1, 0, (struct sockaddr *)&sa, &salen );
+ if (n>0 && address_ok(&sa, host))
+ {
+ buf[n] = 0;
+ run_command(buf, host, NULL);
+ }
+ }
+ listenon(udp_sock);
+
+ /* then check tcpcon or tcp_listen */
+ if (tcpcon.sock != -1)
+ {
+ time_t now;
+ time(&now);
+ if (tcpcon.connect_time + 120 < now)
+ {
+ /* just give up */
+ close(tcpcon.sock);
+ tcpcon.sock = -1;
+ if (tcpcon.outbuf) free(tcpcon.outbuf);
+ tcpcon.outbuf = NULL;
+ listenon(tcp_listen);
+ }
+ else if (tcpcon.outbuf)
+ {
+ if (canwrite(tcpcon.sock) && tcpcon.outpos < tcpcon.outlen)
+ {
+ int l = tcpcon.outlen - tcpcon.outpos;
+ if (l>1024) l = 1024;
+ l = write(tcpcon.sock, tcpcon.outbuf+tcpcon.outpos, l);
+ if (l< 0)
+ {
+ close(tcpcon.sock); tcpcon.sock = -1; free(tcpcon.outbuf);
+ tcpcon.outbuf = NULL;
+ }
+ else
+ tcpcon.outpos += l;
+ if (tcpcon.outpos >= tcpcon.outlen)
+ {
+ close(tcpcon.sock); tcpcon.sock = -1; free(tcpcon.outbuf);
+ tcpcon.outbuf = NULL;
+ }
+ }
+ if (tcpcon.sock == -1)
+ listenon(tcp_listen);
+ else
+ writeon(tcpcon.sock);
+ }
+ else /* we are still reading a command */
+ {
+ if (readyon(tcpcon.sock))
+ {
+ int l = sizeof(tcpcon.buf) - tcpcon.buflen;
+ l = read(tcpcon.sock, tcpcon.buf+tcpcon.buflen, l-1);
+ if (l<0)
+ {
+ close(tcpcon.sock); tcpcon.sock = -1;
+ }
+ else
+ {
+ tcpcon.buf[l] = 0;
+ if (l == 0 || strchr(tcpcon.buf, '\n') || strchr(tcpcon.buf, '\r') || strlen(tcpcon.buf) < l)
+ {
+ run_command(tcpcon.buf, tcpcon.host, &tcpcon);
+ if (tcpcon.outbuf == NULL)
+ {
+ tcpcon.outbuf = malloc(1);
+ tcpcon.outbuf[0] = 0;
+ tcpcon.outlen = 1;
+ tcpcon.outpos = 0;
+ }
+ }
+ }
+ }
+ if (tcpcon.sock == -1)
+ listenon(tcp_listen);
+ else if (tcpcon.outbuf)
+ writeon(tcpcon.sock);
+ else
+ listenon(tcpcon.sock);
+
+ }
+ }
+ else
+ {
+ if (readyon(tcp_listen))
+ {
+ struct sockaddr_in sa;
+ int salen = sizeof(sa);
+ tcpcon.buflen = 0;
+ tcpcon.outbuf = NULL;
+ tcpcon.sock = accept(tcp_listen, (struct sockaddr *)&sa, &salen);
+ if (tcpcon.sock >= 0)
+ {
+ nodelay(tcpcon.sock);
+ if (address_ok(&sa, tcpcon.host))
+ {
+ time(&tcpcon.connect_time);
+ listenon(tcpcon.sock);
+ waituntil(tcpcon.connect_time+122);
+ }
+ else
+ {
+ close(tcpcon.sock);
+ tcpcon.sock = -1;
+ }
+ }
+ }
+ if (tcpcon.sock < 0)
+ listenon(tcp_listen);
+ }
+
+}
+
+void set_reply(struct tcpcon *con, char *reply, int len)
+{
+ if (con)
+ {
+ con->outbuf = reply;
+ con->outlen = len;
+ con->outpos = 0;
+ }
+ else free(reply);
+}
--- /dev/null
+
+#include "metad.h"
+
+typedef struct daemon_opts
+{
+ int min;
+ int period;
+ time_t last_start;
+} *daemon_t;
+
+#define c(sv) ((daemon_t)((sv)->classinfo))
+
+static int daemon_opt(service_t sv, char *opt)
+{
+ /* understand min= period= */
+ if (strncmp(opt, "min=", 4)==0)
+ {
+ c(sv)->min = atoi(opt+4);
+ return 1;
+ }
+ if (strncmp(opt, "period=", 7) == 0)
+ {
+ char *cp = opt+7;
+ int num = atoi(cp);
+ if (num==0) num=1;
+ while (isdigit(*cp)) cp++;
+ switch(*cp) {
+ case 0: break;
+ case 's': break;
+ case 'm': num *= 60; break;
+ case 'h': num *= 3600 ; break;
+ case 'd': num *= 24*3600 ; break;
+ default: error("bad period specifier, %s", opt); break;
+ }
+ c(sv)->period = num;
+ return 1;
+ }
+ return 0;
+}
+
+
+static void daemon_register(service_t sv)
+{
+ /* nothing to do.. */
+ c(sv)->last_start = 0;
+}
+
+static void daemon_unregister(service_t sv)
+{
+ /* nothing to do... */
+
+}
+
+static int daemon_prefork(service_t sv)
+{
+ return 0;
+}
+
+static void daemon_check(service_t sv)
+{
+ /* make sure min are running, and watch for next period */
+ char *env[3];
+ env[0] = "METAD_REASON=min";
+ env[1] = "METAD_ARG=";
+ env[2] = NULL;
+ while (c(sv)->min > 0 && count_procs(sv) < c(sv)->min)
+ {
+ if (new_proc(sv, env)<=0)
+ break;
+ }
+ if (c(sv)->period > 0 &&
+ c(sv)->last_start + c(sv)->period <= time(0))
+ {
+ env[0] = "METAD_REASON=period";
+ new_proc(sv, env);
+ c(sv)->last_start = time(0); /* even if it didn't start, we tried */
+ }
+ if (c(sv)->period > 0)
+ waituntil(c(sv)->last_start + c(sv)->period);
+}
+
+static void daemon_copy(service_t from, service_t to)
+{
+ /* copy the classinfo - min and period */
+ daemon_t n,o;
+ if (from)
+ {
+ /* no special state to copy
+ * the new service should have parsed its own args
+ * - simonb 11nov2003
+ */
+ //o = from->classinfo;
+ //n->min = o->min;
+ //n->period = o->period;
+ //n->last_start = o->last_start;
+ }
+ else
+ {
+ n = (daemon_t)malloc(sizeof(struct daemon_opts));
+ n->min = 0;
+ n->period = 0;
+ n->last_start = 0;
+ to->classinfo = n;
+ }
+}
+
+static void daemon_freestate(service_t sv)
+{
+ free(sv->classinfo);
+}
+
+
+static void daemon_newparent(service_t sv, proc_t p)
+{
+ c(sv)->last_start = time(0);
+}
+
+static void daemon_newchild(service_t sv)
+{
+
+}
+
+static void daemon_send(service_t sv)
+{
+ /* send min, period, last_start */
+ send_byte(1); /* daemon */
+ send_int(c(sv)->min);
+ send_int(c(sv)->period);
+ send_int(c(sv)->last_start);
+}
+
+struct class daemon_class =
+ { "daemon", daemon_opt, daemon_register, daemon_check,
+ daemon_copy, daemon_freestate, daemon_send,
+ daemon_unregister, daemon_newparent, daemon_newchild,
+ daemon_prefork
+ };
--- /dev/null
+
+/* doubly linked lists */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include "dlink.h"
+
+
+void *dl_head()
+{
+ void *h;
+ h = dl_alloc(0);
+ dl_next(h) = h;
+ dl_prev(h) = h;
+ return h;
+}
+
+void dl_free(v)
+void *v;
+{
+ struct __dl_head *vv = v;
+ free(vv-1);
+}
+
+
+void dl_insert(head, val)
+void *head, *val;
+{
+ dl_next(val) = dl_next(head);
+ dl_prev(val) = head;
+ dl_next(dl_prev(val)) = val;
+ dl_prev(dl_next(val)) = val;
+}
+
+void dl_add(head, val)
+void *head, *val;
+{
+ dl_prev(val) = dl_prev(head);
+ dl_next(val) = head;
+ dl_next(dl_prev(val)) = val;
+ dl_prev(dl_next(val)) = val;
+}
+
+void dl_del(val)
+void *val;
+{
+ if (dl_prev(val) == 0 || dl_next(val) == 0)
+ return;
+ dl_prev(dl_next(val)) = dl_prev(val);
+ dl_next(dl_prev(val)) = dl_next(val);
+ dl_prev(val) = dl_next(val) = 0;
+}
+
+char *dl_strndup(char *s, int l)
+{
+ char *n;
+ if (s == NULL)
+ return NULL;
+ n = dl_newv(char, l+1);
+ if (n == NULL)
+ return NULL;
+ else
+ {
+ strncpy(n, s, l);
+ n[l] = 0;
+ return n;
+ }
+}
+
+char *dl_strdup(char *s)
+{
+ return dl_strndup(s, (int)strlen(s));
+}
--- /dev/null
+
+/* doubley linked lists */
+
+struct __dl_head
+{
+ struct __dl_head * dh_prev;
+ struct __dl_head * dh_next;
+};
+
+#define dl_alloc(size) ((void*)(((char*)calloc(1,(size)+sizeof(struct __dl_head)))+sizeof(struct __dl_head)))
+#define dl_new(t) ((t*)dl_alloc(sizeof(t)))
+#define dl_newv(t,n) ((t*)dl_alloc(sizeof(t)*n))
+
+#define dl_next(p) *((void**)&(((struct __dl_head*)(p))[-1].dh_next))
+#define dl_prev(p) *((void**)&(((struct __dl_head*)(p))[-1].dh_prev))
+
+void *dl_head();
+char *dl_strdup(char *);
+char *dl_strndup(char *, int);
+void dl_insert(void*, void*);
+void dl_add(void*, void*);
+void dl_del(void*);
+void dl_free(void*);
--- /dev/null
+
+#define IN_ERROR
+#include "metad.h"
+#include <stdio.h>
+#include <stdarg.h>
+#define STDARGS
+/* following for irix6 !! */
+#define _VA_FP_SAVE_AREA 0
+static int error_dest = ERROR_STDERR;
+static char **error_str = NULL;
+int err_str_len;
+
+int errors_to(int where, char **place)
+{
+ int rv = error_dest;
+ error_dest = where;
+ if (where == ERROR_STRING)
+ error_str = place;
+ if (rv == ERROR_SYSLOG && where != ERROR_SYSLOG)
+ closelog();
+ if (where == ERROR_SYSLOG && rv != ERROR_SYSLOG)
+ {
+#ifdef LOG_DAEMON
+ openlog("metad", LOG_PID, LOG_DAEMON);
+#else
+ openlog("metad", 0);
+#endif
+ }
+ return rv;
+}
+
+void error(char *mesg, char *a, char *b, char *c)
+{
+ char buf[1024];
+
+ sprintf(buf, mesg, a, b, c);
+
+ switch(error_dest)
+ {
+ case ERROR_STDERR:
+ fprintf(stderr, "metad: %s\n", buf);
+ break;
+ case ERROR_STRING:
+ if (*error_str == NULL)
+ {
+ *error_str = (char*)malloc(err_str_len=(strlen(buf)+100));
+ strcpy(*error_str, buf);
+ }
+ else if (strlen(*error_str)+strlen(buf)+1 > err_str_len)
+ {
+ *error_str = (char*)realloc(*error_str, err_str_len += strlen(buf)+100);
+ strcat(*error_str, buf);
+ }
+ else
+ strcat(*error_str, buf);
+ break;
+ case ERROR_SYSLOG:
+ syslog(LOG_ERR, "%s", buf);
+ break;
+ }
+}
+
+#ifdef STDARGS
+void log(int level,...)
+#else
+void log(va_alist)
+va_dcl
+#endif
+{
+ va_list pvar;
+ char buf[1024];
+ char *format;
+
+#ifdef STDARGS
+ va_start(pvar, level);
+#else
+ int level;
+ va_start(pvar);
+ level = va_arg(pvar, int);
+#endif
+ format = va_arg(pvar, char *);
+ vsprintf(buf, format, pvar);
+ switch(error_dest)
+ {
+ case ERROR_STDERR:
+ fprintf(stderr, "metad: %s\n", buf);
+ break;
+ case ERROR_STRING:
+ if (*error_str == NULL)
+ {
+ *error_str = (char*)malloc(err_str_len=(strlen(buf)+100));
+ strcpy(*error_str, buf);
+ }
+ else if (strlen(*error_str)+strlen(buf)+1 > err_str_len)
+ {
+ *error_str = (char*)realloc(*error_str, err_str_len += strlen(buf)+100);
+ strcat(*error_str, buf);
+ }
+ else
+ strcat(*error_str, buf);
+ break;
+ case ERROR_SYSLOG:
+ syslog(level, "%s", buf);
+ break;
+ }
+}
+
+void dolog(service_t sv, proc_t p, char *buf)
+{
+ log(LOG_INFO, "%s: %d: %s\n", sv->service, p->pid, buf);
+}
+
--- /dev/null
+
+/*
+ * load process state from a file.
+ * when metad reruns itself, it saves state in the form
+ * of a "list" output.
+ * we now read that it and add proc entries as appropriate
+ *
+ */
+#include "metad.h"
+#include <fcntl.h>
+#ifdef SOLARIS
+#include <sys/fcntl.h>
+#else
+#include <sys/file.h>
+#endif
+#include "skip.h"
+
+char *get_str();
+char *get_return();
+void qfree(char *a)
+{
+ if (a) free(a);
+}
+
+void loadstate(int fd)
+{
+ int len = lseek(fd, 0, 2);
+ int b;
+ char *buf;
+ lseek(fd, 0, 0);
+
+ buf = (char*)malloc(len);
+ read(fd, buf, len);
+ close(fd);
+
+ init_recv(buf);
+ if (get_byte() != 3) return; /* something VERY wrong */
+ b = get_byte();
+ while(b==1 || b == 2) /* service */
+ {
+ char *sname;
+ int enabled;
+ int args;
+ int class;
+ service_t sv, *svp = NULL;
+
+ sname = get_str();
+ if (sname)
+ {
+ svp = skip_search(services, sname);
+ free(sname);
+ }
+ get_int(); /* max */
+ qfree(get_str()); /* home */
+ qfree(get_str()); /* user */
+ qfree(get_str()); /* crash */
+ if (b == 2)
+ {
+ get_int(); /* watch_output */
+ qfree(get_str()); /* pidfile */
+ }
+ get_int(); /* cnt */
+ enabled = get_int();
+ qfree(get_str()); /* prog */
+ args = get_int();
+ while (args--)
+ qfree(get_str());
+ class = get_byte();
+ switch(class)
+ {
+ case 1:
+ get_int();
+ get_int();
+ get_int();
+ break;
+ case 2:
+ get_int();
+ get_int();
+ get_int();
+ get_int();
+ break;
+ }
+ if (svp) sv= *svp ;else sv= NULL;
+ if (sv) sv->enabled = enabled;
+ b = get_byte();
+ while (b == 3 || b == 4) /* process */
+ {
+ int pid, start, xit;
+ int forkedpid = 0, pipefd = -1;
+ pid = get_int();
+ if (b == 4)
+ {
+ forkedpid = get_int();
+ pipefd = get_int();
+ }
+
+ start = get_int(); /* start */
+ get_int(); /* hold */
+ xit = get_int();
+ get_int(); /* status */
+ if ((sv && (xit == 0 && kill(pid, 0)==0)) || (xit>0 && forkedpid>0 && kill(forkedpid,0)==0) )
+ {
+ proc_t p = (proc_t)malloc(sizeof(struct proc));
+ p->pid = pid;
+ p->service = sv;
+ p->pipefd = pipefd;
+ p->bufptr = 0;
+ p->it_forked = forkedpid;
+ p->is_crash = 0;
+ p->start_time = start;
+ p->hold_time = xit?1:0;
+ p->exit_time = xit;
+ skip_insert(sv->proc_list, p);
+ skip_insert(allprocs, p);
+ }
+ b = get_byte();
+ }
+ }
+ free(buf);
+}
+
+extern char **gargv;
+void restart(void)
+{
+ int fd;
+ int len;
+ char *buf;
+ service_t *svp;
+ char *file = "/var/tmp/...metad-temp-file";
+
+ fd = open(file, O_RDWR|O_TRUNC|O_CREAT, 0600);
+ if (fd < 0)
+ {
+ close(0);
+ execv(gargv[2], gargv);
+ exit(1);
+ }
+ unlink(file);
+ init_return();
+ send_byte(3); /* listing */
+ for (svp = skip_first(services) ; svp ; svp = skip_next(svp))
+ {
+ send_service(*svp);
+ }
+ send_byte(0); /* finished */
+
+ buf = get_return(&len);
+ write(fd, buf, len);
+ if (fd > 0)
+ {
+ close(0);
+ dup(fd);
+ close(fd);
+ }
+ gargv[0] = "metad-restart";
+ execv(gargv[2], gargv);
+ exit(1);
+}
--- /dev/null
+
+
+#include "metad.h"
+#include <signal.h>
+#include "skip.h"
+#include <sys/time.h>
+
+/*
+ * main loop of metad
+ * wait for read-select on some sockets, or for time to pass
+ * or for a process to exit.
+ * when one of these happens, we check with each service to see
+ * if it wants to do something.
+ *
+ * services ask to do something with
+ * listenon(socket)
+ * waituntil(time)
+ * and can check is a socket is read with
+ * readyon(socket)
+ *
+ */
+
+
+fd_set wait_for, are_ready;
+fd_set write_on, can_write;
+time_t when_wake;
+
+void listenon(int socket)
+{
+ FD_SET(socket, &wait_for);
+}
+
+int readyon(int socket)
+{
+ return FD_ISSET(socket, &are_ready);
+}
+
+void writeon(int socket)
+{
+ FD_SET(socket, &write_on);
+}
+
+
+
+int canwrite(int socket)
+{
+ return FD_ISSET(socket, &can_write);
+}
+
+void waituntil(time_t when)
+{
+ if (when_wake == 0 || when_wake > when) when_wake = when;
+}
+
+static struct
+{
+ pid_t pid;
+ int status;
+ time_t time;
+} saved_pids[20] = { { 0 } };
+
+static struct timeval select_tv;
+static void collectchild()
+{
+ pid_t pid;
+ int status;
+
+ if ((pid = waitpid(-1, &status, WNOHANG)) > 0)
+ {
+ int i;
+ for (i=0; i<20 ; i++)
+ if (saved_pids[i].pid == 0)
+ {
+ saved_pids[i].pid = pid;
+ saved_pids[i].status = status;
+ time(&saved_pids[i].time);
+ break;
+ }
+ }
+ select_tv.tv_usec = 0;
+ select_tv.tv_sec = 0;
+}
+
+int is_saved_pid(pid_t pid)
+{
+ int i;
+ for (i=0; i<20; i++)
+ if (saved_pids[i].pid == pid)
+ return 1;
+ return 0;
+}
+
+void main_loop()
+{
+ struct sigaction sa;
+ sigset_t ss;
+ sa.sa_handler = collectchild;
+ sa.sa_flags = 0;
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGCLD);
+ sa.sa_mask = ss;
+ sigaction(SIGCLD, &sa, NULL);
+
+ FD_ZERO(&are_ready);
+ FD_ZERO(&can_write);
+ while (1)
+ {
+ service_t *svp;
+ int i;
+ FD_ZERO(&wait_for);
+ FD_ZERO(&write_on);
+ when_wake = time(0)+5*60; /* always wakeup every 5minutes to look around... */
+ collectchild(); /* incase signal was missed */
+
+ for (i=0 ; i<20 ; i++)
+ if (saved_pids[i].pid)
+ {
+ proc_t *pp;
+ pp = skip_search(allprocs, &saved_pids[i].pid);
+ if (pp)
+ {
+ (*pp)->status = saved_pids[i].status;
+ (*pp)->exit_time = saved_pids[i].time;;
+ select_tv.tv_sec = 0;
+ log(LOG_INFO, "process %d (%s) exited - status 0x%04x", saved_pids[i].pid, (*pp)->service->service, (*pp)->status);
+ } else
+ log(LOG_INFO, "process %d exited - status 0x%04x", saved_pids[i].pid, saved_pids[i].status);
+ saved_pids[i].pid = 0;
+
+ }
+
+ for (svp = skip_first(services) ; svp ; svp = skip_next(svp))
+ check_service(*svp);
+ check_control();
+ are_ready = wait_for;
+ can_write = write_on;
+
+ if (when_wake)
+ {
+ time_t now;
+ time(&now);
+ select_tv.tv_sec = when_wake - now;
+ select_tv.tv_usec = 0;
+ if (when_wake < now) select_tv.tv_sec = 0;
+ }
+ else
+ {
+ select_tv.tv_sec = 100000;
+ select_tv.tv_usec = 0;
+ }
+ if (select(FD_SETSIZE, &are_ready, &can_write, NULL, &select_tv) <= 0)
+ {
+ FD_ZERO(&are_ready);
+ FD_ZERO(&can_write);
+ }
+ }
+}
+
--- /dev/null
+
+/* send commands to metad's
+ * -V display meta version and metad versions
+ *
+ * -u send datagrams, don't use stream connection
+ * -b broadcast datagrams on all interfaces
+ *
+ * -r service run the service
+ * -a args pass args with previous run command
+ * -k service kick(run) the service
+ * -l service list the service
+ * -L list all services
+ * -D service disable service
+ * -E service enable service
+ * -K pid/service kill process/service (Sig 15)
+ * -R reread config
+ * -X tell metad to exit
+ * -v be verbose
+ * -C command send the given command
+ *
+ * -B actually send broadcast requests
+ *
+ * other arguments are host names
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "metad.h"
+#include "args.h"
+#include "dlink.h"
+
+extern char version[];
+char *progname;
+
+void usage(char *fmt, char *arg)
+{
+ char buf[1024];
+ sprintf(buf, fmt, arg);
+ fprintf(stderr,"%s: %s\n", progname, buf);
+ fprintf(stderr,"Usage: %s -[h?Vubv] -[LRXB] {-[rklDEK] service} [-C command] hostlist ...\n", progname);
+}
+
+void help()
+{
+ printf(
+ "Usage: %s -[h?Vubv] -[LRXB] {-[rklDEK] service} [-C command] hostlist ...\n"
+ " -h This help message\n"
+ " -? As above\n"
+ " -V Print version number of metac and the metad running on any\n"
+ " host listed\n"
+ " -u Send commands in udp datagrams. This is faster than the\n"
+ " normal tcp, but\n"
+ " avoids delays on dead machines.\n"
+ " -b Send each request as a broadcast on any available network\n"
+ " interfaces.\n"
+ " -v Be verbose when listing services.\n"
+ " -L List all services supported by the metad on the remote host.\n"
+ " -R Tell metad to reread it's configuration file.\n"
+ " -X Tell metad to eXit.\n"
+ " -B Tell the metad to rebroadcast the request on it's\n"
+ " interfaces.\n"
+ " -r service Try to start a process running for the service.\n"
+ " -k service Kick a service into action, same as above.\n"
+ " -l service List details of the named service.\n"
+ " -D service Disable a service. No more processes will be started for\n"
+ " that service.\n"
+ " -E service Enable a service. Metad will start processes as\n"
+ " appropriate.\n"
+ " -K pid Of metad is managing a process with number pid, then it\n"
+ " is sent a SIGTERM.\n"
+ " -K service Every process running for the given service is sent a\n"
+ " SIGTERM.\n"
+ " -C command Send the given command (which will usually be quoted).\n"
+ " Commands are:\n"
+ " run service args like -r, but allows args to be passed to\n"
+ " the process.\n"
+ " kill signum pid-or-service like -K, but allows a signal number\n"
+ " to be given.\n"
+ " restart tell metad to re-exec itself, for use when a\n"
+ " new version is installed\n"
+ " (plus others that duplicate the above flags)\n"
+ , progname) ;
+}
+
+int send_cmd(char *cmd, int udp, char *host, int verbose);
+
+void main(int argc, char *argv[])
+{
+ void *cmds = dl_head();
+ void *hosts = dl_head();
+ int local_broad = 0;
+ int remote_broad = 0;
+ int use_dgram = 0;
+ int show_version = 0;
+ int show_help = 0;
+ int verbose = 0;
+ char *c, *c2;
+ void *opts = args_init(argc, argv, "??h?V?u?b?r:a:k:l:L?D:E:K:R?X?v?C:B?");
+ int opt;
+ char *arg;
+ int pos, inv;
+ progname = strrchr(argv[0], '/');
+ if (progname) progname++; else progname = argv[0];
+
+ while ((opt = args_next(opts, &pos, &inv, &arg))!= END_ARGS)
+ switch(opt)
+ {
+ case BAD_FLAG:
+ usage("Bad option: %s", argv[pos]);
+ exit(1);
+ case NO_FLAG:
+ c = dl_strdup(arg);
+ dl_add(hosts, c);
+ break;
+ case 'h':
+ case '?':
+ show_help = 1;
+ break;
+ case 'V': /* my version number */
+ printf("%s: version %s\n", progname, version);
+ show_version =1;
+ break;
+ case 'u':
+ use_dgram = 1;
+ break;
+ case 'b':
+ local_broad = 1;
+ break;
+ case 'r':
+ case 'k':
+ c = dl_strndup("kick ", 5+strlen(arg)+1);
+ strcat(c, arg);
+ dl_add(cmds, c);
+ break;
+ case 'a':
+ c = dl_prev(cmds);
+ if (c == cmds || (strncmp(c, "kick ", 5)!= 0 && strncmp(c, "run ", 4)!= 0))
+ {
+ usage("%s: -a only valid after -r or -k", NULL);
+ exit(1);
+ }
+ c2 = dl_strndup(c, strlen(c)+1+strlen(arg)+1);
+ strcat(c2, " ");
+ strcat(c2, arg);
+ dl_del(c); dl_free(c);
+ dl_add(cmds, c2);
+ break;
+ case 'l':
+ c = dl_strndup("list ", 5+strlen(arg)+1);
+ strcat(c, arg);
+ dl_add(cmds, c);
+ break;
+ case 'L':
+ c = dl_strdup("list");
+ dl_add(cmds, c);
+ break;
+ case 'D':
+ c = dl_strndup("disable ", 8+strlen(arg)+1);
+ strcat(c, arg);
+ dl_add(cmds, c);
+ break;
+ case 'E':
+ c = dl_strndup("enable ", 7+strlen(arg)+1);
+ strcat(c, arg);
+ dl_add(cmds, c);
+ break;
+ case 'K':
+ c = dl_strndup("kill 15 ", 8+strlen(arg)+1);
+ strcat(c, arg);
+ dl_add(cmds, c);
+ break;
+ case 'R':
+ c = dl_strdup("reread");
+ dl_add(cmds, c);
+ break;
+ case 'X':
+ c = dl_strdup("die");
+ dl_add(cmds, c);
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'C':
+ c = dl_strdup(arg);
+ dl_add(cmds, c);
+ break;
+ case 'B':
+ remote_broad = 1;
+ break;
+ }
+
+ if (show_help)
+ {
+ help();
+ exit(0);
+ }
+ if (dl_next(hosts) == hosts && local_broad == 0)
+ {
+ /* no where to send to... */
+ if (dl_next(cmds) != cmds)
+ {
+ fprintf(stderr,"%s: commands were specified with no where to send them!\n", progname);
+ exit(1);
+ }
+ /* nothing to do, though might has displayed version.
+ * Should probably give usage...
+ */
+ if (show_version == 0)
+ usage("Nothing to do", NULL), exit(1);
+ exit(0);
+ }
+ if (dl_next(hosts) != hosts && local_broad)
+ {
+ fprintf(stderr, "%s: you probably don't want to broadcast AND list hosts...\n", progname);
+ exit(1);
+ }
+
+ if (show_version)
+ {
+ c = dl_strdup("version");
+ dl_insert(cmds, c);
+ }
+ for (c= dl_next(cmds) ; c != cmds ; c = dl_next(c))
+ {
+ char *cmd = (char*)malloc(10 + strlen(c)); /* make sure there is room for port and broad */
+ char *h;
+
+ strcpy(cmd, "1 ");
+ if (remote_broad)
+ strcat(cmd, "BROAD ");
+ strcat(cmd, c);
+ if (local_broad)
+ broadcast(cmd);
+ else for (h=dl_next(hosts) ; h != hosts ; h = dl_next(h))
+ {
+ if (strcmp(h, ".")== 0)
+ send_cmd(cmd, use_dgram, "localhost", verbose);
+ else
+ send_cmd(cmd, use_dgram, h, verbose);
+ }
+ }
+ exit(0); /* FIXME what exit status */
+}
--- /dev/null
+
+#include "metad.h"
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+#ifdef SOLARIS
+#include <sys/termios.h>
+#endif
+char **gargv;
+
+void main(int argc, char *argv[])
+{
+ gargv = argv;
+
+ {
+ int ttyfd = open("/dev/tty", 2, 0);
+ if (ttyfd >= 0) {
+ ioctl(ttyfd, TIOCNOTTY, NULL);
+ close(ttyfd);
+ }
+ }
+
+ /* FIXME should make sure stdin/stdout/stderr are open to something */
+ service_init();
+ control_init();
+ errors_to(ERROR_SYSLOG, NULL);
+ read_config(services, argv[1]);
+ if (strcmp(argv[0], "metad-restart")==0) {
+ loadstate(0);
+ open("/dev/null", O_RDONLY);
+ }
+ main_loop();
+ /* NOT REACHED */
+}
--- /dev/null
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <signal.h>
+/* hold config file info */
+#ifdef ULTRIX
+char *strdup(char*);
+#endif
+
+typedef struct service {
+ char *service; /* name of service */
+ struct class *class; /* pointer to class*/
+ void *classinfo; /* class specific options */
+ /* class independant options */
+
+ int max_proc; /* max number of processes */
+ char *crash_prog; /* prog to call when process crashes */
+ char *home_dir; /* directory to run process in */
+ char *username; /* who to run process as */
+ char *pidfile; /* file to read process id from after child exits */
+ int watch_output; /* if true, attatch a pipe to std{out,err} and what it */
+ int enabled; /* whether to start enabled */
+ int start_cnt;
+ char *program; /* program to run */
+ char **args; /* arguments */
+
+ int pending; /* set before reprocessing config file, cleared when found in file */
+
+ void *proc_list; /* skiplist of currently active processes */
+ time_t next_hold; /* hold time for next crashing processes */
+} *service_t;
+
+typedef struct class {
+ char *class; /* name of class */
+ int (*c_process_opt)(); /* function to processes options */
+ void (*register_service)(); /* register the service if necessary */
+ void (*c_check_service)(); /* check if anything needs to be done for a service */
+ void (*copy_state)(); /* copy state from old service struct to new */
+ void (*free_state)(); /* free class dependant state */
+ void (*send_class)(); /* send class info */
+ void (*disable_service)(); /* unregister service */
+ void (*new_parent)(); /* in parent of new child */
+ void (*new_child)(); /* in a new child */
+ int (*prefork)(); /* just before fork */
+} *class_t;
+
+typedef struct proc {
+ pid_t pid; /* pid of process */
+ service_t service;
+ int pipefd; /* if a pipe was conencted to stdout/stderr */
+ char pipebuf[300]; /* buffer lines of data from pipefd before syslogging */
+ int bufptr; /* how full buf is */
+ int it_forked; /* true if process fork/exited, and we have pid from pidfile */
+ int is_crash; /* if cleaning up after core dump */
+ time_t start_time; /* when processes started */
+ time_t hold_time; /* time to let go of processes slot - set if process crashes early */
+ time_t exit_time; /* the time that it exited */
+ int status; /* wait status if exit_time > 0 */
+} *proc_t;
+
+
+typedef struct daemon_info { /* class specific info for class "daemon" */
+ int min_proc; /* minimum number of processes to have running */
+ int period; /* period in seconds for restarts */
+
+ time_t last_restart; /* last time a periodic restart was attempted */
+} *daemon_info_t;
+
+
+typedef struct commands {
+ char * name;
+ void (*proc)();
+} *commands_t;
+
+class_t find_class(char *name);
+void loadstate(int fd);
+int do_command(char **args, char *host, void *con);
+int tcp_port();
+int udp_port();
+int control_init(void);
+void check_control(void);
+#ifndef IN_CONTROL
+int set_reply(void *con, char *reply, int len);
+int return_error(void *con, char *fmt, ...);
+#endif
+void listenon(int socket);
+int readyon(int socket);
+void writeon(int socket);
+int canwrite(int socket);
+void waituntil(time_t when);
+void main_loop(void);
+int read_config(void *services, char *file);
+int count_procs(service_t sv);
+void check_service(service_t sv);
+int new_proc(service_t sv, char **env);
+service_t find_service(char *name);
+service_t new_service(char *name, class_t class);
+int process_opt(service_t sv, char *opt);
+void service_init(void);
+void free_service(service_t sv);
+void broadcast(char *line);
+void init_return(void);
+void send_byte(int b);
+int get_byte(void);
+void send_int(int i);
+int get_int(void);
+void send_str(char *);
+void send_service(service_t);
+void do_send(void *);
+void control_close(void);
+void prepare_restart(void);
+void restart(void);
+void init_recv(char*);
+char *prtime(time_t);
+int udp_port(void);
+int tcp_port(void);
+
+int strccmp(char*, char*);
+#ifndef IN_ERROR
+void error(char *mesg,...);
+void log(int, char*, ...);
+#endif
+#define ERROR_STDERR 0
+#define ERROR_SYSLOG 1
+#define ERROR_STRING 2
+int errors_to(int where, char **place);
+
+char **strlistdup(char **l);
+char **strsplit(char *line, char *sep);
+/*char *strdup(char*);*/
+
+void dolog(service_t, proc_t, char *);
+
+extern void *services;
+extern void *allprocs;
+
+
+time_t time(time_t*);
+
+extern int is_saved_pid(pid_t pid);
--- /dev/null
+\input texinfo @c -*-texinfo-*-
+@c %**start of header
+@setfilename metad.info
+@settitle Metad -- The Daemon for controlling daemons.
+@setchapternewpage off
+@c %**end of header
+
+@ifinfo
+
+@node Top, Introduction, (dir), (dir)
+@comment node-name, next, previous, up
+@top Metad
+
+@end ifinfo
+
+@titlepage
+@title METAD
+@subtitle The Daemon for controlling Daemons
+@author The School of Computer Science and Engineering
+@author Computing Support Group
+@end titlepage
+
+@menu
+* Introduction::
+* Configuration::
+* Control::
+* Logs::
+
+ --- The Detailed Node Listing ---
+
+Introduction
+
+* Functionality::
+* Usefulness::
+
+Functionality
+
+* Service Classes::
+
+Configuration
+
+* Invocation::
+* Config file format::
+* General options::
+* The Daemon Class::
+* The Listener Class::
+* The Stream Class::
+
+Config file format
+
+* Service Name and Class::
+* Options::
+* Program and arguments::
+
+General options
+
+* Max::
+* Dir::
+* User::
+* Pidfile::
+* Watch_output::
+* Enabled::
+* Crash::
+
+The Daemon Class
+
+* Min::
+* Period::
+
+Control
+
+* Disabling and Enabling::
+* Killing::
+* Starting::
+* Listing::
+* Reread and Restart::
+* Broadcast::
+* metac::
+@end menu
+
+@node Introduction, Configuration, Top, Top
+@chapter Introduction
+
+@emph{Metad} is a program for running other programs.
+Unlike the shell (which also satisfies that description), Metad runs
+background system programs rather than foreground user programs.
+
+Metad serves a purpose similar to @code{cron}, @code{inetd}, and
+@code{init}, and could replace them all.
+The significant enhancement metad has over these programs is that it is
+easily controllable, from anywhere on the network.
+
+@menu
+* Functionality::
+* Usefulness::
+@end menu
+
+
+@node Functionality, Usefulness, Introduction, Introduction
+@section Functionality
+
+The basic function of metad is to run programs in response to certain
+stimuli.
+These stimuli can be time passing, other programs exiting, I/O
+activity being possible on some socket, or direct request.
+
+Metad will also, on request, send signals to any of the programs that it
+has run, report on its status, and disable or re-enable any of the
+services it offers. The requests are sent to metad over the network by
+the @code{metac} program described below in @xref{metac}.
+
+Metad gets a list of services that it should provide from a
+configuration file (@pxref{Config file format}). This file describes,
+for each service,
+what program to run, how to run it, and when to run it.
+
+The @emph{what} part is simply the name of the file containing the
+program.
+The @emph{how} part includes command line arguments, working directory
+and user id.
+The @emph{when} part is probably most interesting.
+
+@menu
+* Service Classes::
+@end menu
+
+@node Service Classes, , Functionality, Functionality
+@subsection Service Classes
+Meta understands a small number of types or @dfn{classes} or service,
+each of which respond to different stimuli.
+@table @code
+@item daemon
+The @code{daemon} is the simplest form for service
+(and the only one currently implemented).
+A @code{daemon} service will run its program either repeatedly,
+periodically, on request, or any combination of these.
+It may allow multiple instances of the program to be running at once,
+and can impose an upper and lower limit on this number.
+
+It may also be able to run its program just once (at startup) or on
+completion of some other services program (not implemented yet).
+
+@item listener
+The @code{listener} class supports programs which need to listen for input on
+a network port.
+Metad will set up the network port and will wait for activity. When
+activity is noticed, metad will run the program, passing it the network
+port, so that it can do what ever is needed.
+When the program exits, metad will resume waiting for more activity.
+
+The port can be a udp/ip port or a tcp/ip port.
+
+This is similar to the dgram/wait service of @code{inetd} and the
+stream/wait service of some implementations of @code{inetd}.
+
+@item steam
+The @code{stream} class supports programs which need to respond to
+network stream connections, such as @code{telnetd} or @code{fingerd}.
+Metad will listen on a stream socket for a connection request.
+It will then accept the connection, possibly verify the source, and then
+run the program passing the the connection.
+
+As with all metad services, it is possible to limit the number of
+program instances which are running concurrently.
+
+@end table
+
+@node Usefulness, , Functionality, Introduction
+@section Usefulness
+
+The main goal that metad was developed to meet, was to assist in the
+development of various daemon services which needed to be run on various
+machines around the network.
+The sort of assistance required involved:
+@itemize @bullet
+@item
+Restarting a daemon if it dies, possibly reporting the death.
+@item
+Killing a restarting a daemon when a new version is installed.
+@item
+Taking control of new daemons.
+@item
+Running some program on all machines on the network.
+@end itemize
+
+This goal lead to the @emph{daemon} class and the @emph{metac} control
+program being created.
+The @emph{listener} and @emph{stream} classes are later additions
+brought about due to a dissatisfaction with inetd.
+
+Say something about shortcomings?? hiding failure and not being able to
+control named.
+
+@node Configuration, Control, Introduction, Top
+@chapter Configuration
+
+@menu
+* Invocation::
+* Config file format::
+* General options::
+* The Daemon Class::
+* The Listener Class::
+* The Stream Class::
+@end menu
+
+@node Invocation, Config file format, Configuration, Configuration
+@section Invocation
+
+Metad takes precisely two command line arguments.
+The first is used as the name of the configuration file, as described
+below.
+The second is used as the name of the file which contains the metad
+program.
+This is used when metad is asked to restart itself (for instance, when a
+new version has been installed).
+
+Metad also inspects its "zeroth" argument. If this is the string
+@samp{metad-restart}, then metad assumes that it has just restarted
+itself and looks on @strong{stdin} for a description of the previous state
+(in particular, what processes were running).
+
+
+@node Config file format, General options, Invocation, Configuration
+@section Config file format
+
+The configuration file for metad simply lists the different services
+that metad should monitor, one per line.
+Any line that is not empty or a comment line (beginning with a hash or
+number sign @samp{#}) is a service line.
+Each line is broken up into words on spaces and tabs. Spaces and tabs
+can be preserved in words by quoting them with single or double quotes.
+Quotes and back slashes can be preserved by prefixing them with back
+slashes.
+
+The words are the services name, its class, some options, the program
+name, and the programs arguments.
+The program name is distinguished from options by the fact that it
+starts with a slash.
+
+@menu
+* Service Name and Class::
+* Options::
+* Program and arguments::
+@end menu
+
+@node Service Name and Class, Options, Config file format, Config file format
+@subsection Service Name and Class
+
+The service name is any unique string of characters and is
+uninterpreted.
+In the future, services may be grouped based on a common prefix of the
+name. This prefix would be up to a period.
+
+The class name is one of a predefined set of classes, currently only
+@samp{daemon} is supported.
+
+@node Options, Program and arguments, Service Name and Class, Config file format
+@subsection Options
+
+The options provide details one how and when a service will run a
+program.
+Some options are applicable to all classes of service. These are the
+General options described below (@pxref{General options}).
+Others are specific to a class and are detail under the particular class
+below.
+
+Most options take the form of @samp{@var{name}=@var{value}}.
+
+@node Program and arguments, , Options, Config file format
+@subsection Program and arguments
+As mentioned, the program is the first word on the line which begins
+with a slash. The full path name of the program must be given.
+Subsequent words are passed to the program as an argument list. There
+must always be at least one argument, which is normally the name of the program.
+
+@node General options, The Daemon Class, Config file format, Configuration
+@section General options
+
+The General options are applicable to all classes of service. The
+default value can be different with different services.
+The default listed below is the default used for the @code{daemon} service.
+@menu
+* Max::
+* Dir::
+* User::
+* Pidfile::
+* Watch_output::
+* Enabled::
+* Crash::
+@end menu
+
+@node Max, Dir, General options, General options
+@subsection Max
+
+The @dfn{max} option sets the maximum number of concurrent processes
+that can be running for a particular service. The default for
+@code{daemon}s is one. Format is @code{max=@var{nn}} when @var{nn} is a
+decimal integer.
+
+@node Dir, User, Max, General options
+@subsection Dir
+
+The @dfn{dir} option sets the working directory for processes run for
+this service. The default is the same as metad's working directory.
+A full path name should be given.
+
+@node User, Pidfile, Dir, General options
+@subsection User
+
+The @dfn{user} option specifies an alternate userid to for processes run
+for this service. Metad must be run by @strong{root} for this to work.
+The default is to leave the user id unchanged from that of metad.
+
+When setting the userid, the groups ids are also set to those for the
+named user.
+
+@node Pidfile, Watch_output, User, General options
+@subsection Pidfile
+
+Some daemon programs insist on forking and continuing to perform useful
+work in the child while the parent exits. This is useful in many
+circumstances, but makes it difficult for metad to continue to monitor
+and control the program.
+To help with this, the @dfn{pidfile} option specifies the name of a file which the program
+might write the process id of the forked child.
+
+When a process that metad has started exits, metad checks the
+appropriate @var{pidfile} to see if it was written since the process
+started, and if it contains the pid of an active process.
+If this check succeeds, then metad assumes that the process found is
+carrying one the work of the original process.
+
+This process will then be the target of any @strong{kill} commands, and
+it will be treated just as though it were the original process.
+
+@node Watch_output, Enabled, Pidfile, General options
+@subsection Watch_output
+
+If the @dfn{watch_output} option is listed for a service, then metad
+will connect the standard output and standard error streams of any
+processes started for that service to a pipe.
+Metad will monitor the pipe for two purposes.
+
+@enumerate
+@item
+Any data received from the pipe will be logged with syslog as an
+informational message about the processes.
+@item
+Metad will assume that the program is still active until an end-of-file
+is read on the pipe --- even if the process that metad first started exits.
+@end enumerate
+
+@node Enabled, Crash, Watch_output, General options
+@subsection Enabled
+
+The @dfn{enabled} option can be used to enabled or disable a service at
+startup. The value can be one of the words @samp{yes} and @samp{no}. The
+default value is @samp{yes}.
+
+@node Crash, , Enabled, General options
+@subsection Crash
+
+The @dfn{crash} option specifies a program to run after a crash (exit
+due to signal). The program is expected to clean up or report the
+problem.
+Currently this is unimplemented so details are scarce.
+
+@node The Daemon Class, The Listener Class, General options, Configuration
+@section The Daemon Class
+
+The @code{daemon} class understands two more options. They are @samp{min}
+and @samp{period}.
+@menu
+* Min::
+* Period::
+@end menu
+
+@node Min, Period, The Daemon Class, The Daemon Class
+@subsection Min
+
+The @dfn{min} option specifies the minimum number of processes that
+should be running for the service. If fewer than that minimum are
+running, new processes will be started until the minimum is reached.
+
+The default minimum is zero.
+
+@node Period, , Min, The Daemon Class
+@subsection Period
+
+The @dfn{period} option specifies how frequently a daemon service should
+start a new processes. The value should be a decimal integer with a
+single character suffix.
+Suffixes are @samp{s} for seconds, @samp{m} for minutes, @samp{h} for
+hours, or @samp{d} for days. If no suffix is given, seconds is assumed.
+
+If this period passes without a processes being start (or attempted to
+be started), metad will attempt to start a process to execute the
+services program. If the maximum number of processes are already
+running, the attempt will fail, but will still be recorded as an attempt
+and metad will wait for the period to pass again.
+
+@node The Listener Class, The Stream Class, The Daemon Class, Configuration
+@section The Listener Class
+
+The Listener class is not implemented yet, so details are scarce.
+It is expected to need a port option (service/protocol, e.g. pop3/tcp).
+
+@node The Stream Class, , The Listener Class, Configuration
+@section The Stream Class
+
+The Stream class is not yet implemented, so details are not available.
+It is expected to need a port option (service/protocol e.g. telnet/tcp)
+and multiple access options (access+.domain access-@@netgroup
+access+nnn.nnn/16)
+
+The identity of the peer will probably be provided in the environment,
+e.g. METAD_PEER=host.domain.au:port
+
+
+@node Control, Logs, Configuration, Top
+@chapter Control
+
+A particularly useful feature of metad is the fact that it can be
+controlled from anywhere on the network.
+To achieve this, metad understands a fairly simple command protocol.
+Commands can be sent either over a tcp/ip connection, in which case a
+response is returned, or via a udp/ip datagram. The use of datagrams
+means that a command can be broadcast to all metads on a given subnet.
+
+As one of the commands the metad will obey is to rebroadcast a command,
+commands can easily be broadcast around a medium sized internet.
+
+@menu
+* Disabling and Enabling::
+* Killing::
+* Starting::
+* Listing::
+* Reread and Restart::
+* Broadcast::
+* metac::
+@end menu
+
+@node Disabling and Enabling, Killing, Control, Control
+@section Disabling and Enabling
+
+Individual services can be disabled and re-enabled.
+
+When disabled a service will not start any new processes, though it will
+not attempt to stop and processes that are already running.
+A disabled service that would normally listen for some specific network
+activity will not listen for that activity at all.
+
+Services are disabled be sending the command @samp{disable
+@var{servicename}} and are enabled with the command @samp{enabled
+@var{servicename}}.
+
+These can be easily sent with metac using option @samp{-D @var{service}}
+and @samp{-E @var{service}}.
+
+@node Killing, Starting, Disabling and Enabling, Control
+@section Killing
+
+Killing a process involves sending a Unix signal to the process. This
+does not always cause it to die.
+
+To send a signal to all processes for a particular service, the command
+is @samp{kill @var{signum} @var{service}}.
+To send to a specific process, its process id must be given instead of
+the servicename.
+
+Sending signal 15 (SIGTERM) can easily be done with metac using @samp{-K
+@var{service}} or @samp{-K @var{processid}}.
+
+@node Starting, Listing, Killing, Control
+@section Starting
+
+Some classes (currently only daemon, definitely not stream, probably
+listener) will start a new process on request. This request is sent with
+the @samp{run} command.
+Optional extra information can be sent with the run command to modify
+the action of the started program. This is passed the the program
+through the environment variable @emph{METAD_ARG}.
+The command to run a new process is
+@samp{run @var{service} @var{extra-information}}.
+
+If no extra information is to be sent, this can be done with metac using
+@samp{-r @var{service}}. To send extra information simply add @samp{-a
+@var{args}}.
+
+
+@node Listing, Reread and Restart, Starting, Control
+@section Listing
+
+Metad can be asked to send a report on the services it is maintaining
+including the current processes which are running. The information is
+returned in a binary format which is only understood by metac.
+Either a single service can be listed (command @samp{list @var{service}}, option
+@samp{-l @var{service}}) or all services can be listed.
+(command @samp{list}, option @samp{-L}).
+
+I should say something about the listing that is returned... later.
+
+@node Reread and Restart, Broadcast, Listing, Control
+@section Reread and Restart
+
+The @samp{reread} command causes metad to reread its config file.
+New services can be added and options, programs and arguments can be
+changed. If a service has be removed from the config file, then the
+service is not forgotten by metad (it might have running processes) but
+it is disabled.
+
+The @samp{restart} command causes to metad to exec a new instance of
+itself, replacing the old one.
+Information about what processes are running for which service is passed
+through so that there will be no undue interruption of
+service. Processes for services which no longer exist will be simply
+forgotten.
+
+This is useful after installing a new version of metad.
+
+@node Broadcast, metac, Reread and Restart, Control
+@section Broadcast
+
+The @samp{broadcast} command will broadcast it arguments as a command to
+all nearby metads (on the same subnet). This may cause the command to be
+delivered to some metads multiple times (if they are on machines with
+multiple interfaces). As most metad commands are fairly idempotent, this
+is not likely to be a problem, but should be considered.
+
+@node metac, , Broadcast, Control
+@section metac
+
+Metac is the program to use to send commands to metad.
+It takes are arguments a number of commands and a number of hosts to
+send the commands to. It may be asked to broadcast the commands, or to
+send them via datagrams. The default it to send them over a stream
+connection.
+
+Commands and host names may be intermixed on the command line. All
+commands will still be sent to all hosts.
+
+Commands can be given explicitly using the @samp{-C} flag, or more
+briefly using one of the command flags.
+The flags understood by metac are:
+@table @samp
+@item -u
+Send (udp) datagrams, rather than making stream connection.
+@item -b
+Broadcast datagrams on all network interfaces.
+@item -B
+All commands should be sent with a @samp{broad } prefix so that the
+responding metad will broadcast the command in its subnet.
+@item -v
+Be verbose in reporting responses --- what does this mean???
+@item -C command
+Send the given command as is.
+@item -r service
+Send a @samp{run @var{service}} command.
+@item -a args
+Append the @var{args} to the previous @samp{-r} command;
+@item -l service
+Ask for a listing of the named service.
+@item -L
+Ask for a listing of all services.
+@item -D service
+Send a @samp{disable @var{service}} command.
+@item -E service
+Send a @samp{enable @var{service}} command.
+@item -K pid/service
+Send a @samp{kill 15 @var{pid/service}} command to kill the process or
+service.
+@item -R
+Send a @samp{reread} command.
+@item -X
+Send an @samp{exit} command. (but thats not documented...);.
+@item -V
+Display version number of metac and send request for version information
+to metads.
+@end table
+
+@node Logs, , Control, Top
+@chapter Logs
+
+@contents
+@bye
--- /dev/null
+
+/*
+ * args - a getopt link argument processor
+ *
+ * void *handle = args_init(argc, argv, "argctl");
+ *
+ * args_next(handle, &pos, &inv, &optarg) -> BAD_FLAG/END_ARGS/NO_FLAG/opt
+ *
+ * argctl = {<optchar><typechar>}*
+ * typechar == ? -f or -~f
+ * + -f arg or -~f
+ * - -f or -~f arg
+ * : -f arg only
+ *
+ *
+ */
+
+#define IN_ARGS
+#include "args.h"
+struct handle
+{
+ int argc;
+ char **argv;
+ char *ctl;
+ int nextarg, nextchar;
+};
+
+struct handle *args_init(int argc, char **argv, char *ctl)
+{
+ /* if ctl is bad, return NULL, else a handle */
+ int i;
+ handle *h;
+ for (i=0 ; ctl[i]; i+= 2)
+ if (ctl[i+1] != '?' && ctl[i+1] != '-' && ctl[i+1] != '+' && ctl[i+1] != ':')
+ break;
+ if (ctl[i])
+ return NULL;
+
+ h = (struct handle*)malloc(sizeof(struct handle));
+ if (h == NULL)
+ return h;
+ h->argc = argc;
+ h->argv = argv;
+ h->ctl = ctl;
+ h->nextarg = 1;
+ h->nextchar = 0;
+ return h;
+}
+
+int args_next(struct handle *h, int *pos, int *inv, char **opt)
+{
+ int invc = 0;
+ int invfound = 0;
+ int i;
+ char *a;
+ if (h->nextarg >= h->argc)
+ return END_ARGS;
+ if (h->nextchar == 0)
+ {
+ if (h->argv[h->nextarg][0] != '-')
+ {
+ if (pos) *pos = h->nextarg;
+ if (inv) *inv = 0;
+ if (opt) *opt = h->argv[h->nextarg];
+ h->nextarg++;
+ return NO_FLAG;
+ }
+ h->nextchar++;
+ }
+ a = h->argv[h->nextarg];
+ while (a[h->nextchar] == '~')
+ {
+ invc = !invc;
+ invfound = 1;
+ h->nextchar++;
+ }
+ for (i=0 ; h->ctl[i] ; i+=2)
+ if (h->ctl[i] == a[h->nextchar])
+ break;
+ if (h->ctl[i] == 0)
+ {
+ h->nextchar = 0;
+ h->nextarg ++;
+ return BAD_FLAG;
+ }
+ h->nextchar++;
+ if (pos) *pos = h->nextarg;
+ if (inv) *inv = invc;
+ if (a[h->nextchar] == 0)
+ {
+ h->nextchar = 0;
+ h->nextarg ++;
+ }
+ switch(h->ctl[i+1])
+ {
+ case '?':
+ return h->ctl[i];
+ case '+':
+ if (inv)
+ return h->ctl[i];
+ break;
+ case '-':
+ if (!inv)
+ return h->ctl[i];
+ break;
+ case ':':
+ break;
+ }
+ /* need optarg */
+ if (h->nextarg >= h->argc)
+ return BAD_FLAG;
+ if (opt)
+ *opt = h->argv[h->nextarg] + h->nextchar;
+ h->nextarg++;
+ h->nextchar=0;
+ return h->ctl[i];
+}
--- /dev/null
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h> /* for hton? */
+
+int tcp_port()
+{
+ struct servent *sv;
+ if (geteuid() > 0)
+ return htons(6110);
+ sv = getservbyname("metad","tcp");
+ if (sv)
+ return sv->s_port;
+ else
+ return htons(611);
+}
+
+int udp_port()
+{
+ struct servent *sv;
+ if (geteuid() > 0)
+ return htons(6110);
+ sv = getservbyname("metad","udp");
+ if (sv)
+ return sv->s_port;
+ else
+ return htons(611);
+}
--- /dev/null
+
+#include <sys/param.h>
+#include <unistd.h>
+#include <stdio.h>
+
+
+#define WEEKS (DAYS * 7L)
+#define DAYS (HOURS * 24L)
+#define HOURS (MINUTES * 60L)
+#define MINUTES 60L
+
+#ifndef HZ
+# define HZ 50
+#endif
+
+/* static function declarations */
+
+
+char *prtime(time_t time)
+{
+static char tim[9];
+ if (time >= WEEKS) {
+ sprintf(tim,"%4ldw%02ldd", time / WEEKS, (time % WEEKS) / DAYS);
+ } else if (time >= DAYS) {
+ sprintf(tim,"%4ldd%02ldh", time / DAYS , (time % DAYS) / HOURS);
+ } else if (time >= HOURS) {
+ sprintf(tim,"%4ldh%02ldm", time / HOURS , (time % HOURS) / MINUTES);
+ } else if (time >= MINUTES) {
+ sprintf(tim,"%4ldm%02lds", time / MINUTES , time % MINUTES);
+ } else
+ sprintf(tim,"%7ds", (int)time);
+ return tim;
+}
+
+char *prticks(time_t ticks)
+{
+ return prtime(ticks/HZ);
+}
--- /dev/null
+#!/usr/local/bin/ae
+
+dest=/home/www/lib/html/doc/metad
+src=metad
+echo "Publishing $src.texi into $dest..."
+rm $dest/*
+cp $src.texi $dest
+cd $dest || exit 1
+
+texi2html -menu -split_chapter $src.texi
+texi2dvi $src.texi
+dvips $src.dvi > $src.ps
+
+ln -s ${src}_toc.html index.html
+
+chmod a+r *
--- /dev/null
+
+#include "metad.h"
+#undef NULL
+#include <stdio.h>
+#include <skip.h>
+
+/* read the config file
+ *
+ * first mark all services as pending
+ *
+ * then for each line which is not a blank/comment
+ * strplit and find name/class
+ * if name already exists:
+ * if same class, set attributes
+ * if different class, rename old service and create new
+ * else
+ * create new service if remainder parses ok
+ *
+ * if any remaining services are pending, disabled them unless there were errors
+ *
+ */
+
+int read_config(void *services, char *file)
+{
+ service_t *serv;
+ char linebuf[1024];
+ FILE *f;
+ int err = 0;
+ static char *lastfile = NULL;
+ if (file) lastfile = file;
+ else file = lastfile;
+
+ for (serv = skip_first(services) ; serv ; serv = skip_next(serv))
+ (*serv)->pending = 1;
+
+ f = fopen(file, "r");
+ if (f == NULL)
+ {
+ error("cannot find config file %s", file);
+ return 1;
+ }
+ while (fgets(linebuf, sizeof(linebuf), f)!= NULL)
+ {
+ int len = strlen(linebuf);
+ char **words;
+ int w;
+
+ if (len > 1000 && linebuf[len-1] != '\n')
+ {
+ error("line too long in config file");
+ fclose(f);
+ return 1;
+ }
+ if (linebuf[len-1] == '\n') linebuf[--len] = 0;
+
+ words = strsplit(linebuf, " \t\n");
+ if (words)
+ {
+ for (w=0 ;words[w] ; w++) if (words[w][0]=='#') words[w]=NULL;
+ if (words[0] && words[1])
+ {
+ /* not a comment */
+ service_t *svp = skip_search(services, words[0]);
+ service_t sv;
+ class_t cl = find_class(words[1]);
+ if (svp != NULL)
+ {
+ if ((*svp)->class != cl)
+ {
+ /* different classes - rename old services */
+ skip_delete(services, words[0]);
+ strcpy(linebuf, words[0]);
+ do { strcat(linebuf, ".old"); } while (skip_search(services, linebuf)!= NULL);
+ free((*svp)->service);
+ (*svp)->service = strdup(linebuf);
+ skip_insert(services, *svp);
+ svp = NULL;
+ }
+ }
+ if (cl == NULL)
+ {
+ error("unknown class %s", words[1]);
+ err ++;
+ continue;
+ }
+ sv = new_service(words[0], cl);
+ /* now process options */
+ for (w=2 ; words[w] && words[w][0] != '/' ; w++)
+ {
+ int ok = process_opt(sv, words[w]);
+ if (ok == 0)
+ ok = sv->class->c_process_opt(sv, words[w]);
+ if (ok <= 0)
+ {
+ if (ok == 0)
+ error("unknown option: %s", words[w]);
+ else
+ error("error in option: %s", words[w]);
+ free_service(sv);
+ sv = NULL;
+ err++;
+ break;
+ }
+ }
+ if (sv == NULL) continue;
+ if (words[w])
+ sv->program = strdup(words[w++]);
+ if (words[w])
+ sv->args = strlistdup(words+w);
+ if (sv->program == NULL)
+ error("missing program name for service %s", words[0]), err++;
+ else if (sv->args == NULL)
+ error("missing program arguments for service %s", words[0]), err++;
+ else
+ {
+ if (svp)
+ {
+ proc_t *pp2;
+ service_t sv2 = *svp;
+ sv->enabled = sv2->enabled;
+ sv->proc_list = sv2->proc_list;
+
+ /* change service pointer to the new structure (for aidan) */
+ if (sv->proc_list)
+ for (pp2 = skip_first(sv->proc_list) ; pp2 ; pp2=skip_next(pp2))
+ (*pp2)->service = sv;
+
+ sv2->proc_list = NULL;
+ sv->class->copy_state(sv2, sv);
+ skip_delete(services, sv2->service);
+ free_service(sv2);
+ }
+ skip_insert(services, sv);
+ sv->class->register_service(sv);
+ sv = NULL;
+ }
+ if (sv) free_service(sv);
+ }
+ free(words[-1]);
+ free(words-1);
+ }
+ }
+ fclose(f);
+ for (serv = skip_first(services) ; serv ; serv = skip_next(serv))
+ if ((*serv)->pending)
+ (*serv)->enabled = 0;
+ return err;
+}
--- /dev/null
+
+void *malloc(int);
+char *recvbuf;
+int recvptr;
+
+void init_recv(char *buf)
+{
+ recvbuf = buf;
+ recvptr = 0;
+}
+
+int get_byte()
+{
+ return 0xff & recvbuf[recvptr++];
+}
+
+int get_int()
+{
+ int i = 0;
+ i = get_byte();
+ i = (i<<8) | get_byte();
+ i = (i<<8) | get_byte();
+ i = (i<<8) | get_byte();
+ return i;
+}
+
+char *get_str()
+{
+ int l = get_int();
+ char *s,*p;
+ if (l == 0)
+ return (char*)0;
+ p = s = (char*)malloc(l);
+ while (l--)
+ *p++ = get_byte();
+ return s;
+}
--- /dev/null
+
+/* send a command to a metad somewhere
+ * if we use tcp, wait for reply and interpret it
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include "skip.h"
+
+#include "metad.h"
+
+char *get_str(void);
+#ifdef ULTRIX
+char *strdup(char*);
+#endif
+/* cache addresses */
+typedef struct acache
+{
+ char *host;
+ struct sockaddr_in addr;
+} *acache;
+
+extern char *progname;
+
+static int acmp(acache a, acache b, char *host)
+{
+ if (b) host=b->host;
+ return strcmp(a->host, host);
+}
+static void afree(acache a)
+{
+ free(a->host);
+ free(a);
+}
+
+
+static void *addr_cache = NULL;
+
+static void list_service(char *, int);
+
+int send_cmd(char *cmd, int udp, char *host, int verbose)
+{
+
+ acache *cp, c;
+ if (addr_cache == NULL)
+ addr_cache = skip_new(acmp, afree, NULL);
+
+ cp = skip_search(addr_cache, host);
+ if (cp != NULL)
+ c = *cp;
+ else
+ {
+ c = (acache)malloc(sizeof(struct acache));
+ c->host = strdup(host);
+ c->addr.sin_port = udp ? udp_port() : tcp_port();
+ c->addr.sin_addr.s_addr = inet_addr(host);
+ if (c->addr.sin_addr.s_addr > 0 && c->addr.sin_addr.s_addr < 0xffffffff)
+ {
+ c->addr.sin_family = AF_INET;
+ }
+ else
+ {
+ struct hostent *he;
+ he = gethostbyname(host);
+ if (he == NULL)
+ {
+ c->addr.sin_family = -1;
+ fprintf(stderr, "%s: unknown host %s\n", progname, host);
+ }
+ else
+ {
+ memcpy(&c->addr.sin_addr, he->h_addr_list[0], 4);
+ c->addr.sin_family = AF_INET;
+ }
+ }
+ skip_insert(addr_cache, c);
+ }
+ if (c->addr.sin_family != AF_INET)
+ return -1;
+ if (udp)
+ {
+ static int sock = -1;
+
+ if (sock == -1)
+ {
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (geteuid() == 0)
+ {
+ int port;
+ struct sockaddr_in myaddr;
+ port = 600 + time(0)%400;
+
+ memset(&myaddr, 0, sizeof(myaddr));
+ myaddr.sin_family = AF_INET;
+ while (port > 500)
+ {
+ myaddr.sin_port = htons(port);
+ if (bind(sock, (struct sockaddr *)&myaddr, sizeof(myaddr))== 0)
+ break;
+ port --;
+ }
+ if (port == 500)
+ {
+ fprintf(stderr, "%s: cannot bind priv udp port...\n", progname);
+ }
+ }
+ }
+ sendto(sock, cmd, strlen(cmd)+1, 0, (struct sockaddr *)&c->addr, sizeof(c->addr));
+ }
+ else
+ {
+ /* tcp - have to make a new connection each time */
+ int sock;
+ static int port = 0;
+ char buf[8192]; /* FIXME autosize */
+ int n;
+ int have = 0;
+
+ if (port == 0)
+ port = 600 + time(0)%400;
+
+ if (geteuid() == 0)
+ sock = rresvport(&port);
+ else
+ sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock == -1)
+ {
+ fprintf(stderr, "%s: cannot bind socket!!\n", progname);
+ return -1;
+ }
+#ifdef TCP_CONN_ABORT_THRESHOLD
+ {
+ int thresh;
+ thresh = 10*1000;
+ setsockopt(sock, IPPROTO_TCP, TCP_CONN_ABORT_THRESHOLD, (char*)&thresh, 4);
+ }
+#endif
+/* */
+ if (connect(sock, (struct sockaddr *)&c->addr, sizeof(c->addr))!= 0)
+ {
+ fprintf(stderr, "%s: cannot connect to %s\n", progname, c->host);
+ close(sock);
+ c->addr.sin_family = -1;
+ return -1;
+ }
+ write(sock, cmd, strlen(cmd)+1);
+ shutdown(sock, 1); /* don't want to write no more */
+ do
+ {
+ n = read(sock, buf+have, sizeof(buf)-1 - have);
+ if (n>0) have += n;
+ } while (n>0 && have < sizeof(buf)-1);
+ close(sock);
+ if (have <= 0)
+ return 0; /* probably OK, FIXME */
+ buf[have] = 0;
+ switch(buf[0])
+ {
+ case 0: return 0; /* definately ok */
+ case 1: /* error message */
+ fprintf(stderr, "%s: %s: %s\n", progname, c->host, buf+1);
+ return 1;
+ case 2: /* version number */
+ fprintf(stdout, "%s: %s\n", c->host, buf+1);
+ return 0;
+ case 3: /* listing */
+ init_recv(buf+1);
+ list_service(c->host, verbose);
+ /* FIXME */
+ return 0;
+
+ case 'm': /* old version number */
+ fprintf(stdout, "%s\n", buf);
+ return 0;
+ default: /* old metad */
+ /* FIXME */
+ return 0;
+ }
+ }
+ return 0;
+}
+
+
+static void list_service(char *host, int verbose)
+{
+ int b;
+ b = get_byte();
+ while (b == 1 || b == 2) /* a service */
+ {
+ char *sname, *home, *user, *crash, *prog;
+ char **args;
+ int argc;
+ int max, cnt, enabled;
+ int i;
+ int class;
+ char *classname;
+ char *pidfile = NULL;
+ int watch_output = 0;
+ int min, period, last;
+ int proto, port, active, backlog;
+
+ sname = get_str();
+ max = get_int();
+ home = get_str();
+ user = get_str();
+ crash = get_str();
+ if (b == 2)
+ {
+ watch_output = get_int();
+ pidfile = get_str();
+ }
+ cnt = get_int();
+ enabled = get_int();
+ prog = get_str();
+ argc = get_int();
+ args = (char**)malloc((argc+1)*sizeof(char*));
+ for (i=0 ; i<argc ; i++)
+ args[i] = get_str();
+ args[i] = NULL;
+ class = get_byte();
+ switch(class)
+ {
+ case 1: /* daemon */
+ classname = "daemon";
+ min = get_int();
+ period = get_int();
+ last = get_int();
+ break;
+ case 2: /* stream */
+ classname = "stream";
+ proto = get_int();
+ port = get_int();
+ backlog = get_int();
+ active = get_int();
+ break;
+ }
+/* printf("Host: %s\n", host); */
+ if (sname == NULL) sname = "*unknown*";
+ printf("%s:%s%*.0s %s %s (x%d)", host, sname, 18-(int)strlen(host) - (int)strlen(sname), "",
+ classname, enabled?"enabled":"DISABLED", cnt);
+ if (verbose)
+ {
+ printf("\n");
+ printf(" Prog: %s\n", prog);
+ printf(" Args:");
+ for (i=0; i<argc ; i++)
+ printf(" %s", args[i]);
+ printf("\n");
+ printf(" Opts:");
+ if (max) printf(" max=%d", max);
+ if (home) printf(" dir=%s", home);
+ if (user) printf(" user=%s", user);
+ if (crash) printf(" crash=%s", crash);
+ if (pidfile) printf(" pidfile=%s", pidfile);
+ if (watch_output) printf(" watch_output");
+ switch(class)
+ {
+ case 1: /* daemon */
+ if (min) printf(" min=%d", min);
+ if (period) printf(" period=%d", period);
+ break;
+ case 2:
+ printf(" port=%d", port);
+ printf(" backlog=%d", backlog);
+ if (!active) printf(" INACTIVE");
+ break;
+
+ }
+ printf("\n");
+ }
+ b = get_byte();
+ while (b == 3 || b == 4) /* process */
+ {
+ int pid, hold, status;
+ int forkedpid;
+ time_t start, xit;
+ pid = get_int();
+ if (b==4)
+ {
+ forkedpid = get_int();
+ get_int(); /* pipefd */
+ }
+ else forkedpid = 0;
+ start = get_int();
+ hold = get_int();
+ xit = get_int();
+ status = get_int();
+ if (verbose)
+ {
+ printf(" Proc: pid=%5d%s %s", (xit && forkedpid) ? forkedpid : pid,
+ (xit&&forkedpid)?"f":"", ctime(&start));
+ if (xit && forkedpid==0)
+ {
+ char *t,*t2;
+ time_t holdt;
+ holdt = hold - time(0);
+ t = prtime(holdt);
+ while(*t == ' ') t++;
+ t2=ctime(&xit)+4;
+ printf(" Exited %1.12s holding for %s\n", t2, t);
+ }
+ }
+ else
+ {
+ char *t;
+ time_t age = time(0) - start;
+ t = (char*)prtime(age);
+ while(*t == ' ') t++;
+ printf(" %d%s(%s%s)", (xit && forkedpid) ? forkedpid : pid,
+ (xit&&forkedpid)?"f":"",
+ (xit && forkedpid==0)?"exited:":"", t);
+ }
+ b = get_byte();
+ }
+ if (!verbose) printf("\n");
+ }
+}
--- /dev/null
+
+#include "metad.h"
+#include "skip.h"
+
+/*
+ * 3 - listing
+ * 1 - service
+ * 2 - service-2 with watchoutput and pidfile
+ * 3 - process-1
+ * 4 - process-2 with pipefd or it_forked
+ */
+
+void send_proc(proc_t p)
+{
+ if (p->it_forked > 0 || p->pipefd >= 0)
+ {
+ send_byte(4);
+ send_int(p->pid);
+ send_int(p->it_forked);
+ send_int(p->pipefd);
+ }
+ else
+ {
+ send_byte(3); /* process */
+ send_int(p->pid);
+ }
+ send_int(p->start_time);
+ send_int(p->hold_time);
+ send_int(p->exit_time);
+ send_int(p->status);
+}
+
+void send_service(service_t sv)
+{
+ int i;
+ proc_t *pp;
+ if (sv->watch_output || sv->pidfile)
+ send_byte(2);
+ else
+ send_byte(1); /* service */
+ send_str(sv->service);
+ send_int(sv->max_proc);
+ send_str(sv->home_dir);
+ send_str(sv->username);
+ send_str(sv->crash_prog);
+ if (sv->watch_output || sv->pidfile)
+ {
+ send_int(sv->watch_output);
+ send_str(sv->pidfile);
+ }
+ send_int(sv->start_cnt);
+ send_int(sv->enabled);
+ send_str(sv->program);
+ for (i=0 ; sv->args[i] ; i++);
+ send_int(i);
+ for (i=0 ; sv->args[i] ; i++)
+ send_str(sv->args[i]);
+ (sv->class->send_class)(sv);
+ for (pp = skip_first(sv->proc_list) ; pp ; pp = skip_next(pp))
+ send_proc(*pp);
+}
+
+void send_int(int i)
+{
+ send_byte((i>>24)& 0xff);
+ send_byte((i>>16)& 0xff);
+ send_byte((i>> 8)& 0xff);
+ send_byte((i>> 0)& 0xff);
+}
+void send_str(char *s)
+{
+ if (s == NULL)
+ send_int(0);
+ else
+ {
+ send_int(strlen(s)+1);
+ while (*s)
+ send_byte(*s++);
+ send_byte(0);
+ }
+}
+
+static char *sendbuf = NULL;
+static int sendsize;
+static int sendptr;
+
+void send_byte(int b)
+{
+ if (sendptr >= sendsize)
+ {
+ sendsize += 100;
+ sendbuf = (char*)realloc(sendbuf, sendsize);
+ }
+ sendbuf[sendptr++] = b;
+}
+
+void init_return()
+{
+ sendptr = 0;
+ if (sendbuf == NULL)
+ {
+ sendsize = 200;
+ sendbuf = malloc(sendsize);
+ }
+}
+
+void do_send(void *con)
+{
+ set_reply(con, sendbuf, sendptr);
+ sendbuf = NULL;
+ sendptr = 0;
+}
+
+char *get_return(int *i)
+{
+ char *c = sendbuf;
+ *i = sendptr;
+ sendbuf = NULL;
+ sendptr = 0;
+ return c;
+}
--- /dev/null
+
+#include "metad.h"
+#include "skip.h"
+#include <pwd.h>
+#include <grp.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <values.h>
+
+void *services;
+void *allprocs;
+
+int count_procs(service_t sv)
+{
+ proc_t *pp, p;
+ int cnt;
+ time_t now = time(0);
+ for (cnt=0, pp=skip_first(sv->proc_list) ; pp ; pp=skip_next(pp))
+ {
+ p = *pp;
+ if (p->exit_time == 0 || p->hold_time > now || p->pipefd >=0 || p->it_forked)
+ cnt++;
+ }
+ return cnt;
+}
+
+
+void check_service(service_t sv)
+{
+ /* check for exited processes and possibly run crash */
+ proc_t *pp;
+ time_t now = time(0);
+
+ for (pp=skip_first(sv->proc_list) ; pp ; )
+ {
+ proc_t p;
+ p = *pp;
+ pp = skip_next(pp);
+ if (p->exit_time == 0) {
+ /* monitoring a normal child
+ * we should have been alerted if the child exitted,
+ * but just-in-case... try sig0
+ */
+ if (kill(p->pid, 0) == -1 && errno == ESRCH && !is_saved_pid(p->pid)) {
+ p->status = 0; /* who knows. */
+ p->exit_time = now;
+ }
+ }
+ if (p->exit_time > 0 && p->hold_time == 0)
+ {
+ /* a newly exited process */
+ if (p->is_crash)
+ {
+ /* this is a crash recover prog exitting. just set the hold time */
+ p->hold_time = p->exit_time + sv->next_hold;
+ }
+ else if (WIFSIGNALED(p->status) || (WIFEXITED(p->status) && WEXITSTATUS(p->status) != 0))
+ {
+ /* exited badly */
+ if (p->exit_time - p->start_time < 30) {
+ if (sv->next_hold < MAXINT/2)
+ sv->next_hold *= 2;
+ } else
+ sv->next_hold = 2;
+ if (0 && sv->crash_prog)
+ {
+ /* FIXME */
+ }
+ else p->hold_time = p->exit_time + sv->next_hold;
+ }
+ else
+ {
+ if (sv->pidfile)
+ {
+ /* if this file is newer than start_time and contains a pid,
+ * record that pid as this pid and shedule regular checks
+ */
+ int fd = open(sv->pidfile, 0);
+ struct stat stb;
+ char pidline[100];
+ int n;
+ int pid;
+ if (fd >= 0
+ && fstat(fd, &stb) != -1
+ && stb.st_mtime >= p->start_time
+ && (n=read(fd, pidline, sizeof(pidline)-1))> 0
+ && (pidline[n]=0 , pid=atoi(pidline)) > 1
+ && kill(pid, 0) != -1
+ )
+ {
+ /* looks good ! */
+ p->it_forked = pid;
+ p->hold_time = 1;
+ waituntil(now+5); /* check it in 5 seconds and periodically */
+ }
+ else
+ {
+ /* give the program a few seconds to write to the file
+ * e.g. xdm forks, then creates the pid file
+ */
+ if (p->exit_time + 10 > now)
+ {
+ p->it_forked = -1;
+ waituntil(p->exit_time + 11);
+ }
+ else
+ {
+ // give up waiting
+ p->it_forked = 0;
+
+ /* probably exited badly if it didn't run for long
+ (keep in mind that the pidfile wasn't updated) */
+ if (p->exit_time - p->start_time < 30) {
+ if (sv->next_hold < MAXINT/2)
+ sv->next_hold *= 2;
+ } else
+ sv->next_hold = 2;
+
+ p->hold_time = p->exit_time + sv->next_hold;
+ }
+ }
+ if (fd >= 0) close(fd);
+ } else
+ sv->next_hold = 2;
+
+ }
+ }
+ else if (p->exit_time > 0 && p->it_forked > 0)
+ {
+ /* monitoring forked child
+ * try sending sig0 to see if process still exists
+ */
+ if (kill(p->it_forked, 0)== -1 && errno == ESRCH)
+ {
+ p->it_forked = 0;
+ p->exit_time = now;
+ }
+ else
+ waituntil(now+30); /* wait 30 seconds and check again */
+ }
+ if (p->pipefd >= 0)
+ {
+ if (readyon(p->pipefd))
+ {
+ int n = read(p->pipefd, p->pipebuf+p->bufptr, sizeof(p->pipebuf)-p->bufptr-1);
+ if (n<= 0)
+ {
+ close(p->pipefd);
+ p->pipefd = -1;
+ }
+ else
+ {
+ int i = 0;
+ p->bufptr += n;
+ while (i < p->bufptr)
+ {
+ while (i < p->bufptr && p->pipebuf[i] != '\n')
+ i++;
+ if (p->pipebuf[i] == '\n' || i > sizeof(p->pipebuf)-2)
+ {
+ /* ship out this much */
+ int j;
+ p->pipebuf[i] = '\0';
+ dolog(sv, p, p->pipebuf);
+ for (j=i+1 ; j<p->bufptr ; j++)
+ p->pipebuf[j-i-1] = p->pipebuf[j];
+ p-> bufptr -= i+1;
+ i = 0;
+ }
+ else
+ i++;
+ }
+ }
+ }
+ if (p->pipefd >= 0)
+ listenon(p->pipefd);
+ }
+ if (p->exit_time > 0 && p->hold_time < now && p->it_forked == 0
+ && (p->pipefd == -1 || p->exit_time +30 <now))
+ {
+ /* clean up */
+ if (p->pipefd>=0)
+ {
+ /* it has been 30 seconds since the parent exitted, time to
+ * discard the pipe...
+ */
+ close(p->pipefd);
+ p->pipefd = -1;
+ }
+ skip_delete(sv->proc_list, &p->pid);
+ skip_delete(allprocs, &p->pid);
+ free(p);
+ }
+ else if (p->exit_time > 0 && p->hold_time > 2)
+ waituntil(p->hold_time+1);
+ }
+ (sv->class->c_check_service)(sv);
+}
+
+int new_proc(service_t sv, char **env)
+{
+ /* fork, register new child
+ * in child, call sv->class->new_child for final setup before exec
+ */
+ int pid;
+ proc_t p;
+ char **envp;
+ extern char **environ;
+ time_t now;
+ int pipefd[2];
+
+ if (sv->enabled == 0)
+ return -2;
+ if (sv->max_proc > 0 && count_procs(sv) >= sv->max_proc)
+ return -2; /* too many already */
+
+ if (sv->class->prefork(sv))
+ return -2;
+
+ now = time(0);
+ if (sv->watch_output)
+ {
+ if (pipe(pipefd)== -1)
+ pipefd[0] = pipefd[1] = -1;
+ }
+ switch (pid = fork())
+ {
+ case -1:
+ if (sv->watch_output)
+ {
+ close(pipefd[0]); close(pipefd[1]);
+ }
+ return -1; /* cannot fork */
+ case 0: /* child */
+ errors_to(ERROR_SYSLOG, NULL);
+ if (sv->home_dir)
+ {
+ if (chdir(sv->home_dir)!=0)
+ {
+ error("Couldn't chdir to %s", sv->home_dir);
+ exit(10);
+ }
+ }
+ if (sv->username)
+ {
+ struct passwd *pw = getpwnam(sv->username);
+ if (pw) {
+ initgroups(sv->username, pw->pw_gid);
+ setgid(pw->pw_gid);
+ setuid(pw->pw_uid);
+ } else {
+ error("unknown user %s", sv->username);
+ exit(10);
+ }
+ }
+ (sv->class->new_child)(sv);
+ if (!env)
+ envp = environ;
+ else
+ {
+ int cnt, i;
+ cnt = 0;
+ for (i=0 ; env[i] ; i++) cnt++;
+ for (i=0 ; environ[i] ; i++) cnt++;
+ envp = (char**)malloc((cnt+1) * sizeof(char*));
+ cnt = 0;
+ for (i=0 ; env[i] ; i++)
+ envp[cnt++] = env[i];
+ for (i=0 ; environ[i] ; i++)
+ envp[cnt++] = environ[i];
+ envp[cnt] = NULL;
+ }
+ if (sv->watch_output && pipefd[0] >= 0)
+ {
+ close(pipefd[0]);
+ if (pipefd[1] != 1) close(1);
+ if (pipefd[1] != 2) close(2);
+ if (pipefd[1] == 0)
+ {
+ pipefd[1] = dup(pipefd[1]);
+ close(0);
+ }
+ open("/dev/null", 0);
+ dup(pipefd[1]);
+ if (pipefd[1] > 2)
+ {
+ dup(pipefd[1]);
+ close(pipefd[1]);
+ }
+ }
+ execve(sv->program, sv->args, envp);
+ exit(11);
+ default: /* parent */
+ p = (proc_t)malloc(sizeof(struct proc));
+ if (sv->watch_output)
+ {
+ close(pipefd[1]);
+ p->pipefd = pipefd[0];
+ p->bufptr = 0;
+ if (p->pipefd >= 0) listenon(p->pipefd);
+ fcntl(p->pipefd, F_SETFD, 1);
+ }
+ else
+ p->pipefd = -1;
+ p->pid = pid;
+ p->service = sv;
+ p->it_forked = 0;
+ p->is_crash = 0;
+ p->start_time = now;
+ p->hold_time = 0;
+ p->exit_time = 0;
+ skip_insert(sv->proc_list, p);
+ sv->start_cnt ++;
+ skip_insert(allprocs, p);
+ sv->class->new_parent(sv,p);
+ }
+ return pid;
+}
+
+void prepare_restart(void)
+{
+ service_t *sp;
+ for (sp = skip_first(services) ; sp ; sp=skip_next(sp))
+ {
+ service_t sv = *sp;
+ proc_t *pp;
+ for (pp=skip_first(sv->proc_list) ; pp ; pp=skip_next(pp))
+ {
+ proc_t p = *pp;
+ if (p->pipefd >= 0)
+ fcntl(p->pipefd, F_SETFD, 0);
+ }
+ }
+}
+
+static int proc_cmp(proc_t a, proc_t b, int *kp)
+{
+ int k;
+ if (b) k=b->pid; else k = *kp;
+ return k - a->pid;
+}
+
+service_t new_service(char *name, class_t class)
+{
+ service_t sv = (service_t)malloc(sizeof(struct service));
+
+ sv->service = strdup(name);
+ sv->class = class;
+ sv->max_proc = 1;
+ sv->crash_prog = NULL;
+ sv->home_dir = NULL;
+ sv->username = NULL;
+ sv->pidfile = NULL;
+ sv->watch_output = 0;
+ sv->enabled = 1;
+ sv->start_cnt = 0;
+ sv->program = NULL;
+ sv->args = 0;
+ sv->pending = 0;
+ sv->proc_list = skip_new(proc_cmp, NULL, NULL);
+ sv->next_hold = 2;
+ (*class->copy_state)(NULL, sv);
+ return sv;
+}
+
+int process_opt(service_t sv, char *opt)
+{
+ /* max= crash= dir= user= enabled= */
+
+ if (strncmp(opt, "max=", 4)==0)
+ {
+ sv->max_proc = atoi(opt+4);
+ return 1;
+ }
+ else if (strncmp(opt, "crash=", 6)==0)
+ {
+ sv->crash_prog = strdup(opt+6);
+ return 1;
+ }
+ else if (strncmp(opt, "dir=", 4)==0)
+ {
+ sv->home_dir = strdup(opt+4);
+ return 1;
+ }
+ else if (strncmp(opt, "user=", 5)==0)
+ {
+ sv->username = strdup(opt+5);
+ return 1;
+ }
+ else if (strncmp(opt, "enabled=", 8)==0)
+ {
+ if (strcmp(opt+8, "no")==0)
+ sv->enabled = 0;
+ else if (strcmp(opt+8, "yes")==0)
+ sv->enabled = 1;
+ else return -1;
+ return 1;
+ }
+ else if (strncmp(opt, "pidfile=", 8)==0)
+ {
+ sv->pidfile = strdup(opt+8);
+ return 1;
+ }
+ else if (strcmp(opt, "watch_output")==0)
+ {
+ sv->watch_output = 1;
+ return 1;
+ }
+ else return 0;
+}
+
+static int service_cmp(service_t a, service_t b, char *k)
+{
+ if (b) k = b->service;
+ return strcmp(a->service, k);
+}
+
+void service_init(void)
+{
+ services = skip_new(service_cmp, NULL, NULL);
+ allprocs = skip_new(proc_cmp, NULL, NULL);
+}
+
+void free_service(service_t sv)
+{
+ if (sv->service) free(sv->service);
+ if (sv->crash_prog) free(sv->crash_prog);
+ if (sv->classinfo) (sv->class->free_state)(sv);
+ if (sv->home_dir) free(sv->home_dir);
+ if (sv->username) free(sv->username);
+ if (sv->program) free(sv->program);
+ if (sv->pidfile) free(sv->pidfile);
+ if (sv->args) strlistfree(sv->args);
+ if (sv->proc_list) skip_free(sv->proc_list);
+
+ free(sv);
+}
+
+service_t find_service(char *name)
+{
+ service_t *sp;
+ if (name == NULL) return NULL;
+ sp = skip_search(services, name);
+ if (sp)
+ return *sp;
+ else
+ return NULL;
+}
--- /dev/null
+
+/* Skiplists.
+ Provide three functions to skip_new:
+ cmp(e1, e2, k)
+ This should compare e1 with e2 or (if null) k (a key)
+ free should free an entry
+ errfn should do something with an error message
+
+ skip_new(cmp,free,errfn) -> list
+ skip_insert(list,entry) -> bool
+ skip_delete(list,key) -> bool
+ skip_search(list,key) -> *entry
+ skip_first(list) -> *entry
+ skip_next(*entry) -> *entry
+
+ */
+#include <unistd.h>
+#include <stdlib.h>
+
+#define false 0
+#define true 1
+typedef char boolean;
+#define BitsInRandom 31
+
+#define MaxNumberOfLevels 16
+#define MaxLevel (MaxNumberOfLevels-1)
+#define newNodeOfLevel(l) (node)malloc(sizeof(struct node)+(l)*sizeof(node))
+
+typedef void *keyType;
+typedef void *valueType;
+
+
+typedef struct node
+{
+ valueType value; /* must be first for skip_next to work */
+ struct node *forward[1]; /* variable sized array of forward pointers */
+} *node;
+
+typedef struct list
+{
+ int level; /* Maximum level of the list
+ (1 more than the number of levels in the list) */
+ node header; /* pointer to head of list */
+ int (*cmpfn)(); /* compares two values or a key an a value */
+ void (*freefn)(); /* free a value */
+ void (*errfn)(); /* when malloc error occurs */
+} *list;
+
+static void default_errfn(char *msg)
+{
+ write(2, msg, (int)strlen(msg));
+ write(2, "\n", 2);
+ abort();
+}
+
+static int randomsLeft = 0;
+static int randomBits;
+
+list skip_new(cmpfn, freefn, errfn)
+int (*cmpfn)();
+void (*freefn)();
+void (*errfn)();
+{
+ list l;
+ int i;
+
+ if (errfn == NULL)
+ errfn = default_errfn;
+ l = (list)malloc(sizeof(struct list));
+ if (l == NULL)
+ {
+ (*errfn)("Malloc failed in skip_new");
+ return NULL;
+ }
+ l->level = 0;
+ l->header = newNodeOfLevel(MaxNumberOfLevels);
+ if (l->header == NULL)
+ {
+ (*errfn)("Malloc failed in skip_new");
+ return NULL;
+ }
+ for (i=0; i<MaxNumberOfLevels; i++)
+ l->header->forward[i] = NULL;
+ l->header->value = NULL;
+ l->cmpfn = cmpfn;
+ l->freefn = freefn;
+ if (errfn)
+ l->errfn = errfn;
+ else
+ l->errfn = default_errfn;
+ return l;
+}
+
+void skip_free(l)
+list l;
+{
+ register node p;
+ p = l->header;
+ while (p != NULL)
+ {
+ register node q;
+ q = p->forward[0];
+ if (p != l->header) /* header has no meaningful value */
+ (*l->freefn)(p->value);
+ free(p);
+ p = q;
+ }
+ free(l);
+}
+
+static int randomLevel()
+{
+ register int level = 0;
+ register int b;
+ do {
+ if (randomsLeft == 0) {
+#ifdef POSIX
+ randomBits = lrand48();
+#else
+ randomBits = random();
+#endif
+ randomsLeft = BitsInRandom/2;
+ }
+ b = randomBits&3;
+ randomBits>>=2;
+ randomsLeft--;
+ if (!b) level++;
+ } while (!b);
+ return(level>MaxLevel ? MaxLevel : level);
+}
+
+boolean skip_insert(l, value)
+list l;
+valueType value;
+{
+ int k;
+ node update[MaxNumberOfLevels];
+ node p,q;
+ int cm;
+
+ p = l->header;
+ for (k=l->level ; k>=0 ; k--)
+ {
+ cm = 1;
+ while ( p->forward[k] != NULL
+ && (cm=(*l->cmpfn)(p->forward[k]->value, value, 0))<0)
+ p = p->forward[k];
+ update[k] = p;
+ }
+
+ if (cm == 0)
+ return false;
+
+ k = randomLevel();
+ if (k> l->level)
+ {
+ k = ++l->level;
+ update[k] = l->header;
+ }
+ q = newNodeOfLevel(k);
+ if (q == NULL)
+ {
+ (*l->errfn)("Malloc failed in skip_insert");
+ return false;
+ }
+ q->value = value;
+ for ( ; k>=0 ; k--)
+ {
+ p = update[k];
+ q->forward[k] = p->forward[k];
+ p->forward[k] = q;
+ }
+ return true;
+}
+
+boolean skip_delete(l, key)
+list l;
+keyType key;
+{
+ int k, m;
+ node update[MaxNumberOfLevels];
+ node p,q;
+ int cm;
+
+ p = l->header;
+
+ for (k=l->level ; k>=0 ; k--)
+ {
+ cm = 1;
+ while ( p->forward[k] != NULL
+ && (cm=(*l->cmpfn)(p->forward[k]->value, NULL, key))<0)
+ p = p->forward[k];
+ update[k] = p;
+ }
+
+ if (cm == 0)
+ {
+ q = update[0]->forward[0];
+ m=l->level;
+ for (k=0; k<=m && (p=update[k])->forward[k] == q ; k++)
+ p->forward[k] = q->forward[k];
+ if (l->freefn)
+ (*l->freefn)(q->value);
+ free(q);
+ while (l->header->forward[m] == NULL && m > 0)
+ m--;
+ l->level = m;
+ return true;
+ }
+ else
+ return false;
+}
+
+valueType *skip_search(l, key)
+list l;
+keyType key;
+{
+ int k;
+ node p;
+ int cm;
+
+ p = l->header;
+ for (k=l->level ; k>=0 ; k--)
+ {
+ cm = 1;
+ while ( p->forward[k] != NULL
+ && (cm=(*l->cmpfn)(p->forward[k]->value, NULL, key))<0)
+ p = p->forward[k];
+ }
+ if (cm != 0)
+ return NULL;
+ return &p->forward[0]->value;
+}
+
+valueType *skip_first(l)
+list l;
+{
+ node p;
+
+ p = l->header;
+ if (p->forward[0] == NULL)
+ return NULL;
+ return & p->forward[0]->value;
+}
+
+valueType *skip_next(c)
+valueType *c;
+{
+ node p;
+ p = (node)c;
+ if (p->forward[0] == NULL)
+ return NULL;
+ return & p->forward[0]->value;
+}
--- /dev/null
+
+/* include for skiplists */
+#define _PROTO_
+#ifdef _PROTO_
+void *skip_new(int cmpfn(), void freefn(), void errfn());
+char skip_insert(void *list, void *entry);
+char skip_delete(void *list, void *key);
+void *skip_search(void *list, void *key);
+void *skip_first(void *list);
+void *skip_next(void *last);
+void skip_free(void *);
+#else
+void *skip_new();
+char skip_insert();
+char skip_delete();
+void *skip_search();
+void *skip_first();
+void *skip_next();
+#endif
--- /dev/null
+
+int strnccmp(a,b, n)
+char *a, *b;
+int n;
+{
+ char ac, bc;
+
+ while(n--)
+ {
+ ac = *a++;
+ if (ac>='a' && ac <= 'z') ac -= 'a'-'A';
+ bc = *b++;
+ if (bc>='a' && bc <= 'z') bc -= 'a'-'A';
+ if (ac == 0 || ac != bc) break;
+ }
+ if (ac<bc) return -1;
+ if (ac > bc) return 1;
+ return 0;
+}
+
+int strccmp(a,b)
+char *a, *b;
+{
+ return strnccmp(a,b,(int)strlen(a)+1);
+}
--- /dev/null
+
+#define NULL (0)
+char *malloc();
+
+char *strdup(char *s)
+{
+ if (s==NULL)
+ return s;
+ return (char *)strcpy(malloc(strlen(s)+1), s);
+}
+
+char *strndup(char *s, int a)
+{
+ char *r;
+ if (s == NULL)
+ return s;
+
+ r = (char*)malloc(a+1);
+ strncpy(r, s, a);
+ r[a] = 0;
+ return r;
+}
--- /dev/null
+
+#include "metad.h"
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#ifdef SOLARIS
+#include <sys/fcntl.h>
+#else
+#include <sys/file.h>
+#endif
+
+typedef struct stream_opts
+{
+ int proto; /* always TCP */
+ int port;
+ int backlog;
+ int sock;
+ int newsock;
+} *stream_t;
+
+#define c(sv) ((stream_t)((sv)->classinfo))
+
+static int stream_opt(service_t sv, char *opt)
+{
+ /* port= */
+ char *protos = "tcp";
+ if (strncmp(opt, "port=", 5) == 0) {
+ char *ep;
+ int port;
+ struct servent *se;
+ port = strtoul(opt+5, &ep, 10);
+ if (opt[5] && !*ep) {
+ c(sv)->port = htons(port);
+ return 1;
+ }
+ se = getservbyname(opt+5, protos);
+ if (se) {
+ c(sv)->port = se->s_port;
+ return 1;
+ }
+ }
+ if (strncmp(opt, "backlog=", 8) == 0) {
+ char *ep;
+ c(sv)->backlog = strtoul(opt+8, &ep, 10);
+ if (opt[8] && !*ep)
+ return 1;
+ }
+ return 0;
+}
+
+static void stream_register(service_t sv)
+{
+ /* create a socket and bind it */
+ stream_t s = sv->classinfo;
+ struct sockaddr_in addr;
+ int opt;
+
+ if (s->sock >=0 || s->port <= 0)
+ return;
+ s->sock = socket(AF_INET, SOCK_STREAM, s->proto);
+ if (s->sock < 0)
+ return;
+ opt = 1;
+ setsockopt(s->sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = s->port;
+ if (bind(s->sock, (struct sockaddr *)&addr, sizeof(addr))) {
+ close(s->sock);
+ s->sock = -1;
+ return;
+ }
+
+ if (listen(s->sock, s->backlog)<0) {
+ close(s->sock);
+ s->sock = -1;
+ return;
+ }
+ nodelay(s->sock);
+}
+
+static void stream_unregister(service_t sv)
+{
+ stream_t s = sv->classinfo;
+ if (s->sock>=0)
+ close(s->sock);
+ s->sock = -1;
+}
+
+
+static void stream_check(service_t sv)
+{
+ stream_t s = sv->classinfo;
+
+ if (s->sock >=0) {
+ if (readyon(s->sock)) {
+ char *env[3];
+ env[0] = "METAD_REASON=connect";
+ env[1] = "METAD_ARG=";
+ env[2] = NULL;
+ if (new_proc(sv, env) == -1)
+ if (s->newsock >= 0) {
+ close(s->newsock);
+ s->newsock = -1;
+ }
+ }
+ listenon(s->sock);
+ }
+}
+
+
+
+static int stream_prefork(service_t sv)
+{
+ int f;
+ stream_t s = sv->classinfo;
+ if (s->sock < 0)
+ return -2;
+
+ s->newsock = accept(s->sock, NULL, 0);
+ if (s->newsock < 0)
+ return -2;
+ /* disable NDELAY which might be inherited */
+ f = fcntl(s->newsock, F_GETFL, 0);
+ f &= ~O_NDELAY;
+ fcntl(s->newsock, F_SETFL, f);
+ return 0;
+}
+
+static void stream_copy(service_t from, service_t to)
+{
+ stream_t n,o;
+ n = (stream_t)malloc(sizeof(struct stream_opts));
+ if (from) {
+ o = from->classinfo;
+ n->proto = o->proto;
+ n->port = o->port;
+ n->sock = o->sock;
+ n->backlog = o->backlog;
+ o->sock = -1;
+ n->newsock = -1;
+ } else {
+ n->proto = IPPROTO_TCP;
+ n->backlog = 10;
+ n->port = 0;
+ n->sock = -1;
+ n->newsock = -1;
+ }
+ to->classinfo = n;
+}
+
+static void stream_freestate(service_t sv)
+{
+ stream_t s = sv->classinfo;
+ if (s->sock >= 0)
+ close(s->sock);
+ free(s);
+}
+
+static void stream_newparent(service_t sv, proc_t p)
+{
+ if (c(sv)->newsock >= 0) {
+ close(c(sv)->newsock);
+ c(sv)->newsock = -1;
+ }
+}
+
+static void stream_newchild(service_t sv)
+{
+ close(c(sv)->sock);
+ if (c(sv)->newsock < 0)
+ exit(2);
+ dup2(c(sv)->newsock, 0);
+ dup2(c(sv)->newsock, 1);
+ if (c(sv)->newsock > 1)
+ close(c(sv)->newsock);
+
+}
+
+static void stream_send(service_t sv)
+{
+ send_byte(2); /*stream */
+ send_int(c(sv)->proto);
+ send_int(ntohs(c(sv)->port));
+ send_int(c(sv)->backlog);
+ send_int(c(sv)->sock>=0);
+}
+
+struct class stream_class =
+{ "stream", stream_opt, stream_register, stream_check,
+ stream_copy, stream_freestate, stream_send,
+ stream_unregister, stream_newparent, stream_newchild,
+ stream_prefork
+};
+
+
--- /dev/null
+
+/* duplicate a list of strings */
+
+#define NULL ((void *)0)
+char *strdup(char *);
+void *malloc(int);
+
+char **strlistdup(char **l)
+{
+ int len = 0;
+ char **rv;
+ while (l[len]) len++;
+
+ rv = (char**)malloc((len+1)*sizeof(char *));
+ for (len=0 ; l[len]; len++)
+ rv[len] = strdup(l[len]);
+ rv[len] = NULL;
+ return rv;
+}
+
+void strlistfree(char **l)
+{
+ int i;
+ for (i=0 ; l[i] ; i++)
+ free(l[i]);
+ free(l);
+}
+
--- /dev/null
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * split a string into an array of strings,
+ * separated by any chars in the fs string
+ * strings may contain chars from fs if quoted
+ * quotes may be escaped or quoted
+ *
+ * MWR, UNSW, Oct 84
+ */
+
+char **
+strsplit(s, fs)
+char *s, *fs;
+{
+ register char *sp, *sp2, *delim, **ssp, *ns;
+ register unsigned i, num;
+ static char quote[] = "'";
+
+ extern char *malloc();
+
+ if((ns = malloc((unsigned) strlen(s) + 1)) == NULL)
+ return NULL;
+ sp = s;
+ sp2 = ns;
+ num = 0;
+ while(*sp)
+ {
+ while(*sp && strchr(fs, *sp))
+ sp++;
+ if(!*sp)
+ break;
+
+ num++;
+ do
+ {
+ delim = fs;
+ while(*sp && !strchr(delim, *sp))
+ {
+ switch(*sp)
+ {
+ case '\'':
+ case '"':
+ if(delim == fs)
+ {
+ quote[0] = *sp++;
+ delim = quote;
+ continue;
+ }
+ break;
+ case '\\':
+ if(sp[1] == '\'' || sp[1] == '"')
+ sp++;
+ break;
+ }
+ *sp2++ = *sp++;
+ }
+ if(delim == quote && *sp)
+ sp++;
+ }
+ while(delim == quote && *sp);
+ *sp2++ = '\0';
+ }
+
+ if((ssp = (char **) malloc(sizeof(char *) * (num + 2))) == NULL)
+ return NULL;
+ sp = ns;
+ ssp[0] = ns;
+ for(i = 1; i <= num; i++)
+ {
+ ssp[i] = sp;
+ while(*sp)
+ sp++;
+ sp++;
+ }
+ ssp[i] = NULL;
+ return &ssp[1];
+}
--- /dev/null
+
+char version[] = "2.17";
+
+/*
+ * 2.17 06aug2003 metad: check if pid has benee collected before assuming a failed kill means that it is lost.
+ * 2.16 18sep2002 metad: add "stream" service for tcp listening
+ * metac: understand listing of stream service
+ * 2.15 17aug2001 metad: fixed ordering in main_loop
+ * 2.14 07aug2001 metad: short malloc in new_proc was causing problems
+ * 2.13 30apr99 metad: errors during startup were getting lost
+ * lookup username every time instead of once only
+ * disassociate from controlling tty
+ * 2.12 25may98 metad: bugfix - pidfile was not being closed!!
+ * 2.11 08oct96 metad: bug whereby would sometimes spin on failing process as next_hold was set to 0
+ * 2.10 15aug96 metad: that last fix was broken - try again
+ * 2.09 02aug96 metad: where re-read config file, didn't update server pointers in proc structures...
+ * 2.08 04jul96 metad: if pidfile and watchout_put options in force, then
+ * metad would wait for pipe to close even if daemon exitted.
+ * Now metad only wait 30 more seconds for pipe to close, then closes it.
+ * Also restart properly saves it_forked info.
+ * 2.07 02apr96 metad: fix bug in user= processing
+ * 2.06 06mar96 metad: fix memory buf in free_service
+ * 2.05 01feb96 metad: preserve enabled state over restart
+ * 2.04 04dec95 metad: wait a little while for child to write to pidfile -- xdm is slow
+ * 2.03 14nov95 metad: fixed bug where messaged returned on udb request caused crash
+ * 2.02 31oct95 metad: pidfile and watch_output options, minor improvements to logging
+ * metac: understand new protocol phrases for pidfile and watch_output
+ * 2.01 25oct95 metad: zero holdtimes when run command received
+ * metac: improve listings: brief and long, hostname included
+ *
+ */