/* Miscellaneous routines.
 *
 * IRC Services is copyright (c) 1996-2002 Andrew Church.
 *     E-mail: <achurch@achurch.org>
 * Parts written by Andrew Kempe and others.
 * This program is free but copyrighted software; see the file COPYING for
 * details.
 */

#include "services.h"
#include <fcntl.h>

/*************************************************************************/

/* Table used by irc_tolower().  This table follows the RFC 1459
 * requirement that [ \ ] and { | } be considered equivalent.  Protocols
 * which do not follow this requirement should modify the table
 * accordingly.
 */

unsigned char irc_lowertable[256] = {
    0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
    0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
    0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
    0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
    0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
    0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
    0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
    0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,

    0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
    0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
    0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
    0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x5E,0x5F,
    0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
    0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
    0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
    0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,

    0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
    0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
    0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
    0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
    0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,
    0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
    0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,
    0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,

    0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,
    0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
    0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,
    0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
    0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,
    0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
    0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,
    0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
};


/*************************************************************************/

/* irc_tolower:  Like toupper/tolower, but for nicknames and channel names;
 *               the RFC requires that '[' and '{', '\' and '|', ']' and '}'
 *               be pairwise equivalent in such names.  Declared inline for
 *               irc_str[n]icmp()'s benefit.
 */

inline unsigned char irc_tolower(char c)
{
    return irc_lowertable[(uint8)c];
}

/*************************************************************************/

/* irc_str[n]icmp:  Like str[n]icmp, but for nicknames and channel names. */

int irc_stricmp(const char *s1, const char *s2)
{
    register char c1, c2;

    while ((c1 = (char)irc_tolower(*s1)) == (c2 = (char)irc_tolower(*s2))) {
	if (c1 == 0)
	    return 0;
	s1++;
	s2++;
    }
    return c1<c2 ? -1 : 1;
}

int irc_strnicmp(const char *s1, const char *s2, int max)
{
    register char c1, c2;

    if (max <= 0)
	return 0;
    while ((c1 = (char)irc_tolower(*s1)) == (c2 = (char)irc_tolower(*s2))) {
	if (c1 == 0 || --max <= 0)
	    return 0;
	s1++;
	s2++;
    }
    return c1<c2 ? -1 : 1;
}

/*************************************************************************/

/* strscpy:  Copy at most len-1 characters from a string to a buffer, and
 *           add a null terminator after the last character copied.
 */

char *strscpy(char *d, const char *s, size_t len)
{
    char *d_orig = d;

    if (!len)
	return d;
    while (--len && (*d++ = *s++))
	;
    *d = 0;
    return d_orig;
}

/*************************************************************************/

/* stristr:  Search case-insensitively for string s2 within string s1,
 *           returning the first occurrence of s2 or NULL if s2 was not
 *           found.
 */

char *stristr(char *s1, char *s2)
{
    register char *s = s1, *d = s2;

    while (*s1) {
	if (tolower(*s1) == tolower(*d)) {
	    s1++;
	    d++;
	    if (*d == 0)
		return s;
	} else {
	    s = ++s1;
	    d = s2;
	}
    }
    return NULL;
}

/*************************************************************************/

/* strupper, strlower:  Convert a string to upper or lower case.
 */

char *strupper(char *s)
{
    char *t = s;
    while (*t) {
	*t = toupper(*t);
	t++;
    }
    return s;
}

char *strlower(char *s)
{
    char *t = s;
    while (*t) {
	*t = tolower(*t);
	t++;
    }
    return s;
}

/*************************************************************************/

/* strnrepl:  Replace occurrences of `old' with `new' in string `s'.  Stop
 *            replacing if a replacement would cause the string to exceed
 *            `size' bytes (including the null terminator).  Return the
 *            string.
 */

char *strnrepl(char *s, int32 size, const char *old, const char *new)
{
    char *ptr = s;
    int32 left = strlen(s);
    int32 avail = size - (left+1);
    int32 oldlen = strlen(old);
    int32 newlen = strlen(new);
    int32 diff = newlen - oldlen;

    while (left >= oldlen) {
	if (strncmp(ptr, old, oldlen) != 0) {
	    left--;
	    ptr++;
	    continue;
	}
	if (diff > avail)
	    break;
	if (diff != 0)
	    memmove(ptr+oldlen+diff, ptr+oldlen, left+1);
	strncpy(ptr, new, newlen);
	ptr += newlen;
	left -= oldlen;
    }
    return s;
}

