
/*
 * RUNSLIP.C - Manage multiple SLIP connections, automatically reconnect
 *             on failure (i.e. maintain connection)
 *
 * (c)Copyright 1994, Matthew Dillon, All rights reserved.  This code may
 *    be used in any project, commercial or otherwise, as long as I am given
 *    appropriate credit and as long as this copyright remains intact in all
 *    source and document files.
 *
 * notty -12 runslip >>logfile 2>&1	(from a bourn shell)
 *
 * RunSlip controls outgoing slip lines.  The configuration file is tested
 * once every second for changes and the running state of various interfaces
 * is modified.  RUNSLIP also checks the command file for sequential
 * commands (disable, enable, quit commands)
 */

#include "defs.h"

#define CO_BLANK_SLOT	0
#define CO_DISABLED	1
#define CO_CONNECTING	2
#define CO_CONNECTED	3
#define CO_FAILED	4

typedef struct Control {
    struct Control *co_Next;
    int 	co_State;
    int 	co_OldState;
    int		co_Enabled;
    int		co_Baud;
    int		co_Mtu;
    int		co_Interface;
    int		co_Sleep;
    int		co_FailCounter;
    int		co_Pid;		/* running dudiscdev		*/
    int		co_DidFail;
    char 	*co_Name;
    char	*co_Command;
    char	*co_DuCmd;
    char	*co_IfAddr;
    char	*co_IfBroadCast;
    char	*co_IfPP;
    char	*co_IfNetMask;
    struct Line *co_Line;
} Control;

typedef struct Line {
    struct Line *li_Next;
    Control 	*li_Control;	/* current active control	*/
    char	*li_DevName;	/* line device			*/
} Line;

int  ReadLock(const char *linename);
int  LockLine(Control *ctl);
void UnLockLine(Control *ctl);
void Disconnect(Control *ctl, int newState);
void ConnectCommands(Control *ctl);
void DisconnectCommands(Control *ctl);
void system_fmt(Control *co, const char *ctl, ...);
void log(Control *co, const char *ctl, ...);
void LoadConfigFile(const char *fileName);
void RunCommandFile(const char *fileName);
void ReconcileControl(Control *ctl);
Control *FindControl(const char *name);
Line *FindLine(const char *device, int createMe);
char *myStrDup(const char *str);
void freeStrPtr(char **pptr);
int replaceString(char **p1, char **p2);
char *extractField(const char *ptr, const char *def);
char *extractLine(const char *ptr, const char *def);
int getArg0(int pid, char *buf, int bufMax);
void setEnv(const char *envName, const char *ctl, ...);

Control *ControlBase;
Line	*LineBase;

char    *StateStr[] = {
    "BLANK_SLOT",
    "IDLE",
    "CONNECTING",
    "CONNECTED",
    "FAILED"
};

