/****************************************************************************
 * libs/libc/stdio/lib_fopen.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

#ifdef CONFIG_FDSAN
#  include <android/fdsan.h>
#endif

#include "libc.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Open mode flags */

#define MODE_R    (1 << 0) /* Bit 0: "r{b|x|+}" open for reading */
#define MODE_W    (1 << 1) /* Bit 1: "w{b|x|+}" open for writing, truncating,
                            * or creating file */
#define MODE_A    (1 << 2) /* Bit 2: "a{b|x|+}" open for writing, appending
                            * the to file */

#define MODE_NONE 0        /* No access mode determined */
#define MODE_MASK (MODE_R | MODE_W | MODE_A)

#define FLAG_KEEP (O_TEXT | O_CLOEXEC | O_EXCL)

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: fdopen
 ****************************************************************************/

FAR FILE *fdopen(int fd, FAR const char *mode)
{
  FAR struct streamlist *list = lib_get_streams();
  FAR FILE *filep = NULL;
  int oflags;
  int ret;

  /* Map the open mode string to open flags */

  oflags = lib_mode2oflags(mode);
  if (oflags < 0)
    {
      return NULL;
    }

  /* Allocate FILE structure */

  if (fd >= 3)
    {
      filep = lib_zalloc(sizeof(FILE));
      if (filep == NULL)
        {
          ret = -ENOMEM;
          goto errout;
        }

      /* Add FILE structure to the stream list */

      ret = nxmutex_lock(&list->sl_lock);
      if (ret < 0)
        {
          lib_free(filep);
          goto errout;
        }

      sq_addlast(&filep->fs_entry, &list->sl_queue);

      nxmutex_unlock(&list->sl_lock);

      /* Initialize the mutex the manages access to the buffer */

      nxrmutex_init(&filep->fs_lock);

#ifdef CONFIG_FDSAN
      android_fdsan_exchange_owner_tag(fd, 0,
          android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_FILE,
                                        (uintptr_t)filep));
#endif
    }
  else
    {
      filep = &list->sl_std[fd];
    }

#if !defined(CONFIG_STDIO_DISABLE_BUFFERING) && CONFIG_STDIO_BUFFER_SIZE > 0
  /* Set up pointers */

  filep->fs_bufstart = filep->fs_buffer;
  filep->fs_bufend   = filep->fs_bufstart + CONFIG_STDIO_BUFFER_SIZE;
  filep->fs_bufpos   = filep->fs_bufstart;
  filep->fs_bufread  = filep->fs_bufstart;
  filep->fs_flags    = __FS_FLAG_UBF; /* Fake setvbuf and fclose */

#  ifdef CONFIG_STDIO_LINEBUFFER
  /* Setup buffer flags */

  filep->fs_flags   |= __FS_FLAG_LBF; /* Line buffering */

#  endif /* CONFIG_STDIO_LINEBUFFER */
#endif /* !CONFIG_STDIO_DISABLE_BUFFERING && CONFIG_STDIO_BUFFER_SIZE > 0 */

  /* Save the file description and open flags.  Setting the
   * file descriptor locks this stream.
   */

  filep->fs_cookie   = (FAR void *)(intptr_t)fd;
  filep->fs_oflags   = oflags;

  /* Assign custom callbacks to NULL. */

  filep->fs_iofunc.read  = NULL;
  filep->fs_iofunc.write = NULL;
  filep->fs_iofunc.seek  = NULL;
  filep->fs_iofunc.close = NULL;

  return filep;

errout:
  set_errno(-ret);
  return NULL;
}

/****************************************************************************
 * Name: fopen
 ****************************************************************************/

FAR FILE *fopen(FAR const char *path, FAR const char *mode)
{
  FAR FILE *filep = NULL;
  int oflags;
  int fd;

  /* Map the open mode string to open flags */

  oflags = lib_mode2oflags(mode);
  if (oflags < 0)
    {
      return NULL;
    }

  /* Open the file */

  fd = open(path, oflags, 0666);

  /* If the open was successful, then call fdopen() using the file
   * descriptor returned by open.  If open failed, then just return the
   * NULL stream -- open() has already set the errno.
   */

  if (fd >= 0)
    {
      filep = fdopen(fd, mode);
      if (filep == NULL)
        {
          /* Don't forget to close the file descriptor if any other
           * failures are reported by fdopen().
           */

          close(fd);
        }
    }

  return filep;
}

