//
// Copyright 2016 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//
#ifndef PXR_BASE_ARCH_STACK_TRACE_H
#define PXR_BASE_ARCH_STACK_TRACE_H

/// \file arch/stackTrace.h
/// \ingroup group_arch_Diagnostics
/// Architecture-specific call-stack tracing routines.

#include "pxr/pxr.h"
#include "pxr/base/arch/api.h"
#include "pxr/base/arch/defines.h"

#include <inttypes.h>
#include <stdio.h>
#include <functional>
#include <vector>
#include <string>
#include <iosfwd>
#include <ctime>

PXR_NAMESPACE_OPEN_SCOPE

/// \addtogroup group_arch_Diagnostics
///@{

/// Dumps call-stack info to a file, prints a message to the terminal, and 
/// invokes crash handling script.
///
/// The reason for the trace should be supplied in \p reason. An additional
/// message may be provided in \p message.  If \p reason is \c NULL then this
/// function only writes \p message to the banner (if any).
///
/// This routine can be slow and is intended to be called for a fatal error, 
/// such as a caught coredump signal. While it can theoretically be called at
/// any time, \c ArchLogCurrentProcessState() should be used for nonfatal cases.
///
/// This function is implemented by calling an external program.  This is
/// suitable for times where the current process may be corrupted.  In other
/// cases, using \c ArchPrintStackTrace() or other related functions would be
/// much faster.
///
/// Note the use of \c char* as opposed to \c string: this is intentional,
/// because we are trying to use only async-safe function from here on and
/// malloc() is not async-safe.
ARCH_API
void ArchLogFatalProcessState(const char* reason,
                              const char* message = nullptr,
                              const char* extraLogMsg = nullptr);

/// Dumps call-stack info to a file, and prints an informative message.
///
/// The reason for the trace should be supplied in \p reason. An additional
/// message may be provided in \p message.  If \p reason is \c NULL then this
/// function only writes \p message to the banner (if any).
///
/// This function is nearly identical to ArchLogFatalProcessState, including 
/// calling an external program. However, it is intended for cases that may
/// simulate or require the output info from a full fatal crash, but are 
/// not truly fatal errors. For cases where that is not necessary, 
/// using \c ArchPrintStackTrace() or other related functions would be
/// much faster.
///
/// Note the use of \c char* as opposed to \c string: this is intentional,
/// because we are trying to use only async-safe function from here on and
/// malloc() is not async-safe.
ARCH_API
void ArchLogCurrentProcessState(const char* reason,
                                const char* message = nullptr,
                                const char* extraLogMsg = nullptr);

/// Sets command line that gets call-stack info and triggers crash handling
/// script.
///
/// This function sets the command line to execute to gather and log
/// call-stack info.  \p argv must be NULL terminated. If \p command and/or \p
/// argv (or \p fatalArgv in the case of a fatal crash) are NULL, then 
/// the command will not be executed. Otherwise argv[0] and fatalArgv[0] must be 
/// the full path to the program to execute, typically \p command or "$cmd" as
/// described below.
///
/// \p command, \p argv, and \p fatalArgv are not copied and must remain valid 
/// until the next call to \c ArchSetProcessStateLogCommand.
///
/// Simple substitution is supported on argv elements:
/// \li $cmd:      Substitutes the command pathname, or $ARCH_POSTMORTEM if set
/// \li $pid:      Substitutes the process id
/// \li $log:      Substitutes the log pathname
/// \li $time:     Substitutes the user time (if available, else wall time)
/// \li $reason:   Substitutes the reason string for the crash
///
/// \sa ArchLogFatalProcessState
ARCH_API
void ArchSetProcessStateLogCommand(const char* command, 
                                   const char *const argv[],
                                   const char* const fatalArgv[]);

/// Returns true if the fatal signal handler ArchLogFatalProcessState
/// has been invoked.
ARCH_API
bool ArchIsAppCrashing();

/// Log session info.
///
/// Optionally indicate that this is due to a crash by providing
/// the path to a file containing a stack trace in \p crashStackTrace.
///
ARCH_API
void ArchLogSessionInfo(const char *crashStackTrace=NULL);

