// vi:set ft=cpp: -*- Mode: C++ -*-
/**
 * \file
 * \brief   Error helper.
 */
/*
 * (c) 2008-2009 Adam Lackorzynski <adam@os.inf.tu-dresden.de>,
 *               Alexander Warg <warg@os.inf.tu-dresden.de>,
 *               Torsten Frenzel <frenzel@os.inf.tu-dresden.de>
 *     economic rights: Technische Universität Dresden (Germany)
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */
#pragma once

#include <l4/sys/types.h>
#include <l4/cxx/exceptions>
#include <l4/cxx/type_traits>
#include <l4/sys/err.h>

#include <stdarg.h>
#include <stdio.h>

namespace L4Re {

#ifdef __EXCEPTIONS

/**
 * \brief Generate C++ exception
 *
 * \param err   Error value
 * \param extra Optional text for exception (default "")
 *
 * This function throws an L4 exception. The exact exception type depends on
 * the error value (err). This function does never return.
 */
[[noreturn]] inline void throw_error(long err, char const *extra = "")
{
  switch (err)
    {
    case -L4_ENOENT: throw (L4::Element_not_found(extra));
    case -L4_ENOMEM: throw (L4::Out_of_memory(extra));
    case -L4_EEXIST: throw (L4::Element_already_exists(extra));
    case -L4_ERANGE: throw (L4::Bounds_error(extra));
    default: throw (L4::Runtime_error(err, extra));
    }
}

[[noreturn]] inline void throw_error_fmt(long err, char const *const fmt, ...)
  __attribute__((format(printf, 2, 3)));
[[noreturn]] inline void throw_error_fmt(long err, char const *const fmt, ...)
{
  char extra[80];
  va_list argp;
  va_start(argp, fmt);
  vsnprintf(extra, sizeof(extra), fmt, argp);
  va_end(argp);
  throw_error(err, extra);
}

/**
 * \brief Generate C++ exception on error
 *
 * \param err   Error value, if negative exception will be thrown
 * \param extra Optional text for exception (default "")
 * \param ret   Optional value for exception, default is error value (err)
 *
 * This function throws an exception if the err is negative and
 * otherwise returns err.
 */
inline
long chksys(long err, char const *extra = "", long ret = 0)
{
  if (L4_UNLIKELY(err < 0))
    throw_error(ret ? ret : err, extra);

  return err;
}

/**
 * \brief Generate C++ exception on error
 *
 * \param t     Message tag.
 * \param extra Optional text for exception (default "")
 * \param utcb  Option UTCB
 * \param ret   Optional value for exception, default is error value (err)
 *
 * This function throws an exception if the message tag contains an error or
 * the label in the message tag is negative. Otherwise the label in the
 * message tag is returned.
 */
inline
long chksys(l4_msgtag_t const &t, char const *extra = "",
            l4_utcb_t *utcb = l4_utcb(), long ret = 0)
{
  if (L4_UNLIKELY(t.has_error()))
    throw_error(ret ? ret : l4_error_u(t, utcb), extra);
  else if (L4_UNLIKELY(t.label() < 0))
    throw_error(ret ? ret: t.label(), extra);

  return t.label();
}

/**
 * \brief Generate C++ exception on error
 *
 * \param t     Message tag.
 * \param utcb  UTCB.
 * \param extra Optional text for exception (default "")
 *
 * This function throws an exception if the message tag contains an error or
 * the label in the message tag is negative. Otherwise the label in the
 * message tag is returned.
 */
inline
long chksys(l4_msgtag_t const &t, l4_utcb_t *utcb, char const *extra = "")
{ return chksys(t, extra, utcb); }

#if 0
inline
long chksys(long ret, long err, char const *extra = "")
{
  if (L4_UNLIKELY(ret < 0))
    throw_error(err, extra);

  return ret;
}
#endif

/**
 * Check for valid capability or raise C++ exception
 *
 * \tparam T  Type of object to check, must be capability-like
 *            (L4::Cap, L4Re::Util::Unique_cap etc.)
 *
 * \param cap    Capability value to check.
 * \param extra  Optional text for exception.
 * \param err    Error value for exception or 0 if the error code stored in the
 *               invalid capability should be used.
 *
 * This function checks whether the capability is valid. If the capability
 * is invalid, a C++ exception is generated, using err if err is not zero,
 * otherwise the capability value is used. A valid capability will just be
 * returned.
 */
template<typename T>
inline
#if __cplusplus >= 201103L
T chkcap(T &&cap, char const *extra = "", long err = -L4_ENOMEM)
#else
T chkcap(T cap, char const *extra = "", long err = -L4_ENOMEM)
#endif
{
  if (L4_UNLIKELY(!cap.is_valid()))
    throw_error(err ? err : cap.invalid_cap_error(), extra);

#if __cplusplus >= 201103L
  return cxx::forward<T>(cap);
#else
  return cap;
#endif
}

/**
 * Test a message tag for IPC errors.
 *
 * \param tag    Message tag returned by the IPC.
 * \param extra  Exception message in case of error.
 * \param utcb   The UTCB used in the IPC operation.
 *
 * \returns  On IPC error an exception is thrown, otherwise `tag` is returned.
 * \throws L4::Runtime_exception with the translated IPC error code
 *
 * This function does not check the message tag's label value.
 *
 * \note This must be called on a message tag before the UTCB is changed.
 */
inline
l4_msgtag_t
chkipc(l4_msgtag_t tag, char const *extra = "",
       l4_utcb_t *utcb = l4_utcb())
{
  if (L4_UNLIKELY(tag.has_error()))
    chksys(l4_error_u(tag, utcb), extra);

  return tag;
}
#endif

}
