Răsfoiți Sursa

fix(db): clean files for dropped folders at startup (#10280)

This adds a cleanup stage to remove database files for folders that no
longer exist on startup. Folder database files were already removed when
dropping a folder, assuming that the folder database had been opened at
that point. This won't be the case though when a folder is removed from
the config when Syncthing isn't running, or when a folder is dropped and
re-migrated in a restarted migration.
Jakob Borg 2 luni în urmă
părinte
comite
8151bcddff
2 a modificat fișierele cu 43 adăugiri și 4 ștergeri
  1. 7 4
      internal/db/sqlite/db_open.go
  2. 36 0
      internal/db/sqlite/db_update.go

+ 7 - 4
internal/db/sqlite/db_open.go

@@ -82,6 +82,10 @@ func Open(path string, opts ...Option) (*DB, error) {
 		opt(db)
 	}
 
+	if err := db.cleanDroppedFolders(); err != nil {
+		slog.Warn("Failed to clean dropped folders", slogutil.Error(err))
+	}
+
 	return db, nil
 }
 
@@ -120,10 +124,9 @@ func OpenForMigration(path string) (*DB, error) {
 		folderDBOpener: openFolderDBForMigration,
 	}
 
-	// // Touch device IDs that should always exist and have a low index
-	// // numbers, and will never change
-	// db.localDeviceIdx, _ = db.deviceIdxLocked(protocol.LocalDeviceID)
-	// db.tplInput["LocalDeviceIdx"] = db.localDeviceIdx
+	if err := db.cleanDroppedFolders(); err != nil {
+		slog.Warn("Failed to clean dropped folders", slogutil.Error(err))
+	}
 
 	return db, nil
 }

+ 36 - 0
internal/db/sqlite/db_update.go

@@ -8,9 +8,14 @@ package sqlite
 
 import (
 	"fmt"
+	"log/slog"
 	"os"
+	"path/filepath"
 	"runtime"
+	"slices"
 	"strings"
+
+	"github.com/syncthing/syncthing/internal/slogutil"
 )
 
 func (s *DB) DropFolder(folder string) error {
@@ -41,6 +46,37 @@ func (s *DB) ListFolders() ([]string, error) {
 	return res, wrap(err)
 }
 
+// cleanDroppedFolders removes old database files for folders that no longer
+// exist in the main database.
+func (s *DB) cleanDroppedFolders() error {
+	// All expected folder databeses.
+	var names []string
+	err := s.stmt(`SELECT database_name FROM folders`).Select(&names)
+	if err != nil {
+		return wrap(err)
+	}
+
+	// All folder database files on disk.
+	files, err := filepath.Glob(filepath.Join(s.pathBase, "folder.*"))
+	if err != nil {
+		return wrap(err)
+	}
+
+	// Any files that don't match a name in the database are removed.
+	for _, file := range files {
+		base := filepath.Base(file)
+		inDB := slices.ContainsFunc(names, func(name string) bool { return strings.HasPrefix(base, name) })
+		if !inDB {
+			if err := os.Remove(file); err != nil {
+				slog.Warn("Failed to remove database file for old, dropped folder", slogutil.FilePath(base))
+			} else {
+				slog.Info("Cleaned out database file for old, dropped folder", slogutil.FilePath(base))
+			}
+		}
+	}
+	return nil
+}
+
 // wrap returns the error wrapped with the calling function name and
 // optional extra context strings as prefix. A nil error wraps to nil.
 func wrap(err error, context ...string) error {