/*************************************************************************/

/* strtok_remaining:  Return the result of strtok(NULL, "") with any
 *                    leading or trailing whitespace discarded.
 */

char *strtok_remaining(void)
{
    char *s = strtok(NULL, ""), *t;
    if (s) {
	while (isspace(*s))
	    s++;
	t = s + strlen(s)-1;
	while (t >= s && isspace(*t))
	    *t-- = 0;
    }
    return s;
}

/*************************************************************************/
/*************************************************************************/

/* merge_args:  Take an argument count and argument vector and merge them
 *              into a single string in which each argument is separated by
 *              a space.
 */

char *merge_args(int argc, char **argv)
{
    int i;
    static char s[4096];
    char *t;

    t = s;
    for (i = 0; i < argc; i++)
	t += snprintf(t, sizeof(s)-(t-s), "%s%s", *argv++, (i<argc-1) ? " " : "");
    return s;
}

/*************************************************************************/
/*************************************************************************/

/* match_wild:  Attempt to match a string to a pattern which might contain
 *              '*' or '?' wildcards.  Return 1 if the string matches the
 *              pattern, 0 if not.
 */

static int do_match_wild(const char *pattern, const char *str, int docase)
{
    char c;
    const char *s;

    /* This WILL eventually terminate: either by *pattern == 0, or by a
     * trailing '*' (or "*???..."). */

    for (;;) {
	switch (c = *pattern++) {
	  case 0:
	    if (!*str)
		return 1;
	    return 0;
	  case '?':
	    if (!*str)
		return 0;
	    str++;
	    break;
	  case '*':
	    while (*pattern == '?') {
		if (!*str)
		    return 0;
		str++;		/* skip a character for each '?' */
		pattern++;
	    }
	    if (!*pattern)
		return 1;	/* trailing '*' matches everything else */
	    s = str;
	    while (*s) {
		if ((docase ? (*s==*pattern) : (tolower(*s)==tolower(*pattern)))
					&& do_match_wild(pattern+1, s+1, docase))
		    return 1;
		s++;
	    }
	    break;
	  default:
	    if (docase ? (*str != c) : (tolower(*str) != tolower(c)))
		return 0;
	    str++;
	    break;
	} /* switch */
    }
    /* not reached */
}


int match_wild(const char *pattern, const char *str)
{
    return do_match_wild(pattern, str, 1);
}

int match_wild_nocase(const char *pattern, const char *str)
{
    return do_match_wild(pattern, str, 0);
}

/*************************************************************************/
/*************************************************************************/

/* Tables used by valid_nick() and valid_chan().  These tables follow the
 * RFC 1459 definitions; protocols with different definitions should modify
 * the table accordingly (bit 0 = valid as a first character, bit 1 = valid
 * as a subsequent character).  valid_{nick,chan}_table[0] should never be
 * modified.
 */

unsigned char valid_nick_table[256] = {
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,2,0,0,  2,2,2,2,2,2,2,2, 2,2,0,0,0,0,0,0,
    0,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,  3,3,3,3,3,3,3,3, 3,3,3,2,0,2,2,0,
    2,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,  3,3,3,3,3,3,3,3, 3,3,3,2,0,2,2,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
};

unsigned char valid_chan_table[256] = {
    0,2,2,2,2,2,2,0, 2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
    0,2,2,3,2,2,3,2, 2,2,2,2,0,2,2,2,  2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
    2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
    2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
    2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
    2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
    2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
    2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
};

/*************************************************************************/

/* valid_nick, valid_chan: Check whether the given string is a valid
 *                         nickname or channel name, ignoring length.
 *                         Returns nonzero if valid, zero if not.
 */

int valid_nick(const unsigned char *str)
{
    if (!(valid_nick_table[*str] & 1))
	return 0;
    while (*++str) {
	if (!(valid_nick_table[*str] & 2))
	    return 0;
    }
    return 1;
}

int valid_chan(const unsigned char *str)
{
    if (!(valid_chan_table[*str] & 1))
	return 0;
    while (*++str) {
	if (!(valid_chan_table[*str] & 2))
	    return 0;
    }
    return 1;
}

