// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net>
 * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com>
 *
 * Author: Loic Dachary <loic@dachary.org>
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software 
 * Foundation.  See file COPYING.
 * 
 */
#pragma once

#include "common/ceph_context.h"
#include "common/debug.h"
// re-include our assert to clobber boost's
#include "include/ceph_assert.h"
#include "include/common_fwd.h"
#include "osd_types.h"
#include "os/ObjectStore.h"

#include <iosfwd>
#include <map>
#include <memory>
#include <list>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>

#ifdef WITH_CRIMSON
#include <seastar/core/future.hh>
#include "crimson/os/futurized_store.h"
#include "crimson/os/cyanstore/cyan_collection.h"
#endif

/** @name PG Log
 *
 * The pg log serves three primary purposes:
 *
 * 1) improving recovery speed
 *
 * 2) detecting duplicate ops
 *
 * 3) making erasure coded updates safe
 *
 * For (1), the main data type is pg_log_entry_t.  this is indexed in
 * memory by the IndexedLog class - this is where most of the logic
 * surrounding pg log is kept, even though the low level types are in
 * src/osd/osd_types.h
 *
 * (2) uses a type which is a subset of the full log entry, containing
 * just the pieces we need to identify and respond to a duplicate
 * request.
 *
 * As we trim the log, we convert pg_log_entry_t to smaller
 * pg_log_dup_t, and finally remove them once we reach a higher
 * limit. This is controlled by a few options:
 *
 * osd_min_pg_log_entries osd_max_pg_log_entries
 * osd_pg_log_dups_tracked
 *
 * For example, with a min of 100, max of 1000, and dups tracked of
 * 3000, the log entries and dups stored would span the following
 * versions, assuming the current earliest is version 1:
 *
 *   version: 3000 2001 2000 1 [ pg log entries ] [ pg log dups ]
 *
 * after osd_pg_log_trim_min subsequent writes to this PG, the log
 * would be trimmed to look like:
 *
 *   version: 3100 2101 2100 101 [ pg log entries ] [ pg log dups ]
 *
 * (3) means tracking the previous state of an object, so that we can
 * rollback to that prior state if necessary. It's only used for
 * erasure coding. Consider an erasure code of 4+2, for example.
 *
 * This means we split the object into 4 pieces (called shards) and
 * compute 2 parity shards. Each of these shards is stored on a
 * separate OSD. As long as 4 shards are the same version, we can
 * recover the remaining 2 by computation. Imagine during a write, 3
 * of the osds go down and restart, resulting in shards 0,1,2
 * reflecting version A and shards 3,4,5 reflecting version B, after
 * the write.
 *
 * If we had no way to reconstruct version A for another shard, we
 * would have lost the object.
 *
 * The actual data for rollback is stored in a look-aside object and
 * is removed once the EC write commits on all shards. The pg log just
 * stores the versions so we can tell how far we can rollback, and a
 * description of the type of operation for each log entry.  Beyond
 * the pg log, see PGBackend::Trimmer and PGBackend::RollbackVisitor
 * for more details on this.
 *
 * An important implication of this is that although the pg log length
 * is normally bounded, under extreme conditions, with many EC I/Os
 * outstanding, the log may grow beyond that point because we need to
 * keep the rollback information for all outstanding EC I/O.
 *
 * For more on pg log bounds, see where it is calculated in
 * PeeringState::calc_trim_to_aggressive().
 *
 * For more details on how peering uses the pg log, and architectural
 * reasons for its existence, see:
 *
 *   doc/dev/osd_internals/log_based_pg.rst
 *
 */

constexpr auto PGLOG_INDEXED_OBJECTS          = 1 << 0;
constexpr auto PGLOG_INDEXED_CALLER_OPS       = 1 << 1;
constexpr auto PGLOG_INDEXED_EXTRA_CALLER_OPS = 1 << 2;
constexpr auto PGLOG_INDEXED_DUPS             = 1 << 3;
constexpr auto PGLOG_INDEXED_ALL              = PGLOG_INDEXED_OBJECTS 
                                              | PGLOG_INDEXED_CALLER_OPS 
                                              | PGLOG_INDEXED_EXTRA_CALLER_OPS 
                                              | PGLOG_INDEXED_DUPS;

struct PGLog : DoutPrefixProvider {
  std::ostream& gen_prefix(std::ostream& out) const override {
    return out;
  }
  unsigned get_subsys() const override {
    return static_cast<unsigned>(ceph_subsys_osd);
  }
  CephContext *get_cct() const override {
    return cct;
  }

  enum NonPrimary : bool {
    NonPrimaryFalse = false,
    NonPrimaryTrue = true
  };

  ////////////////////////////// sub classes //////////////////////////////
  struct LogEntryHandler {
    virtual void rollback(
      const pg_log_entry_t &entry) = 0;
    virtual void rollforward(
      const pg_log_entry_t &entry) = 0;
    virtual void trim(
      const pg_log_entry_t &entry) = 0;
    virtual void remove(
      const hobject_t &hoid) = 0;
    virtual void try_stash(
      const hobject_t &hoid,
      version_t v) = 0;
    virtual void partial_write(
        pg_info_t *info,
        eversion_t previous_version,
        const pg_log_entry_t &entry
      ) = 0;
    virtual ~LogEntryHandler() {}
  };
  using LogEntryHandlerRef = std::unique_ptr<LogEntryHandler>;

public:
  /**
   * IndexLog - adds in-memory index of the log, by oid.
   * plus some methods to manipulate it all.
   */
  struct IndexedLog : public pg_log_t {
    mutable std::unordered_map<hobject_t, pg_log_entry_t*> objects;  // ptrs into log.  be careful!
    mutable std::unordered_map<osd_reqid_t, pg_log_entry_t*> caller_ops;
    mutable std::unordered_multimap<osd_reqid_t, pg_log_entry_t*> extra_caller_ops;
    mutable std::unordered_map<osd_reqid_t, pg_log_dup_t*> dup_index;

    // recovery pointers
    std::list<pg_log_entry_t>::iterator complete_to; // not inclusive of referenced item
    version_t last_requested = 0;               // last object requested by primary

    //
  private:
    mutable __u16 indexed_data = 0;
    /**
     * rollback_info_trimmed_to_riter points to the first log entry <=
     * rollback_info_trimmed_to
     *
     * It's a reverse_iterator because rend() is a natural representation for
     * tail, and rbegin() works nicely for head.
     */
    mempool::osd_pglog::list<pg_log_entry_t>::reverse_iterator
      rollback_info_trimmed_to_riter;

    /*
     * return true if we need to mark the pglog as dirty
     */
    template <typename F>
    bool advance_can_rollback_to(eversion_t to, F &&f) {
      bool dirty_log = to > can_rollback_to || to > rollback_info_trimmed_to;
      if (dirty_log) {
	if (to > can_rollback_to)
	  can_rollback_to = to;

	if (to > rollback_info_trimmed_to)
	  rollback_info_trimmed_to = to;
      }

      eversion_t previous_version;
      if (rollback_info_trimmed_to_riter == log.rend()) {
	previous_version = tail;
      } else {
	previous_version = rollback_info_trimmed_to_riter->version;
      }
      while (rollback_info_trimmed_to_riter != log.rbegin()) {
	--rollback_info_trimmed_to_riter;
	if (rollback_info_trimmed_to_riter->version > rollback_info_trimmed_to) {
	  ++rollback_info_trimmed_to_riter;
	  break;
	}
	f(*rollback_info_trimmed_to_riter, previous_version);
	previous_version = rollback_info_trimmed_to_riter->version;
      }

      return dirty_log;
    }

    void reset_rollback_info_trimmed_to_riter() {
      rollback_info_trimmed_to_riter = log.rbegin();
      while (rollback_info_trimmed_to_riter != log.rend() &&
	     rollback_info_trimmed_to_riter->version > rollback_info_trimmed_to)
	++rollback_info_trimmed_to_riter;
    }

    // indexes objects, caller ops and extra caller ops
  public:
    IndexedLog() :
      complete_to(log.end()),
      last_requested(0),
      indexed_data(0),
      rollback_info_trimmed_to_riter(log.rbegin())
    { }

