// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#ifndef CEPH_LIBRBD_IMAGECTX_H
#define CEPH_LIBRBD_IMAGECTX_H

#include "include/int_types.h"

#include <atomic>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>

#include "common/Timer.h"
#include "common/ceph_mutex.h"
#include "common/config_proxy.h"
#include "common/event_socket.h"
#include "common/Readahead.h"
#include "common/snap_types.h"
#include "common/zipkin_trace.h"

#include "include/common_fwd.h"
#include "include/buffer_fwd.h"
#include "include/rbd/librbd.hpp"
#include "include/rbd_types.h"
#include "include/types.h"
#include "include/xlist.h"

#include "cls/rbd/cls_rbd_types.h"
#include "cls/rbd/cls_rbd_client.h"
#include "librbd/AsyncRequest.h"
#include "librbd/Types.h"

#include <boost/lockfree/policies.hpp>
#include <boost/lockfree/queue.hpp>

namespace neorados {
class IOContext;
class RADOS;
} // namespace neorados

namespace librbd {

  struct AsioEngine;
  template <typename> class ConfigWatcher;
  template <typename> class ExclusiveLock;
  template <typename> class ImageState;
  template <typename> class ImageWatcher;
  template <typename> class Journal;
  class LibrbdAdminSocketHook;
  template <typename> class ObjectMap;
  template <typename> class Operations;
  template <typename> class PluginRegistry;

  namespace asio { struct ContextWQ; }
  namespace crypto { class CryptoInterface; }
  namespace exclusive_lock { struct Policy; }
  namespace io {
  class AioCompletion;
  class AsyncOperation;
  template <typename> class CopyupRequest;
  struct ImageDispatcherInterface;
  struct ObjectDispatcherInterface;
  }
  namespace journal { struct Policy; }

  namespace operation {
  template <typename> class ResizeRequest;
  }

  struct ImageCtx {
    typedef std::pair<cls::rbd::SnapshotNamespace, std::string> SnapKey;
    struct SnapKeyComparator {
      inline bool operator()(const SnapKey& lhs, const SnapKey& rhs) const {
        // only compare by namespace type and name
        if (lhs.first.which() != rhs.first.which()) {
          return lhs.first.which() < rhs.first.which();
        }
        return lhs.second < rhs.second;
      }
    };

    static const std::string METADATA_CONF_PREFIX;

    CephContext *cct;
    ConfigProxy config;
    std::set<std::string> config_overrides;

    PerfCounters *perfcounter;
    struct rbd_obj_header_ondisk header;
    ::SnapContext snapc;
    std::vector<librados::snap_t> snaps; // this mirrors snapc.snaps, but is in
                                        // a format librados can understand
    std::map<librados::snap_t, SnapInfo> snap_info;
    std::map<SnapKey, librados::snap_t, SnapKeyComparator> snap_ids;
    uint64_t open_snap_id = CEPH_NOSNAP;
    uint64_t snap_id;
    bool snap_exists; // false if our snap_id was deleted
    // whether the image was opened read-only. cannot be changed after opening
    bool read_only;
    uint32_t read_only_flags = 0U;
    uint32_t read_only_mask = ~0U;

    std::map<rados::cls::lock::locker_id_t,
	     rados::cls::lock::locker_info_t> lockers;
    bool exclusive_locked;
    std::string lock_tag;

    std::string name;
    cls::rbd::SnapshotNamespace snap_namespace;
    std::string snap_name;

    std::shared_ptr<AsioEngine> asio_engine;

    // New ASIO-style RADOS API
    neorados::RADOS& rados_api;

    // Legacy RADOS API
    librados::IoCtx data_ctx;
    librados::IoCtx md_ctx;

    ConfigWatcher<ImageCtx> *config_watcher = nullptr;
    ImageWatcher<ImageCtx> *image_watcher;
    Journal<ImageCtx> *journal;

    /**
     * Lock ordering:
     *
     * owner_lock, image_lock
     * async_op_lock, timestamp_lock
     */
    ceph::shared_mutex owner_lock; // protects exclusive lock leadership updates
    mutable ceph::shared_mutex image_lock; // protects snapshot-related member variables,
                       // features (and associated helper classes), and flags
                       // protects access to the mutable image metadata that
                       // isn't guarded by other locks below, and blocks writes
                       // when held exclusively, so snapshots can be consistent.
                       // Fields guarded include:
                       // total_bytes_read
                       // exclusive_locked
                       // lock_tag
                       // lockers
                       // object_map
                       // parent_md and parent

