fix: populate fsid for legacy NFS exports on migration
Migration 0002 added fsid column with DEFAULT 0 but never populated existing rows. Now PopulateLegacyFSIDs() runs after 0002 to extract fsid from legacy options string or generate random. Also: move generateRandomFSID to internal/system to avoid import cycles. Migration 0004 drops the legacy options column. Version: 0.3.1 -> 0.3.2
This commit is contained in:
@@ -73,6 +73,12 @@ func (d *DB) Migrate() error {
|
||||
if err := d.runMigration(name, string(content)); err != nil {
|
||||
return fmt.Errorf("apply migration %s: %w", name, err)
|
||||
}
|
||||
|
||||
if name == "0002_nfs_structured.sql" {
|
||||
if err := d.PopulateLegacyFSIDs(); err != nil {
|
||||
return fmt.Errorf("populate legacy fsids: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Drop the legacy options column after successful migration to structured columns
|
||||
-- Safe to run on fresh installs (column doesn't exist, silently succeeds in SQLite 3.35+)
|
||||
-- For SQLite < 3.35 this would fail; nasctl requires modern SQLite.
|
||||
ALTER TABLE nfs_exports DROP COLUMN options;
|
||||
@@ -3,6 +3,10 @@ package db
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/darroyo/nasctl/internal/system"
|
||||
)
|
||||
|
||||
func scanNFSExport(row interface {
|
||||
@@ -199,3 +203,55 @@ func (d *DB) FSIDExists(fsid int64) (bool, error) {
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
var fsidLegacyPattern = regexp.MustCompile(`fsid=(\d+)`)
|
||||
|
||||
func (d *DB) PopulateLegacyFSIDs() error {
|
||||
rows, err := d.conn.Query(`SELECT id, fsid, options FROM nfs_exports WHERE fsid = 0`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("query legacy fsids: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type pending struct {
|
||||
id int64
|
||||
fsid int64
|
||||
}
|
||||
var updates []pending
|
||||
|
||||
for rows.Next() {
|
||||
var id, fsid int64
|
||||
var options string
|
||||
if err := rows.Scan(&id, &fsid, &options); err != nil {
|
||||
return fmt.Errorf("scan row: %w", err)
|
||||
}
|
||||
newFSID := int64(0)
|
||||
|
||||
if m := fsidLegacyPattern.FindStringSubmatch(options); m != nil {
|
||||
if n, err := strconv.ParseInt(m[1], 10, 64); err == nil && n > 0 {
|
||||
newFSID = n
|
||||
}
|
||||
}
|
||||
|
||||
if newFSID == 0 {
|
||||
n, err := system.GenerateRandomFSID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate fsid: %w", err)
|
||||
}
|
||||
newFSID = int64(n)
|
||||
}
|
||||
|
||||
updates = append(updates, pending{id, newFSID})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return fmt.Errorf("rows iteration: %w", err)
|
||||
}
|
||||
|
||||
for _, u := range updates {
|
||||
if _, err := d.conn.Exec(`UPDATE nfs_exports SET fsid = ? WHERE id = ?`, u.fsid, u.id); err != nil {
|
||||
return fmt.Errorf("update fsid for id %d: %w", u.id, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ package importer
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -170,14 +168,10 @@ func parseExportLine(line string) (db.NFSExport, bool) {
|
||||
}
|
||||
|
||||
func generateImportFSID() (int64, error) {
|
||||
var b [4]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
n, err := system.GenerateRandomFSID()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n := binary.BigEndian.Uint32(b[:])
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
return int64(n), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
package nfs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func generateRandomFSID() (uint32, error) {
|
||||
var b [4]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return 0, fmt.Errorf("crypto/rand read: %w", err)
|
||||
}
|
||||
n := binary.BigEndian.Uint32(b[:])
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
"github.com/darroyo/nasctl/internal/system"
|
||||
)
|
||||
|
||||
func UniqueFSID(used map[uint32]struct{}) (uint32, error) {
|
||||
for i := 0; i < 64; i++ {
|
||||
n, err := generateRandomFSID()
|
||||
n, err := system.GenerateRandomFSID()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package nfs
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/darroyo/nasctl/internal/system"
|
||||
)
|
||||
|
||||
func TestUniqueFSIDNoCollision(t *testing.T) {
|
||||
@@ -14,7 +16,7 @@ func TestUniqueFSIDNoCollision(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
n, err := generateRandomFSID()
|
||||
n, err := system.GenerateRandomFSID()
|
||||
if err != nil {
|
||||
t.Errorf("generateRandomFSID failed: %v", err)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func GenerateRandomFSID() (uint32, error) {
|
||||
var b [4]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return 0, fmt.Errorf("crypto/rand read: %w", err)
|
||||
}
|
||||
n := binary.BigEndian.Uint32(b[:])
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
Reference in New Issue
Block a user