/**
 * Copyright(c) 2011 Trusted Logic.   All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name Trusted Logic nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if defined(__ANDROID32__)
#include <stddef.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * When porting to a new OS, insert here the appropriate include files
 */
#include <sys/stat.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>

#if defined(LINUX) || defined(__ANDROID32__)
#include <unistd.h>
#include <sys/resource.h>


#if defined(__ANDROID32__)
/* fdatasync does not exist on Android */
#define fdatasync fsync
#else
/*
 * http://linux.die.net/man/2/fsync
 * The function fdatasync seems to be absent of the header file
 * in some distributions
 */
int fdatasync(int fd);
#endif /* __ANDROID32__ */
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <semaphore.h>
#define PATH_SEPARATOR '/'
#endif /* LINUX || __ANDROID32__ */

#ifdef WIN32
#include <windows.h>
#include <io.h>
#define PATH_SEPARATOR '\\'
#endif

#ifdef __SYMBIAN32__
#include <unistd.h>
#include "os_symbian.h"
#define PATH_SEPARATOR '\\'
#endif

#include <stdarg.h>
#include <assert.h>

#include "service_delegation_protocol.h"

#include "s_version.h"
#include "s_error.h"
#include "tee_client_api.h"

/* You can define the preprocessor constant SUPPORT_DELEGATION_EXTENSION
   if you want to pass extended options in a configuration file (option '-c').
   It is up to you to define the format of this configuration file and the
   extended option in the source file delegation_client_extension.c. You can
   use extended options, e.g., to control the name of each partition file. */
#ifdef SUPPORT_DELEGATION_EXTENSION
#include "delegation_client_extension.h"
#endif

/*----------------------------------------------------------------------------
 * Design notes
 * ============
 *
 * This implementation of the delegation daemon supports the protocol
 * specified in the Product Reference Manual ("Built-in Services Protocols Specification")
 *
 *----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
 * Defines and structures
 *----------------------------------------------------------------------------*/
#define ECHANGE_BUFFER_INSTRUCTIONS_NB 100

#define DEFAULT_WORKSPACE_SIZE (128*1024)

/* A single shared memory block is used to contain the administrative data, the
   instruction buffer and the workspace. The size of the instruction buffer is
   fixed, but the size of workspace can be configured using the "-workspaceSize"
   command-line option. */
typedef struct
{
   DELEGATION_ADMINISTRATIVE_DATA sAdministrativeData;
   uint32_t                       sInstructions[ECHANGE_BUFFER_INSTRUCTIONS_NB];
   uint8_t                        sWorkspace[1/*g_nWorkspaceSize*/];
} DELEGATION_EXCHANGE_BUFFER;

#define MD_VAR_NOT_USED(variable)  do{(void)(variable);}while(0);

#define MD_INLINE __inline

/* ----------------------------------------------
   Traces and logs

   On Linux, traces and logs go either to the console (stderr) or to the syslog.
   When the daemon is started, the logs go to the console. Once and if the daemon
   is detached, the logs go to syslog.

   On other systems, traces and logs go systematically to stderr

   The difference between traces and logs is that traces are compiled out
   in release builds whereas logs are visible to the customer.

   -----------------------------------------------*/
#if defined(LINUX) || (defined __ANDROID32__)

static bool bDetached = false;

static MD_INLINE void LogError(const char* format, ...)
{
   va_list ap;
   va_start(ap, format);
   if (bDetached)
   {
      vsyslog(LOG_ERR, format, ap);
   }
   else
   {
      fprintf(stderr, "ERROR: ");
      vfprintf(stderr, format, ap);
      fprintf(stderr, "\n");
   }
   va_end(ap);
}

static MD_INLINE void LogWarning(const char* format, ...)
{
   va_list ap;
   va_start(ap, format);
   if (bDetached)
   {
      vsyslog(LOG_WARNING, format, ap);
   }
   else
   {
      fprintf(stderr, "WARNING: ");
      vfprintf(stderr, format, ap);
      fprintf(stderr, "\n");
   }
   va_end(ap);
}
static MD_INLINE void LogInfo(const char* format, ...)
{
   va_list ap;
   va_start(ap, format);
   if (bDetached)
   {
      vsyslog(LOG_INFO, format, ap);
   }
   else
   {
      vfprintf(stderr, format, ap);
      fprintf(stderr, "\n");
   }
   va_end(ap);
}

static MD_INLINE void TRACE_ERROR(const char* format, ...)
{
#ifndef NDEBUG
   va_list ap;
   va_start(ap, format);
   if (bDetached)
   {
      vsyslog(LOG_ERR, format, ap);
   }
   else
   {
      fprintf(stderr, "TRACE: ERROR: ");
      vfprintf(stderr, format, ap);
      fprintf(stderr, "\n");
   }
   va_end(ap);
#else
   MD_VAR_NOT_USED(format);
#endif /* NDEBUG */
}

static MD_INLINE void TRACE_WARNING(const char* format, ...)
{
#ifndef NDEBUG
   va_list ap;
   va_start(ap, format);
   if (bDetached)
   {
      vsyslog(LOG_WARNING, format, ap);
   }
   else
   {
      fprintf(stderr, "TRACE: WARNING: ");
      vfprintf(stderr, format, ap);
      fprintf(stderr, "\n");
   }
   va_end(ap);
#else
   MD_VAR_NOT_USED(format);
#endif /* NDEBUG */
}