    template <typename... Args>
    explicit IndexedLog(Args&&... args) :
      pg_log_t(std::forward<Args>(args)...),
      complete_to(log.end()),
      last_requested(0),
      indexed_data(0),
      rollback_info_trimmed_to_riter(log.rbegin())
    {
      reset_rollback_info_trimmed_to_riter();
      index();
    }

    IndexedLog(const IndexedLog &rhs) :
      pg_log_t(rhs),
      complete_to(log.end()),
      last_requested(rhs.last_requested),
      indexed_data(0),
      rollback_info_trimmed_to_riter(log.rbegin())
    {
      reset_rollback_info_trimmed_to_riter();
      index(rhs.indexed_data);
    }

    IndexedLog &operator=(const IndexedLog &rhs) {
      this->~IndexedLog();
      new (this) IndexedLog(rhs);
      return *this;
    }

    void trim_rollback_info_to(eversion_t to, pg_info_t *info, LogEntryHandler *h) {
      advance_can_rollback_to(
	to,
	[&](pg_log_entry_t &entry, eversion_t previous_version) {
	  h->trim(entry);
	  h->partial_write(info, previous_version, entry);
	});
    }
    bool roll_forward_to(eversion_t to, pg_info_t *info, LogEntryHandler *h) {
      return advance_can_rollback_to(
	to,
	[&](pg_log_entry_t &entry, eversion_t previous_version) {
	  h->rollforward(entry);
	  h->partial_write(info, previous_version, entry);
	});
    }

    void skip_can_rollback_to_to_head(pg_info_t *info, LogEntryHandler *h) {
      advance_can_rollback_to(
	head,
        [&](pg_log_entry_t &entry, eversion_t previous_version) {
	  h->partial_write(info, previous_version, entry);
	});
    }

    void skip_can_rollback_to_to_head() {
      advance_can_rollback_to(head, [&](const pg_log_entry_t &entry,
					eversion_t previous_version) {});
    }

    mempool::osd_pglog::list<pg_log_entry_t> rewind_from_head(eversion_t newhead, bool *dirty_log = nullptr) {
      auto divergent = pg_log_t::rewind_from_head(newhead, dirty_log);
      index();
      reset_rollback_info_trimmed_to_riter();
      return divergent;
    }

    template <typename T>
    void scan_log_after(
      const eversion_t &bound, ///< [in] scan entries > bound
      T &&f) const {
      auto iter = log.rbegin();
      while (iter != log.rend() && iter->version > bound)
	++iter;

      while (true) {
	if (iter == log.rbegin())
	  break;
	f(*(--iter));
      }
    }

    /****/
    void claim_log_and_clear_rollback_info(const pg_log_t& o) {
      // we must have already trimmed the old entries
      ceph_assert(rollback_info_trimmed_to == head);
      ceph_assert(rollback_info_trimmed_to_riter == log.rbegin());

      *this = IndexedLog(o);

      skip_can_rollback_to_to_head();
      index();
    }

    void split_out_child(
      pg_t child_pgid,
      unsigned split_bits,
      IndexedLog *target);

    void split_pwlc(pg_info_t &info) {
      eversion_t previous_version;
      if (rollback_info_trimmed_to_riter == log.rend()) {
	previous_version = tail;
      } else {
	previous_version = rollback_info_trimmed_to_riter->version;
      }
      // When a split occurs log entries are divided between the two PGs,
      // this can leave pwlc refering to entries that are no longer in this
      // PG log. Update pwlc so it is not beyond the last entry in the log.
      // Non-primary shards which don't have a full log may rollback pwlc
      // too far, but this will get corrected by the primary shard when
      // activating shards later in peering.
      for (auto & [shard, versionrange] : info.partial_writes_last_complete) {
	auto &&[old_v,  new_v] = versionrange;
	if (new_v > previous_version) {
	  new_v = previous_version;
	  if (old_v > new_v) {
	    old_v = new_v;
	  }
	}
      }
    }

    void zero() {
      // we must have already trimmed the old entries
      ceph_assert(rollback_info_trimmed_to == head);
      ceph_assert(rollback_info_trimmed_to_riter == log.rbegin());

      unindex();
      pg_log_t::clear();
      rollback_info_trimmed_to_riter = log.rbegin();
      reset_recovery_pointers();
    }
    void clear() {
      skip_can_rollback_to_to_head();
      zero();
    }
    void reset_recovery_pointers() {
      complete_to = log.end();
      last_requested = 0;
    }

    bool logged_object(const hobject_t& oid) const {
      if (!(indexed_data & PGLOG_INDEXED_OBJECTS)) {
         index_objects();
      }
      return objects.count(oid);
    }

    bool logged_req(const osd_reqid_t &r) const {
      if (!(indexed_data & PGLOG_INDEXED_CALLER_OPS)) {
        index_caller_ops();
      }
      if (!caller_ops.count(r)) {
        if (!(indexed_data & PGLOG_INDEXED_EXTRA_CALLER_OPS)) {
          index_extra_caller_ops();
        }
        return extra_caller_ops.count(r);
      }
      return true;
    }

    bool get_request(
      const osd_reqid_t &r,
      eversion_t *version,
      version_t *user_version,
      int *return_code,
      std::vector<pg_log_op_return_item_t> *op_returns) const
    {
      ceph_assert(version);
      ceph_assert(user_version);
      ceph_assert(return_code);
      if (!(indexed_data & PGLOG_INDEXED_CALLER_OPS)) {
        index_caller_ops();
      }
      auto p = caller_ops.find(r);
      if (p != caller_ops.end()) {
	*version = p->second->version;
	*user_version = p->second->user_version;
	*return_code = p->second->return_code;
	*op_returns = p->second->op_returns;
	return true;
      }

      // warning: we will return *a* request for this reqid, but not
      // necessarily the most recent.
      if (!(indexed_data & PGLOG_INDEXED_EXTRA_CALLER_OPS)) {
        index_extra_caller_ops();
      }
      p = extra_caller_ops.find(r);
      if (p != extra_caller_ops.end()) {
	uint32_t idx = 0;
	for (auto i = p->second->extra_reqids.begin();
	     i != p->second->extra_reqids.end();
	     ++idx, ++i) {
	  if (i->first == r) {
	    *version = p->second->version;
	    *user_version = i->second;
	    *return_code = p->second->return_code;
	    *op_returns = p->second->op_returns;
	    if (*return_code >= 0) {
	      auto it = p->second->extra_reqid_return_codes.find(idx);
	      if (it != p->second->extra_reqid_return_codes.end()) {
		*return_code = it->second;
	      }
	    }
	    return true;
	  }
	}
	ceph_abort_msg("in extra_caller_ops but not extra_reqids");
      }

      if (!(indexed_data & PGLOG_INDEXED_DUPS)) {
        index_dups();
      }
      auto q = dup_index.find(r);
      if (q != dup_index.end()) {
	*version = q->second->version;
	*user_version = q->second->user_version;
	*return_code = q->second->return_code;
	*op_returns = q->second->op_returns;
	return true;
      }

      return false;
    }

    bool has_write_since(const hobject_t &oid, const eversion_t &bound) const {
      for (auto i = log.rbegin(); i != log.rend(); ++i) {
	if (i->version <= bound)
	  return false;
	if (i->soid.get_head() == oid.get_head())
	  return true;
      }
      return false;
    }

    /// get a (bounded) std::list of recent reqids for the given object
    void get_object_reqids(const hobject_t& oid, unsigned max,
			   mempool::osd_pglog::vector<std::pair<osd_reqid_t, version_t> > *pls,
			   mempool::osd_pglog::map<uint32_t, int> *return_codes) const {
       // make sure object is present at least once before we do an
       // O(n) search.
      if (!(indexed_data & PGLOG_INDEXED_OBJECTS)) {
        index_objects();
      }
      if (objects.count(oid) == 0)
	return;

      for (auto i = log.rbegin(); i != log.rend(); ++i) {
	if (i->soid == oid) {
	  if (i->reqid_is_indexed()) {
	    if (i->op == pg_log_entry_t::ERROR) {
	      // propagate op errors to the cache tier's PG log
	      return_codes->emplace(pls->size(), i->return_code);
	    }
	    pls->push_back(std::make_pair(i->reqid, i->user_version));
	  }

	  pls->insert(pls->end(), i->extra_reqids.begin(), i->extra_reqids.end());
	  if (pls->size() >= max) {
	    if (pls->size() > max) {
	      pls->resize(max);
	    }
	    return;
	  }
	}
      }
    }