/*************************************************************************/

/* Check whether the given string is a valid domain name, according to RFC
 * rules:
 *   - Contains only letters, digits, hyphens, and periods (dots).
 *   - Begins with a letter or digit.
 *   - Has a letter or digit after every dot (except for a trailing dot).
 *   - Is no more than DOMAIN_MAXLEN characters long.
 *   - Has no more than DOMPART_MAXLEN characters between periods.
 *   - Has at least one character and does not end with a dot. (not RFC)
 */

#define DOMAIN_MAXLEN	255
#define DOMPART_MAXLEN	63

int valid_domain(const char *str)
{
    const char *s;
    int i;
    static const char valid_domain_chars[] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.";

    if (!*str)
	return 0;
    if (str[strspn(str,valid_domain_chars)] != 0)
	return 0;
    s = str;
    while (s-str < DOMAIN_MAXLEN && *s) {
	if (*s == '-' || *s == '.')
	    return 0;
	i = strcspn(s, ".");
	if (i > DOMPART_MAXLEN)
	    return 0;
	s += i;
	if (*s)
	    s++;
    }
    if (s-str > DOMAIN_MAXLEN || *s)
	return 0;
    if (s[-1] == '.')
	return 0;
    return 1;
}

/*************************************************************************/

/* Check whether the given string is a valid E-mail address.  A valid
 * E-mail address:
 *   - Contains none of the following characters:
 *       + control characters (\000-\037)
 *       + space (\040)
 *       + vertical bar ('|') (because some mailers try to pipe with it)
 *       + RFC-822 specials, except [ ] @ .
 *   - Contains exactly one '@', which may not be the first character.
 *   - Contains a valid domain name or an IP address in square brackets
 *     after the '@'.
 *   - Does not contain [ or ] except when used with an IP address as above.
 */

int valid_email(const char *str)
{
    const unsigned char *s;
    const char *atmark;

    for (s = (const unsigned char *)str; *s; s++) {
	if (*s <= '\040')  // 040 == 0x20 == ' '
	    return 0;
	if (strchr("|,:;\\\"()<>", *s))
	    return 0;
    }

    /* Find the @, and abort if there isn't one */
    atmark = strchr(str, '@');
    if (!atmark || atmark == str)
	return 0;
    atmark++;

    /* Don't allow [] in username */
    s = strpbrk(str, "[]");
    if (s && (const char *)s < atmark)
	return 0;

    /* Check for a [1.2.3.4] type of domain */
    if (*atmark == '[') {
	unsigned char ipstr[16];
	const char *bracket = strchr(atmark+1, ']');
	int len = bracket - (atmark+1);
	/* Valid IP addresses have no more than 15 characters */
	if (len <= 15 && !bracket[1]) {
	    strncpy(ipstr, atmark+1, len);
	    ipstr[len] = 0;
	    /* Use pack_ip() to see if it's a valid address */
	    if (pack_ip(ipstr))
		return 1;
	}
    }

    /* Don't allow [] in domains except for the above case */
    if (strpbrk(atmark, "[]"))
	return 0;

    /* Valid domain names cannot contain '@' so we just check valid_domain().
     * Also prohibit domains without dots. */
    return strchr(atmark, '.') && valid_domain(atmark);
}

/*************************************************************************/

/* Check whether the given string is a valid URL.  A valid URL:
 *   - Contains neither control characters (\000-\037) nor spaces (\040).
 *   - Contains a series of letters followed by "://" followed by a valid
 *     domain name, possibly followed by a : and a numeric port number in
 *     the range 1-65535, followed either by a slash and possibly more text
 *     by nothing.
 */

