@ -4,6 +4,8 @@
#include "cocoa_progress_callback.h"
#include "cocoa_progress_callback.h"
#include "updater.h"
#include "updater.h"
#include "common/cocoa_tools.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/log.h"
#include "common/path.h"
#include "common/path.h"
@ -11,17 +13,59 @@
#include "common/string_util.h"
#include "common/string_util.h"
#include "common/timer.h"
#include "common/timer.h"
#include <cerrno>
#include <cstdlib>
#include <cstdlib>
#include <sys/event.h>
#include <sys/types.h>
#include <thread>
#include <thread>
#include <unistd.h>
static void LaunchApplication(const char* path )
static bool WaitForProcessToExit(pid_t process_id, Error* error )
{
{
@autoreleasepool
const int queue = kqueue();
if (queue < 0)
{
{
NSTask* task = [[[NSTask alloc] init] autorelease];
Error::SetErrno(error, "kqueue() failed: ", errno);
[task setLaunchPath:[NSString stringWithUTF8String:path]];
return false;
[task launch];
}
}
const ScopedGuard queue_closer = [queue]() { close(queue); };
struct kevent change;
EV_SET(&change, static_cast<uintptr_t>(process_id), EVFILT_PROC, EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_EXIT, 0,
nullptr);
struct kevent event;
int result;
do
{
result = kevent(queue, &change, 1, &event, 1, nullptr);
} while (result < 0 && errno == EINTR);
if (result < 0)
{
Error::SetErrno(error, "kevent() failed while waiting for DuckStation to exit: ", errno);
return false;
}
if (result == 0)
{
Error::SetString(error, "kevent() returned without reporting that DuckStation exited.");
return false;
}
if (event.flags & EV_ERROR)
{
// The process can exit between launching the updater and registering the event. In that case it is safe to
// continue. Any other registration error is fatal, because modifying the bundle while DuckStation is still
// running could leave the installation unusable.
const int event_error = static_cast<int>(event.data);
if (event_error == ESRCH)
return true;
Error::SetErrno(error, "Failed to monitor DuckStation: ", event_error);
return false;
}
return true;
}
}
int main(int argc, char* argv[])
int main(int argc, char* argv[])
@ -35,20 +79,22 @@ int main(int argc, char* argv[])
CocoaProgressCallback progress;
CocoaProgressCallback progress;
if (argc != 4 )
if (argc != 5 )
{
{
progress.ModalError("Expected 3 arguments: update zip, staging directory, output directory.\n\nThis program is not "
progress.ModalError(
"intended to be run manually, please use the Qt frontend and click Help->Check for Updates.");
"Expected 4 arguments: parent process id, update zip, staging directory, output directory.\n\nThis program is "
"not intended to be run manually, please use the Qt frontend and click Help->Check for Updates.");
return EXIT_FAILURE;
return EXIT_FAILURE;
}
}
std::string zip_path = argv[1];
const int parent_process_id = StringUtil::FromChars<int>(argv[1]).value_or(0);
std::string staging_directory = argv[2];
std::string zip_path = argv[2];
std::string destination_directory = argv[3];
std::string staging_directory = argv[3];
std::string destination_directory = argv[4];
if (zip_path.empty() || staging_directory.empty() || destination_directory.empty())
if (parent_process_id <= 0 || zip_path.empty() || staging_directory.empty() || destination_directory.empty())
{
{
progress.ModalError("One or more parameters is empty .");
progress.ModalError("One or more parameters is invalid .");
return EXIT_FAILURE;
return EXIT_FAILURE;
}
}
@ -59,10 +105,10 @@ int main(int argc, char* argv[])
Log::SetFileOutputParams(true, log_path.c_str());
Log::SetFileOutputParams(true, log_path.c_str());
}
}
std::string program_to_launch = Path::Combine(destination_directory, "Contents/MacOS/DuckStation") ;
std::string application_to_launch = destination_directory ;
int result = EXIT_SUCCESS;
int result = EXIT_SUCCESS;
std::thread worker([&progress, zip_path = std::move(zip_path),
std::thread worker([&progress, parent_process_id, zip_path = std::move(zip_path),
destination_directory = std::move(destination_directory),
destination_directory = std::move(destination_directory),
staging_directory = std::move(staging_directory), &result]() {
staging_directory = std::move(staging_directory), &result]() {
ScopedGuard app_stopper([]() {
ScopedGuard app_stopper([]() {
@ -85,6 +131,15 @@ int main(int argc, char* argv[])
});
});
});
});
progress.FormatStatusText("Waiting for DuckStation process {} to exit...", parent_process_id);
Error wait_error;
if (!WaitForProcessToExit(static_cast<pid_t>(parent_process_id), &wait_error))
{
progress.FormatModalError("Failed to wait for DuckStation to exit: {}", wait_error.GetDescription());
result = EXIT_FAILURE;
return;
}
Updater updater(&progress);
Updater updater(&progress);
if (!updater.Initialize(std::move(staging_directory), std::move(destination_directory)))
if (!updater.Initialize(std::move(staging_directory), std::move(destination_directory)))
{
{
@ -142,8 +197,15 @@ int main(int argc, char* argv[])
if (result == EXIT_SUCCESS)
if (result == EXIT_SUCCESS)
{
{
progress.FormatInformation("Launching '{}'...", program_to_launch);
progress.FormatInformation("Launching '{}'...", application_to_launch);
LaunchApplication(program_to_launch.c_str());
Error launch_error;
if (!CocoaTools::LaunchApplication(application_to_launch, {}, &launch_error))
{
progress.FormatModalError("The update was installed successfully, but DuckStation could not be restarted: {}\n\n"
"Please launch DuckStation manually.",
launch_error.GetDescription());
result = EXIT_FAILURE;
}
}
}
return result;
return result;