
/*
 * $Id: store_dir_diskd.c,v 1.91 2007/12/26 23:35:33 hno Exp $
 *
 * DEBUG: section 47    Store Directory Routines
 * AUTHOR: Duane Wessels
 *
 * SQUID Web Proxy Cache          http://www.squid-cache.org/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

#include "squid.h"

#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>

#include "store_diskd.h"

#define DefaultLevelOneDirs     16
#define DefaultLevelTwoDirs     256
#define STORE_META_BDISKDZ 4096

diskd_stats_t diskd_stats;

typedef struct _RebuildState RebuildState;
struct _RebuildState {
    SwapDir *sd;
    int n_read;
    FILE *log;
    int speed;
    int curlvl1;
    int curlvl2;
    struct {
	unsigned int clean:1;
	unsigned int init:1;
    } flags;
    int done;
    int in_dir;
    int fn;
    struct dirent *entry;
    DIR *td;
    char fullpath[SQUID_MAXPATHLEN];
    char fullfilename[SQUID_MAXPATHLEN];
    struct _store_rebuild_data counts;
};

static int n_diskd_dirs = 0;
static int *diskd_dir_index = NULL;
MemPool *diskd_state_pool = NULL;
static int diskd_initialised = 0;

static char *storeDiskdDirSwapSubDir(SwapDir *, int subdirn);
static int storeDiskdDirCreateDirectory(const char *path, int);
static int storeDiskdDirVerifyCacheDirs(SwapDir *);
static int storeDiskdDirVerifyDirectory(const char *path);
static void storeDiskdDirCreateSwapSubDirs(SwapDir *);
static char *storeDiskdDirSwapLogFile(SwapDir *, const char *);
static EVH storeDiskdDirRebuildFromDirectory;
static EVH storeDiskdDirRebuildFromSwapLog;
static int storeDiskdDirGetNextFile(RebuildState *, sfileno *, int *size);
static StoreEntry *storeDiskdDirAddDiskRestore(SwapDir * SD, const cache_key * key,
    sfileno file_number,
    squid_file_sz swap_file_sz,
    time_t expires,
    time_t timestamp,
    time_t lastref,
    time_t lastmod,
    u_num32 refcount,
    u_short flags,
    int clean);
static void storeDiskdDirRebuild(SwapDir * sd);
static void storeDiskdDirCloseTmpSwapLog(SwapDir * sd);
static FILE *storeDiskdDirOpenTmpSwapLog(SwapDir *, int *, int *);
static STLOGOPEN storeDiskdDirOpenSwapLog;
static STINIT storeDiskdDirInit;
static STCHECKCONFIG storeDiskdCheckConfig;
static STFREE storeDiskdDirFree;
static STLOGCLEANSTART storeDiskdDirWriteCleanStart;
static STLOGCLEANNEXTENTRY storeDiskdDirCleanLogNextEntry;
static STLOGCLEANWRITE storeDiskdDirWriteCleanEntry;
static STLOGCLEANDONE storeDiskdDirWriteCleanDone;
static STLOGCLOSE storeDiskdDirCloseSwapLog;
static STLOGWRITE storeDiskdDirSwapLog;
static STNEWFS storeDiskdDirNewfs;
static STDUMP storeDiskdDirDump;
static STMAINTAINFS storeDiskdDirMaintain;
static STCHECKOBJ storeDiskdDirCheckObj;
static STCHECKLOADAV storeDiskdDirCheckLoadAv;
static STREFOBJ storeDiskdDirRefObj;
static STUNREFOBJ storeDiskdDirUnrefObj;
static QS rev_int_sort;
static int storeDiskdDirClean(int swap_index);
static EVH storeDiskdDirCleanEvent;
static int storeDiskdDirIs(SwapDir * sd);
static int storeDiskdFilenoBelongsHere(int fn, int F0, int F1, int F2);
static int storeDiskdCleanupDoubleCheck(SwapDir *, StoreEntry *);
static void storeDiskdDirStats(SwapDir *, StoreEntry *);
static void storeDiskdDirInitBitmap(SwapDir *);
static int storeDiskdDirValidFileno(SwapDir *, sfileno, int);
static void storeDiskdStats(StoreEntry * sentry);
static void storeDiskdDirSync(SwapDir *);

/* The only externally visible interface */
STSETUP storeFsSetup_diskd;

/*
 * These functions were ripped straight out of the heart of store_dir.c.
 * They assume that the given filenum is on a diskd partiton, which may or
 * may not be true.. 
 * XXX this evilness should be tidied up at a later date!
 */

static int
storeDiskdDirMapBitTest(SwapDir * SD, sfileno filn)
{
    diskdinfo_t *diskdinfo;
    diskdinfo = SD->fsdata;
    return file_map_bit_test(diskdinfo->map, filn);
}

static void
storeDiskdDirMapBitSet(SwapDir * SD, sfileno filn)
{
    diskdinfo_t *diskdinfo;
    diskdinfo = SD->fsdata;
    file_map_bit_set(diskdinfo->map, filn);
}

void
storeDiskdDirMapBitReset(SwapDir * SD, sfileno filn)
{
    diskdinfo_t *diskdinfo;
    diskdinfo = SD->fsdata;
    /* 
     * We have to test the bit before calling file_map_bit_reset.
     * file_map_bit_reset doesn't do bounds checking.  It assumes
     * filn is a valid file number, but it might not be because
     * the map is dynamic in size.  Also clearing an already clear
     * bit puts the map counter of-of-whack.
     */
    if (file_map_bit_test(diskdinfo->map, filn))
	file_map_bit_reset(diskdinfo->map, filn);
}

int
storeDiskdDirMapBitAllocate(SwapDir * SD)
{
    diskdinfo_t *diskdinfo = SD->fsdata;
    int fn;
    fn = file_map_allocate(diskdinfo->map, diskdinfo->suggest);
    file_map_bit_set(diskdinfo->map, fn);
    diskdinfo->suggest = fn + 1;
    return fn;
}

/*
 * Initialise the diskd bitmap
 *
 * If there already is a bitmap, and the numobjects is larger than currently
 * configured, we allocate a new bitmap and 'grow' the old one into it.
 */
static void
storeDiskdDirInitBitmap(SwapDir * sd)
{
    diskdinfo_t *diskdinfo = sd->fsdata;

    if (diskdinfo->map == NULL) {
	/* First time */
	diskdinfo->map = file_map_create();
    } else if (diskdinfo->map->max_n_files) {
	/* it grew, need to expand */
	/* XXX We don't need it anymore .. */
    }
    /* else it shrunk, and we leave the old one in place */
}

static char *
storeDiskdDirSwapSubDir(SwapDir * sd, int subdirn)
{
    diskdinfo_t *diskdinfo = sd->fsdata;

    LOCAL_ARRAY(char, fullfilename, SQUID_MAXPATHLEN);
    assert(0 <= subdirn && subdirn < diskdinfo->l1);
    snprintf(fullfilename, SQUID_MAXPATHLEN, "%s/%02X", sd->path, subdirn);
    return fullfilename;
}

static int
storeDiskdDirCreateDirectory(const char *path, int should_exist)
{
    int created = 0;
    struct stat st;
    getCurrentTime();
    if (0 == stat(path, &st)) {
	if (S_ISDIR(st.st_mode)) {
	    debug(20, should_exist ? 3 : 1) ("%s exists\n", path);
	} else {
	    fatalf("Swap directory %s is not a directory.", path);
	}
    } else if (0 == mkdir(path, 0755)) {
	debug(20, should_exist ? 1 : 3) ("%s created\n", path);
	created = 1;
    } else {
	fatalf("Failed to make swap directory %s: %s",
	    path, xstrerror());
    }
    return created;
}

static int
storeDiskdDirVerifyDirectory(const char *path)
{
    struct stat sb;
    if (stat(path, &sb) < 0) {
	debug(20, 0) ("%s: %s\n", path, xstrerror());
	return -1;
    }
    if (S_ISDIR(sb.st_mode) == 0) {
	debug(20, 0) ("%s is not a directory\n", path);
	return -1;
    }
    return 0;
}

/*
 * This function is called by storeDiskdDirInit().  If this returns < 0,
 * then Squid exits, complains about swap directories not
 * existing, and instructs the admin to run 'squid -z'
 */
static int
storeDiskdDirVerifyCacheDirs(SwapDir * sd)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    int j;
    const char *path = sd->path;

    if (storeDiskdDirVerifyDirectory(path) < 0)
	return -1;
    for (j = 0; j < diskdinfo->l1; j++) {
	path = storeDiskdDirSwapSubDir(sd, j);
	if (storeDiskdDirVerifyDirectory(path) < 0)
	    return -1;
    }
    return 0;
}

static void
storeDiskdDirCreateSwapSubDirs(SwapDir * sd)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    int i, k;
    int should_exist;
    LOCAL_ARRAY(char, name, MAXPATHLEN);
    for (i = 0; i < diskdinfo->l1; i++) {
	snprintf(name, MAXPATHLEN, "%s/%02X", sd->path, i);
	if (storeDiskdDirCreateDirectory(name, 0))
	    should_exist = 0;
	else
	    should_exist = 1;
	debug(47, 1) ("Making directories in %s\n", name);
	for (k = 0; k < diskdinfo->l2; k++) {
	    snprintf(name, MAXPATHLEN, "%s/%02X/%02X", sd->path, i, k);
	    storeDiskdDirCreateDirectory(name, should_exist);
	}
    }
}

static char *
storeDiskdDirSwapLogFile(SwapDir * sd, const char *ext)
{
    LOCAL_ARRAY(char, path, SQUID_MAXPATHLEN);
    LOCAL_ARRAY(char, pathtmp, SQUID_MAXPATHLEN);
    LOCAL_ARRAY(char, digit, 32);
    char *pathtmp2;
    if (Config.Log.swap) {
	xstrncpy(pathtmp, sd->path, SQUID_MAXPATHLEN - 64);
	pathtmp2 = pathtmp;
	while ((pathtmp2 = strchr(pathtmp2, '/')) != NULL)
	    *pathtmp2 = '.';
	while (strlen(pathtmp) && pathtmp[strlen(pathtmp) - 1] == '.')
	    pathtmp[strlen(pathtmp) - 1] = '\0';
	for (pathtmp2 = pathtmp; *pathtmp2 == '.'; pathtmp2++);
	snprintf(path, SQUID_MAXPATHLEN - 64, Config.Log.swap, pathtmp2);
	if (strncmp(path, Config.Log.swap, SQUID_MAXPATHLEN - 64) == 0) {
	    strcat(path, ".");
	    snprintf(digit, 32, "%02d", sd->index);
	    strncat(path, digit, 3);
	}
    } else {
	xstrncpy(path, sd->path, SQUID_MAXPATHLEN - 64);
	strcat(path, "/swap.state");
    }
    if (ext)
	strncat(path, ext, 16);
    return path;
}

