Files
UnrealEngineUWP/Engine/Source/Runtime/Unix/UnixCommonStartup/Private/UnixCommonStartup.cpp
paul chipchase 21c2777e84 Add an UE5 specific version EUnrealEngineObjectUE5Version to be used for global changes instead of EUnrealEngineObjectUEVersion. By splitting and storing both version numbers we allow for hypothetical future UE4 changes that will not conflict when merged to UE5.
#rb CarlMagnus.Nordin
#rnx
#tests Ran overnight preflights on several platforms, opened/cooked/staged/ran the oldest version of InfiltratorDemo that can be downloaded (4.11)

### ObjectVersion
- Add a new version enum EUnrealEngineObjectUE5Version.
-- This version number starts at 1000 which leaves more than enough for for EUnrealEngineObjectUEVersion to be expanded
- Even though very few changes (if any at all) to EUnrealEngineObjectUE4Version are expected there is a static assert to make sure that EUnrealEngineObjectUEVersion::AUTOMATIC_VERSION never overtakes EUnrealEngineObjectUE5Version::INITIAL_VERSION.
- Add a struct FPackageFileVersion that wraps around the version numbers and is used to store them instead of raw int32 values which was done before. This should make it easier to add new version numbers in the future if we desire (although this will cause problems in places that serialize the struct directly)

### FPackageFileSummary
- Adding a new entry to CurrentLegacyFileVersion at value -8 which shows the UE5 version being added. This lets us make the changes without needing to submit anything to UE4 Main.
- When loading a package that does not have a UE5 version, it will remain at 0.
- Added ::IsFileVersionTooOld and ::IsFileVersionTooNew to replace hardcoded tests in the code base for version validity. This will make it easier to make changes in the future.
- A few months ago most of the accessors of the version number were deprecated in favour of a version that did not contain the Engine number (ie UE4Ver -> UEVer in Archive) but to work with these changes the renamed methods now will return or accept the version as FPackageFileVersion rather than int32.  The old UE4 methods will remain deprecated and direct licensees to use the new methods.

### Archive
- Now stores the version as a FPackageFileVersion rather than int32

### LinkerLoad
- Reports the larger version number if we detect a higher version number than we support. Note that this could cause an issue if the UE4 version is ever raised but helps keep the code simple.

### AssetData
- Need to add a new version here to manage existing data that only has the UE4 version

### EditorDomain
- We do not need to version the format, we can just invalidate existing editor domain entries via EditorDomainVersion

### EditorServer
- When reporting that a package is too old we report the UE4 version as that is the only version that can be older than VER_UE4_OLDEST_LOADABLE_PACKAGE
- When reporting that a package is too new it can be either the UE4 or the UE5 version so we print them together "UE4Ver|UE5Ver"

### ContentCommandlets
- The min and max resave versions have been kept as a single value, you will not be able to resave against different UE4 and UE5 versions at the same time. It doesn't seem like a useful feature and would greatly increase the complexity of the code.
- We will also only report the file version as a single value.

### ManifestUObject
- This class was setting an older obsolete version on purpose to try and maintain compatibility with older clients so we need to provide a way to create an older UE4 only version that will leave the UE5 version as unset.

### NetworkPlatformFile
- I was unable to test the code path in FNetworkPlatformFile::ProcessServerCachedFilesResponse as I am unsure how to run the game in a mode that will actually use it.
- When reading an older "CookedVersion.txt" that was saved with a single version, the reads will fail and this will count as a version change in the code so that all of the existing files will be deleted. The existing code would not give the user a log message when this happens and given the very small time window where this might happen caused by this change I have opted to leave this alone and not add any additional logging.
- If we do detect a version mismatch we will still only log the version number as a single version.

### CookOnTheFlyServer
- We now add each version number to the IniVersionMap rather than merge the version and license version as a key/value pair. This allows us to a) use both the UE4 and UE5 version numbers b) we now log a warning that the version values don't match when it is changed, previously since it was a key value we would log a warning about an additional setting instead.
-- I also added "vs" to the log message when values are mismatched to make the space between the two values being printed clearer.

#ROBOMERGE-OWNER: paul.chipchase
#ROBOMERGE-AUTHOR: paul.chipchase
#ROBOMERGE-SOURCE: CL 17549459 via CL 17550236 via CL 17550238 via CL 17550582 via CL 17550583
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Test -> Main) (v870-17433530)

[CL 17550586 by paul chipchase in ue5-main branch]
2021-09-17 07:05:39 -04:00