static MD_INLINE void TRACE_INFO(const char* format, ...)
{
#ifndef NDEBUG
   va_list ap;
   va_start(ap, format);
   if (bDetached)
   {
      vsyslog(LOG_DEBUG, format, ap);
   }
   else
   {
      fprintf(stderr, "TRACE: ");
      vfprintf(stderr, format, ap);
      fprintf(stderr, "\n");
   }
   va_end(ap);
#else
   MD_VAR_NOT_USED(format);
#endif /* NDEBUG */
}
#elif defined __SYMBIAN32__
/* defined in os_symbian.h */

#elif defined NO_LOG_NO_TRACE
static MD_INLINE void LogError(const char* format, ...)
{
   MD_VAR_NOT_USED(format);
}
static MD_INLINE void LogWarning(const char* format, ...)
{
   MD_VAR_NOT_USED(format);
}
static MD_INLINE void LogInfo(const char* format, ...)
{
   MD_VAR_NOT_USED(format);
}

static MD_INLINE void TRACE_ERROR(const char* format, ...)
{
   MD_VAR_NOT_USED(format);
}

static MD_INLINE void TRACE_WARNING(const char* format, ...)
{
   MD_VAR_NOT_USED(format);
}

static MD_INLINE void TRACE_INFO(const char* format, ...)
{
   MD_VAR_NOT_USED(format);
}

#else
/* !defined(LINUX) || !defined(__ANDROID32__) */

static MD_INLINE void LogError(const char* format, ...)
{
   va_list ap;
   va_start(ap, format);
   fprintf(stderr, "ERROR: ");
   vfprintf(stderr, format, ap);
   fprintf(stderr, "\n");
   va_end(ap);
}
static MD_INLINE void LogWarning(const char* format, ...)
{
   va_list ap;
   va_start(ap, format);
   fprintf(stderr, "WARNING: ");
   vfprintf(stderr, format, ap);
   fprintf(stderr, "\n");
   va_end(ap);
}
static MD_INLINE void LogInfo(const char* format, ...)
{
   va_list ap;
   va_start(ap, format);
   vfprintf(stderr, format, ap);
   fprintf(stderr, "\n");
   va_end(ap);
}

static MD_INLINE void TRACE_ERROR(const char* format, ...)
{
#ifndef NDEBUG
   va_list ap;
   va_start(ap, format);
   fprintf(stderr, "TRACE: ERROR: ");
   vfprintf(stderr, format, ap);
   fprintf(stderr, "\n");
   va_end(ap);
#else
   MD_VAR_NOT_USED(format);
#endif /* NDEBUG */
}

static MD_INLINE void TRACE_WARNING(const char* format, ...)
{
#ifndef NDEBUG
   va_list ap;
   va_start(ap, format);
   fprintf(stderr, "TRACE: WARNING: ");
   vfprintf(stderr, format, ap);
   fprintf(stderr, "\n");
   va_end(ap);
#else
   MD_VAR_NOT_USED(format);
#endif /* NDEBUG */
}

static MD_INLINE void TRACE_INFO(const char* format, ...)
{
#ifndef NDEBUG
   va_list ap;
   va_start(ap, format);
   fprintf(stderr, "TRACE: ");
   vfprintf(stderr, format, ap);
   fprintf(stderr, "\n");
   va_end(ap);
#else
   MD_VAR_NOT_USED(format);
#endif /* NDEBUG */
}
#endif /* defined(LINUX) || defined(__ANDROID32__) */

/*----------------------------------------------------------------------------
 * Globals
 *----------------------------------------------------------------------------*/
/* The sector size */
static uint32_t g_nSectorSize;

/* The workspace size */
static uint32_t g_nWorkspaceSize = DEFAULT_WORKSPACE_SIZE;

/* UUID of the delegation service */
static const TEEC_UUID g_sServiceId = SERVICE_DELEGATION_UUID;

/* pWorkspaceBuffer points to the workspace buffer shared with the secure
   world to transfer the sectors in the READ and WRITE instructions  */
static uint8_t* g_pWorkspaceBuffer;
static DELEGATION_EXCHANGE_BUFFER * g_pExchangeBuffer;
TEEC_SharedMemory sExchangeSharedMem;
/*
   The absolute path name for each of the 16 possible partitions.
 */
static char* g_pPartitionNames[16];

/* The file context for each of the 16 possible partitions. An entry
   in this array is NULL if the corresponding partition is currently not opened
 */
static FILE* g_pPartitionFiles[16];

/*----------------------------------------------------------------------------
 * Utilities functions
 *----------------------------------------------------------------------------*/
static void printUsage(void)
{
   LogInfo("usage : tf_daemon [options]");
   LogInfo("where [options] are:");
   LogInfo("-h --help  Display help.");
#ifdef SUPPORT_DELEGATION_EXTENSION
   LogInfo("-c <conf>  Configuration file path.");
#else
   /* If the compilation parameter SUPPORT_DELEGATION_EXTENSION is not set, each
      partition is stored as a file within the base dir */
   LogInfo("-storageDir <baseDir>  Set the directory where the data will be stored; this directory");
   LogInfo("           must be writable and executable (this parameter is mandatory)");
#endif
   LogInfo("-d         Turns on debug mode.  If not specified, the daemon will fork itself");
   LogInfo("           and get detached from the console.");
#ifndef SUPPORT_DELEGATION_EXTENSION
   LogInfo("-workspaceSize <integer>  Set the size in bytes of the workspace. Must be greater or equal to 8 sectors.");
   LogInfo("           (default is 128KB)");
#endif
}

