]> git.neil.brown.name Git - metad.git/commitdiff
Initial checking
authorNeil F. Brown <neilb@dulcimer.orchestra.cse.unsw.EDU.AU>
Tue, 6 Jun 2006 09:26:41 +0000 (19:26 +1000)
committerNeil F. Brown <neilb@dulcimer.orchestra.cse.unsw.EDU.AU>
Tue, 6 Jun 2006 09:26:41 +0000 (19:26 +1000)
36 files changed:
Buildfile [new file with mode: 0644]
PROTOCOL [new file with mode: 0644]
TODO [new file with mode: 0644]
args.c [new file with mode: 0644]
args.h [new file with mode: 0644]
broadcast.c [new file with mode: 0644]
classes.c [new file with mode: 0644]
commands.c [new file with mode: 0644]
control.c [new file with mode: 0644]
daemon.c [new file with mode: 0644]
dlink.c [new file with mode: 0644]
dlink.h [new file with mode: 0644]
error.c [new file with mode: 0644]
loadstate.c [new file with mode: 0644]
mainloop.c [new file with mode: 0644]
meta.c [new file with mode: 0644]
metad.c [new file with mode: 0644]
metad.h [new file with mode: 0644]
metad.texi [new file with mode: 0644]
nargs.c [new file with mode: 0644]
ports.c [new file with mode: 0644]
prtime.c [new file with mode: 0644]
publish [new file with mode: 0644]
read_config.c [new file with mode: 0644]
recvlist.c [new file with mode: 0644]
sendcmd.c [new file with mode: 0644]
sendlist.c [new file with mode: 0644]
service.c [new file with mode: 0644]
skip.c [new file with mode: 0644]
skip.h [new file with mode: 0644]
strccmp.c [new file with mode: 0644]
strdup.c [new file with mode: 0644]
stream.c [new file with mode: 0644]
strlistdup.c [new file with mode: 0644]
strsplit.c [new file with mode: 0644]
version.c [new file with mode: 0644]

