/* * Copyright (c) 2004, 2005 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: servid.c,v 1.4 2005/06/02 19:00:36 ca Exp $") #include "sm/error.h" #include "sm/assert.h" #include "sm/memops.h" #include "sm/limits.h" #include "sm/heap.h" #include "sm/queue.h" #include "sm/servid.h" /* ** ID context ** Note: this module doesn't give out 0 as id because 0 is used as ** "free" marker in id_used[]. ** ** id_min is currently "fixed" here in the code, it could be made an ** option or slightly modified if necessary. ** ** Optimization: by default id_max is large and the application (mcp?) ** is expected not to use too many ids, hence it should be possible ** to sequentially return id_min ... id_max without checking whether ** the id has already been issued; this is recorded in id_wrapped. */ struct id_ctx_S { uint id_size; /* currently used size */ uint id_alloc; /* allocated size */ uint id_next; /* next id to use */ uint id_min; /* min id to return */ uint id_max; /* max id to return */ bool id_wrapped; /* exceeded max once? */ uint id_free_idx; /* a free index in array */ uint *id_used; /* array of ids (0: free) */ }; /* ** SM_NEW_ID_CTX -- Create ID context ** ** Parameters: ** size -- number of ids to maintain (> 1) ** maxid -- maximum value of id to issue (0: use default) ** pid_ctx -- (pointer to) ID context (output) ** ** Returns: ** usual sm_error code */ sm_ret_T sm_new_id_ctx(uint size, uint maxid, id_ctx_P *pid_ctx) { sm_ret_T ret; size_t len; id_ctx_P id_ctx; SM_REQUIRE(pid_ctx != NULL); /* SM_REQUIRE(size > 1); */ SM_REQUIRE(maxid == 0 || maxid > size); id_ctx = (id_ctx_P) sm_zalloc(sizeof(*id_ctx)); if (id_ctx == NULL) goto enomem; len = sizeof(*id_ctx->id_used) * size; SM_ASSERT(len >= size); SM_ASSERT(len >= sizeof(*id_ctx->id_used)); id_ctx->id_used = (uint *) sm_zalloc(len); if (id_ctx->id_used == NULL) goto enomem; id_ctx->id_next = 1; id_ctx->id_min = 1; id_ctx->id_max = maxid == 0 ? INT_MAX : maxid; id_ctx->id_wrapped = false; id_ctx->id_free_idx = 0; id_ctx->id_size = size; id_ctx->id_alloc = size; *pid_ctx = id_ctx; return SM_SUCCESS; enomem: ret = sm_err_temp(ENOMEM); if (id_ctx != NULL) sm_free_size(id_ctx, sizeof(*id_ctx)); return ret; } /* ** SM_CHG_ID_CTX -- Change size for ID context ** ** Parameters: ** id_ctx -- ID context ** size -- new number of ids to maintain ** ** Returns: ** usual sm_error code */ sm_ret_T sm_chg_id_ctx(id_ctx_P id_ctx, uint size) { sm_ret_T ret; size_t len; uint *used; SM_REQUIRE(id_ctx != NULL); if (size <= id_ctx->id_size || size <= id_ctx->id_alloc) { id_ctx->id_size = size; return SM_SUCCESS; } len = sizeof(*used) * size; SM_ASSERT(len >= size); SM_ASSERT(len >= sizeof(*id_ctx->id_used)); used = (uint *) sm_zalloc(len); if (used == NULL) goto enomem; len = sizeof(*id_ctx->id_used) * id_ctx->id_size; sm_memcpy(used, id_ctx->id_used, len); len = sizeof(*id_ctx->id_used) * id_ctx->id_alloc; sm_free_size(id_ctx->id_used, len); id_ctx->id_used = used; id_ctx->id_size = size; id_ctx->id_alloc = size; if (id_ctx->id_max != INT_MAX || id_ctx->id_max <= size) id_ctx->id_max = size + 1; return SM_SUCCESS; enomem: ret = sm_err_temp(ENOMEM); return ret; } /* ** SM_DESTROY_ID_CTX -- Destroy ID context ** ** Parameters: ** id_ctx -- ID context ** ** Returns: ** usual sm_error code */ sm_ret_T sm_destroy_id_ctx(id_ctx_P id_ctx) { if (id_ctx == NULL) return SM_SUCCESS; if (id_ctx->id_used != NULL) { size_t len; len = sizeof(*id_ctx->id_used) * id_ctx->id_alloc; sm_free_size(id_ctx->id_used, len); } sm_free_size(id_ctx, sizeof(*id_ctx)); return SM_SUCCESS; } /* ** SM_FIND_ID -- Find ID in array ** ** Parameters: ** id_ctx -- ID context ** id -- ID to check ** ** Returns: ** <0: ID not found ** >=0: index where ID was found */ static int sm_find_id(id_ctx_P id_ctx, uint id) { uint i; SM_REQUIRE(id_ctx != NULL); for (i = 0; i < id_ctx->id_size; i++) { if (id == id_ctx->id_used[i]) return i; } return -1; } /* ** SM_NEW_ID -- Get new ID ** ** Parameters: ** id_ctx -- ID context ** pid -- (pointer to) ID (output) ** ** Returns: ** usual sm_error code */ sm_ret_T sm_new_id(id_ctx_P id_ctx, uint *pid) { uint id; int idx; SM_REQUIRE(id_ctx != NULL); SM_REQUIRE(pid != NULL); id = id_ctx->id_next; if (!id_ctx->id_wrapped) goto found; do { idx = sm_find_id(id_ctx, id); if (idx < 0) goto found; ++id; } while (id <= id_ctx->id_max); id_ctx->id_wrapped = true; id = id_ctx->id_min; do { idx = sm_find_id(id_ctx, id); if (idx < 0) goto found; ++id; } while (id < id_ctx->id_next); return sm_err_perm(EINVAL); found: *pid = id; id_ctx->id_next = id + 1; if (id_ctx->id_next > id_ctx->id_max) { id_ctx->id_next = id_ctx->id_min; id_ctx->id_wrapped = true; } if (id_ctx->id_free_idx >= id_ctx->id_size /* || id_ctx->id_free_idx < 0 ||*/) { idx = sm_find_id(id_ctx, 0); if (idx < 0 || (uint)idx >= id_ctx->id_size) return sm_err_perm(EINVAL); id_ctx->id_free_idx = idx; } SM_ASSERT(/*id_ctx->id_free_idx >= 0 &&*/ id_ctx->id_free_idx < id_ctx->id_size); id_ctx->id_used[id_ctx->id_free_idx] = id; id_ctx->id_free_idx = sm_find_id(id_ctx, 0); return SM_SUCCESS; } /* ** SM_FREE_ID -- Release ID ** ** Parameters: ** id_ctx -- ID context ** id -- ID to release ** ** Returns: ** usual sm_error code */ sm_ret_T sm_free_id(id_ctx_P id_ctx, uint id) { int idx; SM_REQUIRE(id_ctx != NULL); idx = sm_find_id(id_ctx, id); if (idx < 0) return sm_err_perm(EINVAL); id_ctx->id_used[idx] = 0; id_ctx->id_free_idx = idx; return SM_SUCCESS; }