static TEEC_Result errno2serror(void)
{
   switch (errno)
   {
   case EINVAL:
      return S_ERROR_BAD_PARAMETERS;
   case EMFILE:
      return S_ERROR_NO_MORE_HANDLES;
   case ENOENT:
      return S_ERROR_ITEM_NOT_FOUND;
   case EEXIST:
      return S_ERROR_ITEM_EXISTS;
   case ENOSPC:
      return S_ERROR_STORAGE_NO_SPACE;
   case ENOMEM:
      return S_ERROR_OUT_OF_MEMORY;
   case EBADF:
   case EACCES:
   default:
      return S_ERROR_STORAGE_UNREACHABLE;
   }
}

/*
 * Check if the directory in parameter exists with Read/Write access
 * Return 0 in case of success and 1 otherwise.
 */
int static_checkStorageDirAndAccessRights(char * directoryName)
{
#ifdef __SYMBIAN32__
   /* it looks like stat is not working properly on Symbian
      Create and remove dummy file to check access rights */
   FILE *stream;
   char *checkAccess = NULL;

   if (directoryName == NULL)
   {
      LogError("Directory Name is NULL");
      return 1;
   }

   checkAccess = malloc(strlen(directoryName)+1/* \ */ +1 /* a */ + 1 /* 0 */);
   if (!checkAccess)
   {
      LogError("storageDir '%s' allocation error", directoryName);
      return 1;
   }
   sprintf(checkAccess,"%s\\a",directoryName);
   stream = fopen(checkAccess, "w+b");
   if (!stream)
   {
      LogError("storageDir '%s' is incorrect or cannot be reached", directoryName);
      return 1;
   }
   fclose(stream);
   unlink(checkAccess);
#else
   /* Non-Symbian OS: use stat */
   struct stat buf;
   int result = 0;

   if (directoryName == NULL)
   {
      LogError("Directory Name is NULL");
      return 1;
   }

   result = stat(directoryName, &buf);
   if (result == 0)
   {
      /* Storage dir exists. Check access rights */
#if defined(LINUX) || (defined __ANDROID32__)
      if ((buf.st_mode & (S_IXUSR | S_IWUSR)) != (S_IXUSR | S_IWUSR))
      {
         LogError("storageDir '%s' does not have read-write access", directoryName);
         return 1;
      }
#endif
   }
   else if (errno == ENOENT)
   {
      LogError("storageDir '%s' does not exist", directoryName);
      return 1;
   }
   else
   {
      /* Another error */
      LogError("storageDir '%s' is incorrect or cannot be reached", directoryName);
      return 1;
   }
#endif
   return 0;
}



/*----------------------------------------------------------------------------
 * Instructions
 *----------------------------------------------------------------------------*/

/**
 * This function executes the DESTROY_PARTITION instruction
 *
 * @param nPartitionID: the partition identifier
 **/
static TEEC_Result partitionDestroy(uint32_t nPartitionID)
{
   TEEC_Result  nError = S_SUCCESS;

   if (g_pPartitionFiles[nPartitionID] != NULL)
   {
      /* The partition must not be currently opened */
      LogError("g_pPartitionFiles not NULL");
      return S_ERROR_BAD_STATE;
   }

   /* Try to erase the file */
#if defined(LINUX) || (defined __ANDROID32__) || defined (__SYMBIAN32__)
   if (unlink(g_pPartitionNames[nPartitionID]) != 0)
#endif
#ifdef WIN32
   if (_unlink(g_pPartitionNames[nPartitionID]) != 0)
#endif
   {
      /* File in use or OS didn't allow the operation */
      nError = errno2serror();
   }

   return nError;
}

/**
 * This function executes the CREATE_PARTITION instruction. When successful,
 * it fills the g_pPartitionFiles[nPartitionID] slot.
 *
 * @param nPartitionID: the partition identifier
 **/
static TEEC_Result partitionCreate(uint32_t nPartitionID)
{
   uint32_t nError = S_SUCCESS;

   if (g_pPartitionFiles[nPartitionID] != NULL)
   {
      /* The partition is already opened */
      LogError("g_pPartitionFiles not NULL");
      return S_ERROR_BAD_STATE;
   }

   /* Create the file unconditionnally */
   LogInfo("Create storage file \"%s\"", g_pPartitionNames[nPartitionID]);
   g_pPartitionFiles[nPartitionID] = fopen(g_pPartitionNames[nPartitionID], "w+b");

   if (g_pPartitionFiles[nPartitionID] == NULL)
   {
      LogError("Cannot create storage file \"%s\"", g_pPartitionNames[nPartitionID]);
      nError = errno2serror();
      return nError;
   }

   return nError;
}

/**
 * This function executes the OPEN_PARTITION instruction. When successful,
 * it fills the g_pPartitionFiles[nPartitionID] slot and writes the partition
 * size in hResultEncoder
 *
 * @param nPartitionID: the partition identifier
 * @param pnPartitionSize: filled with the number of sectors in the partition
 **/