    void index(__u16 to_index = PGLOG_INDEXED_ALL) const {
      // if to_index is 0, no need to run any of this code, especially
      // loop below; this can happen with copy constructor for
      // IndexedLog (and indirectly through assignment operator)
      if (!to_index) return;

      if (to_index & PGLOG_INDEXED_OBJECTS)
	objects.clear();
      if (to_index & PGLOG_INDEXED_CALLER_OPS)
	caller_ops.clear();
      if (to_index & PGLOG_INDEXED_EXTRA_CALLER_OPS)
	extra_caller_ops.clear();
      if (to_index & PGLOG_INDEXED_DUPS) {
	dup_index.clear();
	for (auto& i : dups) {
	  dup_index[i.reqid] = const_cast<pg_log_dup_t*>(&i);
	}
      }

      constexpr __u16 any_log_entry_index =
	PGLOG_INDEXED_OBJECTS |
	PGLOG_INDEXED_CALLER_OPS |
	PGLOG_INDEXED_EXTRA_CALLER_OPS;

      if (to_index & any_log_entry_index) {
	for (auto i = log.begin(); i != log.end(); ++i) {
	  if (to_index & PGLOG_INDEXED_OBJECTS) {
	    if (i->object_is_indexed()) {
	      objects[i->soid] = const_cast<pg_log_entry_t*>(&(*i));
	    }
	  }

	  if (to_index & PGLOG_INDEXED_CALLER_OPS) {
	    if (i->reqid_is_indexed()) {
	      caller_ops[i->reqid] = const_cast<pg_log_entry_t*>(&(*i));
	    }
	  }

	  if (to_index & PGLOG_INDEXED_EXTRA_CALLER_OPS) {
	    for (auto j = i->extra_reqids.begin();
		 j != i->extra_reqids.end();
		 ++j) {
	      extra_caller_ops.insert(
		std::make_pair(j->first, const_cast<pg_log_entry_t*>(&(*i))));
	    }
	  }
	}
      }

      indexed_data |= to_index;
    }

    void index_objects() const {
      index(PGLOG_INDEXED_OBJECTS);
    }

    void index_caller_ops() const {
      index(PGLOG_INDEXED_CALLER_OPS);
    }

    void index_extra_caller_ops() const {
      index(PGLOG_INDEXED_EXTRA_CALLER_OPS);
    }

    void index_dups() const {
      index(PGLOG_INDEXED_DUPS);
    }

    void index(pg_log_entry_t& e) {
      if ((indexed_data & PGLOG_INDEXED_OBJECTS) && e.object_is_indexed()) {
        if (objects.count(e.soid) == 0 ||
            objects[e.soid]->version < e.version)
          objects[e.soid] = &e;
      }
      if (indexed_data & PGLOG_INDEXED_CALLER_OPS) {
	// divergent merge_log indexes new before unindexing old
        if (e.reqid_is_indexed()) {
	  caller_ops[e.reqid] = &e;
        }
      }
      if (indexed_data & PGLOG_INDEXED_EXTRA_CALLER_OPS) {
        for (auto j = e.extra_reqids.begin();
	     j != e.extra_reqids.end();
	     ++j) {
	  extra_caller_ops.insert(std::make_pair(j->first, &e));
        }
      }
    }

    void unindex() {
      objects.clear();
      caller_ops.clear();
      extra_caller_ops.clear();
      dup_index.clear();
      indexed_data = 0;
    }

    void unindex(const pg_log_entry_t& e) {
      // NOTE: this only works if we remove from the _tail_ of the log!
      if (indexed_data & PGLOG_INDEXED_OBJECTS) {
	auto it = objects.find(e.soid);
        if (it != objects.end() && it->second->version == e.version)
          objects.erase(it);
      }
      if (e.reqid_is_indexed()) {
        if (indexed_data & PGLOG_INDEXED_CALLER_OPS) {
	  auto it = caller_ops.find(e.reqid);
	  // divergent merge_log indexes new before unindexing old
          if (it != caller_ops.end() && it->second == &e)
            caller_ops.erase(it);
        }
      }
      if (indexed_data & PGLOG_INDEXED_EXTRA_CALLER_OPS) {
        for (auto j = e.extra_reqids.begin();
             j != e.extra_reqids.end();
             ++j) {
          for (auto k = extra_caller_ops.find(j->first);
               k != extra_caller_ops.end() && k->first == j->first;
               ++k) {
            if (k->second == &e) {
              extra_caller_ops.erase(k);
              break;
            }
          }
        }
      }
    }

    void index(pg_log_dup_t& e) {
      if (indexed_data & PGLOG_INDEXED_DUPS) {
	dup_index[e.reqid] = &e;
      }
    }

    void unindex(const pg_log_dup_t& e) {
      if (indexed_data & PGLOG_INDEXED_DUPS) {
	auto i = dup_index.find(e.reqid);
	if (i != dup_index.end()) {
	  dup_index.erase(i);
	}
      }
    }

    // actors
    void add(const pg_log_entry_t& e, enum NonPrimary nonprimary, bool applied, pg_info_t *info, LogEntryHandler *h) {
      if (!applied) {
        if (!nonprimary) {
          ceph_assert(get_can_rollback_to() == head);
        } else {
          ceph_assert(get_can_rollback_to() >= head);
        }
      }

      // make sure our buffers don't pin bigger buffers
      e.mod_desc.trim_bl();

      // add to log
      log.push_back(e);

      // riter previously pointed to the previous entry
      if (rollback_info_trimmed_to_riter == log.rbegin())
	++rollback_info_trimmed_to_riter;

      ceph_assert(e.version > head);
      ceph_assert(head.version == 0 || e.version.version > head.version);
      head = e.version;

      // to our index
      if ((indexed_data & PGLOG_INDEXED_OBJECTS) && e.object_is_indexed()) {
        objects[e.soid] = &(log.back());
      }
      if (indexed_data & PGLOG_INDEXED_CALLER_OPS) {
        if (e.reqid_is_indexed()) {
	  caller_ops[e.reqid] = &(log.back());
        }
      }

      if (indexed_data & PGLOG_INDEXED_EXTRA_CALLER_OPS) {
        for (auto j = e.extra_reqids.begin();
	     j != e.extra_reqids.end();
	     ++j) {
	  extra_caller_ops.insert(std::make_pair(j->first, &(log.back())));
        }
      }

      if (!applied) {
	skip_can_rollback_to_to_head(info, h);
      }
    } // add

    // nonprimary and applied must either both be provided or neither. If
    // neither is provided applied = true and the nonprimary is irrelevant.
    void add(const pg_log_entry_t& e) {
      add(e, NonPrimaryFalse, true, nullptr, nullptr);
    }

    void trim(
      CephContext* cct,
      eversion_t s,
      std::set<eversion_t> *trimmed,
      std::set<std::string>* trimmed_dups,
      eversion_t *write_from_dups);

    std::ostream& print(std::ostream& out) const;
  }; // IndexedLog


protected:
  //////////////////// data members ////////////////////

  pg_missing_tracker_t missing;
  IndexedLog  log;

  eversion_t dirty_to;         ///< must clear/writeout all keys <= dirty_to
  eversion_t dirty_from;       ///< must clear/writeout all keys >= dirty_from
  eversion_t writeout_from;    ///< must writout keys >= writeout_from
  std::set<eversion_t> trimmed;     ///< must clear keys in trimmed
  eversion_t dirty_to_dups;    ///< must clear/writeout all dups <= dirty_to_dups
  eversion_t dirty_from_dups;  ///< must clear/writeout all dups >= dirty_from_dups
  eversion_t write_from_dups;  ///< must write keys >= write_from_dups
  std::set<std::string> trimmed_dups;    ///< must clear keys in trimmed_dups
  CephContext *cct;
  bool pg_log_debug;
  /// Log is clean on [dirty_to, dirty_from)
  bool touched_log;
  bool dirty_log;
  bool clear_divergent_priors;
  bool may_include_deletes_in_missing_dirty = false;