/****************************************************************************
 * Name: lib_mode2oflags
 ****************************************************************************/

int lib_mode2oflags(FAR const char *mode)
{
  unsigned int state;
  int oflags;

  /* Verify that a mode string was provided.  */

  DEBUGASSERT(mode);

  /* Parse the mode string to determine the corresponding open flags */

  state  = MODE_NONE;
  oflags = 0;

  for (; *mode; mode++)
    {
      switch (*mode)
        {
          /* Open for read access ("r{m|b|x|+}") */

          case 'r' :
            if (state == MODE_NONE)
              {
                /* Open for read access */

                oflags = O_RDOK | O_TEXT;
                state  = MODE_R;
              }
            else
              {
                goto errout;
              }
            break;

          /* Open for write access ("w{b|x|+}") */

          case 'w' :
            if (state == MODE_NONE)
              {
                /* Open for write access, truncating any existing file */

                oflags = O_WROK | O_CREAT | O_TRUNC | O_TEXT;
                state  = MODE_W;
              }
            else
              {
                goto errout;
              }
            break;

          /* Open for write/append access ("a{b|x|+}") */

          case 'a' :
            if (state == MODE_NONE)
              {
                /* Write to the end of the file */

                oflags = O_WROK | O_CREAT | O_APPEND | O_TEXT;
                state  = MODE_A;
              }
            else
              {
                goto errout;
              }
            break;

          /* Open for update access ("{r|w|a|b|x}+") */

          case '+' :
            switch (state & MODE_MASK)
              {
                case MODE_R:
                  {
                    /* Retain any binary and exclusive mode selections */

                    oflags &= FLAG_KEEP;

                    /* Open for read/write access */

                    oflags |= O_RDWR;
                 }
                 break;

                case MODE_W:
                  {
                    /* Retain any binary and exclusive mode selections */

                    oflags &= FLAG_KEEP;

                    /* Open for write read/access, truncating any existing
                     * file.
                     */

                    oflags |= O_RDWR | O_CREAT | O_TRUNC;
                  }
                  break;

                case MODE_A:
                  {
                    /* Retain any binary and exclusive mode selections */

                    oflags &= FLAG_KEEP;

                    /* Read from the beginning of the file; write to the
                     * end,
                     */

                    oflags |= O_RDWR | O_CREAT | O_APPEND;
                  }
                  break;

                default:
                  goto errout;
              }
            break;

          /* Attempt to access the file using mmap. */

          case 'm' :
            if (state != MODE_R)
              {
                goto errout;
              }
            break;

          /* Open for binary access ("{r|w|a|x|+}b") */

          case 'b' :
            if ((state & MODE_MASK) != MODE_NONE)
              {
                /* The file is opened in binary mode */

                oflags &= ~O_TEXT;
              }
            else
              {
                goto errout;
              }
            break;

          /* Open for close on execute */

          case 'e' :
            if ((state & MODE_MASK) != MODE_NONE)
              {
                /* The file will be closed on execute */

                oflags |= O_CLOEXEC;
              }
            else
              {
                goto errout;
              }
            break;

          /* Open for exclusive access ("{r|w|a|b|+}x") */

          case 'x' :
            if ((state & MODE_MASK) != MODE_NONE)
              {
                /* The file is opened in exclusive mode */

                oflags |= O_EXCL;
              }
            else
              {
                goto errout;
              }
            break;

          /* Open for text (translated) access ("{r|w|a|x|+}t") */

          case 't' :
            if ((state & MODE_MASK) != MODE_NONE)
              {
                /* The file is opened in text mode */

                oflags |= O_TEXT;
              }
            else
              {
                goto errout;
              }
            break;

          /* Unrecognized or unsupported mode */

          default:
            goto errout;
        }
    }

  return oflags;

/* Both fopen and fdopen should fail with errno == EINVAL if the mode
 * string is invalid.
 */

errout:
  set_errno(EINVAL);
  return ERROR;
}