static void
storeDiskdDirOpenSwapLog(SwapDir * sd)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    char *path;
    int fd;
    path = storeDiskdDirSwapLogFile(sd, NULL);
    fd = file_open(path, O_WRONLY | O_CREAT | O_BINARY);
    if (fd < 0) {
	debug(50, 1) ("%s: %s\n", path, xstrerror());
	fatal("storeDiskdDirOpenSwapLog: Failed to open swap log.");
    }
    debug(47, 3) ("Cache Dir #%d log opened on FD %d\n", sd->index, fd);
    diskdinfo->swaplog_fd = fd;
    if (0 == n_diskd_dirs)
	assert(NULL == diskd_dir_index);
    n_diskd_dirs++;
    assert(n_diskd_dirs <= Config.cacheSwap.n_configured);
}

static void
storeDiskdDirCloseSwapLog(SwapDir * sd)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    if (diskdinfo->swaplog_fd < 0)	/* not open */
	return;
    file_close(diskdinfo->swaplog_fd);
    debug(47, 3) ("Cache Dir #%d log closed on FD %d\n",
	sd->index, diskdinfo->swaplog_fd);
    diskdinfo->swaplog_fd = -1;
    n_diskd_dirs--;
    assert(n_diskd_dirs >= 0);
    if (0 == n_diskd_dirs)
	safe_free(diskd_dir_index);
}

static void
storeDiskdCheckConfig(SwapDir * sd)
{
    requirePathnameExists("diskd_program", Config.Program.diskd);
    if (!opt_create_swap_dirs)
	requirePathnameExists("cache_dir", sd->path);
}

static void
diskdExited(int fd, void *unused)
{
    fatal("diskd exited unexpectedly");
}

static void
storeDiskdDirInit(SwapDir * sd)
{
    static int started_clean_event = 0;
    pid_t pid;
    int i;
    int ikey;
    const char *args[5];
    char skey1[32];
    char skey2[32];
    char skey3[32];
    diskdinfo_t *diskdinfo = sd->fsdata;
    static const char *errmsg =
    "\tFailed to verify one of the swap directories, Check cache.log\n"
    "\tfor details.  Run 'squid -z' to create swap directories\n"
    "\tif needed, or if running Squid for the first time.";

    ikey = (getpid() << 10) + (sd->index << 2);
    ikey &= 0x7fffffff;
    diskdinfo->smsgid = msgget((key_t) ikey, 0700 | IPC_CREAT);
    if (diskdinfo->smsgid < 0) {
	debug(50, 0) ("storeDiskdInit: msgget: %s\n", xstrerror());
	fatal("msgget failed");
    }
    diskdinfo->rmsgid = msgget((key_t) (ikey + 1), 0700 | IPC_CREAT);
    if (diskdinfo->rmsgid < 0) {
	debug(50, 0) ("storeDiskdInit: msgget: %s\n", xstrerror());
	fatal("msgget failed");
    }
    diskdinfo->shm.nbufs = diskdinfo->magic2 * 1.3;
    diskdinfo->shm.id = shmget((key_t) (ikey + 2),
	diskdinfo->shm.nbufs * SHMBUF_BLKSZ, 0600 | IPC_CREAT);
    if (diskdinfo->shm.id < 0) {
	debug(50, 0) ("storeDiskdInit: shmget: %s\n", xstrerror());
	fatal("shmget failed");
    }
    diskdinfo->shm.buf = shmat(diskdinfo->shm.id, NULL, 0);
    if (diskdinfo->shm.buf == (void *) -1) {
	debug(50, 0) ("storeDiskdInit: shmat: %s\n", xstrerror());
	fatal("shmat failed");
    }
    diskdinfo->shm.inuse_map = xcalloc((diskdinfo->shm.nbufs + 7) / 8, 1);
    diskd_stats.shmbuf_count += diskdinfo->shm.nbufs;
    for (i = 0; i < diskdinfo->shm.nbufs; i++) {
	CBIT_SET(diskdinfo->shm.inuse_map, i);
	storeDiskdShmPut(sd, i * SHMBUF_BLKSZ);
    }
    snprintf(skey1, 32, "%d", ikey);
    snprintf(skey2, 32, "%d", ikey + 1);
    snprintf(skey3, 32, "%d", ikey + 2);
    args[0] = "diskd";
    args[1] = skey1;
    args[2] = skey2;
    args[3] = skey3;
    args[4] = NULL;
    pid = ipcCreate(IPC_STREAM,
	Config.Program.diskd,
	args,
	"diskd",
	&diskdinfo->rfd,
	&diskdinfo->wfd,
	&diskdinfo->hIpc);
    if (pid < 0)
	fatalf("execl: %s", Config.Program.diskd);
    fd_note(diskdinfo->rfd, "diskd -> squid health monitor");
    fd_note(diskdinfo->wfd, "squid -> diskd health monitor");
    commSetSelect(diskdinfo->rfd, COMM_SELECT_READ, diskdExited, NULL, 0);
    storeDiskdDirInitBitmap(sd);
    if (storeDiskdDirVerifyCacheDirs(sd) < 0)
	fatal(errmsg);
    storeDiskdDirOpenSwapLog(sd);
    storeDiskdDirRebuild(sd);
    if (!started_clean_event) {
	eventAdd("storeDirClean", storeDiskdDirCleanEvent, NULL, 15.0, 1);
	started_clean_event = 1;
    }
    (void) storeDirGetBlkSize(sd->path, &sd->fs.blksize);
    comm_quick_poll_required();
}


static void
storeDiskdStats(StoreEntry * sentry)
{
    storeAppendPrintf(sentry, "sent_count: %d\n", diskd_stats.sent_count);
    storeAppendPrintf(sentry, "recv_count: %d\n", diskd_stats.recv_count);
    storeAppendPrintf(sentry, "max_away: %d\n", diskd_stats.max_away);
    storeAppendPrintf(sentry, "max_shmuse: %d\n", diskd_stats.max_shmuse);
    storeAppendPrintf(sentry, "open_fail_queue_len: %d\n", diskd_stats.open_fail_queue_len);
    storeAppendPrintf(sentry, "block_queue_len: %d\n", diskd_stats.block_queue_len);
    diskd_stats.max_away = diskd_stats.max_shmuse = 0;
    storeAppendPrintf(sentry, "\n              OPS   SUCCESS    FAIL\n");
    storeAppendPrintf(sentry, "%7s %9d %9d %7d\n",
	"open", diskd_stats.open.ops, diskd_stats.open.success, diskd_stats.open.fail);
    storeAppendPrintf(sentry, "%7s %9d %9d %7d\n",
	"create", diskd_stats.create.ops, diskd_stats.create.success, diskd_stats.create.fail);
    storeAppendPrintf(sentry, "%7s %9d %9d %7d\n",
	"close", diskd_stats.close.ops, diskd_stats.close.success, diskd_stats.close.fail);
    storeAppendPrintf(sentry, "%7s %9d %9d %7d\n",
	"unlink", diskd_stats.unlink.ops, diskd_stats.unlink.success, diskd_stats.unlink.fail);
    storeAppendPrintf(sentry, "%7s %9d %9d %7d\n",
	"read", diskd_stats.read.ops, diskd_stats.read.success, diskd_stats.read.fail);
    storeAppendPrintf(sentry, "%7s %9d %9d %7d\n",
	"write", diskd_stats.write.ops, diskd_stats.write.success, diskd_stats.write.fail);
}

/*
 * storeDiskdDirSync
 *
 * Sync any pending data. We just sit around and read the queue
 * until the data has finished writing.
 */
static void
storeDiskdDirSync(SwapDir * SD)
{
    static time_t lastmsg = 0;
    diskdinfo_t *diskdinfo = SD->fsdata;
    while (diskdinfo->away > 0) {
	if (squid_curtime > lastmsg) {
	    debug(47, 1) ("storeDiskdDirSync: %d messages away\n",
		diskdinfo->away);
	    lastmsg = squid_curtime;
	}
	storeDiskdDirCallback(SD);
    }
}


/*
 * storeDiskdDirCallback
 *
 * Handle callbacks. If we have more than magic2 requests away, we block
 * until the queue is below magic2. Otherwise, we simply return when we
 * don't get a message.
 */
int
storeDiskdDirCallback(SwapDir * SD)
{
    diomsg M;
    int x;
    diskdinfo_t *diskdinfo = SD->fsdata;
    int retval = 0;

    if (diskdinfo->away >= diskdinfo->magic2) {
	diskd_stats.block_queue_len++;
	retval = 1;		/* We might not have anything to do, but our queue
				 * is full.. */
    }
    if (diskd_stats.sent_count - diskd_stats.recv_count >
	diskd_stats.max_away) {
	diskd_stats.max_away = diskd_stats.sent_count - diskd_stats.recv_count;
    }
    while (1) {
	memset(&M, '\0', sizeof(M));
	x = msgrcv(diskdinfo->rmsgid, &M, msg_snd_rcv_sz, 0, IPC_NOWAIT);
	if (x < 0)
	    break;
	else if (x != msg_snd_rcv_sz) {
	    debug(79, 1) ("storeDiskdDirCallback: msgget returns %d\n",
		x);
	    break;
	}
#if 0
	debug(47, 3) ("msgrcv %ld %d %d %p %d %" PRINTF_OFF_T " %d %d\n",
	    M.mtype,
	    M.id,
	    M.seq_no,
	    M.callback_data,
	    M.size,
	    M.offset,
	    M.status,
	    M.shm_offset);
#endif
	diskd_stats.recv_count++;
	diskdinfo->away--;
	storeDiskdHandle(&M);
	retval = 1;		/* Return that we've actually done some work */
	if (M.shm_offset > -1)
	    storeDiskdShmPut(SD, M.shm_offset);
    }
    return retval;
}



static void
storeDiskdDirRebuildComplete(RebuildState * rb)
{
    if (rb->log) {
	debug(47, 1) ("Done reading %s swaplog (%d entries)\n",
	    rb->sd->path, rb->n_read);
	fclose(rb->log);
	rb->log = NULL;
    } else {
	debug(47, 1) ("Done scanning %s (%d entries)\n",
	    rb->sd->path, rb->counts.scancount);
    }
    store_dirs_rebuilding--;
    storeDiskdDirCloseTmpSwapLog(rb->sd);
    storeRebuildComplete(&rb->counts);
    cbdataFree(rb);
}