  void mark_dirty_to(eversion_t to) {
    if (to > dirty_to)
      dirty_to = to;
  }
  void mark_dirty_from(eversion_t from) {
    if (from < dirty_from)
      dirty_from = from;
  }
  void mark_writeout_from(eversion_t from) {
    if (from < writeout_from)
      writeout_from = from;
  }
  void mark_dirty_to_dups(eversion_t to) {
    if (to > dirty_to_dups)
      dirty_to_dups = to;
  }
  void mark_dirty_from_dups(eversion_t from) {
    if (from < dirty_from_dups)
      dirty_from_dups = from;
  }
public:
  bool needs_write() const {
    return !touched_log || is_dirty();
  }

  bool is_dirty() const {
    return dirty_log ||
      (dirty_to != eversion_t()) ||
      (dirty_from != eversion_t::max()) ||
      (writeout_from != eversion_t::max()) ||
      !(trimmed.empty()) ||
      !missing.is_clean() ||
      !(trimmed_dups.empty()) ||
      (dirty_to_dups != eversion_t()) ||
      (dirty_from_dups != eversion_t::max()) ||
      (write_from_dups != eversion_t::max()) ||
      may_include_deletes_in_missing_dirty;
  }

  void mark_log_for_rewrite() {
    mark_dirty_to(eversion_t::max());
    mark_dirty_from(eversion_t());
    mark_dirty_to_dups(eversion_t::max());
    mark_dirty_from_dups(eversion_t());
    touched_log = false;
  }
  bool get_may_include_deletes_in_missing_dirty() const {
    return may_include_deletes_in_missing_dirty;
  }
protected:

  /// DEBUG
  std::set<std::string> log_keys_debug;
  static void clear_after(std::set<std::string> *log_keys_debug, const std::string &lb) {
    if (!log_keys_debug)
      return;
    for (auto i = log_keys_debug->lower_bound(lb);
	 i != log_keys_debug->end();
	 log_keys_debug->erase(i++));
  }
  static void clear_up_to(std::set<std::string> *log_keys_debug, const std::string &ub) {
    if (!log_keys_debug)
      return;
    for (auto i = log_keys_debug->begin();
	 i != log_keys_debug->end() && *i < ub;
	 log_keys_debug->erase(i++));
  }

  void check();
  void undirty() {
    dirty_to = eversion_t();
    dirty_from = eversion_t::max();
    touched_log = true;
    dirty_log = false;
    trimmed.clear();
    trimmed_dups.clear();
    writeout_from = eversion_t::max();
    check();
    missing.flush();
    dirty_to_dups = eversion_t();
    dirty_from_dups = eversion_t::max();
    write_from_dups = eversion_t::max();
  }
public:

  // cppcheck-suppress noExplicitConstructor
  PGLog(CephContext *cct) :
    dirty_from(eversion_t::max()),
    writeout_from(eversion_t::max()),
    dirty_from_dups(eversion_t::max()),
    write_from_dups(eversion_t::max()),
    cct(cct),
    pg_log_debug(!(cct && !(cct->_conf->osd_debug_pg_log_writeout))),
    touched_log(false),
    dirty_log(false),
    clear_divergent_priors(false)
  { }

  void reset_backfill();

  void clear();

  //////////////////// get or std::set missing ////////////////////

  const pg_missing_tracker_t& get_missing() const { return missing; }

  void missing_add(const hobject_t& oid, eversion_t need, eversion_t have, bool is_delete=false) {
    missing.add(oid, need, have, is_delete);
  }

  void missing_add_next_entry(const pg_log_entry_t& e, const pg_pool_t &pool, shard_id_t shard) {
    missing.add_next_event(e, pool, shard);
  }

  //////////////////// get or std::set log ////////////////////

  const IndexedLog &get_log() const { return log; }

  const eversion_t &get_tail() const { return log.tail; }

  void set_tail(eversion_t tail) { log.tail = tail; }

  const eversion_t &get_head() const { return log.head; }

  void set_head(eversion_t head) { log.head = head; }

  void set_last_requested(version_t last_requested) {
    log.last_requested = last_requested;
  }

  void index() { log.index(); }

  void unindex() { log.unindex(); }

  void add(const pg_log_entry_t& e, enum NonPrimary nonprimary, bool applied, pg_info_t *info, LogEntryHandler *h) {
    mark_writeout_from(e.version);
    log.add(e, nonprimary, applied, info, h);
  }

  void add(const pg_log_entry_t& e) {
    add(e, NonPrimaryFalse, true, nullptr, nullptr);
  }

  void reset_recovery_pointers() { log.reset_recovery_pointers(); }

  static void clear_info_log(
    spg_t pgid,
    ObjectStore::Transaction *t);

  void trim(
    eversion_t trim_to,
    pg_info_t &info,
    bool transaction_applied = true,
    bool async = false);

  void roll_forward_to(
    eversion_t roll_forward_to,
    pg_info_t *info,
    LogEntryHandler *h) {
    if (log.roll_forward_to(
	  roll_forward_to,
	  info,
	  h))
      dirty_log = true;
  }

  eversion_t get_can_rollback_to() const {
    return log.get_can_rollback_to();
  }

  void roll_forward(pg_info_t *info, LogEntryHandler *h) {
    roll_forward_to(
      log.head,
      info,
      h);
  }

  void skip_rollforward(pg_info_t *info, LogEntryHandler *h) {
    // Update pwlc during backfill
    log.skip_can_rollback_to_to_head(info, h);
  }

  //////////////////// get or std::set log & missing ////////////////////

  void reset_backfill_claim_log(const pg_log_t &o, pg_info_t *info, LogEntryHandler *h) {
    log.trim_rollback_info_to(log.head, info, h);
    log.claim_log_and_clear_rollback_info(o);
    missing.clear();
    mark_dirty_to(eversion_t::max());
    mark_dirty_to_dups(eversion_t::max());
  }

  void split_into(
      pg_t child_pgid,
      unsigned split_bits,
      PGLog *opg_log) {
    log.split_out_child(child_pgid, split_bits, &opg_log->log);
    missing.split_into(child_pgid, split_bits, &(opg_log->missing));
    opg_log->mark_dirty_to(eversion_t::max());
    opg_log->mark_dirty_to_dups(eversion_t::max());
    mark_dirty_to(eversion_t::max());
    mark_dirty_to_dups(eversion_t::max());
    if (missing.may_include_deletes) {
      opg_log->set_missing_may_contain_deletes();
    }
  }

  void split_pwlc(pg_info_t &info) {
    log.split_pwlc(info);
  }

  void merge_from(
    const std::vector<PGLog*>& sources,
    eversion_t last_update) {
    unindex();
    missing.clear();

    std::vector<pg_log_t*> slogs;
    for (auto s : sources) {
      slogs.push_back(&s->log);
    }
    log.merge_from(slogs, last_update);

    index();

    mark_log_for_rewrite();
  }

  void recover_got(hobject_t oid, eversion_t v, pg_info_t &info) {
    if (missing.is_missing(oid, v)) {
      missing.got(oid, v);
      info.stats.stats.sum.num_objects_missing = missing.num_missing();

      const bool missing_empty = missing.get_items().empty();

      // raise last_complete?
      if (missing_empty) {
	log.complete_to = log.log.end();
	info.last_complete = info.last_update;
      }
      auto oldest_need = missing.get_oldest_need();
      while (log.complete_to != log.log.end()) {
	if (oldest_need <= log.complete_to->version)
	  break;
        /* If optimizations are enabled, then this might be a partial log. In
         * this case, it is possible to complete all objects before missing
         * objects are all recovered (since we are recovering to a later
         * version). Here we refuse to move last_complete beyond the last
         * entry of the log until all missing have been recovered.
         */
        if (!missing_empty &&
            !log.log.empty() &&
            log.complete_to->version == log.log.back().version)
          break;
	if (info.last_complete < log.complete_to->version)
	  info.last_complete = log.complete_to->version;
	++log.complete_to;
      }
    }

    ceph_assert(log.get_can_rollback_to() >= v);
  }