/// Sets the command line to log sessions.
///
/// This function sets the command line to execute to log session info. \p
/// argv is used if no crash stack trace is provided, otherwise \p crashArgv
/// is used.  Both must be NULL terminated.  If \p command or \p argv is NULL
/// then non-crashes are not logged;  if \p command or \p crashArgv is NULL
/// then crashes are not logged.  If not NULL then argv[0] and crashArgv[0]
/// must be full path to the program to execute, typically \p command or
/// "$cmd" as described below.
///
/// \p command, \p argv, and \p crashArgv are not copied and must remain valid
/// until the next call to \c ArchSetLogSession.
///
/// Simple substitution is supported on argv elements:
/// \li $cmd:      Substitutes the command pathname, or $ARCH_LOGSESSION if set
/// \li $prog      Substitutes the program name
/// \li $pid:      Substitutes the process id
/// \li $time:     Substitutes the user time (if available, else wall time)
/// \li $stack:    Substitutes the crash stack string (only in crashArgv)
///
/// \sa ArchLogSessionInfo
ARCH_API
void ArchSetLogSession(const char* command,
                       const char* const argv[],
                       const char* const crashArgv[]);

/// Register the callback to invoke logging at end of a successful session.
///
/// This function registers ArchLogSessionInfo() and records the current
/// timestamp, to send up-time to the DB upon exiting.
ARCH_API
void ArchEnableSessionLogging();

/// Print a stack trace to the given FILE pointer.
ARCH_API
void ArchPrintStackTrace(FILE *fout,
                         const std::string& programName,
                         const std::string& reason);

/// Print a stack trace to the given FILE pointer.
/// This function uses ArchGetProgramInfoForErrors as the \c programName.
/// \overload
ARCH_API
void ArchPrintStackTrace(FILE *fout, const std::string& reason);

/// Print a stack trace to the given ostream.
/// \overload
ARCH_API
void ArchPrintStackTrace(std::ostream& out,
                         const std::string& programName,
                         const std::string& reason);

/// Print a stack trace to the given ostream.
/// This function uses ArchGetProgramInfoForErrors as the \c programName.
/// \overload
ARCH_API
void ArchPrintStackTrace(std::ostream& out, const std::string& reason);

/// A callback to get a symbolic representation of an address.
typedef std::function<std::string(uintptr_t address)> ArchStackTraceCallback;

/// Sets a callback to get a symbolic representation of an address.
///
/// The callback returns a string for an address in a stack trace, typically
/// including the name of the function containing the address. \p cb may be \c
/// NULL to use a default implementation.
ARCH_API
void ArchSetStackTraceCallback(const ArchStackTraceCallback& cb);

/// Returns the callback to get a symbolic representation of an address.
/// \sa ArchSetStackTraceCallback
ARCH_API
void ArchGetStackTraceCallback(ArchStackTraceCallback* cb);

/// Returns the set value for the application's launch time.
/// The timestamp for this value is set when the arch library is initialized.
ARCH_API
time_t ArchGetAppLaunchTime();

/// Enables or disables the automatic logging of crash information.
///
/// This function controls whether the stack trace and build information is
/// automatically caught and stored to an internal database when a fatal crash
/// occurs.
ARCH_API 
void ArchSetFatalStackLogging(bool flag);

/// Returns whether automatic logging of fatal crashes is enabled
/// This is set to false by default.
/// \see ArchSetFatalStackLogging
ARCH_API
bool ArchGetFatalStackLogging();

/// Sets the program name to be used in diagnostic output
///
/// The default value is initialized to ArchGetExecutablePath().
ARCH_API
void ArchSetProgramNameForErrors(const char * progName);

/// Returns the currently set program name for reporting errors.
/// Defaults to ArchGetExecutablePath().
ARCH_API
const char * ArchGetProgramNameForErrors();

/// Sets additional program info to be reported to the terminal in case of a
/// fatal error.
ARCH_API
void ArchSetProgramInfoForErrors( const std::string& key, const std::string& value );

/// Returns currently set program info. 
/// \see ArchSetExtraLogInfoForErrors
ARCH_API
std::string ArchGetProgramInfoForErrors(const std::string& key);

/// Stores (or removes if \p lines is nullptr) a pointer to additional log data
/// that will be output in the stack trace log in case of a fatal error. Note
/// that the pointer \p lines is copied, not the pointed-to data.  In addition,
/// Arch might read the data pointed to by \p lines concurrently at any time.
/// Thus it is the caller's responsibility to ensure that \p lines is both valid
/// and not mutated until replacing or removing it by invoking this function
/// again with the same \p key and different \p lines.
ARCH_API
void ArchSetExtraLogInfoForErrors(const std::string &key,
                                  std::vector<std::string> const *lines);

