From 2b5401dd2fd65addfaaa4a3a7e49f5dfab453015 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:50:54 -0700 Subject: [PATCH] fix: show a warning when SQLite DB is stored on NFS/SMB/FUSE (#1381) --- backend/internal/bootstrap/db_bootstrap.go | 16 +++++++-- .../utils/networked_filesystem_linux.go | 35 +++++++++++++++++++ .../utils/networked_filesystem_nonlinux.go | 8 +++++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 backend/internal/utils/networked_filesystem_linux.go create mode 100644 backend/internal/utils/networked_filesystem_nonlinux.go diff --git a/backend/internal/bootstrap/db_bootstrap.go b/backend/internal/bootstrap/db_bootstrap.go index 4fb7e87a..291d3961 100644 --- a/backend/internal/bootstrap/db_bootstrap.go +++ b/backend/internal/bootstrap/db_bootstrap.go @@ -34,7 +34,8 @@ func NewDatabase() (db *gorm.DB, err error) { } // Run migrations - if err := utils.MigrateDatabase(sqlDb); err != nil { + err = utils.MigrateDatabase(sqlDb) + if err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) } @@ -42,7 +43,10 @@ func NewDatabase() (db *gorm.DB, err error) { } func ConnectDatabase() (db *gorm.DB, err error) { - var dialector gorm.Dialector + var ( + dialector gorm.Dialector + sqliteNetworkFilesystem bool + ) // Choose the correct database provider var onConnFn func(conn *sql.DB) @@ -63,6 +67,14 @@ func ConnectDatabase() (db *gorm.DB, err error) { if err := ensureSqliteDatabaseDir(dbPath); err != nil { return nil, err } + + sqliteNetworkFilesystem, err = utils.IsNetworkedFileSystem(filepath.Dir(dbPath)) + if err != nil { + // Log the error only + slog.Warn("Failed to detect filesystem type for the SQLite database directory", slog.String("path", filepath.Dir(dbPath)), slog.Any("error", err)) + } else if sqliteNetworkFilesystem { + slog.Warn("⚠️⚠️⚠️ SQLite databases should not be stored on a networked file system like NFS, SMB, or FUSE, as there's a risk of crashes and even database corruption", slog.String("path", filepath.Dir(dbPath))) + } } // Before we connect, also make sure that there's a temporary folder for SQLite to write its data diff --git a/backend/internal/utils/networked_filesystem_linux.go b/backend/internal/utils/networked_filesystem_linux.go new file mode 100644 index 00000000..31afd2fa --- /dev/null +++ b/backend/internal/utils/networked_filesystem_linux.go @@ -0,0 +1,35 @@ +//go:build linux + +package utils + +import ( + "fmt" + "syscall" +) + +// Filesystem magic values from Linux's include/uapi/linux/magic.h, used by statfs(2). +const ( + nfsSuperMagic = 0x6969 + smbSuperMagic = 0x517b + cifsSuperMagic = 0xff534d42 + fuseSuperMagic = 0x65735546 +) + +// IsNetworkedFileSystem reports whether path is on a filesystem that is known to be unsafe for SQLite, specifically NFS, SMB/CIFS, or FUSE mounts. +func IsNetworkedFileSystem(path string) (bool, error) { + var statfs syscall.Statfs_t + err := syscall.Statfs(path, &statfs) + if err != nil { + return false, fmt.Errorf("error executing statfs syscall: %w", err) + } + + // Statfs_t.Type is arch-dependent (for example, int32 on some systems and int64 on others). + // Normalize through uint32 first so signed values still preserve the Linux bit pattern for magic numbers such as CIFS (0xff534d42), then compare in a wide unsigned form. + //nolint:gosec + switch uint64(uint32(statfs.Type)) { + case nfsSuperMagic, smbSuperMagic, cifsSuperMagic, fuseSuperMagic: + return true, nil + default: + return false, nil + } +} diff --git a/backend/internal/utils/networked_filesystem_nonlinux.go b/backend/internal/utils/networked_filesystem_nonlinux.go new file mode 100644 index 00000000..386f5539 --- /dev/null +++ b/backend/internal/utils/networked_filesystem_nonlinux.go @@ -0,0 +1,8 @@ +//go:build !linux + +package utils + +// IsNetworkedFileSystem returns false on non-Linux systems because this detection is only used for Linux-specific statfs(2) filesystem magic values. +func IsNetworkedFileSystem(string) (bool, error) { + return false, nil +}