/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SocketProcessHost.h" #include "nsAppRunner.h" #include "nsIObserverService.h" #include "SocketProcessParent.h" #ifdef MOZ_GECKO_PROFILER # include "ProfilerParent.h" #endif namespace mozilla { namespace net { #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline" class OfflineObserver final : public nsIObserver { public: NS_DECL_THREADSAFE_ISUPPORTS explicit OfflineObserver(SocketProcessHost* aProcessHost) : mProcessHost(aProcessHost) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->AddObserver(this, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, false); obs->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); } } void Destroy() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->RemoveObserver(this, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC); } mProcessHost = nullptr; } private: // nsIObserver implementation. NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) override { if (!mProcessHost) { return NS_OK; } if (!strcmp(aTopic, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC)) { NS_ConvertUTF16toUTF8 dataStr(aData); const char* offline = dataStr.get(); if (!mProcessHost->IsConnected() || mProcessHost->GetActor()->SendSetOffline( !strcmp(offline, "true") ? true : false)) { return NS_ERROR_NOT_AVAILABLE; } } else if (!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) { nsCOMPtr obs = mozilla::services::GetObserverService(); obs->RemoveObserver(this, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC); obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); } return NS_OK; } virtual ~OfflineObserver() = default; SocketProcessHost* mProcessHost; }; NS_IMPL_ISUPPORTS(OfflineObserver, nsIObserver) SocketProcessHost::SocketProcessHost(Listener* aListener) : GeckoChildProcessHost(GeckoProcessType_Socket), mListener(aListener), mTaskFactory(this), mLaunchPhase(LaunchPhase::Unlaunched), mShutdownRequested(false), mChannelClosed(false) { MOZ_ASSERT(NS_IsMainThread()); MOZ_COUNT_CTOR(SocketProcessHost); } SocketProcessHost::~SocketProcessHost() { MOZ_COUNT_DTOR(SocketProcessHost); if (mOfflineObserver) { RefPtr observer = mOfflineObserver; NS_DispatchToMainThread( NS_NewRunnableFunction("SocketProcessHost::DestroyOfflineObserver", [observer]() { observer->Destroy(); })); } } bool SocketProcessHost::Launch() { MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched); MOZ_ASSERT(!mSocketProcessParent); MOZ_ASSERT(NS_IsMainThread()); std::vector extraArgs; nsAutoCString parentBuildID(mozilla::PlatformBuildID()); extraArgs.push_back("-parentBuildID"); extraArgs.push_back(parentBuildID.get()); SharedPreferenceSerializer prefSerializer; if (!prefSerializer.SerializeToSharedMemory()) { return false; } // Formats a pointer or pointer-sized-integer as a string suitable for passing // in an arguments list. auto formatPtrArg = [](auto arg) { return nsPrintfCString("%zu", uintptr_t(arg)); }; #if defined(XP_WIN) // Record the handle as to-be-shared, and pass it via a command flag. This // works because Windows handles are system-wide. HANDLE prefsHandle = prefSerializer.GetSharedMemoryHandle(); AddHandleToShare(prefsHandle); AddHandleToShare(prefSerializer.GetPrefMapHandle().get()); extraArgs.push_back("-prefsHandle"); extraArgs.push_back(formatPtrArg(prefsHandle).get()); extraArgs.push_back("-prefMapHandle"); extraArgs.push_back( formatPtrArg(prefSerializer.GetPrefMapHandle().get()).get()); #else // In contrast, Unix fds are per-process. So remap the fd to a fixed one that // will be used in the child. // XXX: bug 1440207 is about improving how fixed fds are used. // // Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel, // and the fixed fd isn't used. However, we still need to mark it for // remapping so it doesn't get closed in the child. AddFdToRemap(prefSerializer.GetSharedMemoryHandle().fd, kPrefsFileDescriptor); AddFdToRemap(prefSerializer.GetPrefMapHandle().get(), kPrefMapFileDescriptor); #endif // Pass the lengths via command line flags. extraArgs.push_back("-prefsLen"); extraArgs.push_back(formatPtrArg(prefSerializer.GetPrefLength()).get()); extraArgs.push_back("-prefMapSize"); extraArgs.push_back(formatPtrArg(prefSerializer.GetPrefMapSize()).get()); mLaunchPhase = LaunchPhase::Waiting; if (!GeckoChildProcessHost::LaunchAndWaitForProcessHandle(extraArgs)) { mLaunchPhase = LaunchPhase::Complete; return false; } return true; } void SocketProcessHost::OnChannelConnected(int32_t peer_pid) { MOZ_ASSERT(!NS_IsMainThread()); GeckoChildProcessHost::OnChannelConnected(peer_pid); // Post a task to the main thread. Take the lock because mTaskFactory is not // thread-safe. RefPtr runnable; { MonitorAutoLock lock(mMonitor); runnable = mTaskFactory.NewRunnableMethod( &SocketProcessHost::OnChannelConnectedTask); } NS_DispatchToMainThread(runnable); } void SocketProcessHost::OnChannelError() { MOZ_ASSERT(!NS_IsMainThread()); GeckoChildProcessHost::OnChannelError(); // Post a task to the main thread. Take the lock because mTaskFactory is not // thread-safe. RefPtr runnable; { MonitorAutoLock lock(mMonitor); runnable = mTaskFactory.NewRunnableMethod(&SocketProcessHost::OnChannelErrorTask); } NS_DispatchToMainThread(runnable); } void SocketProcessHost::OnChannelConnectedTask() { MOZ_ASSERT(NS_IsMainThread()); if (mLaunchPhase == LaunchPhase::Waiting) { InitAfterConnect(true); } } void SocketProcessHost::OnChannelErrorTask() { MOZ_ASSERT(NS_IsMainThread()); if (mLaunchPhase == LaunchPhase::Waiting) { InitAfterConnect(false); } } void SocketProcessHost::InitAfterConnect(bool aSucceeded) { MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting); MOZ_ASSERT(!mSocketProcessParent); MOZ_ASSERT(NS_IsMainThread()); mLaunchPhase = LaunchPhase::Complete; if (aSucceeded) { mSocketProcessParent = MakeUnique(this); DebugOnly rv = mSocketProcessParent->Open( GetChannel(), base::GetProcId(GetChildProcessHandle())); MOZ_ASSERT(rv); nsCOMPtr ioService(do_GetIOService()); MOZ_ASSERT(ioService, "No IO service?"); bool offline = false; DebugOnly result = ioService->GetOffline(&offline); MOZ_ASSERT(NS_SUCCEEDED(result), "Failed getting offline?"); #ifdef MOZ_GECKO_PROFILER Unused << GetActor()->SendInitProfiler( ProfilerParent::CreateForProcess(GetActor()->OtherPid())); #endif Unused << GetActor()->SendSetOffline(offline); mOfflineObserver = new OfflineObserver(this); } if (mListener) { mListener->OnProcessLaunchComplete(this, aSucceeded); } } void SocketProcessHost::Shutdown() { MOZ_ASSERT(!mShutdownRequested); MOZ_ASSERT(NS_IsMainThread()); mListener = nullptr; if (mOfflineObserver) { mOfflineObserver->Destroy(); mOfflineObserver = nullptr; } if (mSocketProcessParent) { // OnChannelClosed uses this to check if the shutdown was expected or // unexpected. mShutdownRequested = true; // The channel might already be closed if we got here unexpectedly. if (!mChannelClosed) { mSocketProcessParent->Close(); } return; } DestroyProcess(); } void SocketProcessHost::OnChannelClosed() { MOZ_ASSERT(NS_IsMainThread()); mChannelClosed = true; if (!mShutdownRequested && mListener) { // This is an unclean shutdown. Notify our listener that we're going away. mListener->OnProcessUnexpectedShutdown(this); } else { DestroyProcess(); } // Release the actor. SocketProcessParent::Destroy(std::move(mSocketProcessParent)); MOZ_ASSERT(!mSocketProcessParent); } void SocketProcessHost::DestroyProcess() { { MonitorAutoLock lock(mMonitor); mTaskFactory.RevokeAll(); } MessageLoop::current()->PostTask(NS_NewRunnableFunction( "DestroySocketProcessRunnable", [this] { Destroy(); })); } //----------------------------------------------------------------------------- // SocketProcessMemoryReporter //----------------------------------------------------------------------------- bool SocketProcessMemoryReporter::IsAlive() const { MOZ_ASSERT(gIOService); if (!gIOService->mSocketProcess) { return false; } return gIOService->mSocketProcess->IsConnected(); } bool SocketProcessMemoryReporter::SendRequestMemoryReport( const uint32_t& aGeneration, const bool& aAnonymize, const bool& aMinimizeMemoryUsage, const dom::MaybeFileDesc& aDMDFile) { MOZ_ASSERT(gIOService); if (!gIOService->mSocketProcess) { return false; } SocketProcessParent* actor = gIOService->mSocketProcess->GetActor(); if (!actor) { return false; } return actor->SendRequestMemoryReport(aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile); } int32_t SocketProcessMemoryReporter::Pid() const { MOZ_ASSERT(gIOService); if (!gIOService->mSocketProcess) { return 0; } if (SocketProcessParent* actor = gIOService->mSocketProcess->GetActor()) { return (int32_t)actor->OtherPid(); } return 0; } } // namespace net } // namespace mozilla