/*
 * Copyright (c) 2003-2006 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: fsspace.c,v 1.19 2006/10/05 04:27:37 ca Exp $")

#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/heap.h"
#include "sm/fs.h"
#include "sm/statfs.h"

/* update only every 60s */
#ifndef FS_UPD_TO
# define FS_UPD_TO	60
#endif

typedef struct filesys_S	filesys_T, *filesys_P;

struct filesys_S
{
	dev_t		 fs_dev;	/* unique device id */
	ulong		 fs_kbfree;	/* KB free */
	ulong		 fs_blksize;	/* block size, in bytes */
	time_T		 fs_lastupdate;	/* last time fs_kbfree was updated */
	const char	*fs_path;	/* some path in the FS */
};

struct fs_ctx_S
{
#if MTA_USE_PTHREADS
	pthread_mutex_t	 fsc_mutex;
#endif
	uint		 fsc_cur_entries; /* cur. number of entries in fsc_sys*/
	uint		 fsc_max_entries; /* max. number of entries in fsc_sys*/
	filesys_P	 fsc_sys;	/* array of filesys_T */
};

/*
**  FS_CTX_CLOSE -- close/free FS context
**
**	Parameters:
**		fs_ctx -- FS context
**
**	Returns:
**		SM_SUCCESS
**
**	Last code review: 2005-03-17 18:53:54
**	Last code change:
*/

sm_ret_T
fs_ctx_close(fs_ctx_P fs_ctx)
{
	if (fs_ctx == NULL)
		return SM_SUCCESS;
#if MTA_USE_PTHREADS
	(void) pthread_mutex_destroy(&fs_ctx->fsc_mutex);
#endif
	SM_FREE(fs_ctx->fsc_sys);
	SM_FREE_SIZE(fs_ctx, sizeof(*fs_ctx));
	return SM_SUCCESS;
}

/*
**  FS_CTX_OPEN -- create a FS context
**
**	Parameters:
**		entries -- maximum number of entries in the FS context
**		fs_ctx -- FS context
**
**	Returns:
**		usual sm_error code
**
**	Side Effects: none on error
**
**	Last code review: 2005-03-17 19:00:45
**	Last code change: 2005-03-17 18:59:18
*/

sm_ret_T
fs_ctx_open(uint entries, fs_ctx_P *pfs_ctx)
{
	sm_ret_T ret;
	fs_ctx_P fs_ctx;
	size_t s;
#if MTA_USE_PTHREADS
	int r;
#endif

	SM_REQUIRE(pfs_ctx != NULL);
	if (entries < 1)
		return sm_err_perm(EINVAL);
	s = 0;
	fs_ctx = (fs_ctx_P) sm_zalloc(sizeof(*fs_ctx));
	if (fs_ctx == NULL)
		return sm_err_temp(ENOMEM);
#if MTA_USE_PTHREADS
	r = pthread_mutex_init(&fs_ctx->fsc_mutex, SM_PTHREAD_MUTEXATTR);
	if (r != 0)
	{
		ret = sm_err_perm(r);
		goto err;
	}
#endif /* MTA_USE_PTHREADS */
	s = sizeof(*(fs_ctx->fsc_sys)) * entries;
	if (s <= 0 || s <= sizeof(*(fs_ctx->fsc_sys)) || s <= entries)
	{
		ret = sm_err_perm(E2BIG);
		goto error;
	}
	fs_ctx->fsc_sys = (filesys_P) sm_zalloc(s);
	if (fs_ctx->fsc_sys == NULL)
	{
		ret = sm_err_temp(ENOMEM);
		goto error;
	}
	fs_ctx->fsc_max_entries = entries;
	fs_ctx->fsc_cur_entries = 0;
	*pfs_ctx = fs_ctx;
	return SM_SUCCESS;

  error:
#if MTA_USE_PTHREADS
	(void) pthread_mutex_destroy(&fs_ctx->fsc_mutex);
  err:
#endif
	if (fs_ctx != NULL)
	{
		if (s > 0 && fs_ctx->fsc_sys != NULL)
			SM_FREE_SIZE(fs_ctx->fsc_sys, s);
		SM_FREE_SIZE(fs_ctx, sizeof(*fs_ctx));
	}
	return ret;
}