/// Logs a stack trace to a file in /var/tmp.
///
/// This function is similar to \c ArchLogPostMortem(), but will not fork an
/// external process and only reports a stack trace.  A file in /var/tmp is
/// created with the name \c st_APPNAME.XXXXXX, where \c mktemp is used to
/// make a unique extension for the file.  If \c sessionLog is specified, then
/// it will be appended to this file.  A message is printed to \c stderr
/// reporting that a stack trace has been taken and what file it has been
/// written to.  And if \c fatal is true, then the stack trace will  be added
/// to the stack_trace database table.
///
/// \note This function is NOT async-signal-safe.  Use \c
/// ArchLogFatalProcessState or \c ArchLogCurrentProcessState from a signal
/// handler instead.
ARCH_API
void ArchLogStackTrace(const std::string& progName,
                       const std::string& reason,
                       bool fatal = false,
                       const std::string& sessionLog = "");

/// Logs a stack trace to a file in /var/tmp.
///
/// This function is similar to \c ArchLogPostMortem(), but will not fork an
/// external process and only reports a stack trace.  A file in /var/tmp is
/// created with the  name \c st_APPNAME.XXXXXX, where \c mktemp is used to
/// make a unique  extension for the file.  If \c sessionLog is specified,
/// then it will be appended to this file.  A message is printed to \c stderr
/// reporting that a stack trace has been taken and what file it has been
/// written to.  And if \c fatal is true, then the stack trace will  be added
/// to the stack_trace database table.
ARCH_API
void ArchLogStackTrace(const std::string& reason,
                       bool fatal = false, 
                       const std::string& sessionLog = "");

/// Return stack trace.
///
/// This function will return a vector of strings containing the current
/// stack. The vector will be of maximum size \p maxDepth.
ARCH_API
std::vector<std::string> ArchGetStackTrace(size_t maxDepth);


/// Save frames of current stack
///
/// This function saves at maximum \c maxDepth frames of the current stack
/// into the vector \c frames.
ARCH_API
void ArchGetStackFrames(size_t maxDepth, std::vector<uintptr_t> *frames);

/// Store at most \p maxDepth frames of the current stack into \p frames.
/// Return the number of stack frames written to \p frames.
ARCH_API
size_t ArchGetStackFrames(size_t maxDepth, uintptr_t *frames);

/// Save frames of current stack.
///
/// This function saves at maximum \p maxDepth frames of the current stack
/// into the vector \p frames, skipping the first \p numFramesToSkipAtTop
/// frames.  The first frame will be at depth \p numFramesToSkipAtTop and the
/// last at depth \p numFramesToSkipAtTop + \p maxDepth - 1.
ARCH_API
void ArchGetStackFrames(size_t maxDepth, size_t numFramesToSkipAtTop,
                        std::vector<uintptr_t> *frames);

/// Store at most \p maxDepth frames of the current stack into \p frames,
/// skipping the first \p numFramesToSkipAtTop frames.  Return the number of
/// stack frames written to \p frames.
ARCH_API
size_t ArchGetStackFrames(size_t maxDepth, size_t numFramesToSkipAtTop,
                          uintptr_t *frames);


/// Print stack frames to the given ostream.
ARCH_API
void ArchPrintStackFrames(std::ostream& out,
                          const std::vector<uintptr_t> &frames,
                          bool skipUnknownFrames = false);

/// Callback for handling crashes.
/// \see ArchCrashHandlerSystemv
typedef void (*ArchCrashHandlerSystemCB)(void* userData);

/// Replacement for 'system' safe for a crash handler
///
/// This function is a substitute for system() which does not allocate or free
/// any data, and times out after \c timeout seconds if the operation in \c
/// argv is not complete. Unlike system, it takes the full \c pathname of the
/// program to run, and won't search the path. Also unlike system, \c argv[]
/// are the separated arguments, starting with the program's name, as for
/// execv. \c callback is called every second. \c userData is passed to \c
/// callback.  \c callback can be used, for example, to print a '.' repeatedly
/// to show progress.  The alarm used in this function could interfere with
/// setitimer or other calls to alarm, and this function uses non-locking fork
/// and exec if available so should not generally be used except following a
/// catastrophe.
ARCH_API
int ArchCrashHandlerSystemv(const char* pathname, char *const argv[],
			    int timeout, ArchCrashHandlerSystemCB callback, 
			    void* userData);

#if defined(ARCH_OS_DARWIN)
// macOS has no ETIME. ECANCELED seems to have about the closest meaning to
// the actual error here. The operation is timing out, not being explicitly
// canceled, but it is canceled.
#ifndef ETIME
#define ETIME ECANCELED
#endif  // end ETIME
#endif  // end ARCH_OS_DARWIN

///@}

PXR_NAMESPACE_CLOSE_SCOPE

#endif  // PXR_BASE_ARCH_STACK_TRACE_H