int valid_url(const char *str)
{
    const unsigned char *s, *colon, *host;
    static const char letters[] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    char domainbuf[DOMAIN_MAXLEN+1];

    for (s = (const unsigned char *)str; *s; s++) {
	if (*s <= '\040')  // 040 == 0x20 == ' '
	    return 0;
    }
    s = (const unsigned char *)strstr(str, "://");
    if (!s)
	return 0;  /* No "://" */
    if (strspn(str, letters) != s - (const unsigned char *)str)
	return 0;  /* Protocol has non-alphabetic characters */
    host = s+3;
    colon = (const unsigned char *)strchr(host, ':');
    /* s will eventually point to the expected end of the host string */
    s = host + strcspn(host, "/");
    /* Make sure the port is valid if present. */
    if (colon && colon < s) {
	const unsigned char *tmp;
	int port = strtol(colon+1, (char **)&tmp, 10);
	if (port < 1 || port > 65535 || tmp != s)
	    return 0;  /* Invalid port number or non-numeric characters */
	s = colon;
    }
    /* The string from host through s-1 must be a valid domain name.
     * Check length (must be >=1, <=DOMAIN_MAXLEN), then copy into
     * temporary buffer and check.  Also discard domain names without
     * dots in them. */
    if (s-host < 1 || s-host > DOMAIN_MAXLEN)
	return 0;
    memcpy(domainbuf, host, s-host);
    domainbuf[s-host] = 0;
    return strchr(domainbuf, '.') && valid_domain(domainbuf);
}

/*************************************************************************/
/*************************************************************************/

/* Process a string containing a number/range list in the form
 * "n1[-n2][,n3[-n4]]...", calling a caller-specified routine for each
 * number in the list.  If the callback returns -1, stop immediately.
 * Returns the sum of all nonnegative return values from the callback.
 * If `count_ret' is non-NULL, *count_ret will be set to the total number
 * of times the callback was called.
 *
 * The number list will never contain duplicates and will always be sorted
 * ascendingly. This means the callback routines don't have to worry about
 * being called twice for the same index. -TheShadow
 * Also, the only values accepted are 0-65536 (inclusive), to avoid someone
 * giving us 0-2^31 and causing freezes or out-of-memory.  Numbers outside
 * this range will be ignored.
 *
 * The callback should be of type range_callback_t, which is defined as:
 *	int (*range_callback_t)(User *u, int num, va_list args)
 */

int process_numlist(const char *numstr, int *count_ret,
		range_callback_t callback, User *u, ...)
{
    int n1, n2, min, max, i;
    int retval = 0;
    int numcount = 0;
    va_list args;
    static char numflag[65536/8+1];  /* 1 bit per index 0-65536 inclusive */

    memset(numflag, 0, sizeof(numflag));
    min = 65536;
    max = 0;
    va_start(args, u);

    /* This algorithm ignores invalid characters, ignores a dash
     * when it precedes a comma, and ignores everything from the
     * end of a valid number or range to the next comma or null.
     */
    while (*numstr) {
        n1 = n2 = strtol(numstr, (char **)&numstr, 10);
        numstr += strcspn(numstr, "0123456789,-");
        if (*numstr == '-') {
            numstr++;
            numstr += strcspn(numstr, "0123456789,");
            if (isdigit(*numstr)) {
                n2 = strtol(numstr, (char **)&numstr, 10);
                numstr += strcspn(numstr, "0123456789,-");
            }
        }
	if (n1 < 0)
	    n1 = 0;
	if (n2 > 65536)
	    n2 = 65536;
	if (n1 < min)
	    min = n1;
	if (n2 > max)
	    max = n2;
        while (n1 <= n2) {
	    if ((n1&7) == 0 && n1+7 <= n2) {
		/* Set a whole byte at once */
		numflag[n1>>3] = 0xFF;
		n1 += 8;
	    } else {
		/* Set just a single bit */
		numflag[n1>>3] |= 1 << (n1&7);
		n1++;
	    }
	}
        numstr += strcspn(numstr, ",");
        if (*numstr)
            numstr++;
    }

    /* Now call the callback routine for each index. */
    numcount = 0;
    for (i = min; i <= max; i++) {
	int res;
	if (!(numflag[i>>3] & (1 << (i&7))))
	    continue;
	numcount++;
	res = callback(u, i, args);
	if (debug)
	    log("debug: process_numlist: tried to do %d; result = %d", i, res);
	if (res < 0)
	    break;
	retval += res;
    }

    va_end(args);
    if (count_ret)
        *count_ret = numcount;
    return retval;
}

/*************************************************************************/
/*************************************************************************/

/* time_msec:  Return the current time to millisecond resolution. */

uint32 time_msec(void)
{
#if HAVE_GETTIMEOFDAY
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec*1000 + tv.tv_usec/1000;
#else
    return time(NULL) * 1000;
#endif
}

/*************************************************************************/