static void
storeDiskdDirRebuildFromDirectory(void *data)
{
    RebuildState *rb = data;
    SwapDir *SD = rb->sd;
    LOCAL_ARRAY(char, hdr_buf, SM_PAGE_SIZE);
    StoreEntry *e = NULL;
    StoreEntry tmpe;
    cache_key key[SQUID_MD5_DIGEST_LENGTH];
    sfileno filn = 0;
    int count;
    int size;
    struct stat sb;
    int swap_hdr_len;
    int fd = -1;
    tlv *tlv_list;
    tlv *t;
    assert(rb != NULL);
    debug(20, 3) ("storeDiskdDirRebuildFromDirectory: DIR #%d\n", rb->sd->index);
    for (count = 0; count < rb->speed; count++) {
	assert(fd == -1);
	fd = storeDiskdDirGetNextFile(rb, &filn, &size);
	if (fd == -2) {
	    storeDiskdDirRebuildComplete(rb);
	    return;
	} else if (fd < 0) {
	    continue;
	}
	assert(fd > -1);
	/* lets get file stats here */
	if (fstat(fd, &sb) < 0) {
	    debug(20, 1) ("storeDiskdDirRebuildFromDirectory: fstat(FD %d): %s\n",
		fd, xstrerror());
	    file_close(fd);
	    store_open_disk_fd--;
	    fd = -1;
	    continue;
	}
	if ((++rb->counts.scancount & 0xFFFF) == 0)
	    debug(20, 3) ("  %s %7d files opened so far.\n",
		rb->sd->path, rb->counts.scancount);
	debug(20, 9) ("file_in: fd=%d %08X\n", fd, filn);
	statCounter.syscalls.disk.reads++;
	if (FD_READ_METHOD(fd, hdr_buf, SM_PAGE_SIZE) < 0) {
	    debug(20, 1) ("storeDiskdDirRebuildFromDirectory: read(FD %d): %s\n",
		fd, xstrerror());
	    file_close(fd);
	    store_open_disk_fd--;
	    fd = -1;
	    continue;
	}
	file_close(fd);
	store_open_disk_fd--;
	fd = -1;
	swap_hdr_len = 0;
#if USE_TRUNCATE
	if (sb.st_size == 0)
	    continue;
#endif
	tlv_list = storeSwapMetaUnpack(hdr_buf, &swap_hdr_len);
	if (tlv_list == NULL) {
	    debug(20, 1) ("storeDiskdDirRebuildFromDirectory: failed to get meta data\n");
	    /* XXX shouldn't this be a call to storeDiskdUnlink ? */
	    storeDiskdDirUnlinkFile(SD, filn);
	    continue;
	}
	debug(20, 3) ("storeDiskdDirRebuildFromDirectory: successful swap meta unpacking\n");
	memset(key, '\0', SQUID_MD5_DIGEST_LENGTH);
	memset(&tmpe, '\0', sizeof(StoreEntry));
	for (t = tlv_list; t; t = t->next) {
	    switch (t->type) {
	    case STORE_META_KEY:
		assert(t->length == SQUID_MD5_DIGEST_LENGTH);
		xmemcpy(key, t->value, SQUID_MD5_DIGEST_LENGTH);
		break;
#if SIZEOF_SQUID_FILE_SZ == SIZEOF_SIZE_T
	    case STORE_META_STD:
		assert(t->length == STORE_HDR_METASIZE);
		xmemcpy(&tmpe.timestamp, t->value, STORE_HDR_METASIZE);
		break;
#else
	    case STORE_META_STD_LFS:
		assert(t->length == STORE_HDR_METASIZE);
		xmemcpy(&tmpe.timestamp, t->value, STORE_HDR_METASIZE);
		break;
	    case STORE_META_STD:
		assert(t->length == STORE_HDR_METASIZE_OLD);
		{
		    struct {
			time_t timestamp;
			time_t lastref;
			time_t expires;
			time_t lastmod;
			size_t swap_file_sz;
			u_short refcount;
			u_short flags;
		    }     *tmp = t->value;
		    assert(sizeof(*tmp) == STORE_HDR_METASIZE_OLD);
		    tmpe.timestamp = tmp->timestamp;
		    tmpe.lastref = tmp->lastref;
		    tmpe.expires = tmp->expires;
		    tmpe.lastmod = tmp->lastmod;
		    tmpe.swap_file_sz = tmp->swap_file_sz;
		    tmpe.refcount = tmp->refcount;
		    tmpe.flags = tmp->flags;
		}
		break;
#endif
	    default:
		break;
	    }
	}
	storeSwapTLVFree(tlv_list);
	tlv_list = NULL;
	if (storeKeyNull(key)) {
	    debug(20, 1) ("storeDiskdDirRebuildFromDirectory: NULL key\n");
	    storeDiskdDirUnlinkFile(SD, filn);
	    continue;
	}
	tmpe.hash.key = key;
	/* check sizes */
	if (tmpe.swap_file_sz == 0) {
	    tmpe.swap_file_sz = sb.st_size;
	} else if (tmpe.swap_file_sz == sb.st_size - swap_hdr_len) {
	    tmpe.swap_file_sz = sb.st_size;
	} else if (tmpe.swap_file_sz != sb.st_size) {
	    debug(20, 1) ("storeDiskdDirRebuildFromDirectory: SIZE MISMATCH %ld!=%ld\n",
		(long int) tmpe.swap_file_sz, (long int) sb.st_size);
	    storeDiskdDirUnlinkFile(SD, filn);
	    continue;
	}
	if (EBIT_TEST(tmpe.flags, KEY_PRIVATE)) {
	    storeDiskdDirUnlinkFile(SD, filn);
	    rb->counts.badflags++;
	    continue;
	}
	e = storeGet(key);
	if (e && e->lastref >= tmpe.lastref) {
	    /* key already exists, current entry is newer */
	    /* keep old, ignore new */
	    rb->counts.dupcount++;
	    continue;
	} else if (NULL != e) {
	    /* URL already exists, this swapfile not being used */
	    /* junk old, load new */
	    storeRelease(e);	/* release old entry */
	    rb->counts.dupcount++;
	}
	rb->counts.objcount++;
	storeEntryDump(&tmpe, 5);
	e = storeDiskdDirAddDiskRestore(SD, key,
	    filn,
	    tmpe.swap_file_sz,
	    tmpe.expires,
	    tmpe.timestamp,
	    tmpe.lastref,
	    tmpe.lastmod,
	    tmpe.refcount,	/* refcount */
	    tmpe.flags,		/* flags */
	    (int) rb->flags.clean);
	storeDirSwapLog(e, SWAP_LOG_ADD);
    }
    eventAdd("storeRebuild", storeDiskdDirRebuildFromDirectory, rb, 0.0, 1);
}

static void
storeDiskdDirRebuildFromSwapLog(void *data)
{
    RebuildState *rb = data;
    SwapDir *SD = rb->sd;
    StoreEntry *e = NULL;
    storeSwapLogData s;
    size_t ss = sizeof(storeSwapLogData);
    int count;
    int used;			/* is swapfile already in use? */
    int disk_entry_newer;	/* is the log entry newer than current entry? */
    double x;
    assert(rb != NULL);
    /* load a number of objects per invocation */
    for (count = 0; count < rb->speed; count++) {
	if (fread(&s, ss, 1, rb->log) != 1) {
	    storeDiskdDirRebuildComplete(rb);
	    return;
	}
	rb->n_read++;
	/*
	 * BC: during 2.4 development, we changed the way swap file
	 * numbers are assigned and stored.  The high 16 bits used
	 * to encode the SD index number.  There used to be a call
	 * to storeDirProperFileno here that re-assigned the index
	 * bits.  Now, for backwards compatibility, we just need
	 * to mask it off.
	 */
	s.swap_filen &= 0x00FFFFFF;
	debug(20, 3) ("storeDiskdDirRebuildFromSwapLog: %s %s %08X\n",
	    swap_log_op_str[(int) s.op],
	    storeKeyText(s.key),
	    s.swap_filen);
	if (s.op == SWAP_LOG_ADD) {
	    /*
	     * Here we have some special checks for large files.
	     * I've been seeing a system crash followed by a reboot
	     * that seems to corrupt the swap log.  Squid believes
	     * that the disk holds some really large files.  It
	     * complains about using being over the high water mark
	     * and proceeds to delete files as fast as it can.  To
	     * prevent that, we call stat() on sufficiently large
	     * files (>128KB) and reject those that are missing or
	     * have the wrong size.
	     */
	    struct stat sb;
	    char *p = storeDiskdDirFullPath(SD, s.swap_filen, NULL);
	    if (s.swap_file_sz < (1 << 17)) {
		(void) 0;
	    } else if (stat(p, &sb) < 0) {
		debug(47, 2) ("its missing!: %s\n", p);
		continue;
	    } else if (sb.st_size != s.swap_file_sz) {
		debug(47, 2) ("size mismatch!: stat=%d, log=%d\n",
		    (int) sb.st_size, (int) s.swap_file_sz);
		continue;
	    } else {
		debug(47, 2) ("big file (%d bytes) checks out\n",
		    (int) s.swap_file_sz);
	    }
	} else if (s.op == SWAP_LOG_DEL) {
	    /* Delete unless we already have a newer copy */
	    if ((e = storeGet(s.key)) != NULL && s.lastref >= e->lastref) {
		/*
		 * Make sure we don't unlink the file, it might be
		 * in use by a subsequent entry.  Also note that
		 * we don't have to subtract from store_swap_size
		 * because adding to store_swap_size happens in
		 * the cleanup procedure.
		 */
		storeRecycle(e);
		rb->counts.cancelcount++;
	    }
	    continue;
	} else {
	    x = log(++rb->counts.bad_log_op) / log(10.0);
	    if (0.0 == x - (double) (int) x)
		debug(20, 1) ("WARNING: %d invalid swap log entries found\n",
		    rb->counts.bad_log_op);
	    rb->counts.invalid++;
	    continue;
	}
	if ((++rb->counts.scancount & 0xFFF) == 0) {
	    struct stat sb;
	    if (0 == fstat(fileno(rb->log), &sb))
		storeRebuildProgress(SD->index,
		    (int) sb.st_size / ss, rb->n_read);
	}
	if (!storeDiskdDirValidFileno(SD, s.swap_filen, 0)) {
	    rb->counts.invalid++;
	    continue;
	}
	if (EBIT_TEST(s.flags, KEY_PRIVATE)) {
	    rb->counts.badflags++;
	    continue;
	}
	e = storeGet(s.key);
	used = storeDiskdDirMapBitTest(SD, s.swap_filen);
	/* If this URL already exists in the cache, does the swap log
	 * appear to have a newer entry?  Compare 'lastref' from the
	 * swap log to e->lastref. */
	disk_entry_newer = e ? (s.lastref > e->lastref ? 1 : 0) : 0;
	if (used && !disk_entry_newer) {
	    /* log entry is old, ignore it */
	    rb->counts.clashcount++;
	    continue;
	} else if (used && e && e->swap_filen == s.swap_filen && e->swap_dirn == SD->index) {
	    /* swapfile taken, same URL, newer, update meta */
	    if (e->store_status == STORE_OK) {
		e->lastref = s.timestamp;
		e->timestamp = s.timestamp;
		e->expires = s.expires;
		e->lastmod = s.lastmod;
		e->flags = s.flags;
		e->refcount += s.refcount;
		storeDiskdDirUnrefObj(SD, e);
	    } else {
		debug_trap("storeDiskdDirRebuildFromSwapLog: bad condition");
		debug(20, 1) ("\tSee %s:%d\n", __FILE__, __LINE__);
	    }
	    continue;
	} else if (used) {
	    /* swapfile in use, not by this URL, log entry is newer */
	    /* This is sorta bad: the log entry should NOT be newer at this
	     * point.  If the log is dirty, the filesize check should have
	     * caught this.  If the log is clean, there should never be a
	     * newer entry. */
	    debug(20, 1) ("WARNING: newer swaplog entry for dirno %d, fileno %08X\n",
		SD->index, s.swap_filen);
	    /* I'm tempted to remove the swapfile here just to be safe,
	     * but there is a bad race condition in the NOVM version if
	     * the swapfile has recently been opened for writing, but
	     * not yet opened for reading.  Because we can't map
	     * swapfiles back to StoreEntrys, we don't know the state
	     * of the entry using that file.  */
	    /* We'll assume the existing entry is valid, probably because
	     * the swap file number got taken while we rebuild */
	    rb->counts.clashcount++;
	    continue;
	} else if (e && !disk_entry_newer) {
	    /* key already exists, current entry is newer */
	    /* keep old, ignore new */
	    rb->counts.dupcount++;
	    continue;
	} else if (e) {
	    /* key already exists, this swapfile not being used */
	    /* junk old, load new */
	    storeRecycle(e);
	    rb->counts.dupcount++;
	} else {
	    /* URL doesnt exist, swapfile not in use */
	    /* load new */
	    (void) 0;
	}
	/* update store_swap_size */
	rb->counts.objcount++;
	e = storeDiskdDirAddDiskRestore(SD, s.key,
	    s.swap_filen,
	    s.swap_file_sz,
	    s.expires,
	    s.timestamp,
	    s.lastref,
	    s.lastmod,
	    s.refcount,
	    s.flags,
	    (int) rb->flags.clean);
	storeDirSwapLog(e, SWAP_LOG_ADD);
    }
    eventAdd("storeRebuild", storeDiskdDirRebuildFromSwapLog, rb, 0.0, 1);
}

