mirror of
https://github.com/lxc/incus
synced 2026-08-02 05:26:46 +00:00
176 lines
4.5 KiB
Go
176 lines
4.5 KiB
Go
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 aren’t even interested in the
|
||
// symlink target.
|
||
srcStat, errSymlink = sftpConn.Stat(normalizedPath)
|
||
}
|
||
|
||
directoryRequested := strings.HasSuffix(path, "/")
|
||
if errSymlink == nil {
|
||
// Let’s 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 aren’t 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
|
||
}
|