new system prototype
This commit is contained in:
120
db_init.php
Normal file
120
db_init.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
// /var/www/html/db_init.php
|
||||
// Shared helpers for opening, creating, and saving the encrypted SQLite database.
|
||||
require_once __DIR__ . '/common.php';
|
||||
|
||||
define('QDB_PATH', __DIR__ . '/uploads/questionnaire_database');
|
||||
define('QDB_LOCK', __DIR__ . '/uploads/.qdb_lock');
|
||||
define('QDB_SCHEMA', __DIR__ . '/schema.sql');
|
||||
define('QDB_VERSION', 3);
|
||||
|
||||
/**
|
||||
* Decrypt the master DB file into a temp SQLite file, or create a fresh one
|
||||
* from schema.sql if no DB exists yet.
|
||||
*
|
||||
* Returns [PDO $pdo, string $tmpPath, resource|null $lockFp].
|
||||
* If $writable is true, an exclusive lock is held -- caller MUST call
|
||||
* qdb_save() or qdb_discard() afterwards.
|
||||
*/
|
||||
function qdb_open(bool $writable = false): array {
|
||||
$dbPath = QDB_PATH;
|
||||
|
||||
if (!is_dir(dirname($dbPath))) {
|
||||
if (!mkdir(dirname($dbPath), 0755, true) && !is_dir(dirname($dbPath))) {
|
||||
throw new Exception("Could not create uploads directory");
|
||||
}
|
||||
}
|
||||
|
||||
$lockFp = null;
|
||||
if ($writable) {
|
||||
$lockFp = fopen(QDB_LOCK, 'c');
|
||||
if ($lockFp === false) throw new Exception("Could not open lock file");
|
||||
if (!flock($lockFp, LOCK_EX)) {
|
||||
fclose($lockFp);
|
||||
throw new Exception("Could not acquire lock");
|
||||
}
|
||||
}
|
||||
|
||||
$tmpDb = tempnam(sys_get_temp_dir(), 'qdb_');
|
||||
$masterKey = get_master_key_bytes();
|
||||
|
||||
$sql = file_get_contents(QDB_SCHEMA);
|
||||
if ($sql === false) {
|
||||
if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); }
|
||||
throw new Exception("Could not read schema.sql");
|
||||
}
|
||||
|
||||
if (file_exists($dbPath) && is_file($dbPath)) {
|
||||
$storedEnc = @file_get_contents($dbPath);
|
||||
if ($storedEnc === false) {
|
||||
if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); }
|
||||
throw new Exception("Could not read stored DB");
|
||||
}
|
||||
$decrypted = aes256_cbc_decrypt_bytes($storedEnc, $masterKey);
|
||||
if (file_put_contents($tmpDb, $decrypted) === false) {
|
||||
if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); }
|
||||
throw new Exception("Could not write temp DB");
|
||||
}
|
||||
} else {
|
||||
$pdo = new PDO("sqlite:$tmpDb");
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->exec($sql);
|
||||
$pdo->exec("PRAGMA user_version = " . QDB_VERSION . ";");
|
||||
$pdo = null;
|
||||
}
|
||||
|
||||
$pdo = new PDO("sqlite:$tmpDb");
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->exec("PRAGMA foreign_keys = ON;");
|
||||
|
||||
// Schema upgrade: apply CREATE TABLE IF NOT EXISTS for any missing tables
|
||||
$currentVersion = (int)$pdo->query("PRAGMA user_version")->fetchColumn();
|
||||
if ($currentVersion < QDB_VERSION) {
|
||||
$pdo->exec("PRAGMA foreign_keys = OFF;");
|
||||
$pdo->exec($sql);
|
||||
$pdo->exec("PRAGMA user_version = " . QDB_VERSION . ";");
|
||||
$pdo->exec("PRAGMA foreign_keys = ON;");
|
||||
}
|
||||
|
||||
return [$pdo, $tmpDb, $lockFp];
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the temp DB and atomically write it back to the master path.
|
||||
* Releases the lock.
|
||||
*/
|
||||
function qdb_save(string $tmpDb, $lockFp): void {
|
||||
$masterKey = get_master_key_bytes();
|
||||
$plainDb = file_get_contents($tmpDb);
|
||||
if ($plainDb === false) throw new Exception("Could not read temp DB for save");
|
||||
$enc = aes256_cbc_encrypt_bytes($plainDb, $masterKey);
|
||||
|
||||
$dbPath = QDB_PATH;
|
||||
$tmpEncrypted = tempnam(dirname($dbPath), 'enc_qdb_');
|
||||
if (file_put_contents($tmpEncrypted, $enc) === false) {
|
||||
throw new Exception("Could not write encrypted DB");
|
||||
}
|
||||
if (!@rename($tmpEncrypted, $dbPath)) {
|
||||
if (!@copy($tmpEncrypted, $dbPath) || !@unlink($tmpEncrypted)) {
|
||||
throw new Exception("Could not save encrypted DB (rename/copy failed)");
|
||||
}
|
||||
}
|
||||
@chmod($dbPath, 0644);
|
||||
@unlink($tmpDb);
|
||||
|
||||
if ($lockFp) {
|
||||
flock($lockFp, LOCK_UN);
|
||||
fclose($lockFp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard changes -- clean up temp file and release lock without saving.
|
||||
*/
|
||||
function qdb_discard(string $tmpDb, $lockFp): void {
|
||||
@unlink($tmpDb);
|
||||
if ($lockFp) {
|
||||
@flock($lockFp, LOCK_UN);
|
||||
@fclose($lockFp);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user