зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1635494 - nsIProcessToolsService;r=lina
A small xpcom module implemented in Rust, designed to provide low-level tools to deal with processes from JS. For the moment, the only notable feature is `kill()`, designed to be used from about:processes Differential Revision: https://phabricator.services.mozilla.com/D82552
This commit is contained in:
Родитель
d3bbed0006
Коммит
d76fedbfbb
|
@ -1994,6 +1994,7 @@ dependencies = [
|
|||
"nserror",
|
||||
"nsstring",
|
||||
"prefs_parser",
|
||||
"processtools",
|
||||
"profiler_helper",
|
||||
"remote",
|
||||
"rlbox_lucet_sandbox",
|
||||
|
@ -3824,6 +3825,16 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
|
||||
|
||||
[[package]]
|
||||
name = "processtools"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"nserror",
|
||||
"winapi 0.3.7",
|
||||
"xpcom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiler_helper"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -56,6 +56,7 @@ DIRS += [
|
|||
'perfmonitoring',
|
||||
'pictureinpicture',
|
||||
'places',
|
||||
'processtools',
|
||||
'processsingleton',
|
||||
'promiseworker',
|
||||
'prompts',
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "processtools"
|
||||
version = "0.1.0"
|
||||
authors = ["David Teller <dteller@mozilla.com>"]
|
||||
|
||||
[dependencies]
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
xpcom = { path = "../../../xpcom/rust/xpcom" }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.3.7"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
|
@ -0,0 +1,33 @@
|
|||
/* 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 "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "ProcessToolsService.h"
|
||||
|
||||
// This anonymous namespace prevents outside C++ code from improperly accessing
|
||||
// these implementation details.
|
||||
namespace {
|
||||
extern "C" {
|
||||
// Implemented in Rust.
|
||||
void new_process_tools_service(nsIProcessToolsService** result);
|
||||
}
|
||||
|
||||
static mozilla::StaticRefPtr<nsIProcessToolsService> sProcessToolsService;
|
||||
} // namespace
|
||||
|
||||
already_AddRefed<nsIProcessToolsService> GetProcessToolsService() {
|
||||
nsCOMPtr<nsIProcessToolsService> processToolsService;
|
||||
|
||||
if (sProcessToolsService) {
|
||||
processToolsService = sProcessToolsService;
|
||||
} else {
|
||||
new_process_tools_service(getter_AddRefs(processToolsService));
|
||||
sProcessToolsService = processToolsService;
|
||||
mozilla::ClearOnShutdown(&sProcessToolsService);
|
||||
}
|
||||
|
||||
return processToolsService.forget();
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/* -*- 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 "nsIProcessToolsService.h"
|
||||
|
||||
already_AddRefed<nsIProcessToolsService> GetProcessToolsService();
|
|
@ -0,0 +1,10 @@
|
|||
Classes = [
|
||||
{
|
||||
'cid': '{79A13656-A472-4713-B0E1-AB39A15CF790}',
|
||||
'contract_ids': ["@mozilla.org/processtools-service;1"],
|
||||
'type': 'nsIProcessToolsService',
|
||||
'constructor': 'GetProcessToolsService',
|
||||
'singleton': True,
|
||||
'headers': ['ProcessToolsService.h'],
|
||||
}
|
||||
]
|
|
@ -0,0 +1,30 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Core', 'DOM: Content Processes')
|
||||
|
||||
XPIDL_MODULE = 'toolkit_processtools'
|
||||
|
||||
XPCOM_MANIFESTS += [
|
||||
'components.conf',
|
||||
]
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIProcessToolsService.idl',
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
'ProcessToolsService.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'ProcessToolsService.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
|
|
@ -0,0 +1,40 @@
|
|||
/* 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 "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(1341f571-ebed-4305-b264-4d8fc3b6b11c)]
|
||||
interface nsIProcessToolsService: nsISupports {
|
||||
/**
|
||||
* Kill a process running on this system.
|
||||
*
|
||||
* Does not cause a crash report to be generated and sent.
|
||||
*
|
||||
* # Note
|
||||
*
|
||||
* `pid` is the unique-to-the-system process identifier, as
|
||||
* obtained with attribute `pid` of this service.
|
||||
*
|
||||
* Under Un*ix, that's what you obtain with `getpid()`, etc.
|
||||
* Under Windows, that's what you obtain with `GetCurrentProcessId()`,
|
||||
* NOT the same thing as the process `HANDLE`.
|
||||
*
|
||||
* # Failure
|
||||
*
|
||||
* Under Windows, if two processes race to `kill()` a third process,
|
||||
* or two threads race to `kill()` a process there is a (small) window
|
||||
* during which this can cause a crash in the losing process.
|
||||
*
|
||||
* # Caveats
|
||||
*
|
||||
* Under Windows, process killing is asynchronous. Therefore, this
|
||||
* function can return before process `pid` is actually dead.
|
||||
*/
|
||||
void kill(in unsigned long long pid);
|
||||
|
||||
/**
|
||||
* The pid for the current process.
|
||||
*/
|
||||
readonly attribute unsigned long long pid;
|
||||
};
|
|
@ -0,0 +1,103 @@
|
|||
/* 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/. */
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
extern crate libc;
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winapi;
|
||||
|
||||
extern crate nserror;
|
||||
extern crate xpcom;
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use nserror::{nsresult, NS_ERROR_FAILURE, NS_OK};
|
||||
use xpcom::{interfaces::nsIProcessToolsService, xpcom, xpcom_method, RefPtr};
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn new_process_tools_service(result: *mut *const nsIProcessToolsService) {
|
||||
let service: RefPtr<ProcessToolsService> = ProcessToolsService::new();
|
||||
RefPtr::new(service.coerce::<nsIProcessToolsService>()).forget(&mut *result);
|
||||
}
|
||||
|
||||
// Implementation note:
|
||||
//
|
||||
// We're following the strategy employed by the `kvstore`.
|
||||
// See https://searchfox.org/mozilla-central/rev/a87a1c3b543475276e6d57a7a80cb02f3e42b6ed/toolkit/components/kvstore/src/lib.rs#78
|
||||
|
||||
#[derive(xpcom)]
|
||||
#[refcnt = "atomic"]
|
||||
#[xpimplements(nsIProcessToolsService)]
|
||||
pub struct InitProcessToolsService {}
|
||||
|
||||
impl ProcessToolsService {
|
||||
pub fn new() -> RefPtr<ProcessToolsService> {
|
||||
ProcessToolsService::allocate(InitProcessToolsService {})
|
||||
}
|
||||
|
||||
// Method `kill`.
|
||||
|
||||
xpcom_method!(
|
||||
kill => Kill(id: u64)
|
||||
);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn kill(&self, pid: u64) -> Result<(), nsresult> {
|
||||
let handle = unsafe {
|
||||
winapi::um::processthreadsapi::OpenProcess(
|
||||
/* dwDesiredAccess */
|
||||
winapi::um::winnt::PROCESS_TERMINATE | winapi::um::winnt::SYNCHRONIZE,
|
||||
/* bInheritHandle */ 0,
|
||||
/* dwProcessId */ pid.try_into().unwrap(),
|
||||
)
|
||||
};
|
||||
if handle.is_null() {
|
||||
// Could not open process.
|
||||
return Err(NS_ERROR_NOT_AVAILABLE);
|
||||
}
|
||||
|
||||
let result = unsafe {
|
||||
winapi::um::processthreadsapi::TerminateProcess(
|
||||
/* hProcess */ handle, /* uExitCode */ 0,
|
||||
)
|
||||
};
|
||||
|
||||
// Close handle regardless of success.
|
||||
let _ = unsafe { winapi::um::handleapi::CloseHandle(handle) };
|
||||
|
||||
if result == 0 {
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn kill(&self, pid: u64) -> Result<(), nsresult> {
|
||||
let pid = pid.try_into().or(Err(NS_ERROR_FAILURE))?;
|
||||
let result = unsafe { libc::kill(pid, libc::SIGKILL) };
|
||||
if result == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(NS_ERROR_FAILURE)
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute `pid`
|
||||
|
||||
xpcom_method!(
|
||||
get_pid => GetPid() -> u64
|
||||
);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn get_pid(&self) -> Result<u64, nsresult> {
|
||||
let pid = unsafe { libc::getpid() } as u64;
|
||||
Ok(pid)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn get_pid(&self) -> Result<u64, nsresult> {
|
||||
let pid = unsafe { winapi::um::processthreadsapi::GetCurrentProcessId() } as u64;
|
||||
Ok(pid)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
"use strict";
|
||||
|
||||
const { AppConstants } = ChromeUtils.import(
|
||||
"resource://gre/modules/AppConstants.jsm"
|
||||
);
|
||||
const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
const { Subprocess } = ChromeUtils.import(
|
||||
"resource://gre/modules/Subprocess.jsm"
|
||||
);
|
||||
|
||||
const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
|
||||
Ci.nsIProcessToolsService
|
||||
);
|
||||
|
||||
const env = Cc["@mozilla.org/process/environment;1"].getService(
|
||||
Ci.nsIEnvironment
|
||||
);
|
||||
|
||||
let PYTHON;
|
||||
|
||||
// Find Python.
|
||||
add_task(async function setup() {
|
||||
PYTHON = await Subprocess.pathSearch(env.get("PYTHON"));
|
||||
});
|
||||
|
||||
// Ensure that killing a process... kills the process.
|
||||
add_task(async function test_subprocess_kill() {
|
||||
// We launch Python, as it's a long-running process and it exists
|
||||
// on all desktop platforms on which we run tests.
|
||||
let proc = await Subprocess.call({
|
||||
command: PYTHON,
|
||||
arguments: [],
|
||||
});
|
||||
|
||||
let isTerminated = false;
|
||||
|
||||
proc.wait().then(() => {
|
||||
isTerminated = true;
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
Assert.ok(
|
||||
!isTerminated,
|
||||
"We haven't killed the process yet, it should still be running."
|
||||
);
|
||||
|
||||
// Time to kill the process.
|
||||
ProcessTools.kill(proc.pid);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
Assert.ok(
|
||||
isTerminated,
|
||||
"We have killed the process already, it shouldn't be running anymore."
|
||||
);
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
[DEFAULT]
|
||||
firefox-appdir = browser
|
||||
skip-if = os == 'android'
|
||||
subprocess = true
|
||||
|
||||
[test_process_kill.js]
|
|
@ -66,6 +66,8 @@ fluent = { version = "0.12" , features = ["fluent-pseudo"] }
|
|||
fluent-ffi = { path = "../../../../intl/l10n/rust/fluent-ffi" }
|
||||
firefox-accounts-bridge = { path = "../../../../services/fxaccounts/rust-bridge/firefox-accounts-bridge", optional=true }
|
||||
|
||||
processtools = { path = "../../../components/processtools" }
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
viaduct = { git = "https://github.com/mozilla/application-services", rev = "9ba519a5739b1976f1d333923d34b7f4916b9e26"}
|
||||
webext_storage_bridge = { path = "../../../components/extensions/storage/webext_storage_bridge" }
|
||||
|
|
|
@ -41,6 +41,7 @@ extern crate netwerk_helper;
|
|||
extern crate nserror;
|
||||
extern crate nsstring;
|
||||
extern crate prefs_parser;
|
||||
extern crate processtools;
|
||||
#[cfg(feature = "gecko_profiler")]
|
||||
extern crate profiler_helper;
|
||||
extern crate rsdparsa_capi;
|
||||
|
|
Загрузка…
Ссылка в новой задаче