// Copyright (C) 2024 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Account management for task scheduling and user isolation.
//!
//! This module handles OS account state tracking, subscription to account
//! events, and management of tasks associated with specific user accounts. It
//! ensures tasks are properly isolated between user accounts and handles
//! account lifecycle events.
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{LazyLock, Mutex, Once};
pub(crate) use ffi::*;
use super::database::RequestDb;
use crate::manage::events::TaskManagerEvent;
use crate::manage::task_manager::TaskManagerTx;
use crate::utils::{call_once, runtime_spawn};
/// Account-related events that require task manager attention.
#[derive(Debug)]
pub(crate) enum AccountEvent {
/// Triggered when a user account is removed from the system.
Remove(i32),
/// Triggered when account state (foreground/background) changes.
Changed,
}
/// Active accounts containing foreground and background user account IDs.
#[derive(Clone)]
struct ActiveAccounts {
/// Currently active foreground user account IDs (supports multi-foreground).
foreground: HashSet<i32>,
/// List of background user account IDs that are active but not in foreground.
background: HashSet<i32>,
}
/// Global active accounts protected by a single mutex to avoid deadlock.
static ACTIVE_ACCOUNTS: LazyLock<Mutex<ActiveAccounts>> = LazyLock::new(|| {
Mutex::new(ActiveAccounts {
foreground: HashSet::new(),
background: HashSet::new(),
})
});
/// Flag indicating if an account update operation is in progress.
static UPDATE_FLAG: AtomicBool = AtomicBool::new(false);
/// Task manager transmitter for account event notifications.
///
/// # Safety
///
/// This static variable is accessed using unsafe operations and should only be
/// modified during initialization via `registry_account_subscribe`.
static mut TASK_MANAGER_TX: Option<TaskManagerTx> = None;
/// Removes all tasks associated with the specified user account.
///
/// # Arguments
///
/// * `user_id` - The identifier of the user account whose tasks should be
/// removed.
///
/// # Notes
///
/// This function is typically called when a user account is removed from the
/// system to ensure proper cleanup of associated resources. It deletes all
/// tasks in the database belonging to the specified user account.
pub(crate) fn remove_account_tasks(user_id: i32) {
info!("delete database task, uid {}", user_id);
let request_db = RequestDb::get_instance();
request_db.delete_all_account_tasks(user_id);
}
/// Initiates an asynchronous update of account information.
///
/// # Arguments
///
/// * `task_manager` - Transmitter for sending account change events to the task
/// manager.
///
/// # Notes
///
/// This function ensures only one account update operation runs at a time using
/// an atomic flag. If an update is already in progress, this call will be
/// ignored. The actual update is performed asynchronously in a separate task.
pub(crate) fn update_accounts(task_manager: TaskManagerTx) {
// Use compare_exchange to ensure only one update runs at a time
if UPDATE_FLAG
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
runtime_spawn(AccountUpdater::new(task_manager).update());
}
}
/// Retrieves the current set of active accounts.
///
/// # Returns
///
/// A tuple containing:
/// - A `HashSet` of foreground account IDs (supports multi-foreground)
/// - A `HashSet` of all active account IDs (both foreground and background)
///
/// # Notes
///
/// This function safely accesses the global account state to provide the
/// current active account information. This is typically used for task
/// filtering and permission checks based on user identity.
pub(crate) fn query_active_accounts() -> (HashSet<u64>, HashSet<u64>) {
let accounts = ACTIVE_ACCOUNTS.lock().unwrap();
let foreground_accounts_u64: HashSet<u64> =
accounts.foreground.iter().map(|&id| id as u64).collect();
let mut active_accounts = foreground_accounts_u64.clone();
active_accounts.extend(accounts.background.iter().map(|&id| id as u64));
(foreground_accounts_u64, active_accounts)
}
/// Internal utility for updating account information asynchronously.
struct AccountUpdater {
/// Flag indicating if any account information has changed during the
/// update.
change_flag: bool,
/// Transmitter for sending events to the task manager.
task_manager: TaskManagerTx,
}
impl AccountUpdater {
/// Creates a new AccountUpdater instance.
///
/// # Arguments
///
/// * `task_manager` - Transmitter for sending account change events.
fn new(task_manager: TaskManagerTx) -> Self {
Self {
change_flag: false,
task_manager,
}
}
/// Performs the asynchronous account information update.
///
/// This method retrieves the current foreground and background accounts
/// and updates the global state if changes are detected.
#[cfg_attr(not(feature = "oh"), allow(unused))]
async fn update(mut self) {
info!("AccountUpdate Start");
// Get previous account state for comparison (lock is released after clone)
let old_state = {
let state = ACTIVE_ACCOUNTS.lock().unwrap();
(state.foreground.clone(), state.background.clone())
};
// Get new account information from system
#[cfg(feature = "oh")]
let new_foreground = get_foreground_accounts().await;
#[cfg(feature = "oh")]
let new_background = get_background_accounts().await;
// Update global state if accounts changed
#[cfg(feature = "oh")]
if let (Some(foreground_accounts), Some(background_accounts)) =
(new_foreground, new_background)
{
if old_state.0 != foreground_accounts || old_state.1 != background_accounts {
self.change_flag = true;
let mut state = ACTIVE_ACCOUNTS.lock().unwrap();
state.foreground = foreground_accounts;
state.background = background_accounts;
}
}
// The change notification is handled in the Drop implementation
}
}
impl Drop for AccountUpdater {
/// Cleans up after an account update operation.
///
/// This implementation:
/// 1. Resets the global update flag to allow new updates
/// 2. Sends a change notification to the task manager if any account state
/// changed
fn drop(&mut self) {
info!("AccountUpdate Finished");
// Reset the update flag to allow new update operations
UPDATE_FLAG.store(false, Ordering::SeqCst);
// Notify task manager only if actual changes occurred
if self.change_flag {
info!("AccountInfo changed, notify task manager");
self.task_manager
.send_event(TaskManagerEvent::Account(AccountEvent::Changed));
}
}
}
#[cfg(feature = "oh")]
/// Retrieves the currently active foreground OS accounts (supports multi-foreground).
///
/// # Returns
///
/// `Some(HashSet<account_ids>)` if foreground accounts were successfully retrieved,
/// `None` if the operation failed after multiple retries.
///
/// # Notes
///
/// This function attempts to retrieve foreground accounts up to 10 times
/// with a 500ms delay between retries. It logs errors and reports system events
/// when retrieval fails.
async fn get_foreground_accounts() -> Option<HashSet<i32>> {
// Retry up to 10 times with 500ms delay
for i in 0..10 {
let mut accounts = Vec::with_capacity(10);
let res = GetForegroundOsAccounts(&mut accounts);
if res == 0 {
return Some(accounts.into_iter().collect());
} else {
error!("GetForegroundOsAccounts failed: {} retry {} times", res, i);
sys_event!(
ExecFault,
DfxCode::OS_ACCOUNT_FAULT_01,
&format!("GetForegroundOsAccounts failed: {} retry {} times", res, i)
);
ylong_runtime::time::sleep(std::time::Duration::from_millis(500)).await;
}
}
None
}
#[cfg(feature = "oh")]
/// Retrieves all currently active background OS accounts.
///
/// # Returns
///
/// `Some(HashSet<account_ids>)` if background accounts were successfully retrieved,
/// `None` if the operation failed after multiple retries.
///
/// # Notes
///
/// This function attempts to retrieve background accounts up to 10 times
/// with a 500ms delay between retries. It logs errors and reports system events
/// when retrieval fails.
async fn get_background_accounts() -> Option<HashSet<i32>> {
// Retry up to 10 times with 500ms delay
for i in 0..10 {
let mut accounts = Vec::with_capacity(10);
let res = GetBackgroundOsAccounts(&mut accounts);
if res == 0 {
return Some(accounts.into_iter().collect());
} else {
error!("GetBackgroundOsAccounts failed: {} retry {} times", res, i);
sys_event!(
ExecFault,
DfxCode::INVALID_IPC_MESSAGE_A00,
&format!("GetBackgroundOsAccounts failed: {} retry {} times", res, i)
);
ylong_runtime::time::sleep(std::time::Duration::from_millis(500)).await;
}
}
None
}
#[cfg(feature = "oh")]
/// Registers subscriptions for OS account state changes.
///
/// # Arguments
///
/// * `task_manager` - Transmitter for sending account events to the task
/// manager.
///
/// # Notes
///
/// This function:
/// 1. Stores the task manager transmitter for future use
/// 2. Subscribes to various account events (switched, activated, removed,
/// stopped)
/// 3. Sets up appropriate handlers for each event type
/// 4. Performs an initial account update
///
/// The function will retry subscription operations indefinitely with a 500ms
/// delay between attempts until successful.
pub(crate) fn registry_account_subscribe(task_manager: TaskManagerTx) {
static ONCE: Once = Once::new();
// Store task manager reference once during initialization
call_once(&ONCE, || unsafe {
TASK_MANAGER_TX = Some(task_manager.clone());
});
info!("registry_account_subscribe");
// Subscribe to account switched events
loop {
let ret = RegistryAccountSubscriber(
OS_ACCOUNT_SUBSCRIBE_TYPE::SWITCHED,
Box::new(task_manager.clone()),
|_, _| {}, // No action needed for switched notification
|_new_id, _old_id, task_manager| update_accounts(task_manager.clone()),
);
if ret != 0 {
error!(
"registry_account_switch_subscribe failed: {} retry 500ms later",
ret
);
sys_event!(
ExecFault,
DfxCode::OS_ACCOUNT_FAULT_00,
&format!(
"registry_account_switch_subscribe failed: {} retry 500ms later",
ret
)
);
std::thread::sleep(std::time::Duration::from_millis(500));
} else {
break;
}
}
// Subscribe to account activated events
loop {
let ret = RegistryAccountSubscriber(
OS_ACCOUNT_SUBSCRIBE_TYPE::ACTIVATED,
Box::new(task_manager.clone()),
|_id, task_manager| update_accounts(task_manager.clone()),
|_, _, _| {}, // No action needed for activation switch callback
);
if ret != 0 {
error!(
"registry_account_active_subscribe failed: {} retry 500ms later",
ret
);
sys_event!(
ExecFault,
DfxCode::OS_ACCOUNT_FAULT_00,
&format!(
"registry_account_active_subscribe failed: {} retry 500ms later",
ret
)
);
std::thread::sleep(std::time::Duration::from_millis(500));
} else {
break;
}
}
// Subscribe to account removed events
loop {
let ret = RegistryAccountSubscriber(
OS_ACCOUNT_SUBSCRIBE_TYPE::REMOVED,
Box::new(task_manager.clone()),
|id, task_manager| {
// Send specific remove event with account ID
task_manager.send_event(TaskManagerEvent::Account(AccountEvent::Remove(*id)));
},
|_, _, _| {}, // No action needed for remove switch callback
);
if ret != 0 {
error!(
"registry_account_remove_subscribe failed: {} retry 500ms later",
ret
);
sys_event!(
ExecFault,
DfxCode::OS_ACCOUNT_FAULT_00,
&format!(
"registry_account_remove_subscribe failed: {} retry 500ms later",
ret
)
);
std::thread::sleep(std::time::Duration::from_millis(500));
} else {
break;
}
}
// Subscribe to account stopped events
loop {
let ret = RegistryAccountSubscriber(
OS_ACCOUNT_SUBSCRIBE_TYPE::STOPPED,
Box::new(task_manager.clone()),
|_id, task_manager| update_accounts(task_manager.clone()),
|_, _, _| {}, // No action needed for stopped switch callback
);
if ret != 0 {
error!(
"registry_account_stop_subscribe failed: {} retry 500ms later",
ret
);
sys_event!(
ExecFault,
DfxCode::OS_ACCOUNT_FAULT_00,
&format!(
"registry_account_stop_subscribe failed: {} retry 500ms later",
ret
)
);
std::thread::sleep(std::time::Duration::from_millis(500));
} else {
break;
}
}
// Perform initial account state update
update_accounts(task_manager.clone());
}
impl RequestDb {
/// Deletes all tasks associated with a specific user account from the
/// database.
///
/// # Arguments
///
/// * `user_id` - The identifier of the user account whose tasks should be
/// deleted.
///
/// # Notes
///
/// This method calculates the actual user ID by dividing the UID by 200000,
/// which appears to be a system-specific way of extracting the base user
/// ID. Errors during execution are logged and reported as system
/// events.
pub(crate) fn delete_all_account_tasks(&self, user_id: i32) {
// Calculate the actual user ID component from the full UID
let sql = format!("DELETE from request_task WHERE uid/200000 = {}", user_id);
if let Err(e) = self.execute(&sql) {
error!("delete_all_account_tasks failed: {}", e);
sys_event!(
ExecFault,
DfxCode::RDB_FAULT_04,
&format!("delete_all_account_tasks failed: {}", e)
);
};
}
}
/// Reads the current process's userId from the access token.
///
/// samgr stamps the per-user userId into the process access token when
/// launching the per-user SA; read it back via one ioctl on
/// `/dev/access_token_id`. A plain Rust fn (not a cxx bridge item) so it can be
/// `#[cfg]`-gated freely: the cxxbridge CLI does not see rustc feature flags, so
/// a cfg-gated bridge item would lose its C++ shim at link time.
#[cfg(feature = "multi-instance")]
pub(crate) fn get_user_id_from_token() -> i32 {
use std::os::unix::io::AsRawFd;
// ACCESS_TOKENID_GET_USERID = _IOR('A', 13, uint32_t).
const ACCESS_TOKENID_GET_USERID: libc::Ioctl = libc::_IOR::<u32>(b'A' as u32, 13);
let Ok(file) = std::fs::File::open("/dev/access_token_id") else {
return 0;
};
let mut user_id: u32 = 0;
// SAFETY: `file` is an open File (raw fd valid), and `&mut user_id` points
// to a writable u32 sized exactly as the ioctl's output arg (_IOR, fixed 4
// bytes). On failure ioctl returns -1 and leaves the buffer unchanged, so no
// uninitialized data escapes the `rc < 0` guard.
let rc = unsafe { libc::ioctl(file.as_raw_fd(), ACCESS_TOKENID_GET_USERID, &mut user_id) };
if rc < 0 {
return 0;
}
user_id as i32
}
// Foreign function interface for interacting with OS account services
#[cxx::bridge(namespace = "OHOS::Request")]
mod ffi {
#[repr(i32)]
enum OS_ACCOUNT_SUBSCRIBE_TYPE {
INVALID_TYPE = -1,
ACTIVATED = 0,
ACTIVATING,
UNLOCKED,
CREATED,
REMOVED,
STOPPING,
STOPPED,
SWITCHING,
SWITCHED,
}
extern "Rust" {
type TaskManagerTx;
}
unsafe extern "C++" {
include!("account.h");
include!("os_account_subscribe_info.h");
include!("c_request_database.h");
type OS_ACCOUNT_SUBSCRIBE_TYPE;
fn GetForegroundOsAccounts(accounts: &mut Vec<i32>) -> i32;
fn GetBackgroundOsAccounts(accounts: &mut Vec<i32>) -> i32;
fn RegistryAccountSubscriber(
subscribe_type: OS_ACCOUNT_SUBSCRIBE_TYPE,
task_manager: Box<TaskManagerTx>,
on_accounts_changed: fn(&i32, task_manager: &TaskManagerTx),
on_accounts_switch: fn(&i32, &i32, task_manager: &TaskManagerTx),
) -> i32;
fn GetOhosAccountUid() -> String;
}
}
// Test module for account management functionality
#[cfg(feature = "oh")]
#[cfg(test)]
mod ut_account {
include!("../../tests/ut/manage/ut_account.rs");
}