Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sample-tapd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@
; The full path to the database
; sqlite.dbfile=~/.tapd/data/testnet/tapd.db

; Skip the temporary directory check on startup.
; sqlite.skiptmpdircheck=false

[postgres]

; Skip applying migrations on startup
Expand Down
1 change: 1 addition & 0 deletions tapcfg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ func DefaultConfig() Config {
DatabaseBackend: DatabaseBackendSqlite,
Sqlite: &tapdb.SqliteConfig{
DatabaseFileName: defaultSqliteDatabasePath,
SkipTmpDirCheck: false,
},
Postgres: &tapdb.PostgresConfig{
Host: "localhost",
Expand Down
13 changes: 13 additions & 0 deletions tapcfg/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
dbType sqlc.BackendType
)

// If we're using sqlite, we need to ensure that the temp directory is
// writable otherwise we might encounter an error at an unexpected
// time.
if !cfg.Sqlite.SkipTmpDirCheck &&
cfg.DatabaseBackend == DatabaseBackendSqlite {

err = checkSQLiteTempDir()
if err != nil {
return nil, fmt.Errorf("unable to ensure sqlite tmp "+
"dir is writable: %w", err)
}
}

// Now that we know where the database will live, we'll go ahead and
// open up the default implementation of it.
switch cfg.DatabaseBackend {
Expand Down
83 changes: 83 additions & 0 deletions tapcfg/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package tapcfg

import (
"fmt"
"os"
"runtime"
"strings"
)

// ensureDirWritable verifies that the provided directory exists, is a directory
// and is writable by creating a temporary file within it.
func ensureDirWritable(dir string) error {
dirInfo, err := os.Stat(dir)
if err != nil {
return fmt.Errorf("not accessible (dir=%s): %w", dir, err)
}

if !dirInfo.IsDir() {
return fmt.Errorf("not a directory (dir=%s)", dir)
}

tmpFile, err := os.CreateTemp(dir, "tapd-tmpdir-check-*")
if err != nil {
return fmt.Errorf("not writable (dir=%s): %w", dir, err)
}
defer func() { _ = os.Remove(tmpFile.Name()) }()

if err := tmpFile.Close(); err != nil {
return fmt.Errorf("not writable (dir=%s): %w", dir, err)
}

return nil
}

// checkSQLiteTempDir checks temp directory locations on Linux/Darwin
// and verifies the first writable option. SQLite honors SQLITE_TMPDIR first,
// then TMPDIR, then falls back to /var/tmp, /usr/tmp and /tmp.
//
// NOTE: SQLite requires a writable temp directory because several internal
// operations need temporary files when they cannot be done purely in memory.
func checkSQLiteTempDir() error {
// This check only runs for Linux/Darwin.
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
return nil
}

// SQLite will use the first available temp directory; we mirror that
// behavior by trying environment variables and standard fallback
// directories in order.
var errs []string

type dirSource struct {
path string
source string
}

sources := []dirSource{
{path: os.Getenv("SQLITE_TMPDIR"), source: "env=SQLITE_TMPDIR"},
{path: os.Getenv("TMPDIR"), source: "env=TMPDIR"},
{path: "/var/tmp", source: "fallback=/var/tmp"},
{path: "/usr/tmp", source: "fallback=/usr/tmp"},
{path: "/tmp", source: "fallback=/tmp"},
}

for _, s := range sources {
if s.path == "" {
continue
}

err := ensureDirWritable(s.path)
if err != nil {
err = fmt.Errorf("(%s) %w", s.source, err)
errs = append(errs, err.Error())
continue
}

// Found a writable temp directory.
return nil
}

return fmt.Errorf("no writable temp directory found; attempts=%s",
strings.Join(errs, "; "))
}
4 changes: 4 additions & 0 deletions tapdb/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ type SqliteConfig struct {
// be created before applying migrations.
SkipMigrationDbBackup bool `long:"skipmigrationdbbackup" description:"Skip creating a backup of the database before applying migrations."`

// SkipTmpDirCheck disables the writable temp directory check performed
// before opening the database.
SkipTmpDirCheck bool `long:"skiptmpdircheck" description:"Skip checking that the temp directory is writable before opening the database."`

// DatabaseFileName is the full file path where the database file can be
// found.
DatabaseFileName string `long:"dbfile" description:"The full path to the database."`
Expand Down
Loading