/* -*- Mode: C++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */

#ifndef RR_WAIT_MANAGER_H_
#define RR_WAIT_MANAGER_H_

#include <unordered_map>

#include "WaitStatus.h"

namespace rr {

enum {
  // Ten years.
  WAIT_BLOCK_MAX = 315360000
};

// The problem with waiting for arbitrary tasks is that we'll reap them and that exposes us
// to pid reuse issues. So we really need to wait for non-exit only and handle exits specially,
// when we're ready and we can ensure those tasks aren't touched again.

struct WaitOptions {
  // Default options: wait for any task, block indefinitely.
  WaitOptions() :
      tid(-1),
      block_seconds(WAIT_BLOCK_MAX),
      unblock_on_other_tasks(false),
      consume(true),
      can_perform_syscall(true) {}
  // Default options, but wait for a specific task and don't stop
  // waiting if other tasks return a status.
  explicit WaitOptions(pid_t tid) :
      tid(tid),
      block_seconds(WAIT_BLOCK_MAX),
      unblock_on_other_tasks(false),
      consume(true),
      can_perform_syscall(true) {}
  // -1 to accept the status of any tid, otherwise returns only the
  // status of 'tid'.
  pid_t tid;
  // Number of seconds we should block waiting for a result.
  double block_seconds;
  // True if we should stop blocking and return WAIT_NO_STATUS if,
  // while blocking, we observe a task other than the requested task(s)
  // return a status.
  bool unblock_on_other_tasks;
  // True if we should consume the wait status. Otherwise we leave it
  // pending, either in the WaitManager (if it already was in the WaitManager)
  // or in the kernel itself. Exit status are never stashed in the WaitManager.
  bool consume;
  // True if we should allow syscalls, false if we should only return cached
  // status. If this is false, consume/block_seconds/unblock_on_other_tasks are ignored.
  bool can_perform_syscall;
};

enum WaitResultCode {
  // Got a valid WaitStatus
  WAIT_OK,
  // No matching task exists
  WAIT_NO_CHILD,
  // There is at least one matching child task but it hasn't reported a wait status.
  WAIT_NO_STATUS,
};
struct WaitResult {
  WaitResultCode code;
  // The tid for which we got a wait status, or -1 if we didn't get one.
  // If options.tid > 0 and the `options.tid` task changes its tid during the wait call,
  // e.g. due to execve tid changes, this may not match options.tid.
  pid_t tid;
  WaitStatus status;
};

// All waits must go through methods on this class.
class WaitManager {
public:
  // Wait for a WSTOPPED notification.
  static WaitResult wait_stop(const WaitOptions& options);
  // Wait for a WEXITED exit notification.
  // unblock_on_other_tasks must be false. perform_syscall must be true
  // (we only cache stops).
  // If `consume` is false then tid must be >= 0 and
  // blocking must be 0: WEXITED returns stops as well for ptracees, so if there
  // is a stop and we don't consume it we won't be able to see an exit notification
  // on that task or a different task.
  static WaitResult wait_exit(const WaitOptions& options);
  // Wait for a WSTOPPED or WEXITED notification.
  // unblock_on_other_tasks must be false. perform_syscall must be true
  // (we only cache stops).
  static WaitResult wait_stop_or_exit(const WaitOptions& options);

  // Gather stop notifications from all tasks without blocking.
  static void poll_stops();
};

}

#endif // RR_WAIT_MANAGER_H_