int
main(int ac, char **av)
{
    int i;

    log(NULL, "RUNSLIP - STARTING");
    LoadConfigFile(RUNSLIP_CONF);

    for (;;) {
        sleep(1);

        /*
         * If configuration file was modified, reload it
         */

	{
	    struct stat st;

	    /*
	     * If command file exists, run commands from it
	     */

	    if (stat(RUNSLIP_CMD, &st) == 0) {
	        log(NULL, "RUN COMMANDS");
		RunCommandFile(RUNSLIP_CMD);
	    }
	}

	/*
	 * Check states
	 */

	{
	    Control *ctl;
	    Line *line;

	    for (ctl = ControlBase; ctl; ctl = ctl->co_Next) {
	        line = ctl->co_Line;

	        if (ctl->co_OldState != ctl->co_State) {
		    ctl->co_OldState = ctl->co_State;
		    log(ctl, "state %s", StateStr[ctl->co_State]);
	        }

	        switch(ctl->co_State) {
		case CO_BLANK_SLOT:
		    break;
		case CO_DISABLED:
		    /*
		     * If the interface is disabled and it wants to be
		     * enabled, we enable it.
		     */
		    if (ctl->co_Enabled) {
		        /*
		         * If another interface is using the line,
		         * disable it.
		         */
			char buf[256];
			FILE *fp;

		        if (line->li_Control && line->li_Control != ctl) {
		            line->li_Control->co_Enabled = 0;
			    Disconnect(line->li_Control, CO_DISABLED);
		        }
		        line->li_Control = ctl;

		        /*
		         * dudiscdev -i prefix -r isfailed -d device -s speed -m mtu <cmds>
		         *
		         * dudiscdev forks and returns the pid of the background task
		         */
			if (LockLine(ctl) > 0) {
			    sprintf(buf, "bin/dudiscdev -i sl -r %d -d %s -s %d -m %d %s",
				ctl->co_DidFail,
				ctl->co_Line->li_DevName,
				ctl->co_Baud,
				ctl->co_Mtu,
				ctl->co_DuCmd
			    );
			    log(ctl, buf);
			    if ((fp = popen(buf, "r")) != NULL) {
				if (fgets(buf, sizeof(buf), fp)) {
				    if ((ctl->co_Pid = strtol(buf, NULL, 0)) != 0) {
					log(ctl, "pid %d", ctl->co_Pid);
				    } else {
					Disconnect(ctl, CO_FAILED);
				    }
				} else {
				    Disconnect(ctl, CO_FAILED);
				}
				pclose(fp);
				ctl->co_State = CO_CONNECTING;
			    } else {
				/*
				 * dudiscdev failed
				 */
				Disconnect(ctl, CO_FAILED);
			    }
			} else {
			    /*
			     * Unable to lock the line
			     */
			    Disconnect(ctl, CO_FAILED);
			}
		    }
		    break;
		case CO_CONNECTING:
		    /*
		     * If connecting check the dudiscdev pid as well as
		     * whether we should disable the control or not.
		     */
		    if (ctl->co_Enabled) {
		        /*
		         * check "/proc/$pid/cmdline"
		         */
		        char buf[256];

			if (getArg0(ctl->co_Pid, buf, 256) == 0) {
			    if (buf[0] == 's' && buf[1] == 'l') {
				ctl->co_Interface = strtol(buf + 2, NULL, 0);
				log(ctl, "connected on ifc sl%d",
				    ctl->co_Interface
				);
				ConnectCommands(ctl);
				ctl->co_State = CO_CONNECTED;
			    }
			} else {
			    Disconnect(ctl, CO_FAILED);
			}
		    } else {
		        Disconnect(ctl, CO_DISABLED);
		    }
		    break;
		case CO_CONNECTED:
		    /*
		     * If connected check the dudiscdev pid as well as
		     * whether we should disable the control or not.
		     */
		    if (ctl->co_Enabled) {
		        /*
		         * check if process still exists.
		         */
			if (kill(ctl->co_Pid, 0) == 0) {
			    ;
			} else {
			    Disconnect(ctl, CO_DISABLED);
			}
		    } else {
		        Disconnect(ctl, CO_DISABLED);
		    }
		    break;
		case CO_FAILED:
		    /*
		     * If failed check the retry countdown and move back
		     * to CO_DISABLED.
		     */
		    if (--ctl->co_FailCounter <= 0) {
		        ctl->co_State = CO_DISABLED;
		        ctl->co_FailCounter = 0;
		    }
		    break;
		}
	    }
	}
    }
}

void
Disconnect(Control *ctl, int newState)
{
    /*
     * error report verbosity is controlled by co_DidFail
     */

    switch(ctl->co_State) {
    case CO_DISABLED:
    	/*
    	 * unable to run dudiscdev.  After 5 failures force the
    	 * sleep period to be a minimum of 60 seconds.
    	 */

	ctl->co_FailCounter = ctl->co_Sleep;

        if (++ctl->co_DidFail > 5) {
            if (ctl->co_FailCounter < 60)
                ctl->co_FailCounter = 60;
	}
        break;
    case CO_CONNECTING:
    	/*
    	 * dudiscdev was unable to connect
    	 */
        if (++ctl->co_DidFail > 3) {
	    ctl->co_FailCounter = ctl->co_Sleep * 3;
        } else {
	    ctl->co_FailCounter = ctl->co_Sleep;
	}
        break;
    case CO_CONNECTED:
        ctl->co_DidFail = 0;	/* failure after success  */
        ctl->co_FailCounter = ctl->co_Sleep;
        break;
    case CO_FAILED:		/* already failed	  */
        break;
    }
    if (ctl->co_Enabled == 0)
        ctl->co_DidFail = 0;

    /*
     * kill any active pid
     */

    if (ctl->co_Pid) {
        kill(ctl->co_Pid, SIGTERM);
        sleep(1);
        if (kill(ctl->co_Pid, 0) == 0) {
	    sleep(1);
	    kill(ctl->co_Pid, SIGKILL);
	    sleep(1);
	}
        ctl->co_Pid = 0;
    }

    /*
     * Disconnect from line
     */

    if (ctl->co_State == CO_CONNECTED) {
	DisconnectCommands(ctl);
    }

    UnLockLine(ctl);

    if (ctl->co_Line->li_Control == ctl) {
        ctl->co_Line->li_Control = NULL;
    }

    ctl->co_State = newState;
}

