Add VM ID database and maintenance functionality

Convert `VirtualizationServiceInternal` to support both the
`IVirtualizationServiceInternal` interface and (optionally) the
`IVirtualizationMaintenance` interface.

Support for the latter has state held in a `maintenance::State` item,
holding both:
- A reference to the device's Secretkeeper instance
- An SQLite database of VM IDs and the corresponding (user_id, app_id).

The latter is implemented in a new maintenance::vmdb submodule.

Bug: 294177871
Test: virtualizationservice_test
Change-Id: I0c2f482252bc97dfdb75dd2e3a43883ab0eb3a77
diff --git a/virtualizationservice/src/maintenance/vmdb.rs b/virtualizationservice/src/maintenance/vmdb.rs
new file mode 100644
index 0000000..bdff034
--- /dev/null
+++ b/virtualizationservice/src/maintenance/vmdb.rs
@@ -0,0 +1,265 @@
+// Copyright 2024, The Android Open Source Project
+//
+// 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.
+
+//! Database of VM IDs.
+
+use anyhow::{Context, Result};
+use log::{debug, error, info, warn};
+use rusqlite::{params, params_from_iter, Connection, OpenFlags, Rows};
+use std::path::PathBuf;
+
+/// Subdirectory to hold the database.
+const DB_DIR: &str = "vmdb";
+
+/// Name of the file that holds the database.
+const DB_FILENAME: &str = "vmids.sqlite";
+
+/// Maximum number of host parameters in a single SQL statement.
+/// (Default value of `SQLITE_LIMIT_VARIABLE_NUMBER` for <= 3.32.0)
+const MAX_VARIABLES: usize = 999;
+
+/// Identifier for a VM and its corresponding secret.
+pub type VmId = [u8; 64];
+
+/// Representation of an on-disk database of VM IDs.
+pub struct VmIdDb {
+    conn: Connection,
+}
+
+impl VmIdDb {
+    /// Connect to the VM ID database file held in the given directory, creating it if necessary.
+    /// The second return value indicates whether a new database file was created.
+    ///
+    /// This function assumes no other threads/processes are attempting to connect concurrently.
+    pub fn new(db_dir: &str) -> Result<(Self, bool)> {
+        let mut db_path = PathBuf::from(db_dir);
+        db_path.push(DB_DIR);
+        if !db_path.exists() {
+            std::fs::create_dir(&db_path).context("failed to create {db_path:?}")?;
+            info!("created persistent db dir {db_path:?}");
+        }
+
+        db_path.push(DB_FILENAME);
+        let (flags, created) = if db_path.exists() {
+            debug!("connecting to existing database {db_path:?}");
+            (
+                OpenFlags::SQLITE_OPEN_READ_WRITE
+                    | OpenFlags::SQLITE_OPEN_URI
+                    | OpenFlags::SQLITE_OPEN_NO_MUTEX,
+                false,
+            )
+        } else {
+            info!("creating fresh database {db_path:?}");
+            (
+                OpenFlags::SQLITE_OPEN_READ_WRITE
+                    | OpenFlags::SQLITE_OPEN_CREATE
+                    | OpenFlags::SQLITE_OPEN_URI
+                    | OpenFlags::SQLITE_OPEN_NO_MUTEX,
+                true,
+            )
+        };
+        let mut result = Self {
+            conn: Connection::open_with_flags(db_path, flags)
+                .context(format!("failed to open/create DB with {flags:?}"))?,
+        };
+
+        if created {
+            result.init_tables().context("failed to create tables")?;
+        }
+        Ok((result, created))
+    }
+
+    /// Delete the associated database file.
+    pub fn delete_db_file(self, db_dir: &str) {
+        let mut db_path = PathBuf::from(db_dir);
+        db_path.push(DB_DIR);
+        db_path.push(DB_FILENAME);
+
+        // Drop the connection before removing the backing file.
+        drop(self);
+        warn!("removing database file {db_path:?}");
+        if let Err(e) = std::fs::remove_file(&db_path) {
+            error!("failed to remove database file {db_path:?}: {e:?}");
+        }
+    }
+
+    /// Create the database table and indices.
+    fn init_tables(&mut self) -> Result<()> {
+        self.conn
+            .execute(
+                "CREATE TABLE IF NOT EXISTS main.vmids (
+                     vm_id BLOB PRIMARY KEY,
+                     user_id INTEGER,
+                     app_id INTEGER
+                 ) WITHOUT ROWID;",
+                (),
+            )
+            .context("failed to create table")?;
+        self.conn
+            .execute("CREATE INDEX IF NOT EXISTS main.vmids_user_index ON vmids(user_id);", [])
+            .context("Failed to create user index")?;
+        self.conn
+            .execute(
+                "CREATE INDEX IF NOT EXISTS main.vmids_app_index ON vmids(user_id, app_id);",
+                [],
+            )
+            .context("Failed to create app index")?;
+        Ok(())
+    }
+
+    /// Add the given VM ID into the database.
+    #[allow(dead_code)] // TODO(b/294177871): connect this up
+    pub fn add_vm_id(&mut self, vm_id: &VmId, user_id: i32, app_id: i32) -> Result<()> {
+        let _rows = self
+            .conn
+            .execute(
+                "REPLACE INTO main.vmids (vm_id, user_id, app_id) VALUES (?1, ?2, ?3);",
+                params![vm_id, &user_id, &app_id],
+            )
+            .context("failed to add VM ID")?;
+        Ok(())
+    }
+
+    /// Remove the given VM IDs from the database.  The collection of IDs is assumed to be smaller
+    /// than the maximum number of SQLite parameters.
+    pub fn delete_vm_ids(&mut self, vm_ids: &[VmId]) -> Result<()> {
+        assert!(vm_ids.len() < MAX_VARIABLES);
+        let mut vars = "?,".repeat(vm_ids.len());
+        vars.pop(); // remove trailing comma
+        let sql = format!("DELETE FROM main.vmids WHERE vm_id IN ({});", vars);
+        let mut stmt = self.conn.prepare(&sql).context("failed to prepare DELETE stmt")?;
+        let _rows = stmt.execute(params_from_iter(vm_ids)).context("failed to delete VM IDs")?;
+        Ok(())
+    }
+
+    /// Return the VM IDs associated with Android user ID `user_id`.
+    pub fn vm_ids_for_user(&mut self, user_id: i32) -> Result<Vec<VmId>> {
+        let mut stmt = self
+            .conn
+            .prepare("SELECT vm_id FROM main.vmids WHERE user_id = ?;")
+            .context("failed to prepare SELECT stmt")?;
+        let rows = stmt.query(params![user_id]).context("query failed")?;
+        Self::vm_ids_from_rows(rows)
+    }
+
+    /// Return the VM IDs associated with `(user_id, app_id)`.
+    pub fn vm_ids_for_app(&mut self, user_id: i32, app_id: i32) -> Result<Vec<VmId>> {
+        let mut stmt = self
+            .conn
+            .prepare("SELECT vm_id FROM main.vmids WHERE user_id = ? AND app_id = ?;")
+            .context("failed to prepare SELECT stmt")?;
+        let rows = stmt.query(params![user_id, app_id]).context("query failed")?;
+        Self::vm_ids_from_rows(rows)
+    }
+
+    /// Retrieve a collection of VM IDs from database rows.
+    fn vm_ids_from_rows(mut rows: Rows) -> Result<Vec<VmId>> {
+        let mut vm_ids: Vec<VmId> = Vec::new();
+        while let Some(row) = rows.next().context("failed row unpack")? {
+            match row.get(0) {
+                Ok(vm_id) => vm_ids.push(vm_id),
+                Err(e) => log::error!("failed to parse row: {e:?}"),
+            }
+        }
+
+        Ok(vm_ids)
+    }
+}
+
+#[cfg(test)]
+pub fn new_test_db() -> VmIdDb {
+    let mut db = VmIdDb { conn: Connection::open_in_memory().unwrap() };
+    db.init_tables().unwrap();
+    db
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    const VM_ID1: VmId = [1u8; 64];
+    const VM_ID2: VmId = [2u8; 64];
+    const VM_ID3: VmId = [3u8; 64];
+    const VM_ID4: VmId = [4u8; 64];
+    const VM_ID5: VmId = [5u8; 64];
+    const USER1: i32 = 1;
+    const USER2: i32 = 2;
+    const USER3: i32 = 3;
+    const USER_UNKNOWN: i32 = 4;
+    const APP_A: i32 = 50;
+    const APP_B: i32 = 60;
+    const APP_C: i32 = 70;
+    const APP_UNKNOWN: i32 = 99;
+
+    #[test]
+    fn test_add_remove() {
+        let mut db = new_test_db();
+        db.add_vm_id(&VM_ID1, USER1, APP_A).unwrap();
+        db.add_vm_id(&VM_ID2, USER1, APP_A).unwrap();
+        db.add_vm_id(&VM_ID3, USER1, APP_A).unwrap();
+        db.add_vm_id(&VM_ID4, USER2, APP_B).unwrap();
+        db.add_vm_id(&VM_ID5, USER3, APP_A).unwrap();
+        db.add_vm_id(&VM_ID5, USER3, APP_C).unwrap();
+        let empty: Vec<VmId> = Vec::new();
+
+        assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
+        assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_app(USER1, APP_A).unwrap());
+        assert_eq!(vec![VM_ID4], db.vm_ids_for_app(USER2, APP_B).unwrap());
+        assert_eq!(vec![VM_ID5], db.vm_ids_for_user(USER3).unwrap());
+        assert_eq!(empty, db.vm_ids_for_user(USER_UNKNOWN).unwrap());
+        assert_eq!(empty, db.vm_ids_for_app(USER1, APP_UNKNOWN).unwrap());
+
+        db.delete_vm_ids(&[VM_ID2, VM_ID3]).unwrap();
+
+        assert_eq!(vec![VM_ID1], db.vm_ids_for_user(USER1).unwrap());
+        assert_eq!(vec![VM_ID1], db.vm_ids_for_app(USER1, APP_A).unwrap());
+
+        // OK to delete things that don't exist.
+        db.delete_vm_ids(&[VM_ID2, VM_ID3]).unwrap();
+
+        assert_eq!(vec![VM_ID1], db.vm_ids_for_user(USER1).unwrap());
+        assert_eq!(vec![VM_ID1], db.vm_ids_for_app(USER1, APP_A).unwrap());
+
+        db.add_vm_id(&VM_ID2, USER1, APP_A).unwrap();
+        db.add_vm_id(&VM_ID3, USER1, APP_A).unwrap();
+
+        assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
+        assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_app(USER1, APP_A).unwrap());
+        assert_eq!(vec![VM_ID4], db.vm_ids_for_app(USER2, APP_B).unwrap());
+        assert_eq!(vec![VM_ID5], db.vm_ids_for_user(USER3).unwrap());
+        assert_eq!(empty, db.vm_ids_for_user(USER_UNKNOWN).unwrap());
+        assert_eq!(empty, db.vm_ids_for_app(USER1, APP_UNKNOWN).unwrap());
+    }
+
+    #[test]
+    fn test_invalid_vm_id() {
+        let mut db = new_test_db();
+        db.add_vm_id(&VM_ID3, USER1, APP_A).unwrap();
+        db.add_vm_id(&VM_ID2, USER1, APP_A).unwrap();
+        db.add_vm_id(&VM_ID1, USER1, APP_A).unwrap();
+
+        // Note that results are returned in `vm_id` order, because the table is `WITHOUT ROWID`.
+        assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
+
+        // Manually insert a row with a VM ID that's the wrong size.
+        db.conn
+            .execute(
+                "REPLACE INTO main.vmids (vm_id, user_id, app_id) VALUES (?1, ?2, ?3);",
+                params![&[99u8; 60], &USER1, APP_A],
+            )
+            .unwrap();
+
+        // Invalid row is skipped and remainder returned.
+        assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
+    }
+}