/**************************************************************** * * * Copyright 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 #include "gtm_stdio.h" #include "gtm_ctype.h" #include "gdsroot.h" #include "gdsbt.h" #include "gdsfhead.h" #include "filestruct.h" #include "repl_msg.h" #include "gtmsource.h" #include "error.h" #include "repl_sem.h" #include "gtmimagename.h" #include "hashtab_str.h" #include "eintr_wrappers.h" #include "gtmmsg.h" #include "anticipatory_freeze.h" #define MAX_TAG_LEN 128 /* Maximum size of an error mnemonic */ #define MAX_READ_SZ 1024 /* Mnemonic + flags shouldn't exceed this limit */ #define COMMENT_DELIMITER ';' #define NEWLINE 0x0A #define EOL_REACHED (char *)(-1) #define EOF_REACHED (char *)(-2) #define EXHAUST_CURRENT_LINE(BUFF, HANDLE, FGETS_RC) \ { \ assert(NEWLINE != BUFF[STRLEN(BUFF) - 1]); \ while (TRUE) \ { \ FGETS_FILE(BUFF, MAX_READ_SZ, HANDLE, FGETS_RC); \ if ((NULL == FGETS_RC) || NEWLINE == BUFF[STRLEN(BUFF) - 1]) \ break; \ } \ } error_def(ERR_CUSTERRNOTFND); error_def(ERR_CUSTERRSYNTAX); error_def(ERR_CUSTOMFILOPERR); error_def(ERR_DSKSPCAVAILABLE); error_def(ERR_ENOSPCQIODEFER); error_def(ERR_REPLINSTFREEZECOMMENT); error_def(ERR_REPLINSTFROZEN); error_def(ERR_TEXT); GBLREF jnlpool_addrs jnlpool; GBLREF jnlpool_ctl_ptr_t jnlpool_ctl; GBLREF boolean_t is_src_server; GBLREF boolean_t holds_sem[NUM_SEM_SETS][NUM_SRC_SEMS]; GBLREF sgmnt_addrs *cs_addrs; GBLREF gd_region *gv_cur_region; /* Typically prototypes are included in the header file. But, in this case the static function - get_mnemonic_offset - has the * hash_table_str as one of the function parameters which means all the files which includes anticipatory_freeze.h needs to include * hashtab_str.h and since there are lot of such C files, we chose to define static function prototypes in the C file itself. */ STATICFNDCL char *scan_space(FILE *handle, char *buff, char *buffptr, char *buff_top); STATICFNDCL int get_mnemonic_offset(hash_table_str **err_hashtab, char *mnemonic_buf, int mnemonic_len); /* Scan through whitespace in the current buffer (read more if required) */ STATICFNDEF char *scan_space(FILE *handle, char *buff, char *buffptr, char *buff_top) { char *fgets_rc; while (TRUE) { for (; (buffptr < buff_top) && (ISSPACE_ASCII(*buffptr)); buffptr++) ; if (buffptr < buff_top) return buffptr; /* first non-whitespace character */ if (NEWLINE == *(buffptr - 1)) return EOL_REACHED; /* current buffer is exhausted and we haven't seen a newline; read more */ FGETS_FILE(buff, MAX_READ_SZ, handle, fgets_rc); if (NULL == fgets_rc) break; buffptr = buff; buff_top = buffptr + STRLEN(buff); } return EOF_REACHED; } STATICFNDEF int get_mnemonic_offset(hash_table_str **err_hashtab, char *mnemonic_buf, int mnemonic_len) { const err_msg *msg_beg, *msg_top; hash_table_str *tmp_err_hashtab; ht_ent_str *err_htent; stringkey key; err_msg *msg_info; boolean_t added; DEBUG_ONLY(int idx;) msg_beg = merrors_ctl.fst_msg; msg_top = msg_beg + merrors_ctl.msg_cnt; assert('\0' == mnemonic_buf[mnemonic_len]); if (NULL == (tmp_err_hashtab = *err_hashtab)) { /* create and populate hash-table for future lookups */ tmp_err_hashtab = (hash_table_str *)malloc(SIZEOF(hash_table_str)); DEBUG_ONLY(tmp_err_hashtab->base = NULL); init_hashtab_str(tmp_err_hashtab, 0, HASHTAB_NO_COMPACT, HASHTAB_NO_SPARE_TABLE); assert(tmp_err_hashtab->base); for (msg_info = (err_msg *)msg_beg; msg_info < msg_top; msg_info++) { key.str.addr = msg_info->tag; key.str.len = STRLEN(msg_info->tag); COMPUTE_HASH_STR(&key); added = add_hashtab_str(tmp_err_hashtab, &key, msg_info, &err_htent); assert(added); assert(err_htent->value); assert(msg_info->tag == ((err_msg *)(err_htent->value))->tag); } *err_hashtab = tmp_err_hashtab; } assert(NULL != tmp_err_hashtab); /* lookup for the mnemonic */ key.str.addr = mnemonic_buf; key.str.len = mnemonic_len; COMPUTE_HASH_STR(&key); if (NULL == (err_htent = lookup_hashtab_str(tmp_err_hashtab, &key))) return -1; msg_info = (err_msg *)(err_htent->value); assert(msg_info >= msg_beg && msg_info < msg_top); return msg_info - msg_beg; } /* Determine whether a given msg_id qualifies for an anticipatory freeze or not */ boolean_t is_anticipatory_freeze_needed(int msg_id) { const err_ctl *ctl; int idx; sgmnt_addrs *csa; assert(NULL != jnlpool.jnlpool_ctl); /* Certain error messages should NOT trigger a freeze even if they are so configured in the custom errors file as they might * result in instance freezes that can be set indefinitely. Currently, we know of at least two such messages: * 1. ENOSPCQIODEFER : To ensure we don't set anticipatory freeze if we don't/can't hold crit (due to possible deadlock) * 2. DSKSPCAVAILABLE : To ensure we don't set anticipatory freeze if the disk space becomes available after an initial * lack of space. */ if ((ERR_ENOSPCQIODEFER == msg_id) || (ERR_DSKSPCAVAILABLE == msg_id)) return FALSE; ctl = err_check(msg_id); if (NULL != ctl) { GET_MSG_IDX(msg_id, ctl, idx); assert(idx < ARRAYSIZE(jnlpool_ctl->merrors_array)); if (jnlpool_ctl->merrors_array[idx] & AFREEZE_MASK) { assert(NULL != gv_cur_region); assert(&FILE_INFO(gv_cur_region)->s_addrs == cs_addrs); csa = &FILE_INFO(gv_cur_region)->s_addrs; assert((NULL != csa) && (NULL != csa->hdr)); if (csa->hdr->freeze_on_fail) return TRUE; } } return FALSE; } /* set the anticipatory freeze in the journal pool */ void set_anticipatory_freeze(int msg_id) { boolean_t was_crit; sgmnt_addrs *repl_csa; # ifdef DEBUG qw_off_t write_addr; uint4 write; # endif assert(is_anticipatory_freeze_needed(msg_id)); DEBUG_ONLY( write_addr = jnlpool_ctl->write_addr; write = jnlpool_ctl->write; ) assert(write == write_addr % jnlpool_ctl->jnlpool_size); repl_csa = &FILE_INFO(jnlpool.jnlpool_dummy_reg)->s_addrs; assert(NULL != repl_csa); was_crit = repl_csa->now_crit; if (!was_crit && !repl_csa->hold_onto_crit) grab_lock(jnlpool.jnlpool_dummy_reg, GRAB_LOCK_ONLY); /* Now that we hold necessary locks, set the freeze and the comment field */ jnlpool.jnlpool_ctl->freeze = TRUE; GENERATE_INST_FROZEN_COMMENT(jnlpool.jnlpool_ctl->freeze_comment, SIZEOF(jnlpool.jnlpool_ctl->freeze_comment), msg_id); /* TODO : Do we need a SHM_WRITE_MEMORY_BARRIER ? */ if (!was_crit && !repl_csa->hold_onto_crit) rel_lock(jnlpool.jnlpool_dummy_reg); } /* initialize jnlpool_ctl->merrors_array to set up the list of errors that should trigger anticipatory freeze errors */ boolean_t init_anticipatory_freeze_errors() { int idx, save_errno, status, mnemonic_len, offset, line_no; FILE *handle; char *fgets_rc; char buff[MAX_READ_SZ], mnemonic_buf[MAX_TAG_LEN]; char *buffptr, *buff_top, *errptr, *errptr_top; mstr custom_err_file; hash_table_str *err_hashtab = NULL; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; /* TODO : Currently, we process errors that belong to merrors[] as those are the ones related to database/journal. Need * to check if cmerrors/cmierrors also need to be included in this list or not. */ assert(IS_MUPIP_IMAGE); /* is_src_server is not initialized at this point */ assert(jnlpool_ctl && !jnlpool_ctl->pool_initialized); /* should be invoked BEFORE the journal pool is fully-initialized */ assert(holds_sem[SOURCE][JNL_POOL_ACCESS_SEM]); /* should hold journal pool access control semaphore */ /* Now, read the custom errors file and populate the journal pool */ custom_err_file = TREF(gtm_custom_errors); handle = Fopen(custom_err_file.addr, "r"); if (NULL == handle) { save_errno = errno; send_msg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fopen"), custom_err_file.len, custom_err_file.addr, save_errno); gtm_putmsg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fopen"), custom_err_file.len, custom_err_file.addr, save_errno); return FALSE; } line_no = 0; /* The code below parses a custom errors file in the following format. * * file ::= line* * line ::= mnemonic SPACE* comment? EOL | * comment EOL * mnemonic ::= ALNUM+ * comment ::= COMMENT_DELIMITER ANY* * * SPACE ::= any ASCII white space character * COMMENT_DELIMITER ::= ';' * ANY ::= any ASCII character except end of line * EOL ::= ASCII end of line character * ALNUM ::= any ASCII alphanumeric character * * NOTES: * "*" denotes zero-or-more of the previous item * "?" denotes zero-or-one of the previous item * "+" denotes one-or-more of the previous item * "|" denotes multiple alternatives * The mnemonic must match an entry in the GT.M error message list. * Anything between the COMMENT_DELIMITER and EOL is ignored. * Each iteration of the loop parses one line. */ while (TRUE) { FGETS_FILE(buff, MAX_READ_SZ, handle, fgets_rc); line_no++; if (NULL == fgets_rc) break; buffptr = buff; buff_top = buffptr + STRLEN(buff); errptr = &mnemonic_buf[0]; errptr_top = errptr + MAX_TAG_LEN; /* The first character has to be alpha-numeric or a comment */ if (!ISALNUM_ASCII(*buffptr) && (COMMENT_DELIMITER != *buffptr)) { send_msg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no, ERR_TEXT, 2, LEN_AND_LIT("First character should be comment (;) or alpha numeric")); gtm_putmsg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no, ERR_TEXT, 2, LEN_AND_LIT("First character should be comment (;) or alpha numeric")); return FALSE; } while (ISALNUM_ASCII(*buffptr)) { *errptr++ = *buffptr++; if (errptr > errptr_top) { send_msg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no, ERR_TEXT, 2, LEN_AND_LIT("Mnemonic too long")); gtm_putmsg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no, ERR_TEXT, 2, LEN_AND_LIT("Mnemonic too long")); return FALSE; } assert(buffptr < buff_top); /* errptr > errptr_top should fail before this */ } *errptr = '\0'; if (0 < (mnemonic_len = (errptr - &mnemonic_buf[0]))) { /* Non-empty error mnemonic found; look it up */ if (-1 == (offset = get_mnemonic_offset(&err_hashtab, mnemonic_buf, mnemonic_len))) { send_msg(VARLSTCNT(4) ERR_CUSTERRNOTFND, 2, mnemonic_len, mnemonic_buf); gtm_putmsg(VARLSTCNT(4) ERR_CUSTERRNOTFND, 2, mnemonic_len, mnemonic_buf); return FALSE; } jnlpool_ctl->merrors_array[offset] |= AFREEZE_MASK; /* duplicate entries are not considered an error */ } assert(ISSPACE_ASCII(*buffptr) || (COMMENT_DELIMITER == *buffptr)); if (EOL_REACHED == (buffptr = scan_space(handle, buff, buffptr, buff_top))) continue; else if (EOF_REACHED == buffptr) break; assert(buffptr < buff_top); if (COMMENT_DELIMITER != *buffptr) { send_msg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no, ERR_TEXT, 2, LEN_AND_LIT("Unexpected character found after mnemonic")); gtm_putmsg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no, ERR_TEXT, 2, LEN_AND_LIT("Unexpected character found after mnemonic")); return FALSE; } /* Need to ignore the rest of the current buffer and exhaust the current line */ if (NEWLINE != *(buff_top - 1)) EXHAUST_CURRENT_LINE(buff, handle, fgets_rc); } if (err_hashtab) { free_hashtab_str(err_hashtab); free(err_hashtab); } if (!feof(handle)) { save_errno = errno; send_msg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fgets"), custom_err_file.len, custom_err_file.addr, save_errno); gtm_putmsg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fgets"), custom_err_file.len, custom_err_file.addr, save_errno); return FALSE; } FCLOSE(handle, status); if (SS_NORMAL != status) { save_errno = errno; send_msg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fclose"), custom_err_file.len, custom_err_file.addr, save_errno); gtm_putmsg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fclose"), custom_err_file.len, custom_err_file.addr, save_errno); return FALSE; } return TRUE; }