void
ConnectCommands(Control *ctl)
{
    if (ctl->co_IfAddr) {
	system_fmt(ctl, "ifconfig sl%d %s %s%s%s%s%s%s", 
	    ctl->co_Interface,
	    ctl->co_IfAddr,
	    ((ctl->co_IfBroadCast) ? " broadcast " : ""),
	    ((ctl->co_IfBroadCast) ? ctl->co_IfBroadCast : ""),
	    ((ctl->co_IfPP) ? " pointopoint " : ""),
	    ((ctl->co_IfPP) ? ctl->co_IfPP : ""),
	    ((ctl->co_IfNetMask) ? " netmask " : ""),
	    ((ctl->co_IfNetMask) ? ctl->co_IfNetMask : "")
	);
	setEnv("broadcast", ctl->co_IfBroadCast);
	setEnv("netmask", ctl->co_IfNetMask);
	setEnv("pointtopoint", ctl->co_IfPP);
	setEnv("pointopoint", ctl->co_IfPP);
	setEnv("address", ctl->co_IfAddr);
	setEnv("interface", "sl%d", ctl->co_Interface);
	setEnv("line", ctl->co_Line->li_DevName);

	system_fmt(ctl, "ifconfig sl%d up", 
	    ctl->co_Interface
	);
	if (ctl->co_Command)
	    system_fmt(ctl, ctl->co_Command);
    }
}

void
DisconnectCommands(Control *ctl)
{
    system_fmt(ctl, "ifconfig sl%d down", 
        ctl->co_Interface
    );
}

void
system_fmt(Control *co, const char *ctl, ...)
{
    char buf[1024];
    va_list va;
    int pid;

    va_start(va, ctl);
    vsprintf(buf, ctl, va);
    va_end(va);
    log(co, buf);

    if ((pid = fork()) == 0) {
        execl("/bin/sh", "/bin/sh", "-c", buf, NULL, NULL);
        exit(0);
    } else if (pid > 0) {
        wait4(pid, NULL, 0, NULL);
    }
}

void
log(Control *co, const char *ctl, ...)
{
    va_list va;
    time_t t = time(NULL);
    struct tm *tp = localtime(&t);
    char buf[64];

    strftime(buf, sizeof(buf), "%a %d-%b %H:%M:%S", tp);

    printf("%s %s: ", buf, ((co) ? co->co_Name : ""));
    va_start(va, ctl);
    vprintf(ctl, va);
    va_end(va);
    printf("\n");
    fflush(stdout);
}