305 lines
9.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UnixCommonStartup.h"
#include "Misc/OutputDeviceRedirector.h"
#include "Misc/OutputDeviceError.h"
#include "Misc/FeedbackContext.h"
#include "HAL/ExceptionHandling.h"
#include "Unix/UnixPlatformCrashContext.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "Misc/EngineVersion.h"
#include "HAL/PlatformApplicationMisc.h"
#include <locale.h>
#include <sys/resource.h>
static FString GSavedCommandLine;
extern int32 GuardedMain( const TCHAR* CmdLine );
extern void LaunchStaticShutdownAfterError();
/**
* Game-specific crash reporter
*/
void CommonUnixCrashHandler(const FGenericCrashContext& GenericContext)
{
// at this point we should already be using malloc crash handler (see PlatformCrashHandler)
const FUnixCrashContext& Context = static_cast< const FUnixCrashContext& >( GenericContext );
printf("CommonUnixCrashHandler: Signal=%d\n", Context.Signal);
/** This is the last place to gather memory stats */
FGenericCrashContext::SetMemoryStats(FPlatformMemory::GetStats());
// better than having mutable fields?
const_cast< FUnixCrashContext& >(Context).CaptureStackTrace(Context.ErrorFrame);
if (GLog)
{
GLog->SetCurrentThreadAsMasterThread();
GLog->Flush();
}
if (GWarn)
{
GWarn->Flush();
}
if (GError)
{
GError->Flush();
GError->HandleError();
}
return Context.GenerateCrashInfoAndLaunchReporter();
}
/**
* Sets (soft) limit on a specific resource
*
* @param Resource - one of RLIMIT_* values
* @param DesiredLimit - desired value
* @param bIncreaseOnly - avoid changing the limit if current value is sufficient
*/
bool SetResourceLimit(int Resource, rlim_t DesiredLimit, bool bIncreaseOnly)
{
rlimit Limit;
if (getrlimit(Resource, &Limit) != 0)
{
fprintf(stderr, "getrlimit() failed with error %d (%s)\n", errno, strerror(errno));
return false;
}
if (bIncreaseOnly && (Limit.rlim_cur == RLIM_INFINITY || Limit.rlim_cur >= DesiredLimit))
{
if (!UE_BUILD_SHIPPING)
{
printf("- Existing per-process limit (soft=%lu, hard=%lu) is enough for us (need only %lu)\n", Limit.rlim_cur, Limit.rlim_max, DesiredLimit);
}
return true;
}
Limit.rlim_cur = DesiredLimit;
if (setrlimit(Resource, &Limit) != 0)
{
fprintf(stderr, "setrlimit() failed with error %d (%s)\n", errno, strerror(errno));
if (errno == EINVAL)
{
if (DesiredLimit == RLIM_INFINITY)
{
fprintf(stderr, "- Max per-process value allowed is %lu (we wanted infinity).\n", Limit.rlim_max);
}
else
{
fprintf(stderr, "- Max per-process value allowed is %lu (we wanted %lu).\n", Limit.rlim_max, DesiredLimit);
}
}
return false;
}
return true;
}
/**
* Sets (hard) limit on a specific resource
*
* @param Resource - one of RLIMIT_* values
*/
static bool SetResourceMaxHardLimit(int Resource)
{
rlimit Limit;
if (getrlimit(Resource, &Limit) != 0)
{
fprintf(stderr, "getrlimit() failed with error %d (%s)\n", errno, strerror(errno));
return false;
}
return SetResourceLimit(Resource, Limit.rlim_max, true);
}
/**
* Expects GSavedCommandLine to be set up. Increases limit on
* - number of open files to be no less than desired (if specified on command line, otherwise left alone)
* - size of core file, so core gets dumped and we can debug crashed builds (unless overridden with -nocore)
*
*/
static bool IncreasePerProcessLimits()
{
// honor the parameter if given, else change the limit of open files to the max hard limit allowed
int32 FileHandlesToReserve = -1;
if (FParse::Value(*GSavedCommandLine, TEXT("numopenfiles="), FileHandlesToReserve) && FileHandlesToReserve > 0)
{
if (!UE_BUILD_SHIPPING)
{
printf("Increasing per-process limit of open file handles to %d\n", FileHandlesToReserve);
}
if (!SetResourceLimit(RLIMIT_NOFILE, FileHandlesToReserve, true))
{
fprintf(stderr, "Could not adjust number of file handles, consider changing \"nofile\" in /etc/security/limits.conf and relogin.\nerror(%d): %s\n", errno, strerror(errno));
return false;
}
}
else
{
// Set the highest value we can for number of files open
SetResourceMaxHardLimit(RLIMIT_NOFILE);
}
// core dump policy:
// - Shipping disables it by default (unless -core is passed)
// - The rest set it to infinity unless -nocore is passed
// (in all scenarios user wish as expressed with -core or -nocore takes priority)
// Note that we used to have Test disable cores by default too. This has been changed around UE 4.15.
// Since 4.20, inability to change the limit is no longer a failure unless switches were used
bool bFailIfUnableToChange = false;
bool bDisableCore = (UE_BUILD_SHIPPING != 0);
if (FParse::Param(*GSavedCommandLine, TEXT("nocore")))
{
bDisableCore = true;
bFailIfUnableToChange = true;
}
if (FParse::Param(*GSavedCommandLine, TEXT("core")))
{
bDisableCore = false;
bFailIfUnableToChange = true;
}
if (bDisableCore)
{
printf("Disabling core dumps.\n");
if (!SetResourceLimit(RLIMIT_CORE, 0, false) && bFailIfUnableToChange)
{
fprintf(stderr, "Could not set core file size to 0, error(%d): %s\n", errno, strerror(errno));
return false;
}
}
else
{
printf("Increasing per-process limit of core file size to infinity.\n");
if (!SetResourceLimit(RLIMIT_CORE, RLIM_INFINITY, true) && bFailIfUnableToChange)
{
fprintf(stderr, "Could not adjust core file size, consider changing \"core\" in /etc/security/limits.conf and relogin.\nerror(%d): %s\n", errno, strerror(errno));
fprintf(stderr, "Alternatively, pass -nocore if you are unable or unwilling to do that.\n");
return false;
}
}
return true;
}
int CommonUnixMain(int argc, char *argv[], int (*RealMain)(const TCHAR * CommandLine), void (*AppExitCallback)())
{
FString EarlyInitCommandLine;
FPlatformApplicationMisc::EarlyUnixInitialization(EarlyInitCommandLine);
GSavedCommandLine += EarlyInitCommandLine;
FPlatformMisc::SetGracefulTerminationHandler();
if (UE_BUILD_SHIPPING)
{
// only printed in shipping
printf("%s %d %d\n", StringCast<ANSICHAR>(*FEngineVersion::Current().ToString()).Get(), GPackageFileUEVersion.ToValue(), GPackageFileLicenseeUEVersion);
}
int ErrorLevel = 0;
if (setenv("LC_NUMERIC", "en_US", 1) != 0)
{
int ErrNo = errno;
fprintf(stderr, "Unable to setenv(LC_NUMERIC): errno=%d (%s)", ErrNo, strerror(ErrNo));
}
for (int32 Option = 1; Option < argc; Option++)
{
GSavedCommandLine += TEXT(" ");
// we need to quote stuff that has spaces in it because something somewhere is removing quotation marks before they arrive here
FString Temp = UTF8_TO_TCHAR(argv[Option]);
if (Temp.Contains(TEXT(" ")))
{
int32 Quote = 0;
if(Temp.StartsWith(TEXT("-")))
{
int32 Separator;
if (Temp.FindChar('=', Separator))
{
Quote = Separator + 1;
}
}
Temp = Temp.Left(Quote) + TEXT("\"") + Temp.Mid(Quote) + TEXT("\"");
}
GSavedCommandLine += Temp; // note: technically it depends on locale
}
if (!UE_BUILD_SHIPPING)
{
GAlwaysReportCrash = true; // set by default and reverse the behavior
if ( FParse::Param( *GSavedCommandLine,TEXT("nocrashreports") ) || FParse::Param( *GSavedCommandLine,TEXT("no-crashreports") ) )
{
GAlwaysReportCrash = false;
}
}
if (FPlatformApplicationMisc::ShouldIncreaseProcessLimits() && !IncreasePerProcessLimits())
{
fprintf(stderr, "Could not set desired per-process limits, consider changing system limits.\n");
ErrorLevel = 1;
}
else
{
#if UE_BUILD_DEBUG
if( true && !GAlwaysReportCrash )
#else
if( FPlatformMisc::IsDebuggerPresent() && !GAlwaysReportCrash )
#endif
{
// Don't use exception handling when a debugger is attached to exactly trap the crash. This does NOT check
// whether we are the first instance or not!
ErrorLevel = RealMain( *GSavedCommandLine );
}
else
{
FPlatformMisc::SetCrashHandler(CommonUnixCrashHandler);
GIsGuarded = 1;
// Run the guarded code.
ErrorLevel = RealMain( *GSavedCommandLine );
GIsGuarded = 0;
}
}
// Final shut down.
if (AppExitCallback)
{
// Workaround function to avoid circular dependencies between Launch and CommonUnixStartup modules.
// Other platforms call FEngineLoop::AppExit() in their main() (removed by preprocessor if compiled without engine),
// but on Unix we want to share a common main() in CommonUnixStartup module, so not just the engine but all the programs
// could share this logic. Unfortunately, AppExit() practice breaks this nice approach since FEngineLoop cannot be moved outside of
// Launch module without making too many changes. Hence CommonUnixMain will call it through this function if provided.
AppExitCallback();
}
// check if a specific return code has been set
uint8 OverriddenErrorLevel = 0;
if (FPlatformMisc::HasOverriddenReturnCode(&OverriddenErrorLevel))
{
ErrorLevel = OverriddenErrorLevel;
}
if (ErrorLevel)
{
printf("Exiting abnormally (error code: %d)\n", ErrorLevel);
}
return ErrorLevel;
}
class FUnixCommonStartupModule : public IModuleInterface
{
/** IModuleInterface implementation */
virtual void StartupModule() override {};
virtual void ShutdownModule() override {};
};
IMPLEMENT_MODULE(FUnixCommonStartupModule, UnixCommonStartup);