1
0
mirror of https://github.com/lxc/incus.git synced 2026-02-05 09:46:19 +01:00
Files
incus/cmd/incus-migrate/transfer.go
2025-04-28 20:26:11 -04:00

142 lines
3.1 KiB
Go

package main
import (
"context"
"fmt"
"io"
"net"
"os"
"os/exec"
"strings"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/lxc/incus/v6/internal/linux"
"github.com/lxc/incus/v6/internal/migration"
"github.com/lxc/incus/v6/internal/rsync"
"github.com/lxc/incus/v6/shared/util"
"github.com/lxc/incus/v6/shared/ws"
)
// Send an rsync stream of a path over a websocket.
func rsyncSend(ctx context.Context, conn *websocket.Conn, path string, rsyncArgs string, migrationType MigrationType) error {
cmd, dataSocket, stderr, err := rsyncSendSetup(ctx, path, rsyncArgs, migrationType)
if err != nil {
return err
}
if dataSocket != nil {
defer func() { _ = dataSocket.Close() }()
}
readDone, writeDone := ws.Mirror(conn, dataSocket)
<-writeDone
_ = dataSocket.Close()
output, err := io.ReadAll(stderr)
if err != nil {
_ = cmd.Process.Kill()
_ = cmd.Wait()
return fmt.Errorf("Failed to rsync: %v\n%s", err, output)
}
err = cmd.Wait()
<-readDone
if err != nil {
return fmt.Errorf("Failed to rsync: %v\n%s", err, output)
}
return nil
}
// Spawn the rsync process.
func rsyncSendSetup(ctx context.Context, path string, rsyncArgs string, migrationType MigrationType) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
auds := fmt.Sprintf("@incus-migrate/%s", uuid.New().String())
if len(auds) > linux.ABSTRACT_UNIX_SOCK_LEN-1 {
auds = auds[:linux.ABSTRACT_UNIX_SOCK_LEN-1]
}
l, err := net.Listen("unix", auds)
if err != nil {
return nil, nil, nil, err
}
execPath, err := os.Readlink("/proc/self/exe")
if err != nil {
return nil, nil, nil, err
}
if !util.PathExists(execPath) {
execPath = os.Args[0]
}
rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", execPath, auds)
args := []string{
"-ar",
"--devices",
"--numeric-ids",
"--partial",
"--sparse",
}
if migrationType == MigrationTypeContainer || migrationType == MigrationTypeVolumeFilesystem {
args = append(args, "--xattrs", "--delete", "--compress", "--compress-level=2")
}
if migrationType == MigrationTypeVM || migrationType == MigrationTypeVolumeBlock {
args = append(args, "--exclude", "*.img")
}
if rsync.AtLeast("3.1.3") {
args = append(args, "--filter=-x security.selinux")
}
if rsync.AtLeast("3.1.0") {
args = append(args, "--ignore-missing-args")
}
if rsyncArgs != "" {
args = append(args, strings.Split(rsyncArgs, " ")...)
}
args = append(args, []string{path, "localhost:/tmp/foo"}...)
args = append(args, []string{"-e", rsyncCmd}...)
cmd := exec.CommandContext(ctx, "rsync", args...)
cmd.Stdout = os.Stderr
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, nil, nil, err
}
err = cmd.Start()
if err != nil {
return nil, nil, nil, err
}
conn, err := l.Accept()
if err != nil {
_ = cmd.Process.Kill()
_ = cmd.Wait()
return nil, nil, nil, err
}
_ = l.Close()
return cmd, conn, stderr, nil
}
func protoSendError(conn *websocket.Conn, err error) {
migration.ProtoSendControl(conn, err)
if err != nil {
closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
_ = conn.WriteMessage(websocket.CloseMessage, closeMsg)
_ = conn.Close()
}
}