/* * Copyright 2020 The Dragonfly Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package config holds all DaemonConfig of dfget. package config import ( "encoding/json" "fmt" "os" "path" "path/filepath" "strings" "syscall" "time" "d7y.io/dragonfly/v2/cmd/dependency/base" logger "d7y.io/dragonfly/v2/internal/dflog" "d7y.io/dragonfly/v2/pkg/unit" "github.com/pkg/errors" "golang.org/x/time/rate" "d7y.io/dragonfly/v2/internal/dferrors" "d7y.io/dragonfly/v2/pkg/basic" "d7y.io/dragonfly/v2/pkg/util/net/urlutils" "d7y.io/dragonfly/v2/pkg/util/stringutils" ) type DfgetConfig = ClientOption // ClientOption holds all the runtime config information. type ClientOption struct { base.Options `yaml:",inline" mapstructure:",squash"` // URL download URL. URL string `yaml:"url,omitempty" mapstructure:"url,omitempty"` // Output full output path. Output string `yaml:"output,omitempty" mapstructure:"output,omitempty"` // Timeout download timeout(second). Timeout time.Duration `yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` BenchmarkRate unit.Bytes `yaml:"benchmarkRate,omitempty" mapstructure:"benchmarkRate,omitempty"` // Md5 expected file md5. // Deprecated: Md5 is deprecated, use DigestMethod with DigestValue instead Md5 string `yaml:"md5,omitempty" mapstructure:"md5,omitempty"` Digest string `yaml:"digest,omitempty" mapstructure:"digest,omitempty"` // DigestMethod indicates digest method, like md5, sha256 DigestMethod string `yaml:"digestMethod,omitempty" mapstructure:"digestMethod,omitempty"` // DigestValue indicates digest value DigestValue string `yaml:"digestValue,omitempty" mapstructure:"digestValue,omitempty"` // Tag identify download task, it is available merely when md5 param not exist. Tag string `yaml:"tag,omitempty" mapstructure:"tag,omitempty"` // CallSystem system name that executes dfget. CallSystem string `yaml:"callSystem,omitempty" mapstructure:"callSystem,omitempty"` // Pattern download pattern, must be 'p2p' or 'cdn' or 'source', // default:`p2p`. Pattern string `yaml:"pattern,omitempty" mapstructure:"pattern,omitempty"` // CA certificate to verify when supernode interact with the source. Cacerts []string `yaml:"cacert,omitempty" mapstructure:"cacert,omitempty"` // Filter filter some query params of url, use char '&' to separate different params. // eg: -f 'key&sign' will filter 'key' and 'sign' query param. // in this way, different urls correspond one same download task that can use p2p mode. Filter string `yaml:"filter,omitempty" mapstructure:"filter,omitempty"` // Header of http request. // eg: --header='Accept: *' --header='Host: abc'. Header []string `yaml:"header,omitempty" mapstructure:"header,omitempty"` // DisableBackSource indicates whether to not back source to download when p2p fails. DisableBackSource bool `yaml:"disableBackSource,omitempty" mapstructure:"disableBackSource,omitempty"` // Insecure indicates whether skip secure verify when supernode interact with the source. Insecure bool `yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` // ShowProgress shows progress bar, it's conflict with `--console`. ShowProgress bool `yaml:"show-progress,omitempty" mapstructure:"show-progress,omitempty"` RateLimit rate.Limit `yaml:"rateLimit,omitempty" mapstructure:"rateLimit,omitempty"` // Config file paths, // default:["/etc/dragonfly/dfget.yaml","/etc/dragonfly.conf"]. // // NOTE: It is recommended to use `/etc/dragonfly/dfget.yaml` as default, // and the `/etc/dragonfly.conf` is just to ensure compatibility with previous versions. //ConfigFiles []string `json:"-"` // MoreDaemonOptions indicates more options passed to daemon by command line. MoreDaemonOptions string `yaml:"moreDaemonOptions,omitempty" mapstructure:"moreDaemonOptions,omitempty"` } func NewDfgetConfig() *ClientOption { return &dfgetConfig } func (cfg *ClientOption) Validate() error { if cfg == nil { return errors.Wrap(dferrors.ErrInvalidArgument, "runtime config") } if !urlutils.IsValidURL(cfg.URL) { return errors.Wrapf(dferrors.ErrInvalidArgument, "url: %v", cfg.URL) } if err := cfg.checkOutput(); err != nil { return errors.Wrapf(dferrors.ErrInvalidArgument, "output: %v", err) } return nil } func (cfg *ClientOption) Convert(args []string) error { var err error if cfg.Output, err = filepath.Abs(cfg.Output); err != nil { return err } if cfg.URL == "" && len(args) > 0 { cfg.URL = args[0] } if cfg.Digest != "" { cfg.Tag = "" } if cfg.Console { cfg.ShowProgress = false } return nil } func (cfg *ClientOption) String() string { js, _ := json.Marshal(cfg) return string(js) } // This function must be called after checkURL func (cfg *ClientOption) checkOutput() error { if stringutils.IsBlank(cfg.Output) { url := strings.TrimRight(cfg.URL, "/") idx := strings.LastIndexByte(url, '/') if idx < 0 { return fmt.Errorf("get output from url[%s] error", cfg.URL) } cfg.Output = url[idx+1:] } if !filepath.IsAbs(cfg.Output) { absPath, err := filepath.Abs(cfg.Output) if err != nil { return fmt.Errorf("get absolute path[%s] error: %v", cfg.Output, err) } cfg.Output = absPath } dir, _ := path.Split(cfg.Output) if err := MkdirAll(dir, 0777, basic.UserID, basic.UserGroup); err != nil { return err } if f, err := os.Stat(cfg.Output); err == nil && f.IsDir() { return fmt.Errorf("path[%s] is directory but requires file path", cfg.Output) } // check permission for dir := cfg.Output; !stringutils.IsBlank(dir); dir = filepath.Dir(dir) { if err := syscall.Access(dir, syscall.O_RDWR); err == nil { break } else if os.IsPermission(err) || dir == "/" { return fmt.Errorf("user[%s] path[%s] %v", basic.Username, cfg.Output, err) } } return nil } // MkdirAll make directories recursive, and changes uid, gid to latest directory. // For example: the path /data/x exists, uid=1, gid=1 // when call MkdirAll("/data/x/y/z", 0755, 2, 2) // MkdirAll creates /data/x/y and change owner to 2:2, creates /data/x/y/z and change owner to 2:2 func MkdirAll(dir string, perm os.FileMode, uid, gid int) error { var ( // directories to create dirs []string err error ) subDir := dir // find not exist directories from bottom to top for { if subDir == "" { break } _, err = os.Stat(subDir) // directory exists if err == nil { break } if os.IsNotExist(err) { dirs = append(dirs, subDir) } else { logger.Errorf("stat error: %s", err) return err } subDir = path.Dir(subDir) } // no directory to create if len(dirs) == 0 { return nil } err = os.MkdirAll(dir, perm) if err != nil { logger.Errorf("mkdir error: %s", err) return err } // update owner from top to bottom for _, d := range dirs { err = os.Chown(d, uid, gid) if err != nil { logger.Errorf("chown error: %s", err) return err } } return nil }