/**************************************************************** * * * Copyright 2001, 2012 Fidelity Information Services, Inc * * * * This source code contains the intellectual property * * of its copyright holder(s), and is made available * * under a license. If you do not know the terms of * * the license, please stop and do not read further. * * * ****************************************************************/ #include "mdef.h" #include "gtm_stdlib.h" #include "gtm_stdio.h" #include "gtm_string.h" #include "gtm_unistd.h" #include "gtm_fcntl.h" #include "gtm_stat.h" #include "gtm_inet.h" #include "gtm_time.h" #include #include #include #include "eintr_wrappers.h" #include "gdsroot.h" #include "gdsblk.h" #include "gtm_facility.h" #include "fileinfo.h" #include "gdsbt.h" #include "gdsfhead.h" #include "filestruct.h" #include "jnl.h" #include "repl_msg.h" #include "gtmsource.h" #include "gtmrecv.h" #include "iosp.h" #include "gtmio.h" #include "gtm_logicals.h" #include "trans_log_name.h" #include "gtmmsg.h" #include "repl_sem.h" #include "repl_instance.h" #include "ftok_sems.h" #include "error.h" #include "gds_rundown.h" #include "buddy_list.h" /* needed for muprec.h */ #include "hashtab_int4.h" /* needed for muprec.h */ #include "hashtab_int8.h" /* needed for muprec.h */ #include "hashtab_mname.h" /* needed for muprec.h */ #include "muprec.h" #include "have_crit.h" #ifdef __MVS__ #include "gtm_zos_io.h" #endif GBLREF jnlpool_addrs jnlpool; GBLREF recvpool_addrs recvpool; GBLREF boolean_t in_repl_inst_edit; /* Used by an assert in repl_inst_read/repl_inst_write */ GBLREF boolean_t in_repl_inst_create; /* Used by repl_inst_read/repl_inst_write */ GBLREF boolean_t in_mupip_ftok; /* Used by an assert in repl_inst_read */ GBLREF jnl_gbls_t jgbl; GBLREF gd_addr *gd_header; GBLREF gd_region *gv_cur_region; GBLREF sgmnt_addrs *cs_addrs; GBLREF sgmnt_data_ptr_t cs_data; GBLREF bool in_backup; GBLREF int4 strm_index; GBLREF boolean_t is_src_server; GBLREF boolean_t holds_sem[NUM_SEM_SETS][NUM_SRC_SEMS]; GBLREF boolean_t is_rcvr_server; ZOS_ONLY(error_def(ERR_BADTAG);) error_def(ERR_LOGTOOLONG); error_def(ERR_NOTALLDBOPN); error_def(ERR_REPLFTOKSEM); error_def(ERR_REPLINSTACC); error_def(ERR_REPLINSTCLOSE); error_def(ERR_REPLINSTCREATE); error_def(ERR_REPLINSTFMT); error_def(ERR_REPLINSTNOHIST); error_def(ERR_REPLINSTOPEN); error_def(ERR_REPLINSTREAD); error_def(ERR_REPLINSTSEQORD); error_def(ERR_REPLINSTUNDEF); error_def(ERR_REPLINSTWRITE); error_def(ERR_SYSCALL); error_def(ERR_TEXT); /* Description: * Get the environment of replication instance. * Parameters: * fn : repl instance file name it gets * fn_len: length of fn. * bufsize: the buffer size caller gives. If exceeded, it trucates file name. * Return Value: * TRUE, on success * FALSE, otherwise. */ boolean_t repl_inst_get_name(char *fn, unsigned int *fn_len, unsigned int bufsize, instname_act error_action) { char temp_inst_fn[MAX_FN_LEN+1]; mstr log_nam, trans_name; uint4 ustatus; int4 status; boolean_t ret; log_nam.addr = GTM_REPL_INSTANCE; log_nam.len = SIZEOF(GTM_REPL_INSTANCE) - 1; trans_name.addr = temp_inst_fn; ret = FALSE; if ((SS_NORMAL == (status = TRANS_LOG_NAME(&log_nam, &trans_name, temp_inst_fn, SIZEOF(temp_inst_fn), do_sendmsg_on_log2long))) && (0 != trans_name.len)) { temp_inst_fn[trans_name.len] = '\0'; if (!get_full_path(trans_name.addr, trans_name.len, fn, fn_len, bufsize, &ustatus)) { gtm_putmsg(VARLSTCNT(9) ERR_REPLINSTACC, 2, trans_name.len, trans_name.addr, ERR_TEXT, 2, RTS_ERROR_LITERAL("full path could not be found"), ustatus); } else ret = TRUE; } if (FALSE == ret) { if (issue_rts_error == error_action) { if (SS_LOG2LONG == status) rts_error(VARLSTCNT(5) ERR_LOGTOOLONG, 3, log_nam.len, log_nam.addr, SIZEOF(temp_inst_fn) - 1); else rts_error(VARLSTCNT(1) ERR_REPLINSTUNDEF); } else if (issue_gtm_putmsg == error_action) { if (SS_LOG2LONG == status) gtm_putmsg(VARLSTCNT(5) ERR_LOGTOOLONG, 3, log_nam.len, log_nam.addr, SIZEOF(temp_inst_fn) - 1); else gtm_putmsg(VARLSTCNT(1) ERR_REPLINSTUNDEF); } } return ret; } /* Description: * Reads "buflen" bytes of data into "buff" from the file "fn" at offset "offset" * Parameters: * fn : Instance file name. * offset: Offset at which to read * buff : Buffer to read into * buflen: Number of bytes to read * Return Value: * None */ void repl_inst_read(char *fn, off_t offset, sm_uc_ptr_t buff, size_t buflen) { int status, fd; size_t actual_readlen; unix_db_info *udi; gd_region *reg; repl_inst_hdr_ptr_t replhdr; /* Assert that except for MUPIP REPLIC -INSTANCE_CREATE or -EDITINSTANCE or MUPIP FTOK, all callers hold the FTOK semaphore * on the replication instance file OR the journal pool lock. Note that the instance file might be pointed to by one of the * two region pointers "jnlpool.jnlpool_dummy_reg" or "recvpool.recvpool_dummy_reg" depending on whether the journal pool * or the receive pool was attached to first by this particular process. If both of them are non-NULL, both the region * pointers should be identical. This is also asserted below. * Note: Typically, journal pool lock should have sufficed. However, in certain places like jnlpool_init and recvpool_init, * the journal pool is not yet created and hence grab_lock/rel_lock does not make sense. In those cases we need the FTOK * lock on the instance file. The ONLY exception to this is ROLLBACK in which case it does NOT hold the FTOK semaphore and * since it is NOT necessary for ROLLBACK to have a journal pool open, grab_lock will not be done either. Assert * accordingly. */ assert((NULL == jnlpool.jnlpool_dummy_reg) || (NULL == recvpool.recvpool_dummy_reg) || jnlpool.jnlpool_dummy_reg == recvpool.recvpool_dummy_reg); reg = jnlpool.jnlpool_dummy_reg; if (NULL == reg) reg = recvpool.recvpool_dummy_reg; assert((NULL == reg) && (in_repl_inst_create || in_repl_inst_edit || in_mupip_ftok) || (NULL != reg) && !in_repl_inst_create && !in_repl_inst_edit && !in_mupip_ftok); if (NULL != reg) { udi = FILE_INFO(reg); assert(udi->grabbed_ftok_sem || ((NULL != jnlpool.jnlpool_ctl) && udi->s_addrs.now_crit) || jgbl.mur_rollback); } OPENFILE(fn, O_RDONLY, fd); if (FD_INVALID == fd) rts_error(VARLSTCNT(5) ERR_REPLINSTOPEN, 2, LEN_AND_STR(fn), errno); assert(0 < buflen); if (0 != offset) { LSEEKREAD(fd, offset, buff, buflen, status); } else { /* Read starts from the replication instance file header. Assert that the entire file header was requested. */ assert(REPL_INST_HDR_SIZE <= buflen); /* Use LSEEKREAD_AVAILABLE macro instead of LSEEKREAD. This is because if we are not able to read the entire * fileheader, we still want to see if the "label" field of the file header got read in which case we can * do the format check first. It is important to do the format check before checking "status" returned from * LSEEKREAD* macros since the inability to read the entire file header might actually be due to the * older format replication instance file being smaller than even the newer format instance file header. */ LSEEKREAD_AVAILABLE(fd, offset, buff, buflen, actual_readlen, status); if (GDS_REPL_INST_LABEL_SZ <= actual_readlen) { /* Have read the entire label in the instance file header. Check if it is the right version */ if (memcmp(buff, GDS_REPL_INST_LABEL, GDS_REPL_INST_LABEL_SZ - 1)) { rts_error(VARLSTCNT(8) ERR_REPLINSTFMT, 6, LEN_AND_STR(fn), GDS_REPL_INST_LABEL_SZ - 1, GDS_REPL_INST_LABEL, GDS_REPL_INST_LABEL_SZ - 1, buff); } } if (0 == status) { /* Check a few other fields in the file-header for compatibility */ assert(actual_readlen == buflen); replhdr = (repl_inst_hdr_ptr_t)buff; /* Check endianness match */ if (GTM_IS_LITTLE_ENDIAN != replhdr->is_little_endian) { rts_error(VARLSTCNT(8) ERR_REPLINSTFMT, 6, LEN_AND_STR(fn), LEN_AND_LIT(ENDIANTHIS), LEN_AND_LIT(ENDIANOTHER)); } /* Check 64bitness match */ if (GTM_IS_64BIT != replhdr->is_64bit) { rts_error(VARLSTCNT(8) ERR_REPLINSTFMT, 6, LEN_AND_STR(fn), LEN_AND_LIT(GTM_BITNESS_THIS), LEN_AND_LIT(GTM_BITNESS_OTHER)); } /* At the time of this writing, the only minor version supported is 1. * Whenever this gets updated, we need to add code to do the online upgrade. * Add an assert as a reminder to do this. */ assert(1 == replhdr->replinst_minorver); /* Check if on-the-fly minor-version upgrade is necessary */ if (GDS_REPL_INST_MINOR_LABEL != replhdr->replinst_minorver) assert(FALSE); } } assert((0 == status) || in_repl_inst_edit); if (0 != status) { if (-1 == status) rts_error(VARLSTCNT(6) ERR_REPLINSTREAD, 4, buflen, (qw_off_t *)&offset, LEN_AND_STR(fn)); else rts_error(VARLSTCNT(7) ERR_REPLINSTREAD, 4, buflen, (qw_off_t *)&offset, LEN_AND_STR(fn), status); } CLOSEFILE_RESET(fd, status); /* resets "fd" to FD_INVALID */ assert(0 == status); if (0 != status) rts_error(VARLSTCNT(5) ERR_REPLINSTCLOSE, 2, LEN_AND_STR(fn), status); } /* Description: * Writes "buflen" bytes of data from "buff" into the file "fn" at offset "offset" * Parameters: * fn : Instance file name. * offset: Offset at which to write * buff : Buffer to write from * buflen: Number of bytes to write * Return Value: * None. */ void repl_inst_write(char *fn, off_t offset, sm_uc_ptr_t buff, size_t buflen) { int status, fd, oflag; unix_db_info *udi; gd_region *reg; ZOS_ONLY(int realfiletag;) /* Assert that except for MUPIP REPLIC -INSTANCE_CREATE or -EDITINSTANCE, all callers hold the FTOK semaphore on the * replication instance file OR the journal pool lock. Note that the instance file might be pointed to by one of the * two region pointers "jnlpool.jnlpool_dummy_reg" or "recvpool.recvpool_dummy_reg" depending on whether the journal pool * or the receive pool was attached to first by this particular process. If both of them are non-NULL, both the region * pointers should be identical. This is also asserted below. * Note: Typically, journal pool lock should have sufficed. However, in certain places like jnlpool_init and recvpool_init, * the journal pool is not yet created and hence grab_lock/rel_lock does not make sense. In those case we need the FTOK * lock on the instance file. The ONLY exception to this is ROLLBACK in which case it does NOT hold the FTOK semaphore and * since it is NOT necessary for ROLLBACK to have a journal pool open, grab_lock will not be done either. Assert * accordingly. */ assert((NULL == jnlpool.jnlpool_dummy_reg) || (NULL == recvpool.recvpool_dummy_reg) || jnlpool.jnlpool_dummy_reg == recvpool.recvpool_dummy_reg); DEBUG_ONLY( reg = jnlpool.jnlpool_dummy_reg; if (NULL == reg) reg = recvpool.recvpool_dummy_reg; ) assert((NULL == reg) && (in_repl_inst_create || in_repl_inst_edit) || (NULL != reg) && !in_repl_inst_create && !in_repl_inst_edit); DEBUG_ONLY( if (NULL != reg) { udi = FILE_INFO(reg); assert(udi->grabbed_ftok_sem || ((NULL != jnlpool.jnlpool_ctl) && udi->s_addrs.now_crit) || jgbl.mur_rollback); } ) oflag = O_RDWR; if (in_repl_inst_create) oflag |= (O_CREAT | O_EXCL); OPENFILE3(fn, oflag, 0666, fd); if (FD_INVALID == fd) { if (!in_repl_inst_create) rts_error(VARLSTCNT(5) ERR_REPLINSTOPEN, 2, LEN_AND_STR(fn), errno); else rts_error(VARLSTCNT(5) ERR_REPLINSTCREATE, 2, LEN_AND_STR(fn), errno); } #ifdef __MVS__ if (-1 == (in_repl_inst_create ? gtm_zos_set_tag(fd, TAG_BINARY, TAG_NOTTEXT, TAG_FORCE, &realfiletag) : gtm_zos_tag_to_policy(fd, TAG_BINARY, &realfiletag))) TAG_POLICY_GTM_PUTMSG(fn, errno, realfiletag, TAG_BINARY); #endif assert(0 < buflen); LSEEKWRITE(fd, offset, buff, buflen, status); assert(0 == status); if (0 != status) rts_error(VARLSTCNT(7) ERR_REPLINSTWRITE, 4, buflen, (qw_off_t *)&offset, LEN_AND_STR(fn), status); CLOSEFILE_RESET(fd, status); /* resets "fd" to FD_INVALID */ assert(0 == status); if (0 != status) rts_error(VARLSTCNT(5) ERR_REPLINSTCLOSE, 2, LEN_AND_STR(fn), status); } /* Description: * Hardens all pending writes for the instance file to disk * Parameters: * fn : Instance file name. * Return Value: * None. */ void repl_inst_sync(char *fn) { int status, fd, oflag; unix_db_info *udi; gd_region *reg; /* Assert that except for MUPIP REPLIC -INSTANCE_CREATE or -EDITINSTANCE, all callers hold the FTOK semaphore * on the replication instance file. Note that the instance file might be pointed to by one of the two region * pointers "jnlpool.jnlpool_dummy_reg" or "recvpool.recvpool_dummy_reg" depending on whether the journal pool * or the receive pool was attached to first by this particular process. If both of them are non-NULL, both the * region pointers should be identical. This is also asserted below. */ assert((NULL == jnlpool.jnlpool_dummy_reg) || (NULL == recvpool.recvpool_dummy_reg) || jnlpool.jnlpool_dummy_reg == recvpool.recvpool_dummy_reg); DEBUG_ONLY( reg = jnlpool.jnlpool_dummy_reg; if (NULL == reg) reg = recvpool.recvpool_dummy_reg; ) DEBUG_ONLY( assert(NULL != reg); udi = FILE_INFO(reg); assert((NULL != jnlpool.jnlpool_ctl) && udi->s_addrs.now_crit); ) oflag = O_RDWR; OPENFILE3(fn, oflag, 0666, fd); if (FD_INVALID == fd) rts_error(VARLSTCNT(5) ERR_REPLINSTOPEN, 2, LEN_AND_STR(fn), errno); GTM_FSYNC(fd, status); assert(0 == status); if (0 != status) rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("fsync()"), CALLFROM, errno); CLOSEFILE_RESET(fd, status); /* resets "fd" to FD_INVALID */ assert(0 == status); if (0 != status) rts_error(VARLSTCNT(5) ERR_REPLINSTCLOSE, 2, LEN_AND_STR(fn), status); } /* Description: * Reset journal pool shmid and semid in replication instance file. * Parameters: * None * Return Value: * None */ void repl_inst_jnlpool_reset(void) { repl_inst_hdr repl_instance; unix_db_info *udi; udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); assert(udi->grabbed_ftok_sem); if (NULL != jnlpool.repl_inst_filehdr) { /* If journal pool exists, reset sem/shm ids in the file header in the journal pool and flush changes to disk */ jnlpool.repl_inst_filehdr->jnlpool_semid = INVALID_SEMID; jnlpool.repl_inst_filehdr->jnlpool_shmid = INVALID_SHMID; jnlpool.repl_inst_filehdr->jnlpool_semid_ctime = 0; jnlpool.repl_inst_filehdr->jnlpool_shmid_ctime = 0; repl_inst_flush_filehdr(); } else { /* If journal pool does not exist, reset sem/shm ids directly in the replication instance file header on disk */ repl_inst_read((char *)udi->fn, (off_t)0, (sm_uc_ptr_t)&repl_instance, SIZEOF(repl_inst_hdr)); repl_instance.jnlpool_semid = INVALID_SEMID; repl_instance.jnlpool_shmid = INVALID_SHMID; repl_instance.jnlpool_semid_ctime = 0; repl_instance.jnlpool_shmid_ctime = 0; repl_inst_write((char *)udi->fn, (off_t)0, (sm_uc_ptr_t)&repl_instance, SIZEOF(repl_inst_hdr)); } } /* Description: * Reset receiver pool shmid and semid in replication instance file. * Parameters: * None * Return Value: * None */ void repl_inst_recvpool_reset(void) { repl_inst_hdr repl_instance; unix_db_info *udi; udi = FILE_INFO(recvpool.recvpool_dummy_reg); assert(udi->grabbed_ftok_sem); if (NULL != jnlpool.repl_inst_filehdr) { /* If journal pool exists, reset sem/shm ids in the file header in the journal pool and flush changes to disk */ jnlpool.repl_inst_filehdr->recvpool_semid = INVALID_SEMID; jnlpool.repl_inst_filehdr->recvpool_shmid = INVALID_SHMID; jnlpool.repl_inst_filehdr->recvpool_semid_ctime = 0; jnlpool.repl_inst_filehdr->recvpool_shmid_ctime = 0; repl_inst_flush_filehdr(); } else { /* If journal pool does not exist, reset sem/shm ids directly in the replication instance file header on disk */ repl_inst_read((char *)udi->fn, (off_t)0, (sm_uc_ptr_t)&repl_instance, SIZEOF(repl_inst_hdr)); repl_instance.recvpool_semid = INVALID_SEMID; repl_instance.recvpool_shmid = INVALID_SHMID; repl_instance.recvpool_semid_ctime = 0; repl_instance.recvpool_shmid_ctime = 0; repl_inst_write((char *)udi->fn, (off_t)0, (sm_uc_ptr_t)&repl_instance, SIZEOF(repl_inst_hdr)); } } /* Wrapper routine to GRAB the ftok semaphore lock of the replication instance file and to test for errors */ void repl_inst_ftok_sem_lock(void) { gd_region *reg; unix_db_info *udi; assert(!jgbl.mur_rollback); /* Rollback already has standalone access and will not ask for ftok lock */ assert((NULL != jnlpool.jnlpool_dummy_reg) || (NULL != recvpool.recvpool_dummy_reg)); assert((NULL == jnlpool.jnlpool_dummy_reg) || (NULL == recvpool.recvpool_dummy_reg) || (recvpool.recvpool_dummy_reg == jnlpool.jnlpool_dummy_reg)); reg = jnlpool.jnlpool_dummy_reg; if (NULL == reg) reg = recvpool.recvpool_dummy_reg; assert(NULL != reg); udi = FILE_INFO(reg); assert(!udi->grabbed_ftok_sem); if (!udi->grabbed_ftok_sem) { assert(0 == have_crit(CRIT_HAVE_ANY_REG)); if (!ftok_sem_lock(reg, FALSE, FALSE)) { assert(FALSE); rts_error(VARLSTCNT(4) ERR_REPLFTOKSEM, 2, LEN_AND_STR(udi->fn)); } } assert(udi->grabbed_ftok_sem); } /* Wrapper routine to RELEASE the ftok semaphore lock of the replication instance file and to test for errors */ void repl_inst_ftok_sem_release(void) { gd_region *reg; unix_db_info *udi; assert(!jgbl.mur_rollback); /* Rollback already has standalone access and will not ask for ftok lock */ assert((NULL != jnlpool.jnlpool_dummy_reg) || (NULL != recvpool.recvpool_dummy_reg)); assert((NULL == jnlpool.jnlpool_dummy_reg) || (NULL == recvpool.recvpool_dummy_reg) || (recvpool.recvpool_dummy_reg == jnlpool.jnlpool_dummy_reg)); reg = jnlpool.jnlpool_dummy_reg; if (NULL == reg) reg = recvpool.recvpool_dummy_reg; assert(NULL != reg); udi = FILE_INFO(reg); assert(udi->grabbed_ftok_sem); if (udi->grabbed_ftok_sem) /* Be safe in PRO and avoid releasing if we do not hold the ftok semaphore */ { assert(0 == have_crit(CRIT_HAVE_ANY_REG)); if (!ftok_sem_release(reg, FALSE, FALSE)) { assert(FALSE); rts_error(VARLSTCNT(4) ERR_REPLFTOKSEM, 2, LEN_AND_STR(udi->fn)); } } assert(!udi->grabbed_ftok_sem); } /* Description: * Get the 'n'th histinfo record from the instance file. * Parameters: * index : The number of the histinfo record to be read. 0 for the first histinfo record, 1 for the second and so on... * histinfo : A pointer to the repl_histinfo structure to be filled in. * Return Value: * 0, on success * ERR_REPLINSTNOHIST, if "index" is not a valid histinfo index. */ int4 repl_inst_histinfo_get(int4 index, repl_histinfo *histinfo) { off_t offset; unix_db_info *udi; repl_inst_hdr_ptr_t repl_inst_filehdr; udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); assert(udi->s_addrs.now_crit || jgbl.mur_rollback); if (0 > index) return ERR_REPLINSTNOHIST; repl_inst_filehdr = jnlpool.repl_inst_filehdr; assert(NULL != repl_inst_filehdr); assert(index < repl_inst_filehdr->num_histinfo); /* assert that no caller should request a get of an unused (but allocated) histinfo */ if (index >= repl_inst_filehdr->num_alloc_histinfo) return ERR_REPLINSTNOHIST; offset = REPL_INST_HISTINFO_START + (index * SIZEOF(repl_histinfo)); repl_inst_read((char *)udi->fn, offset, (sm_uc_ptr_t)histinfo, SIZEOF(repl_histinfo)); assert(histinfo->histinfo_num == index); return 0; } /* * Parameters: * seqno : The journal seqno that is to be searched in the instance file history. * strm_idx : -1, 0, 1, 2, ... 15 indicating the stream # within which to search. * : -1 (aka INVALID_SUPPL_STRM) implies search across ALL streams. * histinfo : A pointer to the repl_histinfo to be filled in. Contents might have been modified even on error return. * Description: * If strm_idx=-1 * ----------------- * Given an input "seqno", locate the histinfo record (from ANY stream) in the instance file whose "start_seqno" * corresponds to "seqno-1". * If strm_idx=0 * ---------------- * Given an input "seqno", locate the histinfo record (from 0th stream) in the instance file whose "start_seqno" * corresponds to "seqno-1". * If strm_idx=1,2,...,15 * ------------------------- * Given an input "seqno", locate the histinfo record (from "strm_index"th stream) in the instance file * whose "strm_seqno" (not start_seqno) corresponds to "seqno-1". * Return Value: * 0, on success * ERR_REPLINSTNOHIST, if "seqno" is NOT present in the instance file history range. There are two cases to consider here. * If there was an error fetching a history record, "histinfo->histinfo_num" will be set to INVALID_HISTINFO_NUM. * Otherwise, if we ran out of history records, "histinfo" will point to the 0th history record corresponding to "strm_idx". */ int4 repl_inst_histinfo_find_seqno(seq_num seqno, int4 strm_idx, repl_histinfo *histinfo) { unix_db_info *udi; int4 histnum, status; seq_num cur_seqno; # ifdef DEBUG seq_num prev_seqno; int4 prev_histnum; # endif repl_inst_hdr_ptr_t inst_hdr; udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); assert(udi->s_addrs.now_crit || jgbl.mur_rollback); assert(0 != seqno); inst_hdr = jnlpool.repl_inst_filehdr; assert(NULL != inst_hdr); assert((INVALID_SUPPL_STRM == strm_idx) || inst_hdr->is_supplementary && (0 <= strm_idx) && (MAX_SUPPL_STRMS > strm_idx)); assert(inst_hdr->num_histinfo <= inst_hdr->num_alloc_histinfo); if (INVALID_SUPPL_STRM == strm_idx) histnum = inst_hdr->num_histinfo - 1; else histnum = inst_hdr->last_histinfo_num[strm_idx]; assert(-1 == INVALID_HISTINFO_NUM); /* so we can safely decrement 0 and reach -1 i.e. an invalid history number */ DEBUG_ONLY(prev_seqno = 0;) do { assert(histnum < inst_hdr->num_histinfo); assert(INVALID_HISTINFO_NUM <= histnum); if (INVALID_HISTINFO_NUM == histnum) return ERR_REPLINSTNOHIST; status = repl_inst_histinfo_get(histnum, histinfo); if (0 != status) { assert(FALSE); histinfo->histinfo_num = INVALID_HISTINFO_NUM; /* signal to caller this is an out-of-design situation */ return ERR_REPLINSTNOHIST; } assert((INVALID_SUPPL_STRM == strm_idx) || (strm_idx == histinfo->strm_index)); cur_seqno = (0 < strm_idx) ? histinfo->strm_seqno : histinfo->start_seqno; assert(cur_seqno); assert((0 == prev_seqno) || (prev_seqno > cur_seqno) || ((INVALID_SUPPL_STRM == strm_idx) && (prev_seqno == cur_seqno))); DEBUG_ONLY(prev_seqno = cur_seqno;) if (seqno > cur_seqno) break; DEBUG_ONLY(prev_histnum = histnum;) histnum = (INVALID_SUPPL_STRM == strm_idx) ? (histnum - 1) : histinfo->prev_histinfo_num; } while (TRUE); return 0; } /* This function finds the histinfo in the local replication instance file corresponding to seqno "seqno-1". * It is a wrapper on top of the function "repl_inst_histinfo_find_seqno" which additionally does error checking. * For the case where "repl_inst_histinfo_find_seqno" returns 0 with a -1 histinfo_num, this function returns ERR_REPLINSTNOHIST. */ int4 repl_inst_wrapper_histinfo_find_seqno(seq_num seqno, int4 strm_idx, repl_histinfo *local_histinfo) { unix_db_info *udi; char histdetail[256]; int4 status; repl_histinfo *next_histinfo; udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); assert(udi->s_addrs.now_crit || jgbl.mur_rollback); assert(NULL != jnlpool.repl_inst_filehdr); /* journal pool should be set up */ assert((is_src_server && ((INVALID_SUPPL_STRM == strm_index) || (0 == strm_index))) || (!is_src_server && ((INVALID_SUPPL_STRM == strm_index) || ((0 <= strm_index) && (MAX_SUPPL_STRMS > strm_index))))); status = repl_inst_histinfo_find_seqno(seqno, strm_idx, local_histinfo); assert((0 == status) || (ERR_REPLINSTNOHIST == status)); /* the only error returned by "repl_inst_histinfo_find_seqno" */ if (0 != status) { status = ERR_REPLINSTNOHIST; SPRINTF(histdetail, "seqno "INT8_FMT" "INT8_FMTX, seqno - 1, seqno - 1); gtm_putmsg(VARLSTCNT(6) ERR_REPLINSTNOHIST, 4, LEN_AND_STR(histdetail), LEN_AND_STR(udi->fn)); } else assert(0 <= local_histinfo->histinfo_num); return status; } /* Description: * Add a new histinfo record to the replication instance file. * Parameters: * histinfo : A pointer to the histinfo structure to be added to the instance file. * Return Value: * None * Errors: * Issues ERR_REPLINSTSEQORD error if new histinfo will cause seqno to be out of order. */ void repl_inst_histinfo_add(repl_histinfo *histinfo) { boolean_t is_supplementary, start_seqno_equal; int4 histinfo_num, strm_histinfo_num, prev_histinfo_num, status; int strm_idx, idx; off_t offset; repl_histinfo *last_histinfo, last_histrec, *last_strm_histinfo, last_strm_histrec; repl_histinfo last2_histinfo, *prev_strm_histinfo, prev_strm_histrec; seq_num histinfo_strm_seqno, prev_strm_seqno; unix_db_info *udi; udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); assert(udi->s_addrs.now_crit); assert(jnlpool.repl_inst_filehdr->num_histinfo <= jnlpool.repl_inst_filehdr->num_alloc_histinfo); histinfo_num = jnlpool.repl_inst_filehdr->num_histinfo; assert(0 <= histinfo_num); strm_idx = histinfo->strm_index; /* Assert that the very first history record in any instance file (irrespective of whether the * instance is a root primary or propagating primary) should correspond to stream-0. */ assert((0 < histinfo_num) || (0 == strm_idx)); is_supplementary = jnlpool.repl_inst_filehdr->is_supplementary; assert(!is_supplementary && (0 == strm_idx) || (is_supplementary && (0 <= strm_idx) && (MAX_SUPPL_STRMS > strm_idx))); /* If -updateresync is specified and instance is not supplementary, then there better be NO history records */ assert((HISTINFO_TYPE_UPDRESYNC != histinfo->history_type) || is_supplementary || (0 == histinfo_num)); if (strm_idx && !jnlpool.jnlpool_ctl->upd_disabled) { /* A non-supplementary stream history record is being written into a supplementary root primary instance. * Convert the history record as appropriate. See below macro definition for more comments on the conversion. */ CONVERT_NONSUPPL2SUPPL_HISTINFO(histinfo, jnlpool.jnlpool_ctl) } if (0 < histinfo_num) { last_histinfo = &last_histrec; status = repl_inst_histinfo_get(histinfo_num - 1, last_histinfo); assert(0 == status); /* Since histinfo_num-1 we are passing is >=0 and < num_histinfo */ assert(jnlpool.jnlpool_ctl->last_histinfo_seqno == last_histinfo->start_seqno); if (histinfo->start_seqno < last_histinfo->start_seqno) { /* cannot create histinfo with out-of-order start_seqno */ rts_error(VARLSTCNT(8) ERR_REPLINSTSEQORD, 6, LEN_AND_LIT("New history record"), &histinfo->start_seqno, &last_histinfo->start_seqno, LEN_AND_STR(udi->fn)); } } strm_histinfo_num = jnlpool.repl_inst_filehdr->last_histinfo_num[strm_idx]; prev_histinfo_num = strm_histinfo_num; if (0 <= strm_histinfo_num) { assert(strm_histinfo_num < histinfo_num); if (strm_histinfo_num != (histinfo_num - 1)) { last_strm_histinfo = &last_strm_histrec; status = repl_inst_histinfo_get(strm_histinfo_num, last_strm_histinfo); assert(0 == status); /* Since the strm_histinfo_num we are passing is >=0 and < num_histinfo */ } else { /* Had read this history record just now from the instance file. Use it and avoid another read */ last_strm_histinfo = last_histinfo; } assert(strm_idx == last_strm_histinfo->strm_index); /* Check if the history record to be added has the same histinfo content as the last history record * already present in the instance file (in the stream of interest). This is possible in case of a secondary * where the receiver was receiving journal records (from the primary) for a while, was shut down and then * restarted. Same instance is sending information so no new histinfo information needed. Return right away. * The only exception is if this is a supplementary instance and the new history record is an UPDATERESYNC * type of record in which case it is possible the two histories have the histinfo content identical but * have different start_seqnos. In this case, some updates went in between the two histories so we want * to record the input history as a separate record instead of returning (since this signals the beginning * of a new stream of updates). */ if ((!is_supplementary || (HISTINFO_TYPE_UPDRESYNC != histinfo->history_type)) && !STRCMP(last_strm_histinfo->root_primary_instname, histinfo->root_primary_instname) && (last_strm_histinfo->root_primary_cycle == histinfo->root_primary_cycle) && (last_strm_histinfo->creator_pid == histinfo->creator_pid) && (last_strm_histinfo->created_time == histinfo->created_time)) { return; } assert((histinfo->start_seqno != last_strm_histinfo->start_seqno) || (histinfo->strm_seqno == last_strm_histinfo->strm_seqno) || (HISTINFO_TYPE_NORESYNC == histinfo->history_type)); /* If stream seqnos match between input history and last stream specific history in the instance file, * make sure the to-be-written history record skips past the last stream specific history record (as we * expect a decreasing sequence of strm_seqnos in the "prev_histinfo_num" linked list of history records). * The only exception is if we are a supplementary instance and this is stream # 0. In that case, only if * the start_seqno is also equal, will we skip. This is because if start_seqno is not equal, the stream # 0 * history records identify a range of updates that happened (even if the updates happened in non-zero * stream #s) and that is used by history record matching between two supplementary instances at replication * connection time. * The same skipping logic applies to "start_seqno" in case the instance is non-supplementary (in which case * the "strm_seqno" field is 0). */ histinfo_strm_seqno = histinfo->strm_seqno; prev_strm_seqno = last_strm_histinfo->strm_seqno; if (histinfo_strm_seqno == prev_strm_seqno) { start_seqno_equal = (histinfo->start_seqno == last_strm_histinfo->start_seqno); if (histinfo_strm_seqno && strm_idx || start_seqno_equal) { assert(prev_histinfo_num > last_strm_histinfo->prev_histinfo_num); prev_histinfo_num = last_strm_histinfo->prev_histinfo_num; } if (start_seqno_equal && (strm_histinfo_num == (histinfo_num - 1))) { /* Starting seqno of the last histinfo in the instance file matches the input histinfo. * This means there are no journal records corresponding to the input stream in the journal * files after the last histinfo (which happens to be same as the input stream) was written * in the instance file. Overwrite the last histinfo with the new histinfo information before * writing new journal records. */ histinfo_num--; } } else if (HISTINFO_TYPE_NORESYNC == histinfo->history_type) { /* Determine the correct value of "prev_histinfo_num" */ prev_strm_histinfo = &prev_strm_histrec; prev_strm_histrec = *last_strm_histinfo; assert(prev_strm_seqno == prev_strm_histinfo->strm_seqno); while (histinfo_strm_seqno <= prev_strm_seqno) { prev_histinfo_num = prev_strm_histinfo->prev_histinfo_num; assert(INVALID_HISTINFO_NUM != prev_histinfo_num); if (INVALID_HISTINFO_NUM == prev_histinfo_num) break; status = repl_inst_histinfo_get(prev_histinfo_num, prev_strm_histinfo); assert(0 == status); /* Since prev_histinfo_num we are passing is >=0 and < num_histinfo */ assert(prev_strm_seqno > prev_strm_histinfo->strm_seqno); prev_strm_seqno = prev_strm_histinfo->strm_seqno; } } } /* Assert that the history record we are going to add is in sync with the current seqno state of the instance */ assert(jnlpool.jnlpool_ctl->jnl_seqno == histinfo->start_seqno); assert(jnlpool.jnlpool_ctl->strm_seqno[histinfo->strm_index] == histinfo->strm_seqno); offset = REPL_INST_HISTINFO_START + (SIZEOF(repl_histinfo) * (off_t)histinfo_num); /* Initialize the following members of the repl_histinfo structure. Everything else should be initialized by caller. * histinfo_num * prev_histinfo_num * last_histinfo_num[] */ histinfo->histinfo_num = histinfo_num; histinfo->prev_histinfo_num = (HISTINFO_TYPE_UPDRESYNC == histinfo->history_type) ? INVALID_HISTINFO_NUM : prev_histinfo_num; assert(histinfo->prev_histinfo_num < histinfo->histinfo_num); for (idx = 0; idx < MAX_SUPPL_STRMS; idx++) { assert((jnlpool.repl_inst_filehdr->last_histinfo_num[idx] < histinfo_num) || (idx == strm_idx) && (jnlpool.repl_inst_filehdr->last_histinfo_num[idx] == histinfo_num)); histinfo->last_histinfo_num[idx] = jnlpool.repl_inst_filehdr->last_histinfo_num[idx]; } if (strm_histinfo_num == histinfo_num) { /* The last history record in the instance file is going to be overwritten with another history record of * the same stream. In this case, jnlpool.repl_inst_filehdr->last_histinfo_num[strm_idx] would not reflect a * state of the instance file BEFORE this history record was added. So find the correct value. Thankfully * the last history record (that we are about to overwrite) already has this value so copy it over. */ histinfo->last_histinfo_num[strm_idx] = last_histinfo->last_histinfo_num[strm_idx]; } assert(strm_histinfo_num == jnlpool.repl_inst_filehdr->last_histinfo_num[strm_idx]); assert(strm_histinfo_num <= histinfo_num); assert(strm_histinfo_num >= prev_histinfo_num); assert(histinfo_num > prev_histinfo_num); assert((INVALID_HISTINFO_NUM == histinfo->prev_histinfo_num) || (0 <= histinfo->prev_histinfo_num)); assert(is_supplementary || (prev_histinfo_num == (histinfo_num - 1))); # ifdef DEBUG /* Assert that the prev_histinfo_num list of history records have decreasing "start_seqno" and "strm_seqno" values. * The only exception is stream # 0 for a supplementary instance as described in a previous comment in this function. */ if (INVALID_HISTINFO_NUM != prev_histinfo_num) { status = repl_inst_histinfo_get(prev_histinfo_num, &last2_histinfo); assert(0 == status); /* Since the strm_histinfo_num we are passing is >=0 and < num_histinfo */ assert(strm_idx == last2_histinfo.strm_index); /* they both better have the same stream # */ assert(histinfo->start_seqno > last2_histinfo.start_seqno); assert(!histinfo->strm_seqno || (histinfo->strm_seqno > last2_histinfo.strm_seqno) || (0 == strm_idx)); } /* Assert that the last_histinfo_num fields reflect a state of the instance file that does not include the about-to-be * added history record. This ensures the instance file header will get restored to a valid state in case of a rollback * that truncates exactly at this history record boundary. */ for (idx = 0; idx < MAX_SUPPL_STRMS; idx++) assert(histinfo->last_histinfo_num[idx] < histinfo_num); # endif /* Assert that if this is not the first history record being written into the instance file * it should have a valid 0th stream history record number. This is relied upon by "gtmsource_send_new_histrec" */ assert((0 == histinfo_num) || (INVALID_HISTINFO_NUM != histinfo->last_histinfo_num[0])); repl_inst_write(udi->fn, offset, (sm_uc_ptr_t)histinfo, SIZEOF(repl_histinfo)); /* Update stream specific history number fields in the file header to reflect the latest history addition to this stream */ jnlpool.repl_inst_filehdr->last_histinfo_num[strm_idx] = histinfo_num; /* If -updateresync history record for a non-zero stream #, then initialize strm_group_info in file header */ if ((0 < strm_idx) && (HISTINFO_TYPE_UPDRESYNC == histinfo->history_type)) jnlpool.repl_inst_filehdr->strm_group_info[strm_idx - 1] = histinfo->lms_group; histinfo_num++; if (jnlpool.repl_inst_filehdr->num_alloc_histinfo < histinfo_num) jnlpool.repl_inst_filehdr->num_alloc_histinfo = histinfo_num; jnlpool.repl_inst_filehdr->num_histinfo = histinfo_num; repl_inst_flush_filehdr(); jnlpool.jnlpool_ctl->last_histinfo_seqno = histinfo->start_seqno; repl_inst_sync(udi->fn); /* Harden the new histinfo to disk before any logical records for this arrive. */ return; } /* Description: * Given an input "rollback_seqno", virtually truncate all histinfo records that correspond to seqnos >= "rollback_seqno" * This function also updates other fields (unrelated to histinfo truncation) in the file header * to reflect a clean shutdown by MUPIP JOURNAL ROLLBACK. This function is also invoked by MUPIP BACKUP in order * to ensure the backed up instance file is initialized to reflect a clean shutdown. * Parameters: * rollback_seqno : The seqno after which all histinfo records have to be truncated. * Note: In case of a supplementary instance file, this function expects the caller to have * set "inst_hdr->strm_seqno[]" to reflect the "rollback_seqno". * Return Value: * Sequence number (start_seqno) of the last history record in the instance file * Errors: * Issues ERR_REPLINSTNOHIST message if the call to "repl_inst_histinfo_find_seqno" returned an error. */ seq_num repl_inst_histinfo_truncate(seq_num rollback_seqno) { char histdetail[256]; int4 status, index, num_histinfo, last_histnum; int idx; repl_histinfo temphistinfo, nexthistinfo, strmhistinfo; repl_inst_hdr_ptr_t inst_hdr; unix_db_info *udi; seq_num last_histinfo_seqno = 0; udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); assert(in_backup || jgbl.mur_rollback); /* Only ROLLBACK or BACKUP calls this function */ assert(udi->s_addrs.now_crit || jgbl.mur_rollback); inst_hdr = jnlpool.repl_inst_filehdr; assert(NULL != inst_hdr); /* Should have been set when mupip rollback invoked "mu_replpool_grab_sem" */ num_histinfo = inst_hdr->num_histinfo; if (0 != num_histinfo) { status = repl_inst_histinfo_find_seqno(rollback_seqno, INVALID_SUPPL_STRM, &temphistinfo); if (0 != status) { assert(ERR_REPLINSTNOHIST == status); /* the only error returned by "repl_inst_histinfo_find_seqno" */ if ((INVALID_HISTINFO_NUM == temphistinfo.histinfo_num) || (temphistinfo.start_seqno != rollback_seqno)) { /* The truncation seqno is not the starting seqno of the instance file. In that case, issue * a RELINSTNOHIST warning message even though rollback is going to proceed anycase. */ assert(FALSE); NON_GTM64_ONLY(SPRINTF(histdetail, "seqno [0x%llx]", rollback_seqno - 1)); GTM64_ONLY(SPRINTF(histdetail, "seqno [0x%lx]", rollback_seqno - 1)); gtm_putmsg(VARLSTCNT(6) MAKE_MSG_WARNING(ERR_REPLINSTNOHIST), 4, LEN_AND_STR(histdetail), LEN_AND_STR(udi->fn)); } index = -1; /* Since we are rolling back all history records in the instance file, * clear all of "strm_seqno", "strm_group_info[]" and "last_histinfo_num[]" arrays. * The following logic is similar to that in "repl_inst_create" to initialize the above 4 fields. * Leave out "jnl_seqno" initialization as that is done anyways after this "if" block. */ assert(MAX_SUPPL_STRMS == ARRAYSIZE(inst_hdr->last_histinfo_num)); for (idx = 0; idx < MAX_SUPPL_STRMS; idx++) inst_hdr->last_histinfo_num[idx] = INVALID_HISTINFO_NUM; if (inst_hdr->is_supplementary) { assert(MAX_SUPPL_STRMS == ARRAYSIZE(inst_hdr->strm_seqno)); assert(SIZEOF(seq_num) == SIZEOF(inst_hdr->strm_seqno[0])); memset(inst_hdr->strm_seqno, 0, MAX_SUPPL_STRMS * SIZEOF(seq_num)); inst_hdr->strm_seqno[0] = 1; /* like is done in repl_inst_create */ assert((MAX_SUPPL_STRMS - 1) == ARRAYSIZE(inst_hdr->strm_group_info)); assert(SIZEOF(repl_inst_uuid) == SIZEOF(inst_hdr->strm_group_info[0])); memset(inst_hdr->strm_group_info, 0, (MAX_SUPPL_STRMS - 1) * SIZEOF(repl_inst_uuid)); } } else { index = temphistinfo.histinfo_num; assert(temphistinfo.start_seqno < rollback_seqno); assert(0 <= index); assert(index <= (num_histinfo - 1)); last_histinfo_seqno = temphistinfo.start_seqno; if (index < (num_histinfo - 1)) { status = repl_inst_histinfo_get(index + 1, &nexthistinfo); assert(0 == status); /* Since the histinfo_num we are passing is >=0 and <= num_histinfo */ assert(nexthistinfo.start_seqno >= rollback_seqno); assert(nexthistinfo.histinfo_num == (index + 1)); /* Copy over information from this history record back to the instance file header */ assert(SIZEOF(inst_hdr->last_histinfo_num) == SIZEOF(nexthistinfo.last_histinfo_num)); memcpy(inst_hdr->last_histinfo_num, nexthistinfo.last_histinfo_num, SIZEOF(nexthistinfo.last_histinfo_num)); if (inst_hdr->is_supplementary) { /* inst_hdr->strm_seqno[] is already set by caller */ assert((MAX_SUPPL_STRMS - 1) == ARRAYSIZE(inst_hdr->strm_group_info)); for (idx = 0; idx < (MAX_SUPPL_STRMS - 1); idx++) { last_histnum = nexthistinfo.last_histinfo_num[idx + 1]; assert(INVALID_HISTINFO_NUM <= last_histnum); assert(last_histnum < nexthistinfo.histinfo_num); if (INVALID_HISTINFO_NUM != last_histnum) { status = repl_inst_histinfo_get(last_histnum, &strmhistinfo); assert(0 == status); assert(strmhistinfo.histinfo_num == last_histnum); assert(strmhistinfo.start_seqno < rollback_seqno); assert(strmhistinfo.strm_index); assert(MAX_SUPPL_STRMS > strmhistinfo.strm_index); assert(IS_REPL_INST_UUID_NON_NULL(strmhistinfo.lms_group)); inst_hdr->strm_group_info[idx] = strmhistinfo.lms_group; } else NULL_INITIALIZE_REPL_INST_UUID(inst_hdr->strm_group_info[idx]); } } } /* else index == "num_histinfo - 1" so no changes needed to "last_histinfo_num[]" * or "strm_seqno[]" or "strm_group_info[]" arrays. */ } index++; assert((index == inst_hdr->num_histinfo) || ((inst_hdr->num_histinfo >= 0) && (inst_hdr->num_alloc_histinfo > index))); inst_hdr->num_histinfo = index; } /* Reset "jnl_seqno" to the rollback seqno so future REPLINSTDBMATCH errors are avoided in "gtmsource_seqno_init". * Note that it is possible inst_hdr->num_histinfo is 0 at this point (i.e. no history records). In that case, * repl_inst_create sets the "jnl_seqno" to 0 whereas we might set it here to a potentially non-zero value. * That is because repl_inst_create does not go through the database and get the max of the reg_seqnos to figure * out the instance jnl_seqno. Hence it sets it to a value of 0 indicating the source server that starts up the * instance to fill it in with a non-zero value. On the other hand, rollback or backup (both of which can call * this function "repl_inst_histinfo_truncate") know exactly what the instance seqno is and so can safely set the * "jnl_seqno" to a non-zero value even though there are no history records. Setting it to a non-zero value whenever * possible is useful for example when we ship a backup of a freshly created live non-supplementary instance (with * jnl_seqno of 1) to be used as input to the -updateresync qualifier of a receiver startup on a supplementary * instance. In this case, if the backup had a jnl_seqno of 0, the startup would fail. But since it has a non-zero * "jnl_seqno" (even though there are no history records), the initial handshake between the non-supplementary and * supplementary instances is possible (they avoid history record exchanges due to jnl_seqno == 1). A zero jnl_seqno * would have resulted in a UPDSYNCINSTFILE error in the initial handshake. */ inst_hdr->jnl_seqno = rollback_seqno; /* Reset sem/shm ids to reflect a clean shutdown so future REPLREQRUNDOWN errors are avoided at "jnlpool_init" time */ if (!jgbl.mur_rollback) { /* Reset semid/sem_ctime fields in the instance file header. */ /* Reset "crash" to FALSE so future REPLREQROLLBACK errors are avoided at "jnlpool_init" time */ inst_hdr->crash = FALSE; inst_hdr->jnlpool_semid = INVALID_SEMID; inst_hdr->jnlpool_shmid = INVALID_SHMID; inst_hdr->jnlpool_semid_ctime = 0; inst_hdr->jnlpool_shmid_ctime = 0; inst_hdr->recvpool_semid = INVALID_SEMID; /* Just in case it is not already reset */ inst_hdr->recvpool_shmid = INVALID_SHMID; /* Just in case it is not already reset */ inst_hdr->recvpool_semid_ctime = 0; inst_hdr->recvpool_shmid_ctime = 0; } /* else for rollback, we reset the IPC fields in mu_replpool_remove_sem() and crash in mur_close_files */ /* Flush all file header changes in jnlpool.repl_inst_filehdr to disk */ assert(!jnlpool.jnlpool_dummy_reg->open); /* Ensure the call below will not do a "grab_lock" as there is no jnlpool */ repl_inst_flush_filehdr(); assert((0 == inst_hdr->num_histinfo) || (0 < last_histinfo_seqno)); return last_histinfo_seqno; } /* Description: * Flushes the instance file header pointed to by "jnlpool.repl_inst_filehdr" to disk. * Parameters: * None * Return Value: * None */ void repl_inst_flush_filehdr() { unix_db_info *udi; udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); /* We could come here from several paths. If journal pool exists, we would have done a grab_lock. This covers most of the * cases. If the journal pool doesn't exist, then we could come here from one of the following places * * ROLLBACK (online/noonline): * We already hold standalone access on the journal pool and if the journal pool exists, we also hold the journal pool * lock * * MUPIP RUNDOWN -> mu_rndwn_repl_instance: * We hold the ftok on the instance file and have already made sure that no one else is attached to the journal pool. Even * though we don't hold the access control on the journal pool, no one else can startup at this point because they need * the ftok for which they will have to wait. * * gtmsource_shutdown -> repl_inst_jnlpool_reset: * We hold the ftok on the instance file and have already made sure that no one else is attached to the journal pool. Even * though we don't hold the access control on the journal pool, no one else can startup at this point because they need * the ftok for which they will have to wait. * * gtmrecv_shutdown -> repl_inst_recvpool_reset: * Same as above. * So, in all cases, we are guaranteed that the following code is mutually exclusive (which is what we want). */ assert(udi->s_addrs.now_crit || udi->grabbed_ftok_sem || (jgbl.mur_rollback && holds_sem[SOURCE][JNL_POOL_ACCESS_SEM])); if (jnlpool.jnlpool_dummy_reg->open) COPY_JCTL_STRMSEQNO_TO_INSTHDR_IF_NEEDED; /* Keep the file header copy of "strm_seqno" uptodate with jnlpool_ctl */ assert((NULL == jnlpool.jnlpool_ctl) || udi->s_addrs.now_crit); assert(NULL != jnlpool.repl_inst_filehdr); /* flush the instance file header */ repl_inst_write(udi->fn, (off_t)0, (sm_uc_ptr_t)jnlpool.repl_inst_filehdr, REPL_INST_HDR_SIZE); } /* Description: * Flushes the "gtmsrc_lcl" structure corresponding to the jnlpool.gtmsource_local structure for the * calling source server. Updates "gtmsource_local->last_flush_resync_seqno" to equal "gtmsource_local->read_jnl_seqno" * Parameters: * None * Return Value: * None */ void repl_inst_flush_gtmsrc_lcl() { unix_db_info *udi; int4 index; off_t offset; gtmsrc_lcl_ptr_t gtmsrclcl_ptr; udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); assert(!jgbl.mur_rollback); /* Rollback should never reach here */ assert(udi->s_addrs.now_crit); assert(NULL != jnlpool.gtmsource_local); index = jnlpool.gtmsource_local->gtmsrc_lcl_array_index; assert(0 <= index); assert(jnlpool.gtmsource_local == &jnlpool.gtmsource_local_array[index]); gtmsrclcl_ptr = &jnlpool.gtmsrc_lcl_array[index]; assert(jnlpool.jnlpool_dummy_reg->open); /* journal pool exists and this process has done "jnlpool_init" */ /* Copy each field from "gtmsource_local" to "gtmsrc_lcl" before flushing it to disk. * Do not need the journal pool lock, as we are the only ones reading/updating the below fields * in "gtmsource_local" or "gtmsrc_lcl". */ COPY_GTMSOURCELOCAL_TO_GTMSRCLCL(jnlpool.gtmsource_local, gtmsrclcl_ptr); offset = REPL_INST_HDR_SIZE + (SIZEOF(gtmsrc_lcl) * (off_t)index); repl_inst_write(udi->fn, offset, (sm_uc_ptr_t)gtmsrclcl_ptr, SIZEOF(gtmsrc_lcl)); jnlpool.gtmsource_local->last_flush_resync_seqno = jnlpool.gtmsource_local->read_jnl_seqno; } /* Description: * Flushes the "repl_inst_hdr" and "gtmsrc_lcl" sections in the journal pool to the on disk copy of the instance file. * Parameters: * None * Return Value: * None */ void repl_inst_flush_jnlpool(boolean_t reset_replpool_fields, boolean_t reset_crash) { unix_db_info *udi; int4 index; gtmsrc_lcl_ptr_t gtmsrclcl_ptr; gtmsource_local_ptr_t gtmsourcelocal_ptr; assert(NULL != jnlpool.jnlpool_dummy_reg); udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); /* This function should be invoked only if the caller determines this is last process attached to the journal pool. * Since the ftok lock on the instance file is already held, no other process will be allowed to attach to the * journal pool and hence this is the only process having access to the journal pool during this function. The only * exception is if it is invoked from mur_open_files for Online Rollback. But, in that case Online Rollback will be * holding the access control. Any process calling this function, needs the access control semaphore and hence will * wait for Online Rollback to complete. */ assert(udi->grabbed_ftok_sem || (jgbl.onlnrlbk && udi->s_addrs.now_crit)); assert(holds_sem[SOURCE][JNL_POOL_ACCESS_SEM]); assert(NULL != jnlpool.gtmsource_local_array); assert(NULL != jnlpool.gtmsrc_lcl_array); assert(NULL != jnlpool.repl_inst_filehdr); assert(NULL != jnlpool.jnlpool_ctl); assert((sm_uc_ptr_t)jnlpool.gtmsrc_lcl_array == (sm_uc_ptr_t)jnlpool.repl_inst_filehdr + REPL_INST_HDR_SIZE); /* Reset the instance file header fields (if needed) before flushing and removing the journal pool shared memory */ if (reset_crash) jnlpool.repl_inst_filehdr->crash = FALSE; if (!jgbl.onlnrlbk) { if (reset_replpool_fields) { jnlpool.repl_inst_filehdr->jnlpool_semid = INVALID_SEMID; jnlpool.repl_inst_filehdr->jnlpool_shmid = INVALID_SHMID; jnlpool.repl_inst_filehdr->recvpool_semid = INVALID_SEMID; /* Just in case it is not already reset */ jnlpool.repl_inst_filehdr->recvpool_shmid = INVALID_SHMID; /* Just in case it is not already reset */ } } /* If the source server that created the journal pool died before it was completely initialized in "gtmsource_seqno_init" * do not copy seqnos from the journal pool into the instance file header. Instead keep the instance file header unchanged. */ if (jnlpool.jnlpool_ctl->pool_initialized) { assert(jnlpool.jnlpool_ctl->start_jnl_seqno); assert(jnlpool.jnlpool_ctl->jnl_seqno); jnlpool.repl_inst_filehdr->jnl_seqno = jnlpool.jnlpool_ctl->jnl_seqno; COPY_JCTL_STRMSEQNO_TO_INSTHDR_IF_NEEDED; /* Keep the file header copy of "strm_seqno" uptodate with jnlpool_ctl */ /* Copy all "gtmsource_local" to corresponding "gtmsrc_lcl" structures before flushing to instance file */ gtmsourcelocal_ptr = &jnlpool.gtmsource_local_array[0]; gtmsrclcl_ptr = &jnlpool.gtmsrc_lcl_array[0]; for (index = 0; index < NUM_GTMSRC_LCL; index++, gtmsourcelocal_ptr++, gtmsrclcl_ptr++) COPY_GTMSOURCELOCAL_TO_GTMSRCLCL(gtmsourcelocal_ptr, gtmsrclcl_ptr); repl_inst_write(udi->fn, (off_t)0, (sm_uc_ptr_t)jnlpool.repl_inst_filehdr, REPL_INST_HDR_SIZE + GTMSRC_LCL_SIZE); } else repl_inst_write(udi->fn, (off_t)0, (sm_uc_ptr_t)jnlpool.repl_inst_filehdr, REPL_INST_HDR_SIZE); } /* This function determines if this replication instance was formerly a root primary. It finds this out by looking at the * last histinfo record in the instance file and comparing the "root_primary_instname" field there with this instance name. * If they are the same, it means the last histinfo was generated by this instance and hence was a root primary then. This * function will only be invoked by a propagating primary instance (RECEIVER SERVER or ROLLBACK -FETCHRESYNC). * * It returns TRUE only if the instance file header field "was_rootprimary" is TRUE and if the last histinfo record was generated * by this instance. It returns FALSE otherwise. */ boolean_t repl_inst_was_rootprimary(void) { int4 histinfo_num, status; repl_histinfo temphistinfo, *last_histinfo = &temphistinfo; boolean_t was_rootprimary, was_crit = FALSE; sgmnt_addrs *csa; if (NULL != jnlpool.jnlpool_ctl) { /* If the journal pool is available (indicated by NULL != jnlpool_ctl), we expect jnlpool_dummy_reg to be open. * The only exception is online rollback which doesn't do a jnlpool_init thereby leaving jnlpool_dummy_reg->open * to be FALSE. Assert accordingly. */ assert(((NULL != jnlpool.jnlpool_dummy_reg) && jnlpool.jnlpool_dummy_reg->open) || jgbl.onlnrlbk); csa = &FILE_INFO(jnlpool.jnlpool_dummy_reg)->s_addrs; ASSERT_VALID_JNLPOOL(csa); assert(csa->now_crit); } else assert(jgbl.mur_rollback); /* ROLLBACK (holding access control lock) can come here without journal pool */ /* If this is a supplementary instance, look at the last history record corresponding to the 0th stream index. * If not, look at the last history record. This is okay since there is no multiple streams in this case. */ histinfo_num = (!jnlpool.repl_inst_filehdr->is_supplementary) ? (jnlpool.repl_inst_filehdr->num_histinfo - 1) : jnlpool.repl_inst_filehdr->last_histinfo_num[0]; was_rootprimary = jnlpool.repl_inst_filehdr->was_rootprimary; assert(histinfo_num < jnlpool.repl_inst_filehdr->num_alloc_histinfo); if (was_rootprimary && (0 <= histinfo_num)) { status = repl_inst_histinfo_get(histinfo_num, last_histinfo); assert(0 == status); /* Since the histinfo_num we are passing is >=0 and < num_histinfo */ was_rootprimary = !STRCMP(last_histinfo->root_primary_instname, jnlpool.repl_inst_filehdr->inst_info.this_instname); } else was_rootprimary = FALSE; return was_rootprimary; } /* This function resets "zqgblmod_seqno" and "zqgblmod_tn" in all replicated database file headers to 0. * This shares a lot of its code with the function "gtmsource_update_zqgblmod_seqno_and_tn". * Any changes there might need to be reflected here. */ int4 repl_inst_reset_zqgblmod_seqno_and_tn(void) { gd_region *reg, *reg_top; int ret; boolean_t all_files_open; sgmnt_addrs *repl_csa; ret = SS_NORMAL; /* assume success */ /* source server calls this from gtmsource_losttncomplete which always holds the journal pool access control semaphore * Assert this. */ assert(is_rcvr_server || holds_sem[SOURCE][JNL_POOL_ACCESS_SEM]); if (0 == jnlpool.jnlpool_ctl->max_zqgblmod_seqno) { /* Already reset to 0 by a previous call to this function. No need to do it again. */ return ret; } /* This function is currently ONLY called by receiver server AND mupip replic -source -losttncomplete * both of which should have NO GBLDIR or REGION OPEN at this time. Assert that. */ assert(NULL == gd_header); if (NULL == gd_header) gvinit(); /* We use the same code dse uses to open all regions but we must make sure they are all open before proceeding. */ all_files_open = region_init(FALSE); if (!all_files_open) rts_error(VARLSTCNT(1) ERR_NOTALLDBOPN); repl_csa = &FILE_INFO(jnlpool.jnlpool_dummy_reg)->s_addrs; for (reg = gd_header->regions, reg_top = reg + gd_header->n_regions; reg < reg_top; reg++) { assert(reg->open); TP_CHANGE_REG(reg); if (!REPL_ALLOWED(cs_data)) continue; /* csa->hdr->zqgblmod_seqno is modified by the source server and an online rollback (both of these hold the * database crit while doing so). It is also read by fileheader_sync() which does so while holding crit. * To avoid the latter from reading an inconsistent value (i.e neither the pre-update nor the post-update * value, which is possible if the 8-byte operation is not atomic but a sequence of two 4-byte operations * AND if the pre-update and post-update value differ in their most significant 4-bytes) we grab_crit. We * could have used QWCHANGE_IS_READER_CONSISTENT macro (which checks for most significant 4-byte difference) * instead to determine if it is really necessary to grab crit. But, since the update to zqgblmod_seqno is a * rare operation, we decided to play it safe. */ assert(!cs_addrs->hold_onto_crit); /* this ensures we can safely do unconditional grab_crit and rel_crit */ grab_crit(reg); if (cs_addrs->onln_rlbk_cycle != cs_addrs->nl->onln_rlbk_cycle) { /* concurrent online rollback */ assert(is_rcvr_server); SYNC_ONLN_RLBK_CYCLES; rel_crit(reg); ret = -1; /* failure */ break; } cs_addrs->hdr->zqgblmod_seqno = (seq_num)0; cs_addrs->hdr->zqgblmod_tn = (trans_num)0; rel_crit(reg); } assert((SS_NORMAL == ret) || (reg < reg_top)); if (reg >= reg_top) { assert(!repl_csa->hold_onto_crit); /* so we can do unconditional grab_lock and rel_lock */ /* Since the source server holds the access control at this point, a concurrent online rollback is NOT possible. * But, if we are here from receiver code, then we cannot guarantee this. So, get the journal pool lock and if * an online rollback is detected, return without resetting max_zqgblmod_seqno. The caller knows to take appropriate * action (on seeing -1 as the return code). */ GRAB_LOCK(jnlpool.jnlpool_dummy_reg, GRAB_LOCK_ONLY); if (repl_csa->onln_rlbk_cycle != jnlpool.jnlpool_ctl->onln_rlbk_cycle) { assert(is_rcvr_server); SYNC_ONLN_RLBK_CYCLES; rel_lock(jnlpool.jnlpool_dummy_reg); ret = -1; /* failure */ } else { jnlpool.jnlpool_ctl->max_zqgblmod_seqno = 0; rel_lock(jnlpool.jnlpool_dummy_reg); } } for (reg = gd_header->regions, reg_top = reg + gd_header->n_regions; reg < reg_top; reg++) { /* Rundown all databases that we opened as we dont need them anymore. This is not done in the previous * loop as it has to wait until the ftok semaphore of the instance file has been released as otherwise * an assert in gds_rundown will fail as it tries to get the ftok semaphore of the database while holding * another ftok semaphore already. */ assert(reg->open); TP_CHANGE_REG(reg); assert(!cs_addrs->now_crit); gds_rundown(); } assert(!repl_csa->now_crit); return ret; }