#if SIZEOF_SQUID_FILE_SZ != SIZEOF_SIZE_T
/* This is an exact copy of the above, but using storeSwapLogDataOld entry type */
static void
storeDiskdDirRebuildFromSwapLogOld(void *data)
{
    RebuildState *rb = data;
    SwapDir *SD = rb->sd;
    StoreEntry *e = NULL;
    storeSwapLogDataOld s;
    size_t ss = sizeof(storeSwapLogDataOld);
    int count;
    int used;			/* is swapfile already in use? */
    int disk_entry_newer;	/* is the log entry newer than current entry? */
    double x;
    assert(rb != NULL);
    /* load a number of objects per invocation */
    for (count = 0; count < rb->speed; count++) {
	if (fread(&s, ss, 1, rb->log) != 1) {
	    storeDiskdDirRebuildComplete(rb);
	    return;
	}
	rb->n_read++;
	/*
	 * BC: during 2.4 development, we changed the way swap file
	 * numbers are assigned and stored.  The high 16 bits used
	 * to encode the SD index number.  There used to be a call
	 * to storeDirProperFileno here that re-assigned the index
	 * bits.  Now, for backwards compatibility, we just need
	 * to mask it off.
	 */
	s.swap_filen &= 0x00FFFFFF;
	debug(20, 3) ("storeDiskdDirRebuildFromSwapLog: %s %s %08X\n",
	    swap_log_op_str[(int) s.op],
	    storeKeyText(s.key),
	    s.swap_filen);
	if (s.op == SWAP_LOG_ADD) {
	    /*
	     * Here we have some special checks for large files.
	     * I've been seeing a system crash followed by a reboot
	     * that seems to corrupt the swap log.  Squid believes
	     * that the disk holds some really large files.  It
	     * complains about using being over the high water mark
	     * and proceeds to delete files as fast as it can.  To
	     * prevent that, we call stat() on sufficiently large
	     * files (>128KB) and reject those that are missing or
	     * have the wrong size.
	     */
	    struct stat sb;
	    char *p = storeDiskdDirFullPath(SD, s.swap_filen, NULL);
	    if (s.swap_file_sz < (1 << 17)) {
		(void) 0;
	    } else if (stat(p, &sb) < 0) {
		debug(47, 2) ("its missing!: %s\n", p);
		continue;
	    } else if (sb.st_size != s.swap_file_sz) {
		debug(47, 2) ("size mismatch!: stat=%d, log=%d\n",
		    (int) sb.st_size, (int) s.swap_file_sz);
		continue;
	    } else {
		debug(47, 2) ("big file (%d bytes) checks out\n",
		    (int) s.swap_file_sz);
	    }
	} else if (s.op == SWAP_LOG_DEL) {
	    /* Delete unless we already have a newer copy */
	    if ((e = storeGet(s.key)) != NULL && s.lastref >= e->lastref) {
		/*
		 * Make sure we don't unlink the file, it might be
		 * in use by a subsequent entry.  Also note that
		 * we don't have to subtract from store_swap_size
		 * because adding to store_swap_size happens in
		 * the cleanup procedure.
		 */
		storeRecycle(e);
		rb->counts.cancelcount++;
	    }
	    continue;
	} else {
	    x = log(++rb->counts.bad_log_op) / log(10.0);
	    if (0.0 == x - (double) (int) x)
		debug(20, 1) ("WARNING: %d invalid swap log entries found\n",
		    rb->counts.bad_log_op);
	    rb->counts.invalid++;
	    continue;
	}
	if ((++rb->counts.scancount & 0xFFF) == 0) {
	    struct stat sb;
	    if (0 == fstat(fileno(rb->log), &sb))
		storeRebuildProgress(SD->index,
		    (int) sb.st_size / ss, rb->n_read);
	}
	if (!storeDiskdDirValidFileno(SD, s.swap_filen, 0)) {
	    rb->counts.invalid++;
	    continue;
	}
	if (EBIT_TEST(s.flags, KEY_PRIVATE)) {
	    rb->counts.badflags++;
	    continue;
	}
	e = storeGet(s.key);
	used = storeDiskdDirMapBitTest(SD, s.swap_filen);
	/* If this URL already exists in the cache, does the swap log
	 * appear to have a newer entry?  Compare 'lastref' from the
	 * swap log to e->lastref. */
	disk_entry_newer = e ? (s.lastref > e->lastref ? 1 : 0) : 0;
	if (used && !disk_entry_newer) {
	    /* log entry is old, ignore it */
	    rb->counts.clashcount++;
	    continue;
	} else if (used && e && e->swap_filen == s.swap_filen && e->swap_dirn == SD->index) {
	    /* swapfile taken, same URL, newer, update meta */
	    if (e->store_status == STORE_OK) {
		e->lastref = s.timestamp;
		e->timestamp = s.timestamp;
		e->expires = s.expires;
		e->lastmod = s.lastmod;
		e->flags = s.flags;
		e->refcount += s.refcount;
		storeDiskdDirUnrefObj(SD, e);
	    } else {
		debug_trap("storeDiskdDirRebuildFromSwapLog: bad condition");
		debug(20, 1) ("\tSee %s:%d\n", __FILE__, __LINE__);
	    }
	    continue;
	} else if (used) {
	    /* swapfile in use, not by this URL, log entry is newer */
	    /* This is sorta bad: the log entry should NOT be newer at this
	     * point.  If the log is dirty, the filesize check should have
	     * caught this.  If the log is clean, there should never be a
	     * newer entry. */
	    debug(20, 1) ("WARNING: newer swaplog entry for dirno %d, fileno %08X\n",
		SD->index, s.swap_filen);
	    /* I'm tempted to remove the swapfile here just to be safe,
	     * but there is a bad race condition in the NOVM version if
	     * the swapfile has recently been opened for writing, but
	     * not yet opened for reading.  Because we can't map
	     * swapfiles back to StoreEntrys, we don't know the state
	     * of the entry using that file.  */
	    /* We'll assume the existing entry is valid, probably because
	     * the swap file number got taken while we rebuild */
	    rb->counts.clashcount++;
	    continue;
	} else if (e && !disk_entry_newer) {
	    /* key already exists, current entry is newer */
	    /* keep old, ignore new */
	    rb->counts.dupcount++;
	    continue;
	} else if (e) {
	    /* key already exists, this swapfile not being used */
	    /* junk old, load new */
	    storeRecycle(e);
	    rb->counts.dupcount++;
	} else {
	    /* URL doesnt exist, swapfile not in use */
	    /* load new */
	    (void) 0;
	}
	/* update store_swap_size */
	rb->counts.objcount++;
	e = storeDiskdDirAddDiskRestore(SD, s.key,
	    s.swap_filen,
	    s.swap_file_sz,
	    s.expires,
	    s.timestamp,
	    s.lastref,
	    s.lastmod,
	    s.refcount,
	    s.flags,
	    (int) rb->flags.clean);
	storeDirSwapLog(e, SWAP_LOG_ADD);
    }
    eventAdd("storeRebuild", storeDiskdDirRebuildFromSwapLogOld, rb, 0.0, 1);
}

#endif

static void
storeDiskdDirRebuildFromSwapLogCheckVersion(void *data)
{
    RebuildState *rb = data;
    storeSwapLogHeader hdr;

    if (fread(&hdr, sizeof(hdr), 1, rb->log) != 1) {
	storeDiskdDirRebuildComplete(rb);
	return;
    }
    if (hdr.op == SWAP_LOG_VERSION) {
	if (fseek(rb->log, hdr.record_size, SEEK_SET) != 0) {
	    storeDiskdDirRebuildComplete(rb);
	    return;
	}
	if (hdr.version == 1 && hdr.record_size == sizeof(storeSwapLogData)) {
	    eventAdd("storeRebuild", storeDiskdDirRebuildFromSwapLog, rb, 0.0, 1);
	    return;
	}
#if SIZEOF_SQUID_FILE_SZ != SIZEOF_SIZE_T
	if (hdr.version == 1 && hdr.record_size == sizeof(storeSwapLogDataOld)) {
	    debug(47, 1) ("storeDiskdDirRebuildFromSwapLog: Found current version but without large file support. Upgrading\n");
	    eventAdd("storeRebuild", storeDiskdDirRebuildFromSwapLogOld, rb, 0.0, 1);
	    return;
	}
#endif
	debug(47, 1) ("storeDiskdDirRebuildFromSwapLog: Unsupported swap.state version %d size %d\n",
	    hdr.version, hdr.record_size);
	storeDiskdDirRebuildComplete(rb);
	return;
    }
    rewind(rb->log);
    debug(47, 1) ("storeDiskdDirRebuildFromSwapLog: Old version detected. Upgrading\n");
#if SIZEOF_SQUID_FILE_SZ == SIZEOF_SIZE_T
    eventAdd("storeRebuild", storeDiskdDirRebuildFromSwapLog, rb, 0.0, 1);
#else
    eventAdd("storeRebuild", storeDiskdDirRebuildFromSwapLogOld, rb, 0.0, 1);
#endif
}

