| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | package archiver | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2020-02-17 09:22:32 +01:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-28 21:30:06 +01:00
										 |  |  | 	"github.com/restic/restic/internal/debug" | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	"github.com/restic/restic/internal/fs" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Scanner  traverses the targets and calls the function Result with cumulated | 
					
						
							|  |  |  | // stats concerning the files and folders found. Select is used to decide which | 
					
						
							|  |  |  | // items should be included. Error is called when an error occurs. | 
					
						
							|  |  |  | type Scanner struct { | 
					
						
							| 
									
										
										
										
											2018-07-31 17:25:25 +02:00
										 |  |  | 	FS           fs.FS | 
					
						
							|  |  |  | 	SelectByName SelectByNameFunc | 
					
						
							|  |  |  | 	Select       SelectFunc | 
					
						
							|  |  |  | 	Error        ErrorFunc | 
					
						
							|  |  |  | 	Result       func(item string, s ScanStats) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewScanner initializes a new Scanner. | 
					
						
							|  |  |  | func NewScanner(fs fs.FS) *Scanner { | 
					
						
							|  |  |  | 	return &Scanner{ | 
					
						
							| 
									
										
										
										
											2018-07-31 17:25:25 +02:00
										 |  |  | 		FS:           fs, | 
					
						
							| 
									
										
										
										
											2024-02-10 22:58:10 +01:00
										 |  |  | 		SelectByName: func(_ string) bool { return true }, | 
					
						
							|  |  |  | 		Select:       func(_ string, _ os.FileInfo) bool { return true }, | 
					
						
							|  |  |  | 		Error:        func(_ string, err error) error { return err }, | 
					
						
							|  |  |  | 		Result:       func(_ string, _ ScanStats) {}, | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ScanStats collect statistics. | 
					
						
							|  |  |  | type ScanStats struct { | 
					
						
							|  |  |  | 	Files, Dirs, Others uint | 
					
						
							|  |  |  | 	Bytes               uint64 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-27 11:26:52 +02:00
										 |  |  | func (s *Scanner) scanTree(ctx context.Context, stats ScanStats, tree tree) (ScanStats, error) { | 
					
						
							| 
									
										
										
										
											2021-01-28 21:30:06 +01:00
										 |  |  | 	// traverse the path in the file system for all leaf nodes | 
					
						
							|  |  |  | 	if tree.Leaf() { | 
					
						
							|  |  |  | 		abstarget, err := s.FS.Abs(tree.Path) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-01-28 21:30:06 +01:00
										 |  |  | 			return ScanStats{}, err | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		stats, err = s.scan(ctx, stats, abstarget) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-01-28 21:30:06 +01:00
										 |  |  | 			return ScanStats{}, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return stats, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// otherwise recurse into the nodes in a deterministic order | 
					
						
							|  |  |  | 	for _, name := range tree.NodeNames() { | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		stats, err = s.scanTree(ctx, stats, tree.Nodes[name]) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return ScanStats{}, err | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ctx.Err() != nil { | 
					
						
							| 
									
										
										
										
											2021-01-28 21:30:06 +01:00
										 |  |  | 			return stats, nil | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-28 21:30:06 +01:00
										 |  |  | 	return stats, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Scan traverses the targets. The function Result is called for each new item | 
					
						
							|  |  |  | // found, the complete result is also returned by Scan. | 
					
						
							|  |  |  | func (s *Scanner) Scan(ctx context.Context, targets []string) error { | 
					
						
							|  |  |  | 	debug.Log("start scan for %v", targets) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cleanTargets, err := resolveRelativeTargets(s.FS, targets) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	debug.Log("clean targets %v", cleanTargets) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// we're using the same tree representation as the archiver does | 
					
						
							| 
									
										
										
										
											2024-08-27 11:26:52 +02:00
										 |  |  | 	tree, err := newTree(s.FS, cleanTargets) | 
					
						
							| 
									
										
										
										
											2021-01-28 21:30:06 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	stats, err := s.scanTree(ctx, ScanStats{}, *tree) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	s.Result("", stats) | 
					
						
							| 
									
										
										
										
											2021-01-28 21:30:06 +01:00
										 |  |  | 	debug.Log("result: %+v", stats) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Scanner) scan(ctx context.Context, stats ScanStats, target string) (ScanStats, error) { | 
					
						
							|  |  |  | 	if ctx.Err() != nil { | 
					
						
							| 
									
										
										
										
											2018-09-08 18:35:49 +02:00
										 |  |  | 		return stats, nil | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-31 17:25:25 +02:00
										 |  |  | 	// exclude files by path before running stat to reduce number of lstat calls | 
					
						
							|  |  |  | 	if !s.SelectByName(target) { | 
					
						
							|  |  |  | 		return stats, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// get file information | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	fi, err := s.FS.Lstat(target) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-05-21 00:31:26 +02:00
										 |  |  | 		return stats, s.Error(target, err) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-31 17:25:25 +02:00
										 |  |  | 	// run remaining select functions that require file information | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	if !s.Select(target, fi) { | 
					
						
							|  |  |  | 		return stats, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case fi.Mode().IsRegular(): | 
					
						
							|  |  |  | 		stats.Files++ | 
					
						
							|  |  |  | 		stats.Bytes += uint64(fi.Size()) | 
					
						
							|  |  |  | 	case fi.Mode().IsDir(): | 
					
						
							| 
									
										
										
										
											2024-06-29 17:15:29 +02:00
										 |  |  | 		names, err := fs.Readdirnames(s.FS, target, fs.O_NOFOLLOW) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2022-05-21 00:31:26 +02:00
										 |  |  | 			return stats, s.Error(target, err) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-02-17 09:22:32 +01:00
										 |  |  | 		sort.Strings(names) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		for _, name := range names { | 
					
						
							|  |  |  | 			stats, err = s.scan(ctx, stats, filepath.Join(target, name)) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return stats, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		stats.Dirs++ | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		stats.Others++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	s.Result(target, stats) | 
					
						
							|  |  |  | 	return stats, nil | 
					
						
							|  |  |  | } |