static TEEC_Result partitionOpen(uint32_t nPartitionID, uint32_t* pnPartitionSize)
{
   uint32_t nError = S_SUCCESS;

   if (g_pPartitionFiles[nPartitionID] != NULL)
   {
      /* No partition must be currently opened in the session */
      LogError("g_pPartitionFiles not NULL");
      return S_ERROR_BAD_STATE;
   }

   /* Open the file */
   g_pPartitionFiles[nPartitionID] = fopen(g_pPartitionNames[nPartitionID], "r+b");
   if (g_pPartitionFiles[nPartitionID] == NULL)
   {
      if (errno == ENOENT)
      {
         /* File does not exist */
         LogError("Storage file \"%s\" does not exist", g_pPartitionNames[nPartitionID]);
         nError = S_ERROR_ITEM_NOT_FOUND;
         return nError;
      }
      else
      {
         LogError("cannot open storage file \"%s\"", g_pPartitionNames[nPartitionID]);
         nError = errno2serror();
         return nError;
      }
   }
   /* Determine the current number of sectors */
   fseek(g_pPartitionFiles[nPartitionID], 0L, SEEK_END);
   *pnPartitionSize = ftell(g_pPartitionFiles[nPartitionID]) / g_nSectorSize;

   LogInfo("storage file \"%s\" successfully opened (size = %d KB (%d bytes))",
      g_pPartitionNames[nPartitionID],
      ((*pnPartitionSize) * g_nSectorSize) / 1024,
      ((*pnPartitionSize) * g_nSectorSize));

   return nError;
}


/**
 * This function executes the CLOSE_PARTITION instruction.
 * It closes the partition file.
 *
 * @param nPartitionID: the partition identifier
 **/
static TEEC_Result partitionClose(uint32_t nPartitionID)
{
   if (g_pPartitionFiles[nPartitionID] == NULL)
   {
      /* The partition is currently not opened */
      return S_ERROR_BAD_STATE;
   }
   fclose(g_pPartitionFiles[nPartitionID]);
   g_pPartitionFiles[nPartitionID] = NULL;
   return S_SUCCESS;
}

/**
 * This function executes the READ instruction.
 *
 * @param nPartitionID: the partition identifier
 * @param nSectorIndex: the index of the sector to read
 * @param nWorkspaceOffset: the offset in the workspace where the sector must be written
 **/
static TEEC_Result partitionRead(uint32_t nPartitionID, uint32_t nSectorIndex, uint32_t nWorkspaceOffset)
{
   FILE* pFile;

   TRACE_INFO(">Partition %1X: read sector 0x%08X into workspace at offset 0x%08X",
      nPartitionID, nSectorIndex, nWorkspaceOffset);

   pFile = g_pPartitionFiles[nPartitionID];

   if (pFile == NULL)
   {
      /* The partition is not opened */
      return S_ERROR_BAD_STATE;
   }

   if (fseek(pFile, nSectorIndex*g_nSectorSize, SEEK_SET) != 0)
   {
      LogError("fseek error: %s", strerror(errno));
      return errno2serror();
   }

   if (fread(g_pWorkspaceBuffer + nWorkspaceOffset,
             g_nSectorSize, 1,
             pFile) != 1)
   {
      if (feof(pFile))
      {
         LogError("fread error: End-Of-File detected");
         return S_ERROR_ITEM_NOT_FOUND;
      }
      LogError("fread error: %s", strerror(errno));
      return errno2serror();
   }

   return S_SUCCESS;
}

/**
 * This function executes the WRITE instruction.
 *
 * @param nPartitionID: the partition identifier
 * @param nSectorIndex: the index of the sector to read
 * @param nWorkspaceOffset: the offset in the workspace where the sector must be read
 **/
static TEEC_Result partitionWrite(uint32_t nPartitionID, uint32_t nSectorIndex, uint32_t nWorkspaceOffset)
{
   FILE* pFile;

   TRACE_INFO(">Partition %1X: write sector 0x%X from workspace at offset 0x%X",
      nPartitionID, nSectorIndex, nWorkspaceOffset);

   pFile = g_pPartitionFiles[nPartitionID];

   if (pFile == NULL)
   {
      /* The partition is not opened */
      return S_ERROR_BAD_STATE;
   }

   if (fseek(pFile, nSectorIndex*g_nSectorSize, SEEK_SET) != 0)
   {
      LogError("fseek error: %s", strerror(errno));
      return errno2serror();
   }

   if (fwrite(g_pWorkspaceBuffer + nWorkspaceOffset,
              g_nSectorSize, 1,
              pFile) != 1)
   {
      LogError("fread error: %s", strerror(errno));
      return errno2serror();
   }
   return S_SUCCESS;
}


/**
 * This function executes the SET_SIZE instruction.
 *
 * @param nPartitionID: the partition identifier
 * @param nNewSectorCount: the new sector count
 **/
static TEEC_Result partitionSetSize(uint32_t nPartitionID, uint32_t nNewSectorCount)
{
   FILE* pFile;
   uint32_t nCurrentSectorCount;

   pFile = g_pPartitionFiles[nPartitionID];

   if (pFile==NULL)
   {
      /* The partition is not opened */
      return S_ERROR_BAD_STATE;
   }

   /* Determine the current size of the partition */
   if (fseek(pFile, 0, SEEK_END) != 0)
   {
      LogError("fseek error: %s", strerror(errno));
      return errno2serror();
   }
   nCurrentSectorCount = ftell(pFile) / g_nSectorSize;

   if (nNewSectorCount > nCurrentSectorCount)
   {
      uint32_t nAddedBytesCount;
      /* Enlarge the partition file. Make sure we actually write
         some non-zero data into the new sectors. Otherwise, some file-system
         might not really reserve the storage space but use a
         sparse representation. In this case, a subsequent write instruction
         could fail due to out-of-space, which we want to avoid. */
      nAddedBytesCount = (nNewSectorCount-nCurrentSectorCount)*g_nSectorSize;
      while (nAddedBytesCount)
      {
         if (fputc(0xA5, pFile)!=0xA5)
         {
            return errno2serror();
         }
         nAddedBytesCount--;
      }
   }
   else if (nNewSectorCount < nCurrentSectorCount)
   {
      int result = 0;
      /* Truncate the partition file */
#if defined(LINUX) || (defined __ANDROID32__)
      result = ftruncate(fileno(pFile),nNewSectorCount * g_nSectorSize);
#endif
#if defined (__SYMBIAN32__)
	  LogError("No truncate available in Symbian C API");
#endif
#ifdef WIN32
      result = _chsize(_fileno(pFile),nNewSectorCount * g_nSectorSize);
#endif
      if (result)
      {
         return errno2serror();
      }
   }
   return S_SUCCESS;
}