    ceph::shared_mutex timestamp_lock; // protects (create/access/modify)_timestamp
                                       // and internal diff_iterate_lock_timestamp
    ceph::mutex async_ops_lock; // protects async_ops and async_requests
    ceph::mutex copyup_list_lock; // protects copyup_waiting_list

    unsigned extra_read_flags;  // librados::OPERATION_*

    bool old_format;
    uint8_t order;
    uint64_t size;
    uint64_t features;
    std::string object_prefix;
    char *format_string;
    std::string header_oid;
    std::string id; // only used for new-format images
    ParentImageInfo parent_md;
    ImageCtx *parent;
    ImageCtx *child = nullptr;
    MigrationInfo migration_info;
    cls::rbd::GroupSpec group_spec;
    uint64_t stripe_unit, stripe_count;
    uint64_t flags;
    uint64_t op_features = 0;
    bool operations_disabled = false;
    utime_t create_timestamp;
    utime_t access_timestamp;
    utime_t modify_timestamp;
    utime_t diff_iterate_lock_timestamp;

    file_layout_t layout;

    Readahead readahead;
    std::atomic<uint64_t> total_bytes_read = {0};

    std::map<uint64_t, io::CopyupRequest<ImageCtx>*> copyup_list;

    xlist<io::AsyncOperation*> async_ops;
    xlist<AsyncRequest<>*> async_requests;
    std::list<Context*> async_requests_waiters;

    ImageState<ImageCtx> *state;
    Operations<ImageCtx> *operations;

    ExclusiveLock<ImageCtx> *exclusive_lock;
    ObjectMap<ImageCtx> *object_map;

    xlist<operation::ResizeRequest<ImageCtx>*> resize_reqs;

    io::ImageDispatcherInterface *io_image_dispatcher = nullptr;
    io::ObjectDispatcherInterface *io_object_dispatcher = nullptr;

    asio::ContextWQ *op_work_queue;

    PluginRegistry<ImageCtx>* plugin_registry;

    using Completions = boost::lockfree::queue<io::AioCompletion*>;

    Completions event_socket_completions;
    EventSocket event_socket;

    bool ignore_migrating = false;
    bool disable_zero_copy = false;
    bool enable_sparse_copyup = false;

    /// Cached latency-sensitive configuration settings
    bool non_blocking_aio;
    bool cache;
    uint64_t sparse_read_threshold_bytes;
    uint64_t readahead_max_bytes = 0;
    uint64_t readahead_disable_after_bytes = 0;
    bool clone_copy_on_read;
    bool enable_alloc_hint;
    uint32_t alloc_hint_flags = 0U;
    uint32_t read_flags = 0U;  // librados::OPERATION_*
    uint32_t discard_granularity_bytes = 0;
    bool blkin_trace_all;
    uint64_t mirroring_replay_delay;
    uint64_t mtime_update_interval;
    uint64_t atime_update_interval;

    LibrbdAdminSocketHook *asok_hook;

    exclusive_lock::Policy *exclusive_lock_policy = nullptr;
    journal::Policy *journal_policy = nullptr;

    ZTracer::Endpoint trace_endpoint;

    crypto::CryptoInterface* crypto = nullptr;

    // unit test mock helpers
    static ImageCtx* create(const std::string &image_name,
                            const std::string &image_id,
                            const char *snap, IoCtx& p, bool read_only) {
      return new ImageCtx(image_name, image_id, snap, p, read_only);
    }
    static ImageCtx* create(const std::string &image_name,
                            const std::string &image_id,
                            librados::snap_t snap_id, IoCtx& p,
                            bool read_only) {
      return new ImageCtx(image_name, image_id, snap_id, p, read_only);
    }

