blob: 8152f911a8ab58b6d43bc3e2023aa3280d8048f3 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
use crate::{ConfigError, Result};
use maka_event_log::{
StoreError,
connection::{ConnectionAuthority, OwnedConnection},
root::RootOwner,
};
use sqlx::{Connection, SqliteConnection, sqlite::SqliteConnectOptions};
#[cfg(not(windows))]
use std::fs::OpenOptions;
use std::sync::Arc;
static MIGRATIONS: sqlx::migrate::Migrator = sqlx::migrate!("./migrations");
pub(crate) fn unsigned(value: i64) -> Result<u64> {
u64::try_from(value).map_err(|_| ConfigError::Invalid("negative database revision".into()))
}
pub(super) async fn open(owner: Arc<RootOwner>) -> Result<OwnedConnection> {
let path = owner.canonical_path().join("configuration-rust.sqlite");
#[cfg(not(windows))]
let file = {
let mut options = OpenOptions::new();
options.read(true).write(true).create(true).truncate(false);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
}
options.open(&path)?
};
#[cfg(windows)]
let file = {
use maka_event_log::root::windows::{
create_private_file, file_identity, open_nofollow, validate_private,
};
let file = match create_private_file(&path) {
Ok(file) => file,
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {
open_nofollow(&path, true)?
}
Err(error) => return Err(error.into()),
};
validate_private(&file)?;
if !file.metadata()?.is_file() || file_identity(&file)?.links != 1 {
return Err(ConfigError::Invalid(
"credential database must be private and singly linked".into(),
));
}
file
};
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let metadata = file.metadata()?;
if !metadata.is_file()
|| metadata.nlink() != 1
|| metadata.mode() & 0o077 != 0
|| metadata.uid() != unsafe { libc::geteuid() }
{
return Err(ConfigError::Invalid(
"credential database must be private and singly linked".into(),
));
}
}
owner.validate_current()?;
let connection = OwnedConnection::open(
SqliteConnectOptions::new()
.filename(path)
.create_if_missing(false),
ConnectionAuthority::Root(owner),
|connection| Box::pin(initialize(connection)),
)
.await?;
drop(file);
Ok(connection)
}
async fn initialize(connection: &mut SqliteConnection) -> std::result::Result<(), StoreError> {
// Native plugin owners may open this Root concurrently. Hold the actual
// database writer transaction across schema inspection and migration;
// SQLx uses savepoints for its nested migration transactions.
let mut tx = connection.begin_with("BEGIN IMMEDIATE").await?;
initialize_schema(&mut tx).await?;
tx.commit().await?;
// Bounded configuration transactions do not need to change the persistent
// journal mode. Switching to WAL here would race another opening writer.
sqlx::raw_sql("PRAGMA synchronous = FULL; PRAGMA secure_delete = ON;")
.execute(&mut *connection)
.await?;
crate::pricing::initialize(connection).await?;
Ok(())
}
async fn initialize_schema(
connection: &mut SqliteConnection,
) -> std::result::Result<(), StoreError> {
let application_id: i64 = sqlx::query_scalar("PRAGMA application_id")
.fetch_one(&mut *connection)
.await?;
let version: i64 = sqlx::query_scalar("PRAGMA user_version")
.fetch_one(&mut *connection)
.await?;
let tables: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%'",
)
.fetch_one(&mut *connection)
.await?;
if !((application_id == 0 && version == 0 && tables == 0)
|| (application_id == 0x4d414b43 && matches!(version, 0..=1)))
{
return Err(StoreError::UnsupportedDatabase);
}
let ledger: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = '_sqlx_migrations')",
)
.fetch_one(&mut *connection)
.await?;
if (version == 0 && tables != i64::from(ledger)) || (version == 1 && !ledger) {
return Err(StoreError::UnsupportedDatabase);
}
if ledger {
let applied: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM _sqlx_migrations")
.fetch_one(&mut *connection)
.await?;
if (version == 0) != (applied == 0) {
return Err(StoreError::UnsupportedDatabase);
}
}
// Stamp identity before SQLx creates its ledger so interrupted setup can retry.
if application_id == 0 {
sqlx::query("PRAGMA application_id = 1296124739")
.execute(&mut *connection)
.await?;
}
MIGRATIONS.run_direct(None, &mut *connection, false).await?;
Ok(())
}