incus-mirror/cmd/incus/utils_copy.go
Benjamin Somers bfc1c82d2a incus/utils_copy: Fix wrong error returned
Signed-off-by: Benjamin Somers <benjamin.somers@imt-atlantique.fr>
2026-06-13 06:45:35 +00:00

176 lines
4.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/pkg/sftp"
"github.com/lxc/incus/v7/internal/i18n"
)
// fileCopiable abstracts commands that pull files.
type fileCopiable struct {
flagRecursive bool
flagNoDereference bool
flagFollow bool
flagDereference bool
}
// preCheck performs flag validation.
func (f *fileCopiable) preCheck() error {
// --no-dereference/-P, --follow/-H, and --dereference/-L are mutually exclusive.
found := 0
if f.flagNoDereference {
found++
}
if f.flagFollow {
found++
}
if f.flagDereference {
found++
}
if found > 1 {
return errors.New(i18n.G("--no-dereference/-P, --follow/-H, and --dereference/-L are mutually exclusive"))
}
return nil
}
// pullable abstracts commands that pull files.
type pullable struct {
fileCopiable
}
// preCheck performs flag validation.
func (p *pullable) preCheck(target string) error {
err := p.fileCopiable.preCheck()
if err != nil {
return err
}
// Using stdout as a target implicitly sets -H if -L is not set, but fails if -P is set.
if isStdout(target) {
if p.flagDereference {
return nil
}
if p.flagNoDereference {
return errors.New(i18n.G("--no-dereference/-P cannot be used together with stdout as a target"))
}
p.flagFollow = true
}
return nil
}
// statFile returns the proper stat struct for the given flags, along with the normalized file name.
func (p *pullable) statFile(sftpConn *sftp.Client, path string) (os.FileInfo, string, error) {
normalizedPath, _ := normalizePath(path)
srcLstat, err := sftpConn.Lstat(normalizedPath)
if err != nil {
return nil, "", err
}
isSymlink := srcLstat.Mode()&os.ModeSymlink != 0
srcStat := srcLstat
var errSymlink error
if isSymlink {
// We defer dereferencing error handling, as chances are we arent even interested in the
// symlink target.
srcStat, errSymlink = sftpConn.Stat(normalizedPath)
}
directoryRequested := strings.HasSuffix(path, "/")
if errSymlink == nil {
// Lets be extra careful and check that explicit requests for directories actually point to
// directories.
if directoryRequested && !srcStat.IsDir() {
return nil, "", fmt.Errorf(i18n.G("%s is not a directory"), normalizedPath)
}
// Here, we perform a special handling if -P is used on a directory symlink.
if srcStat.IsDir() && !p.flagRecursive && (!isSymlink || !p.flagNoDereference) {
return nil, "", errors.New(i18n.G("--recursive/-r is required when pulling directories"))
}
}
// Under a few conditions, return the file the link points to and not the link itself.
if p.flagDereference || !p.flagRecursive && !p.flagNoDereference || isSymlink && p.flagFollow || directoryRequested {
if errSymlink != nil {
return nil, "", errSymlink
}
return srcStat, normalizedPath, nil
}
return srcLstat, normalizedPath, nil
}
// pushable abstracts commands that push files.
type pushable struct {
fileCopiable
}
// statFile returns the proper stat struct for the given flags, along with the walkable file name.
func (p *pushable) statFile(path string) (os.FileInfo, string, error) {
absPath, err := filepath.Abs(path)
if err != nil {
return nil, "", err
}
srcLstat, err := os.Lstat(absPath)
if err != nil {
return nil, "", err
}
isSymlink := srcLstat.Mode()&os.ModeSymlink != 0
srcStat := srcLstat
var errSymlink error
if isSymlink {
// We defer dereferencing error handling, as chances are we arent even interested in the
// symlink target.
srcStat, errSymlink = os.Stat(absPath)
}
if errSymlink == nil {
// Here, we perform a special handling if -P is used on a directory symlink.
if srcStat.IsDir() && !p.flagRecursive && (!isSymlink || !p.flagNoDereference) {
return nil, "", errors.New(i18n.G("--recursive/-r is required when pushing directories"))
}
}
// Under a few conditions, return the file the link points to and not the link itself.
if p.flagDereference || !p.flagRecursive && !p.flagNoDereference || isSymlink && p.flagFollow || strings.HasSuffix(path, "/") {
if errSymlink != nil {
return nil, "", err
}
// This is a bit of a hack, but as we are using `filepath.Walk`, we need to point to an actual
// directory to be able to walk it, hence the early dereferencing.
if isSymlink && srcStat.IsDir() {
target, err := os.Readlink(absPath)
if err != nil {
return nil, "", err
}
if !filepath.IsAbs(target) {
target = filepath.Join(filepath.Dir(absPath), target)
}
absPath = target
}
return srcStat, absPath, nil
}
return srcLstat, absPath, nil
}