add --module flag

Support a new concept in containers.conf called "modules".  A "module"
is a containers.conf file located at a specific directory.  More than
one module can be loaded in the specified order, following existing
override semantics.

There are three directories to load modules from:
 - $CONFIG_HOME/containers/containers.conf.modules
 - /etc/containers/containers.conf.modules
 - /usr/share/containers/containers.conf.modules

With CONFIG_HOME pointing to $HOME/.config or, if set, $XDG_CONFIG_HOME.
Absolute paths will be loaded as is, relative paths will be resolved
relative to the three directories above allowing for admin configs
(/etc/) to override system configs (/usr/share/) and user configs
($CONFIG_HOME) to override admin configs.

Pulls in containers/common/pull/1599.

Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2023-08-09 15:50:15 +02:00
parent 9cd4286922
commit d5841ed528
65 changed files with 1253 additions and 756 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"path/filepath"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -1540,6 +1541,38 @@ func AutocompleteCgroupManager(cmd *cobra.Command, args []string, toComplete str
return types, cobra.ShellCompDirectiveNoFileComp return types, cobra.ShellCompDirectiveNoFileComp
} }
// AutocompleteContainersConfModules- Autocomplete containers.conf modules.
func AutocompleteContainersConfModules(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
dirs, err := config.ModuleDirectories()
if err != nil {
return nil, cobra.ShellCompDirectiveDefault
}
var modules []string
for _, d := range dirs {
cleanedD := filepath.Clean(d)
moduleD := cleanedD + string(os.PathSeparator)
_ = filepath.Walk(d,
func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
moduleName := strings.TrimPrefix(path, moduleD)
if toComplete != "" && !strings.HasPrefix(moduleName, toComplete) {
return nil
}
if filepath.Clean(path) == cleanedD || f.IsDir() {
return nil
}
modules = append(modules, moduleName)
return nil
})
}
return modules, cobra.ShellCompDirectiveDefault
}
// AutocompleteEventBackend - Autocomplete event backend options. // AutocompleteEventBackend - Autocomplete event backend options.
// -> "file", "journald", "none" // -> "file", "journald", "none"
func AutocompleteEventBackend(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { func AutocompleteEventBackend(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

View File

@ -11,6 +11,8 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/pkg/util"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
) )
const ( const (
@ -39,19 +41,58 @@ var (
) )
// PodmanConfig returns an entities.PodmanConfig built up from // PodmanConfig returns an entities.PodmanConfig built up from
// environment and CLI // environment and CLI.
func PodmanConfig() *entities.PodmanConfig { func PodmanConfig() *entities.PodmanConfig {
podmanSync.Do(newPodmanConfig) podmanSync.Do(newPodmanConfig)
return &podmanOptions return &podmanOptions
} }
// Return the index of os.Args where to start parsing CLI flags.
// An index > 1 implies Podman is running in shell completion.
func parseIndex() int {
// The shell completion logic will call a command called "__complete" or "__completeNoDesc"
// This command will always be the second argument
// To still parse --remote correctly in this case we have to set args offset to two in this case
if len(os.Args) > 1 && (os.Args[1] == cobra.ShellCompRequestCmd || os.Args[1] == cobra.ShellCompNoDescRequestCmd) {
return 2
}
return 1
}
// Return the containers.conf modules to load.
func containersConfModules() ([]string, error) {
index := parseIndex()
if index > 1 {
// Do not load the modules during shell completion.
return nil, nil
}
var modules []string
fs := pflag.NewFlagSet("module", pflag.ContinueOnError)
fs.ParseErrorsWhitelist.UnknownFlags = true
fs.Usage = func() {}
fs.SetInterspersed(false)
fs.StringSliceVar(&modules, "module", nil, "")
fs.BoolP("help", "h", false, "") // Need a fake help flag to avoid the `pflag: help requested` error
return modules, fs.Parse(os.Args[index:])
}
func newPodmanConfig() { func newPodmanConfig() {
modules, err := containersConfModules()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
if err := setXdgDirs(); err != nil { if err := setXdgDirs(); err != nil {
fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
defaultConfig, err := config.Default() defaultConfig, err := config.New(&config.Options{
SetDefault: true, // This makes sure that following calls to config.Default() return this config
Modules: modules,
})
if err != nil { if err != nil {
fmt.Fprint(os.Stderr, "Failed to obtain podman configuration: "+err.Error()) fmt.Fprint(os.Stderr, "Failed to obtain podman configuration: "+err.Error())
os.Exit(1) os.Exit(1)

View File

@ -7,7 +7,6 @@ import (
"sync" "sync"
"github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/entities"
"github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@ -50,14 +49,7 @@ func IsRemote() bool {
urlFlagName := "url" urlFlagName := "url"
fs.String(urlFlagName, "", "") fs.String(urlFlagName, "", "")
// The shell completion logic will call a command called "__complete" or "__completeNoDesc" _ = fs.Parse(os.Args[parseIndex():])
// This command will always be the second argument
// To still parse --remote correctly in this case we have to set args offset to two in this case
start := 1
if len(os.Args) > 1 && (os.Args[1] == cobra.ShellCompRequestCmd || os.Args[1] == cobra.ShellCompNoDescRequestCmd) {
start = 2
}
_ = fs.Parse(os.Args[start:])
// --connection or --url implies --remote // --connection or --url implies --remote
remoteFromCLI.Value = remoteFromCLI.Value || fs.Changed(connectionFlagName) || fs.Changed(urlFlagName) || fs.Changed(hostFlagName) || fs.Changed(contextFlagName) remoteFromCLI.Value = remoteFromCLI.Value || fs.Changed(connectionFlagName) || fs.Changed(urlFlagName) || fs.Changed(hostFlagName) || fs.Changed(contextFlagName)
}) })

View File

@ -451,6 +451,14 @@ func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) {
} }
podmanConfig.Remote = true podmanConfig.Remote = true
} else { } else {
// The --module's are actually used and parsed in
// `registry.PodmanConfig()`. But we also need to expose them
// as a flag here to a) make sure that rootflags are aware of
// this flag and b) to have shell completions.
moduleFlagName := "module"
pFlags.StringSlice(moduleFlagName, nil, "Load the containers.conf(5) module")
_ = cmd.RegisterFlagCompletionFunc(moduleFlagName, common.AutocompleteContainersConfModules)
// A *hidden* flag to change the database backend. // A *hidden* flag to change the database backend.
pFlags.StringVar(&podmanConfig.ContainersConf.Engine.DBBackend, "db-backend", podmanConfig.ContainersConfDefaultsRO.Engine.DBBackend, "Database backend to use") pFlags.StringVar(&podmanConfig.ContainersConf.Engine.DBBackend, "db-backend", podmanConfig.ContainersConfDefaultsRO.Engine.DBBackend, "Database backend to use")

View File

@ -88,6 +88,12 @@ This will override *imagestore* option in `containers-storage.conf(5)`, refer to
Log messages at and above specified level: __debug__, __info__, __warn__, __error__, __fatal__ or __panic__ (default: _warn_) Log messages at and above specified level: __debug__, __info__, __warn__, __error__, __fatal__ or __panic__ (default: _warn_)
#### **--module**=*path*
Load the specified `containers.conf(5)` module. Can be an absolute or relative path. Please refer to `containers.conf(5)` for details.
This feature is not supported on the remote client, including Mac and Windows (excluding WSL2) machines
#### **--network-cmd-path**=*path* #### **--network-cmd-path**=*path*
Path to the `slirp4netns(1)` command binary to use for setting up a slirp4netns network. Path to the `slirp4netns(1)` command binary to use for setting up a slirp4netns network.
If "" is used, then the binary will first be searched using the `helper_binaries_dir` option in `containers.conf`, and second using the `$PATH` environment variable. If "" is used, then the binary will first be searched using the `helper_binaries_dir` option in `containers.conf`, and second using the `$PATH` environment variable.

4
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/containernetworking/cni v1.1.2 github.com/containernetworking/cni v1.1.2
github.com/containernetworking/plugins v1.3.0 github.com/containernetworking/plugins v1.3.0
github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6 github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6
github.com/containers/common v0.55.1-0.20230811093040-524b4d5c12f9 github.com/containers/common v0.55.1-0.20230814161508-b70b0c49600e
github.com/containers/conmon v2.0.20+incompatible github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.26.1-0.20230807184415-3fb422379cfa github.com/containers/image/v5 v5.26.1-0.20230807184415-3fb422379cfa
github.com/containers/libhvee v0.4.0 github.com/containers/libhvee v0.4.0
@ -157,7 +157,7 @@ require (
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/sftp v1.13.5 // indirect github.com/pkg/sftp v1.13.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/proglottis/gpgme v0.1.3 // indirect github.com/proglottis/gpgme v0.1.3 // indirect
github.com/rivo/uniseg v0.4.4 // indirect github.com/rivo/uniseg v0.4.4 // indirect

22
go.sum
View File

@ -246,8 +246,8 @@ github.com/containernetworking/plugins v1.3.0 h1:QVNXMT6XloyMUoO2wUOqWTC1hWFV62Q
github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0= github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0=
github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6 h1:K/S8SFQsnnNTF0Ws58SrBD9L0EuClzAG8Zp08d7+6AA= github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6 h1:K/S8SFQsnnNTF0Ws58SrBD9L0EuClzAG8Zp08d7+6AA=
github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6/go.mod h1:0sptTFBBtSznLqoTh80DfvMOCNbdRsNRgVOKhBhrupA= github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6/go.mod h1:0sptTFBBtSznLqoTh80DfvMOCNbdRsNRgVOKhBhrupA=
github.com/containers/common v0.55.1-0.20230811093040-524b4d5c12f9 h1:TJIOB2FmgB0YpBby30WmnocbvfCeiJkRORFZjyrkso8= github.com/containers/common v0.55.1-0.20230814161508-b70b0c49600e h1:MD15BW3p8JtzyKx5fLHOQsPyPXZslYth15cu90ttB2o=
github.com/containers/common v0.55.1-0.20230811093040-524b4d5c12f9/go.mod h1:P80FfWkQ7oITVaBAHF50gZVzsj198bJ2IyQjHVsdBVk= github.com/containers/common v0.55.1-0.20230814161508-b70b0c49600e/go.mod h1:u6f5WyhJnfwoKdYlqK8b33OJ2DyYtXJ56jz1K4G7ynw=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.26.1-0.20230807184415-3fb422379cfa h1:wDfVQtc6ik2MvsUmu/YRSyBAE5YUxdjcEDtuT1q2KDo= github.com/containers/image/v5 v5.26.1-0.20230807184415-3fb422379cfa h1:wDfVQtc6ik2MvsUmu/YRSyBAE5YUxdjcEDtuT1q2KDo=
@ -846,8 +846,8 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go= github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
@ -1046,6 +1046,7 @@ github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
@ -1102,8 +1103,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1139,6 +1141,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1184,6 +1187,8 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -1205,6 +1210,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1288,13 +1294,17 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1306,6 +1316,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1364,6 +1375,7 @@ golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4X
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -33,7 +33,7 @@ func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool, sshMode
return nil, nil, nil, nil, err return nil, nil, nil, nil, err
} }
confR, err := config.NewConfig("") // create a hand made config for the remote engine since we might use remote and native at once confR, err := config.New(nil) // create a hand made config for the remote engine since we might use remote and native at once
if err != nil { if err != nil {
return nil, nil, nil, nil, fmt.Errorf("could not make config: %w", err) return nil, nil, nil, nil, fmt.Errorf("could not make config: %w", err)
} }

View File

@ -304,6 +304,13 @@ func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *conf
if syslog { if syslog {
command = append(command, "--syslog") command = append(command, "--syslog")
} }
// Make sure that loaded containers.conf modules are passed down to the
// callback as well.
for _, module := range config.LoadedModules() {
command = append(command, "--module", module)
}
command = append(command, []string{"container", "cleanup"}...) command = append(command, []string{"container", "cleanup"}...)
if rm { if rm {

View File

@ -83,4 +83,98 @@ EOF
is "$output" "Error:.*no such container" is "$output" "Error:.*no such container"
} }
@test "podman --module - absolute path" {
skip_if_remote "--module is not supported for remote clients"
random_data="expected_annotation_$(random_string 15)"
conf_tmp="$PODMAN_TMPDIR/test.conf"
cat > $conf_tmp <<EOF
[containers]
annotations=['module=$random_data']
EOF
run_podman --module=$conf_tmp create -q $IMAGE
cid="$output"
run_podman container inspect $cid --format '{{index .Config.Annotations "module"}}'
is "$output" "$random_data" "container annotation should include the one from the --module"
run_podman rm -f $cid
# Nonexistent module path
nonesuch=${PODMAN_TMPDIR}/nonexistent
run_podman 1 --module=$nonesuch sdfsdfdsf
is "$output" "Failed to obtain podman configuration: could not resolve module \"$nonesuch\": stat $nonesuch: no such file or directory" \
"--module=ENOENT"
}
@test "podman --module - XDG_CONFIG_HOME" {
skip_if_remote "--module is not supported for remote clients"
skip_if_not_rootless "loading a module from XDG_CONFIG_HOME requires rootless"
fake_home="$PODMAN_TMPDIR/home/.config"
fake_modules_dir="$fake_home/containers/containers.conf.modules"
mkdir -p $fake_modules_dir
random_data="expected_annotation_$(random_string 15)"
module_name="test.conf"
conf_tmp="$fake_modules_dir/$module_name"
cat > $conf_tmp <<EOF
[containers]
annotations=['module=$random_data']
EOF
# Test loading a relative path (test.conf) as a module. This should find
# the one in the fake XDG_CONFIG_HOME. We cannot override /etc or
# /usr/share in the tests here, so for those paths we need to rely on the
# unit tests in containers/common/pkg/config and manual QE.
XDG_CONFIG_HOME=$fake_home run_podman --module $module_name run -d -q $IMAGE sleep infinity
cid="$output"
run_podman container inspect $cid --format '{{index .Config.Annotations "module"}}'
is "$output" "$random_data" "container annotation should include the one from the --module"
# Now make sure that conmon's exit-command points to the _absolute path_ of
# the module.
run_podman container inspect $cid --format "{{ .State.ConmonPid }}"
conmon_pid="$output"
is "$(< /proc/$conmon_pid/cmdline)" ".*--exit-command-arg--module--exit-command-arg$conf_tmp.*" "conmon's exit-command uses the module"
run_podman rm -f -t0 $cid
# Corrupt module file
cat > $conf_tmp <<EOF
[containers]
sdf=
EOF
XDG_CONFIG_HOME=$fake_home run_podman 1 --module $module_name
is "$output" "Failed to obtain podman configuration: reading additional config \"$conf_tmp\": decode configuration $conf_tmp: toml: line 3 (last key \"containers.sdf\"): expected value but found '\n' instead" \
"Corrupt module file"
# Nonexistent module name
nonesuch=assume-this-does-not-exist-$(random_string)
XDG_CONFIG_HOME=$fake_home run_podman 1 --module=$nonesuch invalid-command
expect="Failed to obtain podman configuration: could not resolve module \"$nonesuch\": 3 errors occurred:"
for dir in $fake_home /etc /usr/share;do
expect+=$'\n\t'"* stat $dir/containers/containers.conf.modules/$nonesuch: no such file or directory"
done
is "$output" "$expect" "--module=ENOENT : error message"
}
# Too hard to test in 600-completion.bats because of the remote/rootless check
@test "podman --module - command-line completion" {
skip_if_remote "--module is not supported for remote clients"
skip_if_not_rootless "loading a module from XDG_CONFIG_HOME requires rootless"
fake_home="$PODMAN_TMPDIR/home/.config"
fake_modules_dir="$fake_home/containers/containers.conf.modules"
mkdir -p $fake_modules_dir
m1=m1odule_$(random_string)
m2=m2$(random_string)
touch $fake_modules_dir/{$m2,$m1}
XDG_CONFIG_HOME=$fake_home run_podman __completeNoDesc --module ""
# Even if there are modules in /etc or elsewhere, these will be first
assert "${lines[0]}" = "$m1" "completion finds module 1"
assert "${lines[1]}" = "$m2" "completion finds module 2"
}
# vim: filetype=sh # vim: filetype=sh

View File

@ -3,14 +3,11 @@ package config
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/fs"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort"
"strings" "strings"
"sync"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/containers/common/libnetwork/types" "github.com/containers/common/libnetwork/types"
@ -81,6 +78,8 @@ type Config struct {
ConfigMaps ConfigMapConfig `toml:"configmaps"` ConfigMaps ConfigMapConfig `toml:"configmaps"`
// Farms defines configurations for the buildfarm farms // Farms defines configurations for the buildfarm farms
Farms FarmConfig `toml:"farms"` Farms FarmConfig `toml:"farms"`
loadedModules []string // only used at runtime to store which modules were loaded
} }
// ContainersConfig represents the "containers" TOML config table // ContainersConfig represents the "containers" TOML config table
@ -708,166 +707,6 @@ func (c *EngineConfig) ImagePlatformToRuntime(os string, arch string) string {
return c.OCIRuntime return c.OCIRuntime
} }
// NewConfig creates a new Config. It starts with an empty config and, if
// specified, merges the config at `userConfigPath` path. Depending if we're
// running as root or rootless, we then merge the system configuration followed
// by merging the default config (hard-coded default in memory).
// Note that the OCI runtime is hard-set to `crun` if we're running on a system
// with cgroupv2v2. Other OCI runtimes are not yet supporting cgroupv2v2. This
// might change in the future.
func NewConfig(userConfigPath string) (*Config, error) {
// Generate the default config for the system
config, err := DefaultConfig()
if err != nil {
return nil, err
}
// Now, gather the system configs and merge them as needed.
configs, err := systemConfigs()
if err != nil {
return nil, fmt.Errorf("finding config on system: %w", err)
}
for _, path := range configs {
// Merge changes in later configs with the previous configs.
// Each config file that specified fields, will override the
// previous fields.
if err = readConfigFromFile(path, config); err != nil {
return nil, fmt.Errorf("reading system config %q: %w", path, err)
}
logrus.Debugf("Merged system config %q", path)
logrus.Tracef("%+v", config)
}
// If the caller specified a config path to use, then we read it to
// override the system defaults.
if userConfigPath != "" {
var err error
// readConfigFromFile reads in container config in the specified
// file and then merge changes with the current default.
if err = readConfigFromFile(userConfigPath, config); err != nil {
return nil, fmt.Errorf("reading user config %q: %w", userConfigPath, err)
}
logrus.Debugf("Merged user config %q", userConfigPath)
logrus.Tracef("%+v", config)
}
config.addCAPPrefix()
if err := config.Validate(); err != nil {
return nil, err
}
if err := config.setupEnv(); err != nil {
return nil, err
}
return config, nil
}
// readConfigFromFile reads the specified config file at `path` and attempts to
// unmarshal its content into a Config. The config param specifies the previous
// default config. If the path, only specifies a few fields in the Toml file
// the defaults from the config parameter will be used for all other fields.
func readConfigFromFile(path string, config *Config) error {
logrus.Tracef("Reading configuration file %q", path)
meta, err := toml.DecodeFile(path, config)
if err != nil {
return fmt.Errorf("decode configuration %v: %w", path, err)
}
keys := meta.Undecoded()
if len(keys) > 0 {
logrus.Debugf("Failed to decode the keys %q from %q.", keys, path)
}
return nil
}
// addConfigs will search one level in the config dirPath for config files
// If the dirPath does not exist, addConfigs will return nil
func addConfigs(dirPath string, configs []string) ([]string, error) {
newConfigs := []string{}
err := filepath.WalkDir(dirPath,
// WalkFunc to read additional configs
func(path string, d fs.DirEntry, err error) error {
switch {
case err != nil:
// return error (could be a permission problem)
return err
case d.IsDir():
if path != dirPath {
// make sure to not recurse into sub-directories
return filepath.SkipDir
}
// ignore directories
return nil
default:
// only add *.conf files
if strings.HasSuffix(path, ".conf") {
newConfigs = append(newConfigs, path)
}
return nil
}
},
)
if errors.Is(err, os.ErrNotExist) {
err = nil
}
sort.Strings(newConfigs)
return append(configs, newConfigs...), err
}
// Returns the list of configuration files, if they exist in order of hierarchy.
// The files are read in order and each new file can/will override previous
// file settings.
func systemConfigs() (configs []string, finalErr error) {
if path := os.Getenv("CONTAINERS_CONF_OVERRIDE"); path != "" {
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("CONTAINERS_CONF_OVERRIDE file: %w", err)
}
// Add the override config last to make sure it can override any
// previous settings.
defer func() {
if finalErr == nil {
configs = append(configs, path)
}
}()
}
if path := os.Getenv("CONTAINERS_CONF"); path != "" {
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("CONTAINERS_CONF file: %w", err)
}
return append(configs, path), nil
}
if _, err := os.Stat(DefaultContainersConfig); err == nil {
configs = append(configs, DefaultContainersConfig)
}
if _, err := os.Stat(OverrideContainersConfig); err == nil {
configs = append(configs, OverrideContainersConfig)
}
var err error
configs, err = addConfigs(OverrideContainersConfig+".d", configs)
if err != nil {
return nil, err
}
path, err := ifRootlessConfigPath()
if err != nil {
return nil, err
}
if path != "" {
if _, err := os.Stat(path); err == nil {
configs = append(configs, path)
}
configs, err = addConfigs(path+".d", configs)
if err != nil {
return nil, err
}
}
return configs, nil
}
// CheckCgroupsAndAdjustConfig checks if we're running rootless with the systemd // CheckCgroupsAndAdjustConfig checks if we're running rootless with the systemd
// cgroup manager. In case the user session isn't available, we're switching the // cgroup manager. In case the user session isn't available, we're switching the
// cgroup manager to cgroupfs. Note, this only applies to rootless. // cgroup manager to cgroupfs. Note, this only applies to rootless.
@ -1190,37 +1029,6 @@ func rootlessConfigPath() (string, error) {
return filepath.Join(home, UserOverrideContainersConfig), nil return filepath.Join(home, UserOverrideContainersConfig), nil
} }
var (
configErr error
configMutex sync.Mutex
config *Config
)
// Default returns the default container config.
// Configuration files will be read in the following files:
// * /usr/share/containers/containers.conf
// * /etc/containers/containers.conf
// * $HOME/.config/containers/containers.conf # When run in rootless mode
// Fields in latter files override defaults set in previous files and the
// default config.
// None of these files are required, and not all fields need to be specified
// in each file, only the fields you want to override.
// The system defaults container config files can be overwritten using the
// CONTAINERS_CONF environment variable. This is usually done for testing.
func Default() (*Config, error) {
configMutex.Lock()
defer configMutex.Unlock()
if config != nil || configErr != nil {
return config, configErr
}
return defConfig()
}
func defConfig() (*Config, error) {
config, configErr = NewConfig("")
return config, configErr
}
func Path() string { func Path() string {
if path := os.Getenv("CONTAINERS_CONF"); path != "" { if path := os.Getenv("CONTAINERS_CONF"); path != "" {
return path return path
@ -1289,9 +1097,7 @@ func (c *Config) Write() error {
// This function is meant to be used for long-running processes that need to reload potential changes made to // This function is meant to be used for long-running processes that need to reload potential changes made to
// the cached containers.conf files. // the cached containers.conf files.
func Reload() (*Config, error) { func Reload() (*Config, error) {
configMutex.Lock() return New(&Options{SetDefault: true})
defer configMutex.Unlock()
return defConfig()
} }
func (c *Config) ActiveDestination() (uri, identity string, machine bool, err error) { func (c *Config) ActiveDestination() (uri, identity string, machine bool, err error) {

View File

@ -157,9 +157,11 @@ const (
DefaultVolumePluginTimeout = 5 DefaultVolumePluginTimeout = 5
) )
// DefaultConfig defines the default values from containers.conf. // defaultConfig returns Config with builtin defaults and minimal adjustments
func DefaultConfig() (*Config, error) { // to the current host only. It does not read any config files from the host or
defaultEngineConfig, err := defaultConfigFromMemory() // the environment.
func defaultConfig() (*Config, error) {
defaultEngineConfig, err := defaultEngineConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -266,9 +268,9 @@ func defaultFarmConfig() FarmConfig {
} }
} }
// defaultConfigFromMemory returns a default engine configuration. Note that the // defaultEngineConfig eturns a default engine configuration. Note that the
// config is different for root and rootless. It also parses the storage.conf. // config is different for root and rootless. It also parses the storage.conf.
func defaultConfigFromMemory() (*EngineConfig, error) { func defaultEngineConfig() (*EngineConfig, error) {
c := new(EngineConfig) c := new(EngineConfig)
tmp, err := defaultTmpDir() tmp, err := defaultTmpDir()
if err != nil { if err != nil {
@ -653,3 +655,16 @@ func useUserConfigLocations() bool {
// GetRootlessUID == -1 on Windows, so exclude negative range // GetRootlessUID == -1 on Windows, so exclude negative range
return unshare.GetRootlessUID() > 0 return unshare.GetRootlessUID() > 0
} }
// getDefaultImage returns the default machine image stream
// On Windows this refers to the Fedora major release number
func getDefaultMachineImage() string {
return "testing"
}
// getDefaultMachineUser returns the user to use for rootless podman
// This is only for the apple, hyperv, and qemu implementations.
// WSL's user will be hardcoded in podman to "user"
func getDefaultMachineUser() string {
return "core"
}

View File

@ -17,17 +17,6 @@ func getDefaultCgroupsMode() string {
return "enabled" return "enabled"
} }
// getDefaultMachineImage returns the default machine image stream
// On Linux/Mac, this returns the FCOS stream
func getDefaultMachineImage() string {
return "testing"
}
// getDefaultMachineUser returns the user to use for rootless podman
func getDefaultMachineUser() string {
return "core"
}
// getDefaultProcessLimits returns the nproc for the current process in ulimits format // getDefaultProcessLimits returns the nproc for the current process in ulimits format
// Note that nfile sometimes cannot be set to unlimited, and the limit is hardcoded // Note that nfile sometimes cannot be set to unlimited, and the limit is hardcoded
// to (oldMaxSize) 1048576 (2^20), see: http://stackoverflow.com/a/1213069/1811501 // to (oldMaxSize) 1048576 (2^20), see: http://stackoverflow.com/a/1213069/1811501

View File

@ -5,17 +5,6 @@ package config
import "os" import "os"
// getDefaultMachineImage returns the default machine image stream
// On Linux/Mac, this returns the FCOS stream
func getDefaultMachineImage() string {
return "testing"
}
// getDefaultMachineUser returns the user to use for rootless podman
func getDefaultMachineUser() string {
return "core"
}
// isCgroup2UnifiedMode returns whether we are running in cgroup2 mode. // isCgroup2UnifiedMode returns whether we are running in cgroup2 mode.
func isCgroup2UnifiedMode() (isUnified bool, isUnifiedErr error) { func isCgroup2UnifiedMode() (isUnified bool, isUnifiedErr error) {
return false, nil return false, nil

View File

@ -2,19 +2,6 @@ package config
import "os" import "os"
// getDefaultImage returns the default machine image stream
// On Windows this refers to the Fedora major release number
func getDefaultMachineImage() string {
return "testing"
}
// getDefaultMachineUser returns the user to use for rootless podman
// This is only for the hyperv and qemu implementations. WSL's user
// will be hardcoded in podman to "user"
func getDefaultMachineUser() string {
return "core"
}
// isCgroup2UnifiedMode returns whether we are running in cgroup2 mode. // isCgroup2UnifiedMode returns whether we are running in cgroup2 mode.
func isCgroup2UnifiedMode() (isUnified bool, isUnifiedErr error) { func isCgroup2UnifiedMode() (isUnified bool, isUnifiedErr error) {
return false, nil return false, nil

View File

@ -0,0 +1,95 @@
package config
import (
"fmt"
"os"
"path/filepath"
"github.com/containers/storage/pkg/homedir"
"github.com/containers/storage/pkg/unshare"
"github.com/hashicorp/go-multierror"
)
// The subdirectory for looking up containers.conf modules.
const moduleSubdir = "containers/containers.conf.modules"
// Moving the base paths into variables allows for overriding them in units
// tests.
var (
moduleBaseEtc = "/etc/"
moduleBaseUsr = "/usr/share"
)
// LoadedModules returns absolute paths to loaded containers.conf modules.
func (c *Config) LoadedModules() []string {
// Required for conmon's callback to Podman's cleanup.
// Absolute paths make loading the modules a bit faster.
return c.loadedModules
}
// Find the specified modules in the options. Return an error if a specific
// module cannot be located on the host.
func (o *Options) modules() ([]string, error) {
if len(o.Modules) == 0 {
return nil, nil
}
dirs, err := ModuleDirectories()
if err != nil {
return nil, err
}
configs := make([]string, 0, len(o.Modules))
for _, path := range o.Modules {
resolved, err := resolveModule(path, dirs)
if err != nil {
return nil, fmt.Errorf("could not resolve module %q: %w", path, err)
}
configs = append(configs, resolved)
}
return configs, nil
}
// ModuleDirectories return the directories to load modules from:
// 1) XDG_CONFIG_HOME/HOME if rootless
// 2) /etc/
// 3) /usr/share
func ModuleDirectories() ([]string, error) { // Public API for shell completions in Podman
modules := []string{
filepath.Join(moduleBaseEtc, moduleSubdir),
filepath.Join(moduleBaseUsr, moduleSubdir),
}
if !unshare.IsRootless() {
return modules, nil
}
// Prepend the user modules dir.
configHome, err := homedir.GetConfigHome()
if err != nil {
return nil, err
}
return append([]string{filepath.Join(configHome, moduleSubdir)}, modules...), nil
}
// Resolve the specified path to a module.
func resolveModule(path string, dirs []string) (string, error) {
if filepath.IsAbs(path) {
_, err := os.Stat(path)
return path, err
}
// Collect all errors to avoid suppressing important errors (e.g.,
// permission errors).
var multiErr error
for _, d := range dirs {
candidate := filepath.Join(d, path)
_, err := os.Stat(candidate)
if err == nil {
return candidate, nil
}
multiErr = multierror.Append(multiErr, err)
}
return "", multiErr
}

240
vendor/github.com/containers/common/pkg/config/new.go generated vendored Normal file
View File

@ -0,0 +1,240 @@
package config
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/BurntSushi/toml"
"github.com/sirupsen/logrus"
)
var (
cachedConfigError error
cachedConfigMutex sync.Mutex
cachedConfig *Config
)
const (
// FIXME: update code base and tests to use the two constants below.
containersConfEnv = "CONTAINERS_CONF"
containersConfOverrideEnv = containersConfEnv + "_OVERRIDE"
)
// Options to use when loading a Config via New().
type Options struct {
// Attempt to load the following config modules.
Modules []string
// Set the loaded config as the default one which can later on be
// accessed via Default().
SetDefault bool
// Additional configs to load. An internal only field to make the
// behavior observable and testable in unit tests.
additionalConfigs []string
}
// New returns a Config as described in the containers.conf(5) man page.
func New(options *Options) (*Config, error) {
if options == nil {
options = &Options{}
} else if options.SetDefault {
cachedConfigMutex.Lock()
defer cachedConfigMutex.Unlock()
}
return newLocked(options)
}
// Default returns the default container config. If no default config has been
// set yet, a new config will be loaded by New() and set as the default one.
// All callers are expected to use the returned Config read only. Changing
// data may impact other call sites.
func Default() (*Config, error) {
cachedConfigMutex.Lock()
defer cachedConfigMutex.Unlock()
if cachedConfig != nil || cachedConfigError != nil {
return cachedConfig, cachedConfigError
}
cachedConfig, cachedConfigError = newLocked(&Options{SetDefault: true})
return cachedConfig, cachedConfigError
}
// A helper function for New() expecting the caller to hold the
// cachedConfigMutex if options.SetDefault is set..
func newLocked(options *Options) (*Config, error) {
// Start with the built-in defaults
config, err := defaultConfig()
if err != nil {
return nil, err
}
// Now, gather the system configs and merge them as needed.
configs, err := systemConfigs()
if err != nil {
return nil, fmt.Errorf("finding config on system: %w", err)
}
for _, path := range configs {
// Merge changes in later configs with the previous configs.
// Each config file that specified fields, will override the
// previous fields.
if err = readConfigFromFile(path, config); err != nil {
return nil, fmt.Errorf("reading system config %q: %w", path, err)
}
logrus.Debugf("Merged system config %q", path)
logrus.Tracef("%+v", config)
}
modules, err := options.modules()
if err != nil {
return nil, err
}
config.loadedModules = modules
options.additionalConfigs = append(options.additionalConfigs, modules...)
// The _OVERRIDE variable _must_ always win. That's a contract we need
// to honor (for the Podman CI).
if path := os.Getenv(containersConfOverrideEnv); path != "" {
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("%s file: %w", containersConfOverrideEnv, err)
}
options.additionalConfigs = append(options.additionalConfigs, path)
}
// If the caller specified a config path to use, then we read it to
// override the system defaults.
for _, add := range options.additionalConfigs {
if add == "" {
continue
}
// readConfigFromFile reads in container config in the specified
// file and then merge changes with the current default.
if err := readConfigFromFile(add, config); err != nil {
return nil, fmt.Errorf("reading additional config %q: %w", add, err)
}
logrus.Debugf("Merged additional config %q", add)
logrus.Tracef("%+v", config)
}
config.addCAPPrefix()
if err := config.Validate(); err != nil {
return nil, err
}
if err := config.setupEnv(); err != nil {
return nil, err
}
if options.SetDefault {
cachedConfig = config
cachedConfigError = nil
}
return config, nil
}
// NewConfig creates a new Config. It starts with an empty config and, if
// specified, merges the config at `userConfigPath` path.
//
// Deprecated: use new instead.
func NewConfig(userConfigPath string) (*Config, error) {
return New(&Options{additionalConfigs: []string{userConfigPath}})
}
// Returns the list of configuration files, if they exist in order of hierarchy.
// The files are read in order and each new file can/will override previous
// file settings.
func systemConfigs() (configs []string, finalErr error) {
if path := os.Getenv(containersConfEnv); path != "" {
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("%s file: %w", containersConfEnv, err)
}
return append(configs, path), nil
}
if _, err := os.Stat(DefaultContainersConfig); err == nil {
configs = append(configs, DefaultContainersConfig)
}
if _, err := os.Stat(OverrideContainersConfig); err == nil {
configs = append(configs, OverrideContainersConfig)
}
var err error
configs, err = addConfigs(OverrideContainersConfig+".d", configs)
if err != nil {
return nil, err
}
path, err := ifRootlessConfigPath()
if err != nil {
return nil, err
}
if path != "" {
if _, err := os.Stat(path); err == nil {
configs = append(configs, path)
}
configs, err = addConfigs(path+".d", configs)
if err != nil {
return nil, err
}
}
return configs, nil
}
// addConfigs will search one level in the config dirPath for config files
// If the dirPath does not exist, addConfigs will return nil
func addConfigs(dirPath string, configs []string) ([]string, error) {
newConfigs := []string{}
err := filepath.WalkDir(dirPath,
// WalkFunc to read additional configs
func(path string, d fs.DirEntry, err error) error {
switch {
case err != nil:
// return error (could be a permission problem)
return err
case d.IsDir():
if path != dirPath {
// make sure to not recurse into sub-directories
return filepath.SkipDir
}
// ignore directories
return nil
default:
// only add *.conf files
if strings.HasSuffix(path, ".conf") {
newConfigs = append(newConfigs, path)
}
return nil
}
},
)
if errors.Is(err, os.ErrNotExist) {
err = nil
}
sort.Strings(newConfigs)
return append(configs, newConfigs...), err
}
// readConfigFromFile reads the specified config file at `path` and attempts to
// unmarshal its content into a Config. The config param specifies the previous
// default config. If the path, only specifies a few fields in the Toml file
// the defaults from the config parameter will be used for all other fields.
func readConfigFromFile(path string, config *Config) error {
logrus.Tracef("Reading configuration file %q", path)
meta, err := toml.DecodeFile(path, config)
if err != nil {
return fmt.Errorf("decode configuration %v: %w", path, err)
}
keys := meta.Undecoded()
if len(keys) > 0 {
logrus.Debugf("Failed to decode the keys %q from %q.", keys, path)
}
return nil
}

33
vendor/github.com/pkg/sftp/attrs.go generated vendored
View File

@ -1,7 +1,7 @@
package sftp package sftp
// ssh_FXP_ATTRS support // ssh_FXP_ATTRS support
// see http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5 // see https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5
import ( import (
"os" "os"
@ -69,6 +69,20 @@ func fileInfoFromStat(stat *FileStat, name string) os.FileInfo {
} }
} }
// FileInfoUidGid extends os.FileInfo and adds callbacks for Uid and Gid retrieval,
// as an alternative to *syscall.Stat_t objects on unix systems.
type FileInfoUidGid interface {
os.FileInfo
Uid() uint32
Gid() uint32
}
// FileInfoUidGid extends os.FileInfo and adds a callbacks for extended data retrieval.
type FileInfoExtendedData interface {
os.FileInfo
Extended() []StatExtended
}
func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) { func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) {
mtime := fi.ModTime().Unix() mtime := fi.ModTime().Unix()
atime := mtime atime := mtime
@ -86,5 +100,22 @@ func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) {
// os specific file stat decoding // os specific file stat decoding
fileStatFromInfoOs(fi, &flags, fileStat) fileStatFromInfoOs(fi, &flags, fileStat)
// The call above will include the sshFileXferAttrUIDGID in case
// the os.FileInfo can be casted to *syscall.Stat_t on unix.
// If fi implements FileInfoUidGid, retrieve Uid, Gid from it instead.
if fiExt, ok := fi.(FileInfoUidGid); ok {
flags |= sshFileXferAttrUIDGID
fileStat.UID = fiExt.Uid()
fileStat.GID = fiExt.Gid()
}
// if fi implements FileInfoExtendedData, retrieve extended data from it
if fiExt, ok := fi.(FileInfoExtendedData); ok {
fileStat.Extended = fiExt.Extended()
if len(fileStat.Extended) > 0 {
flags |= sshFileXferAttrExtended
}
}
return flags, fileStat return flags, fileStat
} }

View File

@ -1,3 +1,4 @@
//go:build plan9 || windows || android
// +build plan9 windows android // +build plan9 windows android
package sftp package sftp

View File

@ -1,3 +1,4 @@
//go:build darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || aix || js
// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix js // +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix js
package sftp package sftp

65
vendor/github.com/pkg/sftp/client.go generated vendored
View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"io" "io"
"math" "math"
"os" "os"
@ -226,15 +227,22 @@ func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Clie
if err := sftp.sendInit(); err != nil { if err := sftp.sendInit(); err != nil {
wr.Close() wr.Close()
return nil, err return nil, fmt.Errorf("error sending init packet to server: %w", err)
} }
if err := sftp.recvVersion(); err != nil { if err := sftp.recvVersion(); err != nil {
wr.Close() wr.Close()
return nil, err return nil, fmt.Errorf("error receiving version packet from server: %w", err)
} }
sftp.clientConn.wg.Add(1) sftp.clientConn.wg.Add(1)
go sftp.loop() go func() {
defer sftp.clientConn.wg.Done()
if err := sftp.clientConn.recv(); err != nil {
sftp.clientConn.broadcastErr(err)
}
}()
return sftp, nil return sftp, nil
} }
@ -251,11 +259,11 @@ func (c *Client) Create(path string) (*File, error) {
return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC)) return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC))
} }
const sftpProtocolVersion = 3 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 const sftpProtocolVersion = 3 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
func (c *Client) sendInit() error { func (c *Client) sendInit() error {
return c.clientConn.conn.sendPacket(&sshFxInitPacket{ return c.clientConn.conn.sendPacket(&sshFxInitPacket{
Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 Version: sftpProtocolVersion, // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
}) })
} }
@ -267,8 +275,13 @@ func (c *Client) nextID() uint32 {
func (c *Client) recvVersion() error { func (c *Client) recvVersion() error {
typ, data, err := c.recvPacket(0) typ, data, err := c.recvPacket(0)
if err != nil { if err != nil {
if err == io.EOF {
return fmt.Errorf("server unexpectedly closed connection: %w", io.ErrUnexpectedEOF)
}
return err return err
} }
if typ != sshFxpVersion { if typ != sshFxpVersion {
return &unexpectedPacketErr{sshFxpVersion, typ} return &unexpectedPacketErr{sshFxpVersion, typ}
} }
@ -277,6 +290,7 @@ func (c *Client) recvVersion() error {
if err != nil { if err != nil {
return err return err
} }
if version != sftpProtocolVersion { if version != sftpProtocolVersion {
return &unexpectedVersionErr{sftpProtocolVersion, version} return &unexpectedVersionErr{sftpProtocolVersion, version}
} }
@ -910,6 +924,45 @@ func (c *Client) MkdirAll(path string) error {
return nil return nil
} }
// RemoveAll delete files recursively in the directory and Recursively delete subdirectories.
// An error will be returned if no file or directory with the specified path exists
func (c *Client) RemoveAll(path string) error {
// Get the file/directory information
fi, err := c.Stat(path)
if err != nil {
return err
}
if fi.IsDir() {
// Delete files recursively in the directory
files, err := c.ReadDir(path)
if err != nil {
return err
}
for _, file := range files {
if file.IsDir() {
// Recursively delete subdirectories
err = c.RemoveAll(path + "/" + file.Name())
if err != nil {
return err
}
} else {
// Delete individual files
err = c.Remove(path + "/" + file.Name())
if err != nil {
return err
}
}
}
}
return c.Remove(path)
}
// File represents a remote file. // File represents a remote file.
type File struct { type File struct {
c *Client c *Client
@ -1660,7 +1713,7 @@ func (f *File) ReadFromWithConcurrency(r io.Reader, concurrency int) (read int64
Handle: f.handle, Handle: f.handle,
Offset: uint64(off), Offset: uint64(off),
Length: uint32(n), Length: uint32(n),
Data: b, Data: b[:n],
}) })
select { select {

12
vendor/github.com/pkg/sftp/conn.go generated vendored
View File

@ -18,7 +18,9 @@ type conn struct {
} }
// the orderID is used in server mode if the allocator is enabled. // the orderID is used in server mode if the allocator is enabled.
// For the client mode just pass 0 // For the client mode just pass 0.
// It returns io.EOF if the connection is closed and
// there are no more packets to read.
func (c *conn) recvPacket(orderID uint32) (uint8, []byte, error) { func (c *conn) recvPacket(orderID uint32) (uint8, []byte, error) {
return recvPacket(c, c.alloc, orderID) return recvPacket(c, c.alloc, orderID)
} }
@ -61,14 +63,6 @@ func (c *clientConn) Close() error {
return c.conn.Close() return c.conn.Close()
} }
func (c *clientConn) loop() {
defer c.wg.Done()
err := c.recv()
if err != nil {
c.broadcastErr(err)
}
}
// recv continuously reads from the server and forwards responses to the // recv continuously reads from the server and forwards responses to the
// appropriate channel. // appropriate channel.
func (c *clientConn) recv() error { func (c *clientConn) recv() error {

View File

@ -1,3 +1,4 @@
//go:build debug
// +build debug // +build debug
package sftp package sftp

1
vendor/github.com/pkg/sftp/fuzz.go generated vendored
View File

@ -1,3 +1,4 @@
//go:build gofuzz
// +build gofuzz // +build gofuzz
package sftp package sftp

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
// Attributes related flags. // Attributes related flags.
const ( const (
@ -12,7 +12,7 @@ const (
// Attributes defines the file attributes type defined in draft-ietf-secsh-filexfer-02 // Attributes defines the file attributes type defined in draft-ietf-secsh-filexfer-02
// //
// Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5 // Defined in: https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5
type Attributes struct { type Attributes struct {
Flags uint32 Flags uint32
@ -116,32 +116,32 @@ func (a *Attributes) Len() int {
} }
// MarshalInto marshals e onto the end of the given Buffer. // MarshalInto marshals e onto the end of the given Buffer.
func (a *Attributes) MarshalInto(b *Buffer) { func (a *Attributes) MarshalInto(buf *Buffer) {
b.AppendUint32(a.Flags) buf.AppendUint32(a.Flags)
if a.Flags&AttrSize != 0 { if a.Flags&AttrSize != 0 {
b.AppendUint64(a.Size) buf.AppendUint64(a.Size)
} }
if a.Flags&AttrUIDGID != 0 { if a.Flags&AttrUIDGID != 0 {
b.AppendUint32(a.UID) buf.AppendUint32(a.UID)
b.AppendUint32(a.GID) buf.AppendUint32(a.GID)
} }
if a.Flags&AttrPermissions != 0 { if a.Flags&AttrPermissions != 0 {
b.AppendUint32(uint32(a.Permissions)) buf.AppendUint32(uint32(a.Permissions))
} }
if a.Flags&AttrACModTime != 0 { if a.Flags&AttrACModTime != 0 {
b.AppendUint32(a.ATime) buf.AppendUint32(a.ATime)
b.AppendUint32(a.MTime) buf.AppendUint32(a.MTime)
} }
if a.Flags&AttrExtended != 0 { if a.Flags&AttrExtended != 0 {
b.AppendUint32(uint32(len(a.ExtendedAttributes))) buf.AppendUint32(uint32(len(a.ExtendedAttributes)))
for _, ext := range a.ExtendedAttributes { for _, ext := range a.ExtendedAttributes {
ext.MarshalInto(b) ext.MarshalInto(buf)
} }
} }
} }
@ -156,74 +156,51 @@ func (a *Attributes) MarshalBinary() ([]byte, error) {
// UnmarshalFrom unmarshals an Attributes from the given Buffer into e. // UnmarshalFrom unmarshals an Attributes from the given Buffer into e.
// //
// NOTE: The values of fields not covered in the a.Flags are explicitly undefined. // NOTE: The values of fields not covered in the a.Flags are explicitly undefined.
func (a *Attributes) UnmarshalFrom(b *Buffer) (err error) { func (a *Attributes) UnmarshalFrom(buf *Buffer) (err error) {
flags, err := b.ConsumeUint32() flags := buf.ConsumeUint32()
if err != nil {
return err
}
return a.XXX_UnmarshalByFlags(flags, b) return a.XXX_UnmarshalByFlags(flags, buf)
} }
// XXX_UnmarshalByFlags uses the pre-existing a.Flags field to determine which fields to decode. // XXX_UnmarshalByFlags uses the pre-existing a.Flags field to determine which fields to decode.
// DO NOT USE THIS: it is an anti-corruption function to implement existing internal usage in pkg/sftp. // DO NOT USE THIS: it is an anti-corruption function to implement existing internal usage in pkg/sftp.
// This function is not a part of any compatibility promise. // This function is not a part of any compatibility promise.
func (a *Attributes) XXX_UnmarshalByFlags(flags uint32, b *Buffer) (err error) { func (a *Attributes) XXX_UnmarshalByFlags(flags uint32, buf *Buffer) (err error) {
a.Flags = flags a.Flags = flags
// Short-circuit dummy attributes. // Short-circuit dummy attributes.
if a.Flags == 0 { if a.Flags == 0 {
return nil return buf.Err
} }
if a.Flags&AttrSize != 0 { if a.Flags&AttrSize != 0 {
if a.Size, err = b.ConsumeUint64(); err != nil { a.Size = buf.ConsumeUint64()
return err
}
} }
if a.Flags&AttrUIDGID != 0 { if a.Flags&AttrUIDGID != 0 {
if a.UID, err = b.ConsumeUint32(); err != nil { a.UID = buf.ConsumeUint32()
return err a.GID = buf.ConsumeUint32()
}
if a.GID, err = b.ConsumeUint32(); err != nil {
return err
}
} }
if a.Flags&AttrPermissions != 0 { if a.Flags&AttrPermissions != 0 {
m, err := b.ConsumeUint32() a.Permissions = FileMode(buf.ConsumeUint32())
if err != nil {
return err
}
a.Permissions = FileMode(m)
} }
if a.Flags&AttrACModTime != 0 { if a.Flags&AttrACModTime != 0 {
if a.ATime, err = b.ConsumeUint32(); err != nil { a.ATime = buf.ConsumeUint32()
return err a.MTime = buf.ConsumeUint32()
}
if a.MTime, err = b.ConsumeUint32(); err != nil {
return err
}
} }
if a.Flags&AttrExtended != 0 { if a.Flags&AttrExtended != 0 {
count, err := b.ConsumeUint32() count := buf.ConsumeCount()
if err != nil {
return err
}
a.ExtendedAttributes = make([]ExtendedAttribute, count) a.ExtendedAttributes = make([]ExtendedAttribute, count)
for i := range a.ExtendedAttributes { for i := range a.ExtendedAttributes {
a.ExtendedAttributes[i].UnmarshalFrom(b) a.ExtendedAttributes[i].UnmarshalFrom(buf)
} }
} }
return nil return buf.Err
} }
// UnmarshalBinary decodes the binary encoding of Attributes into e. // UnmarshalBinary decodes the binary encoding of Attributes into e.
@ -233,7 +210,7 @@ func (a *Attributes) UnmarshalBinary(data []byte) error {
// ExtendedAttribute defines the extended file attribute type defined in draft-ietf-secsh-filexfer-02 // ExtendedAttribute defines the extended file attribute type defined in draft-ietf-secsh-filexfer-02
// //
// Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5 // Defined in: https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5
type ExtendedAttribute struct { type ExtendedAttribute struct {
Type string Type string
Data string Data string
@ -245,9 +222,9 @@ func (e *ExtendedAttribute) Len() int {
} }
// MarshalInto marshals e onto the end of the given Buffer. // MarshalInto marshals e onto the end of the given Buffer.
func (e *ExtendedAttribute) MarshalInto(b *Buffer) { func (e *ExtendedAttribute) MarshalInto(buf *Buffer) {
b.AppendString(e.Type) buf.AppendString(e.Type)
b.AppendString(e.Data) buf.AppendString(e.Data)
} }
// MarshalBinary returns e as the binary encoding of e. // MarshalBinary returns e as the binary encoding of e.
@ -258,16 +235,13 @@ func (e *ExtendedAttribute) MarshalBinary() ([]byte, error) {
} }
// UnmarshalFrom unmarshals an ExtendedAattribute from the given Buffer into e. // UnmarshalFrom unmarshals an ExtendedAattribute from the given Buffer into e.
func (e *ExtendedAttribute) UnmarshalFrom(b *Buffer) (err error) { func (e *ExtendedAttribute) UnmarshalFrom(buf *Buffer) (err error) {
if e.Type, err = b.ConsumeString(); err != nil { *e = ExtendedAttribute{
return err Type: buf.ConsumeString(),
Data: buf.ConsumeString(),
} }
if e.Data, err = b.ConsumeString(); err != nil { return buf.Err
return err
}
return nil
} }
// UnmarshalBinary decodes the binary encoding of ExtendedAttribute into e. // UnmarshalBinary decodes the binary encoding of ExtendedAttribute into e.
@ -290,11 +264,11 @@ func (e *NameEntry) Len() int {
} }
// MarshalInto marshals e onto the end of the given Buffer. // MarshalInto marshals e onto the end of the given Buffer.
func (e *NameEntry) MarshalInto(b *Buffer) { func (e *NameEntry) MarshalInto(buf *Buffer) {
b.AppendString(e.Filename) buf.AppendString(e.Filename)
b.AppendString(e.Longname) buf.AppendString(e.Longname)
e.Attrs.MarshalInto(b) e.Attrs.MarshalInto(buf)
} }
// MarshalBinary returns e as the binary encoding of e. // MarshalBinary returns e as the binary encoding of e.
@ -307,16 +281,13 @@ func (e *NameEntry) MarshalBinary() ([]byte, error) {
// UnmarshalFrom unmarshals an NameEntry from the given Buffer into e. // UnmarshalFrom unmarshals an NameEntry from the given Buffer into e.
// //
// NOTE: The values of fields not covered in the a.Flags are explicitly undefined. // NOTE: The values of fields not covered in the a.Flags are explicitly undefined.
func (e *NameEntry) UnmarshalFrom(b *Buffer) (err error) { func (e *NameEntry) UnmarshalFrom(buf *Buffer) (err error) {
if e.Filename, err = b.ConsumeString(); err != nil { *e = NameEntry{
return err Filename: buf.ConsumeString(),
Longname: buf.ConsumeString(),
} }
if e.Longname, err = b.ConsumeString(); err != nil { return e.Attrs.UnmarshalFrom(buf)
return err
}
return e.Attrs.UnmarshalFrom(b)
} }
// UnmarshalBinary decodes the binary encoding of NameEntry into e. // UnmarshalBinary decodes the binary encoding of NameEntry into e.

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
import ( import (
"encoding/binary" "encoding/binary"
@ -17,6 +17,7 @@ var (
type Buffer struct { type Buffer struct {
b []byte b []byte
off int off int
Err error
} }
// NewBuffer creates and initializes a new buffer using buf as its initial contents. // NewBuffer creates and initializes a new buffer using buf as its initial contents.
@ -51,14 +52,17 @@ func (b *Buffer) Cap() int { return cap(b.b) }
// Reset resets the buffer to be empty, but it retains the underlying storage for use by future Appends. // Reset resets the buffer to be empty, but it retains the underlying storage for use by future Appends.
func (b *Buffer) Reset() { func (b *Buffer) Reset() {
b.b = b.b[:0] *b = Buffer{
b.off = 0 b: b.b[:0],
}
} }
// StartPacket resets and initializes the buffer to be ready to start marshaling a packet into. // StartPacket resets and initializes the buffer to be ready to start marshaling a packet into.
// It truncates the buffer, reserves space for uint32(length), then appends the given packetType and requestID. // It truncates the buffer, reserves space for uint32(length), then appends the given packetType and requestID.
func (b *Buffer) StartPacket(packetType PacketType, requestID uint32) { func (b *Buffer) StartPacket(packetType PacketType, requestID uint32) {
b.b, b.off = append(b.b[:0], make([]byte, 4)...), 0 *b = Buffer{
b: append(b.b[:0], make([]byte, 4)...),
}
b.AppendUint8(uint8(packetType)) b.AppendUint8(uint8(packetType))
b.AppendUint32(requestID) b.AppendUint32(requestID)
@ -81,15 +85,21 @@ func (b *Buffer) Packet(payload []byte) (header, payloadPassThru []byte, err err
} }
// ConsumeUint8 consumes a single byte from the buffer. // ConsumeUint8 consumes a single byte from the buffer.
// If the buffer does not have enough data, it will return ErrShortPacket. // If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeUint8() (uint8, error) { func (b *Buffer) ConsumeUint8() uint8 {
if b.Err != nil {
return 0
}
if b.Len() < 1 { if b.Len() < 1 {
return 0, ErrShortPacket b.off = len(b.b)
b.Err = ErrShortPacket
return 0
} }
var v uint8 var v uint8
v, b.off = b.b[b.off], b.off+1 v, b.off = b.b[b.off], b.off+1
return v, nil return v
} }
// AppendUint8 appends a single byte into the buffer. // AppendUint8 appends a single byte into the buffer.
@ -98,14 +108,9 @@ func (b *Buffer) AppendUint8(v uint8) {
} }
// ConsumeBool consumes a single byte from the buffer, and returns true if that byte is non-zero. // ConsumeBool consumes a single byte from the buffer, and returns true if that byte is non-zero.
// If the buffer does not have enough data, it will return ErrShortPacket. // If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeBool() (bool, error) { func (b *Buffer) ConsumeBool() bool {
v, err := b.ConsumeUint8() return b.ConsumeUint8() != 0
if err != nil {
return false, err
}
return v != 0, nil
} }
// AppendBool appends a single bool into the buffer. // AppendBool appends a single bool into the buffer.
@ -119,15 +124,21 @@ func (b *Buffer) AppendBool(v bool) {
} }
// ConsumeUint16 consumes a single uint16 from the buffer, in network byte order (big-endian). // ConsumeUint16 consumes a single uint16 from the buffer, in network byte order (big-endian).
// If the buffer does not have enough data, it will return ErrShortPacket. // If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeUint16() (uint16, error) { func (b *Buffer) ConsumeUint16() uint16 {
if b.Err != nil {
return 0
}
if b.Len() < 2 { if b.Len() < 2 {
return 0, ErrShortPacket b.off = len(b.b)
b.Err = ErrShortPacket
return 0
} }
v := binary.BigEndian.Uint16(b.b[b.off:]) v := binary.BigEndian.Uint16(b.b[b.off:])
b.off += 2 b.off += 2
return v, nil return v
} }
// AppendUint16 appends single uint16 into the buffer, in network byte order (big-endian). // AppendUint16 appends single uint16 into the buffer, in network byte order (big-endian).
@ -146,15 +157,21 @@ func unmarshalUint32(b []byte) uint32 {
} }
// ConsumeUint32 consumes a single uint32 from the buffer, in network byte order (big-endian). // ConsumeUint32 consumes a single uint32 from the buffer, in network byte order (big-endian).
// If the buffer does not have enough data, it will return ErrShortPacket. // If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeUint32() (uint32, error) { func (b *Buffer) ConsumeUint32() uint32 {
if b.Err != nil {
return 0
}
if b.Len() < 4 { if b.Len() < 4 {
return 0, ErrShortPacket b.off = len(b.b)
b.Err = ErrShortPacket
return 0
} }
v := binary.BigEndian.Uint32(b.b[b.off:]) v := binary.BigEndian.Uint32(b.b[b.off:])
b.off += 4 b.off += 4
return v, nil return v
} }
// AppendUint32 appends a single uint32 into the buffer, in network byte order (big-endian). // AppendUint32 appends a single uint32 into the buffer, in network byte order (big-endian).
@ -167,16 +184,33 @@ func (b *Buffer) AppendUint32(v uint32) {
) )
} }
// ConsumeCount consumes a single uint32 count from the buffer, in network byte order (big-endian) as an int.
// If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeCount() int {
return int(b.ConsumeUint32())
}
// AppendCount appends a single int length as a uint32 into the buffer, in network byte order (big-endian).
func (b *Buffer) AppendCount(v int) {
b.AppendUint32(uint32(v))
}
// ConsumeUint64 consumes a single uint64 from the buffer, in network byte order (big-endian). // ConsumeUint64 consumes a single uint64 from the buffer, in network byte order (big-endian).
// If the buffer does not have enough data, it will return ErrShortPacket. // If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeUint64() (uint64, error) { func (b *Buffer) ConsumeUint64() uint64 {
if b.Err != nil {
return 0
}
if b.Len() < 8 { if b.Len() < 8 {
return 0, ErrShortPacket b.off = len(b.b)
b.Err = ErrShortPacket
return 0
} }
v := binary.BigEndian.Uint64(b.b[b.off:]) v := binary.BigEndian.Uint64(b.b[b.off:])
b.off += 8 b.off += 8
return v, nil return v
} }
// AppendUint64 appends a single uint64 into the buffer, in network byte order (big-endian). // AppendUint64 appends a single uint64 into the buffer, in network byte order (big-endian).
@ -194,14 +228,9 @@ func (b *Buffer) AppendUint64(v uint64) {
} }
// ConsumeInt64 consumes a single int64 from the buffer, in network byte order (big-endian) with twos complement. // ConsumeInt64 consumes a single int64 from the buffer, in network byte order (big-endian) with twos complement.
// If the buffer does not have enough data, it will return ErrShortPacket. // If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeInt64() (int64, error) { func (b *Buffer) ConsumeInt64() int64 {
u, err := b.ConsumeUint64() return int64(b.ConsumeUint64())
if err != nil {
return 0, err
}
return int64(u), err
} }
// AppendInt64 appends a single int64 into the buffer, in network byte order (big-endian) with twos complement. // AppendInt64 appends a single int64 into the buffer, in network byte order (big-endian) with twos complement.
@ -211,29 +240,52 @@ func (b *Buffer) AppendInt64(v int64) {
// ConsumeByteSlice consumes a single string of raw binary data from the buffer. // ConsumeByteSlice consumes a single string of raw binary data from the buffer.
// A string is a uint32 length, followed by that number of raw bytes. // A string is a uint32 length, followed by that number of raw bytes.
// If the buffer does not have enough data, or defines a length larger than available, it will return ErrShortPacket. // If the buffer does not have enough data, or defines a length larger than available, it will set Err to ErrShortPacket.
// //
// The returned slice aliases the buffer contents, and is valid only as long as the buffer is not reused // The returned slice aliases the buffer contents, and is valid only as long as the buffer is not reused
// (that is, only until the next call to Reset, PutLength, StartPacket, or UnmarshalBinary). // (that is, only until the next call to Reset, PutLength, StartPacket, or UnmarshalBinary).
// //
// In no case will any Consume calls return overlapping slice aliases, // In no case will any Consume calls return overlapping slice aliases,
// and Append calls are guaranteed to not disturb this slice alias. // and Append calls are guaranteed to not disturb this slice alias.
func (b *Buffer) ConsumeByteSlice() ([]byte, error) { func (b *Buffer) ConsumeByteSlice() []byte {
length, err := b.ConsumeUint32() length := int(b.ConsumeUint32())
if err != nil { if b.Err != nil {
return nil, err return nil
} }
if b.Len() < int(length) { if b.Len() < length || length < 0 {
return nil, ErrShortPacket b.off = len(b.b)
b.Err = ErrShortPacket
return nil
} }
v := b.b[b.off:] v := b.b[b.off:]
if len(v) > int(length) { if len(v) > length || cap(v) > length {
v = v[:length:length] v = v[:length:length]
} }
b.off += int(length) b.off += int(length)
return v, nil return v
}
// ConsumeByteSliceCopy consumes a single string of raw binary data as a copy from the buffer.
// A string is a uint32 length, followed by that number of raw bytes.
// If the buffer does not have enough data, or defines a length larger than available, it will set Err to ErrShortPacket.
//
// The returned slice does not alias any buffer contents,
// and will therefore be valid even if the buffer is later reused.
//
// If hint has sufficient capacity to hold the data, it will be reused and overwritten,
// otherwise a new backing slice will be allocated and returned.
func (b *Buffer) ConsumeByteSliceCopy(hint []byte) []byte {
data := b.ConsumeByteSlice()
if grow := len(data) - len(hint); grow > 0 {
hint = append(hint, make([]byte, grow)...)
}
n := copy(hint, data)
hint = hint[:n]
return hint
} }
// AppendByteSlice appends a single string of raw binary data into the buffer. // AppendByteSlice appends a single string of raw binary data into the buffer.
@ -245,17 +297,12 @@ func (b *Buffer) AppendByteSlice(v []byte) {
// ConsumeString consumes a single string of binary data from the buffer. // ConsumeString consumes a single string of binary data from the buffer.
// A string is a uint32 length, followed by that number of raw bytes. // A string is a uint32 length, followed by that number of raw bytes.
// If the buffer does not have enough data, or defines a length larger than available, it will return ErrShortPacket. // If the buffer does not have enough data, or defines a length larger than available, it will set Err to ErrShortPacket.
// //
// NOTE: Go implicitly assumes that strings contain UTF-8 encoded data. // NOTE: Go implicitly assumes that strings contain UTF-8 encoded data.
// All caveats on using arbitrary binary data in Go strings applies. // All caveats on using arbitrary binary data in Go strings applies.
func (b *Buffer) ConsumeString() (string, error) { func (b *Buffer) ConsumeString() string {
v, err := b.ConsumeByteSlice() return string(b.ConsumeByteSlice())
if err != nil {
return "", err
}
return string(v), nil
} }
// AppendString appends a single string of binary data into the buffer. // AppendString appends a single string of binary data into the buffer.

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
import ( import (
"encoding" "encoding"
@ -86,8 +86,9 @@ func (p *ExtendedPacket) MarshalPacket(reqid uint32, b []byte) (header, payload
// If the extension has not been registered, then a new Buffer will be allocated. // If the extension has not been registered, then a new Buffer will be allocated.
// Then the request-specific-data will be unmarshaled from the rest of the buffer. // Then the request-specific-data will be unmarshaled from the rest of the buffer.
func (p *ExtendedPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *ExtendedPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.ExtendedRequest, err = buf.ConsumeString(); err != nil { p.ExtendedRequest = buf.ConsumeString()
return err if buf.Err != nil {
return buf.Err
} }
if p.Data == nil { if p.Data == nil {

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
// ExtensionPair defines the extension-pair type defined in draft-ietf-secsh-filexfer-13. // ExtensionPair defines the extension-pair type defined in draft-ietf-secsh-filexfer-13.
// This type is backwards-compatible with how draft-ietf-secsh-filexfer-02 defines extensions. // This type is backwards-compatible with how draft-ietf-secsh-filexfer-02 defines extensions.
@ -29,15 +29,12 @@ func (e *ExtensionPair) MarshalBinary() ([]byte, error) {
// UnmarshalFrom unmarshals an ExtensionPair from the given Buffer into e. // UnmarshalFrom unmarshals an ExtensionPair from the given Buffer into e.
func (e *ExtensionPair) UnmarshalFrom(buf *Buffer) (err error) { func (e *ExtensionPair) UnmarshalFrom(buf *Buffer) (err error) {
if e.Name, err = buf.ConsumeString(); err != nil { *e = ExtensionPair{
return err Name: buf.ConsumeString(),
Data: buf.ConsumeString(),
} }
if e.Data, err = buf.ConsumeString(); err != nil { return buf.Err
return err
}
return nil
} }
// UnmarshalBinary decodes the binary encoding of ExtensionPair into e. // UnmarshalBinary decodes the binary encoding of ExtensionPair into e.

View File

@ -1,5 +1,5 @@
// Package filexfer implements the wire encoding for secsh-filexfer as described in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 // Package sshfx implements the wire encoding for secsh-filexfer as described in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
package filexfer package sshfx
// PacketMarshaller narrowly defines packets that will only be transmitted. // PacketMarshaller narrowly defines packets that will only be transmitted.
// //

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
import ( import (
"fmt" "fmt"
@ -10,7 +10,7 @@ type Status uint32
// Defines the various SSH_FX_* values. // Defines the various SSH_FX_* values.
const ( const (
// see draft-ietf-secsh-filexfer-02 // see draft-ietf-secsh-filexfer-02
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-7
StatusOK = Status(iota) StatusOK = Status(iota)
StatusEOF StatusEOF
StatusNoSuchFile StatusNoSuchFile
@ -21,28 +21,28 @@ const (
StatusConnectionLost StatusConnectionLost
StatusOPUnsupported StatusOPUnsupported
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-03#section-7 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-03.txt#section-7
StatusV4InvalidHandle StatusV4InvalidHandle
StatusV4NoSuchPath StatusV4NoSuchPath
StatusV4FileAlreadyExists StatusV4FileAlreadyExists
StatusV4WriteProtect StatusV4WriteProtect
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-7 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-04.txt#section-7
StatusV4NoMedia StatusV4NoMedia
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-7 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-05.txt#section-7
StatusV5NoSpaceOnFilesystem StatusV5NoSpaceOnFilesystem
StatusV5QuotaExceeded StatusV5QuotaExceeded
StatusV5UnknownPrincipal StatusV5UnknownPrincipal
StatusV5LockConflict StatusV5LockConflict
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-06#section-8 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-06.txt#section-8
StatusV6DirNotEmpty StatusV6DirNotEmpty
StatusV6NotADirectory StatusV6NotADirectory
StatusV6InvalidFilename StatusV6InvalidFilename
StatusV6LinkLoop StatusV6LinkLoop
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-07#section-8 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-07.txt#section-8
StatusV6CannotDelete StatusV6CannotDelete
StatusV6InvalidParameter StatusV6InvalidParameter
StatusV6FileIsADirectory StatusV6FileIsADirectory
@ -50,10 +50,10 @@ const (
StatusV6ByteRangeLockRefused StatusV6ByteRangeLockRefused
StatusV6DeletePending StatusV6DeletePending
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-08#section-8.1 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-08.txt#section-8.1
StatusV6FileCorrupt StatusV6FileCorrupt
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-10#section-9.1 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-10.txt#section-9.1
StatusV6OwnerInvalid StatusV6OwnerInvalid
StatusV6GroupInvalid StatusV6GroupInvalid

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
import ( import (
"fmt" "fmt"
@ -9,7 +9,7 @@ type PacketType uint8
// Request packet types. // Request packet types.
const ( const (
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
PacketTypeInit = PacketType(iota + 1) PacketTypeInit = PacketType(iota + 1)
PacketTypeVersion PacketTypeVersion
PacketTypeOpen PacketTypeOpen
@ -31,17 +31,17 @@ const (
PacketTypeReadLink PacketTypeReadLink
PacketTypeSymlink PacketTypeSymlink
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-07#section-3.3 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-07.txt#section-3.3
PacketTypeV6Link PacketTypeV6Link
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-08#section-3.3 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-08.txt#section-3.3
PacketTypeV6Block PacketTypeV6Block
PacketTypeV6Unblock PacketTypeV6Unblock
) )
// Response packet types. // Response packet types.
const ( const (
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
PacketTypeStatus = PacketType(iota + 101) PacketTypeStatus = PacketType(iota + 101)
PacketTypeHandle PacketTypeHandle
PacketTypeData PacketTypeData
@ -51,7 +51,7 @@ const (
// Extended packet types. // Extended packet types.
const ( const (
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
PacketTypeExtended = PacketType(iota + 200) PacketTypeExtended = PacketType(iota + 200)
PacketTypeExtendedReply PacketTypeExtendedReply
) )
@ -122,3 +122,48 @@ func (f PacketType) String() string {
return fmt.Sprintf("SSH_FXP_UNKNOWN(%d)", f) return fmt.Sprintf("SSH_FXP_UNKNOWN(%d)", f)
} }
} }
func newPacketFromType(typ PacketType) (Packet, error) {
switch typ {
case PacketTypeOpen:
return new(OpenPacket), nil
case PacketTypeClose:
return new(ClosePacket), nil
case PacketTypeRead:
return new(ReadPacket), nil
case PacketTypeWrite:
return new(WritePacket), nil
case PacketTypeLStat:
return new(LStatPacket), nil
case PacketTypeFStat:
return new(FStatPacket), nil
case PacketTypeSetstat:
return new(SetstatPacket), nil
case PacketTypeFSetstat:
return new(FSetstatPacket), nil
case PacketTypeOpenDir:
return new(OpenDirPacket), nil
case PacketTypeReadDir:
return new(ReadDirPacket), nil
case PacketTypeRemove:
return new(RemovePacket), nil
case PacketTypeMkdir:
return new(MkdirPacket), nil
case PacketTypeRmdir:
return new(RmdirPacket), nil
case PacketTypeRealPath:
return new(RealPathPacket), nil
case PacketTypeStat:
return new(StatPacket), nil
case PacketTypeRename:
return new(RenamePacket), nil
case PacketTypeReadLink:
return new(ReadLinkPacket), nil
case PacketTypeSymlink:
return new(SymlinkPacket), nil
case PacketTypeExtended:
return new(ExtendedPacket), nil
default:
return nil, fmt.Errorf("unexpected request packet type: %v", typ)
}
}

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
// ClosePacket defines the SSH_FXP_CLOSE packet. // ClosePacket defines the SSH_FXP_CLOSE packet.
type ClosePacket struct { type ClosePacket struct {
@ -27,18 +27,18 @@ func (p *ClosePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *ClosePacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *ClosePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil { *p = ClosePacket{
return err Handle: buf.ConsumeString(),
} }
return nil return buf.Err
} }
// ReadPacket defines the SSH_FXP_READ packet. // ReadPacket defines the SSH_FXP_READ packet.
type ReadPacket struct { type ReadPacket struct {
Handle string Handle string
Offset uint64 Offset uint64
Len uint32 Length uint32
} }
// Type returns the SSH_FXP_xy value associated with this packet type. // Type returns the SSH_FXP_xy value associated with this packet type.
@ -58,7 +58,7 @@ func (p *ReadPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
buf.StartPacket(PacketTypeRead, reqid) buf.StartPacket(PacketTypeRead, reqid)
buf.AppendString(p.Handle) buf.AppendString(p.Handle)
buf.AppendUint64(p.Offset) buf.AppendUint64(p.Offset)
buf.AppendUint32(p.Len) buf.AppendUint32(p.Length)
return buf.Packet(payload) return buf.Packet(payload)
} }
@ -66,19 +66,13 @@ func (p *ReadPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *ReadPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *ReadPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil { *p = ReadPacket{
return err Handle: buf.ConsumeString(),
Offset: buf.ConsumeUint64(),
Length: buf.ConsumeUint32(),
} }
if p.Offset, err = buf.ConsumeUint64(); err != nil { return buf.Err
return err
}
if p.Len, err = buf.ConsumeUint32(); err != nil {
return err
}
return nil
} }
// WritePacket defines the SSH_FXP_WRITE packet. // WritePacket defines the SSH_FXP_WRITE packet.
@ -121,26 +115,13 @@ func (p *WritePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// //
// This means this _does not_ alias any of the data buffer that is passed in. // This means this _does not_ alias any of the data buffer that is passed in.
func (p *WritePacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *WritePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil { *p = WritePacket{
return err Handle: buf.ConsumeString(),
Offset: buf.ConsumeUint64(),
Data: buf.ConsumeByteSliceCopy(p.Data),
} }
if p.Offset, err = buf.ConsumeUint64(); err != nil { return buf.Err
return err
}
data, err := buf.ConsumeByteSlice()
if err != nil {
return err
}
if len(p.Data) < len(data) {
p.Data = make([]byte, len(data))
}
n := copy(p.Data, data)
p.Data = p.Data[:n]
return nil
} }
// FStatPacket defines the SSH_FXP_FSTAT packet. // FStatPacket defines the SSH_FXP_FSTAT packet.
@ -170,11 +151,11 @@ func (p *FStatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *FStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *FStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil { *p = FStatPacket{
return err Handle: buf.ConsumeString(),
} }
return nil return buf.Err
} }
// FSetstatPacket defines the SSH_FXP_FSETSTAT packet. // FSetstatPacket defines the SSH_FXP_FSETSTAT packet.
@ -207,8 +188,8 @@ func (p *FSetstatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *FSetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *FSetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil { *p = FSetstatPacket{
return err Handle: buf.ConsumeString(),
} }
return p.Attrs.UnmarshalFrom(buf) return p.Attrs.UnmarshalFrom(buf)
@ -241,9 +222,9 @@ func (p *ReadDirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *ReadDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *ReadDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil { *p = ReadDirPacket{
return err Handle: buf.ConsumeString(),
} }
return nil return buf.Err
} }

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
// InitPacket defines the SSH_FXP_INIT packet. // InitPacket defines the SSH_FXP_INIT packet.
type InitPacket struct { type InitPacket struct {
@ -33,8 +33,8 @@ func (p *InitPacket) MarshalBinary() ([]byte, error) {
func (p *InitPacket) UnmarshalBinary(data []byte) (err error) { func (p *InitPacket) UnmarshalBinary(data []byte) (err error) {
buf := NewBuffer(data) buf := NewBuffer(data)
if p.Version, err = buf.ConsumeUint32(); err != nil { *p = InitPacket{
return err Version: buf.ConsumeUint32(),
} }
for buf.Len() > 0 { for buf.Len() > 0 {
@ -46,7 +46,7 @@ func (p *InitPacket) UnmarshalBinary(data []byte) (err error) {
p.Extensions = append(p.Extensions, &ext) p.Extensions = append(p.Extensions, &ext)
} }
return nil return buf.Err
} }
// VersionPacket defines the SSH_FXP_VERSION packet. // VersionPacket defines the SSH_FXP_VERSION packet.
@ -82,8 +82,8 @@ func (p *VersionPacket) MarshalBinary() ([]byte, error) {
func (p *VersionPacket) UnmarshalBinary(data []byte) (err error) { func (p *VersionPacket) UnmarshalBinary(data []byte) (err error) {
buf := NewBuffer(data) buf := NewBuffer(data)
if p.Version, err = buf.ConsumeUint32(); err != nil { *p = VersionPacket{
return err Version: buf.ConsumeUint32(),
} }
for buf.Len() > 0 { for buf.Len() > 0 {

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
// SSH_FXF_* flags. // SSH_FXF_* flags.
const ( const (
@ -43,12 +43,9 @@ func (p *OpenPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *OpenPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *OpenPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Filename, err = buf.ConsumeString(); err != nil { *p = OpenPacket{
return err Filename: buf.ConsumeString(),
} PFlags: buf.ConsumeUint32(),
if p.PFlags, err = buf.ConsumeUint32(); err != nil {
return err
} }
return p.Attrs.UnmarshalFrom(buf) return p.Attrs.UnmarshalFrom(buf)
@ -81,9 +78,9 @@ func (p *OpenDirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *OpenDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *OpenDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil { *p = OpenDirPacket{
return err Path: buf.ConsumeString(),
} }
return nil return buf.Err
} }

View File

@ -1,59 +1,13 @@
package filexfer package sshfx
import ( import (
"errors" "errors"
"fmt"
"io" "io"
) )
// smallBufferSize is an initial allocation minimal capacity. // smallBufferSize is an initial allocation minimal capacity.
const smallBufferSize = 64 const smallBufferSize = 64
func newPacketFromType(typ PacketType) (Packet, error) {
switch typ {
case PacketTypeOpen:
return new(OpenPacket), nil
case PacketTypeClose:
return new(ClosePacket), nil
case PacketTypeRead:
return new(ReadPacket), nil
case PacketTypeWrite:
return new(WritePacket), nil
case PacketTypeLStat:
return new(LStatPacket), nil
case PacketTypeFStat:
return new(FStatPacket), nil
case PacketTypeSetstat:
return new(SetstatPacket), nil
case PacketTypeFSetstat:
return new(FSetstatPacket), nil
case PacketTypeOpenDir:
return new(OpenDirPacket), nil
case PacketTypeReadDir:
return new(ReadDirPacket), nil
case PacketTypeRemove:
return new(RemovePacket), nil
case PacketTypeMkdir:
return new(MkdirPacket), nil
case PacketTypeRmdir:
return new(RmdirPacket), nil
case PacketTypeRealPath:
return new(RealPathPacket), nil
case PacketTypeStat:
return new(StatPacket), nil
case PacketTypeRename:
return new(RenamePacket), nil
case PacketTypeReadLink:
return new(ReadLinkPacket), nil
case PacketTypeSymlink:
return new(SymlinkPacket), nil
case PacketTypeExtended:
return new(ExtendedPacket), nil
default:
return nil, fmt.Errorf("unexpected request packet type: %v", typ)
}
}
// RawPacket implements the general packet format from draft-ietf-secsh-filexfer-02 // RawPacket implements the general packet format from draft-ietf-secsh-filexfer-02
// //
// RawPacket is intended for use in clients receiving responses, // RawPacket is intended for use in clients receiving responses,
@ -63,7 +17,7 @@ func newPacketFromType(typ PacketType) (Packet, error) {
// For servers expecting to receive arbitrary request packet types, // For servers expecting to receive arbitrary request packet types,
// use RequestPacket. // use RequestPacket.
// //
// Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 // Defined in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
type RawPacket struct { type RawPacket struct {
PacketType PacketType PacketType PacketType
RequestID uint32 RequestID uint32
@ -110,19 +64,14 @@ func (p *RawPacket) MarshalBinary() ([]byte, error) {
// The Data field will alias the passed in Buffer, // The Data field will alias the passed in Buffer,
// so the buffer passed in should not be reused before RawPacket.Reset(). // so the buffer passed in should not be reused before RawPacket.Reset().
func (p *RawPacket) UnmarshalFrom(buf *Buffer) error { func (p *RawPacket) UnmarshalFrom(buf *Buffer) error {
typ, err := buf.ConsumeUint8() *p = RawPacket{
if err != nil { PacketType: PacketType(buf.ConsumeUint8()),
return err RequestID: buf.ConsumeUint32(),
}
p.PacketType = PacketType(typ)
if p.RequestID, err = buf.ConsumeUint32(); err != nil {
return err
} }
p.Data = *buf p.Data = *buf
return nil
return buf.Err
} }
// UnmarshalBinary decodes a full raw packet out of the given data. // UnmarshalBinary decodes a full raw packet out of the given data.
@ -225,7 +174,7 @@ func (p *RawPacket) ReadFrom(r io.Reader, b []byte, maxPacketLength uint32) erro
// where automatic unmarshaling of the packet body does not make sense, // where automatic unmarshaling of the packet body does not make sense,
// use RawPacket. // use RawPacket.
// //
// Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 // Defined in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
type RequestPacket struct { type RequestPacket struct {
RequestID uint32 RequestID uint32
@ -268,18 +217,19 @@ func (p *RequestPacket) MarshalBinary() ([]byte, error) {
// The Request field may alias the passed in Buffer, (e.g. SSH_FXP_WRITE), // The Request field may alias the passed in Buffer, (e.g. SSH_FXP_WRITE),
// so the buffer passed in should not be reused before RequestPacket.Reset(). // so the buffer passed in should not be reused before RequestPacket.Reset().
func (p *RequestPacket) UnmarshalFrom(buf *Buffer) error { func (p *RequestPacket) UnmarshalFrom(buf *Buffer) error {
typ, err := buf.ConsumeUint8() typ := PacketType(buf.ConsumeUint8())
if buf.Err != nil {
return buf.Err
}
req, err := newPacketFromType(typ)
if err != nil { if err != nil {
return err return err
} }
p.Request, err = newPacketFromType(PacketType(typ)) *p = RequestPacket{
if err != nil { RequestID: buf.ConsumeUint32(),
return err Request: req,
}
if p.RequestID, err = buf.ConsumeUint32(); err != nil {
return err
} }
return p.Request.UnmarshalPacketBody(buf) return p.Request.UnmarshalPacketBody(buf)

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
// LStatPacket defines the SSH_FXP_LSTAT packet. // LStatPacket defines the SSH_FXP_LSTAT packet.
type LStatPacket struct { type LStatPacket struct {
@ -27,11 +27,11 @@ func (p *LStatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *LStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *LStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil { *p = LStatPacket{
return err Path: buf.ConsumeString(),
} }
return nil return buf.Err
} }
// SetstatPacket defines the SSH_FXP_SETSTAT packet. // SetstatPacket defines the SSH_FXP_SETSTAT packet.
@ -64,8 +64,8 @@ func (p *SetstatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *SetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *SetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil { *p = SetstatPacket{
return err Path: buf.ConsumeString(),
} }
return p.Attrs.UnmarshalFrom(buf) return p.Attrs.UnmarshalFrom(buf)
@ -98,11 +98,11 @@ func (p *RemovePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *RemovePacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *RemovePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil { *p = RemovePacket{
return err Path: buf.ConsumeString(),
} }
return nil return buf.Err
} }
// MkdirPacket defines the SSH_FXP_MKDIR packet. // MkdirPacket defines the SSH_FXP_MKDIR packet.
@ -135,8 +135,8 @@ func (p *MkdirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *MkdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *MkdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil { *p = MkdirPacket{
return err Path: buf.ConsumeString(),
} }
return p.Attrs.UnmarshalFrom(buf) return p.Attrs.UnmarshalFrom(buf)
@ -169,11 +169,11 @@ func (p *RmdirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *RmdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *RmdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil { *p = RmdirPacket{
return err Path: buf.ConsumeString(),
} }
return nil return buf.Err
} }
// RealPathPacket defines the SSH_FXP_REALPATH packet. // RealPathPacket defines the SSH_FXP_REALPATH packet.
@ -203,11 +203,11 @@ func (p *RealPathPacket) MarshalPacket(reqid uint32, b []byte) (header, payload
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *RealPathPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *RealPathPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil { *p = RealPathPacket{
return err Path: buf.ConsumeString(),
} }
return nil return buf.Err
} }
// StatPacket defines the SSH_FXP_STAT packet. // StatPacket defines the SSH_FXP_STAT packet.
@ -237,11 +237,11 @@ func (p *StatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *StatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *StatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil { *p = StatPacket{
return err Path: buf.ConsumeString(),
} }
return nil return buf.Err
} }
// RenamePacket defines the SSH_FXP_RENAME packet. // RenamePacket defines the SSH_FXP_RENAME packet.
@ -274,15 +274,12 @@ func (p *RenamePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *RenamePacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *RenamePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.OldPath, err = buf.ConsumeString(); err != nil { *p = RenamePacket{
return err OldPath: buf.ConsumeString(),
NewPath: buf.ConsumeString(),
} }
if p.NewPath, err = buf.ConsumeString(); err != nil { return buf.Err
return err
}
return nil
} }
// ReadLinkPacket defines the SSH_FXP_READLINK packet. // ReadLinkPacket defines the SSH_FXP_READLINK packet.
@ -312,18 +309,18 @@ func (p *ReadLinkPacket) MarshalPacket(reqid uint32, b []byte) (header, payload
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *ReadLinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *ReadLinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil { *p = ReadLinkPacket{
return err Path: buf.ConsumeString(),
} }
return nil return buf.Err
} }
// SymlinkPacket defines the SSH_FXP_SYMLINK packet. // SymlinkPacket defines the SSH_FXP_SYMLINK packet.
// //
// The order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed. // The order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed.
// Unfortunately, the reversal was not noticed until the server was widely deployed. // Unfortunately, the reversal was not noticed until the server was widely deployed.
// Covered in Section 3.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL // Covered in Section 4.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
type SymlinkPacket struct { type SymlinkPacket struct {
LinkPath string LinkPath string
TargetPath string TargetPath string
@ -355,14 +352,11 @@ func (p *SymlinkPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *SymlinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *SymlinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
// Arguments were inadvertently reversed. *p = SymlinkPacket{
if p.TargetPath, err = buf.ConsumeString(); err != nil { // Arguments were inadvertently reversed.
return err TargetPath: buf.ConsumeString(),
LinkPath: buf.ConsumeString(),
} }
if p.LinkPath, err = buf.ConsumeString(); err != nil { return buf.Err
return err
}
return nil
} }

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
// FileMode represents a files mode and permission bits. // FileMode represents a files mode and permission bits.
// The bits are defined according to POSIX standards, // The bits are defined according to POSIX standards,

View File

@ -1,4 +1,4 @@
package filexfer package sshfx
import ( import (
"fmt" "fmt"
@ -6,7 +6,7 @@ import (
// StatusPacket defines the SSH_FXP_STATUS packet. // StatusPacket defines the SSH_FXP_STATUS packet.
// //
// Specified in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 // Specified in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-7
type StatusPacket struct { type StatusPacket struct {
StatusCode Status StatusCode Status
ErrorMessage string ErrorMessage string
@ -19,7 +19,7 @@ func (p *StatusPacket) Error() string {
return "sftp: " + p.StatusCode.String() return "sftp: " + p.StatusCode.String()
} }
return fmt.Sprintf("sftp: %q (%s)", p.ErrorMessage, p.StatusCode) return fmt.Sprintf("sftp: %s: %q", p.StatusCode, p.ErrorMessage)
} }
// Is returns true if target is a StatusPacket with the same StatusCode, // Is returns true if target is a StatusPacket with the same StatusCode,
@ -57,21 +57,13 @@ func (p *StatusPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *StatusPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *StatusPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
statusCode, err := buf.ConsumeUint32() *p = StatusPacket{
if err != nil { StatusCode: Status(buf.ConsumeUint32()),
return err ErrorMessage: buf.ConsumeString(),
} LanguageTag: buf.ConsumeString(),
p.StatusCode = Status(statusCode)
if p.ErrorMessage, err = buf.ConsumeString(); err != nil {
return err
} }
if p.LanguageTag, err = buf.ConsumeString(); err != nil { return buf.Err
return err
}
return nil
} }
// HandlePacket defines the SSH_FXP_HANDLE packet. // HandlePacket defines the SSH_FXP_HANDLE packet.
@ -101,11 +93,11 @@ func (p *HandlePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *HandlePacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *HandlePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil { *p = HandlePacket{
return err Handle: buf.ConsumeString(),
} }
return nil return buf.Err
} }
// DataPacket defines the SSH_FXP_DATA packet. // DataPacket defines the SSH_FXP_DATA packet.
@ -143,18 +135,11 @@ func (p *DataPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
// //
// This means this _does not_ alias any of the data buffer that is passed in. // This means this _does not_ alias any of the data buffer that is passed in.
func (p *DataPacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *DataPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
data, err := buf.ConsumeByteSlice() *p = DataPacket{
if err != nil { Data: buf.ConsumeByteSliceCopy(p.Data),
return err
} }
if len(p.Data) < len(data) { return buf.Err
p.Data = make([]byte, len(data))
}
n := copy(p.Data, data)
p.Data = p.Data[:n]
return nil
} }
// NamePacket defines the SSH_FXP_NAME packet. // NamePacket defines the SSH_FXP_NAME packet.
@ -193,14 +178,16 @@ func (p *NamePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
// UnmarshalPacketBody unmarshals the packet body from the given Buffer. // UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed. // It is assumed that the uint32(request-id) has already been consumed.
func (p *NamePacket) UnmarshalPacketBody(buf *Buffer) (err error) { func (p *NamePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
count, err := buf.ConsumeUint32() count := buf.ConsumeCount()
if err != nil { if buf.Err != nil {
return err return buf.Err
} }
p.Entries = make([]*NameEntry, 0, count) *p = NamePacket{
Entries: make([]*NameEntry, 0, count),
}
for i := uint32(0); i < count; i++ { for i := 0; i < count; i++ {
var e NameEntry var e NameEntry
if err := e.UnmarshalFrom(buf); err != nil { if err := e.UnmarshalFrom(buf); err != nil {
return err return err
@ -209,7 +196,7 @@ func (p *NamePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
p.Entries = append(p.Entries, &e) p.Entries = append(p.Entries, &e)
} }
return nil return buf.Err
} }
// AttrsPacket defines the SSH_FXP_ATTRS packet. // AttrsPacket defines the SSH_FXP_ATTRS packet.

View File

@ -60,6 +60,13 @@ func runLs(idLookup NameLookupFileLister, dirent os.FileInfo) string {
uid = lsFormatID(sys.UID) uid = lsFormatID(sys.UID)
gid = lsFormatID(sys.GID) gid = lsFormatID(sys.GID)
default: default:
if fiExt, ok := dirent.(FileInfoUidGid); ok {
uid = lsFormatID(fiExt.Uid())
gid = lsFormatID(fiExt.Gid())
break
}
numLinks, uid, gid = lsLinksUIDGID(dirent) numLinks, uid, gid = lsLinksUIDGID(dirent)
} }

View File

@ -1,3 +1,4 @@
//go:build plan9
// +build plan9 // +build plan9
package sftp package sftp

View File

@ -1,3 +1,4 @@
//go:build windows || android
// +build windows android // +build windows android
package sftp package sftp

View File

@ -1,3 +1,4 @@
//go:build aix || darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || js
// +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris js // +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris js
package sftp package sftp

View File

@ -40,7 +40,7 @@ func newPktMgr(sender packetSender) *packetManager {
return s return s
} }
//// packet ordering // // packet ordering
func (s *packetManager) newOrderID() uint32 { func (s *packetManager) newOrderID() uint32 {
s.packetCount++ s.packetCount++
return s.packetCount return s.packetCount
@ -89,7 +89,7 @@ func (o orderedPackets) Sort() {
}) })
} }
//// packet registry // // packet registry
// register incoming packets to be handled // register incoming packets to be handled
func (s *packetManager) incomingPacket(pkt orderedRequest) { func (s *packetManager) incomingPacket(pkt orderedRequest) {
s.working.Add(1) s.working.Add(1)

View File

@ -31,7 +31,7 @@ type notReadOnly interface {
notReadOnly() notReadOnly()
} }
//// define types by adding methods // // define types by adding methods
// hasPath // hasPath
func (p *sshFxpLstatPacket) getPath() string { return p.Path } func (p *sshFxpLstatPacket) getPath() string { return p.Path }
func (p *sshFxpStatPacket) getPath() string { return p.Path } func (p *sshFxpStatPacket) getPath() string { return p.Path }

25
vendor/github.com/pkg/sftp/packet.go generated vendored
View File

@ -71,6 +71,15 @@ func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
b = marshalUint32(b, fileStat.Mtime) b = marshalUint32(b, fileStat.Mtime)
} }
if flags&sshFileXferAttrExtended != 0 {
b = marshalUint32(b, uint32(len(fileStat.Extended)))
for _, attr := range fileStat.Extended {
b = marshalString(b, attr.ExtType)
b = marshalString(b, attr.ExtData)
}
}
return b return b
} }
@ -281,6 +290,11 @@ func recvPacket(r io.Reader, alloc *allocator, orderID uint32) (uint8, []byte, e
b = make([]byte, length) b = make([]byte, length)
} }
if _, err := io.ReadFull(r, b[:length]); err != nil { if _, err := io.ReadFull(r, b[:length]); err != nil {
// ReadFull only returns EOF if it has read no bytes.
// In this case, that means a partial packet, and thus unexpected.
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
debug("recv packet %d bytes: err %v", length, err) debug("recv packet %d bytes: err %v", length, err)
return 0, nil, err return 0, nil, err
} }
@ -522,7 +536,12 @@ func (p *sshFxpRmdirPacket) UnmarshalBinary(b []byte) error {
} }
type sshFxpSymlinkPacket struct { type sshFxpSymlinkPacket struct {
ID uint32 ID uint32
// The order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed.
// Unfortunately, the reversal was not noticed until the server was widely deployed.
// Covered in Section 4.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
Targetpath string Targetpath string
Linkpath string Linkpath string
} }
@ -1242,7 +1261,7 @@ func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error {
} }
func (p *sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket { func (p *sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket {
err := os.Rename(toLocalPath(p.Oldpath), toLocalPath(p.Newpath)) err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
return statusFromError(p.ID, err) return statusFromError(p.ID, err)
} }
@ -1271,6 +1290,6 @@ func (p *sshFxpExtendedPacketHardlink) UnmarshalBinary(b []byte) error {
} }
func (p *sshFxpExtendedPacketHardlink) respond(s *Server) responsePacket { func (p *sshFxpExtendedPacketHardlink) respond(s *Server) responsePacket {
err := os.Link(toLocalPath(p.Oldpath), toLocalPath(p.Newpath)) err := os.Link(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
return statusFromError(p.ID, err) return statusFromError(p.ID, err)
} }

View File

@ -1,3 +1,4 @@
//go:build !debug
// +build !debug // +build !debug
package sftp package sftp

View File

@ -391,21 +391,6 @@ func (fs *root) Filelist(r *Request) (ListerAt, error) {
return nil, err return nil, err
} }
return listerat{file}, nil return listerat{file}, nil
case "Readlink":
symlink, err := fs.readlink(r.Filepath)
if err != nil {
return nil, err
}
// SFTP-v2: The server will respond with a SSH_FXP_NAME packet containing only
// one name and a dummy attributes value.
return listerat{
&memFile{
name: symlink,
err: os.ErrNotExist, // prevent accidental use as a reader/writer.
},
}, nil
} }
return nil, errors.New("unsupported") return nil, errors.New("unsupported")
@ -434,7 +419,7 @@ func (fs *root) readdir(pathname string) ([]os.FileInfo, error) {
return files, nil return files, nil
} }
func (fs *root) readlink(pathname string) (string, error) { func (fs *root) Readlink(pathname string) (string, error) {
file, err := fs.lfetch(pathname) file, err := fs.lfetch(pathname)
if err != nil { if err != nil {
return "", err return "", err
@ -464,19 +449,10 @@ func (fs *root) Lstat(r *Request) (ListerAt, error) {
return listerat{file}, nil return listerat{file}, nil
} }
// implements RealpathFileLister interface
func (fs *root) Realpath(p string) string {
if fs.startDirectory == "" || fs.startDirectory == "/" {
return cleanPath(p)
}
return cleanPathWithBase(fs.startDirectory, p)
}
// In memory file-system-y thing that the Hanlders live on // In memory file-system-y thing that the Hanlders live on
type root struct { type root struct {
rootFile *memFile rootFile *memFile
mockErr error mockErr error
startDirectory string
mu sync.Mutex mu sync.Mutex
files map[string]*memFile files map[string]*memFile
@ -534,8 +510,8 @@ func (fs *root) exists(path string) bool {
return err != os.ErrNotExist return err != os.ErrNotExist
} }
func (fs *root) fetch(path string) (*memFile, error) { func (fs *root) fetch(pathname string) (*memFile, error) {
file, err := fs.lfetch(path) file, err := fs.lfetch(pathname)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -546,7 +522,12 @@ func (fs *root) fetch(path string) (*memFile, error) {
return nil, errTooManySymlinks return nil, errTooManySymlinks
} }
file, err = fs.lfetch(file.symlink) linkTarget := file.symlink
if !path.IsAbs(linkTarget) {
linkTarget = path.Join(path.Dir(file.name), linkTarget)
}
file, err = fs.lfetch(linkTarget)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -74,6 +74,11 @@ type StatVFSFileCmder interface {
// FileLister should return an object that fulfils the ListerAt interface // FileLister should return an object that fulfils the ListerAt interface
// Note in cases of an error, the error text will be sent to the client. // Note in cases of an error, the error text will be sent to the client.
// Called for Methods: List, Stat, Readlink // Called for Methods: List, Stat, Readlink
//
// Since Filelist returns an os.FileInfo, this can make it non-ideal for implementing Readlink.
// This is because the Name receiver method defined by that interface defines that it should only return the base name.
// However, Readlink is required to be capable of returning essentially any arbitrary valid path relative or absolute.
// In order to implement this more expressive requirement, implement [ReadlinkFileLister] which will then be used instead.
type FileLister interface { type FileLister interface {
Filelist(*Request) (ListerAt, error) Filelist(*Request) (ListerAt, error)
} }
@ -87,12 +92,33 @@ type LstatFileLister interface {
} }
// RealPathFileLister is a FileLister that implements the Realpath method. // RealPathFileLister is a FileLister that implements the Realpath method.
// We use "/" as start directory for relative paths, implementing this // The built-in RealPath implementation does not resolve symbolic links.
// interface you can customize the start directory. // By implementing this interface you can customize the returned path
// and, for example, resolve symbolinc links if needed for your use case.
// You have to return an absolute POSIX path. // You have to return an absolute POSIX path.
// //
// Deprecated: if you want to set a start directory use WithStartDirectory RequestServerOption instead. // Up to v1.13.5 the signature for the RealPath method was:
//
// # RealPath(string) string
//
// we have added a legacyRealPathFileLister that implements the old method
// to ensure that your code does not break.
// You should use the new method signature to avoid future issues
type RealPathFileLister interface { type RealPathFileLister interface {
FileLister
RealPath(string) (string, error)
}
// ReadlinkFileLister is a FileLister that implements the Readlink method.
// By implementing the Readlink method, it is possible to return any arbitrary valid path relative or absolute.
// This allows giving a better response than via the default FileLister (which is limited to os.FileInfo, whose Name method should only return the base name of a file)
type ReadlinkFileLister interface {
FileLister
Readlink(string) (string, error)
}
// This interface is here for backward compatibility only
type legacyRealPathFileLister interface {
FileLister FileLister
RealPath(string) string RealPath(string) string
} }
@ -105,11 +131,19 @@ type NameLookupFileLister interface {
LookupGroupName(string) string LookupGroupName(string) string
} }
// ListerAt does for file lists what io.ReaderAt does for files. // ListerAt does for file lists what io.ReaderAt does for files, i.e. a []os.FileInfo buffer is passed to the ListAt function
// ListAt should return the number of entries copied and an io.EOF // and the entries that are populated in the buffer will be passed to the client.
// error if at end of list. This is testable by comparing how many you //
// copied to how many could be copied (eg. n < len(ls) below). // ListAt should return the number of entries copied and an io.EOF error if at end of list.
// This is testable by comparing how many you copied to how many could be copied (eg. n < len(ls) below).
// The copy() builtin is best for the copying. // The copy() builtin is best for the copying.
//
// Uid and gid information will on unix systems be retrieved from [os.FileInfo.Sys]
// if this function returns a [syscall.Stat_t] when called on a populated entry.
// Alternatively, if the entry implements [FileInfoUidGid], it will be used for uid and gid information.
//
// If a populated entry implements [FileInfoExtendedData], extended attributes will also be returned to the client.
//
// Note in cases of an error, the error text will be sent to the client. // Note in cases of an error, the error text will be sent to the client.
type ListerAt interface { type ListerAt interface {
ListAt([]os.FileInfo, int64) (int, error) ListAt([]os.FileInfo, int64) (int, error)

View File

@ -1,10 +1,9 @@
//go:build plan9
// +build plan9 // +build plan9
package sftp package sftp
import ( import (
"path"
"path/filepath"
"syscall" "syscall"
) )
@ -15,20 +14,3 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error { func testOsSys(sys interface{}) error {
return nil return nil
} }
func toLocalPath(p string) string {
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp[1:]
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/#s/boot" to "#s/boot"
return tmp
}
}
return lp
}

View File

@ -28,7 +28,7 @@ then sends to the client.
Handler for "Put" method and returns an io.Writer for the file which the server Handler for "Put" method and returns an io.Writer for the file which the server
then writes the uploaded file to. The file opening "pflags" are currently then writes the uploaded file to. The file opening "pflags" are currently
preserved in the Request.Flags field as a 32bit bitmask value. See the [SFTP preserved in the Request.Flags field as a 32bit bitmask value. See the [SFTP
spec](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.3) for spec](https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-6.3) for
details. details.
### Filecmd(*Request) error ### Filecmd(*Request) error

View File

@ -219,12 +219,21 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR
rpkt = statusFromError(pkt.ID, rs.closeRequest(handle)) rpkt = statusFromError(pkt.ID, rs.closeRequest(handle))
case *sshFxpRealpathPacket: case *sshFxpRealpathPacket:
var realPath string var realPath string
if realPather, ok := rs.Handlers.FileList.(RealPathFileLister); ok { var err error
realPath = realPather.RealPath(pkt.getPath())
} else { switch pather := rs.Handlers.FileList.(type) {
case RealPathFileLister:
realPath, err = pather.RealPath(pkt.getPath())
case legacyRealPathFileLister:
realPath = pather.RealPath(pkt.getPath())
default:
realPath = cleanPathWithBase(rs.startDirectory, pkt.getPath()) realPath = cleanPathWithBase(rs.startDirectory, pkt.getPath())
} }
rpkt = cleanPacketPath(pkt, realPath) if err != nil {
rpkt = statusFromError(pkt.ID, err)
} else {
rpkt = cleanPacketPath(pkt, realPath)
}
case *sshFxpOpendirPacket: case *sshFxpOpendirPacket:
request := requestFromPacket(ctx, pkt, rs.startDirectory) request := requestFromPacket(ctx, pkt, rs.startDirectory)
handle := rs.nextRequest(request) handle := rs.nextRequest(request)

View File

@ -1,3 +1,4 @@
//go:build !windows && !plan9
// +build !windows,!plan9 // +build !windows,!plan9
package sftp package sftp
@ -21,7 +22,3 @@ func testOsSys(sys interface{}) error {
} }
return nil return nil
} }
func toLocalPath(p string) string {
return p
}

View File

@ -187,6 +187,7 @@ func requestFromPacket(ctx context.Context, pkt hasPath, baseDir string) *Reques
// NOTE: given a POSIX compliant signature: symlink(target, linkpath string) // NOTE: given a POSIX compliant signature: symlink(target, linkpath string)
// this makes Request.Target the linkpath, and Request.Filepath the target. // this makes Request.Target the linkpath, and Request.Filepath the target.
request.Target = cleanPathWithBase(baseDir, p.Linkpath) request.Target = cleanPathWithBase(baseDir, p.Linkpath)
request.Filepath = p.Targetpath
case *sshFxpExtendedPacketHardlink: case *sshFxpExtendedPacketHardlink:
request.Target = cleanPathWithBase(baseDir, p.Newpath) request.Target = cleanPathWithBase(baseDir, p.Newpath)
} }
@ -294,7 +295,12 @@ func (r *Request) call(handlers Handlers, pkt requestPacket, alloc *allocator, o
return filecmd(handlers.FileCmd, r, pkt) return filecmd(handlers.FileCmd, r, pkt)
case "List": case "List":
return filelist(handlers.FileList, r, pkt) return filelist(handlers.FileList, r, pkt)
case "Stat", "Lstat", "Readlink": case "Stat", "Lstat":
return filestat(handlers.FileList, r, pkt)
case "Readlink":
if readlinkFileLister, ok := handlers.FileList.(ReadlinkFileLister); ok {
return readlink(readlinkFileLister, r, pkt)
}
return filestat(handlers.FileList, r, pkt) return filestat(handlers.FileList, r, pkt)
default: default:
return statusFromError(pkt.id(), fmt.Errorf("unexpected method: %s", r.Method)) return statusFromError(pkt.id(), fmt.Errorf("unexpected method: %s", r.Method))
@ -598,6 +604,23 @@ func filestat(h FileLister, r *Request, pkt requestPacket) responsePacket {
} }
} }
func readlink(readlinkFileLister ReadlinkFileLister, r *Request, pkt requestPacket) responsePacket {
resolved, err := readlinkFileLister.Readlink(r.Filepath)
if err != nil {
return statusFromError(pkt.id(), err)
}
return &sshFxpNamePacket{
ID: pkt.id(),
NameAttrs: []*sshFxpNameAttr{
{
Name: resolved,
LongName: resolved,
Attrs: emptyFileStat,
},
},
}
}
// init attributes of request object from packet data // init attributes of request object from packet data
func requestMethod(p requestPacket) (method string) { func requestMethod(p requestPacket) (method string) {
switch p.(type) { switch p.(type) {

View File

@ -1,8 +1,6 @@
package sftp package sftp
import ( import (
"path"
"path/filepath"
"syscall" "syscall"
) )
@ -13,32 +11,3 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error { func testOsSys(sys interface{}) error {
return nil return nil
} }
func toLocalPath(p string) string {
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp
for len(tmp) > 0 && tmp[0] == '\\' {
tmp = tmp[1:]
}
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/C:/Windows" to "C:\\Windows"
return tmp
}
tmp += "\\"
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes but with extra end slash is absolute,
// then we have a filepath encoded with a prefix '/' and a dropped '/' at the end.
// e.g. "/C:" to "C:\\"
return tmp
}
}
return lp
}

62
vendor/github.com/pkg/sftp/server.go generated vendored
View File

@ -24,7 +24,7 @@ const (
// Server is an SSH File Transfer Protocol (sftp) server. // Server is an SSH File Transfer Protocol (sftp) server.
// This is intended to provide the sftp subsystem to an ssh server daemon. // This is intended to provide the sftp subsystem to an ssh server daemon.
// This implementation currently supports most of sftp server protocol version 3, // This implementation currently supports most of sftp server protocol version 3,
// as specified at http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 // as specified at https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
type Server struct { type Server struct {
*serverConn *serverConn
debugStream io.Writer debugStream io.Writer
@ -33,6 +33,7 @@ type Server struct {
openFiles map[string]*os.File openFiles map[string]*os.File
openFilesLock sync.RWMutex openFilesLock sync.RWMutex
handleCount int handleCount int
workDir string
} }
func (svr *Server) nextHandle(f *os.File) string { func (svr *Server) nextHandle(f *os.File) string {
@ -128,6 +129,16 @@ func WithAllocator() ServerOption {
} }
} }
// WithServerWorkingDirectory sets a working directory to use as base
// for relative paths.
// If unset the default is current working directory (os.Getwd).
func WithServerWorkingDirectory(workDir string) ServerOption {
return func(s *Server) error {
s.workDir = cleanPath(workDir)
return nil
}
}
type rxPacket struct { type rxPacket struct {
pktType fxp pktType fxp
pktBytes []byte pktBytes []byte
@ -174,7 +185,7 @@ func handlePacket(s *Server, p orderedRequest) error {
} }
case *sshFxpStatPacket: case *sshFxpStatPacket:
// stat the requested file // stat the requested file
info, err := os.Stat(toLocalPath(p.Path)) info, err := os.Stat(s.toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{ rpkt = &sshFxpStatResponse{
ID: p.ID, ID: p.ID,
info: info, info: info,
@ -184,7 +195,7 @@ func handlePacket(s *Server, p orderedRequest) error {
} }
case *sshFxpLstatPacket: case *sshFxpLstatPacket:
// stat the requested file // stat the requested file
info, err := os.Lstat(toLocalPath(p.Path)) info, err := os.Lstat(s.toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{ rpkt = &sshFxpStatResponse{
ID: p.ID, ID: p.ID,
info: info, info: info,
@ -208,24 +219,24 @@ func handlePacket(s *Server, p orderedRequest) error {
} }
case *sshFxpMkdirPacket: case *sshFxpMkdirPacket:
// TODO FIXME: ignore flags field // TODO FIXME: ignore flags field
err := os.Mkdir(toLocalPath(p.Path), 0755) err := os.Mkdir(s.toLocalPath(p.Path), 0o755)
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpRmdirPacket: case *sshFxpRmdirPacket:
err := os.Remove(toLocalPath(p.Path)) err := os.Remove(s.toLocalPath(p.Path))
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpRemovePacket: case *sshFxpRemovePacket:
err := os.Remove(toLocalPath(p.Filename)) err := os.Remove(s.toLocalPath(p.Filename))
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpRenamePacket: case *sshFxpRenamePacket:
err := os.Rename(toLocalPath(p.Oldpath), toLocalPath(p.Newpath)) err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpSymlinkPacket: case *sshFxpSymlinkPacket:
err := os.Symlink(toLocalPath(p.Targetpath), toLocalPath(p.Linkpath)) err := os.Symlink(s.toLocalPath(p.Targetpath), s.toLocalPath(p.Linkpath))
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpClosePacket: case *sshFxpClosePacket:
rpkt = statusFromError(p.ID, s.closeHandle(p.Handle)) rpkt = statusFromError(p.ID, s.closeHandle(p.Handle))
case *sshFxpReadlinkPacket: case *sshFxpReadlinkPacket:
f, err := os.Readlink(toLocalPath(p.Path)) f, err := os.Readlink(s.toLocalPath(p.Path))
rpkt = &sshFxpNamePacket{ rpkt = &sshFxpNamePacket{
ID: p.ID, ID: p.ID,
NameAttrs: []*sshFxpNameAttr{ NameAttrs: []*sshFxpNameAttr{
@ -240,7 +251,7 @@ func handlePacket(s *Server, p orderedRequest) error {
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
} }
case *sshFxpRealpathPacket: case *sshFxpRealpathPacket:
f, err := filepath.Abs(toLocalPath(p.Path)) f, err := filepath.Abs(s.toLocalPath(p.Path))
f = cleanPath(f) f = cleanPath(f)
rpkt = &sshFxpNamePacket{ rpkt = &sshFxpNamePacket{
ID: p.ID, ID: p.ID,
@ -256,13 +267,14 @@ func handlePacket(s *Server, p orderedRequest) error {
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
} }
case *sshFxpOpendirPacket: case *sshFxpOpendirPacket:
p.Path = toLocalPath(p.Path) lp := s.toLocalPath(p.Path)
if stat, err := os.Stat(p.Path); err != nil { if stat, err := os.Stat(lp); err != nil {
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
} else if !stat.IsDir() { } else if !stat.IsDir() {
rpkt = statusFromError(p.ID, &os.PathError{ rpkt = statusFromError(p.ID, &os.PathError{
Path: p.Path, Err: syscall.ENOTDIR}) Path: lp, Err: syscall.ENOTDIR,
})
} else { } else {
rpkt = (&sshFxpOpenPacket{ rpkt = (&sshFxpOpenPacket{
ID: p.ID, ID: p.ID,
@ -315,7 +327,7 @@ func handlePacket(s *Server, p orderedRequest) error {
} }
// Serve serves SFTP connections until the streams stop or the SFTP subsystem // Serve serves SFTP connections until the streams stop or the SFTP subsystem
// is stopped. // is stopped. It returns nil if the server exits cleanly.
func (svr *Server) Serve() error { func (svr *Server) Serve() error {
defer func() { defer func() {
if svr.pktMgr.alloc != nil { if svr.pktMgr.alloc != nil {
@ -341,6 +353,10 @@ func (svr *Server) Serve() error {
for { for {
pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID()) pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID())
if err != nil { if err != nil {
// Check whether the connection terminated cleanly in-between packets.
if err == io.EOF {
err = nil
}
// we don't care about releasing allocated pages here, the server will quit and the allocator freed // we don't care about releasing allocated pages here, the server will quit and the allocator freed
break break
} }
@ -446,7 +462,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket {
osFlags |= os.O_EXCL osFlags |= os.O_EXCL
} }
f, err := os.OpenFile(toLocalPath(p.Path), osFlags, 0644) f, err := os.OpenFile(svr.toLocalPath(p.Path), osFlags, 0o644)
if err != nil { if err != nil {
return statusFromError(p.ID, err) return statusFromError(p.ID, err)
} }
@ -484,7 +500,7 @@ func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket {
b := p.Attrs.([]byte) b := p.Attrs.([]byte)
var err error var err error
p.Path = toLocalPath(p.Path) p.Path = svr.toLocalPath(p.Path)
debug("setstat name \"%s\"", p.Path) debug("setstat name \"%s\"", p.Path)
if (p.Flags & sshFileXferAttrSize) != 0 { if (p.Flags & sshFileXferAttrSize) != 0 {
@ -603,13 +619,15 @@ func statusFromError(id uint32, err error) *sshFxpStatusPacket {
return ret return ret
} }
switch e := err.(type) { if errors.Is(err, io.EOF) {
case fxerr: ret.StatusError.Code = sshFxEOF
return ret
}
var e fxerr
if errors.As(err, &e) {
ret.StatusError.Code = uint32(e) ret.StatusError.Code = uint32(e)
default: return ret
if e == io.EOF {
ret.StatusError.Code = sshFxEOF
}
} }
return ret return ret

27
vendor/github.com/pkg/sftp/server_plan9.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
package sftp
import (
"path"
"path/filepath"
)
func (s *Server) toLocalPath(p string) string {
if s.workDir != "" && !path.IsAbs(p) {
p = path.Join(s.workDir, p)
}
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp[1:]
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/#s/boot" to "#s/boot"
return tmp
}
}
return lp
}

View File

@ -1,3 +1,4 @@
//go:build darwin || linux
// +build darwin linux // +build darwin linux
// fill in statvfs structure with OS specific values // fill in statvfs structure with OS specific values

View File

@ -1,3 +1,4 @@
//go:build linux
// +build linux // +build linux
package sftp package sftp

View File

@ -1,3 +1,4 @@
//go:build !darwin && !linux && !plan9
// +build !darwin,!linux,!plan9 // +build !darwin,!linux,!plan9
package sftp package sftp

16
vendor/github.com/pkg/sftp/server_unix.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
//go:build !windows && !plan9
// +build !windows,!plan9
package sftp
import (
"path"
)
func (s *Server) toLocalPath(p string) string {
if s.workDir != "" && !path.IsAbs(p) {
p = path.Join(s.workDir, p)
}
return p
}

39
vendor/github.com/pkg/sftp/server_windows.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
package sftp
import (
"path"
"path/filepath"
)
func (s *Server) toLocalPath(p string) string {
if s.workDir != "" && !path.IsAbs(p) {
p = path.Join(s.workDir, p)
}
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp
for len(tmp) > 0 && tmp[0] == '\\' {
tmp = tmp[1:]
}
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/C:/Windows" to "C:\\Windows"
return tmp
}
tmp += "\\"
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes but with extra end slash is absolute,
// then we have a filepath encoded with a prefix '/' and a dropped '/' at the end.
// e.g. "/C:" to "C:\\"
return tmp
}
}
return lp
}

2
vendor/github.com/pkg/sftp/sftp.go generated vendored
View File

@ -1,5 +1,5 @@
// Package sftp implements the SSH File Transfer Protocol as described in // Package sftp implements the SSH File Transfer Protocol as described in
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
package sftp package sftp
import ( import (

View File

@ -1,3 +1,4 @@
//go:build plan9 || windows || (js && wasm)
// +build plan9 windows js,wasm // +build plan9 windows js,wasm
// Go defines S_IFMT on windows, plan9 and js/wasm as 0x1f000 instead of // Go defines S_IFMT on windows, plan9 and js/wasm as 0x1f000 instead of

View File

@ -1,4 +1,6 @@
// +build !plan9,!windows //go:build !plan9 && !windows && (!js || !wasm)
// +build !plan9
// +build !windows
// +build !js !wasm // +build !js !wasm
package sftp package sftp

4
vendor/modules.txt vendored
View File

@ -156,7 +156,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/util
github.com/containers/buildah/util github.com/containers/buildah/util
# github.com/containers/common v0.55.1-0.20230811093040-524b4d5c12f9 # github.com/containers/common v0.55.1-0.20230814161508-b70b0c49600e
## explicit; go 1.18 ## explicit; go 1.18
github.com/containers/common/libimage github.com/containers/common/libimage
github.com/containers/common/libimage/define github.com/containers/common/libimage/define
@ -852,7 +852,7 @@ github.com/pelletier/go-toml/v2/unstable
# github.com/pkg/errors v0.9.1 # github.com/pkg/errors v0.9.1
## explicit ## explicit
github.com/pkg/errors github.com/pkg/errors
# github.com/pkg/sftp v1.13.5 # github.com/pkg/sftp v1.13.6
## explicit; go 1.15 ## explicit; go 1.15
github.com/pkg/sftp github.com/pkg/sftp
github.com/pkg/sftp/internal/encoding/ssh/filexfer github.com/pkg/sftp/internal/encoding/ssh/filexfer