зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1681574: Remove the old update agent skeleton. r=nalexander
I'm keeping the --enable-update-agent config option and the corresponding MOZ_UPDATE_AGENT config flag and define, as these should still be useful. As we never shipped this there is no need to keep anything around to clean up the scheduled tasks. Differential Revision: https://phabricator.services.mozilla.com/D99574
This commit is contained in:
Родитель
f4c5f128e9
Коммит
f949e155a3
|
@ -5516,26 +5516,6 @@ dependencies = [
|
|||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "updateagent"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bits_client",
|
||||
"cfg-if 0.1.10",
|
||||
"chrono",
|
||||
"comedy",
|
||||
"failure",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"prefs_parser",
|
||||
"raw-cpuid",
|
||||
"rust-ini",
|
||||
"winapi 0.3.9",
|
||||
"wineventlog",
|
||||
"wio",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.1.0"
|
||||
|
|
|
@ -13,7 +13,6 @@ members = [
|
|||
"netwerk/test/http3server",
|
||||
"security/manager/ssl/osclientcerts",
|
||||
"testing/geckodriver",
|
||||
"toolkit/components/updateagent",
|
||||
"toolkit/crashreporter/rust",
|
||||
"toolkit/library/gtest/rust",
|
||||
"toolkit/library/rust/",
|
||||
|
|
|
@ -410,11 +410,6 @@ bin/libfreebl_64int_3.so
|
|||
@BINPATH@/maintenanceservice_installer.exe
|
||||
#endif
|
||||
|
||||
; [Background Update Agent]
|
||||
#ifdef MOZ_UPDATE_AGENT
|
||||
@BINPATH@/updateagent@BIN_SUFFIX@
|
||||
#endif
|
||||
|
||||
; [Crash Reporter]
|
||||
;
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
|
|
|
@ -118,11 +118,6 @@
|
|||
!define MOZ_MAINTENANCE_SERVICE
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_UPDATE_AGENT
|
||||
!define MOZ_UPDATE_AGENT
|
||||
!define UpdateAgentFullName "Mozilla Update Agent"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_BITS_DOWNLOAD
|
||||
!define MOZ_BITS_DOWNLOAD
|
||||
#endif
|
||||
|
|
|
@ -497,17 +497,6 @@ Section "-Application" APP_IDX
|
|||
${EndIf}
|
||||
!endif
|
||||
|
||||
!ifdef MOZ_UPDATE_AGENT
|
||||
${PushRegisterUpdateAgentTaskCommand} "register"
|
||||
Pop $0
|
||||
${If} "$0" != ""
|
||||
${LogMsg} "Registering update agent task: $0"
|
||||
nsExec::Exec $0
|
||||
Pop $0
|
||||
${LogMsg} "nsExec::Exec returned $0"
|
||||
${EndIf}
|
||||
!endif
|
||||
|
||||
; These need special handling on uninstall since they may be overwritten by
|
||||
; an install into a different location.
|
||||
StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\App Paths\${FileMainEXE}"
|
||||
|
|
|
@ -159,25 +159,6 @@
|
|||
${EndIf}
|
||||
!endif
|
||||
|
||||
!ifdef MOZ_UPDATE_AGENT
|
||||
; This macro runs the update agent with the update-task-local-service
|
||||
; command, if it detects the needed admin privileges. Otherwise it
|
||||
; runs with update-task.
|
||||
; Both commands attempt to remove the scheduled task, then register
|
||||
; a new one. If the task was registered by an elevated user, it won't
|
||||
; be removable when not elevated, so the unelevated attempt will fail
|
||||
; harmlessly.
|
||||
; Therefore it is safe to run this in both elevated and nonelevated
|
||||
; PostUpdate: The highest privileged run will win out, so the task can
|
||||
; run as Local Service if it was ever possible to register it that way.
|
||||
${PushRegisterUpdateAgentTaskCommand} "update"
|
||||
Pop $0
|
||||
${If} "$0" != ""
|
||||
nsExec::Exec $0
|
||||
Pop $0
|
||||
${EndIf}
|
||||
!endif
|
||||
|
||||
!ifdef MOZ_LAUNCHER_PROCESS
|
||||
${ResetLauncherProcessDefaults}
|
||||
!endif
|
||||
|
@ -1512,7 +1493,6 @@ ${RemoveDefaultBrowserAgentShortcut}
|
|||
Push "minidump-analyzer.exe"
|
||||
Push "pingsender.exe"
|
||||
Push "updater.exe"
|
||||
Push "updateagent.exe"
|
||||
Push "${FileMainEXE}"
|
||||
!macroend
|
||||
!define PushFilesToCheck "!insertmacro PushFilesToCheck"
|
||||
|
@ -1792,44 +1772,3 @@ FunctionEnd
|
|||
!macroend
|
||||
!define ResetLauncherProcessDefaults "!insertmacro ResetLauncherProcessDefaults"
|
||||
!endif
|
||||
|
||||
!ifdef MOZ_UPDATE_AGENT
|
||||
; Push, onto the stack, the command line used to register (or update) the
|
||||
; update agent scheduled task.
|
||||
;
|
||||
; InitHashAppModelId must have already been called to set $AppUserModelID,
|
||||
; if that is empty then an empty string will be pushed instead.
|
||||
;
|
||||
; COMMAND_BASE must be "register" or "update". Both will remove any
|
||||
; pre-existing task and register a new one, but "update" will first attempt
|
||||
; to copy some settings.
|
||||
!macro PushRegisterUpdateAgentTaskCommand COMMAND_BASE
|
||||
Push $0
|
||||
Push $1
|
||||
|
||||
Call IsUserAdmin
|
||||
Pop $0
|
||||
; Register the update agent to run as Local Service if the user is an admin...
|
||||
${If} $0 == "true"
|
||||
; ...and if we have HKLM write access
|
||||
${AndIf} $TmpVal == "HKLM"
|
||||
StrCpy $1 "${COMMAND_BASE}-task-local-service"
|
||||
${Else}
|
||||
; Otherwise attempt to register the task for the current user.
|
||||
; If we had previously registered the task while elevated, then we shouldn't
|
||||
; be able to replace it now with another task of the same name, so this
|
||||
; will fail harmlessly.
|
||||
StrCpy $1 "${COMMAND_BASE}-task"
|
||||
${EndIf}
|
||||
|
||||
${If} "$AppUserModelID" != ""
|
||||
StrCpy $0 '"$INSTDIR\updateagent.exe" $1 "${UpdateAgentFullName} $AppUserModelID" "$AppUserModelID" "$INSTDIR"'
|
||||
${Else}
|
||||
StrCpy $0 ''
|
||||
${EndIf}
|
||||
|
||||
Pop $1
|
||||
Exch $0
|
||||
!macroend
|
||||
!define PushRegisterUpdateAgentTaskCommand "!insertmacro PushRegisterUpdateAgentTaskCommand"
|
||||
!endif
|
||||
|
|
|
@ -595,11 +595,6 @@ Section "Uninstall"
|
|||
DeleteRegValue HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Telemetry"
|
||||
!endif
|
||||
|
||||
!ifdef MOZ_UPDATE_AGENT
|
||||
; Unregister the update agent
|
||||
nsExec::Exec '"$INSTDIR\updateagent.exe" unregister-task "${UpdateAgentFullName} $AppUserModelID"'
|
||||
!endif
|
||||
|
||||
; Uninstall the default browser agent scheduled task.
|
||||
; This also removes the registry entries it creates.
|
||||
ExecWait '"$INSTDIR\default-browser-agent.exe" uninstall $AppUserModelID'
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
[package]
|
||||
name = "updateagent"
|
||||
version = "0.1.0"
|
||||
authors = ["The Mozilla Project Developers"]
|
||||
license = "MPL-2.0"
|
||||
autobins = false
|
||||
edition = "2018"
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
bits_client = { path = "../bitsdownload/bits_client"}
|
||||
cfg-if = "0.1"
|
||||
chrono = "0.4"
|
||||
comedy = "0.1"
|
||||
failure = "0.1"
|
||||
percent-encoding = "2.1"
|
||||
prefs_parser = { path = "../../../modules/libpref/parser" }
|
||||
rust-ini = "0.10"
|
||||
wineventlog = { path = "wineventlog"}
|
||||
wio = "0.2"
|
||||
xml-rs = "0.8"
|
||||
|
||||
[target."cfg(windows)".dependencies.log]
|
||||
version = "0.4"
|
||||
features = ["std"]
|
||||
|
||||
[target."cfg(all(windows, any(target_arch = \"x86\", target_arch = \"x86_64\")))".dependencies]
|
||||
raw-cpuid = "7.0"
|
||||
|
||||
[target."cfg(windows)".dependencies.winapi]
|
||||
version = "0.3.7"
|
||||
features = ["errhandlingapi", "minwindef", "ntdef", "oaidl", "oleauto", "sysinfoapi", "taskschd", "winbase", "winerror", "winnt", "winreg", "wtypes"]
|
||||
|
||||
[[bin]]
|
||||
name = "updateagent"
|
||||
path = "src/main.rs"
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env 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/.
|
||||
import buildconfig
|
||||
|
||||
|
||||
def main(output):
|
||||
output.write(
|
||||
"/* THIS FILE IS GENERATED BY UpdateUrlConstants.py - DO NOT EDIT */\n\n"
|
||||
)
|
||||
|
||||
OS_TARGET = buildconfig.substs["OS_TARGET"]
|
||||
output.write('pub const OS_TARGET: &str = "{}";\n'.format(OS_TARGET))
|
||||
|
||||
TARGET_XPCOM_ABI = buildconfig.substs["TARGET_XPCOM_ABI"]
|
||||
output.write('pub const TARGET_XPCOM_ABI: &str = "{}";\n'.format(TARGET_XPCOM_ABI))
|
||||
|
||||
MOZ_ASAN = "true" if buildconfig.substs.get("MOZ_ASAN") else "false"
|
||||
output.write("pub const MOZ_ASAN: bool = {};\n".format(MOZ_ASAN))
|
||||
|
||||
GRE_MILESTONE = buildconfig.substs["GRE_MILESTONE"]
|
||||
output.write('pub const GRE_MILESTONE: &str = "{}";\n'.format(GRE_MILESTONE))
|
|
@ -1,13 +0,0 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rustc-link-lib=shell32");
|
||||
println!("cargo:rustc-link-lib=shlwapi");
|
||||
println!("cargo:rustc-link-lib=rpcrt4");
|
||||
println!(
|
||||
"cargo:rustc-link-search={}/toolkit/mozapps/update/common",
|
||||
env!("MOZ_TOPOBJDIR")
|
||||
);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
RUST_PROGRAMS += ["updateagent"]
|
||||
RCINCLUDE = "updateagent.rc"
|
||||
|
||||
GENERATED_FILES += ["url_constants.rs"]
|
||||
GENERATED_FILES["url_constants.rs"].script = "UpdateUrlConstants.py"
|
||||
|
||||
with Files("**"):
|
||||
BUG_COMPONENT = ("Toolkit", "Application Update")
|
|
@ -1,173 +0,0 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This code is Windows-specific, don't build this module for another platform.
|
||||
#![cfg(windows)]
|
||||
// We want to use the "windows" subsystem to avoid popping up a console window. This also
|
||||
// prevents Windows consoles from picking up output, however, so it is disabled when debugging.
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod ole_utils;
|
||||
mod task_setup;
|
||||
pub mod taskschd;
|
||||
mod update_paths;
|
||||
mod update_xml;
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::process;
|
||||
|
||||
use comedy::com::ComApartmentScope;
|
||||
use log::{debug, error, info};
|
||||
|
||||
// Used as the name of the task folder and author of the task
|
||||
pub static VENDOR: &str = "Mozilla";
|
||||
// Used as the description of the task and the event log application
|
||||
pub static DESCRIPTION: &str = "Mozilla Update Agent";
|
||||
static BITS_JOB_NAME_PREFIX: &str = "MozillaUpdate ";
|
||||
|
||||
fn main() {
|
||||
let logger = wineventlog::EventLogger::new(DESCRIPTION);
|
||||
log::set_boxed_logger(Box::new(logger)).unwrap();
|
||||
log::set_max_level(log::LevelFilter::Info);
|
||||
|
||||
// TODO: Appropriate threading and security settings will depend on the main work the task
|
||||
// will be doing.
|
||||
let _com = ComApartmentScope::init_mta().unwrap();
|
||||
|
||||
process::exit(match fallible_main() {
|
||||
Ok(_) => {
|
||||
debug!("success");
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Command types
|
||||
pub mod cmd {
|
||||
// Create a new task, removing any old one.
|
||||
// `updateagent.exe register-task TaskName [TaskArgs ...]`
|
||||
pub static REGISTER_TASK: &str = "register-task";
|
||||
pub static REGISTER_TASK_LOCAL_SERVICE: &str = "register-task-local-service";
|
||||
|
||||
// Create a new task, removing any old one, but copying its schedule as appropriate.
|
||||
pub static UPDATE_TASK: &str = "update-task";
|
||||
pub static UPDATE_TASK_LOCAL_SERVICE: &str = "update-task-local-service";
|
||||
|
||||
// Remove the task.
|
||||
pub static UNREGISTER_TASK: &str = "unregister-task";
|
||||
|
||||
// Request to be run immediately by Task Scheduler.
|
||||
pub static RUN_ON_DEMAND: &str = "run-on-demand";
|
||||
|
||||
// The task is set up to execute this command, using the TaskArgs from registration.
|
||||
// `updateagent.exe do-task [TaskArgs ...]`
|
||||
pub static DO_TASK: &str = "do-task";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Command {
|
||||
RegisterTask,
|
||||
RegisterTaskLocalService,
|
||||
UpdateTask,
|
||||
UpdateTaskLocalService,
|
||||
UnregisterTask,
|
||||
RunOnDemand,
|
||||
DoTask,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn parse(s: &str) -> Option<Command> {
|
||||
use Command::*;
|
||||
|
||||
// Build a map to lookup the string. This only runs once so performance isn't critical.
|
||||
let lookup_map: std::collections::HashMap<_, _> = [
|
||||
(REGISTER_TASK, RegisterTask),
|
||||
(REGISTER_TASK_LOCAL_SERVICE, RegisterTaskLocalService),
|
||||
(UPDATE_TASK, UpdateTask),
|
||||
(UPDATE_TASK_LOCAL_SERVICE, UpdateTaskLocalService),
|
||||
(UNREGISTER_TASK, UnregisterTask),
|
||||
(RUN_ON_DEMAND, RunOnDemand),
|
||||
(DO_TASK, DoTask),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
lookup_map.get(s).cloned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use cmd::Command;
|
||||
|
||||
pub fn fallible_main() -> Result<(), String> {
|
||||
let args_os: Vec<_> = env::args_os().collect();
|
||||
|
||||
let command = {
|
||||
let command_str = args_os
|
||||
.get(1)
|
||||
.ok_or_else(|| String::from("missing command"))?
|
||||
.to_string_lossy()
|
||||
.to_owned();
|
||||
|
||||
Command::parse(&command_str)
|
||||
.ok_or_else(|| format!("unknown command \"{}\"", command_str))?
|
||||
};
|
||||
|
||||
// all commands except DoTask take TaskName as args_os[2]
|
||||
let maybe_task_name = args_os
|
||||
.get(2)
|
||||
.ok_or_else(|| String::from("missing TaskName"));
|
||||
|
||||
match command {
|
||||
Command::RegisterTask
|
||||
| Command::RegisterTaskLocalService
|
||||
| Command::UpdateTask
|
||||
| Command::UpdateTaskLocalService => {
|
||||
let exe = env::current_exe().map_err(|e| format!("get current exe failed: {}", e))?;
|
||||
let task_name = maybe_task_name?;
|
||||
|
||||
task_setup::register(&*exe, task_name, &args_os[3..], command)
|
||||
.map_err(|e| format!("register failed: {}", e))
|
||||
}
|
||||
Command::UnregisterTask => {
|
||||
if args_os.len() != 3 {
|
||||
return Err("unregister-task takes only one argument: TaskName".into());
|
||||
}
|
||||
|
||||
task_setup::unregister(maybe_task_name?)
|
||||
.map_err(|e| format!("unregister failed: {}", e))
|
||||
}
|
||||
Command::RunOnDemand => {
|
||||
if args_os.len() != 3 {
|
||||
return Err("run-on-demand takes only one argument: TaskName".into());
|
||||
}
|
||||
|
||||
task_setup::run_on_demand(maybe_task_name?)
|
||||
.map_err(|e| format!("run on demand failed: {}", e))
|
||||
}
|
||||
Command::DoTask => task_action(&args_os[2..]),
|
||||
}
|
||||
}
|
||||
|
||||
fn task_action(args: &[OsString]) -> Result<(), String> {
|
||||
info!("task_action({:?})", args);
|
||||
|
||||
let download_dir = update_paths::get_update_directory()?;
|
||||
let xml_path = update_paths::get_download_xml()?;
|
||||
let mut job_name = OsString::from(BITS_JOB_NAME_PREFIX);
|
||||
job_name.push(update_paths::get_install_hash()?);
|
||||
|
||||
update_xml::sync_download(download_dir, xml_path.clone(), job_name)?;
|
||||
let updates = update_xml::parse_file(&xml_path)?;
|
||||
info!("Got {} updates!", updates.len());
|
||||
|
||||
// TODO: Process updates
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::OsStr;
|
||||
use std::mem;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::ptr::NonNull;
|
||||
use std::slice;
|
||||
|
||||
use winapi::shared::{winerror, wtypes};
|
||||
use winapi::um::{oaidl, oleauto};
|
||||
|
||||
use comedy::HResult;
|
||||
|
||||
/// Conveniently create and destroy a `BSTR`.
|
||||
///
|
||||
/// This is called `BString` by analogy to `String` since it owns the data.
|
||||
///
|
||||
/// The internal `BSTR` is always non-null, even for an empty string, for simple safety reasons.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BString(NonNull<u16>);
|
||||
|
||||
impl BString {
|
||||
pub fn from_slice(v: impl AsRef<[u16]>) -> Result<BString, HResult> {
|
||||
let v = v.as_ref();
|
||||
let real_len = v.len();
|
||||
let len = real_len
|
||||
.try_into()
|
||||
.map_err(|_| HResult::new(winerror::E_OUTOFMEMORY))?;
|
||||
let bs = unsafe { oleauto::SysAllocStringLen(v.as_ptr(), len) };
|
||||
|
||||
Ok(Self(NonNull::new(bs).ok_or_else(|| {
|
||||
HResult::new(winerror::E_OUTOFMEMORY).function("SysAllocStringLen")
|
||||
})?))
|
||||
}
|
||||
|
||||
pub fn from_os_str(s: impl AsRef<OsStr>) -> Result<BString, HResult> {
|
||||
BString::from_slice(s.as_ref().encode_wide().collect::<Vec<_>>().as_slice())
|
||||
}
|
||||
|
||||
/// Take ownership of a `BSTR`.
|
||||
///
|
||||
/// This will be freed when the `BString` is dropped, so the pointer shouldn't be used
|
||||
/// after calling this function.
|
||||
///
|
||||
/// Returns `None` if the pointer is null; though this means an empty string in most
|
||||
/// contexts where `BSTR` is used, `BString` is always non-null.
|
||||
pub unsafe fn from_raw(p: *mut u16) -> Option<Self> {
|
||||
Some(Self(NonNull::new(p)?))
|
||||
}
|
||||
|
||||
/// Get a pointer to the `BSTR`.
|
||||
///
|
||||
/// The caller must ensure that the `BString` outlives the pointer this function returns,
|
||||
/// or else it will end up pointing to garbage.
|
||||
///
|
||||
/// This pointer shouldn't be written to, but most APIs require a mutable pointer.
|
||||
pub fn as_raw_ptr(&self) -> *mut u16 {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
|
||||
/// Build a raw `VARIANT`, essentially a typed pointer.
|
||||
///
|
||||
/// The caller must ensure that the `BString` outlives the `VARIANT` this function returns,
|
||||
/// or else it will end up pointing to garbage.
|
||||
///
|
||||
/// This is meant for passing by value to Windows APIs.
|
||||
pub fn as_raw_variant(&self) -> oaidl::VARIANT {
|
||||
unsafe {
|
||||
let mut v: oaidl::VARIANT = mem::zeroed();
|
||||
{
|
||||
let tv = v.n1.n2_mut();
|
||||
*tv.n3.bstrVal_mut() = self.as_raw_ptr();
|
||||
tv.vt = wtypes::VT_BSTR as wtypes::VARTYPE;
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BString {
|
||||
fn drop(&mut self) {
|
||||
unsafe { oleauto::SysFreeString(self.0.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u16]> for BString {
|
||||
fn as_ref(&self) -> &[u16] {
|
||||
unsafe {
|
||||
let len = oleauto::SysStringLen(self.0.as_ptr());
|
||||
|
||||
slice::from_raw_parts(self.0.as_ptr(), len as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to convert, decorate `Err` with call site info
|
||||
#[macro_export]
|
||||
macro_rules! try_to_bstring {
|
||||
($ex:expr) => {
|
||||
$crate::ole_utils::BString::from_os_str($ex).map_err(|e| e.file_line(file!(), line!()))
|
||||
};
|
||||
}
|
||||
|
||||
pub fn empty_variant() -> oaidl::VARIANT {
|
||||
unsafe {
|
||||
let mut v: oaidl::VARIANT = mem::zeroed();
|
||||
{
|
||||
let tv = v.n1.n2_mut();
|
||||
tv.vt = wtypes::VT_EMPTY as wtypes::VARTYPE;
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OptionBstringExt {
|
||||
fn as_raw_variant(&self) -> oaidl::VARIANT;
|
||||
}
|
||||
|
||||
/// Shorthand for unwrapping, returns `BString::as_raw_variant()` or `empty_variant()`
|
||||
impl OptionBstringExt for Option<&BString> {
|
||||
fn as_raw_variant(&self) -> oaidl::VARIANT {
|
||||
self.map(|bs| bs.as_raw_variant())
|
||||
.unwrap_or_else(empty_variant)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: A `VARIANT_BOOL` is not a `VARIANT`, rather it would go into a `VARIANT` of type
|
||||
// `VT_BOOL`. Some APIs use it directly.
|
||||
pub trait IntoVariantBool {
|
||||
fn into_variant_bool(self) -> wtypes::VARIANT_BOOL;
|
||||
}
|
||||
|
||||
impl IntoVariantBool for bool {
|
||||
fn into_variant_bool(self) -> wtypes::VARIANT_BOOL {
|
||||
if self {
|
||||
wtypes::VARIANT_TRUE
|
||||
} else {
|
||||
wtypes::VARIANT_FALSE
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::cmd::Command;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::Path;
|
||||
|
||||
use comedy::HResult;
|
||||
use log::warn;
|
||||
|
||||
use crate::cmd;
|
||||
use crate::ole_utils::BString;
|
||||
use crate::taskschd::{hr_is_not_found, TaskService};
|
||||
use crate::try_to_bstring;
|
||||
|
||||
fn folder_name() -> Result<BString, HResult> {
|
||||
try_to_bstring!(crate::VENDOR)
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
exe: &Path,
|
||||
name: &OsStr,
|
||||
args: &[OsString],
|
||||
command: Command,
|
||||
) -> Result<(), failure::Error> {
|
||||
let name = try_to_bstring!(name)?;
|
||||
let folder_name = folder_name()?;
|
||||
|
||||
let local_service = match command {
|
||||
Command::RegisterTask | Command::UpdateTask => false,
|
||||
Command::RegisterTaskLocalService | Command::UpdateTaskLocalService => true,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let update_task = match command {
|
||||
Command::RegisterTask | Command::RegisterTaskLocalService => false,
|
||||
Command::UpdateTask | Command::UpdateTaskLocalService => true,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut service = TaskService::connect_local()?;
|
||||
|
||||
// Get or create the folder
|
||||
let mut folder = service.get_folder(&folder_name).or_else(|e| {
|
||||
if hr_is_not_found(&e) {
|
||||
service
|
||||
.get_root_folder()
|
||||
.and_then(|mut root| root.create_folder(&folder_name))
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})?;
|
||||
|
||||
// When updating, we still delete and recreate the task.
|
||||
// The only part that is currently copied over is the start boundary of the
|
||||
// daily trigger, since that it otherwise set to 5 minutes before the registration; in this way
|
||||
// updates won't delay the task's next scheduled run time.
|
||||
|
||||
// TODO: We may want to also track if the task was disabled.
|
||||
|
||||
let start_time = if update_task {
|
||||
// Ignoring any failures, if we can't get the time for any reason we choose a new one.
|
||||
folder
|
||||
.get_task(&name)
|
||||
.ok()
|
||||
.and_then(|mut task| task.get_definition().ok())
|
||||
.and_then(|mut def| def.get_daily_triggers().ok())
|
||||
.and_then(|mut triggers| {
|
||||
// Currently we are only using 1 daily trigger.
|
||||
triggers
|
||||
.get_mut(0)
|
||||
.and_then(|trigger| trigger.get_StartBoundary().ok())
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
folder.delete_task(&name).unwrap_or_else(|e| {
|
||||
// Don't even warn if the task didn't exist.
|
||||
if !hr_is_not_found(&e) {
|
||||
warn!("delete task failed: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let mut task_def = service.new_task_definition()?;
|
||||
|
||||
{
|
||||
let mut task_args = vec![OsString::from(cmd::DO_TASK)];
|
||||
task_args.extend_from_slice(args);
|
||||
|
||||
let mut action = task_def.add_exec_action()?;
|
||||
action.put_Path(exe)?;
|
||||
action.put_Arguments(task_args.as_slice())?;
|
||||
// TODO working directory?
|
||||
}
|
||||
|
||||
{
|
||||
let mut settings = task_def.get_settings()?;
|
||||
settings.put_DisallowStartIfOnBatteries(false)?;
|
||||
settings.put_StopIfGoingOnBatteries(false)?;
|
||||
settings.put_StartWhenAvailable(true)?;
|
||||
settings.put_ExecutionTimeLimit(chrono::Duration::minutes(5))?;
|
||||
}
|
||||
|
||||
{
|
||||
let mut info = task_def.get_registration_info()?;
|
||||
info.put_Author(&try_to_bstring!(crate::VENDOR)?)?;
|
||||
info.put_Description(&try_to_bstring!(crate::DESCRIPTION)?)?;
|
||||
}
|
||||
|
||||
// A daily trigger starting 5 minutes ago.
|
||||
{
|
||||
let mut daily_trigger = task_def.add_daily_trigger()?;
|
||||
if let Some(ref start_time) = start_time {
|
||||
daily_trigger.put_StartBoundary_BString(start_time)?;
|
||||
} else {
|
||||
daily_trigger.put_StartBoundary(chrono::Utc::now() - chrono::Duration::minutes(5))?;
|
||||
}
|
||||
daily_trigger.put_DaysInterval(1)?;
|
||||
// TODO: 12-hourly trigger? logon trigger?
|
||||
}
|
||||
|
||||
let service_account = if local_service {
|
||||
Some(try_to_bstring!("NT AUTHORITY\\LocalService")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut registered_task = task_def.create(&mut folder, &name, service_account.as_ref())?;
|
||||
|
||||
if local_service {
|
||||
// SDDL seem to be the only way to set the security descriptor.
|
||||
// https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format
|
||||
// Setting just the DACL here allows us to avoid specifying the ownership information,
|
||||
// which I think is required if SDDL is provided on initial task registration.
|
||||
let sddl = try_to_bstring!(concat!(
|
||||
"D:(", // DACL
|
||||
"A;", // ace_type = Allow
|
||||
";", // ace_flags = none
|
||||
"GRGX;", // rights = Generic Read, Generic Execute
|
||||
";;", // object_guid, inherit_object_guid = none
|
||||
"BU)" // account_sid = Built-in users
|
||||
))?;
|
||||
|
||||
registered_task.set_sd(&sddl)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unregister(name: &OsStr) -> Result<(), failure::Error> {
|
||||
let name = try_to_bstring!(name)?;
|
||||
let folder_name = folder_name()?;
|
||||
|
||||
let mut service = TaskService::connect_local()?;
|
||||
let maybe_folder = service.get_folder(&folder_name);
|
||||
let mut folder = match maybe_folder {
|
||||
Err(e) => {
|
||||
if hr_is_not_found(&e) {
|
||||
// Just warn and exit if the folder didn't exist.
|
||||
warn!("failed to unregister: task folder didn't exist");
|
||||
return Ok(());
|
||||
} else {
|
||||
// Other errors are fatal.
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
Ok(folder) => folder,
|
||||
};
|
||||
|
||||
folder.delete_task(&name).or_else(|e| {
|
||||
if hr_is_not_found(&e) {
|
||||
// Only warn if the task didn't exist, still try to remove the folder below.
|
||||
warn!("failed to unregister task that didn't exist");
|
||||
Ok(())
|
||||
} else {
|
||||
// Other errors are fatal.
|
||||
Err(e)
|
||||
}
|
||||
})?;
|
||||
|
||||
let count = folder.get_task_count(true).unwrap_or_else(|e| {
|
||||
warn!("failed getting task count: {}", e);
|
||||
1
|
||||
});
|
||||
|
||||
if count == 0 {
|
||||
let result = service
|
||||
.get_root_folder()
|
||||
.and_then(|mut root| root.delete_folder(&folder_name));
|
||||
if let Err(e) = result {
|
||||
warn!("failed deleting folder: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_on_demand(name: &OsStr) -> Result<(), failure::Error> {
|
||||
let name = try_to_bstring!(name)?;
|
||||
let folder_name = folder_name()?;
|
||||
|
||||
let mut service = TaskService::connect_local()?;
|
||||
let task = service.get_folder(&folder_name)?.get_task(&name)?;
|
||||
|
||||
task.run()?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,574 +0,0 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! A partial type-safe interface for Windows Task Scheduler 2.0
|
||||
//!
|
||||
//! This provides structs thinly wrapping the taskschd interfaces, with methods implemented as
|
||||
//! they've been needed for the update agent.
|
||||
//!
|
||||
//! If it turns out that much more flexibility is needed in task definitions, it may be worth
|
||||
//! generating an XML string and using `ITaskFolder::RegisterTask` or
|
||||
//! `ITaskDefinition::put_XmlText`, rather than adding more and more boilerplate here.
|
||||
//!
|
||||
//! See https://docs.microsoft.com/windows/win32/taskschd/task-scheduler-start-page for
|
||||
//! Microsoft's documentation.
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::os::windows::ffi::{OsStrExt, OsStringExt};
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
|
||||
use comedy::com::{create_instance_inproc_server, ComRef};
|
||||
use comedy::error::{HResult, Win32Error};
|
||||
use comedy::{com_call, com_call_getter};
|
||||
use failure::Fail;
|
||||
|
||||
use crate::ole_utils::{empty_variant, BString, IntoVariantBool, OptionBstringExt};
|
||||
use crate::try_to_bstring;
|
||||
|
||||
use winapi::shared::{
|
||||
ntdef::{LONG, SHORT},
|
||||
winerror::{
|
||||
ERROR_ALREADY_EXISTS, ERROR_BAD_ARGUMENTS, ERROR_FILE_NOT_FOUND, E_ACCESSDENIED,
|
||||
SCHED_E_SERVICE_NOT_RUNNING,
|
||||
},
|
||||
};
|
||||
|
||||
use winapi::um::taskschd::{
|
||||
self, IDailyTrigger, IExecAction, IRegisteredTask, IRegistrationInfo, IRunningTask,
|
||||
ITaskDefinition, ITaskFolder, ITaskService, ITaskSettings, ITrigger, ITriggerCollection,
|
||||
};
|
||||
|
||||
/// Check if the `HResult` represents the win32 `ERROR_FILE_NOT_FOUND`, returned by
|
||||
/// several task scheduler methods.
|
||||
pub fn hr_is_not_found(hr: &HResult) -> bool {
|
||||
hr.code() == HResult::from(Win32Error::new(ERROR_FILE_NOT_FOUND)).code()
|
||||
}
|
||||
|
||||
/// Check if the `HResult` represents the win32 `ERROR_ALREADY_EXISTS`, returned by
|
||||
/// several task scheduler methods.
|
||||
pub fn hr_is_already_exists(hr: &HResult) -> bool {
|
||||
hr.code() == HResult::from(Win32Error::new(ERROR_ALREADY_EXISTS)).code()
|
||||
}
|
||||
|
||||
// These macros simplify wrapping `put_*` property methods. They are each slightly different;
|
||||
// I found it significantly more confusing to try to combine them.
|
||||
|
||||
/// put a bool, converting to `VARIANT_BOOL`
|
||||
macro_rules! bool_putter {
|
||||
($interface:ident :: $method:ident) => {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $method(&mut self, v: bool) -> Result<(), HResult> {
|
||||
let v = v.into_variant_bool();
|
||||
unsafe {
|
||||
com_call!(self.0, $interface::$method(v))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// put a value that is already available as a `BString`
|
||||
macro_rules! bstring_putter {
|
||||
($interface:ident :: $method:ident) => {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $method(&mut self, v: &BString) -> Result<(), HResult> {
|
||||
unsafe {
|
||||
com_call!(self.0, $interface::$method(v.as_raw_ptr()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// put a `chrono::DateTime` value
|
||||
macro_rules! datetime_putter {
|
||||
($interface:ident :: $method:ident) => {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $method(&mut self, v: chrono::DateTime<chrono::Utc>) -> Result<(), HResult> {
|
||||
let v = try_to_bstring!(v.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))?;
|
||||
unsafe {
|
||||
com_call!(self.0, $interface::$method(v.as_raw_ptr()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// put a value of type `$ty`, which implements `AsRef<OsStr>`
|
||||
macro_rules! to_os_str_putter {
|
||||
($interface:ident :: $method:ident, $ty:ty) => {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $method(&mut self, v: $ty) -> Result<(), HResult> {
|
||||
let v = try_to_bstring!(v)?;
|
||||
unsafe {
|
||||
com_call!(self.0, $interface::$method(v.as_raw_ptr()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// put a value of type `$ty`, which implements `ToString`
|
||||
macro_rules! to_string_putter {
|
||||
($interface:ident :: $method:ident, $ty:ty) => {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $method(&mut self, v: $ty) -> Result<(), HResult> {
|
||||
let v = try_to_bstring!(v.to_string())?;
|
||||
unsafe {
|
||||
com_call!(self.0, $interface::$method(v.as_raw_ptr()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct TaskService(ComRef<ITaskService>);
|
||||
|
||||
impl TaskService {
|
||||
pub fn connect_local() -> Result<TaskService, ConnectTaskServiceError> {
|
||||
use self::ConnectTaskServiceError::*;
|
||||
|
||||
let task_service = create_instance_inproc_server::<taskschd::TaskScheduler, ITaskService>()
|
||||
.map_err(CreateInstanceFailed)?;
|
||||
|
||||
// Connect to local service with no credentials.
|
||||
unsafe {
|
||||
com_call!(
|
||||
task_service,
|
||||
ITaskService::Connect(
|
||||
empty_variant(),
|
||||
empty_variant(),
|
||||
empty_variant(),
|
||||
empty_variant()
|
||||
)
|
||||
)
|
||||
}
|
||||
.map_err(|hr| match hr.code() {
|
||||
E_ACCESSDENIED => AccessDenied(hr),
|
||||
SCHED_E_SERVICE_NOT_RUNNING => ServiceNotRunning(hr),
|
||||
_ => ConnectFailed(hr),
|
||||
})?;
|
||||
|
||||
Ok(TaskService(task_service))
|
||||
}
|
||||
|
||||
pub fn get_root_folder(&mut self) -> Result<TaskFolder, HResult> {
|
||||
self.get_folder(&try_to_bstring!("\\")?)
|
||||
}
|
||||
|
||||
pub fn get_folder(&mut self, path: &BString) -> Result<TaskFolder, HResult> {
|
||||
unsafe {
|
||||
com_call_getter!(
|
||||
|folder| self.0,
|
||||
ITaskService::GetFolder(path.as_raw_ptr(), folder)
|
||||
)
|
||||
}
|
||||
.map(TaskFolder)
|
||||
}
|
||||
|
||||
pub fn new_task_definition(&mut self) -> Result<TaskDefinition, HResult> {
|
||||
unsafe {
|
||||
com_call_getter!(
|
||||
|task_def| self.0,
|
||||
ITaskService::NewTask(
|
||||
0, // flags (reserved)
|
||||
task_def,
|
||||
)
|
||||
)
|
||||
}
|
||||
.map(TaskDefinition)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Fail)]
|
||||
pub enum ConnectTaskServiceError {
|
||||
#[fail(display = "{}", _0)]
|
||||
CreateInstanceFailed(#[fail(cause)] HResult),
|
||||
#[fail(display = "Access is denied to connect to the Task Scheduler service")]
|
||||
AccessDenied(#[fail(cause)] HResult),
|
||||
#[fail(display = "The Task Scheduler service is not running")]
|
||||
ServiceNotRunning(#[fail(cause)] HResult),
|
||||
#[fail(display = "{}", _0)]
|
||||
ConnectFailed(#[fail(cause)] HResult),
|
||||
}
|
||||
|
||||
pub struct TaskFolder(ComRef<ITaskFolder>);
|
||||
|
||||
impl TaskFolder {
|
||||
pub fn get_task(&mut self, task_name: &BString) -> Result<RegisteredTask, HResult> {
|
||||
unsafe {
|
||||
com_call_getter!(
|
||||
|task| self.0,
|
||||
ITaskFolder::GetTask(task_name.as_raw_ptr(), task)
|
||||
)
|
||||
}
|
||||
.map(RegisteredTask)
|
||||
}
|
||||
|
||||
pub fn get_task_count(&mut self, include_hidden: bool) -> Result<LONG, HResult> {
|
||||
use self::taskschd::IRegisteredTaskCollection;
|
||||
|
||||
let flags = if include_hidden {
|
||||
taskschd::TASK_ENUM_HIDDEN
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let tasks = com_call_getter!(|t| self.0, ITaskFolder::GetTasks(flags as LONG, t))?;
|
||||
|
||||
let mut count = 0;
|
||||
com_call!(tasks, IRegisteredTaskCollection::get_Count(&mut count))?;
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_folder(&mut self, path: &BString) -> Result<TaskFolder, HResult> {
|
||||
let sddl = empty_variant();
|
||||
unsafe {
|
||||
com_call_getter!(
|
||||
|folder| self.0,
|
||||
ITaskFolder::CreateFolder(path.as_raw_ptr(), sddl, folder)
|
||||
)
|
||||
}
|
||||
.map(TaskFolder)
|
||||
}
|
||||
|
||||
pub fn delete_folder(&mut self, path: &BString) -> Result<(), HResult> {
|
||||
unsafe {
|
||||
com_call!(
|
||||
self.0,
|
||||
ITaskFolder::DeleteFolder(
|
||||
path.as_raw_ptr(),
|
||||
0, // flags (reserved)
|
||||
)
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_task(&mut self, task_name: &BString) -> Result<(), HResult> {
|
||||
unsafe {
|
||||
com_call!(
|
||||
self.0,
|
||||
ITaskFolder::DeleteTask(
|
||||
task_name.as_raw_ptr(),
|
||||
0, // flags (reserved)
|
||||
)
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskDefinition(ComRef<ITaskDefinition>);
|
||||
|
||||
impl TaskDefinition {
|
||||
pub fn get_settings(&mut self) -> Result<TaskSettings, HResult> {
|
||||
unsafe { com_call_getter!(|s| self.0, ITaskDefinition::get_Settings(s)) }.map(TaskSettings)
|
||||
}
|
||||
|
||||
pub fn get_registration_info(&mut self) -> Result<RegistrationInfo, HResult> {
|
||||
unsafe { com_call_getter!(|ri| self.0, ITaskDefinition::get_RegistrationInfo(ri)) }
|
||||
.map(RegistrationInfo)
|
||||
}
|
||||
|
||||
unsafe fn add_action<T: winapi::Interface>(
|
||||
&mut self,
|
||||
action_type: taskschd::TASK_ACTION_TYPE,
|
||||
) -> Result<ComRef<T>, HResult> {
|
||||
use self::taskschd::IActionCollection;
|
||||
|
||||
let actions = com_call_getter!(|ac| self.0, ITaskDefinition::get_Actions(ac))?;
|
||||
let action = com_call_getter!(|a| actions, IActionCollection::Create(action_type, a))?;
|
||||
action.cast()
|
||||
}
|
||||
|
||||
pub fn add_exec_action(&mut self) -> Result<ExecAction, HResult> {
|
||||
unsafe { self.add_action(taskschd::TASK_ACTION_EXEC) }.map(ExecAction)
|
||||
}
|
||||
|
||||
unsafe fn add_trigger<T: winapi::Interface>(
|
||||
&mut self,
|
||||
trigger_type: taskschd::TASK_TRIGGER_TYPE2,
|
||||
) -> Result<ComRef<T>, HResult> {
|
||||
let triggers = com_call_getter!(|tc| self.0, ITaskDefinition::get_Triggers(tc))?;
|
||||
let trigger = com_call_getter!(|t| triggers, ITriggerCollection::Create(trigger_type, t))?;
|
||||
trigger.cast()
|
||||
}
|
||||
|
||||
pub fn add_daily_trigger(&mut self) -> Result<DailyTrigger, HResult> {
|
||||
unsafe { self.add_trigger(taskschd::TASK_TRIGGER_DAILY) }.map(DailyTrigger)
|
||||
}
|
||||
|
||||
pub fn get_daily_triggers(&mut self) -> Result<Vec<DailyTrigger>, HResult> {
|
||||
let mut found_triggers = Vec::new();
|
||||
|
||||
unsafe {
|
||||
let triggers = com_call_getter!(|tc| self.0, ITaskDefinition::get_Triggers(tc))?;
|
||||
let mut count = 0;
|
||||
com_call!(triggers, ITriggerCollection::get_Count(&mut count))?;
|
||||
|
||||
// Item indexes start at 1
|
||||
for i in 1..=count {
|
||||
let trigger = com_call_getter!(|t| triggers, ITriggerCollection::get_Item(i, t))?;
|
||||
|
||||
let mut trigger_type = 0;
|
||||
com_call!(trigger, ITrigger::get_Type(&mut trigger_type))?;
|
||||
|
||||
if trigger_type == taskschd::TASK_TRIGGER_DAILY {
|
||||
found_triggers.push(DailyTrigger(trigger.cast()?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(found_triggers)
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
&mut self,
|
||||
folder: &mut TaskFolder,
|
||||
task_name: &BString,
|
||||
service_account: Option<&BString>,
|
||||
) -> Result<RegisteredTask, HResult> {
|
||||
self.register_impl(folder, task_name, service_account, taskschd::TASK_CREATE)
|
||||
}
|
||||
|
||||
fn register_impl(
|
||||
&mut self,
|
||||
folder: &mut TaskFolder,
|
||||
task_name: &BString,
|
||||
service_account: Option<&BString>,
|
||||
creation_flags: taskschd::TASK_CREATION,
|
||||
) -> Result<RegisteredTask, HResult> {
|
||||
let task_definition = self.0.as_raw_ptr();
|
||||
|
||||
let password = empty_variant();
|
||||
|
||||
let logon_type = if service_account.is_some() {
|
||||
taskschd::TASK_LOGON_SERVICE_ACCOUNT
|
||||
} else {
|
||||
taskschd::TASK_LOGON_INTERACTIVE_TOKEN
|
||||
};
|
||||
|
||||
let sddl = empty_variant();
|
||||
|
||||
let registered_task = unsafe {
|
||||
com_call_getter!(
|
||||
|rt| folder.0,
|
||||
ITaskFolder::RegisterTaskDefinition(
|
||||
task_name.as_raw_ptr(),
|
||||
task_definition,
|
||||
creation_flags as LONG,
|
||||
service_account.as_raw_variant(),
|
||||
password,
|
||||
logon_type,
|
||||
sddl,
|
||||
rt,
|
||||
)
|
||||
)?
|
||||
};
|
||||
|
||||
Ok(RegisteredTask(registered_task))
|
||||
}
|
||||
|
||||
pub fn get_xml(task_definition: &ComRef<ITaskDefinition>) -> Result<OsString, String> {
|
||||
unsafe {
|
||||
let mut xml = ptr::null_mut();
|
||||
com_call!(task_definition, ITaskDefinition::get_XmlText(&mut xml))
|
||||
.map_err(|e| format!("{}", e))?;
|
||||
|
||||
Ok(OsString::from_wide(
|
||||
BString::from_raw(xml)
|
||||
.ok_or_else(|| "null xml".to_string())?
|
||||
.as_ref(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskSettings(ComRef<ITaskSettings>);
|
||||
|
||||
impl TaskSettings {
|
||||
bool_putter!(ITaskSettings::put_AllowDemandStart);
|
||||
bool_putter!(ITaskSettings::put_DisallowStartIfOnBatteries);
|
||||
to_string_putter!(ITaskSettings::put_ExecutionTimeLimit, chrono::Duration);
|
||||
bool_putter!(ITaskSettings::put_Hidden);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn put_MultipleInstances(&mut self, v: InstancesPolicy) -> Result<(), HResult> {
|
||||
unsafe {
|
||||
com_call!(self.0, ITaskSettings::put_MultipleInstances(v as u32))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
bool_putter!(ITaskSettings::put_RunOnlyIfIdle);
|
||||
bool_putter!(ITaskSettings::put_RunOnlyIfNetworkAvailable);
|
||||
bool_putter!(ITaskSettings::put_StartWhenAvailable);
|
||||
bool_putter!(ITaskSettings::put_StopIfGoingOnBatteries);
|
||||
bool_putter!(ITaskSettings::put_Enabled);
|
||||
bool_putter!(ITaskSettings::put_WakeToRun);
|
||||
}
|
||||
|
||||
pub struct RegistrationInfo(ComRef<IRegistrationInfo>);
|
||||
|
||||
impl RegistrationInfo {
|
||||
bstring_putter!(IRegistrationInfo::put_Author);
|
||||
bstring_putter!(IRegistrationInfo::put_Description);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u32)]
|
||||
pub enum InstancesPolicy {
|
||||
Parallel = taskschd::TASK_INSTANCES_PARALLEL,
|
||||
Queue = taskschd::TASK_INSTANCES_QUEUE,
|
||||
IgnoreNew = taskschd::TASK_INSTANCES_IGNORE_NEW,
|
||||
StopExisting = taskschd::TASK_INSTANCES_STOP_EXISTING,
|
||||
}
|
||||
|
||||
pub struct DailyTrigger(ComRef<IDailyTrigger>);
|
||||
|
||||
impl DailyTrigger {
|
||||
datetime_putter!(IDailyTrigger::put_StartBoundary);
|
||||
|
||||
// I'd like to have this only use the type-safe DateTime, but when copying it seems less
|
||||
// error-prone to use the string directly rather than try to parse it and then convert it back
|
||||
// to string.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn put_StartBoundary_BString(&mut self, v: &BString) -> Result<(), HResult> {
|
||||
unsafe {
|
||||
com_call!(self.0, IDailyTrigger::put_StartBoundary(v.as_raw_ptr()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn get_StartBoundary(&mut self) -> Result<BString, HResult> {
|
||||
unsafe {
|
||||
let mut start_boundary = ptr::null_mut();
|
||||
let hr = com_call!(
|
||||
self.0,
|
||||
IDailyTrigger::get_StartBoundary(&mut start_boundary)
|
||||
)?;
|
||||
BString::from_raw(start_boundary).ok_or_else(|| HResult::new(hr))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn put_DaysInterval(&mut self, v: SHORT) -> Result<(), HResult> {
|
||||
unsafe {
|
||||
com_call!(self.0, IDailyTrigger::put_DaysInterval(v))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExecAction(ComRef<IExecAction>);
|
||||
|
||||
impl ExecAction {
|
||||
to_os_str_putter!(IExecAction::put_Path, &Path);
|
||||
to_os_str_putter!(IExecAction::put_WorkingDirectory, &Path);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn put_Arguments(&mut self, args: &[OsString]) -> Result<(), HResult> {
|
||||
// based on `make_command_line()` from libstd
|
||||
// https://github.com/rust-lang/rust/blob/37ff5d388f8c004ca248adb635f1cc84d347eda0/src/libstd/sys/windows/process.rs#L457
|
||||
|
||||
let mut s = Vec::new();
|
||||
|
||||
fn append_arg(cmd: &mut Vec<u16>, arg: &OsStr) -> Result<(), HResult> {
|
||||
cmd.push('"' as u16);
|
||||
|
||||
let mut backslashes: usize = 0;
|
||||
for x in arg.encode_wide() {
|
||||
if x == 0 {
|
||||
return Err(HResult::from(Win32Error::new(ERROR_BAD_ARGUMENTS))
|
||||
.file_line(file!(), line!()));
|
||||
}
|
||||
|
||||
if x == '\\' as u16 {
|
||||
backslashes += 1;
|
||||
} else {
|
||||
if x == '"' as u16 {
|
||||
// Add n+1 backslashes for a total of 2n+1 before internal '"'.
|
||||
cmd.extend((0..=backslashes).map(|_| '\\' as u16));
|
||||
}
|
||||
backslashes = 0;
|
||||
}
|
||||
cmd.push(x);
|
||||
}
|
||||
|
||||
// Add n backslashes for a total of 2n before ending '"'.
|
||||
cmd.extend((0..backslashes).map(|_| '\\' as u16));
|
||||
cmd.push('"' as u16);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
if !s.is_empty() {
|
||||
s.push(' ' as u16);
|
||||
}
|
||||
|
||||
// always quote args
|
||||
append_arg(&mut s, arg.as_ref())?;
|
||||
}
|
||||
|
||||
let args = BString::from_slice(s).map_err(|e| e.file_line(file!(), line!()))?;
|
||||
|
||||
unsafe {
|
||||
com_call!(self.0, IExecAction::put_Arguments(args.as_raw_ptr()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegisteredTask(ComRef<IRegisteredTask>);
|
||||
|
||||
impl RegisteredTask {
|
||||
pub fn set_sd(&mut self, sddl: &BString) -> Result<(), HResult> {
|
||||
unsafe {
|
||||
com_call!(
|
||||
self.0,
|
||||
IRegisteredTask::SetSecurityDescriptor(
|
||||
sddl.as_raw_ptr(),
|
||||
0, // flags (none)
|
||||
)
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_definition(&mut self) -> Result<TaskDefinition, HResult> {
|
||||
unsafe { com_call_getter!(|tc| self.0, IRegisteredTask::get_Definition(tc)) }
|
||||
.map(TaskDefinition)
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<(), HResult> {
|
||||
self.run_impl(Option::<&OsStr>::None)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_impl(&self, param: Option<impl AsRef<OsStr>>) -> Result<ComRef<IRunningTask>, HResult> {
|
||||
// Running with parameters isn't currently exposed.
|
||||
// param can also be an array of strings, but that is not supported here
|
||||
let param = if let Some(p) = param {
|
||||
Some(try_to_bstring!(p)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
unsafe {
|
||||
com_call_getter!(
|
||||
|rt| self.0,
|
||||
IRegisteredTask::Run(param.as_ref().as_raw_variant(), rt)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::{env::current_exe, ffi::OsString, path::PathBuf};
|
||||
use winapi::shared::{
|
||||
minwindef::MAX_PATH,
|
||||
winerror::{FAILED, HRESULT},
|
||||
};
|
||||
use wio::wide::{FromWide, ToWide};
|
||||
|
||||
pub fn get_download_xml() -> Result<OsString, String> {
|
||||
let mut update_dir = PathBuf::from(get_update_directory()?);
|
||||
update_dir.push("download.xml");
|
||||
Ok(update_dir.into_os_string())
|
||||
}
|
||||
|
||||
pub fn get_install_hash() -> Result<OsString, String> {
|
||||
let update_dir = PathBuf::from(get_update_directory()?);
|
||||
update_dir
|
||||
.file_name()
|
||||
.map(|file_name| OsString::from(file_name))
|
||||
.ok_or("Couldn't get update directory name".to_string())
|
||||
}
|
||||
|
||||
#[link(name = "updatecommon", kind = "static")]
|
||||
extern "C" {
|
||||
fn get_common_update_directory(install_path: *const u16, result: *mut u16) -> HRESULT;
|
||||
}
|
||||
pub fn get_update_directory() -> Result<OsString, String> {
|
||||
let mut binary_path = current_exe()
|
||||
.map_err(|e| format!("Couldn't get executing binary's path: {}", e))?
|
||||
.parent()
|
||||
.ok_or("Couldn't get executing binary's parent directory")?
|
||||
.as_os_str()
|
||||
.to_wide_null();
|
||||
// It looks like Path.as_os_str returns a path without a trailing slash, so this may be
|
||||
// unnecessary. But the documentation does not appear to guarantee the lack of trailing slash.
|
||||
// get_common_update_directory, however, must be given the installation path without a trailing
|
||||
// slash. So remove any trailing path slashes, just to be safe.
|
||||
strip_trailing_path_separators(&mut binary_path);
|
||||
|
||||
let mut buffer: Vec<u16> = Vec::with_capacity(MAX_PATH + 1);
|
||||
let hr = unsafe { get_common_update_directory(binary_path.as_ptr(), buffer.as_mut_ptr()) };
|
||||
if FAILED(hr) {
|
||||
return Err(format!("Failed to get update directory: HRESULT={}", hr));
|
||||
}
|
||||
Ok(unsafe { OsString::from_wide_ptr_null(buffer.as_ptr()) })
|
||||
}
|
||||
|
||||
fn strip_trailing_path_separators(path: &mut Vec<u16>) {
|
||||
let forward_slash = '/' as u16;
|
||||
let backslash = '\\' as u16;
|
||||
for character in path.iter_mut().rev() {
|
||||
if *character == forward_slash || *character == backslash {
|
||||
*character = 0;
|
||||
} else if *character != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use bits_client::{BitsClient, BitsJobState, BitsProxyUsage};
|
||||
use std::{ffi::OsString, fs::create_dir_all, path::Path};
|
||||
|
||||
pub fn sync_download_url(
|
||||
download_dir: OsString,
|
||||
save_path: OsString,
|
||||
job_name: OsString,
|
||||
url: OsString,
|
||||
) -> Result<(), String> {
|
||||
let immediate_download_dir = Path::new(&save_path)
|
||||
.parent()
|
||||
.ok_or("Couldn't get immediate download directory".to_string())?;
|
||||
create_dir_all(immediate_download_dir)
|
||||
.map_err(|e| format!("Couldn't create download directory: {}", e))?;
|
||||
|
||||
let mut client = BitsClient::new(job_name, download_dir)
|
||||
.map_err(|e| format!("Error constructing BitsClient: {}", e))?;
|
||||
|
||||
// Kill the transfer after 15 minutes of making no progress.
|
||||
let no_progress_timeout_sec: u32 = 15 * 60;
|
||||
// Transfer status updates are checked by polling. Set the polling interval to 10 minutes.
|
||||
// This is just for monitoring update progress, which we don't really care about right now.
|
||||
// Transfer completion (including errors) are propagated immediately, without waiting for the
|
||||
// monitor interval.
|
||||
let monitor_interval_ms: u32 = 10 * 60 * 1000;
|
||||
let (job, mut monitor_client) = client
|
||||
.start_job(
|
||||
url,
|
||||
OsString::from(save_path),
|
||||
BitsProxyUsage::Preconfig,
|
||||
no_progress_timeout_sec,
|
||||
monitor_interval_ms,
|
||||
)
|
||||
.map_err(|e| format!("BitsClient error when starting job: {}", e))?
|
||||
.map_err(|e| format!("BITS error when starting job: {}", e))?;
|
||||
|
||||
loop {
|
||||
// The monitor timeout should be at least as long as the monitor interval so we don't time
|
||||
// out just waiting for the interval to expire.
|
||||
let monitor_timeout_ms = 15 * 60 * 1000;
|
||||
// get_status will cause the program to sleep until the monitor interval expires, or the
|
||||
// transfer completes.
|
||||
match monitor_client.get_status(monitor_timeout_ms) {
|
||||
Err(error) => {
|
||||
let _ = client.cancel_job(job.guid);
|
||||
return Err(format!("BitsMonitorClient Error: {}", error));
|
||||
}
|
||||
Ok(Err(error)) => {
|
||||
let _ = client.cancel_job(job.guid);
|
||||
return Err(format!("BITS error while getting status: {}", error));
|
||||
}
|
||||
Ok(Ok(job_status)) => match job_status.state {
|
||||
BitsJobState::Suspended => {
|
||||
let _ = client.cancel_job(job.guid);
|
||||
return Err("BITS job externally suspended".to_string());
|
||||
}
|
||||
BitsJobState::Cancelled => return Err("BITS job externally cancelled".to_string()),
|
||||
BitsJobState::Acknowledged => {
|
||||
return Err("BITS job externally completed".to_string())
|
||||
}
|
||||
BitsJobState::Error => {
|
||||
let _ = client.cancel_job(job.guid);
|
||||
if let Some(error) = job_status.error {
|
||||
return Err(format!("BITS error while transferring: {}", error));
|
||||
}
|
||||
return Err("Unknown BITS error".to_string());
|
||||
}
|
||||
BitsJobState::Other(state) => {
|
||||
let _ = client.cancel_job(job.guid);
|
||||
return Err(format!("BITS returned an unknown state: {}", state));
|
||||
}
|
||||
BitsJobState::Queued
|
||||
| BitsJobState::Connecting
|
||||
| BitsJobState::Transferring
|
||||
| BitsJobState::TransientError => {
|
||||
// Continue waiting for transfer to complete
|
||||
}
|
||||
BitsJobState::Transferred => break,
|
||||
},
|
||||
}
|
||||
}
|
||||
client
|
||||
.complete_job(job.guid)
|
||||
.map_err(|e| format!("BitsClient error when completing job: {}", e))?
|
||||
.map_err(|e| format!("BITS error when completing job: {}", e))?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
mod download;
|
||||
mod parse;
|
||||
mod update_url;
|
||||
|
||||
use log::{debug, info};
|
||||
pub use parse::{parse_file, Update, UpdatePatchType, UpdateType, UpdateXmlPatch};
|
||||
use std::ffi::OsString;
|
||||
|
||||
pub fn sync_download(
|
||||
download_dir: OsString,
|
||||
save_path: OsString,
|
||||
job_name: OsString,
|
||||
) -> Result<(), String> {
|
||||
let url = OsString::from(update_url::generate()?);
|
||||
info!(
|
||||
"Checking for updates using URL: {:?}.\nSaving to {:?}",
|
||||
url, save_path
|
||||
);
|
||||
download::sync_download_url(download_dir, save_path, job_name, url)?;
|
||||
debug!("XML download successful");
|
||||
Ok(())
|
||||
}
|
|
@ -1,302 +0,0 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use log::{debug, warn};
|
||||
use std::{collections::BTreeMap, ffi::OsString, fs::File, io::BufReader};
|
||||
|
||||
pub struct Update {
|
||||
pub update_type: UpdateType,
|
||||
pub attributes: BTreeMap<String, String>,
|
||||
pub patches: Vec<UpdateXmlPatch>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum UpdateType {
|
||||
Major,
|
||||
Minor,
|
||||
}
|
||||
|
||||
pub struct UpdateXmlPatch {
|
||||
pub patch_type: UpdatePatchType,
|
||||
pub url: String,
|
||||
pub size: u64,
|
||||
pub attributes: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum UpdatePatchType {
|
||||
Complete,
|
||||
Partial,
|
||||
}
|
||||
|
||||
pub fn parse_file(path: &OsString) -> Result<Vec<Update>, String> {
|
||||
let file = File::open(path)
|
||||
.map_err(|e| format!("Unable to open path \"{:?}\" for reading: {}", path, e))?;
|
||||
let buffered_reader = BufReader::new(file);
|
||||
let mut xml_reader = xml::EventReader::new(buffered_reader);
|
||||
loop {
|
||||
match get_next_xml_event(&mut xml_reader)? {
|
||||
xml::reader::XmlEvent::StartElement { name, .. } => {
|
||||
if name.local_name != "updates" || name.prefix.is_some() {
|
||||
return Err(format!(
|
||||
concat!(
|
||||
"XML Parse Error: Expected top level element: <updates>. ",
|
||||
"Instead found: <{}>"
|
||||
),
|
||||
name
|
||||
));
|
||||
}
|
||||
break;
|
||||
}
|
||||
xml::reader::XmlEvent::EndElement { .. } => return Err(
|
||||
"XML Parse Error: Expected top level element. Instead found the end of an element"
|
||||
.to_string(),
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let mut updates: Vec<Update> = Vec::new();
|
||||
// Read tags within <updates> until we read the closing </updates> tag
|
||||
loop {
|
||||
match get_next_xml_event(&mut xml_reader)? {
|
||||
xml::reader::XmlEvent::StartElement {
|
||||
name, attributes, ..
|
||||
} => {
|
||||
// UpdateService.jsm's implementation ignores non-<update> tags, so this does
|
||||
// too.
|
||||
if name.local_name == "update" && name.prefix.is_none() {
|
||||
let maybe_update = read_update(attributes, &mut xml_reader)?;
|
||||
if let Some(update) = maybe_update {
|
||||
updates.push(update);
|
||||
}
|
||||
} else {
|
||||
skip_tag(name, &mut xml_reader)?;
|
||||
}
|
||||
}
|
||||
xml::reader::XmlEvent::EndElement { name } => {
|
||||
if name.local_name != "updates" || name.prefix.is_some() {
|
||||
return Err(format!(
|
||||
"XML Parse Error: Expected: </updates>. Instead found: </{}>",
|
||||
name
|
||||
));
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// XML documents must have exactly one top-level element. Expect to read the end of the
|
||||
// document.
|
||||
loop {
|
||||
// This is the one place we don't want to use the get_next_xml_event wrapper, because
|
||||
// we are expecting to see EndDocument.
|
||||
let event = xml_reader
|
||||
.next()
|
||||
.map_err(|e| format!("XML Parser Error: {}", e))?;
|
||||
match event {
|
||||
xml::reader::XmlEvent::EndDocument => break,
|
||||
xml::reader::XmlEvent::StartElement { name, .. } => {
|
||||
return Err(format!(
|
||||
"XML Parse Error: Expected end of document. Instead found: <{}>",
|
||||
name
|
||||
))
|
||||
}
|
||||
xml::reader::XmlEvent::EndElement { name } => {
|
||||
return Err(format!(
|
||||
"XML Parse Error: Expected end of document. Instead found: </{}>",
|
||||
name
|
||||
))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(updates)
|
||||
}
|
||||
|
||||
// This wrapper should be called to get the next XML event from the reader. This wrapper should
|
||||
// be used whenever you are NOT expecting an EndDocument. The reason for the wrapper is that
|
||||
// the standard pattern used in this file for reading XML events looks like this:
|
||||
// loop {
|
||||
// match reader.next()? {
|
||||
// cases_we_care_about => handling,
|
||||
// _ => {},
|
||||
// }
|
||||
// }
|
||||
// This pattern has a potential footgun: If EndDocument is not one of the handled cases and we
|
||||
// hit an unexpected EndDocument, the above pattern turns into an infinite loop. This wrapper
|
||||
// avoids that footgun by converting any unexpected EndDocument to an error.
|
||||
fn get_next_xml_event(
|
||||
reader: &mut xml::EventReader<BufReader<File>>,
|
||||
) -> Result<xml::reader::XmlEvent, String> {
|
||||
let event = reader
|
||||
.next()
|
||||
.map_err(|e| format!("XML Parser Error: {}", e))?;
|
||||
if event == xml::reader::XmlEvent::EndDocument {
|
||||
return Err("XML Parse Error: Unexpected end of document".to_string());
|
||||
}
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
fn make_attr_map(
|
||||
attr_vec: Vec<xml::attribute::OwnedAttribute>,
|
||||
) -> Result<BTreeMap<String, String>, String> {
|
||||
let mut attr_map = BTreeMap::new();
|
||||
for attr in attr_vec {
|
||||
let name = if let Some(mut prefixed_name) = attr.name.prefix {
|
||||
prefixed_name.push(':');
|
||||
prefixed_name.push_str(&attr.name.local_name);
|
||||
prefixed_name
|
||||
} else {
|
||||
attr.name.local_name
|
||||
};
|
||||
|
||||
let prev_value = attr_map.insert(name.clone(), attr.value);
|
||||
if prev_value.is_some() {
|
||||
// It's invalid to have the same attribute twice in an element
|
||||
return Err(format!("XML Parse Error: Duplicate \"{}\" attribute", name));
|
||||
}
|
||||
}
|
||||
Ok(attr_map)
|
||||
}
|
||||
|
||||
fn skip_tag(
|
||||
tag_to_skip: xml::name::OwnedName,
|
||||
reader: &mut xml::EventReader<BufReader<File>>,
|
||||
) -> Result<(), String> {
|
||||
debug!("Skipping unexpected XML tag: <{}>", tag_to_skip);
|
||||
loop {
|
||||
match get_next_xml_event(reader)? {
|
||||
xml::reader::XmlEvent::StartElement { name, .. } => {
|
||||
skip_tag(name, reader)?;
|
||||
}
|
||||
xml::reader::XmlEvent::EndElement { name } => {
|
||||
if name == tag_to_skip {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(format!(
|
||||
"XML Parse Error: Expected </{}>. Instead found </{}>",
|
||||
tag_to_skip, name
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read an <update> tag. Updates with invalid values for the "type" attribute will be silently
|
||||
// dropped by returning Ok(None).
|
||||
fn read_update(
|
||||
attributes: Vec<xml::attribute::OwnedAttribute>,
|
||||
reader: &mut xml::EventReader<BufReader<File>>,
|
||||
) -> Result<Option<Update>, String> {
|
||||
let mut attributes = make_attr_map(attributes)?;
|
||||
let maybe_update_type =
|
||||
attributes
|
||||
.remove("type")
|
||||
.and_then(|type_string| match type_string.as_str() {
|
||||
"major" => Some(UpdateType::Major),
|
||||
"minor" => Some(UpdateType::Minor),
|
||||
_ => None,
|
||||
});
|
||||
let mut maybe_update = if let Some(update_type) = maybe_update_type {
|
||||
Some(Update {
|
||||
update_type,
|
||||
attributes,
|
||||
patches: Vec::new(),
|
||||
})
|
||||
} else {
|
||||
warn!("Dropping malformed <update> XML element");
|
||||
None
|
||||
};
|
||||
// Read tags within the <update> tag until we reach the end of the tag
|
||||
loop {
|
||||
match get_next_xml_event(reader)? {
|
||||
xml::reader::XmlEvent::StartElement {
|
||||
name, attributes, ..
|
||||
} => {
|
||||
// UpdateService.jsm's implementation ignores unexpected tags, so this does too
|
||||
if let Some(update) = maybe_update.as_mut() {
|
||||
if name.local_name == "patch" && name.prefix.is_none() {
|
||||
let maybe_patch = read_patch(attributes, reader)?;
|
||||
if let Some(patch) = maybe_patch {
|
||||
update.patches.push(patch);
|
||||
}
|
||||
} else {
|
||||
skip_tag(name, reader)?;
|
||||
}
|
||||
} else {
|
||||
skip_tag(name, reader)?;
|
||||
}
|
||||
}
|
||||
xml::reader::XmlEvent::EndElement { name } => {
|
||||
if name.local_name != "update" || name.prefix.is_some() {
|
||||
return Err(format!(
|
||||
"XML Parse Error: Expected </update>. Instead found </{}>",
|
||||
name
|
||||
));
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(maybe_update)
|
||||
}
|
||||
|
||||
// Read a <patch> tag. Patches with invalid values such as non-numeric size or types other than
|
||||
// complete or partial will be silently dropped by returning Ok(None).
|
||||
// Note for when this mockup gets turned into production code: We should probably log something
|
||||
// in the cases when this function silently drops a patch.
|
||||
fn read_patch(
|
||||
attributes: Vec<xml::attribute::OwnedAttribute>,
|
||||
reader: &mut xml::EventReader<BufReader<File>>,
|
||||
) -> Result<Option<UpdateXmlPatch>, String> {
|
||||
let mut attributes = make_attr_map(attributes)?;
|
||||
let maybe_patch_type =
|
||||
attributes
|
||||
.remove("type")
|
||||
.and_then(|type_string| match type_string.as_str() {
|
||||
"partial" => Some(UpdatePatchType::Partial),
|
||||
"complete" => Some(UpdatePatchType::Complete),
|
||||
_ => None,
|
||||
});
|
||||
let maybe_url = attributes.remove("URL");
|
||||
let maybe_size = attributes
|
||||
.remove("size")
|
||||
.and_then(|size_string| size_string.parse::<u64>().ok());
|
||||
let maybe_patch = if let (Some(patch_type), Some(url), Some(size)) =
|
||||
(maybe_patch_type, maybe_url, maybe_size)
|
||||
{
|
||||
Some(UpdateXmlPatch {
|
||||
patch_type,
|
||||
url,
|
||||
size,
|
||||
attributes,
|
||||
})
|
||||
} else {
|
||||
warn!("Dropping malformed <patch> XML element");
|
||||
None
|
||||
};
|
||||
// Read tags within the <patch> tag until we reach the end of the tag
|
||||
loop {
|
||||
match get_next_xml_event(reader)? {
|
||||
xml::reader::XmlEvent::StartElement { name, .. } => {
|
||||
// UpdateService.jsm's implementation ignores unexpected tags, so this does too
|
||||
skip_tag(name, reader)?;
|
||||
}
|
||||
xml::reader::XmlEvent::EndElement { name } => {
|
||||
if name.local_name != "patch" || name.prefix.is_some() {
|
||||
return Err(format!(
|
||||
"XML Parse Error: Expected </patch>. Instead found </{}>",
|
||||
name
|
||||
));
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(maybe_patch)
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
//! This module contains convenient accessors for C++ constants.
|
||||
//!
|
||||
//! The contents of this file are generated from
|
||||
//! `toolkit/components/updateagent/UpdateUrlConstants.py`.
|
||||
include!(concat!(
|
||||
env!("MOZ_TOPOBJDIR"),
|
||||
"/toolkit/components/updateagent/url_constants.rs"
|
||||
));
|
|
@ -1,282 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
//! This crate generates the update url used by Firefox to check for updates.
|
||||
|
||||
mod constants;
|
||||
mod read_pref;
|
||||
mod windows;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use ini::Ini;
|
||||
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
||||
use read_pref::read_pref;
|
||||
use std::{env::current_exe, path::Path};
|
||||
|
||||
pub fn generate() -> Result<String, String> {
|
||||
let binary_path =
|
||||
current_exe().map_err(|e| format!("Couldn't get executing binary's path: {}", e))?;
|
||||
let resource_path = binary_path
|
||||
.parent()
|
||||
.ok_or("Couldn't get executing binary's parent directory")?;
|
||||
|
||||
let application_ini = read_application_ini(&resource_path)?;
|
||||
|
||||
let windows_cpu_arch = windows::get_cpu_arch();
|
||||
|
||||
// Get the data to be substituted into the URL
|
||||
let url = get_url_template(&application_ini)?;
|
||||
let (product, version, build_id) = get_application_info(&application_ini)?;
|
||||
let build_target = get_build_target(Some(&windows_cpu_arch))?;
|
||||
let channel = get_channel(&resource_path)?;
|
||||
let os_version = get_os_version(Some(&windows_cpu_arch))?;
|
||||
let locale = get_locale(&resource_path)?;
|
||||
let (distribution, distribution_version) = get_distribution_info(&resource_path)?;
|
||||
let system_capabilities = get_system_capabilities()?;
|
||||
|
||||
// Perform variable substitution
|
||||
let url = url.replace("%PRODUCT%", &product);
|
||||
let url = url.replace("%VERSION%", &version);
|
||||
let url = url.replace("%BUILD_ID%", &build_id);
|
||||
let url = url.replace("%BUILD_TARGET%", &build_target);
|
||||
let url = url.replace("%OS_VERSION%", &os_version);
|
||||
let url = url.replace("%LOCALE%", &locale);
|
||||
let url = url.replace("%CHANNEL%", &channel);
|
||||
let url = url.replace("%PLATFORM_VERSION%", constants::GRE_MILESTONE);
|
||||
let url = url.replace("%SYSTEM_CAPABILITIES%", &system_capabilities);
|
||||
let url = url.replace("%DISTRIBUTION%", &distribution);
|
||||
let url = url.replace("%DISTRIBUTION_VERSION%", &distribution_version);
|
||||
|
||||
let url = url.replace("+", "%2B");
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
fn read_application_ini(resource_path: &Path) -> Result<Ini, String> {
|
||||
let application_ini_path = resource_path.join("application.ini");
|
||||
let application_ini_path_str = application_ini_path
|
||||
.to_str()
|
||||
.ok_or("Path to application.ini is not valid Unicode")?;
|
||||
let application_ini = Ini::load_from_file(application_ini_path_str)
|
||||
.map_err(|e| format!("Unable to read application.ini: {}", e))?;
|
||||
Ok(application_ini)
|
||||
}
|
||||
|
||||
fn get_url_template(application_ini: &Ini) -> Result<String, String> {
|
||||
let app_update_section = application_ini
|
||||
.section(Some("AppUpdate"))
|
||||
.ok_or("application.ini has no AppUpdate section")?;
|
||||
let template = app_update_section
|
||||
.get("URL")
|
||||
.ok_or("application.ini has no URL in its AppUpdate section")?;
|
||||
Ok(template.clone())
|
||||
}
|
||||
|
||||
/// On success, returns a tuple of product, version, and build ID strings
|
||||
fn get_application_info(application_ini: &Ini) -> Result<(String, String, String), String> {
|
||||
let app_section = application_ini
|
||||
.section(Some("App"))
|
||||
.ok_or("application.ini has no App section")?;
|
||||
let product = app_section
|
||||
.get("Name")
|
||||
.ok_or("application.ini has no Name in its App section")?;
|
||||
let version = app_section
|
||||
.get("Version")
|
||||
.ok_or("application.ini has no Version in its App section")?;
|
||||
let build_id = app_section
|
||||
.get("BuildID")
|
||||
.ok_or("application.ini has no BuildID in its App section")?;
|
||||
Ok((
|
||||
product.to_string(),
|
||||
version.to_string(),
|
||||
build_id.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_build_target(_maybe_windows_cpu_arch: Option<&str>) -> Result<String, String> {
|
||||
let mut target = String::new();
|
||||
target.push_str(constants::OS_TARGET);
|
||||
target.push('_');
|
||||
target.push_str(constants::TARGET_XPCOM_ABI);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(windows_cpu_arch) = _maybe_windows_cpu_arch {
|
||||
target.push('-');
|
||||
target.push_str(&windows_cpu_arch);
|
||||
} else {
|
||||
return Err("No CPU Architecture provided (required on Windows)".to_string());
|
||||
}
|
||||
if constants::MOZ_ASAN {
|
||||
target.push_str("-asan");
|
||||
}
|
||||
}
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
fn get_channel(resource_path: &Path) -> Result<String, String> {
|
||||
let channel_prefs_path = resource_path
|
||||
.join("defaults")
|
||||
.join("pref")
|
||||
.join("channel-prefs.js");
|
||||
let channel_prefs_path_str = channel_prefs_path
|
||||
.to_str()
|
||||
.ok_or("Path to channel-prefs.js is not valid Unicode")?;
|
||||
let channel = read_pref(channel_prefs_path_str, "app.update.channel")
|
||||
.map_err(|e| format!("Error parsing channel-prefs.js: {}", e))?;
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
fn get_os_version(_maybe_windows_cpu_arch: Option<&str>) -> Result<String, String> {
|
||||
let (os_name, os_version) = windows::get_sys_info()?;
|
||||
|
||||
let mut version_string = String::new();
|
||||
version_string.push_str(&os_name);
|
||||
version_string.push(' ');
|
||||
version_string.push_str(&os_version);
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(windows_cpu_arch) = _maybe_windows_cpu_arch {
|
||||
version_string.push(' ');
|
||||
version_string.push('(');
|
||||
version_string.push_str(&windows_cpu_arch);
|
||||
version_string.push(')');
|
||||
} else {
|
||||
return Err("No CPU Architecture provided (required on Windows)".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(secondary_libraries) = windows::get_secondary_libraries()? {
|
||||
version_string.push(' ');
|
||||
version_string.push('(');
|
||||
version_string.push_str(&secondary_libraries);
|
||||
version_string.push(')');
|
||||
}
|
||||
|
||||
Ok(encode_uri_component(&version_string))
|
||||
}
|
||||
|
||||
fn get_locale(resource_path: &Path) -> Result<String, String> {
|
||||
let locale_ini_path = resource_path.join("locale.ini");
|
||||
let locale_ini_path_str = locale_ini_path
|
||||
.to_str()
|
||||
.ok_or("Path to locale.ini is not valid Unicode")?;
|
||||
let locale_ini = Ini::load_from_file(locale_ini_path_str)
|
||||
.map_err(|e| format!("Unable to read locale.ini: {}", e))?;
|
||||
let locale_section = locale_ini
|
||||
.section(Some("locale"))
|
||||
.ok_or("locale.ini has no locale section")?;
|
||||
let locale = locale_section
|
||||
.get("locale")
|
||||
.ok_or("locale.ini has no locale in its locale section")?;
|
||||
Ok(locale.clone())
|
||||
}
|
||||
|
||||
/// This mimics Javascript's encodeURIComponent.
|
||||
/// MDN specifies that encodeURIComponent escapes all characters except:
|
||||
/// A-Z a-z 0-9 - _ . ! ~ * ' ( )
|
||||
fn encode_uri_component(value: &str) -> String {
|
||||
const URI_COMPONENT_ASCII_ESCAPE_SET: &AsciiSet = &NON_ALPHANUMERIC
|
||||
.remove(b'-')
|
||||
.remove(b'_')
|
||||
.remove(b'.')
|
||||
.remove(b'!')
|
||||
.remove(b'~')
|
||||
.remove(b'*')
|
||||
.remove(b'\'')
|
||||
.remove(b'(')
|
||||
.remove(b')');
|
||||
utf8_percent_encode(&value, URI_COMPONENT_ASCII_ESCAPE_SET).to_string()
|
||||
}
|
||||
|
||||
fn get_system_capabilities() -> Result<String, String> {
|
||||
let instruction_set = get_instruction_set();
|
||||
let memory_mb = windows::get_memory_mb();
|
||||
|
||||
Ok(format!("ISET:{},MEM:{}", instruction_set, memory_mb))
|
||||
}
|
||||
|
||||
// is_x86_feature_detected!("sse4a") doesn't quite match Firefox's implementation; this should
|
||||
// match more closely. Firefox does not check the brand of the CPU before making the cpuid checks,
|
||||
// whereas is_x86_feature_detected!("sse4a") only makes the cpuid checks if the CPU is the right
|
||||
// brand.
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn has_sse4a() -> bool {
|
||||
const QUERY_MAX_EXTENDED_LEVEL: u32 = 0x8000_0000;
|
||||
const QUERY_EXTENDED_FEATURE_INFO: u32 = 0x8000_0001;
|
||||
const ECX_SSE4A: u32 = 1 << 6;
|
||||
|
||||
let max_extended_level = raw_cpuid::cpuid!(QUERY_MAX_EXTENDED_LEVEL).eax;
|
||||
if max_extended_level < QUERY_EXTENDED_FEATURE_INFO {
|
||||
return false;
|
||||
}
|
||||
|
||||
(raw_cpuid::cpuid!(QUERY_EXTENDED_FEATURE_INFO).ecx & ECX_SSE4A) != 0
|
||||
}
|
||||
|
||||
/// On success, returns a string describing the CPU instruction set. On failure, returns the string
|
||||
/// "unknown".
|
||||
fn get_instruction_set() -> &'static str {
|
||||
cfg_if! {
|
||||
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
|
||||
if is_x86_feature_detected!("sse4.2") {
|
||||
return "SSE4_2";
|
||||
} else if is_x86_feature_detected!("sse4.1") {
|
||||
return "SSE4_1";
|
||||
} else if has_sse4a() {
|
||||
return "SSE4A";
|
||||
} else if is_x86_feature_detected!("ssse3") {
|
||||
return "SSSE3";
|
||||
} else if is_x86_feature_detected!("sse3") {
|
||||
return "SSE3";
|
||||
} else if is_x86_feature_detected!("sse2") {
|
||||
return "SSE2";
|
||||
} else if is_x86_feature_detected!("sse") {
|
||||
return "SSE";
|
||||
} else if is_x86_feature_detected!("mmx") {
|
||||
return "MMX";
|
||||
}
|
||||
"unknown"
|
||||
} else if #[cfg(target_arch = "aarch64")] {
|
||||
"NEON"
|
||||
} else {
|
||||
compile_error!("CPU feature detection not implemented for this arch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On success, returns a tuple of the distribution id and version strings
|
||||
fn get_distribution_info(resource_path: &Path) -> Result<(String, String), String> {
|
||||
let distribution_ini_path = resource_path.join("distribution").join("distribution.ini");
|
||||
let distribution_ini_path_str = distribution_ini_path
|
||||
.to_str()
|
||||
.ok_or("Path to distribution.ini is not valid Unicode")?;
|
||||
// It is ok for the distribution.ini file not to exist.
|
||||
let maybe_distribution_ini = Ini::load_from_file(distribution_ini_path_str).ok();
|
||||
match maybe_distribution_ini {
|
||||
Some(distribution_ini) => {
|
||||
let global_section = distribution_ini
|
||||
.section(Some("Global"))
|
||||
.ok_or("distribution.ini has no Global section")?;
|
||||
let id = global_section
|
||||
.get("id")
|
||||
.ok_or("distribution.ini has no id in its Global section")?;
|
||||
let id = if id.is_empty() {
|
||||
"default".to_string()
|
||||
} else {
|
||||
id.to_string()
|
||||
};
|
||||
let version = global_section
|
||||
.get("version")
|
||||
.ok_or("distribution.ini has no version in its Global section")?;
|
||||
let version = if version.is_empty() {
|
||||
"default".to_string()
|
||||
} else {
|
||||
version.to_string()
|
||||
};
|
||||
Ok((id, version))
|
||||
}
|
||||
None => Ok(("default".to_string(), "default".to_string())),
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
//! This module contains a wrapper around the prefs_parser that allows it to be
|
||||
//! more easily used to extract a single pref value from a file.
|
||||
|
||||
use prefs_parser::{prefs_parser_parse, PrefType, PrefValue, PrefValueKind};
|
||||
use std::{
|
||||
cell::Cell,
|
||||
ffi::{CStr, CString},
|
||||
fs::File,
|
||||
io::Read,
|
||||
os::raw::c_char,
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
static PREF_NAME_TO_READ: Cell<String> = Cell::new(String::new());
|
||||
static PREF_VALUE: Cell<Option<String>> = Cell::new(None);
|
||||
static ERROR_COUNT: Cell<u32> = Cell::new(0);
|
||||
}
|
||||
|
||||
/// This function reads the file at the path specified and finds the value for the pref name
|
||||
/// specified. The function is capable of reading a String type pref only.
|
||||
///
|
||||
/// The underlying mechanism for doing this is prefs_parser. Unfortunately, prefs_parser is made to
|
||||
/// be convenient to call from C++ and is difficult to call from Rust. This function wraps that
|
||||
/// interface.
|
||||
pub fn read_pref(path: &str, pref_to_read: &str) -> Result<String, String> {
|
||||
PREF_NAME_TO_READ.with(|pref_value| pref_value.set(pref_to_read.to_owned()));
|
||||
PREF_VALUE.with(|pref_value| pref_value.set(None));
|
||||
ERROR_COUNT.with(|error_count| error_count.set(0));
|
||||
|
||||
let mut file =
|
||||
File::open(path).map_err(|e| format!("Unable to open \"{}\" to read pref: {}", path, e))?;
|
||||
let mut data = String::new();
|
||||
file.read_to_string(&mut data)
|
||||
.map_err(|e| format!("Unable to read \"{}\": {}", path, e))?;
|
||||
|
||||
let path_cstring =
|
||||
CString::new(path).map_err(|e| format!("Unable to convert path to cstring: {}", e))?;
|
||||
let data_cstring = CString::new(data).map_err(|e| {
|
||||
format!(
|
||||
"Unable to convert contents of \"{}\" to a cstring: {}",
|
||||
path, e
|
||||
)
|
||||
})?;
|
||||
|
||||
let success = prefs_parser_parse(
|
||||
path_cstring.as_ptr(),
|
||||
PrefValueKind::Default,
|
||||
data_cstring.as_ptr(),
|
||||
data_cstring.as_bytes().len(),
|
||||
pref_fn,
|
||||
error_fn,
|
||||
);
|
||||
|
||||
if !success {
|
||||
return Err("prefs_parser_parse returned false".to_string());
|
||||
}
|
||||
let error_count = ERROR_COUNT.with(|error_count| error_count.get());
|
||||
if error_count > 0 {
|
||||
return Err(format!("Encountered {} errors while parsing", error_count));
|
||||
}
|
||||
let pref_value = PREF_VALUE.with(|pref_value| pref_value.take());
|
||||
pref_value.ok_or_else(|| format!("Pref name \"{}\" not found", pref_to_read))
|
||||
}
|
||||
|
||||
/// This function is crafted to be compatible with prefs_parser::PrefFn.
|
||||
/// It accepts pref data and searches for a string pref that with a name matching the one stored in
|
||||
/// PREF_NAME_TO_READ. When it finds a matching pref, it stores its value in PREF_VALUE.
|
||||
extern "C" fn pref_fn(
|
||||
pref_name: *const c_char,
|
||||
pref_type: PrefType,
|
||||
_pref_value_kind: PrefValueKind,
|
||||
pref_value: PrefValue,
|
||||
_is_sticky: bool,
|
||||
_is_locked: bool,
|
||||
) {
|
||||
if pref_type != PrefType::String {
|
||||
return;
|
||||
}
|
||||
let pref_name_str = match unsafe { CStr::from_ptr(pref_name) }.to_str() {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
let pref_matches = PREF_NAME_TO_READ.with(|pref_cell| -> bool {
|
||||
let pref_to_read = pref_cell.take();
|
||||
let pref_matches = pref_name_str == pref_to_read;
|
||||
pref_cell.set(pref_to_read);
|
||||
pref_matches
|
||||
});
|
||||
if !pref_matches {
|
||||
return;
|
||||
}
|
||||
let pref_value_str = match unsafe { CStr::from_ptr(pref_value.string_val) }.to_str() {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
PREF_VALUE.with(|pref_value| pref_value.set(Some(pref_value_str.to_owned())));
|
||||
}
|
||||
|
||||
/// This function is crafted to be compatible with prefs_parser::ErrorFn.
|
||||
/// When an error is passed to it, it increments ERROR_COUNT.
|
||||
extern "C" fn error_fn(_msg: *const c_char) {
|
||||
ERROR_COUNT.with(|error_cell| {
|
||||
let prev_error_count = error_cell.get();
|
||||
error_cell.set(prev_error_count + 1);
|
||||
});
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
/* 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(windows)]
|
||||
|
||||
//! This file contains the Windows-specific code for generating the update URL
|
||||
//! Currently, this crate only supports being built on Windows, but this code is separated anyways
|
||||
//! because we may later want to extend this to other OS's.
|
||||
|
||||
use std::{mem, ptr};
|
||||
use winapi::{
|
||||
shared::{minwindef::BOOL, winerror::ERROR_SUCCESS},
|
||||
um::{
|
||||
errhandlingapi::GetLastError,
|
||||
sysinfoapi::{
|
||||
GetNativeSystemInfo, GetVersionExW, GlobalMemoryStatusEx, LPSYSTEM_INFO,
|
||||
MEMORYSTATUSEX, SYSTEM_INFO,
|
||||
},
|
||||
winnt::{
|
||||
LPOSVERSIONINFOEXW, LPOSVERSIONINFOW, OSVERSIONINFOEXW, PROCESSOR_ARCHITECTURE_AMD64,
|
||||
PROCESSOR_ARCHITECTURE_ARM64, PROCESSOR_ARCHITECTURE_IA64,
|
||||
PROCESSOR_ARCHITECTURE_INTEL, PROCESSOR_ARCHITECTURE_UNKNOWN, VER_PLATFORM_WIN32_NT,
|
||||
VER_PLATFORM_WIN32_WINDOWS,
|
||||
},
|
||||
winreg::{RegGetValueW, HKEY_LOCAL_MACHINE, RRF_RT_REG_DWORD, RRF_SUBKEY_WOW6464KEY},
|
||||
},
|
||||
};
|
||||
use wio::wide::ToWide;
|
||||
|
||||
/// Returns a string indicating the CPU architecture of the current platform.
|
||||
/// It will be one of:
|
||||
/// "x86"
|
||||
/// "x64"
|
||||
/// "IA64"
|
||||
/// "aarch64"
|
||||
/// "unknown"
|
||||
pub fn get_cpu_arch() -> &'static str {
|
||||
let arch = unsafe {
|
||||
let mut info: SYSTEM_INFO = mem::zeroed();
|
||||
info.u.s_mut().wProcessorArchitecture = PROCESSOR_ARCHITECTURE_UNKNOWN;
|
||||
GetNativeSystemInfo(&mut info as LPSYSTEM_INFO);
|
||||
info.u.s().wProcessorArchitecture
|
||||
};
|
||||
match arch {
|
||||
PROCESSOR_ARCHITECTURE_ARM64 => "aarch64",
|
||||
PROCESSOR_ARCHITECTURE_AMD64 => "x64",
|
||||
PROCESSOR_ARCHITECTURE_IA64 => "IA64",
|
||||
PROCESSOR_ARCHITECTURE_INTEL => "x86",
|
||||
_ => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
/// This function will return a tuple of the OS name and version strings.
|
||||
pub fn get_sys_info() -> Result<(String, String), String> {
|
||||
let (platform_id, major_ver, minor_ver, sp_major_ver, sp_minor_ver, build_number) = unsafe {
|
||||
let mut info: OSVERSIONINFOEXW = mem::zeroed();
|
||||
info.dwOSVersionInfoSize = mem::size_of::<OSVERSIONINFOEXW>() as u32;
|
||||
let success: BOOL = GetVersionExW(&mut info as LPOSVERSIONINFOEXW as LPOSVERSIONINFOW);
|
||||
if success == 0 {
|
||||
return Err(format!("GetVersionExW failed: {:#X}", GetLastError()));
|
||||
}
|
||||
(
|
||||
info.dwPlatformId,
|
||||
info.dwMajorVersion,
|
||||
info.dwMinorVersion,
|
||||
info.wServicePackMajor,
|
||||
info.wServicePackMinor,
|
||||
info.dwBuildNumber,
|
||||
)
|
||||
};
|
||||
let os_name = match platform_id {
|
||||
VER_PLATFORM_WIN32_NT => "Windows_NT".to_string(),
|
||||
_ => "Windows_Unknown".to_string(),
|
||||
};
|
||||
let mut os_version =
|
||||
if platform_id == VER_PLATFORM_WIN32_NT || platform_id == VER_PLATFORM_WIN32_WINDOWS {
|
||||
format!(
|
||||
"{}.{}.{}.{}.{}",
|
||||
major_ver, minor_ver, sp_major_ver, sp_minor_ver, build_number
|
||||
)
|
||||
} else {
|
||||
"0.0.unknown".to_string()
|
||||
};
|
||||
|
||||
if major_ver >= 10 {
|
||||
os_version.push('.');
|
||||
os_version.push_str(&get_ubr());
|
||||
}
|
||||
|
||||
Ok((os_name, os_version))
|
||||
}
|
||||
|
||||
/// Gets the Update Build Revision of the current Windows platform. This will not be available on
|
||||
/// systems prior to Windows 10. If the value cannot be determined, the string "unknown" will be
|
||||
/// returned.
|
||||
pub fn get_ubr() -> String {
|
||||
let key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
|
||||
let value = "UBR";
|
||||
let wide_key: Vec<u16> = key.to_wide_null();
|
||||
let wide_value: Vec<u16> = value.to_wide_null();
|
||||
|
||||
let ubr = unsafe {
|
||||
let mut ubr: u32 = 0;
|
||||
let mut ubr_size: u32 = mem::size_of::<u32>() as u32;
|
||||
|
||||
let status = RegGetValueW(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
wide_key.as_ptr(),
|
||||
wide_value.as_ptr(),
|
||||
RRF_RT_REG_DWORD | RRF_SUBKEY_WOW6464KEY,
|
||||
ptr::null_mut(),
|
||||
&mut ubr as *mut u32 as *mut _,
|
||||
&mut ubr_size,
|
||||
);
|
||||
|
||||
if status != ERROR_SUCCESS as i32 {
|
||||
return "unknown".to_string();
|
||||
}
|
||||
|
||||
ubr
|
||||
};
|
||||
|
||||
ubr.to_string()
|
||||
}
|
||||
|
||||
/// This function gets a string describing the relevant secondary libraries. On Windows, there are
|
||||
/// never any relevant secondary libraries, so this function will always return None.
|
||||
pub fn get_secondary_libraries() -> Result<Option<String>, String> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// On success, returns a string indicating how many megabytes of memory are available on the
|
||||
/// system. On failure, returns the string "unknown".
|
||||
pub fn get_memory_mb() -> String {
|
||||
let memory_bytes: u64 = unsafe {
|
||||
let mut mem_info: MEMORYSTATUSEX = mem::zeroed();
|
||||
mem_info.dwLength = mem::size_of::<MEMORYSTATUSEX>() as u32;
|
||||
let success: BOOL = GlobalMemoryStatusEx(&mut mem_info);
|
||||
if success == 0 {
|
||||
return "unknown".to_string();
|
||||
}
|
||||
mem_info.ullTotalPhys
|
||||
};
|
||||
|
||||
format!("{:.0}", memory_bytes as f64 / 1024_f64 / 1024_f64)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity
|
||||
version="1.0.0.0"
|
||||
processorArchitecture="*"
|
||||
name="Mozilla Update Agent"
|
||||
type="win32"
|
||||
/>
|
||||
<description>Mozilla Update Agent</description>
|
||||
<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<ms_asmv3:security>
|
||||
<ms_asmv3:requestedPrivileges>
|
||||
<ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</ms_asmv3:requestedPrivileges>
|
||||
</ms_asmv3:security>
|
||||
</ms_asmv3:trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
|
@ -1,3 +0,0 @@
|
|||
#include <winresrc.h>
|
||||
|
||||
1 RT_MANIFEST "updateagent.exe.manifest"
|
|
@ -39,9 +39,6 @@ if CONFIG["MOZ_MAINTENANCE_SERVICE"] or CONFIG["MOZ_UPDATER"]:
|
|||
if CONFIG["MOZ_MAINTENANCE_SERVICE"]:
|
||||
DIRS += ["components/maintenanceservice"]
|
||||
|
||||
if CONFIG["MOZ_UPDATE_AGENT"]:
|
||||
DIRS += ["components/updateagent"]
|
||||
|
||||
DIRS += ["xre"]
|
||||
|
||||
if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android":
|
||||
|
|
|
@ -14,7 +14,7 @@ serde_derive = "1.0"
|
|||
serde_json = "1.0"
|
||||
url = "2.1"
|
||||
viaduct = { git = "https://github.com/mozilla/application-services", rev = "8a576fbe79199fa8664f64285524017f74ebcc5f"}
|
||||
wineventlog = { path = "../../../components/updateagent/wineventlog"}
|
||||
wineventlog = { path = "wineventlog"}
|
||||
wio = "0.2"
|
||||
winapi = { version = "0.3", features = ["errhandlingapi", "handleapi", "minwindef", "winerror", "wininet", "winuser"] }
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ following will be built:
|
|||
1. the maintenance service (when `--enable-maintenance-service`);
|
||||
2. the updater binary (when `MOZ_UPDATER=1`);
|
||||
3. the Windows Default Browser Agent (when `--enable-default-browser-agent`);
|
||||
4. the Background Update Agent (when `--enable-update-agent`).
|
||||
|
||||
Packaging the installer and uninstaller is not yet supported: instead,
|
||||
use an (artifact) build with `--enable-application=browser`.
|
||||
use an (artifact) build with `--enable-application=browser`.
|
||||
|
|
|
@ -20,11 +20,6 @@ if CONFIG['MOZ_MAINTENANCE_SERVICE']:
|
|||
'/toolkit/components/maintenanceservice'
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_UPDATE_AGENT']:
|
||||
DIRS += [
|
||||
'/toolkit/components/updateagent',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_UPDATER']:
|
||||
# NSS (and NSPR).
|
||||
DIRS += [
|
||||
|
|
Загрузка…
Ссылка в новой задаче