/**
 * This function executes the SYNC instruction.
 *
 * @param pPartitionID: the partition identifier
 **/
static TEEC_Result partitionSync(uint32_t nPartitionID)
{
   TEEC_Result nError = S_SUCCESS;
   int result;

   FILE* pFile = g_pPartitionFiles[nPartitionID];

   if (pFile == NULL)
   {
      /* The partition is not currently opened */
      return S_ERROR_BAD_STATE;
   }

   /* First make sure that the data in the stdio buffers
      is flushed to the file descriptor */
   result=fflush(pFile);
   if (result)
   {
      nError=errno2serror();
      goto end;
   }
   /* Then synchronize the file descriptor with the file-system */

#if defined(LINUX) || (defined __ANDROID32__)
   result=fdatasync(fileno(pFile));
#endif
#if defined (__SYMBIAN32__)
   result=fsync(fileno(pFile));
#endif
#ifdef WIN32
   result=_commit(_fileno(pFile));
#endif
   if (result)
   {
      nError=errno2serror();
   }

end:
   return nError;
}

/**
 * This function executes the NOTIFY instruction.
 *
 * @param pMessage the message string
 * @param nMessageType the type of messages
 **/
static void notify(const wchar_t* pMessage, uint32_t nMessageType)
{
   switch (nMessageType)
   {
   case DELEGATION_NOTIFY_TYPE_ERROR:
      LogError("%ls", pMessage);
      break;
   case DELEGATION_NOTIFY_TYPE_WARNING:
      LogWarning("%ls", pMessage);
      break;
   case DELEGATION_NOTIFY_TYPE_DEBUG:
      LogInfo("DEBUG: %ls", pMessage);
      break;
   case DELEGATION_NOTIFY_TYPE_INFO:
   default:
      LogInfo("%ls", pMessage);
      break;
   }
}

/*----------------------------------------------------------------------------
 * Session main function
 *----------------------------------------------------------------------------*/

/*
 * This function runs a session opened on the delegation service. It fetches
 * instructions and execute them in a loop. It never returns, but may call
 * exit when instructed to shutdown by the service
 */
