src/history.c

/* [<][>]
[^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following functions.
  1. hisSetkey
  2. hisOpen
  3. hisGetDbz
  4. hisGet
  5. hisAddDbz
  6. hisAdd
  7. hisPruneDbz
  8. hisPrune

/* $Id: history.c,v 1.2 1999/09/16 22:17:44 proff Exp $
 * $Copyright$
 */

#include "nglobal.h"

#include "dbz.h"

#include "ipc.h"
#include "lock.h"

#include "history.h"

static int his_fd = -1;

#define HEADER_LEN 5

static void hisSetkey (char *s, datum * key)
/* [<][>][^][v][top][bottom][index][help] */
{
        int len;
        len = strlen (s);
        key->dptr = s;
        key->dsize = len;
        return;
}

static bool hisOpen ()
/* [<][>][^][v][top][bottom][index][help] */
{

        struct stat st;
        char dbdir[MAX_PATH];
        char dbpag[MAX_PATH];
        bool rebuild = FALSE;
        if (his_fd != -1)
                return TRUE;
        his_fd = open (con->historyFile, O_RDWR);
        if (chdir(con->cacheDir)<0)
        {
                loge (("couldn't chdir('%s')", con->cacheDir));
                return FALSE;
        }
        if (his_fd == -1)
        {
                loge (("missing history file '%s'", con->historyFile));
                rebuild = TRUE;
        }
        sprintf (dbdir, "%.127s.dir", con->historyFile);
        if (stat (dbdir, &st) == -1)
        {
                loge (("missing history file '%s'", dbdir));
                rebuild = TRUE;
        }
        sprintf (dbpag, "%.127s.pag", con->historyFile);
        if (stat (dbpag, &st) == -1)
        {
                loge (("missing history file '%s'", dbpag));
                rebuild = TRUE;
        }
        if (rebuild)
        {
                unlink (con->historyFile);
                unlink (dbdir);
                unlink (dbpag);
                his_fd = open (con->historyFile, O_RDWR | O_CREAT | O_EXCL, 0664);
                if (his_fd == -1)
                {
                        loge (("couldn't create history file '%s'", con->historyFile));
                        return FALSE;
                }
                dbzincore (1);
                if (dbzfresh (con->historyFile, 700001, '\n', '0', 0) == -1)
                {
                        loge (("couldn't dbzfresh() '%s'", con->historyFile));
                        return FALSE;
                }
                log (("created history file '%s' (ignore earlier errors if this is a first-run)", con->historyFile));
        } else
                dbminit (con->historyFile);
        dbzwritethrough (1);
        return TRUE;
}

EXPORT char *hisGetDbz (char *msgid)
/* [<][>][^][v][top][bottom][index][help] */
{
#define HIS_BLK_SIZE 512
        static char *buf;
        static char *ret;
        long offset;
        int cc;
        int len;
        datum key;
        datum val;
        char *p, *p2;

        if (!hisOpen ())
                return FALSE;
        hisSetkey (msgid, &key);
        val = dbzfetch (key);
        if (!val.dptr)
                return NULL;
        Stats->historyFetches++;
        memcpy ((char *) &offset, val.dptr, sizeof offset);
        offset -= HEADER_LEN;
        if (lseek (his_fd, offset, SEEK_SET) != offset)
        {
                loge (("couldn't lseek to %ld in file '%s'", offset, con->historyFile));
                return NULL;
        }
        if (!buf)
                buf = (char *) Smalloc (HIS_BLK_SIZE + 1);
        if ((cc = read (his_fd, buf, HIS_BLK_SIZE)) <= 0)
        {
                loge (("couldn't read history data key %s from %s", msgid, con->historyFile));
                return NULL;
        }
        buf[cc] = '\0';
        if (!(p = strchr (buf, ' ')) || (len = strToi (buf)) < HEADER_LEN)
        {
                logw (("malformed history data returned from key %s", msgid));
                return NULL;
        }
        if (len > cc)
        {
                int n;
                buf = Srealloc (buf, len);
                if ((n = read (his_fd, buf + cc, len - cc)) <= 0)
                {
                        loge (("couldn't read history data key %s from %s", msgid, con->historyFile));
                        return NULL;
                }
                buf[cc + n] = '\0';
        }
        p = strchr (buf, '\n');
        if (!p || !(p2 = strchr (p + 1, '\n')))
        {
                logw (("malformed data '%.128s' in '%.128s'",  buf, con->historyFile));
                return NULL;
        }
        *p2 = '\0';
        if (ret)
                free (ret);
        ret = Sstrdup (p + 1);
        Stats->msgidFromCache++;
        Stats->msgidFromCacheBytes+=len;
        return ret;
}

EXPORT char *hisGet (char *msgid)
/* [<][>][^][v][top][bottom][index][help] */
{
        return (Task->ti_state !=nc_oneshot) ? hisGetIPC (msgid) : hisGetDbz (msgid);
}