void
LoadConfigFile(const char *fileName)
{
    FILE *fi;
    char buf[1024];
    Control ctl;

    bzero(&ctl, sizeof(ctl));

    if ((fi = fopen(fileName, "r")) != NULL) {
        flock(fileno(fi), LOCK_EX);

        while (fgets(buf, sizeof(buf), fi) != NULL) {
            if (buf[0] == '\n' || buf[0] == '#')
                continue;
            if (strncmp(buf, "end", 3) == 0)
	        ReconcileControl(&ctl);
            if (strncmp(buf, "name", 4) == 0) {
	        ReconcileControl(&ctl);
	        ctl.co_Name = extractField(buf + 4, NULL);
	    }
            if (strncmp(buf, "enabled", 7) == 0) {
                ctl.co_Enabled = 1;
	    }
            if (strncmp(buf, "disable", 7) == 0) {
                ctl.co_Enabled = 0;
	    }
            if (strncmp(buf, "baud", 4) == 0) {
                ctl.co_Baud = strtol(buf + 4, NULL, 0);
	    }
            if (strncmp(buf, "mtu", 3) == 0) {
                ctl.co_Mtu = strtol(buf + 3, NULL, 0);
	    }
            if (strncmp(buf, "sleep", 5) == 0) {
                ctl.co_Sleep = strtol(buf + 5, NULL, 0);
	    }
            if (strncmp(buf, "ducmd", 5) == 0) {
		freeStrPtr(&ctl.co_DuCmd);
	        ctl.co_DuCmd = extractLine(buf + 5, "");
	    }
            if (strncmp(buf, "command", 7) == 0) {
                char *cmd;

                if ((cmd = extractLine(buf + 7, NULL))) {
                    if (ctl.co_Command) {
                        ctl.co_Command = realloc(ctl.co_Command, strlen(ctl.co_Command) + strlen(cmd) + 2);
                        strcat(ctl.co_Command, ";");
                        strcat(ctl.co_Command, cmd);
                        free(cmd);
                    } else {
                        ctl.co_Command = cmd;
                    }
		}
	    }
            if (strncmp(buf, "address", 7) == 0) {
		freeStrPtr(&ctl.co_IfAddr);
	        ctl.co_IfAddr = extractField(buf + 7, NULL);
	    }
            if (strncmp(buf, "broadcast", 9) == 0) {
		freeStrPtr(&ctl.co_IfBroadCast);
	        ctl.co_IfBroadCast = extractField(buf + 9, NULL);
	    }
            if (strncmp(buf, "pointtopoint", 12) == 0) {
		freeStrPtr(&ctl.co_IfPP);
	        ctl.co_IfPP = extractField(buf + 12, NULL);
	    }
            if (strncmp(buf, "netmask", 7) == 0) {
		freeStrPtr(&ctl.co_IfNetMask);
	        ctl.co_IfNetMask = extractField(buf + 7, NULL);
	    }
            if (strncmp(buf, "line", 4) == 0) {
                char *field;

                if ((field = strtok(buf + 4, " \t\n")) != NULL)
                    ctl.co_Line = FindLine(field, 1);
	    }
        }
	ReconcileControl(&ctl);

        flock(fileno(fi), LOCK_UN);
        fclose(fi);
    }
}

void
RunCommandFile(const char *fileName)
{
    FILE *fi;
    char buf[256];

    if ((fi = fopen(fileName, "r")) != NULL) {
        flock(fileno(fi), LOCK_EX);
	remove(fileName);

        while (fgets(buf, sizeof(buf), fi) != NULL) {
            char *p1;
            char *p2;
            char *p3;
            Control *ctl;

            if ((p1 = strtok(buf, " \t\n")) == NULL)
                continue;

	    if (strcmp(p1, "reload") == 0) {
		log(NULL, "RELOAD CONFIG FILE");
		LoadConfigFile(RUNSLIP_CONF);
		continue;
	    }

	    if ((p2 = strtok(NULL, " \t\n")) == NULL) {
	        log(NULL, "COMMAND %s NEEDS A SLIP ID", p1);
                continue;
	    }

	    if ((ctl = FindControl(p2)) == NULL) {
	        Line *line;

	        if ((line = FindLine(p2, 0)) == NULL) {
		    log(NULL, "COMMAND %s: SLIP ID %s NOT FOUND", p1, p2);
		    continue;
		}
		if ((ctl = line->li_Control) == NULL) {
		    log(NULL, "COMMAND %s: SLIP LINE %s NOT ACTIVE", p1, p2);
		    continue;
		}
	    }

	    p3 = strtok(NULL, " \t\n");		/* may be NULL */

            if (strcmp(p1, "enable") == 0) {
                ctl->co_Enabled = 1;
                if (p3 && strcmp(ctl->co_Line->li_DevName, p3) != 0) {
                    if (FindLine(p3, 0)) {
			Disconnect(ctl, CO_DISABLED);
			ctl->co_Line = FindLine(p3, 0);
		    } else {
		        log(ctl, "RESET/device override: device not in config file");
		    }
                }
                log(ctl, "ENABLED");
            }
            if (strcmp(p1, "disable") == 0) {
                ctl->co_Enabled = 0;
                log(ctl, "DISABLED");
            }
            if (strcmp(p1, "reset") == 0) {
                log(ctl, "RESET");
		Disconnect(ctl, CO_DISABLED);
                if (p3 && strcmp(ctl->co_Line->li_DevName, p3) != 0) {
                    if (FindLine(p3, 0)) {
			ctl->co_Line = FindLine(p3, 0);
		    } else {
		        log(ctl, "RESET/device override: device not in config file");
		    }
                }
	    }
        }
        flock(fileno(fi), LOCK_UN);
        fclose(fi);
    }
}

