/**
 *  @file
 *
 *  @brief RTEMS File Sysyem Path Eval Support
 *  @ingroup LibIOInternal
 */

/*
 * Copyright (c) 2012 embedded brains GmbH.  All rights reserved.
 *
 *  embedded brains GmbH
 *  Obere Lagerstr. 30
 *  82178 Puchheim
 *  Germany
 *  <rtems@embedded-brains.de>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <rtems/libio_.h>

#include <string.h>

static size_t get_parentpathlen(const char *path, size_t pathlen)
{
  while (pathlen > 0) {
    size_t i = pathlen - 1;

    if (rtems_filesystem_is_delimiter(path [i])) {
      return pathlen;
    }

    pathlen = i;
  }

  return 0;
}

static void set_startloc(
  rtems_filesystem_eval_path_context_t *ctx,
  rtems_filesystem_global_location_t *const *global_root_ptr,
  rtems_filesystem_global_location_t *const *global_current_ptr
)
{
  if (ctx->pathlen > 0) {
    char c = ctx->path [0];

    ctx->rootloc = rtems_filesystem_global_location_obtain(global_root_ptr);

    if (rtems_filesystem_is_delimiter(c)) {
      ++ctx->path;
      --ctx->pathlen;
      ctx->startloc = rtems_filesystem_global_location_obtain(
        &ctx->rootloc
      );
    } else {
      ctx->startloc = rtems_filesystem_global_location_obtain(
        global_current_ptr
      );
    }
  } else {
    ctx->rootloc = rtems_filesystem_global_location_obtain_null();
    ctx->startloc = rtems_filesystem_global_location_obtain_null();
    errno = ENOENT;
  }
}

static void check_access(
  rtems_filesystem_eval_path_context_t *ctx,
  int eval_flags
)
{
  const rtems_filesystem_location_info_t *currentloc = &ctx->currentloc;
  const rtems_filesystem_mount_table_entry_t *mt_entry = currentloc->mt_entry;

  if ((eval_flags & RTEMS_FS_PERMS_WRITE) == 0 || mt_entry->writeable) {
    struct stat st;
    int rv;

    st.st_mode = 0;
    st.st_uid = 0;
    st.st_gid = 0;
    rv = (*currentloc->handlers->fstat_h)(currentloc, &st);
    if (rv == 0) {
      bool access_ok = rtems_filesystem_check_access(
        eval_flags,
        st.st_mode,
        st.st_uid,
        st.st_gid
      );

      if (!access_ok) {
        rtems_filesystem_eval_path_error(ctx, EACCES);
      }
    } else {
      rtems_filesystem_eval_path_error(ctx, 0);
    }
  } else {
    rtems_filesystem_eval_path_error(ctx, EROFS);
  }
}

void rtems_filesystem_eval_path_continue(
  rtems_filesystem_eval_path_context_t *ctx
)
{
  int eval_flags;

  while (ctx->pathlen > 0) {
    (*ctx->currentloc.mt_entry->ops->eval_path_h)(ctx);
  }

  eval_flags = rtems_filesystem_eval_path_get_flags(ctx);
  if (rtems_filesystem_eval_path_has_token(ctx)) {
    bool make = (eval_flags & RTEMS_FS_MAKE) != 0;

    if (make) {
      check_access(ctx, RTEMS_FS_PERMS_WRITE);
    } else {
      rtems_filesystem_eval_path_error(ctx, ENOENT);
    }
  } else {
    bool exclusive = (eval_flags & RTEMS_FS_EXCLUSIVE) != 0;

    if (!exclusive) {
      check_access(ctx, ctx->flags);
    } else {
      rtems_filesystem_eval_path_error(ctx, EEXIST);
    }
  }
}

rtems_filesystem_location_info_t *
rtems_filesystem_eval_path_start_with_root_and_current(
  rtems_filesystem_eval_path_context_t *ctx,
  const char *path,
  size_t pathlen,
  int eval_flags,
  rtems_filesystem_global_location_t *const *global_root_ptr,
  rtems_filesystem_global_location_t *const *global_current_ptr
)
{
  memset(ctx, 0, sizeof(*ctx));

  ctx->path = path;
  ctx->pathlen = pathlen;
  ctx->flags = eval_flags;

  set_startloc(ctx, global_root_ptr, global_current_ptr);

  rtems_filesystem_instance_lock(&ctx->startloc->location);

  rtems_filesystem_location_clone(
    &ctx->currentloc,
    &ctx->startloc->location
  );

  rtems_filesystem_eval_path_continue(ctx);

  return &ctx->currentloc;
}

rtems_filesystem_location_info_t *
rtems_filesystem_eval_path_start(
  rtems_filesystem_eval_path_context_t *ctx,
  const char *path,
  int eval_flags
)
{
  return rtems_filesystem_eval_path_start_with_root_and_current(
    ctx,
    path,
    strlen(path),
    eval_flags,
    &rtems_filesystem_root,
    &rtems_filesystem_current
  );
}

rtems_filesystem_location_info_t *
rtems_filesystem_eval_path_start_with_parent(
  rtems_filesystem_eval_path_context_t *ctx,
  const char *path,
  int eval_flags,
  rtems_filesystem_location_info_t *parentloc,
  int parent_eval_flags
)
{
  size_t pathlen = strlen(path);
  const char *parentpath = path;
  size_t parentpathlen = get_parentpathlen(path, pathlen);
  const char *name = NULL;
  size_t namelen = 0;
  const rtems_filesystem_location_info_t *currentloc = NULL;

  if (pathlen > 0) {
    if (parentpathlen == 0) {
      parentpath = ".";
      parentpathlen = 1;
      name = path;
      namelen = pathlen;
    } else {
      name = path + parentpathlen;
      namelen = pathlen - parentpathlen;
    }
  }

  currentloc = rtems_filesystem_eval_path_start_with_root_and_current(
    ctx,
    parentpath,
    parentpathlen,
    parent_eval_flags,
    &rtems_filesystem_root,
    &rtems_filesystem_current
  );

  rtems_filesystem_location_clone(parentloc, currentloc);

  ctx->path = name;
  ctx->pathlen = namelen;
  ctx->flags = eval_flags;

  rtems_filesystem_eval_path_continue(ctx);

  return &ctx->currentloc;
}

void rtems_filesystem_eval_path_recursive(
  rtems_filesystem_eval_path_context_t *ctx,
  const char *path,
  size_t pathlen
)
{
  if (pathlen > 0) {
    if (ctx->recursionlevel < RTEMS_FILESYSTEM_SYMLOOP_MAX) {
      const char *saved_path = ctx->path;
      size_t saved_pathlen = ctx->pathlen;

      if (rtems_filesystem_is_delimiter(path [0])) {
        rtems_filesystem_eval_path_restart(ctx, &ctx->rootloc);
      }

      ctx->path = path;
      ctx->pathlen = pathlen;

      ++ctx->recursionlevel;
      while (ctx->pathlen > 0) {
        (*ctx->currentloc.mt_entry->ops->eval_path_h)(ctx);
      }
      --ctx->recursionlevel;

      ctx->path = saved_path;
      ctx->pathlen = saved_pathlen;
    } else {
      rtems_filesystem_eval_path_error(ctx, ELOOP);
    }
  } else {
    rtems_filesystem_eval_path_error(ctx, ENOENT);
  }
}

void rtems_filesystem_eval_path_error(
  rtems_filesystem_eval_path_context_t *ctx,
  int eno
)
{
  ctx->path = NULL;
  ctx->pathlen = 0;
  ctx->token = NULL;
  ctx->tokenlen = 0;

  if (!rtems_filesystem_location_is_null(&ctx->currentloc)) {
    if (eno != 0) {
      errno = eno;
    }

    rtems_filesystem_location_detach(&ctx->currentloc);
  }
}

static void free_location(rtems_filesystem_location_info_t *loc)
{
  rtems_filesystem_mt_entry_declare_lock_context(lock_context);

  (*loc->mt_entry->ops->freenod_h)(loc);

  rtems_filesystem_mt_entry_lock(lock_context);
  rtems_chain_extract_unprotected(&loc->mt_entry_node);
  rtems_filesystem_mt_entry_unlock(lock_context);
}

void rtems_filesystem_eval_path_cleanup(
  rtems_filesystem_eval_path_context_t *ctx
)
{
  free_location(&ctx->currentloc);
  rtems_filesystem_instance_unlock(&ctx->startloc->location);
  rtems_filesystem_global_location_release(ctx->startloc, false);
  rtems_filesystem_global_location_release(ctx->rootloc, false);
}

void rtems_filesystem_eval_path_cleanup_with_parent(
  rtems_filesystem_eval_path_context_t *ctx,
  rtems_filesystem_location_info_t *parentloc
)
{
  free_location(parentloc);
  rtems_filesystem_eval_path_cleanup(ctx);
}

void rtems_filesystem_eval_path_restart(
  rtems_filesystem_eval_path_context_t *ctx,
  rtems_filesystem_global_location_t **newstartloc_ptr
)
{
  free_location(&ctx->currentloc);
  rtems_filesystem_instance_unlock(&ctx->startloc->location);
  rtems_filesystem_global_location_assign(
    &ctx->startloc,
    rtems_filesystem_global_location_obtain(newstartloc_ptr)
  );
  rtems_filesystem_instance_lock(&ctx->startloc->location);
  rtems_filesystem_location_clone(&ctx->currentloc, &ctx->startloc->location);
}
