/* * 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; }