/*
**  FS_UPDATE -- update an FS entry in an FS context
**	(only if last update is "too old")
**
**	Parameters:
**		fs_ctx -- FS context
**		fs_idx -- index in FS
**
**	Returns:
**		usual sm_error code; errno from stat
**
**	Side Effects: none on error
**
**	Last code review: 2005-03-17 21:31:33
**	Last code change: 2005-03-17 18:47:08
*/

static sm_ret_T
fs_update(fs_ctx_P fs_ctx, uint fs_idx)
{
	sm_ret_T ret;
	ulong kbfree;
	time_T nowt;

	SM_IS_FS_CTX(fs_ctx);
	SM_REQUIRE(/*fs_idx >= 0 &&*/ fs_idx < fs_ctx->fsc_max_entries);

	nowt = time(NULLT);
	ret = SM_SUCCESS;
	if ((fs_ctx->fsc_sys)[fs_idx].fs_lastupdate + FS_UPD_TO < nowt)
	{
		ret = freediskspace((fs_ctx->fsc_sys)[fs_idx].fs_path,
			&((fs_ctx->fsc_sys)[fs_idx].fs_blksize), &kbfree);
		if (sm_is_success(ret))
		{
			(fs_ctx->fsc_sys)[fs_idx].fs_kbfree = kbfree;
			(fs_ctx->fsc_sys)[fs_idx].fs_lastupdate = nowt;
		}
	}
	return ret;
}

/*
**  FS_NEW -- add an FS entry to an FS context
**	Allow this function to "grow" fsc_sys??
**
**	Parameters:
**		fs_ctx -- FS context
**		path -- path to FS (must stay available, is NOT copied)
**		pfs_idx -- (pointer to) index in FS (output)
**
**	Returns:
**		usual sm_error code; E2BIG, stat() errno,
**
**	Side Effects: none on error (except if unlock fails)
**
**	Last code review: 2005-03-17 18:50:59
**	Last code change:
*/

sm_ret_T
fs_new(fs_ctx_P fs_ctx, const char *path, int *pfs_idx)
{
	sm_ret_T ret;
	uint u;
#if MTA_USE_PTHREADS
	int r;
#endif
	struct stat st;

	SM_IS_FS_CTX(fs_ctx);
	SM_REQUIRE(pfs_idx != NULL);
#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&fs_ctx->fsc_mutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return sm_err_perm(r);
#endif

	*pfs_idx = -1;
	ret = SM_SUCCESS;
	if (stat(path, &st) < 0)
	{
		ret = sm_err_temp(errno);
		goto error;
	}
	for (u = 0; u < fs_ctx->fsc_cur_entries; ++u)
	{
		if ((fs_ctx->fsc_sys)[u].fs_dev == st.st_dev)
		{
			*pfs_idx = u;
			break;
		}
	}
	if (*pfs_idx == -1)
	{
		if (fs_ctx->fsc_cur_entries >= fs_ctx->fsc_max_entries)
		{
			ret = sm_err_temp(E2BIG);
			goto error;
		}
		u = fs_ctx->fsc_cur_entries;
		(fs_ctx->fsc_sys)[u].fs_path = path;
		(fs_ctx->fsc_sys)[u].fs_dev = st.st_dev;
		(fs_ctx->fsc_sys)[u].fs_kbfree = 0;
		(fs_ctx->fsc_sys)[u].fs_blksize = 0;
		(fs_ctx->fsc_sys)[u].fs_lastupdate = 0;
		ret = fs_update(fs_ctx, u);
		*pfs_idx = u;
		++fs_ctx->fsc_cur_entries;
	}

#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&fs_ctx->fsc_mutex);
	SM_ASSERT(r == 0);
	if (r != 0 && sm_is_success(ret))
		ret = sm_err_perm(r);
#endif
	return ret;

  error:
#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&fs_ctx->fsc_mutex);
	SM_ASSERT(r == 0);
	if (r != 0 && sm_is_success(ret))
		ret = sm_err_perm(r);
#endif
	return ret;
}

