| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package gps |
| |
| import ( |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "github.com/pkg/errors" |
| ) |
| |
| // fsLink represents a symbolic link. |
| type fsLink struct { |
| path string |
| to string |
| |
| // circular denotes if evaluating the symlink fails with "too many links" error. |
| // This error means that it's very likely that the symlink has a circular reference. |
| circular bool |
| |
| // broken denotes that attempting to resolve the link fails, most likely because |
| // the destaination doesn't exist. |
| broken bool |
| } |
| |
| // filesystemState represents the state of a file system. |
| type filesystemState struct { |
| root string |
| dirs []string |
| files []string |
| links []fsLink |
| } |
| |
| func (s filesystemState) setup() error { |
| for _, dir := range s.dirs { |
| p := filepath.Join(s.root, dir) |
| |
| if err := os.MkdirAll(p, 0777); err != nil { |
| return errors.Errorf("os.MkdirAll(%q, 0777) err=%q", p, err) |
| } |
| } |
| |
| for _, file := range s.files { |
| p := filepath.Join(s.root, file) |
| |
| f, err := os.Create(p) |
| if err != nil { |
| return errors.Errorf("os.Create(%q) err=%q", p, err) |
| } |
| |
| if err := f.Close(); err != nil { |
| return errors.Errorf("file %q Close() err=%q", p, err) |
| } |
| } |
| |
| for _, link := range s.links { |
| p := filepath.Join(s.root, link.path) |
| |
| // On Windows, relative symlinks confuse filepath.Walk. So, we'll just sigh |
| // and do absolute links, assuming they are relative to the directory of |
| // link.path. |
| // |
| // Reference: https://github.com/golang/go/issues/17540 |
| // |
| // TODO(ibrasho): This was fixed in Go 1.9. Remove this when support for |
| // 1.8 is dropped. |
| dir := filepath.Dir(p) |
| to := "" |
| if link.to != "" { |
| to = filepath.Join(dir, link.to) |
| } |
| |
| if err := os.Symlink(to, p); err != nil { |
| return errors.Errorf("os.Symlink(%q, %q) err=%q", to, p, err) |
| } |
| } |
| |
| return nil |
| } |
| |
| // deriveFilesystemState returns a filesystemState based on the state of |
| // the filesystem on root. |
| func deriveFilesystemState(root string) (filesystemState, error) { |
| fs := filesystemState{root: root} |
| |
| err := filepath.Walk(fs.root, func(path string, info os.FileInfo, err error) error { |
| if err != nil { |
| return err |
| } |
| |
| if path == fs.root { |
| return nil |
| } |
| |
| relPath, err := filepath.Rel(fs.root, path) |
| if err != nil { |
| return err |
| } |
| |
| if (info.Mode() & os.ModeSymlink) != 0 { |
| l := fsLink{path: relPath} |
| |
| l.to, err = filepath.EvalSymlinks(path) |
| if err != nil && strings.HasSuffix(err.Error(), "too many links") { |
| l.circular = true |
| } else if err != nil && os.IsNotExist(err) { |
| l.broken = true |
| } else if err != nil { |
| return err |
| } |
| |
| fs.links = append(fs.links, l) |
| |
| return nil |
| } |
| |
| if info.IsDir() { |
| fs.dirs = append(fs.dirs, relPath) |
| |
| return nil |
| } |
| |
| fs.files = append(fs.files, relPath) |
| |
| return nil |
| }) |
| |
| if err != nil { |
| return filesystemState{}, err |
| } |
| |
| return fs, nil |
| } |