/*             ----> DO NOT REMOVE THE FOLLOWING NOTICE <----

                   Copyright (c) 2014-2015 Datalight, Inc.
                       All Rights Reserved Worldwide.

    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; use version 2 of the License.

    This program is distributed in the hope that it will be useful,
    but "AS-IS," 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.
*/
/*  Businesses and individuals that for commercial or other reasons cannot
    comply with the terms of the GPLv2 license may obtain a commercial license
    before incorporating Reliance Edge into proprietary software for
    distribution in any form.  Visit http://www.datalight.com/reliance-edge for
    more information.
*/
/** @file
    @brief Implements the Reliance Edge file system formatter.
*/
#include <redfs.h>
#include <redcoreapi.h>
#include <redcore.h>

#if FORMAT_SUPPORTED


/** @brief Format a file system volume.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EBUSY  Volume is mounted.
    @retval -RED_EIO    A disk I/O error occurred.
*/
REDSTATUS RedVolFormat(void)
{
    REDSTATUS   ret;

    if(gpRedVolume->fMounted)
    {
        ret = -RED_EBUSY;
    }
    else
    {
        ret = RedOsBDevOpen(gbRedVolNum, BDEV_O_RDWR);
    }

    if(ret == 0)
    {
        MASTERBLOCK    *pMB;
        REDSTATUS       ret2;

        /*  Overwrite the master block with zeroes, so that if formatting is
            interrupted, the volume will not be mountable.
        */
        ret = RedBufferGet(BLOCK_NUM_MASTER, BFLAG_NEW | BFLAG_DIRTY, CAST_VOID_PTR_PTR(&pMB));

        if(ret == 0)
        {
            ret = RedBufferFlush(BLOCK_NUM_MASTER, 1U);

            RedBufferDiscard(pMB);
        }

        if(ret == 0)
        {
            ret = RedIoFlush(gbRedVolNum);
        }

      #if REDCONF_IMAP_EXTERNAL == 1
        if((ret == 0) && !gpRedCoreVol->fImapInline)
        {
            uint32_t ulImapBlock;
            uint32_t ulImapBlockLimit = gpRedCoreVol->ulImapStartBN + (gpRedCoreVol->ulImapNodeCount * 2U);
            uint16_t uImapFlags = (uint16_t)((uint32_t)BFLAG_META_IMAP | BFLAG_NEW | BFLAG_DIRTY);

            /*  Technically it is only necessary to create one copy of each imap
                node (the copy the metaroot points at), but creating them both
                avoids headaches during disk image analysis from stale imaps
                left over from previous formats.
            */
            for(ulImapBlock = gpRedCoreVol->ulImapStartBN; ulImapBlock < ulImapBlockLimit; ulImapBlock++)
            {
                IMAPNODE   *pImap;

                ret = RedBufferGet(ulImapBlock, uImapFlags, CAST_VOID_PTR_PTR(&pImap));
                if(ret != 0)
                {
                    break;
                }

                RedBufferPut(pImap);
            }
        }
      #endif

        /*  Write the first metaroot.
        */
        if(ret == 0)
        {
            RedMemSet(gpRedMR, 0U, sizeof(*gpRedMR));

            gpRedMR->ulFreeBlocks = gpRedVolume->ulBlocksAllocable;
          #if REDCONF_API_POSIX == 1
            gpRedMR->ulFreeInodes = gpRedVolConf->ulInodeCount;
          #endif
            gpRedMR->ulAllocNextBlock = gpRedCoreVol->ulFirstAllocableBN;

            /*  The branched flag is typically set automatically when bits in
                the imap change.  It is set here explicitly because the imap has
                only been initialized, not changed.
            */
            gpRedCoreVol->fBranched = true;

            ret = RedVolTransact();
        }

      #if REDCONF_API_POSIX == 1
        /*  Create the root directory.
        */
        if(ret == 0)
        {
            CINODE rootdir;

            rootdir.ulInode = INODE_ROOTDIR;
            ret = RedInodeCreate(&rootdir, INODE_INVALID, RED_S_IFDIR);

            if(ret == 0)
            {
                RedInodePut(&rootdir, 0U);
            }
        }
      #endif

      #if REDCONF_API_FSE == 1
        /*  The FSE API does not support creating or deletes files, so all the
            inodes are created during setup.
        */
        if(ret == 0)
        {
            uint32_t ulInodeIdx;

            for(ulInodeIdx = 0U; ulInodeIdx < gpRedVolConf->ulInodeCount; ulInodeIdx++)
            {
                CINODE ino;

                ino.ulInode = INODE_FIRST_FREE + ulInodeIdx;
                ret = RedInodeCreate(&ino, INODE_INVALID, RED_S_IFREG);

                if(ret == 0)
                {
                    RedInodePut(&ino, 0U);
                }
            }
        }
      #endif

        /*  Write the second metaroot.
        */
        if(ret == 0)
        {
            ret = RedVolTransact();
        }

        /*  Populate and write out the master block.
        */
        if(ret == 0)
        {
            ret = RedBufferGet(BLOCK_NUM_MASTER, (uint16_t)((uint32_t)BFLAG_META_MASTER | BFLAG_NEW | BFLAG_DIRTY), CAST_VOID_PTR_PTR(&pMB));
        }

        if(ret == 0)
        {
            pMB->ulVersion = RED_DISK_LAYOUT_VERSION;
            RedStrNCpy(pMB->acBuildNum, RED_BUILD_NUMBER, sizeof(pMB->acBuildNum));
            pMB->ulFormatTime = RedOsClockGetTime();
            pMB->ulInodeCount = gpRedVolConf->ulInodeCount;
            pMB->ulBlockCount = gpRedVolume->ulBlockCount;
            pMB->uMaxNameLen = REDCONF_NAME_MAX;
            pMB->uDirectPointers = REDCONF_DIRECT_POINTERS;
            pMB->uIndirectPointers = REDCONF_INDIRECT_POINTERS;
            pMB->bBlockSizeP2 = BLOCK_SIZE_P2;

          #if REDCONF_API_POSIX == 1
            pMB->bFlags |= MBFLAG_API_POSIX;
          #endif
          #if REDCONF_INODE_TIMESTAMPS == 1
            pMB->bFlags |= MBFLAG_INODE_TIMESTAMPS;
          #endif
          #if REDCONF_INODE_BLOCKS == 1
            pMB->bFlags |= MBFLAG_INODE_BLOCKS;
          #endif
          #if (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_LINK == 1)
            pMB->bFlags |= MBFLAG_INODE_NLINK;
          #endif

            ret = RedBufferFlush(BLOCK_NUM_MASTER, 1U);

            RedBufferPut(pMB);
        }

        if(ret == 0)
        {
            ret = RedIoFlush(gbRedVolNum);
        }

        ret2 = RedOsBDevClose(gbRedVolNum);
        if(ret == 0)
        {
            ret = ret2;
        }
    }

    /*  Discard the buffers so a subsequent format will not run into blocks it
        does not expect.
    */
    if(ret == 0)
    {
        ret = RedBufferDiscardRange(0U, gpRedVolume->ulBlockCount);
    }

    return ret;
}


#endif /* FORMAT_SUPPORTED */