/* strtotime:  Convert a string into a time_t.  Essentially the same as
 *             strtol(), but assumes base 10 and returns a time_t.
 */

time_t strtotime(const char *str, char **endptr)
{
    time_t t = 0;

    while (*str >= '0' && *str <= '9') {
	if (t > MAX_TIME_T/10 || MAX_TIME_T - t*10 < *str-'0') {
	    t = MAX_TIME_T;
	    errno = ERANGE;
	} else {
	    t = t*10 + *str-'0';
	}
	str++;
    }
    if (endptr)
	*endptr = (char *)str;
    return t;
}

/*************************************************************************/

/* dotime:  Return the number of seconds corresponding to the given time
 *          string.  If the given string does not represent a valid time,
 *          return -1.
 *
 *          A time string is either a plain integer (representing a number
 *          of seconds), an integer followed by one of these characters:
 *          "s" (seconds), "m" (minutes), "h" (hours), or "d" (days), or a
 *          sequence of such integer-character pairs (without separators,
 *          e.g. "1h30m").
 */

int dotime(const char *s)
{
    int amount;

    amount = strtol(s, (char **)&s, 10);
    if (*s) {
	char c = *s++;
	int rest = dotime(s);
	if (rest < 0)
	    return -1;
	switch (c) {
	    case 's': return rest + amount;
	    case 'm': return rest + amount*60;
	    case 'h': return rest + amount*3600;
	    case 'd': return rest + amount*86400;
	    default : return -1;
	}
    } else {
	return amount;
    }
}

/*************************************************************************/
/*************************************************************************/

/* Translate an IPv4 dotted-quad address to a 4-byte string.  Return NULL
 * if the given string is not in dotted-quad format or `ipaddr' is NULL.
 */

uint8 *pack_ip(const char *ipaddr)
{
    static uint8 ipbuf[4];
    const char *s, *s2;
    int i;
    long tmp;

    if (!ipaddr)
	return NULL;
    s = ipaddr;
    for (i = 0; i < 4; i++) {
	if (i > 0 && *s++ != '.')
	    return NULL;
	if (isspace(*s))  /* because strtol() will skip whitespace */
	    return NULL;
	tmp = strtol(s, (char **)&s2, 10);
	if (s2 == s || tmp < 0 || tmp > 255)
	    return NULL;
	ipbuf[i] = (uint8)tmp;
	s = s2;
    }
    if (*s)
	return NULL;
    return ipbuf;
}

/*************************************************************************/

/* Translate a 4-byte character string to a dotted-quad IPv4 address.
 * Always succeeds and always returns a string <= 15 characters in length,
 * unless `ip' is NULL, in which case the function returns NULL.
 */

char *unpack_ip(const uint8 *ip)
{
    static char ipbuf[17];  /* 1 byte extra to catch overflow (paranoia) */

    if (!ip)
	return NULL;
    snprintf(ipbuf, sizeof(ipbuf), "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
    if (strlen(ipbuf) > 15)
	fatal("BUG: unpack_ip(): result too long (%s)", ipbuf);
    return ipbuf;
}

/*************************************************************************/
/*************************************************************************/

/* Lock the data directory if possible; return nonzero on success, zero on
 * failure (data directory already locked or cannot create lock file).
 * On failure, errno will be EEXIST if the directory was already locked or
 * a value other than EEXIST if an error occurred creating the lock file.
 *
 * This does not attempt to correct for NFS brokenness w.r.t. O_EXCL and
 * will contain a race condition when used on an NFS filesystem (or any
 * other filesystem which does not support O_EXCL properly).
 */

int lock_data(void)
{
    int fd;

    errno = 0;
    fd = open(LockFilename, O_WRONLY | O_CREAT | O_EXCL, 0);
    if (fd > 0) {
	close(fd);
	return 1;
    }
    return 0;
}

/*************************************************************************/

/* Unlock the data directory.  Assumes we locked it in the first place.
 * Returns a positive nonzero value on success, zero on failure (unable to
 * remove the lock file), or -1 if the lock file didn't exist in the first
 * place (possibly because it was removed by another (misbehaving) program).
 */

int unlock_data(void)
{
    errno = 0;
    if (unlink(LockFilename) == 0)
	return 1;
    if (errno == ENOENT)
	return -1;
    return 0;
}

/*************************************************************************/