diff --git a/Buildfile b/Buildfile
new file mode 100644 (file)
index 0000000..f5d5ccd
--- /dev/null
+++ b/Buildfile
@@ -0,0 +1,55 @@
+
+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
diff --git a/PROTOCOL b/PROTOCOL
new file mode 100644 (file)
index 0000000..2977d95
--- /dev/null
+++ b/PROTOCOL
@@ -0,0 +1,27 @@
+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
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..b9ead0d
--- /dev/null
+++ b/TODO
@@ -0,0 +1,7 @@
+
+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
diff --git a/args.c b/args.c
new file mode 100644 (file)
index 0000000..26bded0
--- /dev/null
+++ b/args.c
@@ -0,0 +1,119 @@
+
+/*
+ * 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];
+}
diff --git a/args.h b/args.h
new file mode 100644 (file)
index 0000000..6c9af00
--- /dev/null
+++ b/args.h
@@ -0,0 +1,9 @@
+
+#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)
diff --git a/broadcast.c b/broadcast.c
new file mode 100644 (file)
index 0000000..b1ffeec
--- /dev/null
@@ -0,0 +1,84 @@
+
+/* 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);
+}
diff --git a/classes.c b/classes.c
new file mode 100644 (file)
index 0000000..088fc42
--- /dev/null
+++ b/classes.c
@@ -0,0 +1,17 @@
+
+#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;
+}
diff --git a/commands.c b/commands.c
new file mode 100644 (file)
index 0000000..e8d93a5
--- /dev/null
@@ -0,0 +1,253 @@
+
+#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;
+}
diff --git a/control.c b/control.c
new file mode 100644 (file)
index 0000000..1d62968
--- /dev/null
+++ b/control.c
@@ -0,0 +1,312 @@
+
+#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);
+}
diff --git a/daemon.c b/daemon.c
new file mode 100644 (file)
index 0000000..88c4592
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,137 @@
+
+#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
+    };
diff --git a/dlink.c b/dlink.c
new file mode 100644 (file)
index 0000000..77c7b5e
--- /dev/null
+++ b/dlink.c
@@ -0,0 +1,74 @@
+
+/* 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));
+}
diff --git a/dlink.h b/dlink.h
new file mode 100644 (file)
index 0000000..996b0d8
--- /dev/null
+++ b/dlink.h
@@ -0,0 +1,23 @@
+
+/* 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*);
diff --git a/error.c b/error.c
new file mode 100644 (file)
index 0000000..43262bb
--- /dev/null
+++ b/error.c
@@ -0,0 +1,112 @@
+
+#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);
+}
+
diff --git a/loadstate.c b/loadstate.c
new file mode 100644 (file)
index 0000000..620b508
--- /dev/null
@@ -0,0 +1,158 @@
+
+/*
+ * 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);
+}
diff --git a/mainloop.c b/mainloop.c
new file mode 100644 (file)
index 0000000..c7ea270
--- /dev/null
@@ -0,0 +1,158 @@
+
+
+#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);
+       }
+    }
+}
+
diff --git a/meta.c b/meta.c
new file mode 100644 (file)
index 0000000..29205be
--- /dev/null
+++ b/meta.c
@@ -0,0 +1,244 @@
+
+/* 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 */
+}
diff --git a/metad.c b/metad.c
new file mode 100644 (file)
index 0000000..cd44de0
--- /dev/null
+++ b/metad.c
@@ -0,0 +1,33 @@
+
+#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 */
+}
diff --git a/metad.h b/metad.h
new file mode 100644 (file)
index 0000000..395525c
--- /dev/null
+++ b/metad.h
@@ -0,0 +1,145 @@
+
+#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);
diff --git a/metad.texi b/metad.texi
new file mode 100644 (file)
index 0000000..fa222c9
--- /dev/null
@@ -0,0 +1,585 @@
+\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
diff --git a/nargs.c b/nargs.c
new file mode 100644 (file)
index 0000000..f831299
--- /dev/null
+++ b/nargs.c
@@ -0,0 +1,117 @@
+
+/*
+ * 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];
+}
diff --git a/ports.c b/ports.c
new file mode 100644 (file)
index 0000000..34ba265
--- /dev/null
+++ b/ports.c
@@ -0,0 +1,29 @@
+
+#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);
+}
diff --git a/prtime.c b/prtime.c
new file mode 100644 (file)
index 0000000..bca343c
--- /dev/null
+++ b/prtime.c
@@ -0,0 +1,38 @@
+
+#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);
+}
diff --git a/publish b/publish
new file mode 100644 (file)
index 0000000..cb0c945
--- /dev/null
+++ b/publish
@@ -0,0 +1,16 @@
+#!/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 *
diff --git a/read_config.c b/read_config.c
new file mode 100644 (file)
index 0000000..f9c003e
--- /dev/null
@@ -0,0 +1,148 @@
+
+#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;
+}
diff --git a/recvlist.c b/recvlist.c
new file mode 100644 (file)
index 0000000..a13b426
--- /dev/null
@@ -0,0 +1,37 @@
+
+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;
+}
diff --git a/sendcmd.c b/sendcmd.c
new file mode 100644 (file)
index 0000000..7f115e3
--- /dev/null
+++ b/sendcmd.c
@@ -0,0 +1,324 @@
+
+/* 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");
+    }
+}
diff --git a/sendlist.c b/sendlist.c
new file mode 100644 (file)
index 0000000..6b1c093
--- /dev/null
@@ -0,0 +1,121 @@
+
+#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;
+}
diff --git a/service.c b/service.c
new file mode 100644 (file)
index 0000000..4d3a09f
--- /dev/null
+++ b/service.c
@@ -0,0 +1,449 @@
+
+#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;
+}
diff --git a/skip.c b/skip.c
new file mode 100644 (file)
index 0000000..d7aca1e
--- /dev/null
+++ b/skip.c
@@ -0,0 +1,254 @@
+
+/* 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;
+}
diff --git a/skip.h b/skip.h
new file mode 100644 (file)
index 0000000..afad69b
--- /dev/null
+++ b/skip.h
@@ -0,0 +1,19 @@
+
+/* 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
diff --git a/strccmp.c b/strccmp.c
new file mode 100644 (file)
index 0000000..36b20d4
--- /dev/null
+++ b/strccmp.c
@@ -0,0 +1,25 @@
+
+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);
+}
diff --git a/strdup.c b/strdup.c
new file mode 100644 (file)
index 0000000..35bbb06
--- /dev/null
+++ b/strdup.c
@@ -0,0 +1,22 @@
+
+#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;
+}
diff --git a/stream.c b/stream.c
new file mode 100644 (file)
index 0000000..ae6b818
--- /dev/null
+++ b/stream.c
@@ -0,0 +1,198 @@
+
+#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
+};
+
+  
diff --git a/strlistdup.c b/strlistdup.c
new file mode 100644 (file)
index 0000000..489f943
--- /dev/null
@@ -0,0 +1,28 @@
+
+/* 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);
+}
+
diff --git a/strsplit.c b/strsplit.c
new file mode 100644 (file)
index 0000000..7f82a7c
--- /dev/null
@@ -0,0 +1,79 @@
+#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];
+}
diff --git a/version.c b/version.c
new file mode 100644 (file)
index 0000000..c84afa1
--- /dev/null
+++ b/version.c
@@ -0,0 +1,31 @@
+
+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
+ *
+ */