| Index: go/src/infra/tools/cipd/files.go
|
| diff --git a/go/src/infra/tools/cipd/files.go b/go/src/infra/tools/cipd/files.go
|
| deleted file mode 100644
|
| index 67bd57b4fb070ce60e049120800bfd05e560eac6..0000000000000000000000000000000000000000
|
| --- a/go/src/infra/tools/cipd/files.go
|
| +++ /dev/null
|
| @@ -1,415 +0,0 @@
|
| -// Copyright 2014 The Chromium 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 cipd
|
| -
|
| -import (
|
| - "fmt"
|
| - "io"
|
| - "io/ioutil"
|
| - "os"
|
| - "path/filepath"
|
| - "strings"
|
| -)
|
| -
|
| -// File defines a single file to be added or extracted from a package. All paths
|
| -// are slash separated (including symlink targets).
|
| -type File interface {
|
| - // Name returns slash separated file path relative to a package root, e.g. "dir/dir/file".
|
| - Name() string
|
| - // Size returns size of the file. 0 for symlinks.
|
| - Size() uint64
|
| - // Executable returns true if the file is executable. Only used for Linux\Mac archives. false for symlinks.
|
| - Executable() bool
|
| - // Symlink returns true if the file is a symlink.
|
| - Symlink() bool
|
| - // SymlinkTarget return a path the symlink is pointing to.
|
| - SymlinkTarget() (string, error)
|
| - // Open opens the regular file for reading. Returns error for symlink files.
|
| - Open() (io.ReadCloser, error)
|
| -}
|
| -
|
| -// Destination knows how to create files when extracting a package. It supports
|
| -// transactional semantic by providing 'Begin' and 'End' methods. No changes
|
| -// should be applied until End(true) is called. A call to End(false) should
|
| -// discard any pending changes. All paths are slash separated.
|
| -type Destination interface {
|
| - // Begin initiates a new write transaction. Called before first CreateFile.
|
| - Begin() error
|
| - // CreateFile opens a writer to extract some package file to. 'name' must
|
| - // be a slash separated path relative to the destination root.
|
| - CreateFile(name string, executable bool) (io.WriteCloser, error)
|
| - // CreateSymlink creates a symlink (with absolute or relative target). 'name'
|
| - // must be a slash separated path relative to the destination root.
|
| - CreateSymlink(name string, target string) error
|
| - // End finalizes package extraction (commit or rollback, based on success).
|
| - End(success bool) error
|
| -}
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -// File system source.
|
| -
|
| -type fileSystemFile struct {
|
| - absPath string
|
| - name string
|
| - size uint64
|
| - executable bool
|
| - symlinkTarget string
|
| -}
|
| -
|
| -func (f *fileSystemFile) Name() string { return f.name }
|
| -func (f *fileSystemFile) Size() uint64 { return f.size }
|
| -func (f *fileSystemFile) Executable() bool { return f.executable }
|
| -func (f *fileSystemFile) Symlink() bool { return f.symlinkTarget != "" }
|
| -
|
| -func (f *fileSystemFile) SymlinkTarget() (string, error) {
|
| - if f.symlinkTarget != "" {
|
| - return f.symlinkTarget, nil
|
| - }
|
| - return "", fmt.Errorf("Not a symlink: %s", f.Name())
|
| -}
|
| -
|
| -func (f *fileSystemFile) Open() (io.ReadCloser, error) {
|
| - if f.Symlink() {
|
| - return nil, fmt.Errorf("Opening a symlink is not allowed: %s", f.Name())
|
| - }
|
| - return os.Open(f.absPath)
|
| -}
|
| -
|
| -// ScanFilter is predicate used by ScanFileSystem to decide what files to skip.
|
| -type ScanFilter func(abs string) bool
|
| -
|
| -// ScanFileSystem returns all files in some file system directory in
|
| -// an alphabetical order. It returns only files, skipping directory entries
|
| -// (i.e. empty directories are completely invisible). ScanFileSystem does not
|
| -// follow symbolic links, but recognizes them as such (see Symlink() method
|
| -// of File interface). It scans "dir" path, returning File objects that have
|
| -// paths relative to "root". It skips files and directories for which
|
| -// 'exclude(absolute path)' returns true.
|
| -func ScanFileSystem(dir string, root string, exclude ScanFilter) ([]File, error) {
|
| - root, err := filepath.Abs(filepath.Clean(root))
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| - dir, err = filepath.Abs(filepath.Clean(dir))
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| - if !isSubpath(dir, root) {
|
| - return nil, fmt.Errorf("Scanned directory must be under root directory")
|
| - }
|
| -
|
| - files := []File{}
|
| -
|
| - err = filepath.Walk(dir, func(abs string, info os.FileInfo, err error) error {
|
| - if err != nil {
|
| - return err
|
| - }
|
| - if exclude != nil && abs != dir && exclude(abs) {
|
| - if info.Mode().IsDir() {
|
| - return filepath.SkipDir
|
| - }
|
| - return nil
|
| - }
|
| - if info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
|
| - f, err := WrapFile(abs, root, &info)
|
| - if err != nil {
|
| - return err
|
| - }
|
| - files = append(files, f)
|
| - }
|
| - return nil
|
| - })
|
| -
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| - return files, nil
|
| -}
|
| -
|
| -// WrapFile constructs File object for some file in the file system, specified
|
| -// by its native absolute path 'abs' (subpath of 'root', also specified as
|
| -// a native absolute path). Returned File object has path relative to 'root'.
|
| -// If fileInfo is given, it will be used to grab file mode and size, otherwise
|
| -// os.Lstat will be used to get it. Recognizes symlinks.
|
| -func WrapFile(abs string, root string, fileInfo *os.FileInfo) (File, error) {
|
| - if !filepath.IsAbs(abs) {
|
| - return nil, fmt.Errorf("Expecting absolute path, got this: %q", abs)
|
| - }
|
| - if !filepath.IsAbs(root) {
|
| - return nil, fmt.Errorf("Expecting absolute path, got this: %q", root)
|
| - }
|
| - if !isSubpath(abs, root) {
|
| - return nil, fmt.Errorf("Path %q is not under %q", abs, root)
|
| - }
|
| -
|
| - var info os.FileInfo
|
| - if fileInfo == nil {
|
| - // Use Lstat to NOT follow symlinks (as os.Stat does).
|
| - var err error
|
| - info, err = os.Lstat(abs)
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| - } else {
|
| - info = *fileInfo
|
| - }
|
| -
|
| - rel, err := filepath.Rel(root, abs)
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| -
|
| - // Recognize symlinks as such, convert target to relative path, if needed.
|
| - if info.Mode()&os.ModeSymlink != 0 {
|
| - target, err := os.Readlink(abs)
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| - if filepath.IsAbs(target) {
|
| - // Convert absolute path pointing somewhere in "root" into a path
|
| - // relative to the symlink file itself. Store other absolute paths as
|
| - // they are. For example, it allows to package virtual env directory
|
| - // that symlinks python binary from /usr/bin.
|
| - if isSubpath(target, root) {
|
| - target, err = filepath.Rel(filepath.Dir(abs), target)
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| - }
|
| - } else {
|
| - // Only relative paths that do not go outside "root" are allowed.
|
| - // A package must not depend on its installation path.
|
| - targetAbs := filepath.Clean(filepath.Join(filepath.Dir(abs), target))
|
| - if !isSubpath(targetAbs, root) {
|
| - return nil, fmt.Errorf(
|
| - "Invalid symlink %s: a relative symlink pointing to a file outside of the package root", rel)
|
| - }
|
| - }
|
| - return &fileSystemFile{
|
| - absPath: abs,
|
| - name: filepath.ToSlash(rel),
|
| - symlinkTarget: filepath.ToSlash(target),
|
| - }, nil
|
| - }
|
| -
|
| - // Regular file.
|
| - if info.Mode().IsRegular() {
|
| - return &fileSystemFile{
|
| - absPath: abs,
|
| - name: filepath.ToSlash(rel),
|
| - size: uint64(info.Size()),
|
| - executable: (info.Mode().Perm() & 0111) != 0,
|
| - }, nil
|
| - }
|
| -
|
| - return nil, fmt.Errorf("Not a regular file or symlink: %s", abs)
|
| -}
|
| -
|
| -// isSubpath returns true if 'path' is 'root' or is inside a subdirectory of
|
| -// 'root'. Both 'path' and 'root' should be given as a native paths. If any of
|
| -// paths can't be converted to an absolute path returns false.
|
| -func isSubpath(path, root string) bool {
|
| - path, err := filepath.Abs(filepath.Clean(path))
|
| - if err != nil {
|
| - return false
|
| - }
|
| - root, err = filepath.Abs(filepath.Clean(root))
|
| - if err != nil {
|
| - return false
|
| - }
|
| - if root == path {
|
| - return true
|
| - }
|
| - if root[len(root)-1] != filepath.Separator {
|
| - root += string(filepath.Separator)
|
| - }
|
| - return strings.HasPrefix(path, root)
|
| -}
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -// FileSystemDestination implementation.
|
| -
|
| -type fileSystemDestination struct {
|
| - // Destination directory.
|
| - dir string
|
| - // Root temporary directory.
|
| - tempDir string
|
| - // Where to extract all temp files, subdirectory of tempDir.
|
| - outDir string
|
| - // Currently open files.
|
| - openFiles map[string]*os.File
|
| -}
|
| -
|
| -// NewFileSystemDestination returns a destination in the file system (directory)
|
| -// to extract a package to.
|
| -func NewFileSystemDestination(dir string) Destination {
|
| - return &fileSystemDestination{
|
| - dir: dir,
|
| - openFiles: map[string]*os.File{},
|
| - }
|
| -}
|
| -
|
| -func (d *fileSystemDestination) Begin() (err error) {
|
| - if d.tempDir != "" {
|
| - return fmt.Errorf("Destination is already open")
|
| - }
|
| -
|
| - // Ensure parent directory of destination directory exists.
|
| - d.dir, err = filepath.Abs(filepath.Clean(d.dir))
|
| - if err != nil {
|
| - return err
|
| - }
|
| - err = os.MkdirAll(filepath.Dir(d.dir), 0777)
|
| - if err != nil {
|
| - return err
|
| - }
|
| -
|
| - // Called in case something below fails.
|
| - cleanup := func() {
|
| - if d.tempDir != "" {
|
| - os.RemoveAll(d.tempDir)
|
| - }
|
| - d.tempDir = ""
|
| - d.outDir = ""
|
| - }
|
| -
|
| - // Create root temp dir, on the same level as destination directory.
|
| - d.tempDir, err = ioutil.TempDir(filepath.Dir(d.dir), filepath.Base(d.dir)+"_")
|
| - if err != nil {
|
| - cleanup()
|
| - return err
|
| - }
|
| -
|
| - // Create a staging output directory where everything will be extracted.
|
| - d.outDir = filepath.Join(d.tempDir, "out")
|
| - err = os.MkdirAll(d.outDir, 0777)
|
| - if err != nil {
|
| - cleanup()
|
| - return err
|
| - }
|
| -
|
| - return nil
|
| -}
|
| -
|
| -func (d *fileSystemDestination) CreateFile(name string, executable bool) (io.WriteCloser, error) {
|
| - _, ok := d.openFiles[name]
|
| - if ok {
|
| - return nil, fmt.Errorf("File %s is already open", name)
|
| - }
|
| -
|
| - path, err := d.prepareFilePath(name)
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| -
|
| - // Let the umask trim the file mode. Do not set 'writable' bit though.
|
| - var mode os.FileMode
|
| - if executable {
|
| - mode = 0555
|
| - } else {
|
| - mode = 0444
|
| - }
|
| -
|
| - file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_EXCL, mode)
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| - d.openFiles[name] = file
|
| - return &fileSystemDestinationFile{
|
| - nested: file,
|
| - parent: d,
|
| - closeCallback: func() {
|
| - delete(d.openFiles, name)
|
| - },
|
| - }, nil
|
| -}
|
| -
|
| -func (d *fileSystemDestination) CreateSymlink(name string, target string) error {
|
| - path, err := d.prepareFilePath(name)
|
| - if err != nil {
|
| - return err
|
| - }
|
| -
|
| - // Forbid relative symlinks to files outside of the destination root.
|
| - target = filepath.FromSlash(target)
|
| - if !filepath.IsAbs(target) {
|
| - targetAbs := filepath.Clean(filepath.Join(filepath.Dir(path), target))
|
| - if !isSubpath(targetAbs, d.outDir) {
|
| - return fmt.Errorf("Relative symlink is pointing outside of the destination dir: %s", name)
|
| - }
|
| - }
|
| -
|
| - return os.Symlink(target, path)
|
| -}
|
| -
|
| -func (d *fileSystemDestination) End(success bool) error {
|
| - if d.tempDir == "" {
|
| - return fmt.Errorf("Destination is not open")
|
| - }
|
| - if len(d.openFiles) != 0 {
|
| - return fmt.Errorf("Not all files were closed. Leaking.")
|
| - }
|
| -
|
| - // Clean up temp dir and the state no matter what.
|
| - defer func() {
|
| - os.RemoveAll(d.tempDir)
|
| - d.tempDir = ""
|
| - d.outDir = ""
|
| - }()
|
| -
|
| - if success {
|
| - // Move existing directory away, if it is there.
|
| - old := filepath.Join(d.tempDir, "old")
|
| - if os.Rename(d.dir, old) != nil {
|
| - old = ""
|
| - }
|
| -
|
| - // Move new directory in place.
|
| - err := os.Rename(d.outDir, d.dir)
|
| - if err != nil {
|
| - // Try to return the original directory back...
|
| - if old != "" {
|
| - os.Rename(old, d.dir)
|
| - }
|
| - return err
|
| - }
|
| - }
|
| -
|
| - return nil
|
| -}
|
| -
|
| -// prepareFilePath performs steps common to CreateFile and CreateSymlink: it
|
| -// does some validation, expands "name" to an absolute path and creates parent
|
| -// directories for a future file. Returns absolute path where the file should
|
| -// be put.
|
| -func (d *fileSystemDestination) prepareFilePath(name string) (string, error) {
|
| - if d.tempDir == "" {
|
| - return "", fmt.Errorf("Destination is not open")
|
| - }
|
| - path := filepath.Clean(filepath.Join(d.outDir, filepath.FromSlash(name)))
|
| - if !isSubpath(path, d.outDir) {
|
| - return "", fmt.Errorf("Invalid relative file name: %s", name)
|
| - }
|
| - err := os.MkdirAll(filepath.Dir(path), 0777)
|
| - if err != nil {
|
| - return "", err
|
| - }
|
| - return path, nil
|
| -}
|
| -
|
| -type fileSystemDestinationFile struct {
|
| - nested io.WriteCloser
|
| - parent *fileSystemDestination
|
| - closeCallback func()
|
| -}
|
| -
|
| -func (f *fileSystemDestinationFile) Write(p []byte) (n int, err error) {
|
| - return f.nested.Write(p)
|
| -}
|
| -
|
| -func (f *fileSystemDestinationFile) Close() error {
|
| - f.closeCallback()
|
| - return f.nested.Close()
|
| -}
|
|
|