static int
storeDiskdDirGetNextFile(RebuildState * rb, sfileno * filn_p, int *size)
{
    SwapDir *SD = rb->sd;
    diskdinfo_t *diskdinfo = SD->fsdata;
    int fd = -1;
    int used = 0;
    int dirs_opened = 0;
    debug(20, 3) ("storeDiskdDirGetNextFile: flag=%d, %d: /%02X/%02X\n",
	rb->flags.init,
	rb->sd->index,
	rb->curlvl1,
	rb->curlvl2);
    if (rb->done)
	return -2;
    while (fd < 0 && rb->done == 0) {
	fd = -1;
	if (0 == rb->flags.init) {	/* initialize, open first file */
	    rb->done = 0;
	    rb->curlvl1 = 0;
	    rb->curlvl2 = 0;
	    rb->in_dir = 0;
	    rb->flags.init = 1;
	    assert(Config.cacheSwap.n_configured > 0);
	}
	if (0 == rb->in_dir) {	/* we need to read in a new directory */
	    snprintf(rb->fullpath, SQUID_MAXPATHLEN, "%s/%02X/%02X",
		rb->sd->path,
		rb->curlvl1, rb->curlvl2);
	    if (dirs_opened)
		return -1;
	    rb->td = opendir(rb->fullpath);
	    dirs_opened++;
	    if (rb->td == NULL) {
		debug(50, 1) ("storeDiskdDirGetNextFile: opendir: %s: %s\n",
		    rb->fullpath, xstrerror());
	    } else {
		rb->entry = readdir(rb->td);	/* skip . and .. */
		rb->entry = readdir(rb->td);
		if (rb->entry == NULL && errno == ENOENT)
		    debug(20, 1) ("storeDiskdDirGetNextFile: directory does not exist!.\n");
		debug(20, 3) ("storeDiskdDirGetNextFile: Directory %s\n", rb->fullpath);
	    }
	}
	if (rb->td != NULL && (rb->entry = readdir(rb->td)) != NULL) {
	    rb->in_dir++;
	    if (sscanf(rb->entry->d_name, "%x", &rb->fn) != 1) {
		debug(20, 3) ("storeDiskdDirGetNextFile: invalid %s\n",
		    rb->entry->d_name);
		continue;
	    }
	    if (!storeDiskdFilenoBelongsHere(rb->fn, rb->sd->index, rb->curlvl1, rb->curlvl2)) {
		debug(20, 3) ("storeDiskdDirGetNextFile: %08X does not belong in %d/%d/%d\n",
		    rb->fn, rb->sd->index, rb->curlvl1, rb->curlvl2);
		continue;
	    }
	    used = storeDiskdDirMapBitTest(SD, rb->fn);
	    if (used) {
		debug(20, 3) ("storeDiskdDirGetNextFile: Locked, continuing with next.\n");
		continue;
	    }
	    snprintf(rb->fullfilename, SQUID_MAXPATHLEN, "%s/%s",
		rb->fullpath, rb->entry->d_name);
	    debug(20, 3) ("storeDiskdDirGetNextFile: Opening %s\n", rb->fullfilename);
	    fd = file_open(rb->fullfilename, O_RDONLY | O_BINARY);
	    if (fd < 0)
		debug(50, 1) ("storeDiskdDirGetNextFile: %s: %s\n", rb->fullfilename, xstrerror());
	    else
		store_open_disk_fd++;
	    continue;
	}
	if (rb->td != NULL)
	    closedir(rb->td);
	rb->td = NULL;
	rb->in_dir = 0;
	if (++rb->curlvl2 < diskdinfo->l2)
	    continue;
	rb->curlvl2 = 0;
	if (++rb->curlvl1 < diskdinfo->l1)
	    continue;
	rb->curlvl1 = 0;
	rb->done = 1;
    }
    *filn_p = rb->fn;
    return fd;
}

/* Add a new object to the cache with empty memory copy and pointer to disk
 * use to rebuild store from disk. */
static StoreEntry *
storeDiskdDirAddDiskRestore(SwapDir * SD, const cache_key * key,
    int file_number,
    squid_file_sz swap_file_sz,
    time_t expires,
    time_t timestamp,
    time_t lastref,
    time_t lastmod,
    u_num32 refcount,
    u_short flags,
    int clean)
{
    StoreEntry *e = NULL;
    debug(20, 5) ("storeDiskdAddDiskRestore: %s, fileno=%08X\n", storeKeyText(key), file_number);
    /* if you call this you'd better be sure file_number is not 
     * already in use! */
    e = new_StoreEntry(STORE_ENTRY_WITHOUT_MEMOBJ, NULL);
    e->store_status = STORE_OK;
    storeSetMemStatus(e, NOT_IN_MEMORY);
    e->swap_status = SWAPOUT_DONE;
    e->swap_filen = file_number;
    e->swap_dirn = SD->index;
    e->swap_file_sz = swap_file_sz;
    e->lock_count = 0;
    e->lastref = lastref;
    e->timestamp = timestamp;
    e->expires = expires;
    e->lastmod = lastmod;
    e->refcount = refcount;
    e->flags = flags;
    EBIT_SET(e->flags, ENTRY_CACHABLE);
    EBIT_CLR(e->flags, RELEASE_REQUEST);
    EBIT_CLR(e->flags, KEY_PRIVATE);
    e->ping_status = PING_NONE;
    EBIT_CLR(e->flags, ENTRY_VALIDATED);
    storeDiskdDirMapBitSet(SD, e->swap_filen);
    storeHashInsert(e, key);	/* do it after we clear KEY_PRIVATE */
    storeDiskdDirReplAdd(SD, e);
    return e;
}

CBDATA_TYPE(RebuildState);

static void
storeDiskdDirRebuild(SwapDir * sd)
{
    RebuildState *rb;
    int clean = 0;
    int zero = 0;
    FILE *fp;
    EVH *func = NULL;
    CBDATA_INIT_TYPE(RebuildState);
    rb = cbdataAlloc(RebuildState);
    rb->sd = sd;
    rb->speed = opt_foreground_rebuild ? 1 << 30 : 50;
    /*
     * If the swap.state file exists in the cache_dir, then
     * we'll use storeDiskdDirRebuildFromSwapLog(), otherwise we'll
     * use storeDiskdDirRebuildFromDirectory() to open up each file
     * and suck in the meta data.
     */
    fp = storeDiskdDirOpenTmpSwapLog(sd, &clean, &zero);
    if (fp == NULL || zero) {
	if (fp != NULL)
	    fclose(fp);
	func = storeDiskdDirRebuildFromDirectory;
    } else {
	func = storeDiskdDirRebuildFromSwapLogCheckVersion;
	rb->log = fp;
	rb->flags.clean = (unsigned int) clean;
    }
    debug(20, 1) ("Rebuilding storage in %s (%s)\n",
	sd->path, clean ? "CLEAN" : "DIRTY");
    store_dirs_rebuilding++;
    eventAdd("storeRebuild", func, rb, 0.0, 1);
}

static void
storeDiskdDirCloseTmpSwapLog(SwapDir * sd)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    char *swaplog_path = xstrdup(storeDiskdDirSwapLogFile(sd, NULL));
    char *new_path = xstrdup(storeDiskdDirSwapLogFile(sd, ".new"));
    int fd;
    file_close(diskdinfo->swaplog_fd);
    if (xrename(new_path, swaplog_path) < 0) {
	fatal("storeDiskdDirCloseTmpSwapLog: rename failed");
    }
    fd = file_open(swaplog_path, O_WRONLY | O_CREAT | O_BINARY);
    if (fd < 0) {
	debug(50, 1) ("%s: %s\n", swaplog_path, xstrerror());
	fatal("storeDiskdDirCloseTmpSwapLog: Failed to open swap log.");
    }
    safe_free(swaplog_path);
    safe_free(new_path);
    diskdinfo->swaplog_fd = fd;
    debug(47, 3) ("Cache Dir #%d log opened on FD %d\n", sd->index, fd);
}

static void
storeSwapLogDataFree(void *s)
{
    memFree(s, MEM_SWAP_LOG_DATA);
}

static void
storeDiskdWriteSwapLogheader(int fd)
{
    storeSwapLogHeader *hdr = memAllocate(MEM_SWAP_LOG_DATA);
    hdr->op = SWAP_LOG_VERSION;
    hdr->version = 1;
    hdr->record_size = sizeof(storeSwapLogData);
    /* The header size is a full log record to keep some level of backward
     * compatibility even if the actual header is smaller
     */
    file_write(fd,
	-1,
	hdr,
	sizeof(storeSwapLogData),
	NULL,
	NULL,
	(FREE *) storeSwapLogDataFree);
}

static FILE *
storeDiskdDirOpenTmpSwapLog(SwapDir * sd, int *clean_flag, int *zero_flag)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    char *swaplog_path = xstrdup(storeDiskdDirSwapLogFile(sd, NULL));
    char *clean_path = xstrdup(storeDiskdDirSwapLogFile(sd, ".last-clean"));
    char *new_path = xstrdup(storeDiskdDirSwapLogFile(sd, ".new"));
    struct stat log_sb;
    struct stat clean_sb;
    FILE *fp;
    int fd;
    if (stat(swaplog_path, &log_sb) < 0) {
	debug(47, 1) ("Cache Dir #%d: No log file\n", sd->index);
	safe_free(swaplog_path);
	safe_free(clean_path);
	safe_free(new_path);
	return NULL;
    }
    *zero_flag = log_sb.st_size == 0 ? 1 : 0;
    /* close the existing write-only FD */
    if (diskdinfo->swaplog_fd >= 0)
	file_close(diskdinfo->swaplog_fd);
    /* open a write-only FD for the new log */
    fd = file_open(new_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
    if (fd < 0) {
	debug(50, 1) ("%s: %s\n", new_path, xstrerror());
	fatal("storeDirOpenTmpSwapLog: Failed to open swap log.");
    }
    diskdinfo->swaplog_fd = fd;
    storeDiskdWriteSwapLogheader(fd);
    /* open a read-only stream of the old log */
    fp = fopen(swaplog_path, "rb");
    if (fp == NULL) {
	debug(50, 0) ("%s: %s\n", swaplog_path, xstrerror());
	fatal("Failed to open swap log for reading");
    }
    memset(&clean_sb, '\0', sizeof(struct stat));
    if (stat(clean_path, &clean_sb) < 0)
	*clean_flag = 0;
    else if (clean_sb.st_mtime < log_sb.st_mtime)
	*clean_flag = 0;
    else
	*clean_flag = 1;
    safeunlink(clean_path, 1);
    safe_free(swaplog_path);
    safe_free(clean_path);
    safe_free(new_path);
    return fp;
}

struct _clean_state {
    char *cur;
    char *new;
    char *cln;
    char *outbuf;
    int outbuf_offset;
    int fd;
    RemovalPolicyWalker *walker;
};

