mirror of
https://github.com/lxc/incus
synced 2026-08-02 05:26:46 +00:00
136 lines
3.1 KiB
Go
136 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/pierrec/lz4/v4"
|
|
|
|
internalIO "github.com/lxc/incus/v7/internal/io"
|
|
internalUtil "github.com/lxc/incus/v7/internal/util"
|
|
"github.com/lxc/incus/v7/shared/util"
|
|
)
|
|
|
|
// Uncompress the raft snapshot files in the given database directory.
|
|
//
|
|
// A backup will be created and kept around in case of errors.
|
|
func migrateDatabase(dir string) error {
|
|
global := filepath.Join(dir, "global")
|
|
|
|
err := internalUtil.DirCopy(global, global+".bak")
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to backup database directory %q: %w", global, err)
|
|
}
|
|
|
|
files, err := os.ReadDir(global)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to list database directory %q: %w", global, err)
|
|
}
|
|
|
|
for _, file := range files {
|
|
var timestamp uint64
|
|
var first uint64
|
|
var last uint64
|
|
|
|
if !file.Type().IsRegular() {
|
|
continue
|
|
}
|
|
|
|
n, err := fmt.Sscanf(file.Name(), "snapshot-%d-%d-%d\n", ×tamp, &first, &last)
|
|
if err != nil || n != 3 {
|
|
continue
|
|
}
|
|
|
|
filename := filepath.Join(global, file.Name())
|
|
err = lz4Uncompress(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to uncompress snapshot %q: %w", filename, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Uncompress the given file, preserving its mode and ownership.
|
|
//
|
|
// If the file is not lz4-compressed, this is a no-op.
|
|
func lz4Uncompress(zfilename string) error {
|
|
zr := lz4.NewReader(nil)
|
|
|
|
zfile, err := os.Open(zfilename)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to open file %q: %w", zfilename, err)
|
|
}
|
|
|
|
buf := make([]byte, 4)
|
|
n, err := zfile.Read(buf)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to read header file %q: %w", zfilename, err)
|
|
}
|
|
|
|
if n != 4 {
|
|
return fmt.Errorf("Read only %d bytes from %q", n, zfilename)
|
|
}
|
|
|
|
// Check the file magic, and return now if it's not an lz4 file.
|
|
magic := binary.LittleEndian.Uint32(buf)
|
|
if magic != 0x184D2204 {
|
|
zfile.Close()
|
|
return nil
|
|
}
|
|
|
|
off, err := zfile.Seek(0, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to seek %q: %w", zfilename, err)
|
|
}
|
|
|
|
if off != 0 {
|
|
return fmt.Errorf("Seek %q to offset: %d", zfilename, off)
|
|
}
|
|
|
|
zinfo, err := zfile.Stat()
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to get file info for %q: %w", zfilename, err)
|
|
}
|
|
|
|
// use the same mode for the output file
|
|
mode := zinfo.Mode()
|
|
|
|
_, uid, gid := internalIO.GetOwnerMode(zinfo)
|
|
|
|
filename := zfilename + ".uncompressed"
|
|
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to open file %q: %w", filename, err)
|
|
}
|
|
|
|
zr.Reset(zfile)
|
|
|
|
_, err = util.SafeCopy(file, zr)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to uncompress %q into %q: %w", zfilename, filename, err)
|
|
}
|
|
|
|
for _, c := range []io.Closer{zfile, file} {
|
|
err := c.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to close file: %w", err)
|
|
}
|
|
}
|
|
|
|
err = os.Chown(filename, uid, gid)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to set ownership of %q: %w", filename, err)
|
|
}
|
|
|
|
err = os.Rename(filename, zfilename)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to rename %q to %q: %w", filename, zfilename, err)
|
|
}
|
|
|
|
return nil
|
|
}
|