static int runSession(TEEC_Context* pContext, TEEC_Session* pSession, TEEC_Operation* pOperation)
{
   memset(&g_pExchangeBuffer->sAdministrativeData, 0x00, sizeof(g_pExchangeBuffer->sAdministrativeData));

   while (true)
   {
      S_RESULT    nError;
      TEEC_Result                      nTeeError;
      uint32_t                         nInstructionsIndex;
      uint32_t                         nInstructionsBufferSize = sizeof(g_pExchangeBuffer->sInstructions);

      pOperation->paramTypes = TEEC_PARAM_TYPES(
         TEEC_MEMREF_PARTIAL_INPUT,
         TEEC_MEMREF_PARTIAL_OUTPUT,
         TEEC_MEMREF_PARTIAL_INOUT,
         TEEC_NONE);
      pOperation->params[0].memref.parent = &sExchangeSharedMem;
      pOperation->params[0].memref.offset = offsetof(DELEGATION_EXCHANGE_BUFFER, sAdministrativeData);
      pOperation->params[0].memref.size   = sizeof(g_pExchangeBuffer->sAdministrativeData);

      pOperation->params[1].memref.parent = &sExchangeSharedMem;
      pOperation->params[1].memref.offset = offsetof(DELEGATION_EXCHANGE_BUFFER, sInstructions);
      pOperation->params[1].memref.size   = sizeof(g_pExchangeBuffer->sInstructions);

      pOperation->params[2].memref.parent = &sExchangeSharedMem;
      pOperation->params[2].memref.offset = offsetof(DELEGATION_EXCHANGE_BUFFER, sWorkspace);
      pOperation->params[2].memref.size   = g_nWorkspaceSize;

      nTeeError = TEEC_InvokeCommand(pSession,
                                  SERVICE_DELEGATION_GET_INSTRUCTIONS,   /* commandID */
                                  pOperation,     /* IN OUT operation */
                                  NULL             /* OUT errorOrigin, optional */
                                 );

      if (nTeeError != TEEC_SUCCESS)
      {
         LogError("TEEC_InvokeCommand error: 0x%08X", nTeeError);
         LogError("Daemon exits");
         exit(2);
      }

      if (pOperation->params[1].tmpref.size >  nInstructionsBufferSize)
      {
         /* Should not happen, probably an error from the service */
         pOperation->params[1].tmpref.size = 0;
      }

      /* Reset the operation results */
      nError = TEEC_SUCCESS;
      g_pExchangeBuffer->sAdministrativeData.nSyncExecuted = 0;
      memset(g_pExchangeBuffer->sAdministrativeData.nPartitionErrorStates, 0x00, sizeof(g_pExchangeBuffer->sAdministrativeData.nPartitionErrorStates));
      memset(g_pExchangeBuffer->sAdministrativeData.nPartitionOpenSizes, 0x00, sizeof(g_pExchangeBuffer->sAdministrativeData.nPartitionOpenSizes));

      /* Execute the instructions */
      nInstructionsIndex = 0;
      nInstructionsBufferSize = pOperation->params[1].tmpref.size;
      while (true)
      {
         DELEGATION_INSTRUCTION * pInstruction;
         uint32_t nInstructionID;
         pInstruction = (DELEGATION_INSTRUCTION *)(&g_pExchangeBuffer->sInstructions[nInstructionsIndex/4]);
         if (nInstructionsIndex + 4 <= nInstructionsBufferSize)
         {
            nInstructionID = pInstruction->sGeneric.nInstructionID;
            nInstructionsIndex+=4;
         }
         else
         {
            goto instruction_parse_end;
         }
         if ((nInstructionID & 0x0F) == 0)
         {
            /* Partition-independent instruction */
            switch (nInstructionID)
            {
            case DELEGATION_INSTRUCTION_SHUTDOWN:
               {
                  exit(0);
                  /* The implementation of the TF Client API will automatically
                     destroy the context and release any associated resource */
               }
            case DELEGATION_INSTRUCTION_NOTIFY:
               {
                  /* Parse the instruction parameters */
                  wchar_t  pMessage[100];
                  uint32_t nMessageType;
                  uint32_t nMessageSize;
                  memset(pMessage, 0, 100*sizeof(wchar_t));

                  if (nInstructionsIndex + 8 <= nInstructionsBufferSize)
                  {
                     nMessageType = pInstruction->sNotify.nMessageType;
                     nMessageSize = pInstruction->sNotify.nMessageSize;
                     nInstructionsIndex+=8;
                  }
                  else
                  {
                     goto instruction_parse_end;
                  }
                  if (nMessageSize > (99)*sizeof(wchar_t))
                  {
                     /* How to handle the error correctly in this case ? */
                     goto instruction_parse_end;
                  }
                  if (nInstructionsIndex + nMessageSize <= nInstructionsBufferSize)
                  {
                     memcpy(pMessage, &pInstruction->sNotify.nMessage[0], nMessageSize);
                     nInstructionsIndex+=nMessageSize;
                  }
                  else
                  {
                     goto instruction_parse_end;
                  }
                  /* Align the pInstructionsIndex on 4 bytes */
                  nInstructionsIndex = (nInstructionsIndex+3)&~3;
                  notify(pMessage, nMessageType);
                  break;
               }
            default:
               LogError("Unknown instruction identifier: %02X", nInstructionID);
               nError = S_ERROR_BAD_PARAMETERS;
               break;
            }
         }
         else
         {
            /* Partition-specific instruction */
            uint32_t nPartitionID = (nInstructionID & 0xF0) >> 4;
            if (g_pExchangeBuffer->sAdministrativeData.nPartitionErrorStates[nPartitionID] == S_SUCCESS)
            {
               /* Execute the instruction only if there is currently no
                  error on the partition */
               switch (nInstructionID & 0x0F)
               {
               case DELEGATION_INSTRUCTION_PARTITION_CREATE:
                  nError = partitionCreate(nPartitionID);
                  TRACE_INFO("INSTRUCTION: ID=0x%x pid=%d err=%d", (nInstructionID & 0x0F), nPartitionID, nError);
                  break;
               case DELEGATION_INSTRUCTION_PARTITION_OPEN:
                  {
                     uint32_t nPartitionSize = 0;
                     nError = partitionOpen(nPartitionID, &nPartitionSize);
                     TRACE_INFO("INSTRUCTION: ID=0x%x pid=%d pSize=%d err=%d", (nInstructionID & 0x0F), nPartitionID, nPartitionSize, nError);
                     if (nError == S_SUCCESS)
                     {
                        g_pExchangeBuffer->sAdministrativeData.nPartitionOpenSizes[nPartitionID] = nPartitionSize;
                     }
                     break;
                  }
               case DELEGATION_INSTRUCTION_PARTITION_READ:
                  {
                     /* Parse parameters */
                     uint32_t nSectorID;
                     uint32_t nWorkspaceOffset;
                     if (nInstructionsIndex + 8 <= nInstructionsBufferSize)
                     {
                        nSectorID        = pInstruction->sReadWrite.nSectorID;
                        nWorkspaceOffset = pInstruction->sReadWrite.nWorkspaceOffset;
                        nInstructionsIndex+=8;
                     }
                     else
                     {
                        goto instruction_parse_end;
                     }
                     nError = partitionRead(nPartitionID, nSectorID, nWorkspaceOffset);
                     TRACE_INFO("INSTRUCTION: ID=0x%x pid=%d sid=%d woff=%d err=%d", (nInstructionID & 0x0F), nPartitionID, nSectorID, nWorkspaceOffset, nError);
                     break;
                  }
               case DELEGATION_INSTRUCTION_PARTITION_WRITE:
                  {
                     /* Parse parameters */
                     uint32_t nSectorID;
                     uint32_t nWorkspaceOffset;
                     if (nInstructionsIndex + 8 <= nInstructionsBufferSize)
                     {
                        nSectorID        = pInstruction->sReadWrite.nSectorID;
                        nWorkspaceOffset = pInstruction->sReadWrite.nWorkspaceOffset;
                        nInstructionsIndex+=8;
                     }
                     else
                     {
                        goto instruction_parse_end;
                     }
                     nError = partitionWrite(nPartitionID, nSectorID, nWorkspaceOffset);
                     TRACE_INFO("INSTRUCTION: ID=0x%x pid=%d sid=%d woff=%d err=%d", (nInstructionID & 0x0F), nPartitionID, nSectorID, nWorkspaceOffset, nError);
                     break;
                  }
               case DELEGATION_INSTRUCTION_PARTITION_SYNC:
                  nError = partitionSync(nPartitionID);
                  TRACE_INFO("INSTRUCTION: ID=0x%x pid=%d err=%d", (nInstructionID & 0x0F), nPartitionID, nError);
                  if (nError == S_SUCCESS)
                  {
                     g_pExchangeBuffer->sAdministrativeData.nSyncExecuted++;
                  }
                  break;
               case DELEGATION_INSTRUCTION_PARTITION_SET_SIZE:
                  {
                     uint32_t nNewSize;
                     if (nInstructionsIndex + 4 <= nInstructionsBufferSize)
                     {
                        nNewSize = pInstruction->sSetSize.nNewSize;
                        nInstructionsIndex+=4;
                     }
                     else
                     {
                        goto instruction_parse_end;
                     }
                     nError = partitionSetSize(nPartitionID, nNewSize);
                     TRACE_INFO("INSTRUCTION: ID=0x%x pid=%d nNewSize=%d err=%d", (nInstructionID & 0x0F), nPartitionID, nNewSize, nError);
                     break;
                  }
               case DELEGATION_INSTRUCTION_PARTITION_CLOSE:
                  nError = partitionClose(nPartitionID);
                  TRACE_INFO("INSTRUCTION: ID=0x%x pid=%d err=%d", (nInstructionID & 0x0F), nPartitionID, nError);
                  break;
               case DELEGATION_INSTRUCTION_PARTITION_DESTROY:
                  nError = partitionDestroy(nPartitionID);
                  TRACE_INFO("INSTRUCTION: ID=0x%x pid=%d err=%d", (nInstructionID & 0x0F), nPartitionID, nError);
                  break;
               }
               g_pExchangeBuffer->sAdministrativeData.nPartitionErrorStates[nPartitionID] = nError;
            }
         }
      }
instruction_parse_end:
      memset(pOperation, 0, sizeof(TEEC_Operation));
   }
}