  void reset_complete_to(pg_info_t *info, bool ec_optimizations_enabled) {
    if (log.log.empty()) // caller is split_into()
      return;
    log.complete_to = log.log.begin();
    ceph_assert(log.complete_to != log.log.end());
    auto oldest_need = missing.get_oldest_need();
    if (oldest_need != eversion_t()) {
      while (log.complete_to->version < oldest_need) {
        ++log.complete_to;
	// partial writes allow a shard which did not participate in a write to
	// have a missing version that is newer that the most recent log entry
	if (log.complete_to == log.log.end()) {
	  // keep complete_to one entry behind the end of the log to stop
	  // code incorrectly using it to deduce that recovery has completed
	  --log.complete_to;
	  break;
	}
        ceph_assert(log.complete_to != log.log.end());
      }
    }
    if (!info)
      return;
    if (log.complete_to == log.log.begin()) {
      // partial writes use last complete to track shards that did not
      // participate in a write - do not reset it unnecessarily
      if (!ec_optimizations_enabled) {
	info->last_complete = eversion_t();
      } else if ((oldest_need != eversion_t()) &&
		 info->last_complete >= oldest_need) {
	info->last_complete = eversion_t(oldest_need.epoch,
					 oldest_need.version - 1);
      }
    } else {
      --log.complete_to;
      info->last_complete = log.complete_to->version;
      ++log.complete_to;
    }
  }

  void activate_not_complete(pg_info_t &info, bool ec_optimizations_enabled) {
    reset_complete_to(&info, ec_optimizations_enabled);
    log.last_requested = 0;
  }

  void proc_replica_log(pg_info_t &oinfo,
			const pg_log_t &olog,
			pg_missing_t& omissing, pg_shard_t from,
			bool ec_optimizations_enabled) const;

  void set_missing_may_contain_deletes() {
    missing.may_include_deletes = true;
    may_include_deletes_in_missing_dirty = true;
  }

  void rebuild_missing_set_with_deletes(ObjectStore *store,
					ObjectStore::CollectionHandle& ch,
					const pg_info_t &info);

protected:
  static void split_by_object(
    mempool::osd_pglog::list<pg_log_entry_t> &entries,
    std::map<hobject_t, mempool::osd_pglog::list<pg_log_entry_t>> *out_entries) {
    while (!entries.empty()) {
      auto &out_list = (*out_entries)[entries.front().soid];
      out_list.splice(out_list.end(), entries, entries.begin());
    }
  }

  /**
   * _merge_object_divergent_entries
   *
   * There are 5 distinct cases:
   * 1) There is a more recent update: in this case we assume we adjusted the
   *    store and missing during merge_log
   * 2) The first entry in the divergent sequence is a create.  This might
   *    either be because the object is a clone or because prior_version is
   *    eversion_t().  In this case the object does not exist and we must
   *    adjust missing and the store to match.
   * 3) We are currently missing the object.  In this case, we adjust the
   *    missing to our prior_version taking care to add a divergent_prior
   *    if necessary
   * 4) We can rollback all of the entries.  In this case, we do so using
   *    the rollbacker and return -- the object does not go into missing.
   * 5) We cannot rollback at least 1 of the entries.  In this case, we
   *    clear the object out of the store and add a missing entry at
   *    prior_version taking care to add a divergent_prior if
   *    necessary.
   */
  template <typename missing_type>
  static void _merge_object_divergent_entries(
    const IndexedLog &log,               ///< [in] log to merge against
    const hobject_t &hoid,               ///< [in] object we are merging
    const mempool::osd_pglog::list<pg_log_entry_t> &orig_entries, ///< [in] entries for hoid to merge
    const pg_info_t &info,              ///< [in] info for merging entries
    eversion_t olog_can_rollback_to,     ///< [in] rollback boundary of input InedexedLog
    missing_type &missing,               ///< [in,out] missing to adjust, use
    LogEntryHandler *rollbacker,         ///< [in] optional rollbacker object
    bool ec_optimizations_enabled,       ///< [in] relax asserts for allow_ec_optimzations pools
    const DoutPrefixProvider *dpp        ///< [in] logging provider
    ) {
    ldpp_dout(dpp, 20) << __func__ << ": merging hoid " << hoid
		       << " entries: " << orig_entries << dendl;

    if (hoid > info.last_backfill) {
      ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid << " after last_backfill"
			 << dendl;
      return;
    }

    // entries is non-empty
    ceph_assert(!orig_entries.empty());
    // strip out and ignore ERROR entries
    mempool::osd_pglog::list<pg_log_entry_t> entries;
    eversion_t last;
    bool seen_non_error = false;
    for (auto i = orig_entries.begin();
	 i != orig_entries.end();
	 ++i) {
      // all entries are on hoid
      ceph_assert(i->soid == hoid);
      // did not see error entries before this entry and this entry is not error
      // then this entry is the first non error entry
      bool first_non_error = ! seen_non_error && ! i->is_error();
      if (! i->is_error() ) {
        // see a non error entry now
        seen_non_error = true;
      }
      
      // No need to check the first entry since it prior_version is unavailable
      // in the std::list
      // No need to check if the prior_version is the minimal version
      // No need to check the first non-error entry since the leading error
      // entries are not its prior version
      if (i != orig_entries.begin() && i->prior_version != eversion_t() &&
          ! first_non_error) {
	// in increasing order of version
	ceph_assert(i->version > last);
	// prior_version correct (unless it is an ERROR entry)
	if (ec_optimizations_enabled) {
	  // With partial writes prior_verson may be > last because of
	  // skipped log entries
	  ceph_assert(i->prior_version >= last || i->is_error());
	} else {
	  ceph_assert(i->prior_version == last || i->is_error());
	}
      }
      if (i->is_error()) {
	ldpp_dout(dpp, 20) << __func__ << ": ignoring " << *i << dendl;
      } else {
	ldpp_dout(dpp, 20) << __func__ << ": keeping " << *i << dendl;
	entries.push_back(*i);
	last = i->version;
      }
    }
    if (entries.empty()) {
      ldpp_dout(dpp, 10) << __func__ << ": no non-ERROR entries" << dendl;
      return;
    }

    const eversion_t prior_version = entries.begin()->prior_version;
    const eversion_t first_divergent_update = entries.begin()->version;
    const eversion_t last_divergent_update = entries.rbegin()->version;
    const bool object_not_in_store =
      !missing.is_missing(hoid) &&
      entries.rbegin()->is_delete();
    ldpp_dout(dpp, 10) << __func__ << ": hoid " << " object_not_in_store: "
                       << object_not_in_store << dendl;
    ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
		       << " prior_version: " << prior_version
		       << " first_divergent_update: " << first_divergent_update
		       << " last_divergent_update: " << last_divergent_update
		       << dendl;

    auto objiter = log.objects.find(hoid);
    if (objiter != log.objects.end() &&
	objiter->second->version >= first_divergent_update) {
      /// Case 1)
      ldpp_dout(dpp, 10) << __func__ << ": more recent entry found: "
			 << *objiter->second << ", already merged" << dendl;

      ceph_assert(objiter->second->version > last_divergent_update);

      // ensure missing has been updated appropriately
      if (objiter->second->is_update() ||
	  (missing.may_include_deletes && objiter->second->is_delete())) {
	if (ec_optimizations_enabled) {
	  // relax the assert for partial writes - missing may be newer than the
	  // most recent log entry
	  ceph_assert(missing.is_missing(hoid) &&
		      missing.get_items().at(hoid).need >= objiter->second->version);
	} else {
	  ceph_assert(missing.is_missing(hoid) &&
		      missing.get_items().at(hoid).need == objiter->second->version);
	}
      } else {
	ceph_assert(!missing.is_missing(hoid));
      }
      missing.revise_have(hoid, eversion_t());
      missing.mark_fully_dirty(hoid);
      if (rollbacker) {
	if (!object_not_in_store) {
	  rollbacker->remove(hoid);
	}
	for (auto &&i: entries) {
	  rollbacker->trim(i);
	}
      }
      return;
    }

    ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
		       <<" has no more recent entries in log" << dendl;
    if (prior_version == eversion_t() || entries.front().is_clone()) {
      /// Case 2)
      ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
			 << " prior_version or op type indicates creation,"
			 << " deleting"
			 << dendl;
      if (missing.is_missing(hoid))
	missing.rm(missing.get_items().find(hoid));
      if (rollbacker) {
	if (!object_not_in_store) {
	  rollbacker->remove(hoid);
	}
	for (auto &&i: entries) {
	  rollbacker->trim(i);
	}
      }
      return;
    }

    if (missing.is_missing(hoid)) {
      /// Case 3)
      ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
			 << " missing, " << missing.get_items().at(hoid)
			 << " adjusting" << dendl;

      if (missing.get_items().at(hoid).have == prior_version) {
	ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
			   << " missing.have is prior_version " << prior_version
			   << " removing from missing" << dendl;
	missing.rm(missing.get_items().find(hoid));
      } else {
	ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
			   << " missing.have is " << missing.get_items().at(hoid).have
			   << ", adjusting" << dendl;
	missing.revise_need(hoid, prior_version, false);
	if (prior_version <= info.log_tail) {
	  ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
			     << " prior_version " << prior_version
			     << " <= info.log_tail "
			     << info.log_tail << dendl;
	}
      }
      if (rollbacker) {
	for (auto &&i: entries) {
	  rollbacker->trim(i);
	}
      }
      return;
    }

    ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
		       << " must be rolled back or recovered,"
		       << " attempting to rollback"
		       << dendl;
    bool can_rollback = true;
    // We are going to make an important decision based on the
    // olog_can_rollback_to value we have received, better known it.
    ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
                       << " olog_can_rollback_to: "
                       << olog_can_rollback_to << dendl;
    /// Distinguish between 4) and 5)
    for (auto i = entries.rbegin(); i != entries.rend(); ++i) {
      if (!i->can_rollback() || i->version <= olog_can_rollback_to) {
	ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid << " cannot rollback "
			   << *i << dendl;
	can_rollback = false;
	break;
      }
    }

    if (can_rollback) {
      /// Case 4)
      for (auto i = entries.rbegin(); i != entries.rend(); ++i) {
	ceph_assert(i->can_rollback() && i->version > olog_can_rollback_to);
	ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
			   << " rolling back " << *i << dendl;
	if (rollbacker)
	  rollbacker->rollback(*i);
      }
      ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
			 << " rolled back" << dendl;
      return;
    } else {
      /// Case 5)
      ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid << " cannot roll back, "
			 << "removing and adding to missing" << dendl;
      if (rollbacker) {
	if (!object_not_in_store)
	  rollbacker->remove(hoid);
	for (auto &&i: entries) {
	  rollbacker->trim(i);
	}
      }
      missing.add(hoid, prior_version, eversion_t(), false);
      if (prior_version <= info.log_tail) {
	ldpp_dout(dpp, 10) << __func__ << ": hoid " << hoid
			   << " prior_version " << prior_version
			   << " <= info.log_tail "
			   << info.log_tail << dendl;
      }
    }
  }

