/* * Copyright (c) 2002-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: ibdbr.c,v 1.68 2007/06/18 04:42:31 ca Exp $") #include "sm/error.h" #include "sm/fcntl.h" #include "sm/memops.h" #include "sm/stat.h" #include "sm/time.h" #include "sm/heap.h" #include "sm/assert.h" #include "sm/str.h" #include "sm/io.h" #include "sm/fdset.h" #include "sm/reccom.h" #include "sm/cstr.h" #include "sm/ibdb.h" #include "ibdb.h" /* specify an INCEDB backup on disk */ struct ibdbr_ctx_S { uint ibdbr_version; /* version of this ibdb */ sm_file_T *ibdbr_fp; /* current file */ sm_str_P ibdbr_path; /* pathname of file */ sm_str_P ibdbr_name_rm; /* pathname of file to remove */ char *ibdbr_dir; /* directory of file */ char *ibdbr_base_dir; /* name of base directory */ char *ibdbr_name; /* basename of file */ int ibdbr_mode; /* file mode */ uint32_t ibdbr_sequence; /* current sequence number */ bool ibdbr_first; /* first record? */ uchar *ibdbr_fp_buf; /* buffer for fp if necessary */ }; /* ** IBDBR_CTX_FREE -- free an IBDBR context ** ** Parameters: ** ibdbr_ctx -- INCEDB context ** ** Returns: ** none. */ static void ibdbr_ctx_free(ibdbr_ctx_P ibdbr_ctx) { if (ibdbr_ctx == NULL) return; SM_FREE(ibdbr_ctx->ibdbr_name); SM_FREE(ibdbr_ctx->ibdbr_fp_buf); SM_STR_FREE(ibdbr_ctx->ibdbr_path); SM_STR_FREE(ibdbr_ctx->ibdbr_name_rm); sm_free_size(ibdbr_ctx, sizeof(*ibdbr_ctx)); return; } /* ** IBDBR_CTX_NEW -- allocate new ibdbr context ** ** Parameters: ** base_dir -- name of base directory (can be NULL) ** name -- name of IBDB file ** seq -- initial sequence number ** flags -- flags ** ** Returns: ** INCEDB context (NULL on error) */ static ibdbr_ctx_P ibdbr_ctx_new(const char *base_dir, const char *name, uint32_t seq, uint flags) { size_t l; ibdbr_ctx_P ibdbr_ctx; SM_ASSERT(name != NULL); ibdbr_ctx = (ibdbr_ctx_P) sm_zalloc(sizeof(*ibdbr_ctx)); if (ibdbr_ctx == NULL) return NULL; if (base_dir != NULL && *base_dir != '\0') { size_t lb; lb = strlen(base_dir) + 1; ibdbr_ctx->ibdbr_base_dir = (char *) sm_malloc(lb); if (NULL == ibdbr_ctx->ibdbr_base_dir) goto error; strlcpy(ibdbr_ctx->ibdbr_base_dir, base_dir, lb); } IBDB_DIR(ibdbr_ctx->ibdbr_dir, ibdbr_ctx->ibdbr_base_dir, flags); l = strlen(name) + 1; ibdbr_ctx->ibdbr_name = (char *) sm_malloc(l); if (ibdbr_ctx->ibdbr_name == NULL) goto error; strlcpy(ibdbr_ctx->ibdbr_name, name, l); l += strlen(ibdbr_ctx->ibdbr_dir) + 1 + UINT32_LEN; ibdbr_ctx->ibdbr_path = sm_str_new(NULL, l, l); if (ibdbr_ctx->ibdbr_path == NULL) goto error; crt_ibdb_path(ibdbr_ctx->ibdbr_path, ibdbr_ctx->ibdbr_dir, name, seq); ++l; ibdbr_ctx->ibdbr_name_rm = sm_str_new(NULL, l, l); if (ibdbr_ctx->ibdbr_name_rm == NULL) goto error; ibdbr_ctx->ibdbr_first = true; ibdbr_ctx->ibdbr_sequence = seq; return ibdbr_ctx; error: ibdbr_ctx_free(ibdbr_ctx); return NULL; } /* ** IBDBR_NEXT -- open next ibdb backup on disk ** ** Parameters: ** ibdbr_ctx -- INCEDB context ** ** Returns: ** usual sm_error code (sm_io_{open,close}()) */ static sm_ret_T ibdbr_next(ibdbr_ctx_P ibdbr_ctx) { sm_ret_T ret; sm_file_T *fp; SM_ASSERT(ibdbr_ctx != NULL); ret = sm_io_close(ibdbr_ctx->ibdbr_fp, SM_IO_CF_NONE); ibdbr_ctx->ibdbr_fp = NULL; if (sm_is_err(ret)) return ret; ibdbr_ctx->ibdbr_sequence++; sm_str_clr(ibdbr_ctx->ibdbr_path); crt_ibdb_path(ibdbr_ctx->ibdbr_path, ibdbr_ctx->ibdbr_dir, ibdbr_ctx->ibdbr_name, ibdbr_ctx->ibdbr_sequence); ret = sm_io_open(SmStStdio, sm_str_getdata(ibdbr_ctx->ibdbr_path), ibdbr_ctx->ibdbr_mode, &fp, SM_IO_WHAT_END); if (sm_is_err(ret)) return ret; if (NULL != ibdbr_ctx->ibdbr_fp_buf) { ret = sm_io_setvbuf(fp, ibdbr_ctx->ibdbr_fp_buf, SM_IO_FBF, IBDB_BUF_SIZE); if (sm_is_err(ret)) return ret; } ibdbr_ctx->ibdbr_fp = fp; ibdbr_ctx->ibdbr_first = true; return ret; } /* ** IBDBR_OPEN -- open ibdb backup on disk ** ** Parameters: ** base_dir -- name of base directory (can be NULL) ** name -- base name, will be extended by seq ** mode -- open mode ** seq -- initial sequence number (> 0) ** size -- maximum size (ignored) ** flags -- flags ** pibdbr_ctx -- (pointer to) INCEDB context (output) ** ** Returns: ** usual sm_error code; ENOMEM, */ sm_ret_T ibdbr_open(const char *base_dir, const char *name, int mode, uint32_t seq, size_t size, uint flags, ibdbr_ctx_P *pibdbr_ctx) { sm_ret_T ret; sm_file_T *fp; ibdbr_ctx_P ibdbr_ctx; SM_REQUIRE(pibdbr_ctx != NULL); SM_REQUIRE(seq > 0); ibdbr_ctx = ibdbr_ctx_new(base_dir, name, seq, flags); if (ibdbr_ctx == NULL) return sm_error_temp(SM_EM_IBDB, ENOMEM); ret = sm_io_open(SmStStdio, sm_str_getdata(ibdbr_ctx->ibdbr_path), mode, &fp, SM_IO_WHAT_END); if (sm_is_err(ret)) goto error; if (f_blksize(*fp) < IBDB_MINBUF_SIZE || (f_blksize(*fp) % IBDB_MINBUF_SIZE) != 0) { ibdbr_ctx->ibdbr_fp_buf = sm_malloc(IBDB_BUF_SIZE); if (NULL == ibdbr_ctx->ibdbr_fp_buf) goto error; ret = sm_io_setvbuf(fp, ibdbr_ctx->ibdbr_fp_buf, SM_IO_FBF, IBDB_BUF_SIZE); if (sm_is_err(ret)) goto error; } ibdbr_ctx->ibdbr_fp = fp; ibdbr_ctx->ibdbr_first = true; ibdbr_ctx->ibdbr_mode = mode; *pibdbr_ctx = ibdbr_ctx; return ret; error: ibdbr_ctx_free(ibdbr_ctx); return sm_is_err(ret) ? ret : sm_error_perm(SM_EM_IBDB, ENOMEM); } /* ** IBDBR_HDR_MOD -- read header modification record ** ** Parameters: ** ibdbr_ctx -- INCEDB context ** ibdb_hdrmod -- header modification record ** ** Returns: ** usual sm_error code ** ** Note: ibdb_hdrmod structure must be allocated */ static sm_ret_T ibdbr_hdr_mod(ibdbr_ctx_P ibdbr_ctx, ibdb_hdrmod_P ibdb_hdrmod) { uint32_t n; sm_file_T *fp; sm_ret_T ret; SM_ASSERT(ibdbr_ctx != NULL); SM_ASSERT(ibdb_hdrmod != NULL); fp = ibdbr_ctx->ibdbr_fp; /* transaction ID */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_HM_TAID) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n > SMTP_STID_SIZE) goto error; ret = sm_io_fgetn(fp, (uchar *) ibdb_hdrmod->ibh_ta_id, n); if (sm_is_err(ret)) goto error; ibdb_hdrmod->ibh_ta_id[SMTP_STID_SIZE - 1] = '\0'; /* type */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_HM_TYPE) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != 4) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret)) goto error; ibdb_hdrmod->ibh_type = n; /* position */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_HM_POS) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != 4) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret)) goto error; ibdb_hdrmod->ibh_pos = n; /* header XXX this may exceed the record size??? */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_HM_HDR) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n > SM_MAXHDRLEN) goto error; if (n > 0) { ret = sm_io_fgetncstr(fp, &ibdb_hdrmod->ibh_hdr, n); if (sm_is_err(ret)) goto error; } ret = sm_io_falign(fp, IBDB_REC_SIZE); if (sm_is_err(ret)) goto error; return SM_SUCCESS; error: /* cleanup? */ if (!sm_is_err(ret)) ret = sm_error_perm(SM_EM_IBDB, SM_E_PR_ERR); return ret; } /* ** IBDBR_TA_STATUS -- read transaction status ** ** Parameters: ** ibdbr_ctx -- INCEDB context ** ta -- transaction (sender) data ** pstatus -- (pointer to) transaction status (output) ** ** Returns: ** usual sm_error code ** ** Note: ta structure must be allocated by caller except for cdb_id. */ static sm_ret_T ibdbr_ta_status(ibdbr_ctx_P ibdbr_ctx, ibdb_ta_P ibdb_ta, int *pstatus) { uint32_t n; sm_file_T *fp; sm_ret_T ret; SM_ASSERT(ibdbr_ctx != NULL); SM_ASSERT(ibdb_ta != NULL); fp = ibdbr_ctx->ibdbr_fp; /* transaction status */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_TA_ST) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != 4) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret)) goto error; *pstatus = n; /* transaction ID */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_TAID) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n > SMTP_STID_SIZE) goto error; ret = sm_io_fgetn(fp, (uchar *) ibdb_ta->ibt_ta_id, n); if (sm_is_err(ret)) goto error; ibdb_ta->ibt_ta_id[SMTP_STID_SIZE - 1] = '\0'; /* CDB ID */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_CDBID) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n > CDB_ID_SIZE) goto error; /* todo: don't read CDB if n==1? can callers deal with that?? */ SM_ASSERT(ibdb_ta->ibt_cdb_id == NULL); ret = sm_io_fgetncstr(fp, &ibdb_ta->ibt_cdb_id, n); if (sm_is_err(ret)) goto error; #if 0 /* ??? */ if (n == CDB_ID_SIZE) sm_cstr_wr_elem(ibdb_ta->ibt_cdb_id, CDB_ID_SIZE - 1) = '\0'; #endif /* 0 */ /* nrcpts */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_NRCPTS) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != 4) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret)) goto error; ibdb_ta->ibt_nrcpts = n; /* mail XXX this may exceed the record size??? */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_MAIL) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n > sm_str_getsize(ibdb_ta->ibt_mail_pa)) goto error; ret = sm_io_fgetn(fp, sm_str_getdata(ibdb_ta->ibt_mail_pa), n); if (sm_is_err(ret)) goto error; SM_STR_SETLEN(ibdb_ta->ibt_mail_pa, n); ret = sm_io_falign(fp, IBDB_REC_SIZE); if (sm_is_err(ret)) goto error; return SM_SUCCESS; error: /* cleanup? */ if (!sm_is_err(ret)) ret = sm_error_perm(SM_EM_IBDB, SM_E_PR_ERR); return ret; } /* ** IBDBR_RCPT_STATUS -- read recipient status ** ** Parameters: ** ibdbr_ctx -- INCEDB context ** ibdb_rcpt -- recipient data ** pstatus -- (pointer to) recipient status (output) ** ** Returns: ** usual sm_error code ** ** Note: ibdb_rcpt structure must be allocated by caller. */ static sm_ret_T ibdbr_rcpt_status(ibdbr_ctx_P ibdbr_ctx, ibdb_rcpt_P ibdb_rcpt, int *pstatus) { uint32_t n; sm_file_T *fp; sm_ret_T ret; fp = ibdbr_ctx->ibdbr_fp; /* recipient status */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_RCPT_ST) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != 4) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret)) goto error; *pstatus = n; /* recipient index */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_RCPT_IDX) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != 4) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret)) goto error; ibdb_rcpt->ibr_idx = n; /* transaction ID */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_TAID) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n > SMTP_STID_SIZE) goto error; ret = sm_io_fgetn(fp, (uchar *) ibdb_rcpt->ibr_ta_id, n); if (sm_is_err(ret)) goto error; ibdb_rcpt->ibr_ta_id[SMTP_STID_SIZE - 1] = '\0'; /* rcpt XXX this may exceed the record size??? */ ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n != RT_IBDB_RCPT_PA) goto error; ret = sm_io_fgetuint32(fp, &n); if (sm_is_err(ret) || n > sm_str_getsize(ibdb_rcpt->ibr_pa)) goto error; ret = sm_io_fgetn(fp, sm_str_getdata(ibdb_rcpt->ibr_pa), n); if (sm_is_err(ret)) goto error; SM_STR_SETLEN(ibdb_rcpt->ibr_pa, n); ret = sm_io_falign(fp, IBDB_REC_SIZE); if (sm_is_err(ret)) goto error; return SM_SUCCESS; error: /* cleanup? */ if (!sm_is_err(ret)) ret = sm_error_perm(SM_EM_IBDB, SM_E_PR_ERR); return ret; } /* ** IDBR_GET -- read record from INCEDB ** ** Parameters: ** ibdbr_ctx -- INCEDB context ** ibdb_rcpt -- recipient data ** ibdb_ta -- transaction (sender) data ** ibdb_hdrmod -- header modification data ** pstatus -- (pointer to) transaction status (output) ** ** Returns: ** >0: RT_IBDB_RCPT, RT_IBDB_TA, etc ** <0: usual sm_error code ** ** Locking: must be done by caller. */ sm_ret_T ibdbr_get(ibdbr_ctx_P ibdbr_ctx, ibdb_rcpt_P ibdb_rcpt, ibdb_ta_P ibdb_ta, ibdb_hdrmod_P ibdb_hdrmod, int *pstatus) { uint32_t val; sm_ret_T ret; SM_ASSERT(ibdbr_ctx != NULL); SM_ASSERT(ibdb_rcpt != NULL); SM_ASSERT(ibdb_ta != NULL); again: if (sm_io_eof(ibdbr_ctx->ibdbr_fp)) { /* roll-over */ ret = ibdbr_next(ibdbr_ctx); if (sm_is_err(ret)) goto error; } /* first: make sure one record is in file buffer... */ ret = sm_io_ffill(ibdbr_ctx->ibdbr_fp, IBDB_REC_SIZE); if (sm_is_err(ret)) { if (sm_io_eof(ibdbr_ctx->ibdbr_fp)) { /* roll-over */ ret = ibdbr_next(ibdbr_ctx); if (sm_is_err(ret)) goto error; ret = sm_io_ffill(ibdbr_ctx->ibdbr_fp, IBDB_REC_SIZE); } if (sm_is_err(ret)) { /* ** This may happen if an entry wasn't written ** completely. Just ignore that entry. */ if (SM_IO_EOF == ret || sm_error_perm(SM_EM_IO, EIO) == ret) ret = sm_error_perm(SM_EM_IBDB, ENOENT); goto error; } } /* next: get record type */ ret = sm_io_fgetuint32(ibdbr_ctx->ibdbr_fp, &val); if (sm_is_err(ret)) goto error; /* first record in file? check version number */ if (ibdbr_ctx->ibdbr_first) { if (val != RT_IBDB_VERS_R) goto error; /* version number */ ret = sm_io_fgetuint32(ibdbr_ctx->ibdbr_fp, &val); if (sm_is_err(ret) || val != RT_IBDB_VERS_N) goto error; ret = sm_io_fgetuint32(ibdbr_ctx->ibdbr_fp, &val); if (sm_is_err(ret) || val != 4) goto error; ret = sm_io_fgetuint32(ibdbr_ctx->ibdbr_fp, &val); if (sm_is_err(ret)) goto error; if (!IBDB_VRS_COMPAT(IBDB_VERSION, val)) goto error; ret = sm_io_falign(ibdbr_ctx->ibdbr_fp, IBDB_REC_SIZE); if (sm_is_err(ret)) goto error; ibdbr_ctx->ibdbr_version = val; ibdbr_ctx->ibdbr_first = false; goto again; } if (val == RT_IBDB_TA) ret = ibdbr_ta_status(ibdbr_ctx, ibdb_ta, pstatus); else if (val == RT_IBDB_RCPT) ret = ibdbr_rcpt_status(ibdbr_ctx, ibdb_rcpt, pstatus); else if (val == RT_IBDB_HDRMOD) { *pstatus = 0; /* ??? */ ret = ibdbr_hdr_mod(ibdbr_ctx, ibdb_hdrmod); } else ret = sm_error_perm(SM_EM_IBDB, SM_E_PR_ERR); if (sm_is_err(ret)) goto error; return val; error: if (sm_is_success(ret)) ret = sm_error_perm(SM_EM_IBDB, SM_E_VER_MIX); return ret; } /* ** IDBR_CLOSE -- close the file ** ** Parameters: ** ibdbr_ctx -- INCEDB context ** ** Returns: ** usual sm_error code (from sm_io_close()) */ sm_ret_T ibdbr_close(ibdbr_ctx_P ibdbr_ctx) { sm_ret_T ret; sm_file_T *fp; SM_ASSERT(ibdbr_ctx != NULL); ret = SM_SUCCESS; fp = ibdbr_ctx->ibdbr_fp; if (fp != NULL) { ret = sm_io_close(fp, SM_IO_CF_NONE); if (sm_is_err(ret)) return ret; } ibdbr_ctx_free(ibdbr_ctx); return ret; } /* ** IBDBR_UNLINK -- unlink IBDB files ** ** Parameters: ** ibdbr_ctx -- ibdbr context ** first -- first sequence number to unlink ** last -- last sequence number to unlink ** ** Returns: ** SM_SUCCESS */ sm_ret_T ibdbr_unlink(ibdbr_ctx_P ibdbr_ctx, uint32_t first, uint32_t last) { size_t i; SM_ASSERT(ibdbr_ctx != NULL); for (i = first; i <= last; i++) { sm_str_clr(ibdbr_ctx->ibdbr_name_rm); crt_ibdb_path(ibdbr_ctx->ibdbr_name_rm, ibdbr_ctx->ibdbr_dir, ibdbr_ctx->ibdbr_name, i); (void) unlink((const char *) sm_str_getdata(ibdbr_ctx->ibdbr_name_rm)); sm_str_clr(ibdbr_ctx->ibdbr_name_rm); } return SM_SUCCESS; }