/*
**  FS_GETFREE -- get free space in FS
**
**	Parameters:
**		fs_ctx -- FS context
**		fs_idx -- index in FS
**		pkbfree -- (pointer to) free space (KB) (output)
**
**	Returns:
**		usual sm_error code; fs_update()
**
**	Side Effects: none on error (except if unlock fails)
**		ok: may update free space value in fs_ctx
**
**	Locking: locks fs_ctx
**
**	Last code review: 2005-03-17 21:33:09
**	Last code change:
*/

sm_ret_T
fs_getfree(fs_ctx_P fs_ctx, uint fs_idx, ulong *pkbfree)
{
	sm_ret_T ret;
#if MTA_USE_PTHREADS
	int r;
#endif

	SM_IS_FS_CTX(fs_ctx);
	/* SM_REQUIRE(fs_idx >= 0); */
	SM_REQUIRE(pkbfree != NULLPTR);
#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&fs_ctx->fsc_mutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return sm_err_perm(r);
#endif
	SM_REQUIRE(fs_idx < fs_ctx->fsc_max_entries);

	ret = fs_update(fs_ctx, fs_idx);
	if (!sm_is_err(ret))
		*pkbfree = (fs_ctx->fsc_sys)[fs_idx].fs_kbfree;

#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&fs_ctx->fsc_mutex);
	SM_ASSERT(r == 0);
	if (r != 0 && sm_is_success(ret))
		ret = sm_err_perm(r);
#endif
	return ret;
}

/*
**  FS_CHGFREE -- change free space in FS
**
**	Parameters:
**		fs_ctx -- FS context
**		fs_idx -- index in FS
**		chg_free -- change in free space (KB)
**			>0: more free space
**			<0: less free space
**		pkbfree -- (pointer to) free space (KB) (output)
**
**	Returns:
**		SM_SUCCESS except for (un)lock errors
**
**	Locking: locks fs_ctx
**
**	Last code review: 2005-03-17 21:35:25
**	Last code change:
*/

sm_ret_T
fs_chgfree(fs_ctx_P fs_ctx, uint fs_idx, long chg_free, ulong *pkbfree)
{
#if MTA_USE_PTHREADS
	int r;
#endif
	sm_ret_T ret;
	long value;

	SM_IS_FS_CTX(fs_ctx);
	SM_REQUIRE(pkbfree != NULLPTR);
	/* SM_REQUIRE(fs_idx >= 0); */
#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&fs_ctx->fsc_mutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return sm_err_perm(r);
#endif
	SM_REQUIRE(fs_idx < fs_ctx->fsc_max_entries);

	ret = SM_SUCCESS;
	if (chg_free < 0)
	{
		value = 0 - chg_free;
		if (value > (fs_ctx->fsc_sys)[fs_idx].fs_kbfree)
			(fs_ctx->fsc_sys)[fs_idx].fs_kbfree = 0;
		else
			(fs_ctx->fsc_sys)[fs_idx].fs_kbfree += chg_free;
	}
	else if (chg_free > 0)
	{
		value = chg_free + (fs_ctx->fsc_sys)[fs_idx].fs_kbfree;
		if (value < chg_free
		    || value < (fs_ctx->fsc_sys)[fs_idx].fs_kbfree)
			(fs_ctx->fsc_sys)[fs_idx].fs_kbfree = LONG_MAX;
		else
			(fs_ctx->fsc_sys)[fs_idx].fs_kbfree = value;
	}
	*pkbfree = (fs_ctx->fsc_sys)[fs_idx].fs_kbfree;

#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&fs_ctx->fsc_mutex);
	SM_ASSERT(r == 0);
	if (r != 0 && sm_is_success(ret))
		ret = sm_err_perm(r);
#endif
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1