/* 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/. */

#ifndef SANDBOX_PROFILER_H
#define SANDBOX_PROFILER_H

#include <thread>
#include <linux/limits.h>
#include <semaphore.h>

#include "ProfilerNativeStack.h"
#include "MicroGeckoProfiler.h"

#include "mozilla/ProfileChunkedBuffer.h"
#include "mozilla/ProfilerState.h"

#include "mozilla/BoundedMPSCQueue.h"

#if defined(HAVE_REPORT_UPROFILER_PARENT) && \
    defined(HAVE_REPORT_UPROFILER_CHILD)
#  error Cannot include SandboxProfilerChild.h AND SandboxProfilerParent.h
#endif

namespace mozilla {

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

class MOZ_STACK_CLASS AutoForbidSignalContext {
 public:
  AutoForbidSignalContext();
  ~AutoForbidSignalContext();
};
#endif  // defined(DEBUG)

enum class SandboxProfilerPayloadType : uint8_t { Init, Request, Log };

using SandboxProfilerPayload = struct {
  NativeStack mStack;
  uint64_t mId;
  const char* mOp;
  int mFlags;
  char mPath[PATH_MAX];
  char mPath2[PATH_MAX];
  pid_t mPid;
  SandboxProfilerPayloadType mType;
};

using SandboxProfilerQueue = BoundedMPSCQueue<SandboxProfilerPayload, 15>;

extern struct UprofilerFuncPtrs uprofiler;
extern bool uprofiler_initted;

class SandboxProfiler final {
 public:
  SandboxProfiler();
  ~SandboxProfiler();

  // Needs to be accessible in both child (within libmozsandbox.so) and parent
  // (within libxul.so) it's easier and less of a mess if this is done from the
  // header which can more easily be included by both sides.
  static bool Active() {
    if (!uprofiler_initted) {
      return false;
    }

    if (!uprofiler.is_active || uprofiler.is_active == is_active_noop) {
      return false;
    }

    if (!uprofiler.feature_active ||
        uprofiler.feature_active == feature_active_noop) {
      return false;
    }

    return uprofiler.is_active() &&
           uprofiler.feature_active(ProfilerFeature::Sandbox);
  }

  static void Shutdown();

  // Should NOT BE CALLED UNDER SIGSYS, this is here to make sure we do the
  // dlopen()/dlsym() on the main thread so it is available for later use on
  // others threads. We expect that only stack trace would be collected under
  // SIGSYS context, and the rest of the profiler marker would happen on another
  // safer thread.
  static inline bool Init();

  static void Create();
  static void inline ReportAudit(const char* aKind, const char* aOp, int aFlags,
                                 uint64_t aId, int aPerms, const char* aPath,
                                 pid_t aPid);
  static void ReportRequest(const void* top, uint64_t aId, const char* aOp,
                            int aFlags, const char* aPath, const char* aPath2,
                            pid_t aPid);
  static void ReportInit(const void* top);
  static void ReportLog(const char* buf);

 private:
  std::thread mThreadLogs;
  std::thread mThreadSyscalls;

  // For child and parent, same reason as Active() above
  template <typename N, typename T, typename V, size_t len>
  static constexpr void Report(const char* aKind, std::array<N, len> aArgNames,
                               std::array<T, len> aArgTypes,
                               std::array<V, len> aArgValues, void* aStack) {
    if (!Active()) {
      return;
    }

    if (!uprofiler_initted) {
      fprintf(stderr, "[%d] no uprofiler, skip\n", getpid());
      return;
    }

    MOZ_ASSERT(!sInSignalContext,
               "SandboxProfiler::Report called in SIGSYS handler");

    if (aStack) {
      uprofiler.simple_event_marker_with_stack(
          aKind, 'S', 'I', aArgNames.size(), aArgNames.data(), aArgTypes.data(),
          aArgValues.data(), aStack);
    } else {
      uprofiler.simple_event_marker(aKind, 'S', 'I', aArgNames.size(),
                                    aArgNames.data(), aArgTypes.data(),
                                    aArgValues.data());
    }
  }

  // Verify that:
  //  - Not in shutdown
  //  - SandboxProfiler exists
  //  - Profiler is active
  //  - aQueue exists
  static bool ActiveWithQueue(SandboxProfilerQueue* aQueue);

  // Rely on semaphores to handle consuming the queue:
  //  - One semaphore per queue (= thread)
  //  - Gets SIGNAL'd when a payload has been pushed to the SandboxProfilerQueue
  //  - SandboxProfiler dedicated thread WAIT's on the semaphore, gets unblocked
  //    on signal
  //  - Semaphore's sem_post() is safe to use in a signal context
  //  - Using semaphores allows to wake the SandboxProfiler dedicated thread
  //    only when needed and avoid using sleep()
  static void Signal(sem_t* aSem);
  static int Wait(sem_t* aSem);

  void ThreadMain(const char* aThreadName, SandboxProfilerQueue* aQueue,
                  sem_t* aRequest);
  void ReportInitImpl(SandboxProfilerPayload& payload,
                      ProfileChunkedBuffer& buffer);
  void ReportLogImpl(SandboxProfilerPayload& payload);
  void ReportRequestImpl(SandboxProfilerPayload& payload,
                         ProfileChunkedBuffer& buffer);
};

}  // namespace mozilla
#endif  // SANDBOX_PROFILER_H