    /**
     * Either image_name or image_id must be set.
     * If id is not known, pass the empty std::string,
     * and init() will look it up.
     */
    ImageCtx(const std::string &image_name, const std::string &image_id,
	     const char *snap, IoCtx& p, bool read_only);
    ImageCtx(const std::string &image_name, const std::string &image_id,
	     librados::snap_t snap_id, IoCtx& p, bool read_only);
    ~ImageCtx();
    void init();
    void shutdown();
    void init_layout(int64_t pool_id);
    void perf_start(std::string name);
    void perf_stop();
    void set_read_flag(unsigned flag);
    int get_read_flags(librados::snap_t snap_id);
    int snap_set(uint64_t snap_id);
    void snap_unset();
    librados::snap_t get_snap_id(const cls::rbd::SnapshotNamespace& in_snap_namespace,
                                 const std::string& in_snap_name) const;
    const SnapInfo* get_snap_info(librados::snap_t in_snap_id) const;
    int get_snap_name(librados::snap_t in_snap_id,
		      std::string *out_snap_name) const;
    int get_snap_namespace(librados::snap_t in_snap_id,
			   cls::rbd::SnapshotNamespace *out_snap_namespace) const;
    int get_parent_spec(librados::snap_t in_snap_id,
			cls::rbd::ParentImageSpec *pspec) const;
    int is_snap_protected(librados::snap_t in_snap_id,
			  bool *is_protected) const;
    int is_snap_unprotected(librados::snap_t in_snap_id,
			    bool *is_unprotected) const;

    uint64_t get_current_size() const;
    uint64_t get_object_size() const;
    std::string get_object_name(uint64_t num) const;
    uint64_t get_stripe_unit() const;
    uint64_t get_stripe_count() const;
    uint64_t get_stripe_period() const;
    utime_t get_create_timestamp() const;
    utime_t get_access_timestamp() const;
    utime_t get_modify_timestamp() const;

    void set_access_timestamp(utime_t at);
    void set_modify_timestamp(utime_t at);

    void add_snap(cls::rbd::SnapshotNamespace in_snap_namespace,
		  std::string in_snap_name,
		  librados::snap_t id,
		  uint64_t in_size, const ParentImageInfo &parent,
		  uint8_t protection_status, uint64_t flags, utime_t timestamp);
    void rm_snap(cls::rbd::SnapshotNamespace in_snap_namespace,
		 std::string in_snap_name,
		 librados::snap_t id);
    uint64_t get_image_size(librados::snap_t in_snap_id) const;
    uint64_t get_effective_image_size(librados::snap_t in_snap_id) const;
    uint64_t get_object_count(librados::snap_t in_snap_id) const;
    bool test_features(uint64_t test_features) const;
    bool test_features(uint64_t test_features,
                       const ceph::shared_mutex &in_image_lock) const;
    bool test_op_features(uint64_t op_features) const;
    bool test_op_features(uint64_t op_features,
                          const ceph::shared_mutex &in_image_lock) const;
    int get_flags(librados::snap_t in_snap_id, uint64_t *flags) const;
    int test_flags(librados::snap_t in_snap_id,
                   uint64_t test_flags, bool *flags_set) const;
    int test_flags(librados::snap_t in_snap_id,
                   uint64_t test_flags, const ceph::shared_mutex &in_image_lock,
                   bool *flags_set) const;
    int update_flags(librados::snap_t in_snap_id, uint64_t flag, bool enabled);

    const ParentImageInfo* get_parent_info(librados::snap_t in_snap_id) const;
    int64_t get_parent_pool_id(librados::snap_t in_snap_id) const;
    std::string get_parent_image_id(librados::snap_t in_snap_id) const;
    uint64_t get_parent_snap_id(librados::snap_t in_snap_id) const;
    int get_parent_overlap(librados::snap_t in_snap_id,
			   uint64_t *overlap) const;
    void register_watch(Context *on_finish);
    uint64_t prune_parent_extents(std::vector<std::pair<uint64_t,uint64_t> >& objectx,
				  uint64_t overlap);

    void cancel_async_requests();
    void cancel_async_requests(Context *on_finish);

    void apply_metadata(const std::map<std::string, bufferlist> &meta,
                        bool thread_safe);

    ExclusiveLock<ImageCtx> *create_exclusive_lock();
    ObjectMap<ImageCtx> *create_object_map(uint64_t snap_id);
    Journal<ImageCtx> *create_journal();

    void set_image_name(const std::string &name);

    void notify_update();
    void notify_update(Context *on_finish);

    exclusive_lock::Policy *get_exclusive_lock_policy() const;
    void set_exclusive_lock_policy(exclusive_lock::Policy *policy);

    journal::Policy *get_journal_policy() const;
    void set_journal_policy(journal::Policy *policy);

    void rebuild_data_io_context();
    IOContext get_data_io_context() const;
    IOContext duplicate_data_io_context() const;

    static void get_timer_instance(CephContext *cct, SafeTimer **timer,
                                   ceph::mutex **timer_lock);

  private:
    std::shared_ptr<neorados::IOContext> data_io_context;
  };
}

#endif
