/* return the lmaster given a key */ uint32_t ctdb_lmaster(struct ctdb_context *ctdb, const TDB_DATA *key) { uint32_t idx, lmaster; idx = ctdb_hash(key) % ctdb->vnn_map->size; lmaster = ctdb->vnn_map->map[idx]; return lmaster; }
/** * write a record to a normal database * * This is the server-variant of the ctdb_ltdb_store function. * It contains logic to determine whether a record should be * stored or deleted. It also sends SCHEDULE_FOR_DELETION * controls to the local ctdb daemon if apporpriate. */ static int ctdb_ltdb_store_server(struct ctdb_db_context *ctdb_db, TDB_DATA key, struct ctdb_ltdb_header *header, TDB_DATA data) { struct ctdb_context *ctdb = ctdb_db->ctdb; TDB_DATA rec; int ret; bool seqnum_suppressed = false; bool keep = false; bool schedule_for_deletion = false; bool remove_from_delete_queue = false; uint32_t lmaster; if (ctdb->flags & CTDB_FLAG_TORTURE) { struct ctdb_ltdb_header *h2; rec = tdb_fetch(ctdb_db->ltdb->tdb, key); h2 = (struct ctdb_ltdb_header *)rec.dptr; if (rec.dptr && rec.dsize >= sizeof(h2) && h2->rsn > header->rsn) { DEBUG(DEBUG_CRIT,("RSN regression! %llu %llu\n", (unsigned long long)h2->rsn, (unsigned long long)header->rsn)); } if (rec.dptr) free(rec.dptr); } if (ctdb->vnn_map == NULL) { /* * Called from a client: always store the record * Also don't call ctdb_lmaster since it uses the vnn_map! */ keep = true; goto store; } lmaster = ctdb_lmaster(ctdb_db->ctdb, &key); /* * If we migrate an empty record off to another node * and the record has not been migrated with data, * delete the record instead of storing the empty record. */ if (data.dsize != 0) { keep = true; } else if (header->flags & CTDB_REC_RO_FLAGS) { keep = true; } else if (ctdb_db->persistent) { keep = true; } else if (header->flags & CTDB_REC_FLAG_AUTOMATIC) { /* * The record is not created by the client but * automatically by the ctdb_ltdb_fetch logic that * creates a record with an initial header in the * ltdb before trying to migrate the record from * the current lmaster. Keep it instead of trying * to delete the non-existing record... */ keep = true; schedule_for_deletion = true; } else if (header->flags & CTDB_REC_FLAG_MIGRATED_WITH_DATA) { keep = true; } else if (ctdb_db->ctdb->pnn == lmaster) { /* * If we are lmaster, then we usually keep the record. * But if we retrieve the dmaster role by a VACUUM_MIGRATE * and the record is empty and has never been migrated * with data, then we should delete it instead of storing it. * This is part of the vacuuming process. * * The reason that we usually need to store even empty records * on the lmaster is that a client operating directly on the * lmaster (== dmaster) expects the local copy of the record to * exist after successful ctdb migrate call. If the record does * not exist, the client goes into a migrate loop and eventually * fails. So storing the empty record makes sure that we do not * need to change the client code. */ if (!(header->flags & CTDB_REC_FLAG_VACUUM_MIGRATED)) { keep = true; } else if (ctdb_db->ctdb->pnn != header->dmaster) { keep = true; } } else if (ctdb_db->ctdb->pnn == header->dmaster) { keep = true; } if (keep) { if (!ctdb_db->persistent && (ctdb_db->ctdb->pnn == header->dmaster) && !(header->flags & CTDB_REC_RO_FLAGS)) { header->rsn++; if (data.dsize == 0) { schedule_for_deletion = true; } } remove_from_delete_queue = !schedule_for_deletion; } store: /* * The VACUUM_MIGRATED flag is only set temporarily for * the above logic when the record was retrieved by a * VACUUM_MIGRATE call and should not be stored in the * database. * * The VACUUM_MIGRATE call is triggered by a vacuum fetch, * and there are two cases in which the corresponding record * is stored in the local database: * 1. The record has been migrated with data in the past * (the MIGRATED_WITH_DATA record flag is set). * 2. The record has been filled with data again since it * had been submitted in the VACUUM_FETCH message to the * lmaster. * For such records it is important to not store the * VACUUM_MIGRATED flag in the database. */ header->flags &= ~CTDB_REC_FLAG_VACUUM_MIGRATED; /* * Similarly, clear the AUTOMATIC flag which should not enter * the local database copy since this would require client * modifications to clear the flag when the client stores * the record. */ header->flags &= ~CTDB_REC_FLAG_AUTOMATIC; rec.dsize = sizeof(*header) + data.dsize; rec.dptr = talloc_size(ctdb, rec.dsize); CTDB_NO_MEMORY(ctdb, rec.dptr); memcpy(rec.dptr, header, sizeof(*header)); memcpy(rec.dptr + sizeof(*header), data.dptr, data.dsize); /* Databases with seqnum updates enabled only get their seqnum changes when/if we modify the data */ if (ctdb_db->seqnum_update != NULL) { TDB_DATA old; old = tdb_fetch(ctdb_db->ltdb->tdb, key); if ( (old.dsize == rec.dsize) && !memcmp(old.dptr+sizeof(struct ctdb_ltdb_header), rec.dptr+sizeof(struct ctdb_ltdb_header), rec.dsize-sizeof(struct ctdb_ltdb_header)) ) { tdb_remove_flags(ctdb_db->ltdb->tdb, TDB_SEQNUM); seqnum_suppressed = true; } if (old.dptr) free(old.dptr); } DEBUG(DEBUG_DEBUG, (__location__ " db[%s]: %s record: hash[0x%08x]\n", ctdb_db->db_name, keep?"storing":"deleting", ctdb_hash(&key))); if (keep) { ret = tdb_store(ctdb_db->ltdb->tdb, key, rec, TDB_REPLACE); } else { ret = tdb_delete(ctdb_db->ltdb->tdb, key); } if (ret != 0) { int lvl = DEBUG_ERR; if (keep == false && tdb_error(ctdb_db->ltdb->tdb) == TDB_ERR_NOEXIST) { lvl = DEBUG_DEBUG; } DEBUG(lvl, (__location__ " db[%s]: Failed to %s record: " "%d - %s\n", ctdb_db->db_name, keep?"store":"delete", ret, tdb_errorstr(ctdb_db->ltdb->tdb))); schedule_for_deletion = false; remove_from_delete_queue = false; } if (seqnum_suppressed) { tdb_add_flags(ctdb_db->ltdb->tdb, TDB_SEQNUM); } talloc_free(rec.dptr); if (schedule_for_deletion) { int ret2; ret2 = ctdb_local_schedule_for_deletion(ctdb_db, header, key); if (ret2 != 0) { DEBUG(DEBUG_ERR, (__location__ " ctdb_local_schedule_for_deletion failed.\n")); } } if (remove_from_delete_queue) { ctdb_local_remove_from_delete_queue(ctdb_db, header, key); } return ret; }