// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
// vim: ts=8 sw=2 sts=2 expandtab

/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2015 Red Hat
 *
 * 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.
 *
 */

#include "include/rados/librados.hpp"
#include "common/debug.h"
#include "common/errno.h"

#include "PoolDump.h"

using namespace librados;
using std::cerr;
using std::less;
using std::map;
using std::string;

#define dout_context g_ceph_context
#define dout_subsys ceph_subsys_rados

/**
 * Export RADOS objects from a live cluster
 * to a serialized format via a file descriptor.
 *
 * @returns 0 on success, else error code
 */
int PoolDump::dump(IoCtx *io_ctx)
{
  ceph_assert(io_ctx != NULL);

  int r = 0;
  write_super();

  r = write_simple(TYPE_POOL_BEGIN, file_fd);
  if (r != 0) {
    return r;
  }

  io_ctx->set_namespace(all_nspaces);
  librados::NObjectIterator i = io_ctx->nobjects_begin();

  librados::NObjectIterator i_end = io_ctx->nobjects_end();
  for (; i != i_end; ++i) {
    const std::string oid = i->get_oid();
    dout(10) << "OID '" << oid << "'" << dendl;

    // Compose OBJECT_BEGIN
    // ====================
    object_begin obj_begin;
    obj_begin.hoid.hobj.oid = i->get_oid();
    obj_begin.hoid.hobj.nspace = i->get_nspace();
    obj_begin.hoid.hobj.set_key(i->get_locator());

    // Only output head, RadosImport only wants that
    obj_begin.hoid.hobj.snap = CEPH_NOSNAP;

    // Skip setting object_begin.oi, RadosImport doesn't care

    r = write_section(TYPE_OBJECT_BEGIN, obj_begin, file_fd);
    if (r != 0) {
      return r;
    }

    // Compose TYPE_DATA chunks
    // ========================
    const uint32_t op_size = 4096 * 1024;
    uint64_t offset = 0;
    io_ctx->set_namespace(i->get_nspace());
    io_ctx->locator_set_key(i->get_locator());
    while (true) {
      bufferlist outdata;
      r = io_ctx->read(oid, outdata, op_size, offset);
      if (r <= 0) {
        // Error or no data
        break;
      }

      r = write_section(TYPE_DATA,
          data_section(offset, outdata.length(), outdata), file_fd);
      if (r != 0) {
        // Output stream error
        return r;
      }

      if (outdata.length() < op_size) {
        // No more data
        break;
      }
      offset += outdata.length();
    }

    // Compose TYPE_ATTRS chunk
    // ========================
    std::map<std::string, bufferlist> raw_xattrs;
    std::map<std::string, bufferlist,less<>> xattrs;
    r = io_ctx->getxattrs(oid, raw_xattrs);
    if (r < 0) {
      cerr << "error getting xattr set " << oid << ": " << cpp_strerror(r)
           << std::endl;
      return r;
    }
    // Prepend "_" to mimic how user keys are represented in a pg export
    for (std::map<std::string, bufferlist>::iterator i = raw_xattrs.begin();
         i != raw_xattrs.end(); ++i) {
      std::pair< std::string, bufferlist> item(std::string("_") + std::string(i->first.c_str()), i->second);
      xattrs.insert(item);
    }
    r = write_section(TYPE_ATTRS, attr_section(xattrs), file_fd);
    if (r != 0) {
      return r;
    }

    // Compose TYPE_OMAP_HDR section
    // =============================
    bufferlist omap_header;
    r = io_ctx->omap_get_header(oid, &omap_header);
    if (r < 0) {
      cerr << "error getting omap header " << oid
	   << ": " << cpp_strerror(r) << std::endl;
      return r;
    }
    r = write_section(TYPE_OMAP_HDR, omap_hdr_section(omap_header), file_fd);
    if (r != 0) {
      return r;
    }

    // Compose TYPE_OMAP
    int MAX_READ = 512;
    string last_read = "";
    do {
      map<string, bufferlist> values;
      r = io_ctx->omap_get_vals(oid, last_read, MAX_READ, &values);
      if (r < 0) {
	cerr << "error getting omap keys " << oid << ": "
	     << cpp_strerror(r) << std::endl;
	return r;
      }
      if (values.size()) {
        last_read = values.rbegin()->first;
      } else {
        break;
      }

      r = write_section(TYPE_OMAP, omap_section(values), file_fd);
      if (r != 0) {
        return r;
      }
      r = values.size();
    } while (r == MAX_READ);

    // Close object
    // =============
    r = write_simple(TYPE_OBJECT_END, file_fd);
    if (r != 0) {
      return r;
    }
  }

  r = write_simple(TYPE_POOL_END, file_fd);
#if defined(__linux__)
  if (file_fd != STDOUT_FILENO)
    posix_fadvise(file_fd, 0, 0, POSIX_FADV_DONTNEED);
#endif
  return r;
}