  /// Merge all entries using above
  template <typename missing_type>
  static void _merge_divergent_entries(
    const IndexedLog &log,               ///< [in] log to merge against
    mempool::osd_pglog::list<pg_log_entry_t> &entries,       ///< [in] entries to merge
    const pg_info_t &oinfo,              ///< [in] info for merging entries
    eversion_t olog_can_rollback_to,     ///< [in] rollback boundary of input IndexedLog
    missing_type &omissing,              ///< [in,out] missing to adjust, use
    LogEntryHandler *rollbacker,         ///< [in] optional rollbacker object
    bool ec_optimizations_enabled,       ///< [in] relax asserts for allow_ec_optimzations pools
    const DoutPrefixProvider *dpp        ///< [in] logging provider
    ) {
    std::map<hobject_t, mempool::osd_pglog::list<pg_log_entry_t> > split;
    split_by_object(entries, &split);
    for (auto i = split.begin(); i != split.end(); ++i) {
      _merge_object_divergent_entries(
	log,
	i->first,
	i->second,
	oinfo,
	olog_can_rollback_to,
	omissing,
	rollbacker,
	ec_optimizations_enabled,
	dpp);
    }
  }

  /**
   * Exists for use in TestPGLog for simply testing single divergent log
   * cases
   */
  void merge_old_entry(
    ObjectStore::Transaction& t,
    const pg_log_entry_t& oe,
    const pg_info_t& info,
    LogEntryHandler *rollbacker) {
    mempool::osd_pglog::list<pg_log_entry_t> entries;
    entries.push_back(oe);
    _merge_object_divergent_entries(
      log,
      oe.soid,
      entries,
      info,
      log.get_can_rollback_to(),
      missing,
      rollbacker,
      false, // not allow_ec_optimizations pool
      this);
  }

  bool merge_log_dups(const pg_log_t& olog);

public:

  void rewind_divergent_log(eversion_t newhead,
                            pg_info_t &info,
                            LogEntryHandler *rollbacker,
                            bool &dirty_info,
                            bool &dirty_big_info,
			    bool ec_optimizations_enabled);

  void merge_log(pg_info_t &oinfo,
		 pg_log_t&& olog,
		 pg_shard_t from,
		 pg_info_t &info,
		 const pg_pool_t &pool,
		 pg_shard_t to,
		 LogEntryHandler *rollbacker,
		 bool &dirty_info, bool &dirty_big_info,
		 bool ec_optimizations_enabled);

  template <typename missing_type>
  static bool append_log_entries_update_missing(
    const hobject_t &last_backfill,
    const mempool::osd_pglog::list<pg_log_entry_t> &entries,
    bool maintain_rollback,
    IndexedLog *log,
    missing_type &missing,
    LogEntryHandler *rollbacker,
    const pg_pool_t &pool,
    shard_id_t shard,
    const DoutPrefixProvider *dpp) {
    bool invalidate_stats = false;
    if (log && !entries.empty()) {
      ceph_assert(log->head < entries.begin()->version);
    }
    for (auto p = entries.begin(); p != entries.end(); ++p) {
      invalidate_stats = invalidate_stats || !p->is_error();
      if (log) {
	ldpp_dout(dpp, 20) << "update missing, append " << *p << dendl;
	log->add(*p);
      }
      if (p->soid <= last_backfill &&
	  !p->is_error()) {
	if (missing.may_include_deletes) {
	  missing.add_next_event(*p, pool, shard);
	} else {
	  if (p->is_delete()) {
	    missing.rm(p->soid, p->version);
	  } else {
	    missing.add_next_event(*p, pool, shard);
	  }
	  if (rollbacker) {
	    // hack to match PG::mark_all_unfound_lost
	    if (maintain_rollback && p->is_lost_delete() && p->can_rollback()) {
	      rollbacker->try_stash(p->soid, p->version.version);
	    } else if (p->is_delete()) {
	      rollbacker->remove(p->soid);
	    }
	  }
	}
      }
    }
    return invalidate_stats;
  }
  bool append_new_log_entries(
    const hobject_t &last_backfill,
    const mempool::osd_pglog::list<pg_log_entry_t> &entries,
    pg_info_t *info,
    LogEntryHandler *rollbacker,
    const pg_pool_t &pool,
    shard_id_t shard,
    bool ec_optimizations_enabled) {
    bool invalidate_stats = append_log_entries_update_missing(
      last_backfill,
      entries,
      true,
      &log,
      missing,
      rollbacker,
      pool,
      shard,
      this);
    if (!entries.empty()) {
      mark_writeout_from(entries.begin()->version);
      if (entries.begin()->is_lost_delete()) {
	// hack: since lost deletes queue recovery directly, and don't
	// go through activate_not_complete() again, our complete_to
	// iterator may still point at log.end(). Reset it to point
	// before these new lost_delete entries.  This only occurs
	// when lost+delete entries are initially added, which is
	// always in a std::list of solely lost_delete entries, so it is
	// sufficient to check whether the first entry is a
	// lost_delete
	reset_complete_to(nullptr, ec_optimizations_enabled);
      }
    }
    return invalidate_stats;
  }