void
ReconcileControl(Control *ctl)
{
    if (ctl->co_Name) {
        Control *cfind = FindControl(ctl->co_Name);
        short modified = 0;	/* modified, requires redial		*/
        short changed = 0;	/* changed, does not require redial	*/
        short isNew = 0;

        if (cfind == NULL) {
            cfind = calloc(sizeof(Control), 1);
            cfind->co_Next = ControlBase;
            cfind->co_Name = myStrDup(ctl->co_Name);
            cfind->co_State = CO_DISABLED;
            ControlBase = cfind;
            modified = 1;
            isNew = 1;
        }
	/*
	 * A reload of the configuration file does not modify the
	 * existing enabled/disabled state of a connection
	 */
        if (isNew) {
	    if (cfind->co_Enabled != ctl->co_Enabled) {
		cfind->co_Enabled = ctl->co_Enabled;
		modified = 1;
	    }
	}
        if (cfind->co_Baud != ctl->co_Baud) {
            cfind->co_Baud = ctl->co_Baud;
            modified = 1;
        }
        if (cfind->co_Mtu != ctl->co_Mtu) {
            cfind->co_Mtu = ctl->co_Mtu;
            modified = 1;
        }
        if (cfind->co_Sleep != ctl->co_Sleep) {
            cfind->co_Sleep = ctl->co_Sleep;
            changed = 1;
        }
        if (replaceString(&cfind->co_Name, &ctl->co_Name))
            modified = 1;
        if (replaceString(&cfind->co_DuCmd, &ctl->co_DuCmd))
            modified = 1;
        if (replaceString(&cfind->co_Command, &ctl->co_Command))
            modified = 1;
        if (replaceString(&cfind->co_IfAddr, &ctl->co_IfAddr))
            modified = 1;
        if (replaceString(&cfind->co_IfBroadCast, &ctl->co_IfBroadCast))
            modified = 1;
        if (replaceString(&cfind->co_IfPP, &ctl->co_IfPP))
            modified = 1;
        if (replaceString(&cfind->co_IfNetMask, &ctl->co_IfNetMask))
            modified = 1;
	if (cfind->co_Line != ctl->co_Line) {
	    /*
	     * line changes do to a configuration file reload do not
	     * apply to already active connections.
	     */
	    if (isNew || cfind->co_Enabled == 0) {
		cfind->co_Line = ctl->co_Line;
		modified = 1;
	    }
	}

        if (modified)
            Disconnect(cfind, CO_DISABLED);

	if (modified || changed) {
	    cfind->co_FailCounter = 0;
	    cfind->co_DidFail = 0;

	    log(cfind, "CONFIG %s line %s Mtu %d Speed %d Enabled %d",
	        cfind->co_Name,
	        cfind->co_Line->li_DevName,
	        cfind->co_Mtu,
	        cfind->co_Baud,
	        cfind->co_Enabled
	    );
	    log(cfind, "    address      %s", (cfind->co_IfAddr) ? cfind->co_IfAddr : "<none>");
	    log(cfind, "    pointtopoint %s", (cfind->co_IfPP) ? cfind->co_IfPP : "<none>");
	    log(cfind, "    netmask      %s", (cfind->co_IfNetMask) ? cfind->co_IfNetMask : "<none>");
	    log(cfind, "    broadcast    %s", (cfind->co_IfBroadCast) ? cfind->co_IfBroadCast : "<none>");
	    if (cfind->co_Command) {
		log(cfind, "    command");
		log(cfind, cfind->co_Command);
	    }
	}
    }
    freeStrPtr(&ctl->co_Name);
    freeStrPtr(&ctl->co_DuCmd);
    freeStrPtr(&ctl->co_Command);
    freeStrPtr(&ctl->co_IfAddr);
    freeStrPtr(&ctl->co_IfBroadCast);
    freeStrPtr(&ctl->co_IfPP);
    freeStrPtr(&ctl->co_IfNetMask);
    bzero(ctl, sizeof(Control));
}

