Skip to content

Commit 7013ac3

Browse files
authored
Merge pull request #1886 from lightninglabs/wip/check-tmp-env-writable
tapcfg: validate SQLite temp dir at startup and add opt out flag
2 parents 83a1440 + f16b0cd commit 7013ac3

File tree

5 files changed

+104
-0
lines changed

5 files changed

+104
-0
lines changed

sample-tapd.conf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@
296296
; The full path to the database
297297
; sqlite.dbfile=~/.tapd/data/testnet/tapd.db
298298

299+
; Skip the temporary directory check on startup.
300+
; sqlite.skiptmpdircheck=false
301+
299302
[postgres]
300303

301304
; Skip applying migrations on startup

tapcfg/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ func DefaultConfig() Config {
449449
DatabaseBackend: DatabaseBackendSqlite,
450450
Sqlite: &tapdb.SqliteConfig{
451451
DatabaseFileName: defaultSqliteDatabasePath,
452+
SkipTmpDirCheck: false,
452453
},
453454
Postgres: &tapdb.PostgresConfig{
454455
Host: "localhost",

tapcfg/server.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
4747
dbType sqlc.BackendType
4848
)
4949

50+
// If we're using sqlite, we need to ensure that the temp directory is
51+
// writable otherwise we might encounter an error at an unexpected
52+
// time.
53+
if !cfg.Sqlite.SkipTmpDirCheck &&
54+
cfg.DatabaseBackend == DatabaseBackendSqlite {
55+
56+
err = checkSQLiteTempDir()
57+
if err != nil {
58+
return nil, fmt.Errorf("unable to ensure sqlite tmp "+
59+
"dir is writable: %w", err)
60+
}
61+
}
62+
5063
// Now that we know where the database will live, we'll go ahead and
5164
// open up the default implementation of it.
5265
switch cfg.DatabaseBackend {

tapcfg/validate.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package tapcfg
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"runtime"
7+
"strings"
8+
)
9+
10+
// ensureDirWritable verifies that the provided directory exists, is a directory
11+
// and is writable by creating a temporary file within it.
12+
func ensureDirWritable(dir string) error {
13+
dirInfo, err := os.Stat(dir)
14+
if err != nil {
15+
return fmt.Errorf("not accessible (dir=%s): %w", dir, err)
16+
}
17+
18+
if !dirInfo.IsDir() {
19+
return fmt.Errorf("not a directory (dir=%s)", dir)
20+
}
21+
22+
tmpFile, err := os.CreateTemp(dir, "tapd-tmpdir-check-*")
23+
if err != nil {
24+
return fmt.Errorf("not writable (dir=%s): %w", dir, err)
25+
}
26+
defer func() { _ = os.Remove(tmpFile.Name()) }()
27+
28+
if err := tmpFile.Close(); err != nil {
29+
return fmt.Errorf("not writable (dir=%s): %w", dir, err)
30+
}
31+
32+
return nil
33+
}
34+
35+
// checkSQLiteTempDir checks temp directory locations on Linux/Darwin
36+
// and verifies the first writable option. SQLite honors SQLITE_TMPDIR first,
37+
// then TMPDIR, then falls back to /var/tmp, /usr/tmp and /tmp.
38+
//
39+
// NOTE: SQLite requires a writable temp directory because several internal
40+
// operations need temporary files when they cannot be done purely in memory.
41+
func checkSQLiteTempDir() error {
42+
// This check only runs for Linux/Darwin.
43+
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
44+
return nil
45+
}
46+
47+
// SQLite will use the first available temp directory; we mirror that
48+
// behavior by trying environment variables and standard fallback
49+
// directories in order.
50+
var errs []string
51+
52+
type dirSource struct {
53+
path string
54+
source string
55+
}
56+
57+
sources := []dirSource{
58+
{path: os.Getenv("SQLITE_TMPDIR"), source: "env=SQLITE_TMPDIR"},
59+
{path: os.Getenv("TMPDIR"), source: "env=TMPDIR"},
60+
{path: "/var/tmp", source: "fallback=/var/tmp"},
61+
{path: "/usr/tmp", source: "fallback=/usr/tmp"},
62+
{path: "/tmp", source: "fallback=/tmp"},
63+
}
64+
65+
for _, s := range sources {
66+
if s.path == "" {
67+
continue
68+
}
69+
70+
err := ensureDirWritable(s.path)
71+
if err != nil {
72+
err = fmt.Errorf("(%s) %w", s.source, err)
73+
errs = append(errs, err.Error())
74+
continue
75+
}
76+
77+
// Found a writable temp directory.
78+
return nil
79+
}
80+
81+
return fmt.Errorf("no writable temp directory found; attempts=%s",
82+
strings.Join(errs, "; "))
83+
}

tapdb/sqlite.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ type SqliteConfig struct {
5757
// be created before applying migrations.
5858
SkipMigrationDbBackup bool `long:"skipmigrationdbbackup" description:"Skip creating a backup of the database before applying migrations."`
5959

60+
// SkipTmpDirCheck disables the writable temp directory check performed
61+
// before opening the database.
62+
SkipTmpDirCheck bool `long:"skiptmpdircheck" description:"Skip checking that the temp directory is writable before opening the database."`
63+
6064
// DatabaseFileName is the full file path where the database file can be
6165
// found.
6266
DatabaseFileName string `long:"dbfile" description:"The full path to the database."`

0 commit comments

Comments
 (0)