EXPORT bool hisAddDbz (char *msgid, char *path)
/* [<][>][^][v][top][bottom][index][help] */
{
        datum keydat, val;
        long offset;
        int len;
        char *buf;

        if (!hisOpen ())
                return FALSE;
        hisSetkey (msgid, &keydat);
        len = HEADER_LEN + strlen (keydat.dptr) + 1 + strlen (path) + 1;
        if (len >= 10000)
        {
                logw (("record length for <%s> %.256s %d>=10000", msgid, path, len));
                return FALSE;
        }
        buf = (char *) Smalloc (len + 1);
        sprintf (buf, "%-*d %.*s\n%.255s\n", HEADER_LEN - 1, len, MAX_MSGID, keydat.dptr, path);
        offset = lseek (his_fd, 0, SEEK_END);
        Stats->historySize = offset;
        if (offset == -1)
        {
                lockun (his_fd);
                close (his_fd);
                his_fd = -1;
                loge (("couldn't lseek '%s' to %ld", con->historyFile, offset));
                free (buf);
                return FALSE;
        }
        if (write (his_fd, buf, len) != len)
        {
                lockun (his_fd);
                close (his_fd);
                his_fd = -1;
                loge (("error during write to '%s'", con->historyFile));
                free (buf);
                return FALSE;
        }
        offset += HEADER_LEN;
        val.dptr = (char *) &offset;
        val.dsize = sizeof offset;
        if (store (keydat, val) == -1)
        {
                logw (("couldn't dbzstore(\"%s\")", msgid));
                free (buf);
                return FALSE;
        }
        free (buf);
        Stats->historyStores++;
        Stats->historyStoresBytes+=len;
        return TRUE;
}

EXPORT bool hisAdd (char *msgid, char *path)
/* [<][>][^][v][top][bottom][index][help] */
{
        return (Task->ti_state !=nc_oneshot)? hisAddIPC (msgid, path) : hisAddDbz (msgid, path);
}

static char *hisPruneDbz (int newsize)
/* [<][>][^][v][top][bottom][index][help] */
{
        FILE *r, *w;
        char buf[10000];
        char buf2[10000];
        int len;
        int offset;
        datum val, key;
        if (chdir (con->cacheDir)==-1)
        {
                loge (("couldn't chdir(\"%s\") for history prune", con->cacheDir));
        }
        if (!(r = fopen (con->historyFile, "r")))
                return con->historyFile;
        if (!(w = fopen (con->historyFile, "r+")))
                return con->historyFile;
        dbzincore (1);
        if (dbzagain (con->historyFile, con->historyFile) < 0)
                return "couldn't dbzagain()";
        fseek (r, -newsize, SEEK_END);
        if (!fgets (buf, sizeof buf, r))
                return "couldn't fgets() after initial fseek()";
        /* get to the start of a new line */
        if (!fgets (buf, sizeof buf, r))
                return "second fgets()";
        /* are we on the key or the record? */
        if (!isdigit (*buf) || sscanf (buf, "%d ", &len) != 1)
        {
                if (!fgets (buf, sizeof buf, r))
                        return "couldn't fgets() initial record";
                if (!isdigit (*buf) || sscanf (buf, "%d ", &len) != 1)
                        return "malformed record in history";
        }
        do
        {
                if (!fgets (buf2, sizeof buf2, r))
                        return "couldn't fgets() record";
                offset = ftell (w) + HEADER_LEN;
                val.dsize = sizeof offset;
                val.dptr = (char *) &offset;
                /* we don't need to have the key in the base file for
                   store() -- unlike fetch() */
                if (fputs (buf, w) == EOF)
                        return "couldn't fputs() key";
                if (fputs (buf2, w) == EOF)
                        return "couldn't fputs() record";
                key.dptr = buf + HEADER_LEN;
                key.dsize = strlen (key.dptr) - 1;
                if (store (key, val) < 0)
                {
                        logw (("return couldn't store() %s", buf));
                        continue;
                }
        }
        while (fgets (buf, sizeof buf, r));
        fflush (w);
        offset = ftell (w);
        if (ferror (r) || fclose (w) != 0)
                return con->historyFile;
        if (ftruncate (his_fd, offset) < 0)
                return "unable to ftruncate() history";
        fclose (r);
        dbzsync ();
        return NULL;
}

EXPORT bool hisPrune ()
/* [<][>][^][v][top][bottom][index][help] */
{
        struct stat st;
        char *msg;

        settaskinfo ("pruning history database");
        log (("pruning history database"));
        
        if (chdir(con->cacheDir)<0 || !hisOpen () || stat (con->historyFile, &st) < 0)
        {
                loge (("fatal error: couldn't open/stat %s", con->historyFile));
                retire_vm_proc (1);
        }
        if (st.st_size < con->hisHighWater)
                return TRUE;
        dbmclose ();

        if (lockex (his_fd) < 0)
                msg = "couldn't lockex()";
        else
        {
                msg = hisPruneDbz (con->hisLowWater);
                lockun (his_fd);
        }
        if (msg)
        {
                loge (("couldn't prune history (%s)", msg));
                retire_vm_proc (1);     /* seriously suffering */
        }
        return TRUE;            /* hisPruneDbz leaves con->historyFile open */
}

/* [<][>][^][v][top][bottom][index][help] */