/*---------------------------------------------------------------------------*
  Project:  TwlSDK - FS - libraries
  File:     fs_archive.c

  Copyright 2007-2009 Nintendo. All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law. They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Date:: 2009-08-27#$
  $Rev: 11020 $
  $Author: yosizaki $

 *---------------------------------------------------------------------------*/


#include <nitro/fs.h>

#include <nitro/mi/memory.h>
#include <nitro/std.h>


#include "../include/util.h"
#include "../include/command.h"


#if defined(FS_IMPLEMENT)


/*---------------------------------------------------------------------------*/
/* Constants */

// Enable when internally supporting long archive names
#define FS_SUPPORT_LONG_ARCNAME


/*---------------------------------------------------------------------------*/
/* Variables */

// Archive list registered to the file system
static FSArchive   *arc_list = NULL;

// Current directory
static FSDirPos     current_dir_pos;
static char         current_dir_path[FS_ENTRY_LONGNAME_MAX];

#if defined(FS_SUPPORT_LONG_ARCNAME)
// Static buffer for internal support of long archive names
#define FS_LONG_ARCNAME_LENGTH_MAX  15
#define FS_LONG_ARCNAME_TABLE_MAX   16
static char FSiLongNameTable[FS_LONG_ARCNAME_TABLE_MAX][FS_LONG_ARCNAME_LENGTH_MAX + 1];
#endif


/*---------------------------------------------------------------------------*/
/* Functions */

FSArchive* FSi_GetArchiveChain(void)
{
    return arc_list;
}

/*---------------------------------------------------------------------------*
  Name:         FSi_IsEventCommand

  Description:  Determines whether the command is an event notification.

  Arguments:    command: Command type

  Returns:      TRUE when command is event notification.
 *---------------------------------------------------------------------------*/
static BOOL FSi_IsEventCommand(FSCommandType command)
{
    return
        ((command == FS_COMMAND_ACTIVATE) ||
        (command == FS_COMMAND_IDLE) ||
        (command == FS_COMMAND_SUSPEND) ||
        (command == FS_COMMAND_RESUME) ||
        (command == FS_COMMAND_MOUNT) ||
        (command == FS_COMMAND_UNMOUNT) ||
        (command == FS_COMMAND_INVALID));
}

/*---------------------------------------------------------------------------*
  Name:         FSi_EndCommand

  Description:  Completes the command and returns if there is an idle thread.

  Arguments:    file: Completed command
                ret: Command result value

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void FSi_EndCommand(FSFile *file, FSResult ret)
{
    OSIntrMode bak_psr = OS_DisableInterrupts();
    // Delete command from the list
    FSArchive *const arc = file->arc;
    if (arc)
    {
        FSFile    **pp = &arc->list;
        for (; *pp; pp = &(*pp)->next)
        {
            if (*pp == file)
            {
                *pp = file->next;
                break;
            }
        }
        file->next = NULL;
    }
    // Store results
    {
        FSCommandType   command = FSi_GetCurrentCommand(file);
        if (!FSi_IsEventCommand(command) && (arc != NULL))
        {
            arc->command = command;
            arc->result = ret;
        }
        file->error = ret;
        file->stat &= ~(FS_FILE_STATUS_CANCEL | FS_FILE_STATUS_BUSY |
                        FS_FILE_STATUS_BLOCKING | FS_FILE_STATUS_OPERATING |
                        FS_FILE_STATUS_ASYNC_DONE | FS_FILE_STATUS_UNICODE_MODE);
    }
    // Notify if there is an idling thread
    OS_WakeupThread(file->queue);
    (void)OS_RestoreInterrupts(bak_psr);
}


// The following two temporarily publicized for procedures
// Commands recursively calling from the procedures are all synchronous, so it is acceptable for applying only the FSi_WaitForArchiveCompletion function by directly calling via the vtbl.
// 

FSResult FSi_WaitForArchiveCompletion(FSFile *file, FSResult result)
{
    if (result == FS_RESULT_PROC_ASYNC)
    {
        FSi_WaitConditionOn(&file->stat, FS_FILE_STATUS_ASYNC_DONE, file->queue);
        file->stat &= ~FS_FILE_STATUS_ASYNC_DONE;
        result = file->error;
    }
    return result;
}

/*---------------------------------------------------------------------------*
  Name:         FSi_InvokeCommand

  Description:  Issues a command to an archive.
                Command list management is resolved before calling this function.

  Arguments:    file: FSFile structure that stores the argument
                Command: Command ID

  Returns:      The processing result for the command.
 *---------------------------------------------------------------------------*/
