/*
 * Copyright Red Hat
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * $Id: //eng/vdo-releases/aluminum/src/c++/vdo/base/slabDepot.h#12 $
 */

#ifndef SLAB_DEPOT_H
#define SLAB_DEPOT_H

#include "buffer.h"

#include "adminState.h"
#include "completion.h"
#include "fixedLayout.h"
#include "journalPoint.h"
#include "statistics.h"
#include "types.h"
#include "waitQueue.h"

/**
 * A SlabDepot is responsible for managing all of the slabs and block
 * allocators of a VDO. It has a single array of slabs in order to eliminate
 * the need for additional math in order to compute which physical zone a PBN
 * is in. It also has a BlockAllocator per zone.
 *
 * Load operations are required to be performed on a single thread. Normal
 * operations are assumed to be performed in the appropriate zone. Allocations
 * and reference count updates must be done from the thread of their physical
 * zone. Requests to commit slab journal tail blocks from the recovery journal
 * must be done on the journal zone thread. Save operations are required to be
 * launched from the same thread as the original load operation.
 **/

typedef enum {
  NORMAL_LOAD,
  RECOVERY_LOAD,
  REBUILD_LOAD
} SlabDepotLoadType;

/**
 * Calculate the number of slabs a depot would have.
 *
 * @param depot  The depot
 *
 * @return The number of slabs
 **/