  void write_log_and_missing(
    ObjectStore::Transaction& t,
    std::map<std::string,ceph::buffer::list> *km,
    const coll_t& coll,
    const ghobject_t &log_oid,
    bool require_rollback);

  static void write_log_and_missing_wo_missing(
    ObjectStore::Transaction& t,
    std::map<std::string,ceph::buffer::list>* km,
    pg_log_t &log,
    const coll_t& coll,
    const ghobject_t &log_oid, std::map<eversion_t, hobject_t> &divergent_priors,
    bool require_rollback,
    const DoutPrefixProvider *dpp = nullptr);

  static void write_log_and_missing(
    ObjectStore::Transaction& t,
    std::map<std::string,ceph::buffer::list>* km,
    pg_log_t &log,
    const coll_t& coll,
    const ghobject_t &log_oid,
    const pg_missing_tracker_t &missing,
    bool require_rollback,
    bool *rebuilt_missing_set_with_deletes,
    const DoutPrefixProvider *dpp = nullptr);

  static void _write_log_and_missing_wo_missing(
    ObjectStore::Transaction& t,
    std::map<std::string,ceph::buffer::list>* km,
    pg_log_t &log,
    const coll_t& coll, const ghobject_t &log_oid,
    std::map<eversion_t, hobject_t> &divergent_priors,
    eversion_t dirty_to,
    eversion_t dirty_from,
    eversion_t writeout_from,
    bool dirty_divergent_priors,
    bool touch_log,
    bool require_rollback,
    eversion_t dirty_to_dups,
    eversion_t dirty_from_dups,
    eversion_t write_from_dups,
    std::set<std::string> *log_keys_debug,
    const DoutPrefixProvider *dpp = nullptr
    );

  static void _write_log_and_missing(
    ObjectStore::Transaction& t,
    std::map<std::string,ceph::buffer::list>* km,
    pg_log_t &log,
    const coll_t& coll, const ghobject_t &log_oid,
    eversion_t dirty_to,
    eversion_t dirty_from,
    eversion_t writeout_from,
    std::set<eversion_t> &&trimmed,
    std::set<std::string> &&trimmed_dups,
    const pg_missing_tracker_t &missing,
    bool touch_log,
    bool require_rollback,
    bool clear_divergent_priors,
    eversion_t dirty_to_dups,
    eversion_t dirty_from_dups,
    eversion_t write_from_dups,
    bool *may_include_deletes_in_missing_dirty,
    std::set<std::string> *log_keys_debug,
    const DoutPrefixProvider *dpp = nullptr
    );

  void read_log_and_missing(
    ObjectStore *store,
    ObjectStore::CollectionHandle& ch,
    ghobject_t pgmeta_oid,
    const pg_info_t &info,
    std::ostringstream &oss,
    bool tolerate_divergent_missing_log,
    bool ec_optimizations_enabled, // Relax asserts for partial writes
    bool debug_verify_stored_missing = false
    ) {
    return read_log_and_missing(
      cct, store, ch, pgmeta_oid, info,
      log, missing, oss,
      tolerate_divergent_missing_log,
      ec_optimizations_enabled,
      &clear_divergent_priors,
      this,
      (pg_log_debug ? &log_keys_debug : nullptr),
      debug_verify_stored_missing);
  }

