1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
package main
import (
"database/sql"
"fmt"
)
// =============================================================================
// DB helpers
// =============================================================================
// Pragmas must travel as _pragma DSN parameters: the modernc driver applies
// those to every connection it opens, and silently ignores DSN parameters it
// does not recognise — the _journal_mode=/_busy_timeout= spelling used here
// previously looked correct but set nothing at all, leaving connections on the
// default journal mode with no busy timeout (fail immediately when locked).
func openDB(path string) (*sql.DB, error) {
db, err := sql.Open("sqlite",
"file:"+path+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)")
if err != nil {
return nil, err
}
return db, nil
}
// openImageFileDB opens a packed .hosty *file* read-only. Such files normally
// live on read-only media (the Nix store, a read-only mount, a CD) and are never
// written while we read them, so:
//
// - do NOT request journal_mode(WAL): switching a database to WAL requires
// creating a -shm sidecar next to it, which fails with SQLITE_CANTOPEN on
// read-only media.
// - immutable=1 tells SQLite the file cannot change, so it skips locking and
// the -shm/-wal machinery entirely.
//
// Only use this for inert .hosty artifacts. For an installed data.hosty use
// openReadOnlyDB — see the warning there.
func openImageFileDB(path string) (*sql.DB, error) {
db, err := sql.Open("sqlite",
"file:"+path+"?mode=ro&immutable=1&_pragma=busy_timeout(5000)")
if err != nil {
return nil, err
}
return db, nil
}
// openReadOnlyDB opens a *live* database read-only, i.e. one that another
// process may be writing concurrently (an installed data.hosty is written by the
// running service and by the FUSE mount, which holds a read-write WAL connection
// to the same file).
//
// Deliberately no immutable=1 here: it makes SQLite ignore the -wal file and
// skip locking, which on a database that does change yields stale reads or
// spurious SQLITE_CORRUPT. journal_mode is likewise not set — the writer already
// put the file in WAL, and a read-only connection must not try to change it.
func openReadOnlyDB(path string) (*sql.DB, error) {
db, err := sql.Open("sqlite",
"file:"+path+"?mode=ro&_pragma=busy_timeout(5000)")
if err != nil {
return nil, err
}
return db, nil
}
func metaGet(db *sql.DB, key string) (string, error) {
var val string
err := db.QueryRow(`SELECT value FROM _hosty_meta WHERE key = ?`, key).Scan(&val)
if err != nil {
return "", fmt.Errorf("_hosty_meta[%q]: %w", key, err)
}
return val, nil
}
func metaSet(db *sql.DB, key, value string) error {
_, err := db.Exec(`INSERT OR REPLACE INTO _hosty_meta (key, value) VALUES (?, ?)`, key, value)
return err
}
// initHostyTables creates the spec tables in data.hosty.
// Port assignments and sensitive config values live in the environment
// database instead (see openEnvDB).
func initHostyTables(db *sql.DB) error {
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS _hosty_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS _hosty_image_fs (
path TEXT PRIMARY KEY,
mode INTEGER NOT NULL,
mtime INTEGER NOT NULL,
symlink_target TEXT,
content BLOB,
content_zstd BLOB
);
CREATE TABLE IF NOT EXISTS _hosty_fs (
path TEXT PRIMARY KEY,
mode INTEGER NOT NULL,
mtime INTEGER NOT NULL,
symlink_target TEXT,
content BLOB
);
CREATE TABLE IF NOT EXISTS _hosty_config (
key TEXT PRIMARY KEY,
description TEXT NOT NULL,
required INTEGER NOT NULL DEFAULT 1,
sensitive INTEGER NOT NULL DEFAULT 0,
default_val TEXT,
value TEXT
);
`)
return err
}
|