pax_global_header00006660000000000000000000000064140436276110014516gustar00rootroot0000000000000052 comment=3bf3fe5b381bcfa471f7f6054c0cf2309c35e651 go-billy-5.3.1/000077500000000000000000000000001404362761100132425ustar00rootroot00000000000000go-billy-5.3.1/.github/000077500000000000000000000000001404362761100146025ustar00rootroot00000000000000go-billy-5.3.1/.github/workflows/000077500000000000000000000000001404362761100166375ustar00rootroot00000000000000go-billy-5.3.1/.github/workflows/test.yml000066400000000000000000000006751404362761100203510ustar00rootroot00000000000000on: [push, pull_request] name: Test jobs: test: strategy: matrix: go-version: [1.14.x, 1.15.x, 1.16.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v1 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v2 - name: Test run: go test ./...go-billy-5.3.1/.github/workflows/test_js.yml000066400000000000000000000011661404362761100210410ustar00rootroot00000000000000on: [push, pull_request] name: Test jobs: test: strategy: matrix: go-version: [1.14.x, 1.15.x, 1.16.x] runs-on: ubuntu-latest steps: - name: Install Go uses: actions/setup-go@v1 with: go-version: ${{ matrix.go-version }} - name: Install wasmbrowsertest run: | go get github.com/agnivade/wasmbrowsertest mv $HOME/go/bin/wasmbrowsertest $HOME/go/bin/go_js_wasm_exec - name: Checkout code uses: actions/checkout@v2 - name: Test run: go test -exec="$HOME/go/bin/go_js_wasm_exec" ./... env: GOOS: js GOARCH: wasmgo-billy-5.3.1/.gitignore000066400000000000000000000000541404362761100152310ustar00rootroot00000000000000/coverage.txt /vendor Gopkg.lock Gopkg.toml go-billy-5.3.1/LICENSE000066400000000000000000000261331404362761100142540ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 Sourced Technologies S.L. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. go-billy-5.3.1/README.md000066400000000000000000000040171404362761100145230ustar00rootroot00000000000000# go-billy [![GoDoc](https://godoc.org/gopkg.in/go-git/go-billy.v5?status.svg)](https://pkg.go.dev/github.com/go-git/go-billy/v5) [![Test](https://github.com/go-git/go-billy/workflows/Test/badge.svg)](https://github.com/go-git/go-billy/actions?query=workflow%3ATest) The missing interface filesystem abstraction for Go. Billy implements an interface based on the `os` standard library, allowing to develop applications without dependency on the underlying storage. Makes it virtually free to implement mocks and testing over filesystem operations. Billy was born as part of [go-git/go-git](https://github.com/go-git/go-git) project. ## Installation ```go import "github.com/go-git/go-billy/v5" // with go modules enabled (GO111MODULE=on or outside GOPATH) import "github.com/go-git/go-billy" // with go modules disabled ``` ## Usage Billy exposes filesystems using the [`Filesystem` interface](https://pkg.go.dev/github.com/go-git/go-billy/v5?tab=doc#Filesystem). Each filesystem implementation gives you a `New` method, whose arguments depend on the implementation itself, that returns a new `Filesystem`. The following example caches in memory all readable files in a directory from any billy's filesystem implementation. ```go func LoadToMemory(origin billy.Filesystem, path string) (*memory.Memory, error) { memory := memory.New() files, err := origin.ReadDir("/") if err != nil { return nil, err } for _, file := range files { if file.IsDir() { continue } src, err := origin.Open(file.Name()) if err != nil { return nil, err } dst, err := memory.Create(file.Name()) if err != nil { return nil, err } if _, err = io.Copy(dst, src); err != nil { return nil, err } if err := dst.Close(); err != nil { return nil, err } if err := src.Close(); err != nil { return nil, err } } return memory, nil } ``` ## Why billy? The library billy deals with storage systems and Billy is the name of a well-known, IKEA bookcase. That's it. ## License Apache License Version 2.0, see [LICENSE](LICENSE) go-billy-5.3.1/fs.go000066400000000000000000000175521404362761100142130ustar00rootroot00000000000000package billy import ( "errors" "io" "os" "time" ) var ( ErrReadOnly = errors.New("read-only filesystem") ErrNotSupported = errors.New("feature not supported") ErrCrossedBoundary = errors.New("chroot boundary crossed") ) // Capability holds the supported features of a billy filesystem. This does // not mean that the capability has to be supported by the underlying storage. // For example, a billy filesystem may support WriteCapability but the // storage be mounted in read only mode. type Capability uint64 const ( // WriteCapability means that the fs is writable. WriteCapability Capability = 1 << iota // ReadCapability means that the fs is readable. ReadCapability // ReadAndWriteCapability is the ability to open a file in read and write mode. ReadAndWriteCapability // SeekCapability means it is able to move position inside the file. SeekCapability // TruncateCapability means that a file can be truncated. TruncateCapability // LockCapability is the ability to lock a file. LockCapability // DefaultCapabilities lists all capable features supported by filesystems // without Capability interface. This list should not be changed until a // major version is released. DefaultCapabilities Capability = WriteCapability | ReadCapability | ReadAndWriteCapability | SeekCapability | TruncateCapability | LockCapability // AllCapabilities lists all capable features. AllCapabilities Capability = WriteCapability | ReadCapability | ReadAndWriteCapability | SeekCapability | TruncateCapability | LockCapability ) // Filesystem abstract the operations in a storage-agnostic interface. // Each method implementation mimics the behavior of the equivalent functions // at the os package from the standard library. type Filesystem interface { Basic TempFile Dir Symlink Chroot } // Basic abstract the basic operations in a storage-agnostic interface as // an extension to the Basic interface. type Basic interface { // Create creates the named file with mode 0666 (before umask), truncating // it if it already exists. If successful, methods on the returned File can // be used for I/O; the associated file descriptor has mode O_RDWR. Create(filename string) (File, error) // Open opens the named file for reading. If successful, methods on the // returned file can be used for reading; the associated file descriptor has // mode O_RDONLY. Open(filename string) (File, error) // OpenFile is the generalized open call; most users will use Open or Create // instead. It opens the named file with specified flag (O_RDONLY etc.) and // perm, (0666 etc.) if applicable. If successful, methods on the returned // File can be used for I/O. OpenFile(filename string, flag int, perm os.FileMode) (File, error) // Stat returns a FileInfo describing the named file. Stat(filename string) (os.FileInfo, error) // Rename renames (moves) oldpath to newpath. If newpath already exists and // is not a directory, Rename replaces it. OS-specific restrictions may // apply when oldpath and newpath are in different directories. Rename(oldpath, newpath string) error // Remove removes the named file or directory. Remove(filename string) error // Join joins any number of path elements into a single path, adding a // Separator if necessary. Join calls filepath.Clean on the result; in // particular, all empty strings are ignored. On Windows, the result is a // UNC path if and only if the first path element is a UNC path. Join(elem ...string) string } type TempFile interface { // TempFile creates a new temporary file in the directory dir with a name // beginning with prefix, opens the file for reading and writing, and // returns the resulting *os.File. If dir is the empty string, TempFile // uses the default directory for temporary files (see os.TempDir). // Multiple programs calling TempFile simultaneously will not choose the // same file. The caller can use f.Name() to find the pathname of the file. // It is the caller's responsibility to remove the file when no longer // needed. TempFile(dir, prefix string) (File, error) } // Dir abstract the dir related operations in a storage-agnostic interface as // an extension to the Basic interface. type Dir interface { // ReadDir reads the directory named by dirname and returns a list of // directory entries sorted by filename. ReadDir(path string) ([]os.FileInfo, error) // MkdirAll creates a directory named path, along with any necessary // parents, and returns nil, or else returns an error. The permission bits // perm are used for all directories that MkdirAll creates. If path is/ // already a directory, MkdirAll does nothing and returns nil. MkdirAll(filename string, perm os.FileMode) error } // Symlink abstract the symlink related operations in a storage-agnostic // interface as an extension to the Basic interface. type Symlink interface { // Lstat returns a FileInfo describing the named file. If the file is a // symbolic link, the returned FileInfo describes the symbolic link. Lstat // makes no attempt to follow the link. Lstat(filename string) (os.FileInfo, error) // Symlink creates a symbolic-link from link to target. target may be an // absolute or relative path, and need not refer to an existing node. // Parent directories of link are created as necessary. Symlink(target, link string) error // Readlink returns the target path of link. Readlink(link string) (string, error) } // Change abstract the FileInfo change related operations in a storage-agnostic // interface as an extension to the Basic interface type Change interface { // Chmod changes the mode of the named file to mode. If the file is a // symbolic link, it changes the mode of the link's target. Chmod(name string, mode os.FileMode) error // Lchown changes the numeric uid and gid of the named file. If the file is // a symbolic link, it changes the uid and gid of the link itself. Lchown(name string, uid, gid int) error // Chown changes the numeric uid and gid of the named file. If the file is a // symbolic link, it changes the uid and gid of the link's target. Chown(name string, uid, gid int) error // Chtimes changes the access and modification times of the named file, // similar to the Unix utime() or utimes() functions. // // The underlying filesystem may truncate or round the values to a less // precise time unit. Chtimes(name string, atime time.Time, mtime time.Time) error } // Chroot abstract the chroot related operations in a storage-agnostic interface // as an extension to the Basic interface. type Chroot interface { // Chroot returns a new filesystem from the same type where the new root is // the given path. Files outside of the designated directory tree cannot be // accessed. Chroot(path string) (Filesystem, error) // Root returns the root path of the filesystem. Root() string } // File represent a file, being a subset of the os.File type File interface { // Name returns the name of the file as presented to Open. Name() string io.Writer io.Reader io.ReaderAt io.Seeker io.Closer // Lock locks the file like e.g. flock. It protects against access from // other processes. Lock() error // Unlock unlocks the file. Unlock() error // Truncate the file. Truncate(size int64) error } // Capable interface can return the available features of a filesystem. type Capable interface { // Capabilities returns the capabilities of a filesystem in bit flags. Capabilities() Capability } // Capabilities returns the features supported by a filesystem. If the FS // does not implement Capable interface it returns all features. func Capabilities(fs Basic) Capability { capable, ok := fs.(Capable) if !ok { return DefaultCapabilities } return capable.Capabilities() } // CapabilityCheck tests the filesystem for the provided capabilities and // returns true in case it supports all of them. func CapabilityCheck(fs Basic, capabilities Capability) bool { fsCaps := Capabilities(fs) return fsCaps&capabilities == capabilities } go-billy-5.3.1/fs_test.go000066400000000000000000000017121404362761100152410ustar00rootroot00000000000000package billy_test import ( "testing" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) type FSSuite struct{} func Test(t *testing.T) { TestingT(t) } var _ = Suite(&FSSuite{}) func (s *FSSuite) TestCapabilities(c *C) { cases := []struct { caps Capability expected bool }{ {LockCapability, false}, {ReadCapability, true}, {ReadCapability | WriteCapability, true}, {ReadCapability | WriteCapability | ReadAndWriteCapability | TruncateCapability, true}, {ReadCapability | WriteCapability | ReadAndWriteCapability | TruncateCapability | LockCapability, false}, {TruncateCapability | LockCapability, false}, } // This filesystem supports all capabilities except for LockCapability fs := new(test.NoLockCapFs) for _, e := range cases { c.Assert(CapabilityCheck(fs, e.caps), Equals, e.expected) } dummy := new(test.BasicMock) c.Assert(Capabilities(dummy), Equals, DefaultCapabilities) } go-billy-5.3.1/go.mod000066400000000000000000000004301404362761100143450ustar00rootroot00000000000000module github.com/go-git/go-billy/v5 require ( github.com/kr/text v0.2.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f ) go 1.13 go-billy-5.3.1/go.sum000066400000000000000000000024161404362761100144000ustar00rootroot00000000000000github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/go-git/go-billy v1.0.0 h1:bXR6Zu3opPSg0R4dDxqaLglY4rxw7ja7wS16qSpOKL4= github.com/go-git/go-billy v3.1.0+incompatible h1:dwrJ8G2Jt1srYgIJs+lRjA36qBY68O2Lg5idKG8ef5M= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= go-billy-5.3.1/helper/000077500000000000000000000000001404362761100145215ustar00rootroot00000000000000go-billy-5.3.1/helper/chroot/000077500000000000000000000000001404362761100160175ustar00rootroot00000000000000go-billy-5.3.1/helper/chroot/chroot.go000066400000000000000000000122051404362761100176440ustar00rootroot00000000000000package chroot import ( "os" "path/filepath" "strings" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/polyfill" ) // ChrootHelper is a helper to implement billy.Chroot. type ChrootHelper struct { underlying billy.Filesystem base string } // New creates a new filesystem wrapping up the given 'fs'. // The created filesystem has its base in the given ChrootHelperectory of the // underlying filesystem. func New(fs billy.Basic, base string) billy.Filesystem { return &ChrootHelper{ underlying: polyfill.New(fs), base: base, } } func (fs *ChrootHelper) underlyingPath(filename string) (string, error) { if isCrossBoundaries(filename) { return "", billy.ErrCrossedBoundary } return fs.Join(fs.Root(), filename), nil } func isCrossBoundaries(path string) bool { path = filepath.ToSlash(path) path = filepath.Clean(path) return strings.HasPrefix(path, ".."+string(filepath.Separator)) } func (fs *ChrootHelper) Create(filename string) (billy.File, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } f, err := fs.underlying.Create(fullpath) if err != nil { return nil, err } return newFile(fs, f, filename), nil } func (fs *ChrootHelper) Open(filename string) (billy.File, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } f, err := fs.underlying.Open(fullpath) if err != nil { return nil, err } return newFile(fs, f, filename), nil } func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } f, err := fs.underlying.OpenFile(fullpath, flag, mode) if err != nil { return nil, err } return newFile(fs, f, filename), nil } func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } return fs.underlying.Stat(fullpath) } func (fs *ChrootHelper) Rename(from, to string) error { var err error from, err = fs.underlyingPath(from) if err != nil { return err } to, err = fs.underlyingPath(to) if err != nil { return err } return fs.underlying.Rename(from, to) } func (fs *ChrootHelper) Remove(path string) error { fullpath, err := fs.underlyingPath(path) if err != nil { return err } return fs.underlying.Remove(fullpath) } func (fs *ChrootHelper) Join(elem ...string) string { return fs.underlying.Join(elem...) } func (fs *ChrootHelper) TempFile(dir, prefix string) (billy.File, error) { fullpath, err := fs.underlyingPath(dir) if err != nil { return nil, err } f, err := fs.underlying.(billy.TempFile).TempFile(fullpath, prefix) if err != nil { return nil, err } return newFile(fs, f, fs.Join(dir, filepath.Base(f.Name()))), nil } func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) { fullpath, err := fs.underlyingPath(path) if err != nil { return nil, err } return fs.underlying.(billy.Dir).ReadDir(fullpath) } func (fs *ChrootHelper) MkdirAll(filename string, perm os.FileMode) error { fullpath, err := fs.underlyingPath(filename) if err != nil { return err } return fs.underlying.(billy.Dir).MkdirAll(fullpath, perm) } func (fs *ChrootHelper) Lstat(filename string) (os.FileInfo, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } return fs.underlying.(billy.Symlink).Lstat(fullpath) } func (fs *ChrootHelper) Symlink(target, link string) error { target = filepath.FromSlash(target) // only rewrite target if it's already absolute if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) { target = fs.Join(fs.Root(), target) target = filepath.Clean(filepath.FromSlash(target)) } link, err := fs.underlyingPath(link) if err != nil { return err } return fs.underlying.(billy.Symlink).Symlink(target, link) } func (fs *ChrootHelper) Readlink(link string) (string, error) { fullpath, err := fs.underlyingPath(link) if err != nil { return "", err } target, err := fs.underlying.(billy.Symlink).Readlink(fullpath) if err != nil { return "", err } if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) { return target, nil } target, err = filepath.Rel(fs.base, target) if err != nil { return "", err } return string(os.PathSeparator) + target, nil } func (fs *ChrootHelper) Chroot(path string) (billy.Filesystem, error) { fullpath, err := fs.underlyingPath(path) if err != nil { return nil, err } return New(fs.underlying, fullpath), nil } func (fs *ChrootHelper) Root() string { return fs.base } func (fs *ChrootHelper) Underlying() billy.Basic { return fs.underlying } // Capabilities implements the Capable interface. func (fs *ChrootHelper) Capabilities() billy.Capability { return billy.Capabilities(fs.underlying) } type file struct { billy.File name string } func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File { filename = fs.Join(fs.Root(), filename) filename, _ = filepath.Rel(fs.Root(), filename) return &file{ File: f, name: filename, } } func (f *file) Name() string { return f.name } go-billy-5.3.1/helper/chroot/chroot_test.go000066400000000000000000000206521404362761100207100ustar00rootroot00000000000000package chroot import ( "os" "path/filepath" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&ChrootSuite{}) type ChrootSuite struct{} func (s *ChrootSuite) TestCreate(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") f, err := fs.Create("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(m.CreateArgs, HasLen, 1) c.Assert(m.CreateArgs[0], Equals, "/foo/bar/qux") } func (s *ChrootSuite) TestCreateErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Create("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestLeadingPeriodsPathNotCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") f, err := fs.Create("..foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "..foo") } func (s *ChrootSuite) TestOpen(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") f, err := fs.Open("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(m.OpenArgs, HasLen, 1) c.Assert(m.OpenArgs[0], Equals, "/foo/bar/qux") } func (s *ChrootSuite) TestChroot(c *C) { m := &test.BasicMock{} fs, _ := New(m, "/foo").Chroot("baz") f, err := fs.Open("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(m.OpenArgs, HasLen, 1) c.Assert(m.OpenArgs[0], Equals, "/foo/baz/bar/qux") } func (s *ChrootSuite) TestChrootErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs, err := New(m, "/foo").Chroot("../qux") c.Assert(fs, IsNil) c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestOpenErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Open("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestOpenFile(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") f, err := fs.OpenFile("bar/qux", 42, 0777) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(m.OpenFileArgs, HasLen, 1) c.Assert(m.OpenFileArgs[0], Equals, [3]interface{}{"/foo/bar/qux", 42, os.FileMode(0777)}) } func (s *ChrootSuite) TestOpenFileErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.OpenFile("../foo", 42, 0777) c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestStat(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Stat("bar/qux") c.Assert(err, IsNil) c.Assert(m.StatArgs, HasLen, 1) c.Assert(m.StatArgs[0], Equals, "/foo/bar/qux") } func (s *ChrootSuite) TestStatErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Stat("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestRename(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Rename("bar/qux", "qux/bar") c.Assert(err, IsNil) c.Assert(m.RenameArgs, HasLen, 1) c.Assert(m.RenameArgs[0], Equals, [2]string{"/foo/bar/qux", "/foo/qux/bar"}) } func (s *ChrootSuite) TestRenameErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Rename("../foo", "bar") c.Assert(err, Equals, billy.ErrCrossedBoundary) err = fs.Rename("foo", "../bar") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestRemove(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Remove("bar/qux") c.Assert(err, IsNil) c.Assert(m.RemoveArgs, HasLen, 1) c.Assert(m.RemoveArgs[0], Equals, "/foo/bar/qux") } func (s *ChrootSuite) TestRemoveErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Remove("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestTempFile(c *C) { m := &test.TempFileMock{} fs := New(m, "/foo") _, err := fs.TempFile("bar", "qux") c.Assert(err, IsNil) c.Assert(m.TempFileArgs, HasLen, 1) c.Assert(m.TempFileArgs[0], Equals, [2]string{"/foo/bar", "qux"}) } func (s *ChrootSuite) TestTempFileErrCrossedBoundary(c *C) { m := &test.TempFileMock{} fs := New(m, "/foo") _, err := fs.TempFile("../foo", "qux") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestTempFileWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.TempFile("", "") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestReadDir(c *C) { m := &test.DirMock{} fs := New(m, "/foo") _, err := fs.ReadDir("bar") c.Assert(err, IsNil) c.Assert(m.ReadDirArgs, HasLen, 1) c.Assert(m.ReadDirArgs[0], Equals, "/foo/bar") } func (s *ChrootSuite) TestReadDirErrCrossedBoundary(c *C) { m := &test.DirMock{} fs := New(m, "/foo") _, err := fs.ReadDir("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestReadDirWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.ReadDir("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestMkDirAll(c *C) { m := &test.DirMock{} fs := New(m, "/foo") err := fs.MkdirAll("bar", 0777) c.Assert(err, IsNil) c.Assert(m.MkdirAllArgs, HasLen, 1) c.Assert(m.MkdirAllArgs[0], Equals, [2]interface{}{"/foo/bar", os.FileMode(0777)}) } func (s *ChrootSuite) TestMkdirAllErrCrossedBoundary(c *C) { m := &test.DirMock{} fs := New(m, "/foo") err := fs.MkdirAll("../foo", 0777) c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestMkdirAllWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.MkdirAll("", 0) c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestLstat(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") _, err := fs.Lstat("qux") c.Assert(err, IsNil) c.Assert(m.LstatArgs, HasLen, 1) c.Assert(m.LstatArgs[0], Equals, "/foo/qux") } func (s *ChrootSuite) TestLstatErrCrossedBoundary(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") _, err := fs.Lstat("../qux") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestLstatWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Lstat("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestSymlink(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") err := fs.Symlink("../baz", "qux/bar") c.Assert(err, IsNil) c.Assert(m.SymlinkArgs, HasLen, 1) c.Assert(m.SymlinkArgs[0], Equals, [2]string{filepath.FromSlash("../baz"), "/foo/qux/bar"}) } func (s *ChrootSuite) TestSymlinkWithAbsoluteTarget(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") err := fs.Symlink("/bar", "qux/baz") c.Assert(err, IsNil) c.Assert(m.SymlinkArgs, HasLen, 1) c.Assert(m.SymlinkArgs[0], Equals, [2]string{filepath.FromSlash("/foo/bar"), "/foo/qux/baz"}) } func (s *ChrootSuite) TestSymlinkErrCrossedBoundary(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") err := fs.Symlink("qux", "../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestSymlinkWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Symlink("qux", "bar") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestReadlink(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") link, err := fs.Readlink("/qux") c.Assert(err, IsNil) c.Assert(link, Equals, filepath.FromSlash("/qux")) c.Assert(m.ReadlinkArgs, HasLen, 1) c.Assert(m.ReadlinkArgs[0], Equals, "/foo/qux") } func (s *ChrootSuite) TestReadlinkWithRelative(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") link, err := fs.Readlink("qux/bar") c.Assert(err, IsNil) c.Assert(link, Equals, filepath.FromSlash("/qux/bar")) c.Assert(m.ReadlinkArgs, HasLen, 1) c.Assert(m.ReadlinkArgs[0], Equals, "/foo/qux/bar") } func (s *ChrootSuite) TestReadlinkErrCrossedBoundary(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") _, err := fs.Readlink("../qux") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestReadlinkWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Readlink("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestCapabilities(c *C) { testCapabilities(c, new(test.BasicMock)) testCapabilities(c, new(test.OnlyReadCapFs)) testCapabilities(c, new(test.NoLockCapFs)) } func testCapabilities(c *C, basic billy.Basic) { baseCapabilities := billy.Capabilities(basic) fs := New(basic, "/foo") capabilities := billy.Capabilities(fs) c.Assert(capabilities, Equals, baseCapabilities) } go-billy-5.3.1/helper/mount/000077500000000000000000000000001404362761100156635ustar00rootroot00000000000000go-billy-5.3.1/helper/mount/mount.go000066400000000000000000000133431404362761100173600ustar00rootroot00000000000000package mount import ( "io" "os" "path/filepath" "strings" "fmt" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/polyfill" ) var separator = string(filepath.Separator) // Mount is a helper that allows to emulate the behavior of mount in memory. // Very usufull to create a temporal dir, on filesystem where is a performance // penalty in doing so. type Mount struct { underlying billy.Filesystem source billy.Filesystem mountpoint string } // New creates a new filesystem wrapping up 'fs' the intercepts all the calls // made to `mountpoint` path and redirecting it to `source` filesystem. func New(fs billy.Basic, mountpoint string, source billy.Basic) *Mount { return &Mount{ underlying: polyfill.New(fs), source: polyfill.New(source), mountpoint: cleanPath(mountpoint), } } func (h *Mount) Create(path string) (billy.File, error) { fs, fullpath := h.getBasicAndPath(path) if fullpath == "." { return nil, os.ErrInvalid } f, err := fs.Create(fullpath) return wrapFile(f, path), err } func (h *Mount) Open(path string) (billy.File, error) { fs, fullpath := h.getBasicAndPath(path) if fullpath == "." { return nil, os.ErrInvalid } f, err := fs.Open(fullpath) return wrapFile(f, path), err } func (h *Mount) OpenFile(path string, flag int, mode os.FileMode) (billy.File, error) { fs, fullpath := h.getBasicAndPath(path) if fullpath == "." { return nil, os.ErrInvalid } f, err := fs.OpenFile(fullpath, flag, mode) return wrapFile(f, path), err } func (h *Mount) Rename(from, to string) error { fromInSource := h.isMountpoint(from) toInSource := h.isMountpoint(to) var fromFS, toFS billy.Basic switch { case fromInSource && toInSource: from = h.mustRelToMountpoint(from) to = h.mustRelToMountpoint(to) return h.source.Rename(from, to) case !fromInSource && !toInSource: return h.underlying.Rename(from, to) case fromInSource && !toInSource: fromFS = h.source from = h.mustRelToMountpoint(from) toFS = h.underlying to = cleanPath(to) case !fromInSource && toInSource: fromFS = h.underlying from = cleanPath(from) toFS = h.source to = h.mustRelToMountpoint(to) } if err := copyPath(fromFS, toFS, from, to); err != nil { return err } return fromFS.Remove(from) } func (h *Mount) Stat(path string) (os.FileInfo, error) { fs, fullpath := h.getBasicAndPath(path) return fs.Stat(fullpath) } func (h *Mount) Remove(path string) error { fs, fullpath := h.getBasicAndPath(path) if fullpath == "." { return os.ErrInvalid } return fs.Remove(fullpath) } func (h *Mount) ReadDir(path string) ([]os.FileInfo, error) { fs, fullpath, err := h.getDirAndPath(path) if err != nil { return nil, err } return fs.ReadDir(fullpath) } func (h *Mount) MkdirAll(filename string, perm os.FileMode) error { fs, fullpath, err := h.getDirAndPath(filename) if err != nil { return err } return fs.MkdirAll(fullpath, perm) } func (h *Mount) Symlink(target, link string) error { fs, fullpath, err := h.getSymlinkAndPath(link) if err != nil { return err } resolved := filepath.Join(filepath.Dir(link), target) if h.isMountpoint(resolved) != h.isMountpoint(link) { return fmt.Errorf("invalid symlink, target is crossing filesystems") } return fs.Symlink(target, fullpath) } func (h *Mount) Join(elem ...string) string { return h.underlying.Join(elem...) } func (h *Mount) Readlink(link string) (string, error) { fs, fullpath, err := h.getSymlinkAndPath(link) if err != nil { return "", err } return fs.Readlink(fullpath) } func (h *Mount) Lstat(path string) (os.FileInfo, error) { fs, fullpath, err := h.getSymlinkAndPath(path) if err != nil { return nil, err } return fs.Lstat(fullpath) } func (h *Mount) Underlying() billy.Basic { return h.underlying } // Capabilities implements the Capable interface. func (fs *Mount) Capabilities() billy.Capability { return billy.Capabilities(fs.underlying) & billy.Capabilities(fs.source) } func (fs *Mount) getBasicAndPath(path string) (billy.Basic, string) { path = cleanPath(path) if !fs.isMountpoint(path) { return fs.underlying, path } return fs.source, fs.mustRelToMountpoint(path) } func (fs *Mount) getDirAndPath(path string) (billy.Dir, string, error) { path = cleanPath(path) if !fs.isMountpoint(path) { return fs.underlying.(billy.Dir), path, nil } return fs.source.(billy.Dir), fs.mustRelToMountpoint(path), nil } func (fs *Mount) getSymlinkAndPath(path string) (billy.Symlink, string, error) { path = cleanPath(path) if !fs.isMountpoint(path) { return fs.underlying.(billy.Symlink), path, nil } return fs.source.(billy.Symlink), fs.mustRelToMountpoint(path), nil } func (fs *Mount) mustRelToMountpoint(path string) string { path = cleanPath(path) fullpath, err := filepath.Rel(fs.mountpoint, path) if err != nil { panic(err) } return fullpath } func (fs *Mount) isMountpoint(path string) bool { path = cleanPath(path) return strings.HasPrefix(path, fs.mountpoint) } func cleanPath(path string) string { path = filepath.FromSlash(path) rel, err := filepath.Rel(separator, path) if err == nil { path = rel } return filepath.Clean(path) } // copyPath copies a file across filesystems. func copyPath(src, dst billy.Basic, srcPath, dstPath string) error { dstFile, err := dst.Create(dstPath) if err != nil { return err } srcFile, err := src.Open(srcPath) if err != nil { return err } _, err = io.Copy(dstFile, srcFile) if err != nil { return err } err = dstFile.Close() if err != nil { _ = srcFile.Close() return err } return srcFile.Close() } type file struct { billy.File name string } func wrapFile(f billy.File, filename string) billy.File { if f == nil { return nil } return &file{ File: f, name: cleanPath(filename), } } func (f *file) Name() string { return f.name } go-billy-5.3.1/helper/mount/mount_test.go000066400000000000000000000247751404362761100204320ustar00rootroot00000000000000package mount import ( "os" "path/filepath" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/test" "github.com/go-git/go-billy/v5/util" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&MountSuite{}) type MountSuite struct { Helper *Mount Underlying mock Source mock } type mock struct { test.BasicMock test.DirMock test.SymlinkMock } func (s *MountSuite) SetUpTest(c *C) { s.Underlying.BasicMock = test.BasicMock{} s.Underlying.DirMock = test.DirMock{} s.Underlying.SymlinkMock = test.SymlinkMock{} s.Source.BasicMock = test.BasicMock{} s.Source.DirMock = test.DirMock{} s.Source.SymlinkMock = test.SymlinkMock{} s.Helper = New(&s.Underlying, "/foo", &s.Source) } func (s *MountSuite) TestCreate(c *C) { f, err := s.Helper.Create("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(s.Underlying.CreateArgs, HasLen, 1) c.Assert(s.Underlying.CreateArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.CreateArgs, HasLen, 0) } func (s *MountSuite) TestCreateMountPoint(c *C) { f, err := s.Helper.Create("foo") c.Assert(f, IsNil) c.Assert(err, Equals, os.ErrInvalid) } func (s *MountSuite) TestCreateInMount(c *C) { f, err := s.Helper.Create("foo/bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("foo", "bar", "qux")) c.Assert(s.Underlying.CreateArgs, HasLen, 0) c.Assert(s.Source.CreateArgs, HasLen, 1) c.Assert(s.Source.CreateArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestOpen(c *C) { f, err := s.Helper.Open("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(s.Underlying.OpenArgs, HasLen, 1) c.Assert(s.Underlying.OpenArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.OpenArgs, HasLen, 0) } func (s *MountSuite) TestOpenMountPoint(c *C) { f, err := s.Helper.Open("foo") c.Assert(f, IsNil) c.Assert(err, Equals, os.ErrInvalid) } func (s *MountSuite) TestOpenInMount(c *C) { f, err := s.Helper.Open("foo/bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("foo", "bar", "qux")) c.Assert(s.Underlying.OpenArgs, HasLen, 0) c.Assert(s.Source.OpenArgs, HasLen, 1) c.Assert(s.Source.OpenArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestOpenFile(c *C) { f, err := s.Helper.OpenFile("bar/qux", 42, 0777) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(s.Underlying.OpenFileArgs, HasLen, 1) c.Assert(s.Underlying.OpenFileArgs[0], Equals, [3]interface{}{filepath.Join("bar", "qux"), 42, os.FileMode(0777)}) c.Assert(s.Source.OpenFileArgs, HasLen, 0) } func (s *MountSuite) TestOpenFileMountPoint(c *C) { f, err := s.Helper.OpenFile("foo", 42, 0777) c.Assert(f, IsNil) c.Assert(err, Equals, os.ErrInvalid) } func (s *MountSuite) TestOpenFileInMount(c *C) { f, err := s.Helper.OpenFile("foo/bar/qux", 42, 0777) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("foo", "bar", "qux")) c.Assert(s.Underlying.OpenFileArgs, HasLen, 0) c.Assert(s.Source.OpenFileArgs, HasLen, 1) c.Assert(s.Source.OpenFileArgs[0], Equals, [3]interface{}{filepath.Join("bar", "qux"), 42, os.FileMode(0777)}) } func (s *MountSuite) TestStat(c *C) { _, err := s.Helper.Stat("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.StatArgs, HasLen, 1) c.Assert(s.Underlying.StatArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.StatArgs, HasLen, 0) } func (s *MountSuite) TestStatInMount(c *C) { _, err := s.Helper.Stat("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.StatArgs, HasLen, 0) c.Assert(s.Source.StatArgs, HasLen, 1) c.Assert(s.Source.StatArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestRename(c *C) { err := s.Helper.Rename("bar/qux", "qux") c.Assert(err, IsNil) c.Assert(s.Underlying.RenameArgs, HasLen, 1) c.Assert(s.Underlying.RenameArgs[0], Equals, [2]string{"bar/qux", "qux"}) c.Assert(s.Source.RenameArgs, HasLen, 0) } func (s *MountSuite) TestRenameInMount(c *C) { err := s.Helper.Rename("foo/bar/qux", "foo/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.RenameArgs, HasLen, 0) c.Assert(s.Source.RenameArgs, HasLen, 1) c.Assert(s.Source.RenameArgs[0], Equals, [2]string{filepath.Join("bar", "qux"), "qux"}) } func (s *MountSuite) TestRenameCross(c *C) { underlying := memfs.New() source := memfs.New() util.WriteFile(underlying, "file", []byte("foo"), 0777) fs := New(underlying, "/foo", source) err := fs.Rename("file", "foo/file") c.Assert(err, IsNil) _, err = underlying.Stat("file") c.Assert(err, Equals, os.ErrNotExist) _, err = source.Stat("file") c.Assert(err, IsNil) err = fs.Rename("foo/file", "file") c.Assert(err, IsNil) _, err = underlying.Stat("file") c.Assert(err, IsNil) _, err = source.Stat("file") c.Assert(err, Equals, os.ErrNotExist) } func (s *MountSuite) TestRemove(c *C) { err := s.Helper.Remove("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.RemoveArgs, HasLen, 1) c.Assert(s.Underlying.RemoveArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.RemoveArgs, HasLen, 0) } func (s *MountSuite) TestRemoveMountPoint(c *C) { err := s.Helper.Remove("foo") c.Assert(err, Equals, os.ErrInvalid) } func (s *MountSuite) TestRemoveInMount(c *C) { err := s.Helper.Remove("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.RemoveArgs, HasLen, 0) c.Assert(s.Source.RemoveArgs, HasLen, 1) c.Assert(s.Source.RemoveArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestReadDir(c *C) { _, err := s.Helper.ReadDir("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.ReadDirArgs, HasLen, 1) c.Assert(s.Underlying.ReadDirArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.ReadDirArgs, HasLen, 0) } func (s *MountSuite) TestJoin(c *C) { s.Helper.Join("foo", "bar") c.Assert(s.Underlying.JoinArgs, HasLen, 1) c.Assert(s.Underlying.JoinArgs[0], DeepEquals, []string{"foo", "bar"}) c.Assert(s.Source.JoinArgs, HasLen, 0) } func (s *MountSuite) TestReadDirInMount(c *C) { _, err := s.Helper.ReadDir("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.ReadDirArgs, HasLen, 0) c.Assert(s.Source.ReadDirArgs, HasLen, 1) c.Assert(s.Source.ReadDirArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestMkdirAll(c *C) { err := s.Helper.MkdirAll("bar/qux", 0777) c.Assert(err, IsNil) c.Assert(s.Underlying.MkdirAllArgs, HasLen, 1) c.Assert(s.Underlying.MkdirAllArgs[0], Equals, [2]interface{}{filepath.Join("bar", "qux"), os.FileMode(0777)}) c.Assert(s.Source.MkdirAllArgs, HasLen, 0) } func (s *MountSuite) TestMkdirAllInMount(c *C) { err := s.Helper.MkdirAll("foo/bar/qux", 0777) c.Assert(err, IsNil) c.Assert(s.Underlying.MkdirAllArgs, HasLen, 0) c.Assert(s.Source.MkdirAllArgs, HasLen, 1) c.Assert(s.Source.MkdirAllArgs[0], Equals, [2]interface{}{filepath.Join("bar", "qux"), os.FileMode(0777)}) } func (s *MountSuite) TestLstat(c *C) { _, err := s.Helper.Lstat("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.LstatArgs, HasLen, 1) c.Assert(s.Underlying.LstatArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.LstatArgs, HasLen, 0) } func (s *MountSuite) TestLstatInMount(c *C) { _, err := s.Helper.Lstat("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.LstatArgs, HasLen, 0) c.Assert(s.Source.LstatArgs, HasLen, 1) c.Assert(s.Source.LstatArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestSymlink(c *C) { err := s.Helper.Symlink("../baz", "bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.SymlinkArgs, HasLen, 1) c.Assert(s.Underlying.SymlinkArgs[0], Equals, [2]string{"../baz", filepath.Join("bar", "qux")}) c.Assert(s.Source.SymlinkArgs, HasLen, 0) } func (s *MountSuite) TestSymlinkCrossMount(c *C) { err := s.Helper.Symlink("../foo", "bar/qux") c.Assert(err, NotNil) err = s.Helper.Symlink("../foo/qux", "bar/qux") c.Assert(err, NotNil) err = s.Helper.Symlink("../baz", "foo") c.Assert(err, NotNil) err = s.Helper.Symlink("../../../foo", "foo/bar/qux") c.Assert(err, NotNil) } func (s *MountSuite) TestSymlinkInMount(c *C) { err := s.Helper.Symlink("../baz", "foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.SymlinkArgs, HasLen, 0) c.Assert(s.Source.SymlinkArgs, HasLen, 1) c.Assert(s.Source.SymlinkArgs[0], Equals, [2]string{"../baz", filepath.Join("bar", "qux")}) } func (s *MountSuite) TestRadlink(c *C) { _, err := s.Helper.Readlink("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.ReadlinkArgs, HasLen, 1) c.Assert(s.Underlying.ReadlinkArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.ReadlinkArgs, HasLen, 0) } func (s *MountSuite) TestReadlinkInMount(c *C) { _, err := s.Helper.Readlink("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.ReadlinkArgs, HasLen, 0) c.Assert(s.Source.ReadlinkArgs, HasLen, 1) c.Assert(s.Source.ReadlinkArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestUnderlyingNotSupported(c *C) { h := New(&test.BasicMock{}, "/foo", &test.BasicMock{}) _, err := h.ReadDir("qux") c.Assert(err, Equals, billy.ErrNotSupported) _, err = h.Readlink("qux") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *MountSuite) TestSourceNotSupported(c *C) { h := New(&s.Underlying, "/foo", &test.BasicMock{}) _, err := h.ReadDir("foo") c.Assert(err, Equals, billy.ErrNotSupported) _, err = h.Readlink("foo") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *MountSuite) TestCapabilities(c *C) { testCapabilities(c, new(test.BasicMock), new(test.BasicMock)) testCapabilities(c, new(test.BasicMock), new(test.OnlyReadCapFs)) testCapabilities(c, new(test.BasicMock), new(test.NoLockCapFs)) testCapabilities(c, new(test.OnlyReadCapFs), new(test.BasicMock)) testCapabilities(c, new(test.OnlyReadCapFs), new(test.OnlyReadCapFs)) testCapabilities(c, new(test.OnlyReadCapFs), new(test.NoLockCapFs)) testCapabilities(c, new(test.NoLockCapFs), new(test.BasicMock)) testCapabilities(c, new(test.NoLockCapFs), new(test.OnlyReadCapFs)) testCapabilities(c, new(test.NoLockCapFs), new(test.NoLockCapFs)) } func testCapabilities(c *C, a, b billy.Basic) { aCapabilities := billy.Capabilities(a) bCapabilities := billy.Capabilities(b) fs := New(a, "/foo", b) capabilities := billy.Capabilities(fs) unionCapabilities := aCapabilities & bCapabilities c.Assert(capabilities, Equals, unionCapabilities) fs = New(b, "/foo", a) capabilities = billy.Capabilities(fs) unionCapabilities = aCapabilities & bCapabilities c.Assert(capabilities, Equals, unionCapabilities) } go-billy-5.3.1/helper/polyfill/000077500000000000000000000000001404362761100163535ustar00rootroot00000000000000go-billy-5.3.1/helper/polyfill/polyfill.go000066400000000000000000000043711404362761100205410ustar00rootroot00000000000000package polyfill import ( "os" "path/filepath" "github.com/go-git/go-billy/v5" ) // Polyfill is a helper that implements all missing method from billy.Filesystem. type Polyfill struct { billy.Basic c capabilities } type capabilities struct{ tempfile, dir, symlink, chroot bool } // New creates a new filesystem wrapping up 'fs' the intercepts all the calls // made and errors if fs doesn't implement any of the billy interfaces. func New(fs billy.Basic) billy.Filesystem { if original, ok := fs.(billy.Filesystem); ok { return original } h := &Polyfill{Basic: fs} _, h.c.tempfile = h.Basic.(billy.TempFile) _, h.c.dir = h.Basic.(billy.Dir) _, h.c.symlink = h.Basic.(billy.Symlink) _, h.c.chroot = h.Basic.(billy.Chroot) return h } func (h *Polyfill) TempFile(dir, prefix string) (billy.File, error) { if !h.c.tempfile { return nil, billy.ErrNotSupported } return h.Basic.(billy.TempFile).TempFile(dir, prefix) } func (h *Polyfill) ReadDir(path string) ([]os.FileInfo, error) { if !h.c.dir { return nil, billy.ErrNotSupported } return h.Basic.(billy.Dir).ReadDir(path) } func (h *Polyfill) MkdirAll(filename string, perm os.FileMode) error { if !h.c.dir { return billy.ErrNotSupported } return h.Basic.(billy.Dir).MkdirAll(filename, perm) } func (h *Polyfill) Symlink(target, link string) error { if !h.c.symlink { return billy.ErrNotSupported } return h.Basic.(billy.Symlink).Symlink(target, link) } func (h *Polyfill) Readlink(link string) (string, error) { if !h.c.symlink { return "", billy.ErrNotSupported } return h.Basic.(billy.Symlink).Readlink(link) } func (h *Polyfill) Lstat(path string) (os.FileInfo, error) { if !h.c.symlink { return nil, billy.ErrNotSupported } return h.Basic.(billy.Symlink).Lstat(path) } func (h *Polyfill) Chroot(path string) (billy.Filesystem, error) { if !h.c.chroot { return nil, billy.ErrNotSupported } return h.Basic.(billy.Chroot).Chroot(path) } func (h *Polyfill) Root() string { if !h.c.chroot { return string(filepath.Separator) } return h.Basic.(billy.Chroot).Root() } func (h *Polyfill) Underlying() billy.Basic { return h.Basic } // Capabilities implements the Capable interface. func (h *Polyfill) Capabilities() billy.Capability { return billy.Capabilities(h.Basic) } go-billy-5.3.1/helper/polyfill/polyfill_test.go000066400000000000000000000033601404362761100215750ustar00rootroot00000000000000package polyfill import ( "path/filepath" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&PolyfillSuite{}) type PolyfillSuite struct { Helper billy.Filesystem Underlying billy.Filesystem } func (s *PolyfillSuite) SetUpTest(c *C) { s.Helper = New(&test.BasicMock{}) } func (s *PolyfillSuite) TestTempFile(c *C) { _, err := s.Helper.TempFile("", "") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestReadDir(c *C) { _, err := s.Helper.ReadDir("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestMkdirAll(c *C) { err := s.Helper.MkdirAll("", 0) c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestSymlink(c *C) { err := s.Helper.Symlink("", "") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestReadlink(c *C) { _, err := s.Helper.Readlink("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestLstat(c *C) { _, err := s.Helper.Lstat("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestChroot(c *C) { _, err := s.Helper.Chroot("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestRoot(c *C) { c.Assert(s.Helper.Root(), Equals, string(filepath.Separator)) } func (s *PolyfillSuite) TestCapabilities(c *C) { testCapabilities(c, new(test.BasicMock)) testCapabilities(c, new(test.OnlyReadCapFs)) testCapabilities(c, new(test.NoLockCapFs)) } func testCapabilities(c *C, basic billy.Basic) { baseCapabilities := billy.Capabilities(basic) fs := New(basic) capabilities := billy.Capabilities(fs) c.Assert(capabilities, Equals, baseCapabilities) } go-billy-5.3.1/helper/temporal/000077500000000000000000000000001404362761100163445ustar00rootroot00000000000000go-billy-5.3.1/helper/temporal/temporal.go000066400000000000000000000013251404362761100205170ustar00rootroot00000000000000package temporal import ( "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // Temporal is a helper that implements billy.TempFile over any filesystem. type Temporal struct { billy.Filesystem defaultDir string } // New creates a new filesystem wrapping up 'fs' the intercepts the calls to // the TempFile method. The param defaultDir is used as default directory were // the tempfiles are created. func New(fs billy.Filesystem, defaultDir string) billy.Filesystem { return &Temporal{ Filesystem: fs, defaultDir: defaultDir, } } func (h *Temporal) TempFile(dir, prefix string) (billy.File, error) { if dir == "" { dir = h.defaultDir } return util.TempFile(h.Filesystem, dir, prefix) } go-billy-5.3.1/helper/temporal/temporal_test.go000066400000000000000000000012201404362761100215500ustar00rootroot00000000000000package temporal import ( "strings" "testing" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&TemporalSuite{}) type TemporalSuite struct { test.FilesystemSuite } func (s *TemporalSuite) SetUpTest(c *C) { fs := New(memfs.New(), "foo") s.FilesystemSuite = test.NewFilesystemSuite(fs) } func (s *TemporalSuite) TestTempFileDefaultPath(c *C) { fs := New(memfs.New(), "foo") f, err := fs.TempFile("", "bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(strings.HasPrefix(f.Name(), fs.Join("foo", "bar")), Equals, true) } go-billy-5.3.1/memfs/000077500000000000000000000000001404362761100143515ustar00rootroot00000000000000go-billy-5.3.1/memfs/memory.go000066400000000000000000000177121404362761100162200ustar00rootroot00000000000000// Package memfs provides a billy filesystem base on memory. package memfs // import "github.com/go-git/go-billy/v5/memfs" import ( "errors" "fmt" "io" "os" "path/filepath" "sort" "strings" "time" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/chroot" "github.com/go-git/go-billy/v5/util" ) const separator = filepath.Separator // Memory a very convenient filesystem based on memory files type Memory struct { s *storage tempCount int } //New returns a new Memory filesystem. func New() billy.Filesystem { fs := &Memory{s: newStorage()} return chroot.New(fs, string(separator)) } func (fs *Memory) Create(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) } func (fs *Memory) Open(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDONLY, 0) } func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { f, has := fs.s.Get(filename) if !has { if !isCreate(flag) { return nil, os.ErrNotExist } var err error f, err = fs.s.New(filename, perm, flag) if err != nil { return nil, err } } else { if isExclusive(flag) { return nil, os.ErrExist } if target, isLink := fs.resolveLink(filename, f); isLink { return fs.OpenFile(target, flag, perm) } } if f.mode.IsDir() { return nil, fmt.Errorf("cannot open directory: %s", filename) } return f.Duplicate(filename, perm, flag), nil } var errNotLink = errors.New("not a link") func (fs *Memory) resolveLink(fullpath string, f *file) (target string, isLink bool) { if !isSymlink(f.mode) { return fullpath, false } target = string(f.content.bytes) if !isAbs(target) { target = fs.Join(filepath.Dir(fullpath), target) } return target, true } // On Windows OS, IsAbs validates if a path is valid based on if stars with a // unit (eg.: `C:\`) to assert that is absolute, but in this mem implementation // any path starting by `separator` is also considered absolute. func isAbs(path string) bool { return filepath.IsAbs(path) || strings.HasPrefix(path, string(separator)) } func (fs *Memory) Stat(filename string) (os.FileInfo, error) { f, has := fs.s.Get(filename) if !has { return nil, os.ErrNotExist } fi, _ := f.Stat() var err error if target, isLink := fs.resolveLink(filename, f); isLink { fi, err = fs.Stat(target) if err != nil { return nil, err } } // the name of the file should always the name of the stated file, so we // overwrite the Stat returned from the storage with it, since the // filename may belong to a link. fi.(*fileInfo).name = filepath.Base(filename) return fi, nil } func (fs *Memory) Lstat(filename string) (os.FileInfo, error) { f, has := fs.s.Get(filename) if !has { return nil, os.ErrNotExist } return f.Stat() } type ByName []os.FileInfo func (a ByName) Len() int { return len(a) } func (a ByName) Less(i, j int) bool { return a[i].Name() < a[j].Name() } func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (fs *Memory) ReadDir(path string) ([]os.FileInfo, error) { if f, has := fs.s.Get(path); has { if target, isLink := fs.resolveLink(path, f); isLink { return fs.ReadDir(target) } } var entries []os.FileInfo for _, f := range fs.s.Children(path) { fi, _ := f.Stat() entries = append(entries, fi) } sort.Sort(ByName(entries)) return entries, nil } func (fs *Memory) MkdirAll(path string, perm os.FileMode) error { _, err := fs.s.New(path, perm|os.ModeDir, 0) return err } func (fs *Memory) TempFile(dir, prefix string) (billy.File, error) { return util.TempFile(fs, dir, prefix) } func (fs *Memory) getTempFilename(dir, prefix string) string { fs.tempCount++ filename := fmt.Sprintf("%s_%d_%d", prefix, fs.tempCount, time.Now().UnixNano()) return fs.Join(dir, filename) } func (fs *Memory) Rename(from, to string) error { return fs.s.Rename(from, to) } func (fs *Memory) Remove(filename string) error { return fs.s.Remove(filename) } func (fs *Memory) Join(elem ...string) string { return filepath.Join(elem...) } func (fs *Memory) Symlink(target, link string) error { _, err := fs.Stat(link) if err == nil { return os.ErrExist } if !os.IsNotExist(err) { return err } return util.WriteFile(fs, link, []byte(target), 0777|os.ModeSymlink) } func (fs *Memory) Readlink(link string) (string, error) { f, has := fs.s.Get(link) if !has { return "", os.ErrNotExist } if !isSymlink(f.mode) { return "", &os.PathError{ Op: "readlink", Path: link, Err: fmt.Errorf("not a symlink"), } } return string(f.content.bytes), nil } // Capabilities implements the Capable interface. func (fs *Memory) Capabilities() billy.Capability { return billy.WriteCapability | billy.ReadCapability | billy.ReadAndWriteCapability | billy.SeekCapability | billy.TruncateCapability } type file struct { name string content *content position int64 flag int mode os.FileMode isClosed bool } func (f *file) Name() string { return f.name } func (f *file) Read(b []byte) (int, error) { n, err := f.ReadAt(b, f.position) f.position += int64(n) if err == io.EOF && n != 0 { err = nil } return n, err } func (f *file) ReadAt(b []byte, off int64) (int, error) { if f.isClosed { return 0, os.ErrClosed } if !isReadAndWrite(f.flag) && !isReadOnly(f.flag) { return 0, errors.New("read not supported") } n, err := f.content.ReadAt(b, off) return n, err } func (f *file) Seek(offset int64, whence int) (int64, error) { if f.isClosed { return 0, os.ErrClosed } switch whence { case io.SeekCurrent: f.position += offset case io.SeekStart: f.position = offset case io.SeekEnd: f.position = int64(f.content.Len()) + offset } return f.position, nil } func (f *file) Write(p []byte) (int, error) { if f.isClosed { return 0, os.ErrClosed } if !isReadAndWrite(f.flag) && !isWriteOnly(f.flag) { return 0, errors.New("write not supported") } n, err := f.content.WriteAt(p, f.position) f.position += int64(n) return n, err } func (f *file) Close() error { if f.isClosed { return os.ErrClosed } f.isClosed = true return nil } func (f *file) Truncate(size int64) error { if size < int64(len(f.content.bytes)) { f.content.bytes = f.content.bytes[:size] } else if more := int(size) - len(f.content.bytes); more > 0 { f.content.bytes = append(f.content.bytes, make([]byte, more)...) } return nil } func (f *file) Duplicate(filename string, mode os.FileMode, flag int) billy.File { new := &file{ name: filename, content: f.content, mode: mode, flag: flag, } if isAppend(flag) { new.position = int64(new.content.Len()) } if isTruncate(flag) { new.content.Truncate() } return new } func (f *file) Stat() (os.FileInfo, error) { return &fileInfo{ name: f.Name(), mode: f.mode, size: f.content.Len(), }, nil } // Lock is a no-op in memfs. func (f *file) Lock() error { return nil } // Unlock is a no-op in memfs. func (f *file) Unlock() error { return nil } type fileInfo struct { name string size int mode os.FileMode } func (fi *fileInfo) Name() string { return fi.name } func (fi *fileInfo) Size() int64 { return int64(fi.size) } func (fi *fileInfo) Mode() os.FileMode { return fi.mode } func (*fileInfo) ModTime() time.Time { return time.Now() } func (fi *fileInfo) IsDir() bool { return fi.mode.IsDir() } func (*fileInfo) Sys() interface{} { return nil } func (c *content) Truncate() { c.bytes = make([]byte, 0) } func (c *content) Len() int { return len(c.bytes) } func isCreate(flag int) bool { return flag&os.O_CREATE != 0 } func isExclusive(flag int) bool { return flag&os.O_EXCL != 0 } func isAppend(flag int) bool { return flag&os.O_APPEND != 0 } func isTruncate(flag int) bool { return flag&os.O_TRUNC != 0 } func isReadAndWrite(flag int) bool { return flag&os.O_RDWR != 0 } func isReadOnly(flag int) bool { return flag == os.O_RDONLY } func isWriteOnly(flag int) bool { return flag&os.O_WRONLY != 0 } func isSymlink(m os.FileMode) bool { return m&os.ModeSymlink != 0 } go-billy-5.3.1/memfs/memory_test.go000066400000000000000000000032331404362761100172500ustar00rootroot00000000000000package memfs import ( "fmt" "io" "os" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } type MemorySuite struct { test.FilesystemSuite path string } var _ = Suite(&MemorySuite{}) func (s *MemorySuite) SetUpTest(c *C) { s.FilesystemSuite = test.NewFilesystemSuite(New()) } func (s *MemorySuite) TestCapabilities(c *C) { _, ok := s.FS.(billy.Capable) c.Assert(ok, Equals, true) caps := billy.Capabilities(s.FS) c.Assert(caps, Equals, billy.DefaultCapabilities&^billy.LockCapability) } func (s *MemorySuite) TestNegativeOffsets(c *C) { f, err := s.FS.Create("negative") c.Assert(err, IsNil) buf := make([]byte, 100) _, err = f.ReadAt(buf, -100) c.Assert(err, ErrorMatches, "readat negative: negative offset") _, err = f.Seek(-100, io.SeekCurrent) c.Assert(err, IsNil) _, err = f.Write(buf) c.Assert(err, ErrorMatches, "writeat negative: negative offset") } func (s *MemorySuite) TestExclusive(c *C) { f, err := s.FS.OpenFile("exclusive", os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) c.Assert(err, IsNil) fmt.Fprint(f, "mememememe") err = f.Close() c.Assert(err, IsNil) _, err = s.FS.OpenFile("exclusive", os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) c.Assert(err, ErrorMatches, os.ErrExist.Error()) } func (s *MemorySuite) TestOrder(c *C) { var err error files := []string{ "a", "b", "c", } for _, f := range files { _, err = s.FS.Create(f) c.Assert(err, IsNil) } attemps := 30 for n := 0; n < attemps; n++ { actual, err := s.FS.ReadDir("") c.Assert(err, IsNil) for i, f := range files { c.Assert(actual[i].Name(), Equals, f) } } } go-billy-5.3.1/memfs/storage.go000066400000000000000000000075711404362761100163560ustar00rootroot00000000000000package memfs import ( "errors" "fmt" "io" "os" "path/filepath" ) type storage struct { files map[string]*file children map[string]map[string]*file } func newStorage() *storage { return &storage{ files: make(map[string]*file, 0), children: make(map[string]map[string]*file, 0), } } func (s *storage) Has(path string) bool { path = clean(path) _, ok := s.files[path] return ok } func (s *storage) New(path string, mode os.FileMode, flag int) (*file, error) { path = clean(path) if s.Has(path) { if !s.MustGet(path).mode.IsDir() { return nil, fmt.Errorf("file already exists %q", path) } return nil, nil } name := filepath.Base(path) f := &file{ name: name, content: &content{name: name}, mode: mode, flag: flag, } s.files[path] = f s.createParent(path, mode, f) return f, nil } func (s *storage) createParent(path string, mode os.FileMode, f *file) error { base := filepath.Dir(path) base = clean(base) if f.Name() == string(separator) { return nil } if _, err := s.New(base, mode.Perm()|os.ModeDir, 0); err != nil { return err } if _, ok := s.children[base]; !ok { s.children[base] = make(map[string]*file, 0) } s.children[base][f.Name()] = f return nil } func (s *storage) Children(path string) []*file { path = clean(path) l := make([]*file, 0) for _, f := range s.children[path] { l = append(l, f) } return l } func (s *storage) MustGet(path string) *file { f, ok := s.Get(path) if !ok { panic(fmt.Errorf("couldn't find %q", path)) } return f } func (s *storage) Get(path string) (*file, bool) { path = clean(path) if !s.Has(path) { return nil, false } file, ok := s.files[path] return file, ok } func (s *storage) Rename(from, to string) error { from = clean(from) to = clean(to) if !s.Has(from) { return os.ErrNotExist } move := [][2]string{{from, to}} for pathFrom := range s.files { if pathFrom == from || !filepath.HasPrefix(pathFrom, from) { continue } rel, _ := filepath.Rel(from, pathFrom) pathTo := filepath.Join(to, rel) move = append(move, [2]string{pathFrom, pathTo}) } for _, ops := range move { from := ops[0] to := ops[1] if err := s.move(from, to); err != nil { return err } } return nil } func (s *storage) move(from, to string) error { s.files[to] = s.files[from] s.files[to].name = filepath.Base(to) s.children[to] = s.children[from] defer func() { delete(s.children, from) delete(s.files, from) delete(s.children[filepath.Dir(from)], filepath.Base(from)) }() return s.createParent(to, 0644, s.files[to]) } func (s *storage) Remove(path string) error { path = clean(path) f, has := s.Get(path) if !has { return os.ErrNotExist } if f.mode.IsDir() && len(s.children[path]) != 0 { return fmt.Errorf("dir: %s contains files", path) } base, file := filepath.Split(path) base = filepath.Clean(base) delete(s.children[base], file) delete(s.files, path) return nil } func clean(path string) string { return filepath.Clean(filepath.FromSlash(path)) } type content struct { name string bytes []byte } func (c *content) WriteAt(p []byte, off int64) (int, error) { if off < 0 { return 0, &os.PathError{ Op: "writeat", Path: c.name, Err: errors.New("negative offset"), } } prev := len(c.bytes) diff := int(off) - prev if diff > 0 { c.bytes = append(c.bytes, make([]byte, diff)...) } c.bytes = append(c.bytes[:off], p...) if len(c.bytes) < prev { c.bytes = c.bytes[:prev] } return len(p), nil } func (c *content) ReadAt(b []byte, off int64) (n int, err error) { if off < 0 { return 0, &os.PathError{ Op: "readat", Path: c.name, Err: errors.New("negative offset"), } } size := int64(len(c.bytes)) if off >= size { return 0, io.EOF } l := int64(len(b)) if off+l > size { l = size - off } btr := c.bytes[off : off+l] if len(btr) < len(b) { err = io.EOF } n = copy(b, btr) return } go-billy-5.3.1/osfs/000077500000000000000000000000001404362761100142145ustar00rootroot00000000000000go-billy-5.3.1/osfs/os.go000066400000000000000000000057311404362761100151720ustar00rootroot00000000000000// +build !js // Package osfs provides a billy filesystem for the OS. package osfs // import "github.com/go-git/go-billy/v5/osfs" import ( "io/ioutil" "os" "path/filepath" "sync" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/chroot" ) const ( defaultDirectoryMode = 0755 defaultCreateMode = 0666 ) // Default Filesystem representing the root of the os filesystem. var Default = &OS{} // OS is a filesystem based on the os filesystem. type OS struct{} // New returns a new OS filesystem. func New(baseDir string) billy.Filesystem { return chroot.New(Default, baseDir) } func (fs *OS) Create(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode) } func (fs *OS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { if flag&os.O_CREATE != 0 { if err := fs.createDir(filename); err != nil { return nil, err } } f, err := os.OpenFile(filename, flag, perm) if err != nil { return nil, err } return &file{File: f}, err } func (fs *OS) createDir(fullpath string) error { dir := filepath.Dir(fullpath) if dir != "." { if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil { return err } } return nil } func (fs *OS) ReadDir(path string) ([]os.FileInfo, error) { l, err := ioutil.ReadDir(path) if err != nil { return nil, err } var s = make([]os.FileInfo, len(l)) for i, f := range l { s[i] = f } return s, nil } func (fs *OS) Rename(from, to string) error { if err := fs.createDir(to); err != nil { return err } return rename(from, to) } func (fs *OS) MkdirAll(path string, perm os.FileMode) error { return os.MkdirAll(path, defaultDirectoryMode) } func (fs *OS) Open(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDONLY, 0) } func (fs *OS) Stat(filename string) (os.FileInfo, error) { return os.Stat(filename) } func (fs *OS) Remove(filename string) error { return os.Remove(filename) } func (fs *OS) TempFile(dir, prefix string) (billy.File, error) { if err := fs.createDir(dir + string(os.PathSeparator)); err != nil { return nil, err } f, err := ioutil.TempFile(dir, prefix) if err != nil { return nil, err } return &file{File: f}, nil } func (fs *OS) Join(elem ...string) string { return filepath.Join(elem...) } func (fs *OS) RemoveAll(path string) error { return os.RemoveAll(filepath.Clean(path)) } func (fs *OS) Lstat(filename string) (os.FileInfo, error) { return os.Lstat(filepath.Clean(filename)) } func (fs *OS) Symlink(target, link string) error { if err := fs.createDir(link); err != nil { return err } return os.Symlink(target, link) } func (fs *OS) Readlink(link string) (string, error) { return os.Readlink(link) } // Capabilities implements the Capable interface. func (fs *OS) Capabilities() billy.Capability { return billy.DefaultCapabilities } // file is a wrapper for an os.File which adds support for file locking. type file struct { *os.File m sync.Mutex } go-billy-5.3.1/osfs/os_js.go000066400000000000000000000007541404362761100156660ustar00rootroot00000000000000// +build js package osfs import ( "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/chroot" "github.com/go-git/go-billy/v5/memfs" ) // globalMemFs is the global memory fs var globalMemFs = memfs.New() // Default Filesystem representing the root of in-memory filesystem for a // js/wasm environment. var Default = memfs.New() // New returns a new OS filesystem. func New(baseDir string) billy.Filesystem { return chroot.New(Default, Default.Join("/", baseDir)) } go-billy-5.3.1/osfs/os_js_test.go000066400000000000000000000016241404362761100167220ustar00rootroot00000000000000// +build js package osfs import ( "fmt" "os" "path/filepath" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } type OSSuite struct { test.FilesystemSuite path string tempCounter int } var _ = Suite(&OSSuite{}) func (s *OSSuite) SetUpTest(c *C) { s.tempCounter++ s.path = fmt.Sprintf("test_%d", s.tempCounter) s.FilesystemSuite = test.NewFilesystemSuite(New(s.path)) } func (s *OSSuite) TestOpenDoesNotCreateDir(c *C) { _, err := s.FS.Open("dir/non-existent") c.Assert(err, NotNil) _, err = s.FS.Stat(filepath.Join(s.path, "dir")) c.Assert(os.IsNotExist(err), Equals, true) } func (s *OSSuite) TestCapabilities(c *C) { _, ok := s.FS.(billy.Capable) c.Assert(ok, Equals, true) caps := billy.Capabilities(s.FS) c.Assert(caps, Equals, billy.DefaultCapabilities&^billy.LockCapability) } go-billy-5.3.1/osfs/os_plan9.go000066400000000000000000000042471404362761100162760ustar00rootroot00000000000000// +build plan9 package osfs import ( "io" "os" "path/filepath" "syscall" ) func (f *file) Lock() error { // Plan 9 uses a mode bit instead of explicit lock/unlock syscalls. // // Per http://man.cat-v.org/plan_9/5/stat: “Exclusive use files may be open // for I/O by only one fid at a time across all clients of the server. If a // second open is attempted, it draws an error.” // // There is no obvious way to implement this function using the exclusive use bit. // See https://golang.org/src/cmd/go/internal/lockedfile/lockedfile_plan9.go // for how file locking is done by the go tool on Plan 9. return nil } func (f *file) Unlock() error { return nil } func rename(from, to string) error { // If from and to are in different directories, copy the file // since Plan 9 does not support cross-directory rename. if filepath.Dir(from) != filepath.Dir(to) { fi, err := os.Stat(from) if err != nil { return &os.LinkError{"rename", from, to, err} } if fi.Mode().IsDir() { return &os.LinkError{"rename", from, to, syscall.EISDIR} } fromFile, err := os.Open(from) if err != nil { return &os.LinkError{"rename", from, to, err} } toFile, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode()) if err != nil { return &os.LinkError{"rename", from, to, err} } _, err = io.Copy(toFile, fromFile) if err != nil { return &os.LinkError{"rename", from, to, err} } // Copy mtime and mode from original file. // We need only one syscall if we avoid os.Chmod and os.Chtimes. dir := fi.Sys().(*syscall.Dir) var d syscall.Dir d.Null() d.Mtime = dir.Mtime d.Mode = dir.Mode if err = dirwstat(to, &d); err != nil { return &os.LinkError{"rename", from, to, err} } // Remove original file. err = os.Remove(from) if err != nil { return &os.LinkError{"rename", from, to, err} } return nil } return os.Rename(from, to) } func dirwstat(name string, d *syscall.Dir) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { return &os.PathError{"dirwstat", name, err} } if err = syscall.Wstat(name, buf[:n]); err != nil { return &os.PathError{"dirwstat", name, err} } return nil } go-billy-5.3.1/osfs/os_posix.go000066400000000000000000000006101404362761100164030ustar00rootroot00000000000000// +build !plan9,!windows,!js package osfs import ( "os" "golang.org/x/sys/unix" ) func (f *file) Lock() error { f.m.Lock() defer f.m.Unlock() return unix.Flock(int(f.File.Fd()), unix.LOCK_EX) } func (f *file) Unlock() error { f.m.Lock() defer f.m.Unlock() return unix.Flock(int(f.File.Fd()), unix.LOCK_UN) } func rename(from, to string) error { return os.Rename(from, to) } go-billy-5.3.1/osfs/os_test.go000066400000000000000000000025321404362761100162250ustar00rootroot00000000000000// +build !js package osfs import ( "io/ioutil" "os" "path/filepath" "runtime" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } type OSSuite struct { test.FilesystemSuite path string } var _ = Suite(&OSSuite{}) func (s *OSSuite) SetUpTest(c *C) { s.path, _ = ioutil.TempDir(os.TempDir(), "go-billy-osfs-test") if runtime.GOOS == "plan9" { // On Plan 9, permission mode of newly created files // or directories are based on the permission mode of // the containing directory (see http://man.cat-v.org/plan_9/5/open). // Since TestOpenFileWithModes and TestStat creates files directly // in the temporary directory, we need to make it more permissive. c.Assert(os.Chmod(s.path, 0777), IsNil) } s.FilesystemSuite = test.NewFilesystemSuite(New(s.path)) } func (s *OSSuite) TearDownTest(c *C) { err := os.RemoveAll(s.path) c.Assert(err, IsNil) } func (s *OSSuite) TestOpenDoesNotCreateDir(c *C) { _, err := s.FS.Open("dir/non-existent") c.Assert(err, NotNil) _, err = os.Stat(filepath.Join(s.path, "dir")) c.Assert(os.IsNotExist(err), Equals, true) } func (s *OSSuite) TestCapabilities(c *C) { _, ok := s.FS.(billy.Capable) c.Assert(ok, Equals, true) caps := billy.Capabilities(s.FS) c.Assert(caps, Equals, billy.AllCapabilities) } go-billy-5.3.1/osfs/os_windows.go000066400000000000000000000020761404362761100167430ustar00rootroot00000000000000// +build windows package osfs import ( "os" "runtime" "unsafe" "golang.org/x/sys/windows" ) type fileInfo struct { os.FileInfo name string } func (fi *fileInfo) Name() string { return fi.name } var ( kernel32DLL = windows.NewLazySystemDLL("kernel32.dll") lockFileExProc = kernel32DLL.NewProc("LockFileEx") unlockFileProc = kernel32DLL.NewProc("UnlockFile") ) const ( lockfileExclusiveLock = 0x2 ) func (f *file) Lock() error { f.m.Lock() defer f.m.Unlock() var overlapped windows.Overlapped // err is always non-nil as per sys/windows semantics. ret, _, err := lockFileExProc.Call(f.File.Fd(), lockfileExclusiveLock, 0, 0xFFFFFFFF, 0, uintptr(unsafe.Pointer(&overlapped))) runtime.KeepAlive(&overlapped) if ret == 0 { return err } return nil } func (f *file) Unlock() error { f.m.Lock() defer f.m.Unlock() // err is always non-nil as per sys/windows semantics. ret, _, err := unlockFileProc.Call(f.File.Fd(), 0, 0, 0xFFFFFFFF, 0) if ret == 0 { return err } return nil } func rename(from, to string) error { return os.Rename(from, to) } go-billy-5.3.1/test/000077500000000000000000000000001404362761100142215ustar00rootroot00000000000000go-billy-5.3.1/test/basic.go000066400000000000000000000326071404362761100156410ustar00rootroot00000000000000package test import ( "bytes" "fmt" "io" "io/ioutil" "os" "path/filepath" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // BasicSuite is a convenient test suite to validate any implementation of // billy.Basic type BasicSuite struct { FS Basic } func (s *BasicSuite) TestCreate(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestCreateDepth(c *C) { f, err := s.FS.Create("bar/foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, s.FS.Join("bar", "foo")) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestCreateDepthAbsolute(c *C) { f, err := s.FS.Create("/bar/foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, s.FS.Join("bar", "foo")) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestCreateOverwrite(c *C) { for i := 0; i < 3; i++ { f, err := s.FS.Create("foo") c.Assert(err, IsNil) l, err := f.Write([]byte(fmt.Sprintf("foo%d", i))) c.Assert(err, IsNil) c.Assert(l, Equals, 4) err = f.Close() c.Assert(err, IsNil) } f, err := s.FS.Open("foo") c.Assert(err, IsNil) wrote, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(wrote), DeepEquals, "foo2") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestCreateAndClose(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) _, err = f.Write([]byte("foo")) c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) f, err = s.FS.Open(f.Name()) c.Assert(err, IsNil) wrote, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(wrote), DeepEquals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestOpen(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo") c.Assert(f.Close(), IsNil) f, err = s.FS.Open("foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestOpenNotExists(c *C) { f, err := s.FS.Open("not-exists") c.Assert(err, NotNil) c.Assert(f, IsNil) } func (s *BasicSuite) TestOpenFile(c *C) { defaultMode := os.FileMode(0666) f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode) c.Assert(err, IsNil) s.testWriteClose(c, f, "foo1") // Truncate if it exists f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "foo1overwritten") // Read-only if it exists f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testReadClose(c, f, "foo1overwritten") // Create when it does exist f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "bar") f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) s.testReadClose(c, f, "bar") } func (s *BasicSuite) TestOpenFileNoTruncate(c *C) { defaultMode := os.FileMode(0666) // Create when it does not exist f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "foo1") f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) s.testReadClose(c, f, "foo1") // Create when it does exist f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "bar") f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) s.testReadClose(c, f, "bar1") } func (s *BasicSuite) TestOpenFileAppend(c *C) { defaultMode := os.FileMode(0666) f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_APPEND, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "foo1") f, err = s.FS.OpenFile("foo1", os.O_WRONLY|os.O_APPEND, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "bar1") f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) s.testReadClose(c, f, "foo1bar1") } func (s *BasicSuite) TestOpenFileReadWrite(c *C) { defaultMode := os.FileMode(0666) f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_TRUNC|os.O_RDWR, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") written, err := f.Write([]byte("foobar")) c.Assert(written, Equals, 6) c.Assert(err, IsNil) _, err = f.Seek(0, os.SEEK_SET) c.Assert(err, IsNil) written, err = f.Write([]byte("qux")) c.Assert(written, Equals, 3) c.Assert(err, IsNil) _, err = f.Seek(0, os.SEEK_SET) c.Assert(err, IsNil) s.testReadClose(c, f, "quxbar") } func (s *BasicSuite) TestOpenFileWithModes(c *C) { f, err := s.FS.OpenFile("foo", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, customMode) c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) fi, err := s.FS.Stat("foo") c.Assert(err, IsNil) c.Assert(fi.Mode(), Equals, os.FileMode(customMode)) } func (s *BasicSuite) testWriteClose(c *C, f File, content string) { written, err := f.Write([]byte(content)) c.Assert(written, Equals, len(content)) c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) testReadClose(c *C, f File, content string) { read, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(read), Equals, content) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestFileWrite(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) n, err := f.Write([]byte("foo")) c.Assert(err, IsNil) c.Assert(n, Equals, 3) f.Seek(0, io.SeekStart) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(all), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestFileWriteClose(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) _, err = f.Write([]byte("foo")) c.Assert(err, NotNil) } func (s *BasicSuite) TestFileRead(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(all), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestFileClosed(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) _, err = ioutil.ReadAll(f) c.Assert(err, NotNil) } func (s *BasicSuite) TestFileNonRead(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) f, err := s.FS.OpenFile("foo", os.O_WRONLY, 0) c.Assert(err, IsNil) _, err = ioutil.ReadAll(f) c.Assert(err, NotNil) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestFileSeekstart(c *C) { s.testFileSeek(c, 10, io.SeekStart) } func (s *BasicSuite) TestFileSeekCurrent(c *C) { s.testFileSeek(c, 5, io.SeekCurrent) } func (s *BasicSuite) TestFileSeekEnd(c *C) { s.testFileSeek(c, -26, io.SeekEnd) } func (s *BasicSuite) testFileSeek(c *C, offset int64, whence int) { err := util.WriteFile(s.FS, "foo", []byte("0123456789abcdefghijklmnopqrstuvwxyz"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) some := make([]byte, 5) _, err = f.Read(some) c.Assert(err, IsNil) c.Assert(string(some), Equals, "01234") p, err := f.Seek(offset, whence) c.Assert(err, IsNil) c.Assert(int(p), Equals, 10) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(all, HasLen, 26) c.Assert(string(all), Equals, "abcdefghijklmnopqrstuvwxyz") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestSeekToEndAndWrite(c *C) { defaultMode := os.FileMode(0666) f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_TRUNC|os.O_RDWR, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") _, err = f.Seek(10, io.SeekEnd) c.Assert(err, IsNil) n, err := f.Write([]byte(`TEST`)) c.Assert(err, IsNil) c.Assert(n, Equals, 4) _, err = f.Seek(0, io.SeekStart) c.Assert(err, IsNil) s.testReadClose(c, f, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TEST") } func (s *BasicSuite) TestFileSeekClosed(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) _, err = f.Seek(0, 0) c.Assert(err, NotNil) } func (s *BasicSuite) TestFileCloseTwice(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(f.Close(), NotNil) } func (s *BasicSuite) TestStat(c *C) { util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) fi, err := s.FS.Stat("foo/bar") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "bar") c.Assert(fi.Size(), Equals, int64(3)) c.Assert(fi.Mode(), Equals, customMode) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, false) } func (s *BasicSuite) TestStatNonExistent(c *C) { fi, err := s.FS.Stat("non-existent") comment := Commentf("error: %s", err) c.Assert(os.IsNotExist(err), Equals, true, comment) c.Assert(fi, IsNil) } func (s *BasicSuite) TestRename(c *C) { err := util.WriteFile(s.FS, "foo", nil, 0644) c.Assert(err, IsNil) err = s.FS.Rename("foo", "bar") c.Assert(err, IsNil) foo, err := s.FS.Stat("foo") c.Assert(foo, IsNil) c.Assert(os.IsNotExist(err), Equals, true) bar, err := s.FS.Stat("bar") c.Assert(err, IsNil) c.Assert(bar, NotNil) } func (s *BasicSuite) TestOpenAndWrite(c *C) { err := util.WriteFile(s.FS, "foo", nil, 0644) c.Assert(err, IsNil) foo, err := s.FS.Open("foo") c.Assert(foo, NotNil) c.Assert(err, IsNil) n, err := foo.Write([]byte("foo")) c.Assert(err, NotNil) c.Assert(n, Equals, 0) c.Assert(foo.Close(), IsNil) } func (s *BasicSuite) TestOpenAndStat(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) foo, err := s.FS.Open("foo") c.Assert(foo, NotNil) c.Assert(foo.Name(), Equals, "foo") c.Assert(err, IsNil) c.Assert(foo.Close(), IsNil) stat, err := s.FS.Stat("foo") c.Assert(stat, NotNil) c.Assert(err, IsNil) c.Assert(stat.Name(), Equals, "foo") c.Assert(stat.Size(), Equals, int64(3)) } func (s *BasicSuite) TestRemove(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) err = s.FS.Remove("foo") c.Assert(err, IsNil) } func (s *BasicSuite) TestRemoveNonExisting(c *C) { err := s.FS.Remove("NON-EXISTING") c.Assert(err, NotNil) c.Assert(os.IsNotExist(err), Equals, true) } func (s *BasicSuite) TestRemoveNotEmptyDir(c *C) { err := util.WriteFile(s.FS, "foo", nil, 0644) c.Assert(err, IsNil) err = s.FS.Remove("no-exists") c.Assert(err, NotNil) } func (s *BasicSuite) TestJoin(c *C) { c.Assert(s.FS.Join("foo", "bar"), Equals, fmt.Sprintf("foo%cbar", filepath.Separator)) } func (s *BasicSuite) TestReadAtOnReadWrite(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) _, err = f.Write([]byte("abcdefg")) c.Assert(err, IsNil) rf, ok := f.(io.ReaderAt) c.Assert(ok, Equals, true) b := make([]byte, 3) n, err := rf.ReadAt(b, 2) c.Assert(err, IsNil) c.Assert(n, Equals, 3) c.Assert(string(b), Equals, "cde") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestReadAtOnReadOnly(c *C) { err := util.WriteFile(s.FS, "foo", []byte("abcdefg"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) rf, ok := f.(io.ReaderAt) c.Assert(ok, Equals, true) b := make([]byte, 3) n, err := rf.ReadAt(b, 2) c.Assert(err, IsNil) c.Assert(n, Equals, 3) c.Assert(string(b), Equals, "cde") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestReadAtEOF(c *C) { err := util.WriteFile(s.FS, "foo", []byte("TEST"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) b := make([]byte, 5) n, err := f.ReadAt(b, 0) c.Assert(err, Equals, io.EOF) c.Assert(n, Equals, 4) c.Assert(string(b), Equals, "TEST\x00") err = f.Close() c.Assert(err, IsNil) } func (s *BasicSuite) TestReadAtOffset(c *C) { err := util.WriteFile(s.FS, "foo", []byte("TEST"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) rf, ok := f.(io.ReaderAt) c.Assert(ok, Equals, true) o, err := f.Seek(0, io.SeekCurrent) c.Assert(err, IsNil) c.Assert(o, Equals, int64(0)) b := make([]byte, 4) n, err := rf.ReadAt(b, 0) c.Assert(err, IsNil) c.Assert(n, Equals, 4) c.Assert(string(b), Equals, "TEST") o, err = f.Seek(0, io.SeekCurrent) c.Assert(err, IsNil) c.Assert(o, Equals, int64(0)) err = f.Close() c.Assert(err, IsNil) } func (s *BasicSuite) TestReadWriteLargeFile(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) size := 1 << 20 n, err := f.Write(bytes.Repeat([]byte("F"), size)) c.Assert(err, IsNil) c.Assert(n, Equals, size) c.Assert(f.Close(), IsNil) f, err = s.FS.Open("foo") c.Assert(err, IsNil) b, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(len(b), Equals, size) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestWriteFile(c *C) { err := util.WriteFile(s.FS, "foo", []byte("bar"), 0777) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) wrote, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(wrote), DeepEquals, "bar") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestTruncate(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) for _, sz := range []int64{4, 7, 2, 30, 0, 1} { err = f.Truncate(sz) c.Assert(err, IsNil) bs, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(len(bs), Equals, int(sz)) _, err = f.Seek(0, io.SeekStart) c.Assert(err, IsNil) } c.Assert(f.Close(), IsNil) } go-billy-5.3.1/test/chroot.go000066400000000000000000000056401404362761100160530ustar00rootroot00000000000000package test import ( "os" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // ChrootSuite is a convenient test suite to validate any implementation of // billy.Chroot type ChrootSuite struct { FS interface { Basic Chroot } } func (s *ChrootSuite) TestCreateWithChroot(c *C) { fs, _ := s.FS.Chroot("foo") f, err := fs.Create("bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(f.Name(), Equals, "bar") f, err = s.FS.Open("foo/bar") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, s.FS.Join("foo", "bar")) c.Assert(f.Close(), IsNil) } func (s *ChrootSuite) TestOpenWithChroot(c *C) { fs, _ := s.FS.Chroot("foo") f, err := fs.Create("bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(f.Name(), Equals, "bar") f, err = fs.Open("bar") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "bar") c.Assert(f.Close(), IsNil) } func (s *ChrootSuite) TestOpenOutOffBoundary(c *C) { err := util.WriteFile(s.FS, "bar", nil, 0644) c.Assert(err, IsNil) fs, _ := s.FS.Chroot("foo") f, err := fs.Open("../bar") c.Assert(err, Equals, ErrCrossedBoundary) c.Assert(f, IsNil) } func (s *ChrootSuite) TestStatOutOffBoundary(c *C) { err := util.WriteFile(s.FS, "bar", nil, 0644) c.Assert(err, IsNil) fs, _ := s.FS.Chroot("foo") f, err := fs.Stat("../bar") c.Assert(err, Equals, ErrCrossedBoundary) c.Assert(f, IsNil) } func (s *ChrootSuite) TestStatWithChroot(c *C) { files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } // Some implementations detect directories based on a prefix // for all files; it's easy to miss path separator handling there. fi, err := s.FS.Stat("qu") c.Assert(os.IsNotExist(err), Equals, true, Commentf("error: %s", err)) c.Assert(fi, IsNil) fi, err = s.FS.Stat("qux") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "qux") c.Assert(fi.IsDir(), Equals, true) qux, _ := s.FS.Chroot("qux") fi, err = qux.Stat("baz") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "baz") c.Assert(fi.IsDir(), Equals, false) fi, err = qux.Stat("/baz") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "baz") c.Assert(fi.IsDir(), Equals, false) } func (s *ChrootSuite) TestRenameOutOffBoundary(c *C) { err := util.WriteFile(s.FS, "foo/foo", nil, 0644) c.Assert(err, IsNil) err = util.WriteFile(s.FS, "bar", nil, 0644) c.Assert(err, IsNil) fs, _ := s.FS.Chroot("foo") err = fs.Rename("../bar", "foo") c.Assert(err, Equals, ErrCrossedBoundary) err = fs.Rename("foo", "../bar") c.Assert(err, Equals, ErrCrossedBoundary) } func (s *ChrootSuite) TestRemoveOutOffBoundary(c *C) { err := util.WriteFile(s.FS, "bar", nil, 0644) c.Assert(err, IsNil) fs, _ := s.FS.Chroot("foo") err = fs.Remove("../bar") c.Assert(err, Equals, ErrCrossedBoundary) } func (s *FilesystemSuite) TestRoot(c *C) { c.Assert(s.FS.Root(), Not(Equals), "") } go-billy-5.3.1/test/common_posix.go000066400000000000000000000002221404362761100172560ustar00rootroot00000000000000// +build !windows package test import "os" var ( customMode os.FileMode = 0755 expectedSymlinkTarget = "/dir/file" ) go-billy-5.3.1/test/common_windows.go000066400000000000000000000002231404362761100176070ustar00rootroot00000000000000// +build windows package test import "os" var ( customMode os.FileMode = 0666 expectedSymlinkTarget = "\\dir\\file" ) go-billy-5.3.1/test/dir.go000066400000000000000000000142731404362761100153350ustar00rootroot00000000000000package test import ( "os" "strconv" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // DirSuite is a convenient test suite to validate any implementation of // billy.Dir type DirSuite struct { FS interface { Basic Dir } } func (s *DirSuite) TestMkdirAll(c *C) { err := s.FS.MkdirAll("empty", os.FileMode(0755)) c.Assert(err, IsNil) fi, err := s.FS.Stat("empty") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) } func (s *DirSuite) TestMkdirAllNested(c *C) { err := s.FS.MkdirAll("foo/bar/baz", os.FileMode(0755)) c.Assert(err, IsNil) fi, err := s.FS.Stat("foo/bar/baz") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) fi, err = s.FS.Stat("foo/bar") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) fi, err = s.FS.Stat("foo") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) } func (s *DirSuite) TestMkdirAllIdempotent(c *C) { err := s.FS.MkdirAll("empty", 0755) c.Assert(err, IsNil) fi, err := s.FS.Stat("empty") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) // idempotent err = s.FS.MkdirAll("empty", 0755) c.Assert(err, IsNil) fi, err = s.FS.Stat("empty") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) } func (s *DirSuite) TestMkdirAllAndCreate(c *C) { err := s.FS.MkdirAll("dir", os.FileMode(0755)) c.Assert(err, IsNil) f, err := s.FS.Create("dir/bar/foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) fi, err := s.FS.Stat("dir/bar/foo") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, false) } func (s *DirSuite) TestMkdirAllWithExistingFile(c *C) { f, err := s.FS.Create("dir/foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) err = s.FS.MkdirAll("dir/foo", os.FileMode(0755)) c.Assert(err, NotNil) fi, err := s.FS.Stat("dir/foo") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, false) } func (s *DirSuite) TestStatDir(c *C) { s.FS.MkdirAll("foo/bar", 0755) fi, err := s.FS.Stat("foo/bar") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "bar") c.Assert(fi.Mode().IsDir(), Equals, true) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, true) } func (s *BasicSuite) TestStatDeep(c *C) { files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } // Some implementations detect directories based on a prefix // for all files; it's easy to miss path separator handling there. fi, err := s.FS.Stat("qu") c.Assert(os.IsNotExist(err), Equals, true, Commentf("error: %s", err)) c.Assert(fi, IsNil) fi, err = s.FS.Stat("qux") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "qux") c.Assert(fi.IsDir(), Equals, true) fi, err = s.FS.Stat("qux/baz") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "baz") c.Assert(fi.IsDir(), Equals, false) } func (s *DirSuite) TestReadDir(c *C) { files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } info, err := s.FS.ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 3) info, err = s.FS.ReadDir("/qux") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *DirSuite) TestReadDirNested(c *C) { max := 100 path := "/" for i := 0; i <= max; i++ { path = s.FS.Join(path, strconv.Itoa(i)) } files := []string{s.FS.Join(path, "f1"), s.FS.Join(path, "f2")} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } path = "/" for i := 0; i < max; i++ { path = s.FS.Join(path, strconv.Itoa(i)) info, err := s.FS.ReadDir(path) c.Assert(err, IsNil) c.Assert(info, HasLen, 1) } path = s.FS.Join(path, strconv.Itoa(max)) info, err := s.FS.ReadDir(path) c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *DirSuite) TestReadDirWithMkDirAll(c *C) { err := s.FS.MkdirAll("qux", 0755) c.Assert(err, IsNil) files := []string{"qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } info, err := s.FS.ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].IsDir(), Equals, true) info, err = s.FS.ReadDir("/qux") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *DirSuite) TestReadDirFileInfo(c *C) { err := util.WriteFile(s.FS, "foo", []byte{'F', 'O', 'O'}, 0644) c.Assert(err, IsNil) info, err := s.FS.ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].Size(), Equals, int64(3)) c.Assert(info[0].IsDir(), Equals, false) c.Assert(info[0].Name(), Equals, "foo") } func (s *DirSuite) TestReadDirFileInfoDirs(c *C) { files := []string{"qux/baz/foo"} for _, name := range files { err := util.WriteFile(s.FS, name, []byte{'F', 'O', 'O'}, 0644) c.Assert(err, IsNil) } info, err := s.FS.ReadDir("qux") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].IsDir(), Equals, true) c.Assert(info[0].Name(), Equals, "baz") info, err = s.FS.ReadDir("qux/baz") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].Size(), Equals, int64(3)) c.Assert(info[0].IsDir(), Equals, false) c.Assert(info[0].Name(), Equals, "foo") c.Assert(info[0].Mode(), Not(Equals), 0) } func (s *DirSuite) TestRenameToDir(c *C) { err := util.WriteFile(s.FS, "foo", nil, 0644) c.Assert(err, IsNil) err = s.FS.Rename("foo", "bar/qux") c.Assert(err, IsNil) old, err := s.FS.Stat("foo") c.Assert(old, IsNil) c.Assert(os.IsNotExist(err), Equals, true) dir, err := s.FS.Stat("bar") c.Assert(dir, NotNil) c.Assert(err, IsNil) file, err := s.FS.Stat("bar/qux") c.Assert(file.Name(), Equals, "qux") c.Assert(err, IsNil) } func (s *DirSuite) TestRenameDir(c *C) { err := s.FS.MkdirAll("foo", 0755) c.Assert(err, IsNil) err = util.WriteFile(s.FS, "foo/bar", nil, 0644) c.Assert(err, IsNil) err = s.FS.Rename("foo", "bar") c.Assert(err, IsNil) dirfoo, err := s.FS.Stat("foo") c.Assert(dirfoo, IsNil) c.Assert(os.IsNotExist(err), Equals, true) dirbar, err := s.FS.Stat("bar") c.Assert(err, IsNil) c.Assert(dirbar, NotNil) foo, err := s.FS.Stat("foo/bar") c.Assert(os.IsNotExist(err), Equals, true) c.Assert(foo, IsNil) bar, err := s.FS.Stat("bar/bar") c.Assert(err, IsNil) c.Assert(bar, NotNil) } go-billy-5.3.1/test/fs.go000066400000000000000000000121661404362761100151660ustar00rootroot00000000000000package test import ( "os" "runtime" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // FilesystemSuite is a convenient test suite to validate any implementation of // billy.Filesystem type FilesystemSuite struct { FS Filesystem BasicSuite DirSuite SymlinkSuite TempFileSuite ChrootSuite } // NewFilesystemSuite returns a new FilesystemSuite based on the given fs. func NewFilesystemSuite(fs Filesystem) FilesystemSuite { s := FilesystemSuite{FS: fs} s.BasicSuite.FS = s.FS s.DirSuite.FS = s.FS s.SymlinkSuite.FS = s.FS s.TempFileSuite.FS = s.FS s.ChrootSuite.FS = s.FS return s } func (s *FilesystemSuite) TestSymlinkToDir(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := s.FS.MkdirAll("dir", 0755) c.Assert(err, IsNil) err = s.FS.Symlink("dir", "link") c.Assert(err, IsNil) fi, err := s.FS.Stat("link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") c.Assert(fi.IsDir(), Equals, true) } func (s *FilesystemSuite) TestSymlinkReadDir(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("dir", "link") c.Assert(err, IsNil) info, err := s.FS.ReadDir("link") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].Size(), Equals, int64(3)) c.Assert(info[0].IsDir(), Equals, false) c.Assert(info[0].Name(), Equals, "file") } func (s *FilesystemSuite) TestCreateWithExistantDir(c *C) { err := s.FS.MkdirAll("foo", 0644) c.Assert(err, IsNil) f, err := s.FS.Create("foo") c.Assert(err, NotNil) c.Assert(f, IsNil) } func (s *ChrootSuite) TestReadDirWithChroot(c *C) { files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } qux, _ := s.FS.Chroot("/qux") info, err := qux.(Filesystem).ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *FilesystemSuite) TestSymlinkWithChrootBasic(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } qux, _ := s.FS.Chroot("/qux") err := util.WriteFile(qux, "file", nil, 0644) c.Assert(err, IsNil) err = qux.(Filesystem).Symlink("file", "link") c.Assert(err, IsNil) fi, err := qux.Stat("link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") fi, err = s.FS.Stat("qux/link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") } func (s *FilesystemSuite) TestSymlinkWithChrootCrossBounders(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } qux, _ := s.FS.Chroot("/qux") util.WriteFile(s.FS, "file", []byte("foo"), customMode) err := qux.Symlink("../../file", "qux/link") c.Assert(err, Equals, nil) fi, err := qux.Stat("qux/link") c.Assert(fi, NotNil) c.Assert(err, Equals, nil) } func (s *FilesystemSuite) TestReadDirWithLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) s.FS.Symlink("bar", "foo/qux") info, err := s.FS.ReadDir("/foo") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *FilesystemSuite) TestRemoveAllNonExistent(c *C) { c.Assert(util.RemoveAll(s.FS, "non-existent"), IsNil) } func (s *FilesystemSuite) TestRemoveAllEmptyDir(c *C) { c.Assert(s.FS.MkdirAll("empty", os.FileMode(0755)), IsNil) c.Assert(util.RemoveAll(s.FS, "empty"), IsNil) _, err := s.FS.Stat("empty") c.Assert(err, NotNil) c.Assert(os.IsNotExist(err), Equals, true) } func (s *FilesystemSuite) TestRemoveAll(c *C) { fnames := []string{ "foo/1", "foo/2", "foo/bar/1", "foo/bar/2", "foo/bar/baz/1", "foo/bar/baz/qux/1", "foo/bar/baz/qux/2", "foo/bar/baz/qux/3", } for _, fname := range fnames { err := util.WriteFile(s.FS, fname, nil, 0644) c.Assert(err, IsNil) } c.Assert(util.RemoveAll(s.FS, "foo"), IsNil) for _, fname := range fnames { _, err := s.FS.Stat(fname) comment := Commentf("not removed: %s %s", fname, err) c.Assert(os.IsNotExist(err), Equals, true, comment) } } func (s *FilesystemSuite) TestRemoveAllRelative(c *C) { fnames := []string{ "foo/1", "foo/2", "foo/bar/1", "foo/bar/2", "foo/bar/baz/1", "foo/bar/baz/qux/1", "foo/bar/baz/qux/2", "foo/bar/baz/qux/3", } for _, fname := range fnames { err := util.WriteFile(s.FS, fname, nil, 0644) c.Assert(err, IsNil) } c.Assert(util.RemoveAll(s.FS, "foo/bar/.."), IsNil) for _, fname := range fnames { _, err := s.FS.Stat(fname) comment := Commentf("not removed: %s %s", fname, err) c.Assert(os.IsNotExist(err), Equals, true, comment) } } func (s *FilesystemSuite) TestReadDir(c *C) { err := s.FS.MkdirAll("qux", 0755) c.Assert(err, IsNil) files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } info, err := s.FS.ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 3) info, err = s.FS.ReadDir("/qux") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } go-billy-5.3.1/test/mock.go000066400000000000000000000064571404362761100155150ustar00rootroot00000000000000package test import ( "bytes" "os" "path" "path/filepath" "github.com/go-git/go-billy/v5" ) type BasicMock struct { CreateArgs []string OpenArgs []string OpenFileArgs [][3]interface{} StatArgs []string RenameArgs [][2]string RemoveArgs []string JoinArgs [][]string } func (fs *BasicMock) Create(filename string) (billy.File, error) { fs.CreateArgs = append(fs.CreateArgs, filename) return &FileMock{name: filename}, nil } func (fs *BasicMock) Open(filename string) (billy.File, error) { fs.OpenArgs = append(fs.OpenArgs, filename) return &FileMock{name: filename}, nil } func (fs *BasicMock) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) { fs.OpenFileArgs = append(fs.OpenFileArgs, [3]interface{}{filename, flag, mode}) return &FileMock{name: filename}, nil } func (fs *BasicMock) Stat(filename string) (os.FileInfo, error) { fs.StatArgs = append(fs.StatArgs, filename) return nil, nil } func (fs *BasicMock) Rename(target, link string) error { fs.RenameArgs = append(fs.RenameArgs, [2]string{target, link}) return nil } func (fs *BasicMock) Remove(filename string) error { fs.RemoveArgs = append(fs.RemoveArgs, filename) return nil } func (fs *BasicMock) Join(elem ...string) string { fs.JoinArgs = append(fs.JoinArgs, elem) return path.Join(elem...) } type TempFileMock struct { BasicMock TempFileArgs [][2]string } func (fs *TempFileMock) TempFile(dir, prefix string) (billy.File, error) { fs.TempFileArgs = append(fs.TempFileArgs, [2]string{dir, prefix}) return &FileMock{name: "/tmp/hardcoded/mock/temp"}, nil } type DirMock struct { BasicMock ReadDirArgs []string MkdirAllArgs [][2]interface{} } func (fs *DirMock) ReadDir(path string) ([]os.FileInfo, error) { fs.ReadDirArgs = append(fs.ReadDirArgs, path) return nil, nil } func (fs *DirMock) MkdirAll(filename string, perm os.FileMode) error { fs.MkdirAllArgs = append(fs.MkdirAllArgs, [2]interface{}{filename, perm}) return nil } type SymlinkMock struct { BasicMock LstatArgs []string SymlinkArgs [][2]string ReadlinkArgs []string } func (fs *SymlinkMock) Lstat(filename string) (os.FileInfo, error) { fs.LstatArgs = append(fs.LstatArgs, filename) return nil, nil } func (fs *SymlinkMock) Symlink(target, link string) error { fs.SymlinkArgs = append(fs.SymlinkArgs, [2]string{target, link}) return nil } func (fs *SymlinkMock) Readlink(link string) (string, error) { fs.ReadlinkArgs = append(fs.ReadlinkArgs, link) return filepath.FromSlash(link), nil } type FileMock struct { name string bytes.Buffer } func (f *FileMock) Name() string { return f.name } func (*FileMock) ReadAt(b []byte, off int64) (int, error) { return 0, nil } func (*FileMock) Seek(offset int64, whence int) (int64, error) { return 0, nil } func (*FileMock) Close() error { return nil } func (*FileMock) Lock() error { return nil } func (*FileMock) Unlock() error { return nil } func (*FileMock) Truncate(size int64) error { return nil } type OnlyReadCapFs struct { BasicMock } func (o *OnlyReadCapFs) Capabilities() billy.Capability { return billy.ReadCapability } type NoLockCapFs struct { BasicMock } func (o *NoLockCapFs) Capabilities() billy.Capability { return billy.WriteCapability | billy.ReadCapability | billy.ReadAndWriteCapability | billy.SeekCapability | billy.TruncateCapability } go-billy-5.3.1/test/symlink.go000066400000000000000000000154101404362761100162370ustar00rootroot00000000000000package test import ( "io/ioutil" "os" "runtime" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // SymlinkSuite is a convenient test suite to validate any implementation of // billy.Symlink type SymlinkSuite struct { FS interface { Basic Symlink } } func (s *SymlinkSuite) TestSymlink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "file", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "link") c.Assert(err, IsNil) fi, err := s.FS.Stat("link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") } func (s *SymlinkSuite) TestSymlinkCrossDirs(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "foo/file", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("../foo/file", "bar/link") c.Assert(err, IsNil) fi, err := s.FS.Stat("bar/link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") } func (s *SymlinkSuite) TestSymlinkNested(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "file", []byte("hello world!"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "linkA") c.Assert(err, IsNil) err = s.FS.Symlink("linkA", "linkB") c.Assert(err, IsNil) fi, err := s.FS.Stat("linkB") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "linkB") c.Assert(fi.Size(), Equals, int64(12)) } func (s *SymlinkSuite) TestSymlinkWithNonExistentdTarget(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := s.FS.Symlink("file", "link") c.Assert(err, IsNil) _, err = s.FS.Stat("link") c.Assert(os.IsNotExist(err), Equals, true) } func (s *SymlinkSuite) TestSymlinkWithExistingLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "link", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "link") c.Assert(err, Not(IsNil)) } func (s *SymlinkSuite) TestOpenWithSymlinkToRelativePath(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "dir/link") c.Assert(err, IsNil) f, err := s.FS.Open("dir/link") c.Assert(err, IsNil) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(all), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *SymlinkSuite) TestOpenWithSymlinkToAbsolutePath(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("/dir/file", "dir/link") c.Assert(err, IsNil) f, err := s.FS.Open("dir/link") c.Assert(err, IsNil) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(all), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *SymlinkSuite) TestReadlink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "file", nil, 0644) c.Assert(err, IsNil) _, err = s.FS.Readlink("file") c.Assert(err, Not(IsNil)) } func (s *SymlinkSuite) TestReadlinkWithRelativePath(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "dir/link") c.Assert(err, IsNil) oldname, err := s.FS.Readlink("dir/link") c.Assert(err, IsNil) c.Assert(oldname, Equals, "file") } func (s *SymlinkSuite) TestReadlinkWithAbsolutePath(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("/dir/file", "dir/link") c.Assert(err, IsNil) oldname, err := s.FS.Readlink("dir/link") c.Assert(err, IsNil) c.Assert(oldname, Equals, expectedSymlinkTarget) } func (s *SymlinkSuite) TestReadlinkWithNonExistentTarget(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := s.FS.Symlink("file", "link") c.Assert(err, IsNil) oldname, err := s.FS.Readlink("link") c.Assert(err, IsNil) c.Assert(oldname, Equals, "file") } func (s *SymlinkSuite) TestReadlinkWithNonExistentLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } _, err := s.FS.Readlink("link") c.Assert(os.IsNotExist(err), Equals, true) } func (s *SymlinkSuite) TestStatLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) s.FS.Symlink("bar", "foo/qux") fi, err := s.FS.Stat("foo/qux") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "qux") c.Assert(fi.Size(), Equals, int64(3)) c.Assert(fi.Mode(), Equals, customMode) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, false) } func (s *SymlinkSuite) TestLstat(c *C) { util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) fi, err := s.FS.Lstat("foo/bar") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "bar") c.Assert(fi.Size(), Equals, int64(3)) c.Assert(fi.Mode()&os.ModeSymlink != 0, Equals, false) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, false) } func (s *SymlinkSuite) TestLstatLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } util.WriteFile(s.FS, "foo/bar", []byte("fosddddaaao"), customMode) s.FS.Symlink("bar", "foo/qux") fi, err := s.FS.Lstat("foo/qux") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "qux") c.Assert(fi.Mode()&os.ModeSymlink != 0, Equals, true) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, false) } func (s *SymlinkSuite) TestRenameWithSymlink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := s.FS.Symlink("file", "link") c.Assert(err, IsNil) err = s.FS.Rename("link", "newlink") c.Assert(err, IsNil) _, err = s.FS.Readlink("newlink") c.Assert(err, IsNil) } func (s *SymlinkSuite) TestRemoveWithSymlink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "file", []byte("foo"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "link") c.Assert(err, IsNil) err = s.FS.Remove("link") c.Assert(err, IsNil) _, err = s.FS.Readlink("link") c.Assert(os.IsNotExist(err), Equals, true) _, err = s.FS.Stat("link") c.Assert(os.IsNotExist(err), Equals, true) _, err = s.FS.Stat("file") c.Assert(err, IsNil) } go-billy-5.3.1/test/tempfile.go000066400000000000000000000040401404362761100163530ustar00rootroot00000000000000package test import ( "strings" . "gopkg.in/check.v1" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // TempFileSuite is a convenient test suite to validate any implementation of // billy.TempFile type TempFileSuite struct { FS interface { billy.Basic billy.TempFile } } func (s *TempFileSuite) TestTempFile(c *C) { f, err := s.FS.TempFile("", "bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(strings.Index(f.Name(), "bar"), Not(Equals), -1) } func (s *TempFileSuite) TestTempFileWithPath(c *C) { f, err := s.FS.TempFile("foo", "bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(strings.HasPrefix(f.Name(), s.FS.Join("foo", "bar")), Equals, true) } func (s *TempFileSuite) TestTempFileFullWithPath(c *C) { f, err := s.FS.TempFile("/foo", "bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(strings.Index(f.Name(), s.FS.Join("foo", "bar")), Not(Equals), -1) } func (s *TempFileSuite) TestRemoveTempFile(c *C) { f, err := s.FS.TempFile("test-dir", "test-prefix") c.Assert(err, IsNil) fn := f.Name() c.Assert(f.Close(), IsNil) c.Assert(s.FS.Remove(fn), IsNil) } func (s *TempFileSuite) TestRenameTempFile(c *C) { f, err := s.FS.TempFile("test-dir", "test-prefix") c.Assert(err, IsNil) fn := f.Name() c.Assert(f.Close(), IsNil) c.Assert(s.FS.Rename(fn, "other-path"), IsNil) } func (s *TempFileSuite) TestTempFileMany(c *C) { for i := 0; i < 1024; i++ { var fs []billy.File for j := 0; j < 100; j++ { f, err := s.FS.TempFile("test-dir", "test-prefix") c.Assert(err, IsNil) fs = append(fs, f) } for _, f := range fs { c.Assert(f.Close(), IsNil) c.Assert(s.FS.Remove(f.Name()), IsNil) } } } func (s *TempFileSuite) TestTempFileManyWithUtil(c *C) { for i := 0; i < 1024; i++ { var fs []billy.File for j := 0; j < 100; j++ { f, err := util.TempFile(s.FS, "test-dir", "test-prefix") c.Assert(err, IsNil) fs = append(fs, f) } for _, f := range fs { c.Assert(f.Close(), IsNil) c.Assert(s.FS.Remove(f.Name()), IsNil) } } } go-billy-5.3.1/util/000077500000000000000000000000001404362761100142175ustar00rootroot00000000000000go-billy-5.3.1/util/glob.go000066400000000000000000000050071404362761100154730ustar00rootroot00000000000000package util import ( "path/filepath" "sort" "strings" "github.com/go-git/go-billy/v5" ) // Glob returns the names of all files matching pattern or nil // if there is no matching file. The syntax of patterns is the same // as in Match. The pattern may describe hierarchical names such as // /usr/*/bin/ed (assuming the Separator is '/'). // // Glob ignores file system errors such as I/O errors reading directories. // The only possible returned error is ErrBadPattern, when pattern // is malformed. // // Function originally from https://golang.org/src/path/filepath/match_test.go func Glob(fs billy.Filesystem, pattern string) (matches []string, err error) { if !hasMeta(pattern) { if _, err = fs.Lstat(pattern); err != nil { return nil, nil } return []string{pattern}, nil } dir, file := filepath.Split(pattern) // Prevent infinite recursion. See issue 15879. if dir == pattern { return nil, filepath.ErrBadPattern } var m []string m, err = Glob(fs, cleanGlobPath(dir)) if err != nil { return } for _, d := range m { matches, err = glob(fs, d, file, matches) if err != nil { return } } return } // cleanGlobPath prepares path for glob matching. func cleanGlobPath(path string) string { switch path { case "": return "." case string(filepath.Separator): // do nothing to the path return path default: return path[0 : len(path)-1] // chop off trailing separator } } // glob searches for files matching pattern in the directory dir // and appends them to matches. If the directory cannot be // opened, it returns the existing matches. New matches are // added in lexicographical order. func glob(fs billy.Filesystem, dir, pattern string, matches []string) (m []string, e error) { m = matches fi, err := fs.Stat(dir) if err != nil { return } if !fi.IsDir() { return } names, _ := readdirnames(fs, dir) sort.Strings(names) for _, n := range names { matched, err := filepath.Match(pattern, n) if err != nil { return m, err } if matched { m = append(m, filepath.Join(dir, n)) } } return } // hasMeta reports whether path contains any of the magic characters // recognized by Match. func hasMeta(path string) bool { // TODO(niemeyer): Should other magic characters be added here? return strings.ContainsAny(path, "*?[") } func readdirnames(fs billy.Filesystem, dir string) ([]string, error) { files, err := fs.ReadDir(dir) if err != nil { return nil, err } var names []string for _, file := range files { names = append(names, file.Name()) } return names, nil } go-billy-5.3.1/util/glob_test.go000066400000000000000000000012341404362761100165300ustar00rootroot00000000000000package util_test import ( "path/filepath" "sort" "testing" . "gopkg.in/check.v1" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/util" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&UtilSuite{}) type UtilSuite struct{} func (s *UtilSuite) TestCreate(c *C) { fs := memfs.New() util.WriteFile(fs, "foo/qux", nil, 0644) util.WriteFile(fs, "foo/bar", nil, 0644) util.WriteFile(fs, "foo/baz/foo", nil, 0644) names, err := util.Glob(fs, "*/b*") c.Assert(err, IsNil) c.Assert(names, HasLen, 2) sort.Strings(names) c.Assert(names, DeepEquals, []string{ filepath.Join("foo", "bar"), filepath.Join("foo", "baz"), }) } go-billy-5.3.1/util/util.go000066400000000000000000000145311404362761100155270ustar00rootroot00000000000000package util import ( "io" "os" "path/filepath" "strconv" "sync" "time" "github.com/go-git/go-billy/v5" ) // RemoveAll removes path and any children it contains. It removes everything it // can but returns the first error it encounters. If the path does not exist, // RemoveAll returns nil (no error). func RemoveAll(fs billy.Basic, path string) error { fs, path = getUnderlyingAndPath(fs, path) if r, ok := fs.(removerAll); ok { return r.RemoveAll(path) } return removeAll(fs, path) } type removerAll interface { RemoveAll(string) error } func removeAll(fs billy.Basic, path string) error { // This implementation is adapted from os.RemoveAll. // Simple case: if Remove works, we're done. err := fs.Remove(path) if err == nil || os.IsNotExist(err) { return nil } // Otherwise, is this a directory we need to recurse into? dir, serr := fs.Stat(path) if serr != nil { if os.IsNotExist(serr) { return nil } return serr } if !dir.IsDir() { // Not a directory; return the error from Remove. return err } dirfs, ok := fs.(billy.Dir) if !ok { return billy.ErrNotSupported } // Directory. fis, err := dirfs.ReadDir(path) if err != nil { if os.IsNotExist(err) { // Race. It was deleted between the Lstat and Open. // Return nil per RemoveAll's docs. return nil } return err } // Remove contents & return first error. err = nil for _, fi := range fis { cpath := fs.Join(path, fi.Name()) err1 := removeAll(fs, cpath) if err == nil { err = err1 } } // Remove directory. err1 := fs.Remove(path) if err1 == nil || os.IsNotExist(err1) { return nil } if err == nil { err = err1 } return err } // WriteFile writes data to a file named by filename in the given filesystem. // If the file does not exist, WriteFile creates it with permissions perm; // otherwise WriteFile truncates it before writing. func WriteFile(fs billy.Basic, filename string, data []byte, perm os.FileMode) error { f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err } n, err := f.Write(data) if err == nil && n < len(data) { err = io.ErrShortWrite } if err1 := f.Close(); err == nil { err = err1 } return err } // Random number state. // We generate random temporary file names so that there's a good // chance the file doesn't exist yet - keeps the number of tries in // TempFile to a minimum. var rand uint32 var randmu sync.Mutex func reseed() uint32 { return uint32(time.Now().UnixNano() + int64(os.Getpid())) } func nextSuffix() string { randmu.Lock() r := rand if r == 0 { r = reseed() } r = r*1664525 + 1013904223 // constants from Numerical Recipes rand = r randmu.Unlock() return strconv.Itoa(int(1e9 + r%1e9))[1:] } // TempFile creates a new temporary file in the directory dir with a name // beginning with prefix, opens the file for reading and writing, and returns // the resulting *os.File. If dir is the empty string, TempFile uses the default // directory for temporary files (see os.TempDir). Multiple programs calling // TempFile simultaneously will not choose the same file. The caller can use // f.Name() to find the pathname of the file. It is the caller's responsibility // to remove the file when no longer needed. func TempFile(fs billy.Basic, dir, prefix string) (f billy.File, err error) { // This implementation is based on stdlib ioutil.TempFile. if dir == "" { dir = getTempDir(fs) } nconflict := 0 for i := 0; i < 10000; i++ { name := filepath.Join(dir, prefix+nextSuffix()) f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() rand = reseed() randmu.Unlock() } continue } break } return } // TempDir creates a new temporary directory in the directory dir // with a name beginning with prefix and returns the path of the // new directory. If dir is the empty string, TempDir uses the // default directory for temporary files (see os.TempDir). // Multiple programs calling TempDir simultaneously // will not choose the same directory. It is the caller's responsibility // to remove the directory when no longer needed. func TempDir(fs billy.Dir, dir, prefix string) (name string, err error) { // This implementation is based on stdlib ioutil.TempDir if dir == "" { dir = getTempDir(fs.(billy.Basic)) } nconflict := 0 for i := 0; i < 10000; i++ { try := filepath.Join(dir, prefix+nextSuffix()) err = fs.MkdirAll(try, 0700) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() rand = reseed() randmu.Unlock() } continue } if os.IsNotExist(err) { if _, err := os.Stat(dir); os.IsNotExist(err) { return "", err } } if err == nil { name = try } break } return } func getTempDir(fs billy.Basic) string { ch, ok := fs.(billy.Chroot) if !ok || ch.Root() == "" || ch.Root() == "/" || ch.Root() == string(filepath.Separator) { return os.TempDir() } return ".tmp" } type underlying interface { Underlying() billy.Basic } func getUnderlyingAndPath(fs billy.Basic, path string) (billy.Basic, string) { u, ok := fs.(underlying) if !ok { return fs, path } if ch, ok := fs.(billy.Chroot); ok { path = fs.Join(ch.Root(), path) } return u.Underlying(), path } // ReadFile reads the named file and returns the contents from the given filesystem. // A successful call returns err == nil, not err == EOF. // Because ReadFile reads the whole file, it does not treat an EOF from Read // as an error to be reported. func ReadFile(fs billy.Basic, name string) ([]byte, error) { f, err := fs.Open(name) if err != nil { return nil, err } defer f.Close() var size int if info, err := fs.Stat(name); err == nil { size64 := info.Size() if int64(int(size64)) == size64 { size = int(size64) } } size++ // one byte for final read at EOF // If a file claims a small size, read at least 512 bytes. // In particular, files in Linux's /proc claim size 0 but // then do not work right if read in small pieces, // so an initial read of 1 byte would not work correctly. if size < 512 { size = 512 } data := make([]byte, 0, size) for { if len(data) >= cap(data) { d := append(data[:cap(data)], 0) data = d[:len(data)] } n, err := f.Read(data[len(data):cap(data)]) data = data[:len(data)+n] if err != nil { if err == io.EOF { err = nil } return data, err } } } go-billy-5.3.1/util/util_test.go000066400000000000000000000035451404362761100165710ustar00rootroot00000000000000package util_test import ( "os" "path/filepath" "regexp" "testing" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/util" ) func TestTempFile(t *testing.T) { fs := memfs.New() dir, err := util.TempDir(fs, "", "util_test") if err != nil { t.Fatal(err) } defer util.RemoveAll(fs, dir) f, err := util.TempFile(fs, dir, "foo") if f == nil || err != nil { t.Errorf("TempFile(%q, `foo`) = %v, %v", dir, f, err) } } func TestTempDir_WithDir(t *testing.T) { fs := memfs.New() dir := os.TempDir() name, err := util.TempDir(fs, dir, "util_test") if name == "" || err != nil { t.Errorf("TempDir(dir, `util_test`) = %v, %v", name, err) } if name != "" { util.RemoveAll(fs, name) re := regexp.MustCompile("^" + regexp.QuoteMeta(filepath.Join(dir, "util_test")) + "[0-9]+$") if !re.MatchString(name) { t.Errorf("TempDir(`"+dir+"`, `util_test`) created bad name %s", name) } } } func TestReadFile(t *testing.T) { fs := memfs.New() f, err := util.TempFile(fs, "", "") if err != nil { t.Fatal(err) } f.Write([]byte("foo")) f.Close() data, err := util.ReadFile(fs, f.Name()) if err != nil { t.Fatal(err) } if string(data) != "foo" || err != nil { t.Errorf("ReadFile(%q, %q) = %v, %v", fs, f.Name(), data, err) } } func TestTempDir(t *testing.T) { fs := memfs.New() f, err := util.TempDir(fs, "", "") if err != nil { t.Fatal(err) } _, err = filepath.Rel(os.TempDir(), f) if err != nil { t.Errorf(`TempDir(fs, "", "") = %s, should be relative to os.TempDir if root filesystem`, f) } } func TestTempDir_WithNonRoot(t *testing.T) { fs := memfs.New() fs, _ = fs.Chroot("foo") f, err := util.TempDir(fs, "", "") if err != nil { t.Fatal(err) } _, err = filepath.Rel(os.TempDir(), f) if err == nil { t.Errorf(`TempDir(fs, "", "") = %s, should not be relative to os.TempDir on not root filesystem`, f) } }