/*
  This file is part of Anastasis
  Copyright (C) 2026 Anastasis SARL

  Anastasis is free software; you can redistribute it and/or modify it under the
  terms of the GNU Lesser General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License along with
  Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file cli/anastasis-cli-common.c
 * @brief helpers shared by the Anastasis command line tools
 * @author Christian Grothoff
 */

#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include "anastasis-cli-common.h"

#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif


/**
 * Report that the input was larger than #ANASTASIS_CLI_MAX_STATE_SIZE.
 *
 * @param[out] error error structure to fill in
 */
static void
set_too_big (json_error_t *error)
{
  GNUNET_snprintf (error->text,
                   sizeof (error->text),
                   "input exceeds the limit of %llu bytes",
                   (unsigned long long) ANASTASIS_CLI_MAX_STATE_SIZE);
}


json_t *
ANASTASIS_CLI_json_load_capped (FILE *f,
                                json_error_t *error)
{
  int fd = fileno (f);
  struct stat sb;
  off_t fsize = -1;
  size_t map_size;
  size_t len;
  void *map;
  json_t *ret;

  memset (error,
          0,
          sizeof (*error));
  if ( (-1 != fd) &&
       (0 == fstat (fd,
                    &sb)) &&
       (S_ISREG (sb.st_mode)) &&
       (0 == ftello (f)) )
    fsize = sb.st_size;
  if (-1 != fsize)
  {
    /* Regular file positioned at the start: the size is known before we touch
       the data, so the limit is a single comparison and the file itself can
       serve as the buffer. */
    if (fsize > (off_t) ANASTASIS_CLI_MAX_STATE_SIZE)
    {
      set_too_big (error);
      return NULL;
    }
    if (0 == fsize)
      return json_loadb ("",   /* mmap() rejects a zero-length mapping */
                         0,
                         JSON_DECODE_ANY,
                         error);
    map_size = (size_t) fsize;
    len = map_size;
    map = mmap (NULL,
                map_size,
                PROT_READ,
                MAP_PRIVATE,
                fd,
                0);
  }
  else
  {
    /* A pipe or terminal has no size to stat and nothing to map, so read into
       an anonymous mapping one byte larger than the limit: the pages we never
       fill cost nothing, and filling the last one means the input is too big. */
    map_size = ANASTASIS_CLI_MAX_STATE_SIZE + 1;
    len = 0;
    map = mmap (NULL,
                map_size,
                PROT_READ | PROT_WRITE,
                MAP_PRIVATE | MAP_ANONYMOUS,
                -1,
                0);
  }
  if (MAP_FAILED == map)
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "mmap");
    GNUNET_snprintf (error->text,
                     sizeof (error->text),
                     "failed to map input: %s",
                     strerror (errno));
    return NULL;
  }
  if (-1 == fsize)
  {
    while (len < map_size)
    {
      size_t got;

      got = fread ((char *) map + len,
                   1,
                   map_size - len,
                   f);
      if (0 == got)
        break;   /* EOF or error; ferror() is reported as a parse failure below */
      len += got;
    }
    if (len == map_size)
    {
      GNUNET_break (0 == munmap (map,
                                 map_size));
      set_too_big (error);
      return NULL;
    }
  }
  ret = json_loadb (map,
                    len,
                    JSON_DECODE_ANY,
                    error);
  GNUNET_break (0 == munmap (map,
                             map_size));
  return ret;
}


/* end of anastasis-cli-common.c */