  template <typename missing_type>
  static void read_log_and_missing(
    CephContext *cct,
    ObjectStore *store,
    ObjectStore::CollectionHandle &ch,
    ghobject_t pgmeta_oid,
    const pg_info_t &info,
    IndexedLog &log,
    missing_type &missing,
    std::ostringstream &oss,
    bool tolerate_divergent_missing_log,
    bool ec_optimizations_enabled, // Relax asserts for partial writes
    bool *clear_divergent_priors = nullptr,
    const DoutPrefixProvider *dpp = nullptr,
    std::set<std::string> *log_keys_debug = nullptr,
    bool debug_verify_stored_missing = false
    ) {
    ldpp_dout(dpp, 10) << "read_log_and_missing coll " << ch->cid
		       << " " << pgmeta_oid << dendl;
    size_t total_dups = 0;

    // legacy?
    struct stat st;
    int r = store->stat(ch, pgmeta_oid, &st);
    ceph_assert(r == 0);
    ceph_assert(st.st_size == 0);

    // will get overridden below if it had been recorded
    eversion_t on_disk_can_rollback_to = info.last_update;
    eversion_t on_disk_rollback_info_trimmed_to = eversion_t();
    std::map<eversion_t, hobject_t> divergent_priors;
    bool must_rebuild = false;
    missing.may_include_deletes = false;
    std::list<pg_log_entry_t> entries;
    std::list<pg_log_dup_t> dups;
    const auto NUM_DUPS_WARN_THRESHOLD = 2*cct->_conf->osd_pg_log_dups_tracked;
    store->omap_iterate(
      ch, pgmeta_oid, ObjectStore::omap_iter_seek_t::min_lower_bound(),
      [&, NUM_DUPS_WARN_THRESHOLD]
      (std::string_view key, std::string_view value) mutable {
        using ceph::decode;
	// non-log pgmeta_oid keys are prefixed with _; skip those
	if (key[0] == '_') {
	  return ObjectStore::omap_iter_ret_t::NEXT;
	}
	ceph::bufferlist bl;
	bl.append(value);
	// memcopy the value. this is paranoia but no regression since
	// get_omap_iterator. it could fixed with buffer::create_static()
	auto bp = bl.cbegin();
	if (key == "divergent_priors") {
	  decode(divergent_priors, bp);
	  ldpp_dout(dpp, 20) << "read_log_and_missing " << divergent_priors.size()
			     << " divergent_priors" << dendl;
	  must_rebuild = true;
	  debug_verify_stored_missing = false;
	} else if (key == "can_rollback_to") {
	  decode(on_disk_can_rollback_to, bp);
	} else if (key == "rollback_info_trimmed_to") {
	  decode(on_disk_rollback_info_trimmed_to, bp);
	} else if (key == "may_include_deletes_in_missing") {
	  missing.may_include_deletes = true;
	} else if (key.starts_with("missing")) {
	  hobject_t oid;
	  pg_missing_item item;
	  decode(oid, bp);
	  decode(item, bp);
          ldpp_dout(dpp, 20) << "read_log_and_missing " << item << dendl;
	  if (item.is_delete()) {
	    ceph_assert(missing.may_include_deletes);
	  }
	  missing.add(oid, std::move(item));
	} else if (key.starts_with("dup_")) {
	  ++total_dups;
	  pg_log_dup_t dup;
	  decode(dup, bp);
	  if (!dups.empty()) {
	    ceph_assert(dups.back().version < dup.version);
	  }
	  if (dups.size() == NUM_DUPS_WARN_THRESHOLD) {
	    ldpp_dout(dpp, 0) << "read_log_and_missing WARN num of dups exceeded "
			      << NUM_DUPS_WARN_THRESHOLD << "."
			      << " You can be hit by THE DUPS BUG"
			      << " https://tracker.ceph.com/issues/53729."
			      << " Consider ceph-objectstore-tool --op trim-pg-log-dups"
			      << dendl;
	  }
	  dups.push_back(dup);
	} else {
	  pg_log_entry_t e;
	  e.decode_with_checksum(bp);
	  ldpp_dout(dpp, 20) << "read_log_and_missing " << e << dendl;
	  if (!entries.empty()) {
	    pg_log_entry_t last_e(entries.back());
	    ceph_assert(last_e.version.version < e.version.version);
	    ceph_assert(last_e.version.epoch <= e.version.epoch);
	  }
	  entries.push_back(e);
	  if (log_keys_debug)
	    log_keys_debug->insert(e.get_key_name());
	}
	return ObjectStore::omap_iter_ret_t::NEXT;
      });
    if (info.pgid.is_no_shard()) {
      // replicated pool pg does not persist this key
      assert(on_disk_rollback_info_trimmed_to == eversion_t());
      on_disk_rollback_info_trimmed_to = info.last_update;
    }
    log = IndexedLog(
      info.last_update,
      info.log_tail,
      on_disk_can_rollback_to,
      on_disk_rollback_info_trimmed_to,
      std::move(entries),
      std::move(dups));

    if (must_rebuild || debug_verify_stored_missing) {
      // build missing
      if (debug_verify_stored_missing || info.last_complete < info.last_update) {
	ldpp_dout(dpp, 10)
	  << "read_log_and_missing checking for missing items over interval ("
	  << info.last_complete
	  << "," << info.last_update << "]" << dendl;

	std::set<hobject_t> did;
	std::set<hobject_t> checked;
	std::set<hobject_t> skipped;
	for (auto i = log.log.rbegin(); i != log.log.rend(); ++i) {
	  if (i->soid > info.last_backfill)
	    continue;
	  if (i->is_error())
	    continue;
	  if (!i->is_written_shard(info.pgid.shard)) {
	    // optimized EC - partial write that this shard didn't participate in
	    ceph_assert(ec_optimizations_enabled);
	    continue;
	  }
	  if (did.count(i->soid)) continue;
	  did.insert(i->soid);

	  if (!missing.may_include_deletes && i->is_delete())
	    continue;

	  ceph::buffer::list bv;
	  int r = store->getattr(
	    ch,
	    ghobject_t(i->soid, ghobject_t::NO_GEN, info.pgid.shard),
	    OI_ATTR,
	    bv);
	  if (r >= 0) {
	    object_info_t oi(bv);
	    if (oi.version < i->version) {
	      ldpp_dout(dpp, 15) << "read_log_and_missing  missing " << *i
                           << " (have " << oi.version << ")"
                           << " clean_regions " << i->clean_regions << dendl;

	      if (debug_verify_stored_missing) {
		auto miter = missing.get_items().find(i->soid);
		ceph_assert(miter != missing.get_items().end());
		// the 'have' version is reset if an object is deleted,
		// then created again
		if (ec_optimizations_enabled) {
		  // non-primary shards in an optimized pool may not have updates
		  // because of partial writes, which may result in oi.version being
		  // less than have
		  ceph_assert(miter->second.need >= i->version);
		  ceph_assert(miter->second.have >= oi.version || miter->second.have == eversion_t());
		} else {
		  ceph_assert(miter->second.need == i->version);
		  ceph_assert(miter->second.have == oi.version || miter->second.have == eversion_t());
		}
		checked.insert(i->soid);
	      } else {
		missing.add(i->soid, i->version, oi.version, i->is_delete());
	      }
	    }
	  } else {
	    ldpp_dout(dpp, 15) << "read_log_and_missing  missing " << *i << dendl;
	    if (debug_verify_stored_missing) {
	      auto miter = missing.get_items().find(i->soid);
	      if (i->is_delete()) {
		ceph_assert(miter == missing.get_items().end() ||
		       (miter->second.need == i->version &&
			miter->second.have == eversion_t()));
	      } else {
		ceph_assert(miter != missing.get_items().end());
		if (ec_optimizations_enabled) {
		  // Optimized pools do not store log entries for shards that
		  // did not participate in the write, however missing entries
		  // are calculated from full log so may be for a newer version
		  // that the latest log entry
		  ceph_assert(miter->second.need >= i->version);
		} else {
		  ceph_assert(miter->second.need == i->version);
		}
		ceph_assert(miter->second.have == eversion_t());
	      }
	      checked.insert(i->soid);
	    } else {
	      missing.add(i->soid, i->version, eversion_t(), i->is_delete());
	    }
	  }
	}
	if (debug_verify_stored_missing) {
	  for (auto &&i: missing.get_items()) {
	    if (checked.count(i.first))
	      continue;
	    if (i.first > info.last_backfill) {
	      ldpp_dout(dpp, -1) << __func__ << ": invalid missing std::set entry "
				<< "found before last_backfill: "
				<< i.first << " " << i.second
				<< " last_backfill = " << info.last_backfill
				<< dendl;
	      ceph_abort_msg("invalid missing std::set entry found");
	    }
	    ceph::buffer::list bv;
	    int r = store->getattr(
	      ch,
	      ghobject_t(i.first, ghobject_t::NO_GEN, info.pgid.shard),
	      OI_ATTR,
	      bv);
	    if (r >= 0) {
	      object_info_t oi(bv);
	      ceph_assert(oi.version == i.second.have || eversion_t() == i.second.have);
	    } else {
	      ceph_assert(i.second.is_delete() || eversion_t() == i.second.have);
	    }
	  }
	} else {
	  ceph_assert(must_rebuild);
	  for (auto i = divergent_priors.rbegin();
	       i != divergent_priors.rend();
	       ++i) {
	    if (i->first <= info.last_complete) break;
	    if (i->second > info.last_backfill)
	      continue;
	    if (did.count(i->second)) continue;
	    did.insert(i->second);
	    ceph::buffer::list bv;
	    int r = store->getattr(
	      ch,
	      ghobject_t(i->second, ghobject_t::NO_GEN, info.pgid.shard),
	      OI_ATTR,
	      bv);
	    if (r >= 0) {
	      object_info_t oi(bv);
	      /**
		 * 1) we see this entry in the divergent priors mapping
		 * 2) we didn't see an entry for this object in the log
		 *
		 * From 1 & 2 we know that either the object does not exist
		 * or it is at the version specified in the divergent_priors
		 * map since the object would have been deleted atomically
		 * with the addition of the divergent_priors entry, an older
		 * version would not have been recovered, and a newer version
		 * would show up in the log above.
		 */
	      /**
		 * Unfortunately the assessment above is incorrect because of
		 * http://tracker.ceph.com/issues/17916 (we were incorrectly
		 * not removing the divergent_priors std::set from disk state!),
		 * so let's check that.
		 */
	      if (oi.version > i->first && tolerate_divergent_missing_log) {
		ldpp_dout(dpp, 0) << "read_log divergent_priors entry (" << *i
				  << ") inconsistent with disk state (" <<  oi
				  << "), assuming it is tracker.ceph.com/issues/17916"
				  << dendl;
	      } else {
		ceph_assert(oi.version == i->first);
	      }
	    } else {
	      ldpp_dout(dpp, 15) << "read_log_and_missing  missing " << *i << dendl;
	      missing.add(i->second, i->first, eversion_t(), false);
	    }
	  }
	}
	if (clear_divergent_priors)
	  (*clear_divergent_priors) = true;
      }
    }

    if (!must_rebuild) {
      if (clear_divergent_priors)
	(*clear_divergent_priors) = false;
      missing.flush();
    }
    ldpp_dout(dpp, 10) << "read_log_and_missing done coll " << ch->cid
		       << " total_dups=" << total_dups
		       << " log.dups.size()=" << log.dups.size() << dendl;
  } // static read_log_and_missing

#ifdef WITH_CRIMSON
  seastar::future<> read_log_and_missing_crimson(
    crimson::os::FuturizedStore::Shard &store,
    crimson::os::CollectionRef ch,
    const pg_info_t &info,
    ghobject_t pgmeta_oid
    ) {
    return read_log_and_missing_crimson(
      store, ch, info,
      log, (pg_log_debug ? &log_keys_debug : nullptr),
      missing, pgmeta_oid, this);
  }

  static seastar::future<> read_log_and_missing_crimson(
    crimson::os::FuturizedStore::Shard &store,
    crimson::os::CollectionRef ch,
    const pg_info_t &info,
    IndexedLog &log,
    std::set<std::string>* log_keys_debug,
    pg_missing_tracker_t &missing,
    ghobject_t pgmeta_oid,
    const DoutPrefixProvider *dpp = nullptr);

#endif

}; // struct PGLog
