diff --git a/examples/cmd/server.go b/examples/cmd/server.go index 2ea308d..d8c2b68 100644 --- a/examples/cmd/server.go +++ b/examples/cmd/server.go @@ -110,7 +110,7 @@ func server() error { //nolint:gocyclo if dbPath == "" { return errors.New("db flag is required") } - state, err := sqlite.New(dbPath, dbPass) + state, err := sqlite.Open(dbPath, dbPass) if err != nil { return err } diff --git a/examples/go.mod b/examples/go.mod index db4c114..108a3eb 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -21,11 +21,11 @@ require ( ) require ( - github.com/ncruces/go-sqlite3 v0.18.4 // indirect + github.com/ncruces/go-sqlite3 v0.19.1-0.20241017225339-d6aebe67cc4b // indirect github.com/ncruces/julianday v1.0.0 // indirect github.com/neilotoole/jsoncolor v0.7.1 // indirect - github.com/tetratelabs/wazero v1.8.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect + github.com/tetratelabs/wazero v1.8.1 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index 04c8c2b..7cad18f 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -22,8 +22,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/ncruces/go-sqlite3 v0.18.4 h1:Je8o3y33MDwPYY/Cacas8yCsuoUzpNY/AgoSlN2ekyE= -github.com/ncruces/go-sqlite3 v0.18.4/go.mod h1:4HLag13gq1k10s4dfGBhMfRVsssJRT9/5hYqVM9RUYo= +github.com/ncruces/go-sqlite3 v0.19.1-0.20241017225339-d6aebe67cc4b h1:oAawRfm4i619bgG1TbQQoV/pGOCoPqX7+mHqaGZva0c= +github.com/ncruces/go-sqlite3 v0.19.1-0.20241017225339-d6aebe67cc4b/go.mod h1:yL4ZNWGsr1/8pcLfpPW1RT1WFdvyeHonrgIwwi4rvkg= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/neilotoole/jsoncolor v0.7.1 h1:/MoU7KPLcto+ykcy592Y8eX9WFQhoi3IBEbwrP89dgs= @@ -47,23 +47,23 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= -github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550= +github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/sqlite/go.mod b/sqlite/go.mod index b52dec6..2c0f208 100644 --- a/sqlite/go.mod +++ b/sqlite/go.mod @@ -6,12 +6,12 @@ replace github.com/fido-device-onboard/go-fdo => ../ require ( github.com/fido-device-onboard/go-fdo v0.0.0-00010101000000-000000000000 - github.com/ncruces/go-sqlite3 v0.18.4 - golang.org/x/crypto v0.27.0 + github.com/ncruces/go-sqlite3 v0.19.1-0.20241017225339-d6aebe67cc4b ) require ( github.com/ncruces/julianday v1.0.0 // indirect - github.com/tetratelabs/wazero v1.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect + github.com/tetratelabs/wazero v1.8.1 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/sqlite/go.sum b/sqlite/go.sum index 0ec0ec9..2b5cc25 100644 --- a/sqlite/go.sum +++ b/sqlite/go.sum @@ -1,12 +1,12 @@ -github.com/ncruces/go-sqlite3 v0.18.4 h1:Je8o3y33MDwPYY/Cacas8yCsuoUzpNY/AgoSlN2ekyE= -github.com/ncruces/go-sqlite3 v0.18.4/go.mod h1:4HLag13gq1k10s4dfGBhMfRVsssJRT9/5hYqVM9RUYo= +github.com/ncruces/go-sqlite3 v0.19.1-0.20241017225339-d6aebe67cc4b h1:oAawRfm4i619bgG1TbQQoV/pGOCoPqX7+mHqaGZva0c= +github.com/ncruces/go-sqlite3 v0.19.1-0.20241017225339-d6aebe67cc4b/go.mod h1:yL4ZNWGsr1/8pcLfpPW1RT1WFdvyeHonrgIwwi4rvkg= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= -github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= -github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550= +github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= diff --git a/sqlite/sqlite.go b/sqlite/sqlite.go index 48ed5c4..168a34b 100644 --- a/sqlite/sqlite.go +++ b/sqlite/sqlite.go @@ -24,8 +24,9 @@ import ( "strings" "time" - "github.com/ncruces/go-sqlite3/driver" // Load database/sql driver - _ "github.com/ncruces/go-sqlite3/embed" // Load sqlite WASM binary + "github.com/ncruces/go-sqlite3/driver" // Load database/sql driver + _ "github.com/ncruces/go-sqlite3/embed" // Load sqlite WASM binary + _ "github.com/ncruces/go-sqlite3/vfs/xts" // Encryption VFS "github.com/fido-device-onboard/go-fdo" "github.com/fido-device-onboard/go-fdo/cbor" @@ -33,7 +34,6 @@ import ( "github.com/fido-device-onboard/go-fdo/custom" "github.com/fido-device-onboard/go-fdo/kex" "github.com/fido-device-onboard/go-fdo/protocol" - _ "github.com/fido-device-onboard/go-fdo/sqlite/xts" // Encryption VFS ) // DB implements FDO server state persistence. @@ -44,29 +44,36 @@ type DB struct { db *sql.DB } -// New creates or opens a SQLite database file using a single non-pooled +// Open creates or opens a SQLite database file using a single non-pooled // connection. If a password is specified, then the xts VFS will be used // with a text key. -func New(filename, password string) (*DB, error) { +func Open(filename, password string) (*DB, error) { var query string if password != "" { - query += fmt.Sprintf("?vfs=xts&_pragma=textkey(%q)", password) + query += fmt.Sprintf("?vfs=xts&_pragma=textkey(%q)&_pragma=temp_store(memory)", password) } connector, err := (&driver.SQLite{}).OpenConnector("file:" + filepath.Clean(filename) + query) if err != nil { return nil, fmt.Errorf("error creating sqlite connector: %w", err) } db := sql.OpenDB(connector) - return Init(db) + if err := Init(db); err != nil { + return nil, err + } + return New(db), nil } +// New creates a DB. The expected tables must already be created and pragmas +// must already be set, including foreign_keys=ON. +func New(db *sql.DB) *DB { return &DB{db: db} } + // Init ensures all tables are created and pragma are set. It does not // recognize if tables have been created with invalid schemas. // // In most cases, New should be used, which implicitly calls Init. However, // Init can be useful for alternative SQLite connections that do not use a // local file, such as Cloudflare D1. -func Init(db *sql.DB) (*DB, error) { +func Init(db *sql.DB) error { stmts := []string{ `PRAGMA foreign_keys = ON`, `CREATE TABLE IF NOT EXISTS secrets @@ -153,13 +160,12 @@ func Init(db *sql.DB) (*DB, error) { if _, err := db.Exec(sql); err != nil { _ = db.Close() if strings.Contains(err.Error(), "file is not a database") { - return nil, fmt.Errorf("file is not a database: likely due to incorrect or missing database password") + return fmt.Errorf("file is not a database: likely due to incorrect or missing database password") } - return nil, fmt.Errorf("error creating tables: %w", err) + return fmt.Errorf("error creating tables: %w", err) } } - - return &DB{db: db}, nil + return nil } // Close closes the database connection. diff --git a/sqlite/sqlite_test.go b/sqlite/sqlite_test.go index e2ef8df..6edab8a 100644 --- a/sqlite/sqlite_test.go +++ b/sqlite/sqlite_test.go @@ -41,7 +41,7 @@ func newDB(t *testing.T) (_ *sqlite.DB, cleanup func() error) { cleanup = func() error { return os.Remove("db.test") } _ = cleanup() - state, err := sqlite.New("db.test", "test_password") + state, err := sqlite.Open("db.test", "test_password") if err != nil { t.Fatal(err) } diff --git a/sqlite/xts/xts.go b/sqlite/xts/xts.go deleted file mode 100644 index 9b5199b..0000000 --- a/sqlite/xts/xts.go +++ /dev/null @@ -1,369 +0,0 @@ -// SPDX-FileCopyrightText: (C) 2024 Intel Corporation -// SPDX-License-Identifier: Apache 2.0 - -// Package xts implements AES-XTS encryption for SQLite as a VFS. If a text key -// is provided, PBKDF2 with SHA512 and 10k iterations is used. -package xts - -import ( - "crypto/aes" - "crypto/rand" - "crypto/sha512" - "encoding/hex" - "errors" - "fmt" - "io" - - "github.com/ncruces/go-sqlite3" - "github.com/ncruces/go-sqlite3/vfs" - "golang.org/x/crypto/pbkdf2" - "golang.org/x/crypto/xts" -) - -func init() { - Register("xts", vfs.Find("")) -} - -// Register registers an encrypting VFS, wrapping a base VFS, using an AES-XTS cipher. The key is -// derived using PBKDF2 with SHA512 and 10k iterations. -func Register(name string, base vfs.VFS) { - vfs.Register(name, &xtsVFS{ - VFS: base, - }) -} - -var salt = []byte("github.com/fido-device-onboard/go-fdo/sqlite/xts") - -func kdf(secret string) []byte { - if secret == "" { - key := make([]byte, 32) - n, _ := rand.Read(key) - return key[:n] - } - return pbkdf2.Key([]byte(secret), salt, 10_000, 32, sha512.New) -} - -func newCipher(key []byte) (*xts.Cipher, error) { - return xts.NewCipher(aes.NewCipher, key) -} - -type xtsVFS struct { - vfs.VFS -} - -func (x *xtsVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) { - return nil, 0, sqlite3.CANTOPEN -} - -func (x *xtsVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs.File, _ vfs.OpenFlag, err error) { - if hf, ok := x.VFS.(vfs.VFSFilename); ok { - file, flags, err = hf.OpenFilename(name, flags) - } else { - file, flags, err = x.VFS.Open(name.String(), flags) - } - - // Encrypt everything except super journals and memory files. - if err != nil || flags&(vfs.OPEN_SUPER_JOURNAL|vfs.OPEN_MEMORY) != 0 { - return file, flags, err - } - - if f, ok := name.DatabaseFile().(*xtsFile); ok { - if f.cipher == nil { - return nil, flags, sqlite3.CANTOPEN - } - return &xtsFile{File: file, cipher: f.cipher}, flags, nil - } - - var key []byte - switch params := name.URIParameters(); { - case name == nil: - // Temporary files get a random key. - key = kdf("") - - case len(params["key"]) > 0: - key = []byte(params["key"][0]) - - case len(params["hexkey"]) > 0: - key, err = hex.DecodeString(params["hexkey"][0]) - if err != nil { - return nil, flags, fmt.Errorf("error decoding hex key: %w", err) - } - - case len(params["textkey"]) > 0: - key = kdf(params["textkey"][0]) - - case flags&vfs.OPEN_MAIN_DB != 0: - // Main databases may have their key specified as a PRAGMA. - return &xtsFile{File: file, name: name.String()}, flags, nil - - default: - return nil, flags, sqlite3.CANTOPEN - } - - cipher, err := newCipher(key) - if err != nil { - return nil, flags, sqlite3.CANTOPEN - } - - return &xtsFile{ - File: file, - name: name.String(), - cipher: cipher, - }, flags, nil -} - -const blockSize = 4096 - -type xtsFile struct { - vfs.File - name string - - cipher *xts.Cipher - - block [blockSize]byte -} - -func (x *xtsFile) Pragma(name string, value string) (string, error) { - var key []byte - switch name { - case "key": - key = []byte(value) - case "hexkey": - key, _ = hex.DecodeString(value) - case "textkey": - key = kdf(value) - default: - if f, ok := x.File.(vfs.FilePragma); ok { - return f.Pragma(name, value) - } - return "", sqlite3.NOTFOUND - } - - var err error - if x.cipher, err = newCipher(key); err != nil { - return "", sqlite3.CANTOPEN - } - return "ok", nil -} - -func (x *xtsFile) ReadAt(p []byte, off int64) (n int, err error) { - if x.cipher == nil { - // Only OPEN_MAIN_DB can have a missing key. - if off == 0 && len(p) == 100 { - // SQLite is trying to read the header of a database file. Pretend the file is empty so - // the key may specified as a PRAGMA. - return 0, io.EOF - } - return 0, sqlite3.CANTOPEN - } - - min := (off) &^ (blockSize - 1) // round down - max := (off + int64(len(p)) + (blockSize - 1)) &^ (blockSize - 1) // round up - - // Read one block at a time. - for ; min < max; min += blockSize { - m, err := x.File.ReadAt(x.block[:], min) - if m != blockSize { - return n, err - } - - // Decrypt the entire block in place - sectorNum := uint64(min / blockSize) //nolint:gosec - x.cipher.Decrypt(x.block[:], x.block[:], sectorNum) - - // Append the block contents, starting from partial-block offset if applicable - if off > min { - n += copy(p[n:], x.block[off-min:]) - continue - } - n += copy(p[n:], x.block[:]) - } - - if n != len(p) { - panic("unexpected EOF - should have failed at File.ReadAt or p was not a sized correctly") - } - return n, nil -} - -func (x *xtsFile) WriteAt(p []byte, off int64) (n int, err error) { - if x.cipher == nil { - return 0, sqlite3.READONLY - } - - min := (off) &^ (blockSize - 1) // round down - max := (off + int64(len(p)) + (blockSize - 1)) &^ (blockSize - 1) // round up - - // Write one block at a time. - for ; min < max; min += blockSize { - data := x.block[:] - - // Perform a partial write if offset is not at a block boundary or the length isn't a - // complete block. - if off > min || len(p[n:]) < blockSize { - // Read the current block - m, err := x.File.ReadAt(x.block[:], min) - - // Error on a partial read of the block, unless the end of the file was reached, in - // which case this will be an append operation. - if m != blockSize { - if !errors.Is(err, io.EOF) { - return n, err - } - - // We're either appending an entirely new block, or the final block was only - // partially written. A partially written block can't be decrypted, and is as good - // as corrupt. Either way, zero pad the file to the next block size. - clear(data) - } - - // Upon reading a full block, decrypt it so that plaintext may be appended and the - // whole block can be encrypted. - if m == blockSize { - sectorNum := uint64(min / blockSize) //nolint:gosec - x.cipher.Decrypt(data, data, sectorNum) - } - - // If writing from an offset, remove the data prior to the offset. - if off > min { - data = data[off-min:] - } - } - - // Copy into data, which may be a partial block - written := copy(data, p[n:]) - - // Encrypt the full block in place - sectorNum := uint64(min / blockSize) //nolint:gosec - x.cipher.Encrypt(x.block[:], x.block[:], sectorNum) - - // Write encrypted block to file - m, err := x.File.WriteAt(x.block[:], min) - if m != blockSize { - return n, err - } - n += written - } - - if n != len(p) { - panic("incomplete write - should have failed due to a partial read or partial write of the underlying file") - } - return n, nil -} - -func (x *xtsFile) Truncate(size int64) error { - size = (size + (blockSize - 1)) &^ (blockSize - 1) // round up - return x.File.Truncate(size) -} - -func (x *xtsFile) SectorSize() int { - return lcm(x.File.SectorSize(), blockSize) -} - -func (x *xtsFile) DeviceCharacteristics() vfs.DeviceCharacteristic { - return x.File.DeviceCharacteristics() & (0 | - // The only safe flags are these: - vfs.IOCAP_UNDELETABLE_WHEN_OPEN | - vfs.IOCAP_IMMUTABLE | - vfs.IOCAP_BATCH_ATOMIC) -} - -// Wrap optional methods. - -func (x *xtsFile) SharedMemory() vfs.SharedMemory { - if f, ok := x.File.(vfs.FileSharedMemory); ok { - return f.SharedMemory() - } - return nil -} - -func (x *xtsFile) ChunkSize(size int) { - if f, ok := x.File.(vfs.FileChunkSize); ok { - size = (size + (blockSize - 1)) &^ (blockSize - 1) // round up - f.ChunkSize(size) - } -} - -func (x *xtsFile) SizeHint(size int64) error { - if f, ok := x.File.(vfs.FileSizeHint); ok { - size = (size + (blockSize - 1)) &^ (blockSize - 1) // round up - return f.SizeHint(size) - } - return sqlite3.NOTFOUND -} - -func (x *xtsFile) HasMoved() (bool, error) { - if f, ok := x.File.(vfs.FileHasMoved); ok { - return f.HasMoved() - } - return false, sqlite3.NOTFOUND -} - -func (x *xtsFile) Overwrite() error { - if f, ok := x.File.(vfs.FileOverwrite); ok { - return f.Overwrite() - } - return sqlite3.NOTFOUND -} - -func (x *xtsFile) CommitPhaseTwo() error { - if f, ok := x.File.(vfs.FileCommitPhaseTwo); ok { - return f.CommitPhaseTwo() - } - return sqlite3.NOTFOUND -} - -func (x *xtsFile) BeginAtomicWrite() error { - if f, ok := x.File.(vfs.FileBatchAtomicWrite); ok { - return f.BeginAtomicWrite() - } - return sqlite3.NOTFOUND -} - -func (x *xtsFile) CommitAtomicWrite() error { - if f, ok := x.File.(vfs.FileBatchAtomicWrite); ok { - return f.CommitAtomicWrite() - } - return sqlite3.NOTFOUND -} - -func (x *xtsFile) RollbackAtomicWrite() error { - if f, ok := x.File.(vfs.FileBatchAtomicWrite); ok { - return f.RollbackAtomicWrite() - } - return sqlite3.NOTFOUND -} - -func (x *xtsFile) CheckpointDone() error { - if f, ok := x.File.(vfs.FileCheckpoint); ok { - return f.CheckpointDone() - } - return sqlite3.NOTFOUND -} - -func (x *xtsFile) CheckpointStart() error { - if f, ok := x.File.(vfs.FileCheckpoint); ok { - return f.CheckpointStart() - } - return sqlite3.NOTFOUND -} - -func abs(n int) int { - if n < 0 { - return -n - } - return n -} - -func gcd(m, n int) int { - for n != 0 { - m, n = n, m%n - } - return abs(m) -} - -func lcm(m, n int) int { - if n == 0 { - return 0 - } - return abs(n) * (abs(m) / gcd(m, n)) -}