SlabCount calculateSlabCount(SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Create a slab depot.
 *
 * @param [in]  blockCount        The number of blocks initially available
 * @param [in]  firstBlock        The number of the first block which may be
 *                                allocated
 * @param [in]  slabConfig        The slab configuration
 * @param [in]  threadConfig      The thread configuration of the VDO
 * @param [in]  nonce             The nonce of the VDO
 * @param [in]  vioPoolSize       The size of the VIO pool
 * @param [in]  layer             The physical layer below this depot
 * @param [in]  summaryPartition  The partition which holds the slab summary
 * @param [in]  readOnlyNotifier  The context for entering read-only mode
 * @param [in]  recoveryJournal   The recovery journal of the VDO
 * @param [out] depotPtr          A pointer to hold the depot
 *
 * @return A success or error code
 **/
int makeSlabDepot(BlockCount            blockCount,
                  PhysicalBlockNumber   firstBlock,
                  SlabConfig            slabConfig,
                  const ThreadConfig   *threadConfig,
                  Nonce                 nonce,
                  BlockCount            vioPoolSize,
                  PhysicalLayer        *layer,
                  Partition            *summaryPartition,
                  ReadOnlyNotifier     *readOnlyNotifier,
                  RecoveryJournal      *recoveryJournal,
                  SlabDepot           **depotPtr)
  __attribute__((warn_unused_result));

/**
 * Destroy a slab depot and null out the reference to it.
 *
 * @param depotPtr  The reference to the depot to destroy
 **/
void freeSlabDepot(SlabDepot **depotPtr);

/**
 * Get the size of the encoded state of a slab depot.
 *
 * @return The encoded size of the depot's state
 **/
size_t getSlabDepotEncodedSize(void)
  __attribute__((warn_unused_result));

/**
 * Encode the state of a slab depot into a buffer.
 *
 * @param depot   The depot to encode
 * @param buffer  The buffer to encode into
 *
 * @return UDS_SUCCESS or an error
 **/
int encodeSlabDepot(const SlabDepot *depot, Buffer *buffer)
  __attribute__((warn_unused_result));

/**
 * Decode the state of a slab depot saved in a buffer.
 *
 * @param [in]  buffer            The buffer containing the saved state
 * @param [in]  threadConfig      The thread config of the VDO
 * @param [in]  nonce             The nonce of the VDO
 * @param [in]  layer             The physical layer below this depot
 * @param [in]  summaryPartition  The partition which holds the slab summary
 * @param [in]  readOnlyNotifier  The context for entering read-only mode
 * @param [in]  recoveryJournal   The recovery journal of the VDO
 * @param [out] depotPtr          A pointer to hold the depot
 *
 * @return A success or error code
 **/
int decodeSodiumSlabDepot(Buffer              *buffer,
                          const ThreadConfig  *threadConfig,
                          Nonce                nonce,
                          PhysicalLayer       *layer,
                          Partition           *summaryPartition,
                          ReadOnlyNotifier    *readOnlyNotifier,
                          RecoveryJournal     *recoveryJournal,
                          SlabDepot          **depotPtr)
  __attribute__((warn_unused_result));

/**
 * Decode the state of a slab depot saved in a buffer.
 *
 * @param [in]  buffer            The buffer containing the saved state
 * @param [in]  threadConfig      The thread config of the VDO
 * @param [in]  nonce             The nonce of the VDO
 * @param [in]  layer             The physical layer below this depot
 * @param [in]  summaryPartition  The partition which holds the slab summary
 * @param [in]  readOnlyNotifier  The context for entering read-only mode
 * @param [in]  recoveryJournal   The recovery journal of the VDO
 * @param [out] depotPtr          A pointer to hold the depot
 *
 * @return A success or error code
 **/
int decodeSlabDepot(Buffer              *buffer,
                    const ThreadConfig  *threadConfig,
                    Nonce                nonce,
                    PhysicalLayer       *layer,
                    Partition           *summaryPartition,
                    ReadOnlyNotifier    *readOnlyNotifier,
                    RecoveryJournal     *recoveryJournal,
                    SlabDepot          **depotPtr)
  __attribute__((warn_unused_result));

/**
 * Allocate the RefCounts for all slabs in the depot. This method may be called
 * only before entering normal operation from the load thread.
 *
 * @param depot  The depot whose RefCounts need allocation
 *
 * @return VDO_SUCCESS or an error
 **/
int allocateSlabRefCounts(SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the block allocator for a specified physical zone from a depot.
 *
 * @param depot       The depot
 * @param zoneNumber  The physical zone
 *
 * @return The block allocator for the specified zone
 **/
BlockAllocator *getBlockAllocatorForZone(SlabDepot *depot,
                                         ZoneCount  zoneNumber)
  __attribute__((warn_unused_result));

/**
 * Get the number of the slab that contains a specified block.
 *
 * @param depot          The slab depot
 * @param pbn            The physical block number
 * @param slabNumberPtr  A pointer to hold the slab number
 *
 * @return VDO_SUCCESS or an error
 **/
int getSlabNumber(const SlabDepot     *depot,
                  PhysicalBlockNumber  pbn,
                  SlabCount           *slabNumberPtr)
  __attribute__((warn_unused_result));

/**
 * Get the slab object for the slab that contains a specified block. Will put
 * the VDO in read-only mode if the PBN is not a valid data block nor the zero
 * block.
 *
 * @param depot  The slab depot
 * @param pbn    The physical block number
 *
 * @return The slab containing the block, or NULL if the block number is the
 *         zero block or otherwise out of range
 **/
Slab *getSlab(const SlabDepot *depot, PhysicalBlockNumber pbn)
  __attribute__((warn_unused_result));

/**
 * Get the slab journal for the slab that contains a specified block.
 *
 * @param depot  The slab depot
 * @param pbn    The physical block number within the block depot partition
 *               of any block in the slab
 *
 * @return The slab journal of the slab containing the block, or NULL if the
 *         block number is for the zero block or otherwise out of range
 **/
SlabJournal *getSlabJournal(const SlabDepot *depot, PhysicalBlockNumber pbn)
  __attribute__((warn_unused_result));

/**
 * Determine how many new references a block can acquire. This method must be
 * called from the the physical zone thread of the PBN.
 *
 * @param depot  The slab depot
 * @param pbn    The physical block number that is being queried
 *
 * @return the number of available references
 **/
uint8_t getIncrementLimit(SlabDepot *depot, PhysicalBlockNumber pbn)
  __attribute__((warn_unused_result));

/**
 * Determine whether the given PBN refers to a data block.
 *
 * @param depot  The depot
 * @param pbn    The physical block number to ask about
 *
 * @return <code>True</code> if the PBN corresponds to a data block
 **/
bool isPhysicalDataBlock(const SlabDepot *depot, PhysicalBlockNumber pbn)
  __attribute__((warn_unused_result));

/**
 * Get the total number of data blocks allocated across all the slabs in the
 * depot, which is the total number of blocks with a non-zero reference count.
 * This may be called from any thread.
 *
 * @param depot  The slab depot
 *
 * @return The total number of blocks with a non-zero reference count
 **/
BlockCount getDepotAllocatedBlocks(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the total of the statistics from all the block allocators in the depot.
 *
 * @param depot  The slab depot
 *
 * @return The statistics from all block allocators in the depot
 **/
BlockAllocatorStatistics
getDepotBlockAllocatorStatistics(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the total number of data blocks in all the slabs in the depot. This may
 * be called from any thread.
 *
 * @param depot  The slab depot
 *
 * @return The total number of data blocks in all slabs
 **/
BlockCount getDepotDataBlocks(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the total number of free blocks remaining in all the slabs in the
 * depot, which is the total number of blocks that have a zero reference
 * count. This may be called from any thread.
 *
 * @param depot  The slab depot
 *
 * @return The total number of blocks with a zero reference count
 **/
BlockCount getDepotFreeBlocks(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the total number of slabs in the depot
 *
 * @param depot  The slab depot
 *
 * @return The total number of slabs
 **/
SlabCount getDepotSlabCount(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the total number of unrecovered slabs in the depot, which is the total
 * number of unrecovered slabs from all zones. This may be called from any
 * thread.
 *
 * @param depot  The slab depot
 *
 * @return The total number of slabs that are unrecovered
 **/
SlabCount getDepotUnrecoveredSlabCount(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the aggregated slab journal statistics for the depot.
 *
 * @param depot  The slab depot
 *
 * @return The aggregated statistics for all slab journals in the depot
 **/
SlabJournalStatistics getDepotSlabJournalStatistics(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the cumulative RefCounts statistics for the depot.
 *
 * @param depot  The slab depot
 *
 * @return The cumulative statistics for all RefCounts in the depot
 **/
RefCountsStatistics getDepotRefCountsStatistics(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Asynchronously load any slab depot state that isn't included in the
 * SuperBlock component. This method may be called only before entering normal
 * operation from the load thread.
 *
 * @param depot        The depot to load
 * @param operation    The type of load to perform
 * @param parent       The completion to finish when the load is complete
 * @param context      Additional context for the load operation; may be NULL
 **/
void loadSlabDepot(SlabDepot         *depot,
                   AdminStateCode     operation,
                   VDOCompletion     *parent,
                   void              *context);

/**
 * Prepare the slab depot to come online and start allocating blocks. This
 * method may be called only before entering normal operation from the load
 * thread. It must be called before allocation may proceed.
 *
 * @param depot     The depot to prepare
 * @param loadType  The load type
 * @param parent    The completion to finish when the operation is complete
 **/
void prepareToAllocate(SlabDepot         *depot,
                       SlabDepotLoadType  loadType,
                       VDOCompletion     *parent);

/**
 * Update the slab depot to reflect its new size in memory. This size is saved
 * to disk as part of the super block.
 *
 * @param depot  The depot to update
 **/
void updateSlabDepotSize(SlabDepot *depot);

/**
 * Allocate new memory needed for a resize of a slab depot to the given size.
 *
 * @param depot    The depot to prepare to resize
 * @param newSize  The number of blocks in the new depot
 *
 * @return VDO_SUCCESS or an error
 **/
int prepareToGrowSlabDepot(SlabDepot *depot, BlockCount newSize)
  __attribute__((warn_unused_result));

/**
 * Use the new slabs allocated for resize.
 *
 * @param depot   The depot
 * @param parent  The object to notify when complete
 **/
void useNewSlabs(SlabDepot *depot, VDOCompletion *parent);

/**
 * Abandon any new slabs in this depot, freeing them as needed.
 *
 * @param depot  The depot
 **/
void abandonNewSlabs(SlabDepot *depot);

/**
 * Drain all slab depot I/O. If saving, or flushing, all dirty depot metadata
 * will be written out. If saving or suspending, the depot will be left in a
 * suspended state.
 *
 * @param depot      The depot to drain
 * @param operation  The drain operation (flush, rebuild, suspend, or save)
 * @param parent     The completion to finish when the drain is complete
 **/
void drainSlabDepot(SlabDepot      *depot,
                    AdminStateCode  operation,
                    VDOCompletion  *parent);

/**
 * Resume a suspended slab depot.
 *
 * @param depot   The depot to resume
 * @param parent  The completion to finish when the depot has resumed
 **/
void resumeSlabDepot(SlabDepot *depot, VDOCompletion *parent);

/**
 * Commit all dirty tail blocks which are locking a given recovery journal
 * block. This method must be called from the journal zone thread.
 *
 * @param depot                The depot
 * @param recoveryBlockNumber  The sequence number of the recovery journal
 *                             block whose locks should be released
 **/
void commitOldestSlabJournalTailBlocks(SlabDepot      *depot,
                                       SequenceNumber  recoveryBlockNumber);

/**
 * Get the SlabConfig of a depot.
 *
 * @param depot  The slab depot
 *
 * @return The slab configuration of the specified depot
 **/
const SlabConfig *getSlabConfig(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the slab summary.
 *
 * @param depot  The slab depot
 *
 * @return The slab summary
 **/
SlabSummary *getSlabSummary(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Get the portion of the slab summary for a given physical zone.
 *
 * @param depot  The slab depot
 * @param zone   The zone
 *
 * @return The portion of the slab summary for the specified zone
 **/
SlabSummaryZone *getSlabSummaryForZone(const SlabDepot *depot, ZoneCount zone)
  __attribute__((warn_unused_result));

/**
 * Scrub all unrecovered slabs.
 *
 * @param depot         The depot to scrub
 * @param parent        The object to notify when scrubbing is complete
 * @param callback      The function to call when scrubbing is complete
 * @param errorHandler  The handler for scrubbing errors
 * @param threadID      The thread on which to run the callback
 * @param launchParent  The object to notify when scrubbing has been launched
 *                      for all zones
 **/
void scrubAllUnrecoveredSlabs(SlabDepot     *depot,
                              void          *parent,
                              VDOAction     *callback,
                              VDOAction     *errorHandler,
                              ThreadID       threadID,
                              VDOCompletion *launchParent);

/**
 * Check whether there are outstanding unrecovered slabs.
 *
 * @param depot  The slab depot
 *
 * @return Whether there are outstanding unrecovered slabs
 **/
bool hasUnrecoveredSlabs(SlabDepot *depot);

/**
 * Get the physical size to which this depot is prepared to grow.
 *
 * @param depot  The slab depot
 *
 * @return The new number of blocks the depot will be grown to, or 0 if the
 *         depot is not prepared to grow
 **/
BlockCount getNewDepotSize(const SlabDepot *depot)
  __attribute__((warn_unused_result));

/**
 * Dump the slab depot, in a thread-unsafe fashion.
 *
 * @param depot  The slab depot
 **/
void dumpSlabDepot(const SlabDepot *depot);

#endif // SLAB_DEPOT_H
