Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(807)

Unified Diff: go/src/infra/tools/cipd/files.go

Issue 1129043003: cipd: Refactor client to make it more readable. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « go/src/infra/tools/cipd/fetcher_test.go ('k') | go/src/infra/tools/cipd/files_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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()
-}
« no previous file with comments | « go/src/infra/tools/cipd/fetcher_test.go ('k') | go/src/infra/tools/cipd/files_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698