mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-11-14 18:00:54 +00:00
* UE5 at least currently (as of this commit) re-signs D3D12Core.dll by Epic. Rather than failing to load all such captures, only fail if the capture is marked as downloaded since local captures should be implicitly trusted.
628 lines
15 KiB
C++
628 lines
15 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2019-2022 Baldur Karlsson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include <dirent.h>
|
|
#include <dlfcn.h> // for dladdr
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <pwd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/file.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include "api/app/renderdoc_app.h"
|
|
#include "api/replay/data_types.h"
|
|
#include "common/formatting.h"
|
|
#include "common/threading.h"
|
|
#include "os/os_specific.h"
|
|
#include "strings/string_utils.h"
|
|
|
|
// gives us an address to identify this so with
|
|
static int soLocator = 0;
|
|
|
|
namespace FileIO
|
|
{
|
|
// in posix/.../..._stringio.cpp
|
|
rdcstr GetTempRootPath();
|
|
|
|
rdcstr GetHomeFolderFilename()
|
|
{
|
|
passwd *pw = getpwuid(getuid());
|
|
const char *homedir = pw->pw_dir;
|
|
|
|
return homedir;
|
|
}
|
|
|
|
rdcstr GetTempFolderFilename()
|
|
{
|
|
return GetTempRootPath() + "/";
|
|
}
|
|
|
|
void CreateParentDirectory(const rdcstr &filename)
|
|
{
|
|
rdcstr fn = get_dirname(filename);
|
|
|
|
// want trailing slash so that we create all directories
|
|
fn.push_back('/');
|
|
|
|
int offs = fn.find('/', 1);
|
|
|
|
while(offs >= 0)
|
|
{
|
|
// create directory path from 0 to offs by NULLing the
|
|
// / at offs, mkdir, then set it back
|
|
fn[offs] = 0;
|
|
mkdir(fn.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
|
fn[offs] = '/';
|
|
|
|
offs = fn.find('/', offs + 1);
|
|
}
|
|
}
|
|
|
|
bool IsRelativePath(const rdcstr &path)
|
|
{
|
|
if(path.empty())
|
|
return false;
|
|
|
|
return path.front() != '/';
|
|
}
|
|
|
|
rdcstr GetFullPathname(const rdcstr &filename)
|
|
{
|
|
char path[PATH_MAX + 1] = {0};
|
|
realpath(filename.c_str(), path);
|
|
|
|
return rdcstr(path);
|
|
}
|
|
|
|
rdcstr FindFileInPath(const rdcstr &fileName)
|
|
{
|
|
rdcstr filePath;
|
|
|
|
// Search the PATH directory list for the application (like shell which) to get the absolute path
|
|
// Return "" if no exectuable found in the PATH list
|
|
char *pathEnvVar = getenv("PATH");
|
|
if(!pathEnvVar)
|
|
return filePath;
|
|
|
|
// Make a copy of our PATH so strtok can insert NULL without actually changing env
|
|
char *localPath = new char[strlen(pathEnvVar) + 1];
|
|
strcpy(localPath, pathEnvVar);
|
|
|
|
const char *pathSeparator = ":";
|
|
const char *path = strtok(localPath, pathSeparator);
|
|
while(path)
|
|
{
|
|
rdcstr testPath(path);
|
|
testPath += "/" + fileName;
|
|
if(!access(testPath.c_str(), X_OK))
|
|
{
|
|
filePath = testPath;
|
|
break;
|
|
}
|
|
path = strtok(NULL, pathSeparator);
|
|
}
|
|
|
|
delete[] localPath;
|
|
return filePath;
|
|
}
|
|
|
|
rdcstr GetReplayAppFilename()
|
|
{
|
|
// look up the shared object's path via dladdr
|
|
Dl_info info;
|
|
dladdr((void *)&soLocator, &info);
|
|
rdcstr path = info.dli_fname ? info.dli_fname : "";
|
|
path = get_dirname(path);
|
|
rdcstr replay = path + "/qrenderdoc";
|
|
|
|
FILE *f = FileIO::fopen(replay, FileIO::ReadText);
|
|
if(f)
|
|
{
|
|
FileIO::fclose(f);
|
|
return replay;
|
|
}
|
|
|
|
// if it's not in the same directory, try in a sibling /bin
|
|
//
|
|
// start from our path
|
|
replay = path + "/";
|
|
|
|
// if there's a custom lib subfolder, go up one (e.g. /usr/lib/renderdoc/librenderdoc.so)
|
|
#if defined(RENDERDOC_LIB_SUBFOLDER)
|
|
replay += "../";
|
|
#endif
|
|
|
|
// leave the lib/ folder, and go into bin/
|
|
replay += "../bin/qrenderdoc";
|
|
|
|
f = FileIO::fopen(replay, FileIO::ReadText);
|
|
if(f)
|
|
{
|
|
FileIO::fclose(f);
|
|
return replay;
|
|
}
|
|
|
|
// random guesses!
|
|
const char *guess[] = {"/opt/renderdoc/qrenderdoc", "/opt/renderdoc/bin/qrenderdoc",
|
|
"/usr/local/bin/qrenderdoc", "/usr/bin/qrenderdoc"};
|
|
|
|
for(size_t i = 0; i < ARRAY_COUNT(guess); i++)
|
|
{
|
|
f = FileIO::fopen(guess[i], FileIO::ReadText);
|
|
if(f)
|
|
{
|
|
FileIO::fclose(f);
|
|
return guess[i];
|
|
}
|
|
}
|
|
|
|
// out of ideas, just return the filename and hope it's in PATH
|
|
return "qrenderdoc";
|
|
}
|
|
|
|
void GetDefaultFiles(const rdcstr &logBaseName, rdcstr &capture_filename, rdcstr &logging_filename,
|
|
rdcstr &target)
|
|
{
|
|
rdcstr path;
|
|
GetExecutableFilename(path);
|
|
|
|
const char *mod = strrchr(path.c_str(), '/');
|
|
if(mod != NULL)
|
|
mod++;
|
|
else if(path.length())
|
|
mod = path.c_str(); // Keep Android package name i.e. org.company.app
|
|
else
|
|
mod = "unknown";
|
|
|
|
target = rdcstr(mod);
|
|
|
|
time_t t = time(NULL);
|
|
tm now = *localtime(&t);
|
|
|
|
char temp_folder[2048] = {0};
|
|
|
|
strcpy(temp_folder, GetTempRootPath().c_str());
|
|
|
|
char *temp_override = getenv("RENDERDOC_TEMP");
|
|
if(temp_override && temp_override[0] == '/')
|
|
{
|
|
strncpy(temp_folder, temp_override, sizeof(temp_folder) - 1);
|
|
size_t len = strlen(temp_folder);
|
|
while(temp_folder[len - 1] == '/')
|
|
temp_folder[--len] = 0;
|
|
}
|
|
|
|
capture_filename =
|
|
StringFormat::Fmt("%s/RenderDoc/%s_%04d.%02d.%02d_%02d.%02d.rdc", temp_folder, mod,
|
|
1900 + now.tm_year, now.tm_mon + 1, now.tm_mday, now.tm_hour, now.tm_min);
|
|
|
|
// set by UI when launching programs so all logging goes to the same file
|
|
char *logfile_override = getenv("RENDERDOC_DEBUG_LOG_FILE");
|
|
if(logfile_override)
|
|
logging_filename = rdcstr(logfile_override);
|
|
else
|
|
logging_filename = StringFormat::Fmt(
|
|
"%s/RenderDoc/%s_%04d.%02d.%02d_%02d.%02d.%02d.log", temp_folder, logBaseName.c_str(),
|
|
1900 + now.tm_year, now.tm_mon + 1, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec);
|
|
}
|
|
|
|
uint64_t GetModifiedTimestamp(const rdcstr &filename)
|
|
{
|
|
struct ::stat st;
|
|
int res = stat(filename.c_str(), &st);
|
|
|
|
if(res == 0)
|
|
{
|
|
return (uint64_t)st.st_mtime;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t GetFileSize(const rdcstr &filename)
|
|
{
|
|
struct ::stat st;
|
|
int res = stat(filename.c_str(), &st);
|
|
|
|
if(res == 0)
|
|
{
|
|
return (uint64_t)st.st_size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool Copy(const rdcstr &from, const rdcstr &to, bool allowOverwrite)
|
|
{
|
|
if(from.empty() || to.empty())
|
|
return false;
|
|
|
|
FILE *ff = FileIO::fopen(from, ReadText);
|
|
|
|
if(!ff)
|
|
{
|
|
RDCERR("Can't open source file for copy '%s'", from.c_str());
|
|
return false;
|
|
}
|
|
|
|
FILE *tf = FileIO::fopen(to, FileIO::ReadText);
|
|
|
|
if(tf && !allowOverwrite)
|
|
{
|
|
RDCERR("Destination file for non-overwriting copy '%s' already exists", from.c_str());
|
|
FileIO::fclose(ff);
|
|
FileIO::fclose(tf);
|
|
return false;
|
|
}
|
|
|
|
if(tf)
|
|
FileIO::fclose(tf);
|
|
|
|
tf = FileIO::fopen(to, WriteText);
|
|
|
|
if(!tf)
|
|
{
|
|
FileIO::fclose(ff);
|
|
RDCERR("Can't open destination file for copy '%s'", to.c_str());
|
|
return false;
|
|
}
|
|
|
|
char buffer[BUFSIZ];
|
|
|
|
while(!FileIO::feof(ff))
|
|
{
|
|
size_t nread = FileIO::fread(buffer, 1, BUFSIZ, ff);
|
|
FileIO::fwrite(buffer, 1, nread, tf);
|
|
}
|
|
|
|
FileIO::fclose(ff);
|
|
FileIO::fclose(tf);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Move(const rdcstr &from, const rdcstr &to, bool allowOverwrite)
|
|
{
|
|
if(exists(to.c_str()))
|
|
{
|
|
if(!allowOverwrite)
|
|
return false;
|
|
}
|
|
|
|
return ::rename(from.c_str(), to.c_str()) == 0;
|
|
}
|
|
|
|
void Delete(const rdcstr &path)
|
|
{
|
|
unlink(path.c_str());
|
|
}
|
|
|
|
void GetFilesInDirectory(const rdcstr &path, rdcarray<PathEntry> &ret)
|
|
{
|
|
ret.clear();
|
|
|
|
DIR *d = opendir(path.c_str());
|
|
|
|
if(d == NULL)
|
|
{
|
|
PathProperty flags = PathProperty::ErrorUnknown;
|
|
|
|
if(errno == ENOENT)
|
|
flags = PathProperty::ErrorInvalidPath;
|
|
else if(errno == EACCES)
|
|
flags = PathProperty::ErrorAccessDenied;
|
|
|
|
ret.push_back(PathEntry(path, flags));
|
|
return;
|
|
}
|
|
|
|
dirent *ent = NULL;
|
|
|
|
for(;;)
|
|
{
|
|
ent = readdir(d);
|
|
|
|
if(!ent)
|
|
break;
|
|
|
|
// skip "." and ".."
|
|
if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
|
|
continue;
|
|
|
|
rdcstr fullpath = path;
|
|
fullpath += '/';
|
|
fullpath += ent->d_name;
|
|
|
|
struct ::stat st;
|
|
int res = stat(fullpath.c_str(), &st);
|
|
|
|
// invalid/bad file - skip it
|
|
if(res != 0)
|
|
continue;
|
|
|
|
PathProperty flags = PathProperty::NoFlags;
|
|
|
|
// make directory/executable mutually exclusive for clarity's sake
|
|
if(S_ISDIR(st.st_mode))
|
|
flags |= PathProperty::Directory;
|
|
else if(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
|
|
flags |= PathProperty::Executable;
|
|
|
|
if(ent->d_name[0] == '.')
|
|
flags |= PathProperty::Hidden;
|
|
|
|
PathEntry f(ent->d_name, flags);
|
|
|
|
f.lastmod = (uint32_t)st.st_mtime;
|
|
f.size = (uint64_t)st.st_size;
|
|
|
|
ret.push_back(f);
|
|
}
|
|
|
|
// don't care if we hit an error or enumerated all files, just finish
|
|
|
|
closedir(d);
|
|
}
|
|
|
|
const char *modeString[] = {
|
|
"r", "rb", "w", "wb", "r+b", "w+b",
|
|
};
|
|
|
|
FILE *fopen(const rdcstr &filename, FileMode mode)
|
|
{
|
|
return ::fopen(filename.c_str(), modeString[mode]);
|
|
}
|
|
|
|
rdcstr ErrorString()
|
|
{
|
|
int err = errno;
|
|
|
|
char buf[256] = {0};
|
|
|
|
strerror_r(err, buf, 256);
|
|
|
|
return buf;
|
|
}
|
|
|
|
size_t fread(void *buf, size_t elementSize, size_t count, FILE *f)
|
|
{
|
|
return ::fread(buf, elementSize, count, f);
|
|
}
|
|
size_t fwrite(const void *buf, size_t elementSize, size_t count, FILE *f)
|
|
{
|
|
return ::fwrite(buf, elementSize, count, f);
|
|
}
|
|
|
|
uint64_t ftell64(FILE *f)
|
|
{
|
|
return (uint64_t)::ftell(f);
|
|
}
|
|
void fseek64(FILE *f, uint64_t offset, int origin)
|
|
{
|
|
::fseek(f, (long)offset, origin);
|
|
}
|
|
|
|
bool feof(FILE *f)
|
|
{
|
|
return ::feof(f) != 0;
|
|
}
|
|
|
|
void ftruncateat(FILE *f, uint64_t length)
|
|
{
|
|
::fflush(f);
|
|
int fd = ::fileno(f);
|
|
::ftruncate(fd, (off_t)length);
|
|
}
|
|
|
|
bool fflush(FILE *f)
|
|
{
|
|
return ::fflush(f) == 0;
|
|
}
|
|
|
|
int fclose(FILE *f)
|
|
{
|
|
return ::fclose(f);
|
|
}
|
|
|
|
bool IsUntrustedFile(const rdcstr &filename)
|
|
{
|
|
// do android/linux have any way of marking files as potentially unsafe?
|
|
return false;
|
|
}
|
|
|
|
bool exists(const rdcstr &filename)
|
|
{
|
|
struct ::stat st;
|
|
int res = stat(filename.c_str(), &st);
|
|
|
|
return (res == 0);
|
|
}
|
|
|
|
rdcarray<int> logfiles;
|
|
|
|
// this is used in posix_process.cpp, so that we can close the handle any time that we fork()
|
|
void ReleaseFDAfterFork()
|
|
{
|
|
// we do NOT release the shared lock here, since the file descriptor is shared so we'd be
|
|
// releasing the parent process's lock. Just close our file descriptor
|
|
for(int log : logfiles)
|
|
close(log);
|
|
}
|
|
|
|
rdcstr logfile_readall(uint64_t offset, const rdcstr &filename)
|
|
{
|
|
FILE *f = FileIO::fopen(filename, FileIO::ReadText);
|
|
|
|
rdcstr ret;
|
|
|
|
if(f == NULL)
|
|
return ret;
|
|
|
|
FileIO::fseek64(f, 0, SEEK_END);
|
|
uint64_t size = FileIO::ftell64(f);
|
|
|
|
if(size > offset)
|
|
{
|
|
FileIO::fseek64(f, offset, SEEK_SET);
|
|
|
|
ret.resize(size_t(size - offset));
|
|
|
|
size_t numRead = FileIO::fread(ret.data(), 1, ret.size(), f);
|
|
ret.resize(numRead);
|
|
}
|
|
|
|
FileIO::fclose(f);
|
|
|
|
return ret;
|
|
}
|
|
|
|
LogFileHandle *logfile_open(const rdcstr &filename)
|
|
{
|
|
int fd =
|
|
open(filename.c_str(), O_APPEND | O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
|
if(fd < 0)
|
|
{
|
|
RDCWARN("Couldn't open logfile '%s': %d", filename.c_str(), (int)errno);
|
|
return NULL;
|
|
}
|
|
|
|
logfiles.push_back(fd);
|
|
|
|
// acquire a shared lock. Every process acquires a shared lock to the common logfile. Each time a
|
|
// process shuts down and wants to close the logfile, it releases its shared lock and tries to
|
|
// acquire an exclusive lock, to see if it can delete the file. See logfile_close.
|
|
int err = flock(fd, LOCK_SH | LOCK_NB);
|
|
|
|
if(err < 0)
|
|
RDCWARN("Couldn't acquire shared lock to '%s': %d", filename.c_str(), (int)errno);
|
|
|
|
return (LogFileHandle *)(uintptr_t)fd;
|
|
}
|
|
|
|
void logfile_append(LogFileHandle *logHandle, const char *msg, size_t length)
|
|
{
|
|
if(logHandle)
|
|
{
|
|
int fd = int(uintptr_t(logHandle) & 0xffffffff);
|
|
|
|
write(fd, msg, (unsigned int)length);
|
|
}
|
|
}
|
|
|
|
void logfile_close(LogFileHandle *logHandle, const rdcstr &deleteFilename)
|
|
{
|
|
if(logHandle)
|
|
{
|
|
int fd = int(uintptr_t(logHandle) & 0xffffffff);
|
|
|
|
// release our shared lock
|
|
int err = flock(fd, LOCK_UN | LOCK_NB);
|
|
|
|
if(err == 0 && !deleteFilename.empty())
|
|
{
|
|
// now try to acquire an exclusive lock. If this succeeds, no other processes are using the
|
|
// file (since no other shared locks exist), so we can delete it. If it fails, some other
|
|
// shared lock still exists so we can just close our fd and exit.
|
|
// NOTE: there is a race here between acquiring the exclusive lock and unlinking, but we
|
|
// aren't interested in this kind of race - we're interested in whether an application is
|
|
// still running when the UI closes, or vice versa, or similar cases.
|
|
err = flock(fd, LOCK_EX | LOCK_NB);
|
|
|
|
if(err == 0)
|
|
{
|
|
// we got the exclusive lock. Now release it, close fd, and unlink the file
|
|
err = flock(fd, LOCK_UN | LOCK_NB);
|
|
|
|
// can't really error handle here apart from retrying
|
|
if(err != 0)
|
|
RDCWARN("Couldn't release exclusive lock to '%s': %d", deleteFilename.c_str(), (int)errno);
|
|
|
|
close(fd);
|
|
|
|
unlink(deleteFilename.c_str());
|
|
|
|
// return immediately so we don't close again below.
|
|
return;
|
|
}
|
|
}
|
|
else if(err)
|
|
{
|
|
RDCWARN("Couldn't release shared lock to '%s': %d", deleteFilename.c_str(), (int)errno);
|
|
// nothing to do, we won't try again, just exit. The log might lie around, but that's
|
|
// relatively harmless.
|
|
}
|
|
|
|
logfiles.removeOne(fd);
|
|
|
|
close(fd);
|
|
}
|
|
}
|
|
};
|
|
|
|
namespace StringFormat
|
|
{
|
|
rdcstr sntimef(time_t utcTime, const char *format)
|
|
{
|
|
tm *tmv = localtime(&utcTime);
|
|
|
|
// conservatively assume that most formatters will replace like-for-like (e.g. %H with 12) and
|
|
// a few will increase (%Y to 2019) but generally the string will stay the same size.
|
|
size_t len = strlen(format) + 16;
|
|
|
|
size_t ret = 0;
|
|
char *buf = NULL;
|
|
|
|
// loop until we have successfully formatted
|
|
while(ret == 0)
|
|
{
|
|
// delete any previous buffer
|
|
delete[] buf;
|
|
|
|
// alloate new one of the new size
|
|
buf = new char[len + 1];
|
|
buf[len] = 0;
|
|
|
|
// try formatting
|
|
ret = strftime(buf, len, format, tmv);
|
|
|
|
// double the length for next time, if this failed
|
|
len *= 2;
|
|
}
|
|
|
|
rdcstr str = buf;
|
|
|
|
// delete successful buffer
|
|
delete[] buf;
|
|
|
|
return str;
|
|
}
|
|
};
|