Dragonfly2
311 строк · 9.9 Кб
1/*
2* Copyright 2020 The Dragonfly Authors
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17// Package config holds all DaemonConfig of dfget.
18package config
19
20import (
21"encoding/json"
22"fmt"
23"os"
24"path"
25"path/filepath"
26"regexp"
27"strings"
28"syscall"
29"time"
30
31"d7y.io/dragonfly/v2/client/util"
32"d7y.io/dragonfly/v2/cmd/dependency/base"
33"d7y.io/dragonfly/v2/internal/dferrors"
34logger "d7y.io/dragonfly/v2/internal/dflog"
35"d7y.io/dragonfly/v2/pkg/net/url"
36"d7y.io/dragonfly/v2/pkg/os/user"
37pkgstrings "d7y.io/dragonfly/v2/pkg/strings"
38"d7y.io/dragonfly/v2/pkg/unit"
39)
40
41type DfgetConfig = ClientOption
42
43// ClientOption holds all the runtime config information.
44type ClientOption struct {
45base.Options `yaml:",inline" mapstructure:",squash"`
46// URL download URL.
47URL string `yaml:"url,omitempty" mapstructure:"url,omitempty"`
48
49// Output full output path.
50Output string `yaml:"output,omitempty" mapstructure:"output,omitempty"`
51
52// Timeout download timeout(second).
53Timeout time.Duration `yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"`
54
55BenchmarkRate unit.Bytes `yaml:"benchmarkRate,omitempty" mapstructure:"benchmarkRate,omitempty"`
56
57// Md5 expected file md5.
58// Deprecated: Md5 is deprecated, use DigestMethod with DigestValue instead
59Md5 string `yaml:"md5,omitempty" mapstructure:"md5,omitempty"`
60Digest string `yaml:"digest,omitempty" mapstructure:"digest,omitempty"`
61// DigestMethod indicates digest method, like md5, sha256
62DigestMethod string `yaml:"digestMethod,omitempty" mapstructure:"digestMethod,omitempty"`
63
64// DigestValue indicates digest value
65DigestValue string `yaml:"digestValue,omitempty" mapstructure:"digestValue,omitempty"`
66
67// Tag identify download task, it is available merely when md5 param not exist.
68Tag string `yaml:"tag,omitempty" mapstructure:"tag,omitempty"`
69
70// Application name that executes dfget.
71Application string `yaml:"application,omitempty" mapstructure:"application,omitempty"`
72
73// DaemonSock is daemon download socket path.
74DaemonSock string `yaml:"daemonSock,omitempty" mapstructure:"daemon-sock,omitempty"`
75
76// Priority scheduler will schedule tasks according to priority
77Priority int32 `yaml:"priority,omitempty" mapstructure:"priority,omitempty"`
78
79// CA certificate to verify when supernode interact with the source.
80Cacerts []string `yaml:"cacert,omitempty" mapstructure:"cacert,omitempty"`
81
82// Filter filter some query params of url, use char '&' to separate different params.
83// eg: -f 'key&sign' will filter 'key' and 'sign' query param.
84// in this way, different urls correspond one same download task that can use p2p mode.
85Filter string `yaml:"filter,omitempty" mapstructure:"filter,omitempty"`
86
87// Header of http request.
88// eg: --header='Accept: *' --header='Host: abc'.
89Header []string `yaml:"header,omitempty" mapstructure:"header,omitempty"`
90
91// DisableBackSource indicates whether to not back source to download when p2p fails.
92DisableBackSource bool `yaml:"disableBackSource,omitempty" mapstructure:"disable-back-source,omitempty"`
93
94// Insecure indicates whether skip secure verify when supernode interact with the source.
95Insecure bool `yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"`
96
97// ShowProgress shows progress bar, it's conflict with `--console`.
98ShowProgress bool `yaml:"show-progress,omitempty" mapstructure:"show-progress,omitempty"`
99
100// LogDir is log directory of dfget.
101LogDir string `yaml:"logDir,omitempty" mapstructure:"logDir,omitempty"`
102
103// DataDir is data directory of dfget.
104DataDir string `yaml:"dataDir,omitempty" mapstructure:"dataDir,omitempty"`
105
106// CacheDir is cache directory of dfget.
107CacheDir string `yaml:"cacheDir,omitempty" mapstructure:"cacheDir,omitempty"`
108
109// WorkHome is working directory of dfget.
110WorkHome string `yaml:"workHome,omitempty" mapstructure:"workHome,omitempty"`
111
112RateLimit util.RateLimit `yaml:"rateLimit,omitempty" mapstructure:"rateLimit,omitempty"`
113
114// Config file paths,
115// default:["/etc/dragonfly/dfget.yaml","/etc/dragonfly.conf"].
116//
117// NOTE: It is recommended to use `/etc/dragonfly/dfget.yaml` as default,
118// and the `/etc/dragonfly.conf` is just to ensure compatibility with previous versions.
119//ConfigFiles []string `json:"-"`
120
121// MoreDaemonOptions indicates more options passed to daemon by command line.
122MoreDaemonOptions string `yaml:"moreDaemonOptions,omitempty" mapstructure:"moreDaemonOptions,omitempty"`
123
124// Recursive indicates to download all resources in target url, the target source client must support list action
125Recursive bool `yaml:"recursive,omitempty" mapstructure:"recursive,omitempty"`
126
127// RecursiveList indicates to list all resources in target url, the target source client must support list action
128RecursiveList bool `yaml:"recursiveList,omitempty" mapstructure:"list,omitempty"`
129
130// RecursiveLevel indicates to the maximum number of subdirectories that dfget will recurse into
131RecursiveLevel uint `yaml:"recursiveLevel,omitempty" mapstructure:"level,omitempty"`
132
133RecursiveAcceptRegex string `yaml:"acceptRegex,omitempty" mapstructure:"accept-regex,omitempty"`
134
135RecursiveRejectRegex string `yaml:"rejectRegex,omitempty" mapstructure:"reject-regex,omitempty"`
136
137KeepOriginalOffset bool `yaml:"keepOriginalOffset,omitempty" mapstructure:"original-offset,omitempty"`
138
139// Range stands download range for url, like: 0-9, will download 10 bytes from 0 to 9 ([0:9])
140Range string `yaml:"range,omitempty" mapstructure:"range,omitempty"`
141}
142
143func NewDfgetConfig() *ClientOption {
144return &dfgetConfig
145}
146
147func (cfg *ClientOption) Validate() error {
148if cfg == nil {
149return fmt.Errorf("runtime config: %w", dferrors.ErrInvalidArgument)
150}
151
152if !url.IsValid(cfg.URL) {
153return fmt.Errorf("url %s: %w", cfg.URL, dferrors.ErrInvalidArgument)
154}
155
156if _, err := regexp.Compile(cfg.RecursiveAcceptRegex); err != nil {
157return err
158}
159
160if _, err := regexp.Compile(cfg.RecursiveRejectRegex); err != nil {
161return err
162}
163
164if err := cfg.checkOutput(); err != nil {
165return fmt.Errorf("output %s: %w", err.Error(), dferrors.ErrInvalidArgument)
166}
167
168if err := cfg.checkHeader(); err != nil {
169return fmt.Errorf("output %s: %w", err.Error(), dferrors.ErrInvalidHeader)
170}
171
172if int64(cfg.RateLimit.Limit) < DefaultMinRate.ToNumber() {
173return fmt.Errorf("rate limit must be greater than %s: %w", DefaultMinRate.String(), dferrors.ErrInvalidArgument)
174}
175
176return nil
177}
178
179func (cfg *ClientOption) Convert(args []string) error {
180if pkgstrings.IsBlank(cfg.Output) {
181url := strings.TrimRight(cfg.URL, "/")
182idx := strings.LastIndexByte(url, '/')
183if idx < 0 {
184return fmt.Errorf("get output from url[%s] error", cfg.URL)
185}
186cfg.Output = url[idx+1:]
187}
188
189if !filepath.IsAbs(cfg.Output) {
190absPath, err := filepath.Abs(cfg.Output)
191if err != nil {
192return fmt.Errorf("get absolute path[%s] error: %v", cfg.Output, err)
193}
194cfg.Output = absPath
195}
196if cfg.URL == "" && len(args) > 0 {
197cfg.URL = args[0]
198}
199
200if cfg.Digest != "" {
201cfg.Tag = ""
202}
203
204if cfg.Console {
205cfg.ShowProgress = false
206}
207return nil
208}
209
210func (cfg *ClientOption) String() string {
211data, _ := json.Marshal(cfg)
212return string(data)
213}
214
215// checkHeader is for checking the header format
216func (cfg *ClientOption) checkHeader() error {
217if len(cfg.Header) == 0 {
218return nil
219}
220
221for _, header := range cfg.Header {
222if !strings.Contains(header, ":") {
223return fmt.Errorf("header format error: %v", header)
224}
225idx := strings.Index(header, ":")
226if len(strings.TrimSpace(header[:idx])) == 0 || len(strings.TrimSpace(header[idx+1:])) == 0 {
227return fmt.Errorf("header format error: %v", header)
228}
229}
230
231return nil
232}
233
234// This function must be called after checkURL
235func (cfg *ClientOption) checkOutput() error {
236if !filepath.IsAbs(cfg.Output) {
237return fmt.Errorf("path[%s] is not absolute path", cfg.Output)
238}
239outputDir, _ := path.Split(cfg.Output)
240if err := MkdirAll(outputDir, 0700, os.Getuid(), os.Getgid()); err != nil {
241return err
242}
243
244f, err := os.Stat(cfg.Output)
245// when not recursive download, need a file
246if !cfg.Recursive && err == nil && f.IsDir() {
247return fmt.Errorf("path[%s] is directory but requires file path", cfg.Output)
248}
249
250// check permission
251for dir := cfg.Output; !pkgstrings.IsBlank(dir); dir = filepath.Dir(dir) {
252if err := syscall.Access(dir, syscall.O_RDWR); err == nil {
253break
254} else if os.IsPermission(err) || dir == "/" {
255return fmt.Errorf("user[%s] path[%s] %v", user.Username(), cfg.Output, err)
256}
257}
258return nil
259}
260
261// MkdirAll make directories recursive, and changes uid, gid to the latest directory.
262// For example: the path /data/x exists, uid=1, gid=1
263// when call MkdirAll("/data/x/y/z", 0755, 2, 2)
264// MkdirAll creates /data/x/y and change owner to 2:2, creates /data/x/y/z and change owner to 2:2
265func MkdirAll(dir string, perm os.FileMode, uid, gid int) error {
266var (
267// directories to create
268dirs []string
269err error
270)
271subDir := dir
272// find not exist directories from bottom to top
273for {
274if subDir == "" {
275break
276}
277_, err = os.Stat(subDir)
278// directory exists
279if err == nil {
280break
281}
282if os.IsNotExist(err) {
283dirs = append(dirs, subDir)
284} else {
285logger.Errorf("stat error: %s", err)
286return err
287}
288subDir = path.Dir(subDir)
289}
290
291// no directory to create
292if len(dirs) == 0 {
293return nil
294}
295
296err = os.MkdirAll(dir, perm)
297if err != nil {
298logger.Errorf("mkdir error: %s", err)
299return err
300}
301
302// update owner from top to bottom
303for _, d := range dirs {
304err = os.Chown(d, uid, gid)
305if err != nil {
306logger.Errorf("chown error: %s", err)
307return err
308}
309}
310return nil
311}
312