/**
 * This function opens a new session to the delegation service.
 **/
static int createSession(TEEC_Context* pContext, TEEC_Session* pSession, TEEC_Operation* pOperation)
{
   TEEC_Result nError;
   uint32_t nExchangeBufferSize;

   memset(pOperation, 0, sizeof(TEEC_Operation));
   pOperation->paramTypes = TEEC_PARAM_TYPES(
      TEEC_VALUE_OUTPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);
   nError = TEEC_OpenSession(pContext,
                             pSession,                /* OUT session */
                             &g_sServiceId,           /* destination UUID */
                             TEEC_LOGIN_PRIVILEGED,   /* connectionMethod */
                             NULL,                    /* connectionData */
                             pOperation,              /* IN OUT operation */
                             NULL                     /* OUT errorOrigin, optional */
                             );
   if (nError != TEEC_SUCCESS)
   {
      LogError("Error on TEEC_OpenSession : 0x%x", nError);
      exit(2);
   }
   /* Read sector size */
   g_nSectorSize = pOperation->params[0].value.a;
   LogInfo("Sector Size: %d bytes", g_nSectorSize);

   /* Check sector size */
   if (!(g_nSectorSize == 512 || g_nSectorSize == 1024 || g_nSectorSize == 2048 || g_nSectorSize == 4096))
   {
      LogError("Incorrect sector size: terminating...");
      exit(2);
   }

   /* Check workspace size */
   if (g_nWorkspaceSize < 8 * g_nSectorSize)
   {
      g_nWorkspaceSize = 8 * g_nSectorSize;
      LogWarning("Workspace size too small, automatically set to %d bytes", g_nWorkspaceSize);
   }
   /* Compute the size of the exchange buffer */
   nExchangeBufferSize = sizeof(DELEGATION_EXCHANGE_BUFFER)-1+g_nWorkspaceSize;
   g_pExchangeBuffer  = (DELEGATION_EXCHANGE_BUFFER*)malloc(nExchangeBufferSize);
	 if (g_pExchangeBuffer == NULL)
   {
      LogError("Cannot allocate exchange buffer of %d bytes", nExchangeBufferSize);
      LogError("Now exiting...");
      exit(2);
   }
   g_pWorkspaceBuffer = (uint8_t*)g_pExchangeBuffer->sWorkspace;
   memset(g_pExchangeBuffer, 0x00, nExchangeBufferSize);
   memset(g_pPartitionFiles,0,16*sizeof(FILE*));

   /* Register the exchange buffer as a shared memory block */
   sExchangeSharedMem.buffer = g_pExchangeBuffer;
   sExchangeSharedMem.size   = nExchangeBufferSize;
   sExchangeSharedMem.flags  = TEEC_MEM_INPUT | TEEC_MEM_OUTPUT;
   nError = TEEC_RegisterSharedMemory(pContext, &sExchangeSharedMem);
   if (nError != TEEC_SUCCESS)
   {
      LogError("Error on TEEC_RegisterSharedMemory : 0x%x", nError);
      free(g_pExchangeBuffer);
      exit(2);
   }
   LogInfo("Daemon now connected");
   return 0;
}


/*----------------------------------------------------------------------------
 * Main
 *----------------------------------------------------------------------------*/

