/****************************************************************************
 * apps/system/init/parser.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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 <ctype.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <unistd.h>

#include "init.h"
#include "parser.h"

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static int init_parse_config_lines(FAR const struct parser_s *parser,
                                   FAR const struct parser_s **cur,
                                   FAR size_t *line,
                                   FAR char *buf, FAR size_t *len)
{
  bool create = false;
  FAR char *nl;
  int ret;

  while ((nl = memchr(buf, '\n', *len)))
    {
      *(nl++) = '\0';
      *len -= nl - buf;
      init_debug("line %-3zu '%s'", ++*line, buf);
      if (*buf == '\0')
        {
          continue;
        }

      for (ret = 0; parser[ret].key; ret++)
        {
          if (!strncmp(parser[ret].key, buf, strlen(parser[ret].key)))
            {
              create = true;
              *cur = &parser[ret];
              init_debug("new section (%s)", parser[ret].key);
              break;
            }
        }

      if (!*cur)
        {
          return -EINVAL;
        }

      ret = (*cur)->parse(*cur, create, buf);
      create = false;
      if (ret < 0)
        {
          return ret;
        }

      memmove(buf, nl, *len);
    }

  return 0;
}

static int init_parse_config_buffer(FAR const struct parser_s *parser,
                                    FAR const char *buf, size_t len)
{
  char tmp[CONFIG_SYSTEM_INIT_RC_LINE_MAX];
  FAR const struct parser_s *cur = NULL;
  size_t line = 0;
  size_t off = 0;
  size_t n = 0;
  size_t r;
  int ret;

  for (; ; )
    {
      r = MIN(len - off, sizeof(tmp));
      memcpy(&tmp[n], &buf[off], r);
      if (r == 0)
        {
          if (n == 0)
            {
              break;
            }

          tmp[n++] = '\n';
        }

      n += r;
      off += r;
      ret = init_parse_config_lines(parser, &cur, &line, tmp, &n);
      if (ret < 0)
        {
          return ret;
        }

      if (n == sizeof(tmp))
        {
          return -E2BIG;
        }
    }

  return 0;
}

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

int init_parse_arguments(FAR char *buf, bool dup, int argc, FAR char **argv)
{
  bool quote = false;
  bool new = true;
  int i = 0;

  for (; ; )
    {
      while (isblank(*buf))
        {
          if (!quote)
            {
              *buf = '\0';
              new = true;
            }

          buf++;
        }

      if (*buf == '-' && *(buf + 1) == '-')
        {
          argv[i++] = buf;
          if (i >= argc || *(buf += 2) == '\0')
            {
              break;
            }

          while (isblank(*buf))
            {
              *buf++ = '\0';
            }

          argv[i++] = buf;
          break;
        }

      if (*buf == '\"')
        {
          *buf = '\0';
          if (quote)
            {
              quote = false;
            }
          else
            {
              quote = true;
              new = true;
              buf++;
            }
        }

      if (*buf == '\0')
        {
          break;
        }

      if (new)
        {
          argv[i++] = buf;
          if (i >= argc)
            {
              break;
            }

          new = false;
        }

      buf++;
    }

  if (dup && i > 0)
    {
      argc = i;
      for (i = 0; i < argc; i++)
        {
          argv[i] = strdup(argv[i]);
          if (!argv[i])
            {
              while (i-- > 0)
                {
                  free(argv[i]);
                }

              return -errno;
            }
        }
    }

  return i;
}

int init_parse_config_file(FAR const struct parser_s *parser,
                           FAR const char *file)
{
  char buf[CONFIG_SYSTEM_INIT_RC_LINE_MAX];
  FAR const struct parser_s *cur = NULL;
  size_t line = 0;
  size_t n = 0;
  int ret = 0;
  int fd;

  init_debug("parsing %s", file);

  fd = open(file, O_RDONLY | O_CLOEXEC);
  if (fd < 0)
    {
      init_err("opening %s %d", file, errno);
      return -errno;
    }

  for (; ; )
    {
      ssize_t r = read(fd, &buf[n], sizeof(buf) - n);
      if (r < 0)
        {
          if (errno == EINTR)
            {
              continue;
            }

          ret = -errno;
          goto out;
        }
      else if (r == 0)
        {
          if (n == 0)
            {
              break;
            }

          buf[n++] = '\n';
        }

      n += r;
      ret = init_parse_config_lines(parser, &cur, &line, buf, &n);
      if (ret < 0)
        {
          goto out;
        }

      if (n == sizeof(buf))
        {
          ret = -E2BIG;
          goto out;
        }
    }

  for (n = 0; parser[n].key; n++)
    {
      if (parser[n].check)
        {
          ret = parser[n].check(&parser[n]);
          if (ret < 0)
            {
              break;
            }
        }
    }

out:
  close(fd);
  if (ret < 0)
    {
      init_err("parse %s %d", file, ret);
    }

  return ret;
}

int init_parse_configs(FAR const struct parser_s *parser)
{
  static FAR const char *base = "/etc/init.d/init.";
  static const char preset[] =
    "on boot\n"
    "   trigger init\n"
    "on init\n"
    "   board_init\n"
#ifdef CONFIG_NETUTILS_NETINIT
    "   trigger netinit\n"
    "on netinit\n"
    "   board_netinit\n"
#endif
#ifdef CONFIG_BOARDCTL_FINALINIT
    "   trigger finalinit\n"
    "on finalinit\n"
    "   board_finalinit\n"
#endif
    ;
  char file[PATH_MAX];
  int ret;

  ret = init_parse_config_buffer(parser, preset, sizeof(preset));
  if (ret < 0)
    {
      return ret;
    }

  snprintf(file, sizeof(file), "%src", base);
  ret = init_parse_config_file(parser, file);
  if (ret < 0)
    {
      return ret;
    }

  snprintf(file, sizeof(file), "%scpu%d.rc", base, sched_getcpu());
  ret = access(file, F_OK);
  if (ret < 0)
    {
      init_debug("skipping non-exist file %s", file);
      return 0;
    }

  return init_parse_config_file(parser, file);
}