Control *
FindControl(const char *name)
{
    Control *ctl;

    for (ctl = ControlBase; ctl; ctl = ctl->co_Next) {
        if (strcmp(name, ctl->co_Name) == 0)
            break;
    }
    return(ctl);
}

Line *
FindLine(const char *device, int createMe)
{
    Line *line;

    for (line = LineBase; line; line = line->li_Next) {
        if (strcmp(device, line->li_DevName) == 0)
            break;
    }
    if (line == NULL && createMe) {
        line = calloc(sizeof(Line), 1);
        line->li_Next = LineBase;
        line->li_DevName = myStrDup(device);
        LineBase = line;
    }
    return(line);
}

char *
myStrDup(const char *str)
{
    return(strcpy(malloc(strlen(str) + 1), str));
}

void
freeStrPtr(char **pptr)
{
    if (*pptr) {
        free(*pptr);
        *pptr = NULL;
    }
}

int
replaceString(char **p1, char **p2)
{
    int r = 1;

    if (*p1 && *p2) {
        if (strcmp(*p1, *p2) == 0) {
            r = 0;
        } else {
            freeStrPtr(p1);
            *p1 = myStrDup(*p2);
        }
    } else if (*p1 == NULL && *p2 == NULL) {
        r = 0;
    } else if (*p1) {
	freeStrPtr(p1);
    } else if (*p2) {
	*p1 = myStrDup(*p2);
    }
    return(r);
}

char *
extractField(const char *ptr, const char *def)
{
    int i;
    char *r;

    while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
        ++ptr;
    for (i = 0; ptr[i] && ptr[i] != ' ' && ptr[i] != '\t' && ptr[i] != '\n'; ++i)
        ;
    if (i == 0) {
        if (def) {
            r = myStrDup(def);
        } else {
            r = NULL;
        }
    } else {
        r = malloc(i + 1);
        memcpy(r, ptr, i);
        r[i] = 0;
    }
    return(r);
}

char *
extractLine(const char *ptr, const char *def)
{
    int i;
    char *r;

    while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
        ++ptr;
    for (i = 0; ptr[i] && ptr[i] != '\n'; ++i)
        ;
    while (i > 0 && (ptr[i-1] == ' ' || ptr[i-1] == '\t'))
        --i;
    if (i == 0) {
        if (def) {
            r = myStrDup(def);
        } else {
            r = NULL;
        }
    } else {
        r = malloc(i + 1);
        memcpy(r, ptr, i);
        r[i] = 0;
    }
    return(r);
}

int 
getArg0(int pid, char *buf, int bufMax)
{
    FILE *fi;
    int r = -1;
    char tmp[256];

    buf[0] = 0;

    sprintf(tmp, "/proc/%d/cmdline", pid);

    if ((fi = fopen(tmp, "r")) != NULL) {
        int i;

        r = fread(tmp, 1, sizeof(tmp) - 1, fi);
        if (r >= 0) {
	    tmp[r] = 0;
	    strcpy(buf, tmp);
	    r = 0;
	}
        fclose(fi);
    }
    return(r);
}

void
setEnv(const char *envName, const char *ctl, ...)
{
    va_list va;
    char buf[256];

    if (ctl) {
	va_start(va, ctl);
	vsprintf(buf, ctl, va);
	va_end(va);
	setenv(envName, buf, 1);
    } else {
        unsetenv(envName);
    }
}

/*
 * UUCP style locking, submitted by Thomas Roettgers tom@bangor.franken.de
 * and cleanred up by Matt Dillon.
 */

/*
 * ReadLock() - read uucp lock.  Returns the locking pid on success,
 *              -errno on error, and -1 if the lockfile is nominally
 *		empty.
 */

