/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#include <time.h>
#include <unistd.h>

#include "SandboxInfo.h"

#include "SandboxProfilerChild.h"
#include "SandboxProfiler.h"

#include "public/GeckoTraceEvent.h"

#include "mozilla/Atomics.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/PodOperations.h"

namespace mozilla {

#if defined(DEBUG)
thread_local Atomic<bool> sInSignalContext = Atomic<bool>(false);

AutoForbidSignalContext::AutoForbidSignalContext() { sInSignalContext = true; }

AutoForbidSignalContext::~AutoForbidSignalContext() {
  sInSignalContext = false;
}
#endif  // defined(DEBUG)

static StaticAutoPtr<SandboxProfiler> gProfiler;
static StaticAutoPtr<SandboxProfilerQueue> gSyscallsQueue;
static StaticAutoPtr<SandboxProfilerQueue> gLogsQueue;

static Atomic<bool> isShutdown = Atomic<bool>(false);

// Those function pointers are set by the main thread, and subsequently read by
// all other thread, in particular in SIGSYS handlers.
struct UprofilerFuncPtrs uprofiler;
bool uprofiler_initted = false;

static bool const SANDBOX_PROFILER_DEBUG = false;

// Semaphores that we use to signal the SandboxProfilerEmitter thread when data
// has been pushed to the SandboxProfilerQueue
static sem_t gSyscallRequest;
static sem_t gLogsRequest;

// This is only be called on main thread, and not within SIGSYS context
//
// We might be called either from the profiler-started notification observer in
// which case the !Active() call is not useful, but also directly from Sandbox'
// SandboxLateInit where we want to verify if we are not already active: that
// can happen if the user started the profiler via MOZ_PROFILER_STARTUP=1

/* static */
void SandboxProfiler::Create() {
  MOZ_ASSERT(!sInSignalContext,
             "SandboxProfiler::Create called in SIGSYS handler");

  if (!Init()) {
    return;
  }

  if (!Active()) {
    return;
  }

  if (!gSyscallsQueue) {
    gSyscallsQueue = new SandboxProfilerQueue();
  }

  if (!gLogsQueue) {
    gLogsQueue = new SandboxProfilerQueue();
  }

  if (!gProfiler) {
    gProfiler = new SandboxProfiler();
  }
}

SandboxProfiler::SandboxProfiler() {
  mThreadLogs = std::thread(&SandboxProfiler::ThreadMain, this,
                            "SandboxProfilerEmitterLogs", gLogsQueue.get(),
                            &gLogsRequest);
  mThreadSyscalls = std::thread(&SandboxProfiler::ThreadMain, this,
                                "SandboxProfilerEmitterSyscalls",
                                gSyscallsQueue.get(), &gSyscallRequest);
}

/* static */
void SandboxProfiler::Shutdown() {
  isShutdown = true;

  if (gProfiler) {
    // Unblock remaining
    SandboxProfiler::Signal(&gSyscallRequest);
    SandboxProfiler::Signal(&gLogsRequest);
  }

  gProfiler = nullptr;
  gSyscallsQueue = nullptr;
  gLogsQueue = nullptr;
}

SandboxProfiler::~SandboxProfiler() {
  if (mThreadLogs.joinable()) {
    mThreadLogs.join();
  }

  if (mThreadSyscalls.joinable()) {
    mThreadSyscalls.join();
  }
}

/* static */
bool SandboxProfiler::ActiveWithQueue(SandboxProfilerQueue* aQueue) {
  return !isShutdown && gProfiler && Active() && aQueue;
}

/* static */
void SandboxProfiler::Signal(sem_t* aSem) {
  if (sem_post(aSem) < 0) {
    if constexpr (SANDBOX_PROFILER_DEBUG) {
      fprintf(stderr, "[%d] %s SEM_POST errno=%d\n", getpid(),
              __PRETTY_FUNCTION__, errno);
    }
  }
}

/* static */
int SandboxProfiler::Wait(sem_t* aSem) { return sem_wait(aSem); }

/* static */
void SandboxProfiler::ReportInit(const void* top) {
  if (!ActiveWithQueue(gSyscallsQueue)) {
    return;
  }

  SandboxProfilerPayload payload = {
      .mStack = NativeStack{.mCount = 0},
      .mType = SandboxProfilerPayloadType::Init,
  };
  uprofiler.native_backtrace(top, &payload.mStack);

  MOZ_ASSERT(gSyscallsQueue, "Queue is valid for Send() from ReportInit()");
  if (!gSyscallsQueue) {
    if constexpr (SANDBOX_PROFILER_DEBUG) {
      fprintf(stderr,
              "[%d] WARNING: Hello PRODUCER: gSyscallsQueue disappeared\n",
              getpid());
    }
    return;
  }

  int rv = gSyscallsQueue->Send(payload);
  if (rv == 0) {
    if constexpr (SANDBOX_PROFILER_DEBUG) {
      fprintf(stderr,
              "[%d] WARNING: Hello PRODUCER: one stack mCount=%zu DROPPED\n",
              getpid(), payload.mStack.mCount);
    }
  }

  SandboxProfiler::Signal(&gSyscallRequest);
}

void SandboxProfiler::ReportInitImpl(SandboxProfilerPayload& payload,
                                     ProfileChunkedBuffer& buffer) {
  const char buf[] = "uprofiler init";
  std::array arg_names = {"init"};
  std::array arg_types = {
      TRACE_VALUE_TYPE_STRING,
  };
  std::array arg_values = {reinterpret_cast<unsigned long long>(buf)};

  Report("SandboxBroker::InitWithStack", arg_names, arg_types, arg_values,
         &buffer);
}

/* static */
void SandboxProfiler::ReportLog(const char* aBuf) {
  if (!ActiveWithQueue(gLogsQueue)) {
    return;
  }

  if (!SandboxInfo::Get().Test(SandboxInfo::kVerbose) &&
      !SandboxInfo::Get().Test(SandboxInfo::kVerboseTests)) {
    return;
  }

  SandboxProfilerPayload payload = {
      .mStack = NativeStack{.mCount = 0},
      .mType = SandboxProfilerPayloadType::Log,
  };

  const size_t bufLen = strnlen(aBuf, PATH_MAX);
  PodCopy(payload.mPath, aBuf, bufLen);

  MOZ_ASSERT(gLogsQueue, "Queue is valid for Send() from ReportLog()");
  if (!gLogsQueue) {
    if constexpr (SANDBOX_PROFILER_DEBUG) {
      fprintf(stderr, "[%d] WARNING: Hello PRODUCER: gLogsQueue disappeared\n",
              getpid());
    }
    return;
  }

  int rv = gLogsQueue->Send(payload);
  if (rv == 0) {
    if constexpr (SANDBOX_PROFILER_DEBUG) {
      fprintf(stderr, "[%d] WARNING: Hello PRODUCER: one log stack DROPPED\n",
              getpid());
    }
  }

  SandboxProfiler::Signal(&gLogsRequest);
}

void SandboxProfiler::ReportLogImpl(SandboxProfilerPayload& payload) {
  std::array arg_names = {"log"};
  std::array arg_types = {
      TRACE_VALUE_TYPE_STRING,
  };
  std::array arg_values = {
      reinterpret_cast<unsigned long long>(payload.mPath),
  };

  Report("SandboxBroker::Log", arg_names, arg_types, arg_values, nullptr);
}

/* static */
void SandboxProfiler::ReportRequest(const void* top, uint64_t aId,
                                    const char* aOp, int aFlags,
                                    const char* aPath, const char* aPath2,
                                    pid_t aPid) {
  if (!ActiveWithQueue(gSyscallsQueue)) {
    return;
  }

  // Take a stack, this should be safe to do in the context of SIGSYS
  SandboxProfilerPayload payload = {
      .mStack = NativeStack{.mCount = 0},
      .mId = aId,
      .mOp = aOp,
      .mFlags = aFlags,
      .mPid = aPid,
      .mType = SandboxProfilerPayloadType::Request,
  };

  if (aPath) {
    const size_t pathLen = strnlen(aPath, PATH_MAX);
    PodCopy(payload.mPath, aPath, pathLen);
  } else {
    payload.mPath[0] = '\0';
  }

  if (aPath2) {
    const size_t path2Len = strnlen(aPath2, PATH_MAX);
    PodCopy(payload.mPath2, aPath2, path2Len);
  } else {
    payload.mPath2[0] = '\0';
  }

  uprofiler.native_backtrace(top, &payload.mStack);

  MOZ_ASSERT(gSyscallsQueue, "Queue is valid for Send() from ReportRequest()");
  if (!gSyscallsQueue) {
    if constexpr (SANDBOX_PROFILER_DEBUG) {
      fprintf(stderr,
              "[%d] WARNING: Hello PRODUCER: gSyscallsQueue disappeared\n",
              getpid());
    }
    return;
  }

  int rv = gSyscallsQueue->Send(payload);
  if (rv == 0) {
    if constexpr (SANDBOX_PROFILER_DEBUG) {
      fprintf(stderr,
              "[%d] WARNING: Hello PRODUCER: one stack mCount=%zu DROPPED\n",
              getpid(), payload.mStack.mCount);
    }
  }

  SandboxProfiler::Signal(&gSyscallRequest);
}

void SandboxProfiler::ReportRequestImpl(SandboxProfilerPayload& payload,
                                        ProfileChunkedBuffer& buffer) {
  std::array arg_names = {"id", "op", "rflags", "path", "path2", "pid"};
  std::array arg_types = {
      TRACE_VALUE_TYPE_UINT,    // id
      TRACE_VALUE_TYPE_STRING,  // op
      TRACE_VALUE_TYPE_UINT,    // rflags
      TRACE_VALUE_TYPE_STRING,  // path
      TRACE_VALUE_TYPE_STRING,  // path2
      TRACE_VALUE_TYPE_UINT     // pid
  };

  std::array arg_values = {static_cast<unsigned long long>(payload.mId),
                           reinterpret_cast<unsigned long long>(payload.mOp),
                           static_cast<unsigned long long>(payload.mFlags),
                           reinterpret_cast<unsigned long long>(payload.mPath),
                           reinterpret_cast<unsigned long long>(payload.mPath2),
                           static_cast<unsigned long long>(payload.mPid)};

  Report("SandboxBrokerClient", arg_names, arg_types, arg_values, &buffer);
}

void SandboxProfiler::ThreadMain(const char* aThreadName,
                                 SandboxProfilerQueue* aQueue,
                                 sem_t* aRequest) {
  uprofiler.register_thread(aThreadName, CallerPC());
  SandboxProfilerPayload p;

  DebugOnly<int> sem_init_rv =
      sem_init(aRequest, /* pshared */ 0, /* value */ 0);
  MOZ_ASSERT(sem_init_rv == 0, "Failure to initialize semaphore");

  while (!isShutdown) {
    errno = 0;
    if (SandboxProfiler::Wait(aRequest) < 0) {
      int _errno = errno;
      MOZ_ASSERT(_errno != EINVAL, "sem_wait() returned EINVAL");
      if (_errno == EAGAIN || _errno == EINTR) {
        continue;
      }
    }

    MOZ_ASSERT(aQueue, "Syscalls queue is valid for Recv()");
    if (!aQueue) {
      if constexpr (SANDBOX_PROFILER_DEBUG) {
        fprintf(stderr,
                "[%d] WARNING: Hello CONSUMER [%s]: aQueue disappeared\n",
                getpid(), aThreadName);
      }
      continue;
    }

    int deq = aQueue->Recv(&p);
    if (deq > 0) {
      switch (p.mType) {
        case SandboxProfilerPayloadType::Init:
        case SandboxProfilerPayloadType::Request: {
          ProfileBufferChunkManagerSingle chunkManager{
              mozilla::ProfileBufferChunkManager::scExpectedMaximumStackSize};
          ProfileChunkedBuffer chunkedBuffer{
              ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager};
          uprofiler.backtrace_into_buffer(&p.mStack, &chunkedBuffer);

          switch (p.mType) {
            case SandboxProfilerPayloadType::Init:
              ReportInitImpl(p, chunkedBuffer);
              break;

            case SandboxProfilerPayloadType::Request:
              ReportRequestImpl(p, chunkedBuffer);
              break;

            default:
              // impossible?
              MOZ_ASSERT_UNREACHABLE("Should have been Init/Request");
              break;
          }
        } break;

        case SandboxProfilerPayloadType::Log:
          ReportLogImpl(p);
          break;

        default:
          fprintf(stderr, "[%d] mType=%hhu\n", getpid(),
                  static_cast<uint8_t>(p.mType));
          MOZ_CRASH("Unsupported type");
          break;
      }
    }
  }

  sem_destroy(aRequest);

  uprofiler.unregister_thread();
}

}  // namespace mozilla
