| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | package util | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-22 22:22:19 +08:00
										 |  |  | // ErrWalkSkipDir is the Error returned when we want to skip descending into a directory
 | 
					
						
							| 
									
										
										
										
											2019-01-29 05:37:44 +08:00
										 |  |  | var ErrWalkSkipDir = errors.New("skip this directory") | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-22 22:22:19 +08:00
										 |  |  | // WalkFunc is a callback function called for each path as a directory is walked
 | 
					
						
							|  |  |  | // If resolvedPath != "", then we are following symbolic links.
 | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | type WalkFunc func(resolvedPath string, info os.FileInfo, err error) error | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-22 22:22:19 +08:00
										 |  |  | // Walk walks a path, optionally following symbolic links, and for each path,
 | 
					
						
							|  |  |  | // it calls the walkFn passed.
 | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-09-22 22:22:19 +08:00
										 |  |  | // It is similar to filepath.Walk, except that it supports symbolic links and
 | 
					
						
							|  |  |  | // can detect infinite loops while following sym links.
 | 
					
						
							|  |  |  | // It solves the issue where your WalkFunc needs a path relative to the symbolic link
 | 
					
						
							|  |  |  | // (resolving links within walkfunc loses the path to the symbolic link for each traversal).
 | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | func Walk(path string, followSymlinks bool, detectSymlinkInfiniteLoop bool, walkFn WalkFunc) error { | 
					
						
							|  |  |  | 	info, err := os.Lstat(path) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var symlinkPathsFollowed map[string]bool | 
					
						
							|  |  |  | 	var resolvedPath string | 
					
						
							|  |  |  | 	if followSymlinks { | 
					
						
							|  |  |  | 		resolvedPath = path | 
					
						
							|  |  |  | 		if detectSymlinkInfiniteLoop { | 
					
						
							|  |  |  | 			symlinkPathsFollowed = make(map[string]bool, 8) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return walk(path, info, resolvedPath, symlinkPathsFollowed, walkFn) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-22 22:22:19 +08:00
										 |  |  | // walk walks the path. It is a helper/sibling function to Walk.
 | 
					
						
							|  |  |  | // It takes a resolvedPath into consideration. This way, paths being walked are
 | 
					
						
							|  |  |  | // always relative to the path argument, even if symbolic links were resolved).
 | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-09-22 22:22:19 +08:00
										 |  |  | // If resolvedPath is "", then we are not following symbolic links.
 | 
					
						
							|  |  |  | // If symlinkPathsFollowed is not nil, then we need to detect infinite loop.
 | 
					
						
							| 
									
										
										
										
											2016-03-02 22:11:24 +08:00
										 |  |  | func walk(path string, info os.FileInfo, resolvedPath string, symlinkPathsFollowed map[string]bool, walkFn WalkFunc) error { | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 	if info == nil { | 
					
						
							| 
									
										
										
										
											2021-06-15 17:55:47 +08:00
										 |  |  | 		return errors.New("walk: Nil FileInfo passed") | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	err := walkFn(resolvedPath, info, nil) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-11-19 21:47:17 +08:00
										 |  |  | 		if info.IsDir() && errors.Is(err, ErrWalkSkipDir) { | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 			err = nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-06-15 14:10:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if resolvedPath != "" && info.Mode()&os.ModeSymlink == os.ModeSymlink { | 
					
						
							|  |  |  | 		// We only want to lstat on directories. If this entry is a symbolic link to a file, no need to recurse.
 | 
					
						
							|  |  |  | 		statInfo, err := os.Stat(resolvedPath) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !statInfo.IsDir() { | 
					
						
							|  |  |  | 			return nil | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-06-10 21:25:07 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-15 14:10:30 +08:00
										 |  |  | 		path2, err := filepath.EvalSymlinks(resolvedPath) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// vout("SymLink Path: %v, links to: %v", resolvedPath, path2)
 | 
					
						
							|  |  |  | 		if symlinkPathsFollowed != nil { | 
					
						
							|  |  |  | 			if _, ok := symlinkPathsFollowed[path2]; ok { | 
					
						
							|  |  |  | 				errMsg := "potential symLink infinite loop, path: %v, link to: %v" | 
					
						
							|  |  |  | 				return fmt.Errorf(errMsg, resolvedPath, path2) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			symlinkPathsFollowed[path2] = true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		info2, err := os.Lstat(path2) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return walk(path, info2, path2, symlinkPathsFollowed, walkFn) | 
					
						
							|  |  |  | 	} else if info.IsDir() { | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 		list, err := ioutil.ReadDir(path) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return walkFn(resolvedPath, info, err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-03-03 18:02:18 +08:00
										 |  |  | 		var subFiles = make([]subFile, 0) | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 		for _, fileInfo := range list { | 
					
						
							|  |  |  | 			path2 := filepath.Join(path, fileInfo.Name()) | 
					
						
							|  |  |  | 			var resolvedPath2 string | 
					
						
							|  |  |  | 			if resolvedPath != "" { | 
					
						
							|  |  |  | 				resolvedPath2 = filepath.Join(resolvedPath, fileInfo.Name()) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-03-03 18:02:18 +08:00
										 |  |  | 			subFiles = append(subFiles, subFile{path: path2, resolvedPath: resolvedPath2, fileInfo: fileInfo}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if containsDistFolder(subFiles) { | 
					
						
							|  |  |  | 			err := walk( | 
					
						
							|  |  |  | 				filepath.Join(path, "dist"), | 
					
						
							|  |  |  | 				info, | 
					
						
							|  |  |  | 				filepath.Join(resolvedPath, "dist"), | 
					
						
							|  |  |  | 				symlinkPathsFollowed, | 
					
						
							|  |  |  | 				walkFn) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-03-03 18:02:18 +08:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			for _, p := range subFiles { | 
					
						
							|  |  |  | 				err = walk(p.path, p.fileInfo, p.resolvedPath, symlinkPathsFollowed, walkFn) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					return err | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-03-03 18:02:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-08 12:22:09 +08:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-03-02 22:11:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-03 18:02:18 +08:00
										 |  |  | type subFile struct { | 
					
						
							|  |  |  | 	path, resolvedPath string | 
					
						
							|  |  |  | 	fileInfo           os.FileInfo | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-03-02 22:11:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-03 18:02:18 +08:00
										 |  |  | func containsDistFolder(subFiles []subFile) bool { | 
					
						
							|  |  |  | 	for _, p := range subFiles { | 
					
						
							|  |  |  | 		if p.fileInfo.IsDir() && p.fileInfo.Name() == "dist" { | 
					
						
							| 
									
										
										
										
											2016-03-02 22:11:24 +08:00
										 |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-01-12 00:37:58 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // CleanRelativePath returns the shortest path name equivalent to path
 | 
					
						
							|  |  |  | // by purely lexical processing. It make sure the provided path is rooted
 | 
					
						
							|  |  |  | // and then uses filepath.Clean and filepath.Rel to make sure the path
 | 
					
						
							|  |  |  | // doesn't include any separators or elements that shouldn't be there
 | 
					
						
							|  |  |  | // like ., .., //.
 | 
					
						
							|  |  |  | func CleanRelativePath(path string) (string, error) { | 
					
						
							|  |  |  | 	cleanPath := filepath.Clean(filepath.Join("/", path)) | 
					
						
							|  |  |  | 	rel, err := filepath.Rel("/", cleanPath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		// slash is prepended above therefore this is not expected to fail
 | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return rel, nil | 
					
						
							|  |  |  | } |