int 
ReadLock(const char *lockFile)
{
    int fd;
    int n = -1;
    char p[21];

    if ((fd = open(lockFile, O_RDONLY)) >= 0) {
	n = read(fd, p, sizeof(p) - 1);
	close(fd);
    }
    if (n >= 0) {
        p[n] = 0;
        if ((n = strtol(p, NULL, 10)) == 0)
            n = -1;
    } else {
        n = -errno;
    }
    return(n);
}

/*
 * LockLine() - lock a tty device
 *
 * Establish a lock file containing our pid, if possible.
 */

int
LockLine(Control *ctl)
{
    int fd;
    int r = 0;
    int retries = 4;
    char lname[1024];

    /*
     * construct lock file path
     */

    {
        char *ptr;
	char *devName = ctl->co_Line->li_DevName;

        if ((ptr = strrchr(devName, '/')) == NULL)
            ptr = devName - 1;
	sprintf(lname, LOCK_FILENAME, ptr + 1);
    }

    /*
     * on loop exit:
     *	r < 0   failure
     *  r == 0  create new lock file
     *  r > 0	success
     */

    while ((fd = open(lname, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) {
	int  linePid;
	struct stat st;

	/*
	 * Determine if lock directory exists.  If it does not, this
	 * system does not implement lock files and we simply return
	 * success.
	 */

	if (stat(LOCK_DIR, &st) != 0) {
	    r = 1;
	    log(ctl, "Warning, dir %s does not exist, no lockfile created", LOCK_DIR);
	    break;
	}

	/*
	 * sleep a second in case of retries
	 */

    	sleep(1);

	linePid = ReadLock(lname);

	/*
	 * lockfile went away
	 */

	if (linePid == -ENOENT)
	    continue;

	/*
	 * lockfile is empty or does not contain
	 * a valid pid.  We might have caught a
	 * lock in the midst of being made or we
	 * might be dealing with a screwed up lock
	 * file.  Try N times over N seconds then 
	 * unlink the file.
	 */

	if (linePid < 0) {
	    if (--retries == 0) {
		log(ctl, "Deleting corrupt lockfile %s", lname);
		if (unlink(lname) != 0) {
		    log(ctl, "unlink failed (%s)", strerror(errno));
		    r = -1;
		    break;
		}
	    }
	    continue;
	}

	/*
	 * we are locking the line.. that is, we have already
	 * locked the line (???)
	 */

	if (linePid == getpid()) {
	    r = 1;
	    break;
	}

	/*
	 * If the lock is stale, destroy it.  There is a
	 * timing window here!
	 */

	if ((kill(linePid, 0) < 0) && errno == ESRCH) {
	    unlink(lname);
	    continue;
	}

	/*
	 * The lockfile is valid.  i.e. failure
	 */

 	log(ctl, "Lockfile %s currently in use by pid %d\n", lname, linePid);
	r = -1;
	break;
    }

    /*
     * If we got the file, write the lock info and return success.
     */

    if (r == 0) {
	char pidc[20];

	sprintf(pidc, "%10d\n", getpid());
	if (write(fd, pidc, strlen(pidc) + 1) < 0) {
	    log(ctl, "FATAL: Cannot write to Lockfile %s (%s)", lname, strerror(errno));
	    unlink(lname);
	    r = -1;
	} else {
	    r = 1;
	}
    }

    if (fd >= 0)
	close(fd);  

    return(r);
}

/*
 * Remove the lockfile we had previously established.
 */

void
UnLockLine(Control *ctl)
{
    char lname[1024];
    int linePid;

    /*
     * Only do this if the line is associated with this control
     */

    if (ctl->co_Line->li_Control == ctl) {
	/*
	 * construct lock path
	 */
	
	{
	    char *ptr;
	    char *devName = ctl->co_Line->li_DevName;

	    if ((ptr = strrchr(devName, '/')) == NULL)
		ptr = devName - 1;
	    sprintf(lname, LOCK_FILENAME, ptr + 1);
	}

	/*
	 * If we own the lock, unlink it to kill the lock
	 */
	
	linePid = ReadLock(lname);

	if (linePid == getpid())
	    unlink(lname);
    }
}