#define CLEAN_BUF_SZ 16384
/*
 * Begin the process to write clean cache state.  For DISKD this means
 * opening some log files and allocating write buffers.  Return 0 if
 * we succeed, and assign the 'func' and 'data' return pointers.
 */
static int
storeDiskdDirWriteCleanStart(SwapDir * sd)
{
    struct _clean_state *state = xcalloc(1, sizeof(*state));
#if HAVE_FCHMOD
    struct stat sb;
#endif
    sd->log.clean.write = NULL;
    sd->log.clean.state = NULL;
    state->new = xstrdup(storeDiskdDirSwapLogFile(sd, ".clean"));
    state->cur = xstrdup(storeDiskdDirSwapLogFile(sd, NULL));
    state->cln = xstrdup(storeDiskdDirSwapLogFile(sd, ".last-clean"));
    state->outbuf = xcalloc(CLEAN_BUF_SZ, 1);
    state->outbuf_offset = 0;
    state->walker = sd->repl->WalkInit(sd->repl);
    unlink(state->cln);
    state->fd = file_open(state->new, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
    if (state->fd < 0) {
	xfree(state->new);
	xfree(state->cur);
	xfree(state->cln);
	xfree(state);
	return -1;
    }
    debug(20, 3) ("storeDirWriteCleanLogs: opened %s, FD %d\n",
	state->new, state->fd);
    storeDiskdWriteSwapLogheader(state->fd);
#if HAVE_FCHMOD
    if (stat(state->cur, &sb) == 0)
	fchmod(state->fd, sb.st_mode);
#endif
    sd->log.clean.write = storeDiskdDirWriteCleanEntry;
    sd->log.clean.state = state;
    return 0;
}

/*
 * Get the next entry that is a candidate for clean log writing
 */
const StoreEntry *
storeDiskdDirCleanLogNextEntry(SwapDir * sd)
{
    const StoreEntry *entry = NULL;
    struct _clean_state *state = sd->log.clean.state;
    if (state->walker)
	entry = state->walker->Next(state->walker);
    return entry;
}

/*
 * "write" an entry to the clean log file.
 */
static void
storeDiskdDirWriteCleanEntry(SwapDir * sd, const StoreEntry * e)
{
    storeSwapLogData s;
    static size_t ss = sizeof(storeSwapLogData);
    struct _clean_state *state = sd->log.clean.state;
    memset(&s, '\0', ss);
    s.op = (char) SWAP_LOG_ADD;
    s.swap_filen = e->swap_filen;
    s.timestamp = e->timestamp;
    s.lastref = e->lastref;
    s.expires = e->expires;
    s.lastmod = e->lastmod;
    s.swap_file_sz = e->swap_file_sz;
    s.refcount = e->refcount;
    s.flags = e->flags;
    xmemcpy(&s.key, e->hash.key, SQUID_MD5_DIGEST_LENGTH);
    xmemcpy(state->outbuf + state->outbuf_offset, &s, ss);
    state->outbuf_offset += ss;
    /* buffered write */
    if (state->outbuf_offset + ss > CLEAN_BUF_SZ) {
	if (FD_WRITE_METHOD(state->fd, state->outbuf, state->outbuf_offset) < 0) {
	    debug(50, 0) ("storeDirWriteCleanLogs: %s: write: %s\n",
		state->new, xstrerror());
	    debug(20, 0) ("storeDirWriteCleanLogs: Current swap logfile not replaced.\n");
	    file_close(state->fd);
	    state->fd = -1;
	    unlink(state->new);
	    safe_free(state);
	    sd->log.clean.state = NULL;
	    sd->log.clean.write = NULL;
	    return;
	}
	state->outbuf_offset = 0;
    }
}

static void
storeDiskdDirWriteCleanDone(SwapDir * sd)
{
    struct _clean_state *state = sd->log.clean.state;
    if (NULL == state)
	return;
    if (state->fd < 0)
	return;
    state->walker->Done(state->walker);
    if (FD_WRITE_METHOD(state->fd, state->outbuf, state->outbuf_offset) < 0) {
	debug(50, 0) ("storeDirWriteCleanLogs: %s: write: %s\n",
	    state->new, xstrerror());
	debug(20, 0) ("storeDirWriteCleanLogs: Current swap logfile "
	    "not replaced.\n");
	file_close(state->fd);
	state->fd = -1;
	unlink(state->new);
    }
    safe_free(state->outbuf);
    /*
     * You can't rename open files on Microsoft "operating systems"
     * so we have to close before renaming.
     */
    storeDiskdDirCloseSwapLog(sd);
    /* rename */
    if (state->fd >= 0) {
#ifdef _SQUID_OS2_
	file_close(state->fd);
	state->fd = -1;
#endif
	xrename(state->new, state->cur);
    }
    /* touch a timestamp file if we're not still validating */
    if (store_dirs_rebuilding)
	(void) 0;
    else if (state->fd < 0)
	(void) 0;
    else
	file_close(file_open(state->cln, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY));
    /* close */
    safe_free(state->cur);
    safe_free(state->new);
    safe_free(state->cln);
    if (state->fd >= 0)
	file_close(state->fd);
    state->fd = -1;
    safe_free(state);
    sd->log.clean.state = NULL;
    sd->log.clean.write = NULL;
}

static void
storeDiskdDirSwapLog(const SwapDir * sd, const StoreEntry * e, int op)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    storeSwapLogData *s = memAllocate(MEM_SWAP_LOG_DATA);
    s->op = (char) op;
    s->swap_filen = e->swap_filen;
    s->timestamp = e->timestamp;
    s->lastref = e->lastref;
    s->expires = e->expires;
    s->lastmod = e->lastmod;
    s->swap_file_sz = e->swap_file_sz;
    s->refcount = e->refcount;
    s->flags = e->flags;
    xmemcpy(s->key, e->hash.key, SQUID_MD5_DIGEST_LENGTH);
    file_write(diskdinfo->swaplog_fd,
	-1,
	s,
	sizeof(storeSwapLogData),
	NULL,
	NULL,
	(FREE *) storeSwapLogDataFree);
}

static void
storeDiskdDirNewfs(SwapDir * sd)
{
    debug(47, 3) ("Creating swap space in %s\n", sd->path);
    storeDiskdDirCreateDirectory(sd->path, 0);
    storeDiskdDirCreateSwapSubDirs(sd);
}

static int
rev_int_sort(const void *A, const void *B)
{
    const int *i1 = A;
    const int *i2 = B;
    return *i2 - *i1;
}

static int
storeDiskdDirClean(int swap_index)
{
    DIR *dp = NULL;
    struct dirent *de = NULL;
    LOCAL_ARRAY(char, p1, MAXPATHLEN + 1);
    LOCAL_ARRAY(char, p2, MAXPATHLEN + 1);
#if USE_TRUNCATE
    struct stat sb;
#endif
    int files[20];
    int swapfileno;
    int fn;			/* same as swapfileno, but with dirn bits set */
    int n = 0;
    int k = 0;
    int N0, N1, N2;
    int D0, D1, D2;
    SwapDir *SD;
    diskdinfo_t *diskdinfo;
    N0 = n_diskd_dirs;
    D0 = diskd_dir_index[swap_index % N0];
    SD = &Config.cacheSwap.swapDirs[D0];
    diskdinfo = SD->fsdata;
    N1 = diskdinfo->l1;
    D1 = (swap_index / N0) % N1;
    N2 = diskdinfo->l2;
    D2 = ((swap_index / N0) / N1) % N2;
    snprintf(p1, SQUID_MAXPATHLEN, "%s/%02X/%02X",
	Config.cacheSwap.swapDirs[D0].path, D1, D2);
    debug(36, 3) ("storeDirClean: Cleaning directory %s\n", p1);
    dp = opendir(p1);
    if (dp == NULL) {
	if (errno == ENOENT) {
	    debug(36, 0) ("storeDirClean: WARNING: Creating %s\n", p1);
	    if (mkdir(p1, 0777) == 0)
		return 0;
	}
	debug(50, 0) ("storeDirClean: %s: %s\n", p1, xstrerror());
	safeunlink(p1, 1);
	return 0;
    }
    while ((de = readdir(dp)) != NULL && k < 20) {
	if (sscanf(de->d_name, "%X", &swapfileno) != 1)
	    continue;
	fn = swapfileno;	/* XXX should remove this cruft ! */
	if (storeDiskdDirValidFileno(SD, fn, 1))
	    if (storeDiskdDirMapBitTest(SD, fn))
		if (storeDiskdFilenoBelongsHere(fn, D0, D1, D2))
		    continue;
#if USE_TRUNCATE
	if (!stat(de->d_name, &sb))
	    if (sb.st_size == 0)
		continue;
#endif
	files[k++] = swapfileno;
    }
    closedir(dp);
    if (k == 0)
	return 0;
    qsort(files, k, sizeof(int), rev_int_sort);
    if (k > 10)
	k = 10;
    for (n = 0; n < k; n++) {
	debug(36, 3) ("storeDirClean: Cleaning file %08X\n", files[n]);
	snprintf(p2, MAXPATHLEN + 1, "%s/%08X", p1, files[n]);
#if USE_TRUNCATE
	truncate(p2, 0);
#else
	safeunlink(p2, 0);
#endif
	statCounter.swap.files_cleaned++;
    }
    debug(36, 3) ("Cleaned %d unused files from %s\n", k, p1);
    return k;
}

static void
storeDiskdDirCleanEvent(void *unused)
{
    static int swap_index = 0;
    int i;
    int j = 0;
    int n = 0;
    /*
     * Assert that there are DISKD cache_dirs configured, otherwise
     * we should never be called.
     */
    assert(n_diskd_dirs);
    if (NULL == diskd_dir_index) {
	SwapDir *sd;
	diskdinfo_t *diskdinfo;
	/*
	 * Initialize the little array that translates DISKD cache_dir
	 * number into the Config.cacheSwap.swapDirs array index.
	 */
	diskd_dir_index = xcalloc(n_diskd_dirs, sizeof(*diskd_dir_index));
	for (i = 0, n = 0; i < Config.cacheSwap.n_configured; i++) {
	    sd = &Config.cacheSwap.swapDirs[i];
	    if (!storeDiskdDirIs(sd))
		continue;
	    diskd_dir_index[n++] = i;
	    diskdinfo = sd->fsdata;
	    j += (diskdinfo->l1 * diskdinfo->l2);
	}
	assert(n == n_diskd_dirs);
	/*
	 * Start the storeDiskdDirClean() swap_index with a random
	 * value.  j equals the total number of DISKD level 2
	 * swap directories
	 */
	swap_index = (int) (squid_random() % j);
    }
    if (0 == store_dirs_rebuilding) {
	n = storeDiskdDirClean(swap_index);
	swap_index++;
    }
    eventAdd("storeDirClean", storeDiskdDirCleanEvent, NULL,
	15.0 * exp(-0.25 * n), 1);
}

