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:
David Teller 2020-09-16 10:03:33 +00:00
Родитель d3bbed0006
Коммит d76fedbfbb
13 изменённых файлов: 316 добавлений и 0 удалений

11
Cargo.lock сгенерированный
Просмотреть файл

@ -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;