#ifdef INCLUDE_CLIENT_DELEGATION
int delegation_main(int argc, char* argv[])
#else
int main(int argc, char* argv[])
#endif
{
   TEEC_Result    nError;
   TEEC_Context   sContext;
   TEEC_Session   sSession;
   TEEC_Operation sOperation;
   bool        debug = false;

#ifndef SUPPORT_DELEGATION_EXTENSION
   char * baseDir = NULL;

   LogInfo("TFSW Normal-World Daemon");
#else
   LogInfo("TFSW Normal-World Ext Daemon");
#endif
   LogInfo(S_VERSION_STRING);
   LogInfo("");

   /* Skip program name */
   argv++;
   argc--;

   while (argc != 0)
   {
      if (strcmp(argv[0], "-d") == 0)
      {
         debug = true;
      }
#ifdef SUPPORT_DELEGATION_EXTENSION
      else if (strcmp(argv[0], "-c") == 0)
      {
         int error;
         argc--;
         argv++;
         if (argc == 0)
         {
            printUsage();
            return 1;
         }
         /* Note that the function parseCommandLineExtension can modify the
            content of the g_partitionNames array */
         error = parseCommandLineExtension(argv[0], g_pPartitionNames);
         if ( error != 0 )
         {
            printUsage();
            return error;
         }
      }
#else
      else if (strcmp(argv[0], "-storageDir") == 0)
      {
         uint32_t i = 0;
         argc--;
         argv++;
         if (argc == 0)
         {
            printUsage();
            return 1;
         }
         if (baseDir != NULL)
         {
            LogError("Only one storage directory may be specified");
            return 1;
         }
         baseDir = malloc(strlen(argv[0])+1); /* Zero-terminated string */
         if (baseDir == NULL)
         {
             LogError("Out of memory");
             return 2;
         }

         strcpy(baseDir, argv[0]);

         /* Set default names to the partitions */
         for ( i=0; i<16 ;i++ )
         {
            g_pPartitionNames[i] = NULL;
            g_pPartitionNames[i] = malloc(strlen(baseDir) + 1 /* separator */ + sizeof("Store_X.tf"));
            if (g_pPartitionNames[i] != NULL)
            {
               sprintf(g_pPartitionNames[i], "%s%cStore_%1X.tf", baseDir, PATH_SEPARATOR, i);
            }
            else
            {
               free(baseDir);
               i=0;
               while(g_pPartitionNames[i] != NULL) free(g_pPartitionNames[i++]);
               LogError("Out of memory");
               return 2;
            }
         }
      }
      else if (strcmp(argv[0], "-workspaceSize") == 0)
      {
         argc--;
         argv++;
         if (argc == 0)
         {
            printUsage();
            return 1;
         }
         g_nWorkspaceSize=atol(argv[0]);
      }
#endif /* ! SUPPORT_DELEGATION_EXTENSION */
      /*****************************************/
      else if (strcmp(argv[0], "--help") == 0 || strcmp(argv[0], "-h") == 0)
      {
         printUsage();
         return 0;
      }
      else
      {
         printUsage();
         return 1;
      }
      argc--;
      argv++;
   }

#ifndef SUPPORT_DELEGATION_EXTENSION
   if (baseDir == NULL)
   {
      LogError("-storageDir option is mandatory");
      return 1;
   }
   else
   {
      if (static_checkStorageDirAndAccessRights(baseDir) != 0)
      {
         return 1;
      }
   }
#endif /* #ifndef SUPPORT_DELEGATION_EXTENSION */

   /*
    * Detach the daemon from the console
    */

#if defined(LINUX) || (defined __ANDROID32__)
   {
      /*
       * Turns this application into a daemon => fork off parent process, setup logging, ...
       */

      /* Our process ID and Session ID */
      pid_t pid, sid;

      if (!debug)
      {
         LogInfo("tf_daemon is detaching from console... Further traces go to syslog");
         /* Fork off the parent process */
         pid = fork();
         if (pid < 0)
         {
            LogError("daemon forking failed");
            return 1;
         }

         if (pid > 0)
         {
            /* parent */
            return 0;
         }
         bDetached = true;
      }

      /* Change the file mode mask */
      umask(0077);

      if (!debug)
      {
         /* Open any logs here */
         openlog("tf_daemon", 0, LOG_DAEMON);

         /* Detach from the console */
         sid = setsid();
         if (sid < 0)
         {
            /* Log the failure */
            LogError("daemon group creation failed");
            return 1;
         }
         /* Close out the standard file descriptors */
         close(STDIN_FILENO);
         close(STDOUT_FILENO);
         close(STDERR_FILENO);
      }
   }
   /* Change priority so that tf_driver.ko with no polling thread is faster */
   if (setpriority(PRIO_PROCESS, 0, 19)!=0)
   {
      LogError("Daemon cannot change priority");
      return 1;
   }

#endif

   TRACE_INFO("Sector size is %d", g_nSectorSize);

   LogInfo("tf_daemon - started");

   nError = TEEC_InitializeContext(NULL,  /* const char * name */
                                   &sContext);   /* TEEC_Context* context */
   if (nError != TEEC_SUCCESS)
   {
      LogError("TEEC_InitializeContext error: 0x%08X", nError);
      LogError("Now exiting...");
      exit(2);
   }

   /* Open a session */
   if(createSession(&sContext, &sSession, &sOperation) == 0)
   {
      /* Run the session. This should never return */
      runSession(&sContext, &sSession, &sOperation);
   }
   TEEC_FinalizeContext(&sContext);

   return 3;
}