static int
storeDiskdDirIs(SwapDir * sd)
{
    if (strncmp(sd->type, "diskd", 3) == 0)
	return 1;
    return 0;
}

/*
 * Does swapfile number 'fn' belong in cachedir #F0,
 * level1 dir #F1, level2 dir #F2?
 */
static int
storeDiskdFilenoBelongsHere(int fn, int F0, int F1, int F2)
{
    int D1, D2;
    int L1, L2;
    int filn = fn;
    diskdinfo_t *diskdinfo;
    assert(F0 < Config.cacheSwap.n_configured);
    diskdinfo = Config.cacheSwap.swapDirs[F0].fsdata;
    L1 = diskdinfo->l1;
    L2 = diskdinfo->l2;
    D1 = ((filn / L2) / L2) % L1;
    if (F1 != D1)
	return 0;
    D2 = (filn / L2) % L2;
    if (F2 != D2)
	return 0;
    return 1;
}

int
storeDiskdDirValidFileno(SwapDir * SD, sfileno filn, int flag)
{
    diskdinfo_t *diskdinfo = SD->fsdata;
    if (filn < 0)
	return 0;
    /*
     * If flag is set it means out-of-range file number should
     * be considered invalid.
     */
    if (flag)
	if (filn > diskdinfo->map->max_n_files)
	    return 0;
    return 1;
}

void
storeDiskdDirMaintain(SwapDir * SD)
{
    diskdinfo_t *diskdinfo = SD->fsdata;
    StoreEntry *e = NULL;
    int removed = 0;
    int max_scan;
    int max_remove;
    double f;
    RemovalPurgeWalker *walker;
    /* We can't delete objects while rebuilding swap */
    if (store_dirs_rebuilding) {
	return;
    } else {
	f = (double) (SD->cur_size - SD->low_size) / (SD->max_size - SD->low_size);
	f = f < 0.0 ? 0.0 : f > 1.0 ? 1.0 : f;
	max_scan = (int) (f * 400.0 + 100.0);
	max_remove = (int) (f * 70.0 + 10.0);
	/*
	 * This is kinda cheap, but so we need this priority hack?
	 */
    }
    debug(20, 3) ("storeMaintainSwapSpace: f=%f, max_scan=%d, max_remove=%d\n", f, max_scan, max_remove);
    walker = SD->repl->PurgeInit(SD->repl, max_scan);
    while (1) {
	if (SD->cur_size < SD->low_size && diskdinfo->map->n_files_in_map < FILEMAP_MAX)
	    break;
	if (removed >= max_remove)
	    break;
	e = walker->Next(walker);
	if (!e)
	    break;		/* no more objects */
	removed++;
	storeRelease(e);
    }
    walker->Done(walker);
    debug(20, (removed ? 2 : 3)) ("storeDiskdDirMaintain: %s removed %d/%d f=%.03f max_scan=%d\n",
	SD->path, removed, max_remove, f, max_scan);
}

/*
 * storeDiskdDirCheckObj
 *
 * This routine is called by storeDirSelectSwapDir to see if the given
 * object is able to be stored on this filesystem. DISKD filesystems will
 * happily store anything as long as the LRU time isn't too small.
 */
int
storeDiskdDirCheckObj(SwapDir * SD, const StoreEntry * e)
{
    diskdinfo_t *diskdinfo = SD->fsdata;
    /* Check the queue length */
    if (diskdinfo->away >= diskdinfo->magic1)
	return 0;
    return 1;
}

int
storeDiskdDirCheckLoadAv(SwapDir * SD, store_op_t op)
{
    diskdinfo_t *diskdinfo = SD->fsdata;
    /* Calculate the storedir load relative to magic2 on a scale of 0 .. 1000 */
    /* the parse function guarantees magic2 is positive */
    if (diskdinfo->away >= diskdinfo->magic1)
	return -1;
    return DISKD_LOAD_BASE + (diskdinfo->away * DISKD_LOAD_QUEUE_WEIGHT / diskdinfo->magic2);
}

/*
 * storeDiskdDirRefObj
 *
 * This routine is called whenever an object is referenced, so we can
 * maintain replacement information within the storage fs.
 */
void
storeDiskdDirRefObj(SwapDir * SD, StoreEntry * e)
{
    debug(47, 3) ("storeDiskdDirRefObj: referencing %p %d/%d\n", e, e->swap_dirn,
	e->swap_filen);
    if (SD->repl->Referenced)
	SD->repl->Referenced(SD->repl, e, &e->repl);
}

/*
 * storeDiskdDirUnrefObj
 * This routine is called whenever the last reference to an object is
 * removed, to maintain replacement information within the storage fs.
 */
void
storeDiskdDirUnrefObj(SwapDir * SD, StoreEntry * e)
{
    debug(47, 3) ("storeDiskdDirUnrefObj: referencing %p %d/%d\n", e,
	e->swap_dirn, e->swap_filen);
    if (SD->repl->Dereferenced)
	SD->repl->Dereferenced(SD->repl, e, &e->repl);
}

/*
 * storeDiskdDirUnlinkFile
 *
 * This is a *synchronous* unlink which is currently used in the rebuild
 * process. This is bad, but it'll have to stay until the dir rebuild
 * uses storeDiskdUnlink() ..
 */
void
storeDiskdDirUnlinkFile(SwapDir * SD, sfileno f)
{
    debug(79, 3) ("storeDiskdDirUnlinkFile: unlinking fileno %08X\n", f);
    /* storeDiskdDirMapBitReset(SD, f); */
#if USE_UNLINKD
    unlinkdUnlink(storeDiskdDirFullPath(SD, f, NULL));
#elif USE_TRUNCATE
    truncate(storeDiskdDirFullPath(SD, f, NULL), 0);
#else
    unlink(storeDiskdDirFullPath(SD, f, NULL));
#endif
}

/*
 * Add and remove the given StoreEntry from the replacement policy in
 * use.
 */

void
storeDiskdDirReplAdd(SwapDir * SD, StoreEntry * e)
{
    debug(20, 4) ("storeDiskdDirReplAdd: added node %p to dir %d\n", e,
	SD->index);
    SD->repl->Add(SD->repl, e, &e->repl);
}


void
storeDiskdDirReplRemove(StoreEntry * e)
{
    SwapDir *SD;
    if (e->swap_dirn < 0)
	return;
    SD = INDEXSD(e->swap_dirn);
    debug(20, 4) ("storeDiskdDirReplRemove: remove node %p from dir %d\n", e,
	SD->index);
    SD->repl->Remove(SD->repl, e, &e->repl);
}



/*
 * SHM manipulation routines
 */

void *
storeDiskdShmGet(SwapDir * sd, int *shm_offset)
{
    char *buf = NULL;
    diskdinfo_t *diskdinfo = sd->fsdata;
    int i;
    for (i = 0; i < diskdinfo->shm.nbufs; i++) {
	if (CBIT_TEST(diskdinfo->shm.inuse_map, i))
	    continue;
	CBIT_SET(diskdinfo->shm.inuse_map, i);
	*shm_offset = i * SHMBUF_BLKSZ;
	buf = diskdinfo->shm.buf + (*shm_offset);
	break;
    }
    assert(buf);
    assert(buf >= diskdinfo->shm.buf);
    assert(buf < diskdinfo->shm.buf + (diskdinfo->shm.nbufs * SHMBUF_BLKSZ));
    diskd_stats.shmbuf_count++;
    if (diskd_stats.max_shmuse < diskd_stats.shmbuf_count)
	diskd_stats.max_shmuse = diskd_stats.shmbuf_count;
    return buf;
}

void
storeDiskdShmPut(SwapDir * sd, int offset)
{
    int i;
    diskdinfo_t *diskdinfo = sd->fsdata;
    assert(offset >= 0);
    assert(offset < diskdinfo->shm.nbufs * SHMBUF_BLKSZ);
    i = offset / SHMBUF_BLKSZ;
    assert(i < diskdinfo->shm.nbufs);
    assert(CBIT_TEST(diskdinfo->shm.inuse_map, i));
    CBIT_CLR(diskdinfo->shm.inuse_map, i);
    diskd_stats.shmbuf_count--;
}




/* ========== LOCAL FUNCTIONS ABOVE, GLOBAL FUNCTIONS BELOW ========== */

void
storeDiskdDirStats(SwapDir * SD, StoreEntry * sentry)
{
    diskdinfo_t *diskdinfo = SD->fsdata;
#ifdef HAVE_STATVFS
    fsblkcnt_t totl_kb = 0;
    fsblkcnt_t free_kb = 0;
    fsfilcnt_t totl_in = 0;
    fsfilcnt_t free_in = 0;
#else
    int totl_kb = 0;
    int free_kb = 0;
    int totl_in = 0;
    int free_in = 0;
#endif
    int x;
    storeAppendPrintf(sentry, "First level subdirectories: %d\n", diskdinfo->l1);
    storeAppendPrintf(sentry, "Second level subdirectories: %d\n", diskdinfo->l2);
    storeAppendPrintf(sentry, "Maximum Size: %d KB\n", SD->max_size);
    storeAppendPrintf(sentry, "Current Size: %d KB\n", SD->cur_size);
    storeAppendPrintf(sentry, "Percent Used: %0.2f%%\n",
	100.0 * SD->cur_size / SD->max_size);
    storeAppendPrintf(sentry, "Current load metric: %d / %d\n", storeDiskdDirCheckLoadAv(SD, ST_OP_CREATE), MAX_LOAD_VALUE);
    storeAppendPrintf(sentry, "Filemap bits in use: %d of %d (%d%%)\n",
	diskdinfo->map->n_files_in_map, diskdinfo->map->max_n_files,
	percent(diskdinfo->map->n_files_in_map, diskdinfo->map->max_n_files));
    x = storeDirGetUFSStats(SD->path, &totl_kb, &free_kb, &totl_in, &free_in);
    if (0 == x) {
#ifdef HAVE_STATVFS
	storeAppendPrintf(sentry, "Filesystem Space in use: %" PRIu64 "/%" PRIu64 " KB (%.0f%%)\n",
	    (uint64_t) (totl_kb - free_kb),
	    (uint64_t) totl_kb,
	    dpercent(totl_kb - free_kb, totl_kb));
	storeAppendPrintf(sentry, "Filesystem Inodes in use: %" PRIu64 "/%" PRIu64 " (%.0f%%)\n",
	    (uint64_t) (totl_in - free_in),
	    (uint64_t) totl_in,
	    dpercent(totl_in - free_in, totl_in));
#else
	storeAppendPrintf(sentry, "Filesystem Space in use: %d/%d KB (%d%%)\n",
	    totl_kb - free_kb,
	    totl_kb,
	    percent(totl_kb - free_kb, totl_kb));
	storeAppendPrintf(sentry, "Filesystem Inodes in use: %d/%d (%d%%)\n",
	    totl_in - free_in,
	    totl_in,
	    percent(totl_in - free_in, totl_in));
#endif
    }
    storeAppendPrintf(sentry, "Flags:");
    if (SD->flags.selected)
	storeAppendPrintf(sentry, " SELECTED");
    if (SD->flags.read_only)
	storeAppendPrintf(sentry, " READ-ONLY");
    storeAppendPrintf(sentry, "\n");
    storeAppendPrintf(sentry, "Pending operations: %d\n", diskdinfo->away);
}