static FSResult FSi_InvokeCommand(FSFile *file, FSCommandType command)
{
    FSResult            result = FS_RESULT_UNSUPPORTED;
    FSArchive   * const arc = file->arc;

    {
        const void   *(*table) = (const void*)arc->vtbl;
        // Undefined command out of range
        if ((command < 0) || (command >= FS_COMMAND_MAX))
        {
            OS_TWarning("undefined command (%d)\n", command);
            result = FS_RESULT_UNSUPPORTED;
        }
        // Call by restoring the argument if it is not a command whose implementation was abandoned by the driver side
        else if (table[command] == NULL)
        {
            result = FS_RESULT_UNSUPPORTED;
        }
        else
        {
#define FS_DECLARE_ARGUMENT_(type)                              \
            type *arg = (type*) file->argument;                 \
            (void)arg

#define FS_INVOKE_METHOD_(command, ...)                         \
            do                                                  \
            {                                                   \
                FS_DECLARE_ARGUMENT_(FSArgumentFor ## command); \
                result = arc->vtbl->command(__VA_ARGS__);       \
            }                                                   \
            while(0)
#define FS_NOTIFY_EVENT_(command, ...)                          \
            do                                                  \
            {                                                   \
                FS_DECLARE_ARGUMENT_(FSArgumentFor ## command); \
                (void)arc->vtbl->command(__VA_ARGS__);          \
                return FS_RESULT_SUCCESS;                       \
            }                                                   \
            while(0)
            switch (command)
            {
            case FS_COMMAND_READFILE:
                FS_INVOKE_METHOD_(ReadFile, arc, file, arg->buffer, &arg->length);
                break;
            case FS_COMMAND_WRITEFILE:
                FS_INVOKE_METHOD_(WriteFile, arc, file, arg->buffer, &arg->length);
                break;
            case FS_COMMAND_SEEKDIR:
                FS_INVOKE_METHOD_(SeekDirectory, arc, file, arg->id, arg->position);
                break;
            case FS_COMMAND_READDIR:
                FS_INVOKE_METHOD_(ReadDirectory, arc, file, arg->info);
                break;
            case FS_COMMAND_FINDPATH:
                FS_INVOKE_METHOD_(FindPath, arc, arg->baseid, arg->relpath, &arg->target_id, arg->target_is_directory);
                break;
            case FS_COMMAND_GETPATH:
                FS_INVOKE_METHOD_(GetPath, arc, file, arg->is_directory, arg->buffer, &arg->length);
                break;
            case FS_COMMAND_OPENFILEFAST:
                FS_INVOKE_METHOD_(OpenFileFast, arc, file, arg->id, arg->mode);
                break;
            case FS_COMMAND_OPENFILEDIRECT:
                FS_INVOKE_METHOD_(OpenFileDirect, arc, file, arg->top, arg->bottom, &arg->id);
                break;
            case FS_COMMAND_CLOSEFILE:
                FS_INVOKE_METHOD_(CloseFile, arc, file);
                break;
            case FS_COMMAND_ACTIVATE:
                FS_NOTIFY_EVENT_(Activate, arc);
                break;
            case FS_COMMAND_IDLE:
                FS_NOTIFY_EVENT_(Idle, arc);
                break;
            case FS_COMMAND_SUSPEND:
                FS_NOTIFY_EVENT_(Suspend, arc);
                break;
            case FS_COMMAND_RESUME:
                FS_NOTIFY_EVENT_(Resume, arc);
                break;
            case FS_COMMAND_OPENFILE:
                FS_INVOKE_METHOD_(OpenFile, arc, file, arg->baseid, arg->relpath, arg->mode);
                break;
            case FS_COMMAND_SEEKFILE:
                FS_INVOKE_METHOD_(SeekFile, arc, file, &arg->offset, arg->from);
                break;
            case FS_COMMAND_GETFILELENGTH:
                FS_INVOKE_METHOD_(GetFileLength, arc, file, &arg->length);
                break;
            case FS_COMMAND_GETFILEPOSITION:
                FS_INVOKE_METHOD_(GetFilePosition, arc, file, &arg->position);
                break;
                // From here the extended command
            case FS_COMMAND_MOUNT:
                FS_NOTIFY_EVENT_(Mount, arc);
                break;
            case FS_COMMAND_UNMOUNT:
                FS_NOTIFY_EVENT_(Unmount, arc);
                break;
            case FS_COMMAND_GETARCHIVECAPS:
                FS_INVOKE_METHOD_(GetArchiveCaps, arc, &arg->caps);
                break;
            case FS_COMMAND_CREATEFILE:
                FS_INVOKE_METHOD_(CreateFile, arc, arg->baseid, arg->relpath, arg->permit);
                break;
            case FS_COMMAND_DELETEFILE:
                FS_INVOKE_METHOD_(DeleteFile, arc, arg->baseid, arg->relpath);
                break;
            case FS_COMMAND_RENAMEFILE:
                FS_INVOKE_METHOD_(RenameFile, arc, arg->baseid_src, arg->relpath_src, arg->baseid_dst, arg->relpath_dst);
                break;
            case FS_COMMAND_GETPATHINFO:
                FS_INVOKE_METHOD_(GetPathInfo, arc, arg->baseid, arg->relpath, arg->info);
                break;
            case FS_COMMAND_SETPATHINFO:
                FS_INVOKE_METHOD_(SetPathInfo, arc, arg->baseid, arg->relpath, arg->info);
                break;
            case FS_COMMAND_CREATEDIRECTORY:
                FS_INVOKE_METHOD_(CreateDirectory, arc, arg->baseid, arg->relpath, arg->permit);
                break;
            case FS_COMMAND_DELETEDIRECTORY:
                FS_INVOKE_METHOD_(DeleteDirectory, arc, arg->baseid, arg->relpath);
                break;
            case FS_COMMAND_RENAMEDIRECTORY:
                FS_INVOKE_METHOD_(RenameDirectory, arc, arg->baseid_src, arg->relpath_src, arg->baseid_dst, arg->relpath_dst);
                break;
            case FS_COMMAND_GETARCHIVERESOURCE:
                FS_INVOKE_METHOD_(GetArchiveResource, arc, arg->resource);
                break;
            case FS_COMMAND_FLUSHFILE:
                FS_INVOKE_METHOD_(FlushFile, arc, file);
                break;
            case FS_COMMAND_SETFILELENGTH:
                FS_INVOKE_METHOD_(SetFileLength, arc, file, arg->length);
                break;
            case FS_COMMAND_OPENDIRECTORY:
                FS_INVOKE_METHOD_(OpenDirectory, arc, file, arg->baseid, arg->relpath, arg->mode);
                break;
            case FS_COMMAND_CLOSEDIRECTORY:
                FS_INVOKE_METHOD_(CloseDirectory, arc, file);
                break;
            case FS_COMMAND_SETSEEKCACHE:
                FS_INVOKE_METHOD_(SetSeekCache, arc, file, arg->buf, arg->buf_size);
                break;
            default:
                result = FS_RESULT_UNSUPPORTED;
            }
#undef FS_DECLARE_ARGUMENT_
#undef FS_INVOKE_METHOD_
#undef FS_NOTIFY_EVENT_
        }
    }
    // If this is a normal command with no event notification, return to satisfy conditions when issued
    if (!FSi_IsEventCommand(command))
    {
        // Because there are many cases where the path name is incorrect and unsupported, if there is the possibility, a warning will be output
        // 
        if (result == FS_RESULT_UNSUPPORTED)
        {
            OS_TWarning("archive \"%s:\" cannot support command %d.\n", FS_GetArchiveName(arc), command);
        }
        // If necessary, apply blocking
        if ((file->stat & FS_FILE_STATUS_BLOCKING) != 0)
        {
            result = FSi_WaitForArchiveCompletion(file, result);
        }
        // Release here if completed while nobody is blocking
        else if (result != FS_RESULT_PROC_ASYNC)
        {
            FSi_EndCommand(file, result);
        }
    }
    return result;
}

/*---------------------------------------------------------------------------*
  Name:         FSi_NextCommand

  Description:  Archive selects the next command that should be processed.
                If an asynchronous command is selected, return that pointer.
                If anything other than NULL is returned, it is necessary to process at the source of the call.

  Arguments:    arc: Archive that gets the next command
                owner: TRUE if source of call already has command execution rights

  Returns:      The next command that must be processed here.
 *---------------------------------------------------------------------------*/
static FSFile *FSi_NextCommand(FSArchive *arc, BOOL owner)
{
    FSFile  *next = NULL;
    // First, check all command cancel requests
    {
        OSIntrMode bak_psr = OS_DisableInterrupts();
        if ((arc->flag & FS_ARCHIVE_FLAG_CANCELING) != 0)
        {
            FSFile *p = arc->list;
            arc->flag &= ~FS_ARCHIVE_FLAG_CANCELING;
            while (p != NULL)
            {
                FSFile *q = p->next;
                // Commands whose processes have already started cannot be externally canceled.
                if (FS_IsCanceling(p) && ((p->stat & FS_FILE_STATUS_OPERATING) == 0))
                {
                    FSi_EndCommand(p, FS_RESULT_CANCELED);
                    if (!q)
                    {
                        q = arc->list;
                    }
                }
                p = q;
            }
        }
        (void)OS_RestoreInterrupts(bak_psr);
    }
    // Determine whether the next command can be executed
    {
        OSIntrMode  bak_psr = OS_DisableInterrupts();
        if (((arc->flag & FS_ARCHIVE_FLAG_SUSPENDING) == 0) &&
            ((arc->flag & FS_ARCHIVE_FLAG_SUSPEND) == 0) && arc->list)
        {
            // Start up here if archive is stopped
            const BOOL  is_started = owner && ((arc->flag & FS_ARCHIVE_FLAG_RUNNING) == 0);
            if (is_started)
            {
                arc->flag |= FS_ARCHIVE_FLAG_RUNNING;
            }
            (void)OS_RestoreInterrupts(bak_psr);
            if (is_started)
            {
                (void)FSi_InvokeCommand(arc->list, FS_COMMAND_ACTIVATE);
            }
            bak_psr = OS_DisableInterrupts();
            // Command at the top of the list gets execution rights
            if (owner || is_started)
            {
                next = arc->list;
                next->stat |= FS_FILE_STATUS_OPERATING;
            }
            // If command issuing source is blocking, transfer execution rights
            if (owner && ((next->stat & FS_FILE_STATUS_BLOCKING) != 0))
            {
                OS_WakeupThread(next->queue);
                next = NULL;
            }
            (void)OS_RestoreInterrupts(bak_psr);
        }
        // Suspend or idle state
        else
        {
            // If does not have execution rights, stop and go to idle state
            if (owner)
            {
                if ((arc->flag & FS_ARCHIVE_FLAG_RUNNING) != 0)
                {
                    FSFile  tmp;
                    FS_InitFile(&tmp);
                    tmp.arc = arc;
                    arc->flag &= ~FS_ARCHIVE_FLAG_RUNNING;
                    (void)FSi_InvokeCommand(&tmp, FS_COMMAND_IDLE);
                }
                // If suspend is executing, notify the source of the suspend function call
                if ((arc->flag & FS_ARCHIVE_FLAG_SUSPENDING) != 0)
                {
                    arc->flag &= ~FS_ARCHIVE_FLAG_SUSPENDING;
                    arc->flag |= FS_ARCHIVE_FLAG_SUSPEND;
                    OS_WakeupThread(&arc->queue);
                }
            }
            (void)OS_RestoreInterrupts(bak_psr);
        }
    }
    return next;
}

/*---------------------------------------------------------------------------*
  Name:         FSi_ExecuteAsyncCommand

  Description:  Executes an asynchronous command.
                The first call is made from the user thread with interrupts enabled.
                As long as the archive is operating synchronously, command processing will repeat here. If even a single asynchronous process occurs, the rest is performed with NotifyAsyncEnd().
                

                Therefore, it is necessary to be aware of the NotifyAsyncEnd() calling environment when archive processing switches between synchronous and asynchronous.
                

  Arguments:    file: FSFile structure that stores the asynchronous command to execute

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void FSi_ExecuteAsyncCommand(FSFile *file)
{
    FSArchive   * const arc = file->arc;
    while (file)
    {
        // If call source is waiting on completion, wake that up and leave it to the process
        {
            OSIntrMode bak_psr = OS_DisableInterrupts();
            file->stat |= FS_FILE_STATUS_OPERATING;
            if ((file->stat & FS_FILE_STATUS_BLOCKING) != 0)
            {
                OS_WakeupThread(file->queue);
                file = NULL;
            }
            (void)OS_RestoreInterrupts(bak_psr);
        }
        if (!file)
        {
            break;
        }
        // If processing is asynchronous, quit for the time being
        else if (FSi_InvokeCommand(file, FSi_GetCurrentCommand(file)) == FS_RESULT_PROC_ASYNC)
        {
            break;
        }
        // If the result is a synchronous completion, select the continuation here
        else
        {
            file = FSi_NextCommand(arc, TRUE);
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         FSi_ExecuteSyncCommand

  Description:  Executes a command that was blocked.

  Arguments:    file: FSFile structure that stores the command to execute

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void FSi_ExecuteSyncCommand(FSFile *file)
{
    // If necessary, wait for turn, then process the command
    FSi_WaitConditionChange(&file->stat, FS_FILE_STATUS_OPERATING, FS_FILE_STATUS_BUSY, file->queue);
    // If you do not have the execution rights for some reason (for example, the command has already been cancelled), do nothing
    if ((file->stat & FS_FILE_STATUS_OPERATING) != 0)
    {
        FSArchive   * const arc = file->arc;
        FSResult            result;
        result = FSi_InvokeCommand(file, FSi_GetCurrentCommand(file));
        FSi_EndCommand(file, result);
        // If there is an asynchronous type command to be processed here, execute instead
        file = FSi_NextCommand(arc, TRUE);
        if (file)
        {
            FSi_ExecuteAsyncCommand(file);
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         FSi_SendCommand

  Description:  Issues a command to an archive.
                Adjust start timing and block here if synchronous.

  Arguments:    file: FSFile structure in which the command argument has been specified
                Command: Command ID
                blocking: TRUE if blocking

  Returns:      TRUE if the command is successful.
 *---------------------------------------------------------------------------*/
BOOL FSi_SendCommand(FSFile *file, FSCommandType command, BOOL blocking)
{
    BOOL                retval = FALSE;
    FSArchive   * const arc = file->arc;
    BOOL                owner = FALSE;

    if (FS_IsBusy(file))
    {
        OS_TPanic("specified file is now still proceccing previous command!");
    }
    if (!arc)
    {
        OS_TWarning("specified handle is not related by any archive\n");
        file->error = FS_RESULT_INVALID_PARAMETER;
        return FALSE;
    }

    // Initialize command
    file->error = FS_RESULT_BUSY;
    file->stat &= ~(FS_FILE_STATUS_CMD_MASK << FS_FILE_STATUS_CMD_SHIFT);
    file->stat |= (command << FS_FILE_STATUS_CMD_SHIFT);
    file->stat |= FS_FILE_STATUS_BUSY;
    file->next = NULL;
    if (blocking)
    {
        file->stat |= FS_FILE_STATUS_BLOCKING;
    }
    // If unloading, cancel the process and if not, add to the end of the list
    {
        OSIntrMode          bak_psr = OS_DisableInterrupts();
        if ((arc->flag & FS_ARCHIVE_FLAG_UNLOADING) != 0)
        {
            FSi_EndCommand(file, FS_RESULT_CANCELED);
        }
        else
        {
            FSFile    **pp;
            for (pp = &arc->list; *pp; pp = &(*pp)->next)
            {
            }
            *pp = file;
        }
        owner = (arc->list == file) && ((arc->flag & FS_ARCHIVE_FLAG_RUNNING) == 0);
        (void)OS_RestoreInterrupts(bak_psr);
    }
    // If added to the list, check the command execution rights
    if (file->error != FS_RESULT_CANCELED)
    {
        // If there is a command that should be processed here, execute that instead
        FSFile *next = FSi_NextCommand(arc, owner);
        // If necessary, block here
        if (blocking)
        {
            FSi_ExecuteSyncCommand(file);
            retval = FS_IsSucceeded(file);
        }
        // Added to the list, so will be processed by someone
        else
        {
            if (next != NULL)
            {
                FSi_ExecuteAsyncCommand(next);
            }
            retval = TRUE;
        }
    }

    return retval;
}

/*---------------------------------------------------------------------------*
  Name:         FSi_EndArchive

  Description:  End all archives and release.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void FSi_EndArchive(void)
{
    OSIntrMode bak_psr = OS_DisableInterrupts();
    while (arc_list)
    {
        FSArchive *p_arc = arc_list;
        arc_list = arc_list->next;
        (void)FS_UnloadArchive(p_arc);
        FS_ReleaseArchiveName(p_arc);
    }
    (void)OS_RestoreInterrupts(bak_psr);
}

/*---------------------------------------------------------------------------*
  Name:         FS_FindArchive

  Description:  Searches for archive name.
                If there is no matching name, returns NULL.

  Arguments:    name: String of the archive name to search for
                name_len: Length of name string

  Returns:      Pointer to the archive that was found, or NULL.
 *---------------------------------------------------------------------------*/
FSArchive *FS_FindArchive(const char *name, int name_len)
{
    OSIntrMode  bak_psr = OS_DisableInterrupts();
    FSArchive  *arc = arc_list;
    for (; arc; arc = arc->next)
    {
        if (FS_IsArchiveLoaded(arc))
        {
            const char *arcname = FS_GetArchiveName(arc);
            if ((STD_CompareNString(arcname, name, name_len) == 0) && (arcname[name_len] == '\0'))
            {
                break;
            }
        }
    }
    (void)OS_RestoreInterrupts(bak_psr);
    return arc;
}

/*---------------------------------------------------------------------------*
  Name:         FS_GetArchiveResultCode

  Description:  Gets latest error code for the specified archive.

  Arguments:    path_or_archive  Specify targeted archive
                                 FSArchive structure or path string

  Returns:      Latest error code for the specified archive.
                If the corresponding archive does not exist, FS_RESULT_ERROR.
 *---------------------------------------------------------------------------*/
FSResult FS_GetArchiveResultCode(const void *path_or_archive)
{
    OSIntrMode  bak_psr = OS_DisableInterrupts();
    FSArchive  *arc = arc_list;
    while (arc && (arc != (const FSArchive *)path_or_archive))
    {
        arc = arc->next;
    }
    if (!arc)
    {
        arc = FS_NormalizePath((const char *)path_or_archive, NULL, NULL);
    }
    (void)OS_RestoreInterrupts(bak_psr);
    return arc ? arc->result : FS_RESULT_ERROR;
}

/*---------------------------------------------------------------------------*
  Name:         FS_GetCurrentDirectoryPath

  Description:  Gets current directory with path name.

  Arguments:    None.

  Returns:      String that represents current directory.
 *---------------------------------------------------------------------------*/
const char *FS_GetCurrentDirectory(void)
{
    return current_dir_path;
}

/*---------------------------------------------------------------------------*
  Name:         FS_SetCurrentDirectory

  Description:  Changes the current directory.

  Arguments:    path: Path name

  Returns:      TRUE if successful.
 *---------------------------------------------------------------------------*/
BOOL FS_SetCurrentDirectory(const char *path)
{
    BOOL        retval = FALSE;
    FSArchive  *arc = NULL;
    u32         baseid = 0;
    char        relpath[FS_ENTRY_LONGNAME_MAX];

    SDK_NULL_ASSERT(path);
    SDK_ASSERT(OS_GetProcMode() != OS_PROCMODE_IRQ);

    // Get normalized path name
    arc = FS_NormalizePath(path, &baseid, relpath);
    if (arc)
    {
        // In any case, the setting was successful
        current_dir_pos.arc = arc;
        current_dir_pos.own_id = 0;
        current_dir_pos.index = 0;
        current_dir_pos.pos = 0;
        (void)STD_CopyLString(current_dir_path, relpath, sizeof(current_dir_path));
        // If possible, try to get directory ID
        if (arc->vtbl->FindPath != NULL)
        {
            FSFile                  dir[1];
            FSArgumentForFindPath   arg[1];
            FS_InitFile(dir);
            dir->arc = arc;
            dir->argument = arg;
            arg->baseid = baseid;
            arg->relpath = relpath;
            arg->target_is_directory = TRUE;
            if (FSi_SendCommand(dir, FS_COMMAND_FINDPATH, TRUE))
            {
                current_dir_pos.own_id = (u16)arg->target_id;
                (void)STD_CopyLString(current_dir_path, relpath, sizeof(current_dir_path));
            }
        }
        retval = TRUE;
    }
    return retval;
}

/*---------------------------------------------------------------------------*
  Name:         FSi_CopySafeString

  Description:  Checks buffer size and copies string.

  Arguments:    dst: Transfer destination buffer
                dstlen: Transfer destination size
                src: Transfer source buffer
                srclen: Transfer destination size
                stickyFailure: FALSE, if truncated at transfer origin
                
  Returns:      Archive pointer or NULL.
 *---------------------------------------------------------------------------*/
static int FSi_CopySafeString(char *dst, int dstlen, const char *src, int srclen, BOOL *stickyFailure)
{
    int     i;
    int     n = (dstlen - 1 < srclen) ? (dstlen - 1) : srclen;
    for (i = 0; (i < n) && src[i]; ++i)
    {
        dst[i] = src[i];
    }
    if ((i < srclen) && src[i])
    {
        *stickyFailure = TRUE;
    }
    dst[i] = '\0';
    return i;
}

/*---------------------------------------------------------------------------*
  Name:         FS_NormalizePath

  Description:  Normalizes to format of (Standard Directory ID) + (Path Name) considering the current directory in the specified path.
                
                

  Arguments:    path: Non-normalized path string
                baseid: Standard directory ID storage destination or NULL
                relpath: Path name storage destination after conversion or NULL

  Returns:      Archive pointer or NULL.
 *---------------------------------------------------------------------------*/
FSArchive* FS_NormalizePath(const char *path, u32 *baseid, char *relpath)
{
    FSArchive  *arc = NULL;
    int         pathlen = 0;
    int         pathmax = FS_ENTRY_LONGNAME_MAX;
    BOOL        stickyFailure = FALSE;
    // Reevaluate when current directory is not specified
    if (current_dir_pos.arc == NULL)
    {
        current_dir_pos.arc = arc_list;
        current_dir_pos.own_id = 0;
        current_dir_pos.pos = 0;
        current_dir_pos.index = 0;
        current_dir_path[0] = '\0';
    }
    // With "/path" make the current archive route the base
    if (FSi_IsSlash((u8)*path))
    {
        arc = current_dir_pos.arc;
        ++path;
        if (baseid)
        {
            *baseid = 0;
        }
    }
    else
    {
        int     i;
        for (i = 0; ; i = FSi_IncrementSjisPosition(path, i))
        {
            u32     c = (u8)path[i];
            // With "(path/)*path" the relevant path from the current position
            if (!c || FSi_IsSlash(c))
            {
                arc = current_dir_pos.arc;
                if (baseid)
                {
                    *baseid = current_dir_pos.own_id;
                }
                if(relpath)
                {
                    // If directory is retained only by the path name, link it
                    if ((current_dir_pos.own_id == 0) && (current_dir_path[0] != '\0'))
                    {
                        pathlen += FSi_CopySafeString(&relpath[pathlen], pathmax - pathlen,
                                                      current_dir_path, FS_ENTRY_LONGNAME_MAX, &stickyFailure);
                        pathlen += FSi_CopySafeString(&relpath[pathlen], pathmax - pathlen,
                                                      "/", 1, &stickyFailure);
                    }
                }
                break;
            }
            // With "arc:/path," the full path
            else if (c == ':')
            {
                arc = FS_FindArchive(path, i);
                if (!arc)
                {
                    OS_TWarning("archive \"%*s\" is not found.", i, path);
                }
                path += i + 1;
                if (FSi_IsSlash((u8)*path))
                {
                    ++path;
                }
                if (baseid)
                {
                    *baseid = 0;
                }
                break;
            }
        }
    }
    if(relpath)
    {
        // Normalize relative paths, being careful of specialized entry names.
        int     curlen = 0;
        while (!stickyFailure)
        {
            char    c = path[curlen];
            if ((c != '\0') && !FSi_IsSlash((u8)c))
            {
                curlen += STD_IsSjisCharacter(&path[curlen]) ? 2 : 1;
            }
            else
            {
                // Ignore empty directory
                if (curlen == 0)
                {
                }
                // Ignore "." (current directory)
                else if ((curlen == 1) && (path[0] == '.'))
                {
                }
                // ".." (parent directory) raises the root one level as the upper limit
                else if ((curlen == 2) && (path[0] == '.') && (path[1] == '.'))
                {
                    if (pathlen > 0)
                    {
                        --pathlen;
                    }
                    pathlen = FSi_DecrementSjisPositionToSlash(relpath, pathlen) + 1;
                }
                // Add entry for anything else
                else
                {
                    pathlen += FSi_CopySafeString(&relpath[pathlen], pathmax - pathlen,
                                                  path, curlen, &stickyFailure);
                    if (c != '\0')
                    {
                        pathlen += FSi_CopySafeString(&relpath[pathlen], pathmax - pathlen,
                                                      "/", 1, &stickyFailure);
                    }
                }
                if (c == '\0')
                {
                    break;
                }
                path += curlen + 1;
                curlen = 0;
            }
        }
        relpath[pathlen] = '\0';
        pathlen = FSi_TrimSjisTrailingSlash(relpath);
    }
    return stickyFailure ? NULL : arc;
}

/*---------------------------------------------------------------------------*
  Name:         FS_InitArchive

  Description:  Initializes archive structure.

  Arguments:    p_arc: Archive to initialize

  Returns:      None.
 *---------------------------------------------------------------------------*/
void FS_InitArchive(FSArchive *p_arc)
{
    SDK_NULL_ASSERT(p_arc);
    MI_CpuClear8(p_arc, sizeof(FSArchive));
    OS_InitThreadQueue(&p_arc->queue);
}

/*---------------------------------------------------------------------------*
  Name:         FS_RegisterArchiveName

  Description:  Registers the archive name in the file system and associates it.
                The archive itself is not yet loaded into the file system.
                The archive name "rom" is reserved by the file system.

  Arguments:    p_arc: Archive to associate with the name
                name: String of the name to register
                name_len: Length of name string

  Returns:      None.
 *---------------------------------------------------------------------------*/
BOOL FS_RegisterArchiveName(FSArchive *p_arc, const char *name, u32 name_len)
{
    BOOL    retval = FALSE;

    SDK_ASSERT(FS_IsAvailable());
    SDK_NULL_ASSERT(p_arc);
    SDK_NULL_ASSERT(name);

    {
        OSIntrMode bak_intr = OS_DisableInterrupts();
        if (!FS_FindArchive(name, (s32)name_len))
        {
            // Adds to the end of the list
            FSArchive **pp;
            for (pp = &arc_list; *pp; pp = &(*pp)->next)
            {
            }
            *pp = p_arc;
            // With the current specifications, the archive has a maximum of 3 characters
            if (name_len <= FS_ARCHIVE_NAME_LEN_MAX)
            {
                p_arc->name.pack = 0;
                (void)STD_CopyLString(p_arc->name.ptr, name, (int)(name_len + 1));
            }
            else
            {
#if defined(FS_SUPPORT_LONG_ARCNAME)
            // Feature for expanding the limit of the number of characters in an archive name
            // It is necessary to allocate external memory to support without changing the FSArchive structure size
            // 
                if (name_len <= FS_LONG_ARCNAME_LENGTH_MAX)
                {
                    int i;
                    for (i = 0; ; ++i)
                    {
                        if (i >= FS_LONG_ARCNAME_TABLE_MAX)
                        {
                            OS_TPanic("failed to allocate memory for long archive-name(%.*s)!", name_len, name);
                        }
                        else if (FSiLongNameTable[i][0] == '\0')
                        {
                            (void)STD_CopyLString(FSiLongNameTable[i], name, (int)(name_len + 1));
                            p_arc->name.pack = (u32)FSiLongNameTable[i];
                            break;
                        }
                    }
                }
#endif
                // Cannot register archive names that are too long
                else
                {
                    OS_TPanic("too long archive-name(%.*s)!", name_len, name);
                }
            }
            p_arc->flag |= FS_ARCHIVE_FLAG_REGISTER;
            retval = TRUE;
        }
        (void)OS_RestoreInterrupts(bak_intr);
    }
    return retval;
}

/*---------------------------------------------------------------------------*
  Name:         FS_ReleaseArchiveName

  Description:  Release registered archive names.
                They must be unloaded from the file system.

  Arguments:    p_arc: Archive with name to release

  Returns:      None.
 *---------------------------------------------------------------------------*/
void FS_ReleaseArchiveName(FSArchive *p_arc)
{
    SDK_ASSERT(FS_IsAvailable());
    SDK_NULL_ASSERT(p_arc);

	if(p_arc == arc_list)
    {
        OS_TPanic("[file-system] cannot modify \"rom\" archive.\n");
    }

    if (p_arc->name.pack)
    {
        OSIntrMode bak_psr = OS_DisableInterrupts();
        // Cut from the list
        FSArchive **pp;
        for (pp = &arc_list; *pp; pp = &(*pp)->next)
        {
            if(*pp == p_arc)
            {
                *pp = (*pp)->next;
                break;
            }
        }
#if defined(FS_SUPPORT_LONG_ARCNAME)
        // Deallocate buffer to the system if there is a long archive name
        if (p_arc->name.ptr[3] != '\0')
        {
            ((char *)p_arc->name.pack)[0] = '\0';
        }
#endif
        p_arc->name.pack = 0;
        p_arc->next = NULL;
        p_arc->flag &= ~FS_ARCHIVE_FLAG_REGISTER;
        // Deallocate if it is the current archive
        if (current_dir_pos.arc == p_arc)
        {
            current_dir_pos.arc = NULL;
        }
        (void)OS_RestoreInterrupts(bak_psr);
    }
}

/*---------------------------------------------------------------------------*
  Name:         FS_GetArchiveName

  Description:  Gets archive name.

  Arguments:    p_arc: Archive whose name to get

  Returns:      Archive name registered in the file system.
 *---------------------------------------------------------------------------*/
const char *FS_GetArchiveName(const FSArchive *arc)
{
#if defined(FS_SUPPORT_LONG_ARCNAME)
    return (arc->name.ptr[3] != '\0') ? (const char *)arc->name.pack : arc->name.ptr;
#else
    return arc->name.ptr;
#endif
}

/*---------------------------------------------------------------------------*
  Name:         FS_MountArchive

  Description:  Mounts the archive.

  Arguments:    arc: Archive to mount
                userdata: User-defined variable that associates with an archive
                vtbl: Command interface
                reserved: Reserved for future use (always specify 0)


  Returns:      TRUE if mounting is successful.
 *---------------------------------------------------------------------------*/
BOOL FS_MountArchive(FSArchive *arc, void *userdata,
                     const FSArchiveInterface *vtbl, u32 reserved)
{
    (void)reserved;
    SDK_ASSERT(FS_IsAvailable());
    SDK_NULL_ASSERT(arc);
    SDK_ASSERT(!FS_IsArchiveLoaded(arc));
    // Initialize archive with new specifications
    arc->userdata = userdata;
    arc->vtbl = vtbl;
    // Issue mounting notification event
    {
        FSFile  tmp[1];
        FS_InitFile(tmp);
        tmp->arc = arc;
        (void)FSi_InvokeCommand(tmp, FS_COMMAND_MOUNT);
    }
    arc->flag |= FS_ARCHIVE_FLAG_LOADED;
    return TRUE;
}

/*---------------------------------------------------------------------------*
  Name:         FS_UnmountArchive

  Description:  Unmounts the archive.

  Arguments:    arc: Archive to unmount

  Returns:      TRUE if unmounting is successful.
 *---------------------------------------------------------------------------*/
BOOL    FS_UnmountArchive(FSArchive *arc)
{
    SDK_ASSERT(FS_IsAvailable());
    SDK_NULL_ASSERT(arc);

    {
        OSIntrMode bak_psr = OS_DisableInterrupts();
        // Ignore if it is not loaded
        if (FS_IsArchiveLoaded(arc))
        {
            // Cancel all idling commands
            {
                BOOL    bak_state = FS_SuspendArchive(arc);
                FSFile *file = arc->list;
                arc->flag |= FS_ARCHIVE_FLAG_UNLOADING;
                while (file)
                {
                    FSFile *next = file->next;
                    FSi_EndCommand(file, FS_RESULT_CANCELED);
                    file = next;
                }
                arc->list = NULL;
                if (bak_state)
                {
                    (void)FS_ResumeArchive(arc);
                }
            }
            // Issue unmounting notification event
            {
                FSFile  tmp[1];
                FS_InitFile(tmp);
                tmp->arc = arc;
                (void)FSi_InvokeCommand(tmp, FS_COMMAND_UNMOUNT);
            }
            arc->flag &= ~(FS_ARCHIVE_FLAG_CANCELING |
                           FS_ARCHIVE_FLAG_LOADED | FS_ARCHIVE_FLAG_UNLOADING);
        }
        (void)OS_RestoreInterrupts(bak_psr);
    }
    return TRUE;

}

/*---------------------------------------------------------------------------*
  Name:         FS_SuspendArchive

  Description:  Stops archive processing mechanism itself.
                If a process is currently executing, waits for it to complete.

  Arguments:    p_arc: Archive to suspend

  Returns:      TRUE if it was not in a suspended state before the call.
 *---------------------------------------------------------------------------*/
BOOL FS_SuspendArchive(FSArchive *p_arc)
{
    BOOL    retval = FALSE;

    SDK_ASSERT(FS_IsAvailable());
    SDK_NULL_ASSERT(p_arc);

    {
        OSIntrMode bak_psr = OS_DisableInterrupts();
        retval = !FS_IsArchiveSuspended(p_arc);
        if (retval)
        {
            if ((p_arc->flag & FS_ARCHIVE_FLAG_RUNNING) == 0)
            {
                p_arc->flag |= FS_ARCHIVE_FLAG_SUSPEND;
            }
            else
            {
                p_arc->flag |= FS_ARCHIVE_FLAG_SUSPENDING;
                FSi_WaitConditionOff(&p_arc->flag, FS_ARCHIVE_FLAG_SUSPENDING, &p_arc->queue);
            }
        }
        (void)OS_RestoreInterrupts(bak_psr);
    }
    return retval;
}

/*---------------------------------------------------------------------------*
  Name:         FS_ResumeArchive

  Description:  Resumes the suspended archive processing.

  Arguments:    arc: Archive to reopen

  Returns:      TRUE if it was not in a suspended state before the call.
 *---------------------------------------------------------------------------*/
BOOL FS_ResumeArchive(FSArchive *arc)
{
    BOOL    retval;
    SDK_ASSERT(FS_IsAvailable());
    SDK_NULL_ASSERT(arc);
    {
        OSIntrMode bak_irq = OS_DisableInterrupts();
        retval = !FS_IsArchiveSuspended(arc);
        if (!retval)
        {
            arc->flag &= ~FS_ARCHIVE_FLAG_SUSPEND;
        }
        (void)OS_RestoreInterrupts(bak_irq);
    }
    {
        FSFile *file = NULL;
        file = FSi_NextCommand(arc, TRUE);
        if (file)
        {
            FSi_ExecuteAsyncCommand(file);
        }
    }
    return retval;
}


/*---------------------------------------------------------------------------*
  Name:         FS_NotifyArchiveAsyncEnd

  Description:  This is called from the archive implementation to send a notification when asynchronous archive processing is complete.
                

  Arguments:    arc: Archive for which to notify completion
                ret: Processing result

  Returns:      None.
 *---------------------------------------------------------------------------*/
void FS_NotifyArchiveAsyncEnd(FSArchive *arc, FSResult ret)
{
    FSFile     *file = arc->list;
    if ((file->stat & FS_FILE_STATUS_BLOCKING) != 0)
    {
        OSIntrMode bak_psr = OS_DisableInterrupts();
        file->stat |= FS_FILE_STATUS_ASYNC_DONE;
        file->error = ret;
        OS_WakeupThread(file->queue);
        (void)OS_RestoreInterrupts(bak_psr);
    }
    else
    {
        FSi_EndCommand(file, ret);
        file = FSi_NextCommand(arc, TRUE);
        if (file)
        {
            FSi_ExecuteAsyncCommand(file);
        }
    }
}


/*---------------------------------------------------------------------------*
  Name:         FS_WaitAsync

  Description:  Waits for end of asynchronous function and returns result.

  Arguments:    File to wait

  Returns:      If succeeded, TRUE.
 *---------------------------------------------------------------------------*/
BOOL FS_WaitAsync(FSFile *file)
{
    SDK_NULL_ASSERT(file);
    SDK_ASSERT(FS_IsAvailable());
    SDK_ASSERT(OS_GetProcMode() != OS_PROCMODE_IRQ);

    {
        BOOL    is_owner = FALSE;
        OSIntrMode bak_psr = OS_DisableInterrupts();
        if (FS_IsBusy(file))
        {
            // Take on processing here if an unprocessed asynchronous mode
            is_owner = !(file->stat & (FS_FILE_STATUS_BLOCKING | FS_FILE_STATUS_OPERATING));
            if (is_owner)
            {
                file->stat |= FS_FILE_STATUS_BLOCKING;
            }
        }
        (void)OS_RestoreInterrupts(bak_psr);
        if (is_owner)
        {
            FSi_ExecuteSyncCommand(file);
        }
        else
        {
            FSi_WaitConditionOff(&file->stat, FS_FILE_STATUS_BUSY, file->queue);
        }
    }

    return FS_IsSucceeded(file);
}


#endif /* FS_IMPLEMENT */