static void
storeDiskdDirParseQ1(SwapDir * sd, const char *name, const char *value, int reconfiguring)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    int old_magic1 = diskdinfo->magic1;
    diskdinfo->magic1 = atoi(value);
    if (!reconfiguring)
	return;
    if (old_magic1 < diskdinfo->magic1) {
	/*
	 * This is because shm.nbufs is computed at startup, when
	 * we call shmget().  We can't increase the Q1/Q2 parameters
	 * beyond their initial values because then we might have
	 * more "Q2 messages" than shared memory chunks, and this
	 * will cause an assertion in storeDiskdShmGet().
	 */
	debug(3, 1) ("WARNING: cannot increase cache_dir '%s' Q1 value while Squid is running.\n", sd->path);
	diskdinfo->magic1 = old_magic1;
	return;
    }
    if (old_magic1 != diskdinfo->magic1)
	debug(3, 1) ("cache_dir '%s' new Q1 value '%d'\n",
	    sd->path, diskdinfo->magic1);
}

static void
storeDiskdDirDumpQ1(StoreEntry * e, const char *option, SwapDir * sd)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    storeAppendPrintf(e, " Q1=%d", diskdinfo->magic1);
}

static void
storeDiskdDirParseQ2(SwapDir * sd, const char *name, const char *value, int reconfiguring)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    int old_magic2 = diskdinfo->magic2;
    diskdinfo->magic2 = atoi(value);
    if (!reconfiguring)
	return;
    if (old_magic2 < diskdinfo->magic2) {
	/* See comments in Q1 function above */
	debug(3, 1) ("WARNING: cannot increase cache_dir '%s' Q2 value while Squid is running.\n", sd->path);
	diskdinfo->magic2 = old_magic2;
	return;
    }
    if (old_magic2 != diskdinfo->magic2)
	debug(3, 1) ("cache_dir '%s' new Q2 value '%d'\n",
	    sd->path, diskdinfo->magic2);
}

static void
storeDiskdDirDumpQ2(StoreEntry * e, const char *option, SwapDir * sd)
{
    diskdinfo_t *diskdinfo = sd->fsdata;
    storeAppendPrintf(e, " Q2=%d", diskdinfo->magic2);
}

static struct cache_dir_option options[] =
{
#if NOT_YET
    {"L1", storeDiskdDirParseL1, storeDiskdDirDumpL1},
    {"L2", storeDiskdDirParseL2, storeDiskdDirDumpL2},
#endif
    {"Q1", storeDiskdDirParseQ1, storeDiskdDirDumpQ1},
    {"Q2", storeDiskdDirParseQ2, storeDiskdDirDumpQ2},
    {NULL, NULL}
};

/*
 * storeDiskdDirReconfigure
 *
 * This routine is called when the given swapdir needs reconfiguring 
 */
static void
storeDiskdDirReconfigure(SwapDir * sd, int index, char *path)
{
    int i;
    int size;
    int l1;
    int l2;

    i = GetInteger();
    size = i << 10;		/* Mbytes to kbytes */
    if (size <= 0)
	fatal("storeDiskdDirReconfigure: invalid size value");
    i = GetInteger();
    l1 = i;
    if (l1 <= 0)
	fatal("storeDiskdDirReconfigure: invalid level 1 directories value");
    i = GetInteger();
    l2 = i;
    if (l2 <= 0)
	fatal("storeDiskdDirReconfigure: invalid level 2 directories value");

    /* just reconfigure it */
    if (size == sd->max_size)
	debug(3, 1) ("Cache dir '%s' size remains unchanged at %d KB\n",
	    path, size);
    else
	debug(3, 1) ("Cache dir '%s' size changed to %d KB\n",
	    path, size);
    sd->max_size = size;
    parse_cachedir_options(sd, options, 1);
}

void
storeDiskdDirDump(StoreEntry * entry, SwapDir * s)
{
    diskdinfo_t *diskdinfo = s->fsdata;
    storeAppendPrintf(entry, " %d %d %d",
	s->max_size >> 10,
	diskdinfo->l1,
	diskdinfo->l2);
    dump_cachedir_options(entry, options, s);
}

/*
 * Only "free" the filesystem specific stuff here
 */
static void
storeDiskdDirFree(SwapDir * s)
{
    diskdinfo_t *diskdinfo = s->fsdata;
    if (diskdinfo->swaplog_fd > -1) {
	file_close(diskdinfo->swaplog_fd);
	diskdinfo->swaplog_fd = -1;
    }
    filemapFreeMemory(diskdinfo->map);
    xfree(diskdinfo);
    s->fsdata = NULL;		/* Will aid debugging... */

}

char *
storeDiskdDirFullPath(SwapDir * SD, sfileno filn, char *fullpath)
{
    LOCAL_ARRAY(char, fullfilename, SQUID_MAXPATHLEN);
    diskdinfo_t *diskdinfo = SD->fsdata;
    int L1 = diskdinfo->l1;
    int L2 = diskdinfo->l2;
    if (!fullpath)
	fullpath = fullfilename;
    fullpath[0] = '\0';
    snprintf(fullpath, SQUID_MAXPATHLEN, "%s/%02X/%02X/%08X",
	SD->path,
	((filn / L2) / L2) % L1,
	(filn / L2) % L2,
	filn);
    return fullpath;
}

/*
 * storeDiskdCleanupDoubleCheck
 *
 * This is called by storeCleanup() if -S was given on the command line.
 */
static int
storeDiskdCleanupDoubleCheck(SwapDir * sd, StoreEntry * e)
{
    struct stat sb;
    if (stat(storeDiskdDirFullPath(sd, e->swap_filen, NULL), &sb) < 0) {
	debug(20, 0) ("storeDiskdCleanupDoubleCheck: MISSING SWAP FILE\n");
	debug(20, 0) ("storeDiskdCleanupDoubleCheck: FILENO %08X\n", e->swap_filen);
	debug(20, 0) ("storeDiskdCleanupDoubleCheck: PATH %s\n",
	    storeDiskdDirFullPath(sd, e->swap_filen, NULL));
	storeEntryDump(e, 0);
	return -1;
    }
    if (e->swap_file_sz != sb.st_size) {
	debug(20, 0) ("storeDiskdCleanupDoubleCheck: SIZE MISMATCH\n");
	debug(20, 0) ("storeDiskdCleanupDoubleCheck: FILENO %08X\n", e->swap_filen);
	debug(20, 0) ("storeDiskdCleanupDoubleCheck: PATH %s\n",
	    storeDiskdDirFullPath(sd, e->swap_filen, NULL));
	debug(20, 0) ("storeDiskdCleanupDoubleCheck: ENTRY SIZE: %ld, FILE SIZE: %ld\n",
	    (long int) e->swap_file_sz, (long int) sb.st_size);
	storeEntryDump(e, 0);
	return -1;
    }
    return 0;
}

/*
 * storeDiskdDirParse
 *
 * Called when a *new* fs is being setup.
 */
static void
storeDiskdDirParse(SwapDir * sd, int index, char *path)
{
    int i;
    int size;
    int l1;
    int l2;
    diskdinfo_t *diskdinfo;

    i = GetInteger();
    size = i << 10;		/* Mbytes to kbytes */
    if (size <= 0)
	fatal("storeDiskdDirParse: invalid size value");
    i = GetInteger();
    l1 = i;
    if (l1 <= 0)
	fatal("storeDiskdDirParse: invalid level 1 directories value");
    i = GetInteger();
    l2 = i;
    if (l2 <= 0)
	fatal("storeDiskdDirParse: invalid level 2 directories value");

    sd->fsdata = diskdinfo = xcalloc(1, sizeof(*diskdinfo));
    sd->index = index;
    sd->path = xstrdup(path);
    sd->max_size = size;
    diskdinfo->l1 = l1;
    diskdinfo->l2 = l2;
    diskdinfo->swaplog_fd = -1;
    diskdinfo->map = NULL;	/* Debugging purposes */
    diskdinfo->suggest = 0;
    diskdinfo->magic1 = 64;
    diskdinfo->magic2 = 72;
    sd->checkconfig = storeDiskdCheckConfig;
    sd->init = storeDiskdDirInit;
    sd->newfs = storeDiskdDirNewfs;
    sd->dump = storeDiskdDirDump;
    sd->freefs = storeDiskdDirFree;
    sd->dblcheck = storeDiskdCleanupDoubleCheck;
    sd->statfs = storeDiskdDirStats;
    sd->maintainfs = storeDiskdDirMaintain;
    sd->checkobj = storeDiskdDirCheckObj;
    sd->checkload = storeDiskdDirCheckLoadAv;
    sd->refobj = storeDiskdDirRefObj;
    sd->unrefobj = storeDiskdDirUnrefObj;
    sd->callback = storeDiskdDirCallback;
    sd->sync = storeDiskdDirSync;
    sd->obj.create = storeDiskdCreate;
    sd->obj.open = storeDiskdOpen;
    sd->obj.close = storeDiskdClose;
    sd->obj.read = storeDiskdRead;
    sd->obj.write = storeDiskdWrite;
    sd->obj.unlink = storeDiskdUnlink;
    sd->obj.recycle = storeDiskdRecycle;
    sd->log.open = storeDiskdDirOpenSwapLog;
    sd->log.close = storeDiskdDirCloseSwapLog;
    sd->log.write = storeDiskdDirSwapLog;
    sd->log.clean.start = storeDiskdDirWriteCleanStart;
    sd->log.clean.nextentry = storeDiskdDirCleanLogNextEntry;
    sd->log.clean.done = storeDiskdDirWriteCleanDone;

    parse_cachedir_options(sd, options, 0);

    /* Initialise replacement policy stuff */
    sd->repl = createRemovalPolicy(Config.replPolicy);
}

/*
 * Initial setup / end destruction
 */
static void
storeDiskdDirDone(void)
{
    memPoolDestroy(diskd_state_pool);
    diskd_initialised = 0;
}

void
storeFsSetup_diskd(storefs_entry_t * storefs)
{
    assert(!diskd_initialised);
    storefs->parsefunc = storeDiskdDirParse;
    storefs->reconfigurefunc = storeDiskdDirReconfigure;
    storefs->donefunc = storeDiskdDirDone;
    diskd_state_pool = memPoolCreate("DISKD IO State data", sizeof(diskdstate_t));
    memset(&diskd_stats, '\0', sizeof(diskd_stats));
    cachemgrRegister("diskd", "DISKD Stats", storeDiskdStats, 0, 1);
    debug(79, 1) ("diskd started\n");
    diskd_initialised = 1;
}
