main
Raw Download raw file
  1package chroot
  2
  3import (
  4	"os"
  5	"path/filepath"
  6	"strings"
  7
  8	"github.com/go-git/go-billy/v5"
  9	"github.com/go-git/go-billy/v5/helper/polyfill"
 10)
 11
 12// ChrootHelper is a helper to implement billy.Chroot.
 13type ChrootHelper struct {
 14	underlying billy.Filesystem
 15	base       string
 16}
 17
 18// New creates a new filesystem wrapping up the given 'fs'.
 19// The created filesystem has its base in the given ChrootHelperectory of the
 20// underlying filesystem.
 21func New(fs billy.Basic, base string) billy.Filesystem {
 22	return &ChrootHelper{
 23		underlying: polyfill.New(fs),
 24		base:       base,
 25	}
 26}
 27
 28func (fs *ChrootHelper) underlyingPath(filename string) (string, error) {
 29	if isCrossBoundaries(filename) {
 30		return "", billy.ErrCrossedBoundary
 31	}
 32
 33	return fs.Join(fs.Root(), filename), nil
 34}
 35
 36func isCrossBoundaries(path string) bool {
 37	path = filepath.ToSlash(path)
 38	path = filepath.Clean(path)
 39
 40	return strings.HasPrefix(path, ".."+string(filepath.Separator))
 41}
 42
 43func (fs *ChrootHelper) Create(filename string) (billy.File, error) {
 44	fullpath, err := fs.underlyingPath(filename)
 45	if err != nil {
 46		return nil, err
 47	}
 48
 49	f, err := fs.underlying.Create(fullpath)
 50	if err != nil {
 51		return nil, err
 52	}
 53
 54	return newFile(fs, f, filename), nil
 55}
 56
 57func (fs *ChrootHelper) Open(filename string) (billy.File, error) {
 58	fullpath, err := fs.underlyingPath(filename)
 59	if err != nil {
 60		return nil, err
 61	}
 62
 63	f, err := fs.underlying.Open(fullpath)
 64	if err != nil {
 65		return nil, err
 66	}
 67
 68	return newFile(fs, f, filename), nil
 69}
 70
 71func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) {
 72	fullpath, err := fs.underlyingPath(filename)
 73	if err != nil {
 74		return nil, err
 75	}
 76
 77	f, err := fs.underlying.OpenFile(fullpath, flag, mode)
 78	if err != nil {
 79		return nil, err
 80	}
 81
 82	return newFile(fs, f, filename), nil
 83}
 84
 85func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) {
 86	fullpath, err := fs.underlyingPath(filename)
 87	if err != nil {
 88		return nil, err
 89	}
 90
 91	return fs.underlying.Stat(fullpath)
 92}
 93
 94func (fs *ChrootHelper) Rename(from, to string) error {
 95	var err error
 96	from, err = fs.underlyingPath(from)
 97	if err != nil {
 98		return err
 99	}
100
101	to, err = fs.underlyingPath(to)
102	if err != nil {
103		return err
104	}
105
106	return fs.underlying.Rename(from, to)
107}
108
109func (fs *ChrootHelper) Remove(path string) error {
110	fullpath, err := fs.underlyingPath(path)
111	if err != nil {
112		return err
113	}
114
115	return fs.underlying.Remove(fullpath)
116}
117
118func (fs *ChrootHelper) Join(elem ...string) string {
119	return fs.underlying.Join(elem...)
120}
121
122func (fs *ChrootHelper) TempFile(dir, prefix string) (billy.File, error) {
123	fullpath, err := fs.underlyingPath(dir)
124	if err != nil {
125		return nil, err
126	}
127
128	f, err := fs.underlying.(billy.TempFile).TempFile(fullpath, prefix)
129	if err != nil {
130		return nil, err
131	}
132
133	return newFile(fs, f, fs.Join(dir, filepath.Base(f.Name()))), nil
134}
135
136func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) {
137	fullpath, err := fs.underlyingPath(path)
138	if err != nil {
139		return nil, err
140	}
141
142	return fs.underlying.(billy.Dir).ReadDir(fullpath)
143}
144
145func (fs *ChrootHelper) MkdirAll(filename string, perm os.FileMode) error {
146	fullpath, err := fs.underlyingPath(filename)
147	if err != nil {
148		return err
149	}
150
151	return fs.underlying.(billy.Dir).MkdirAll(fullpath, perm)
152}
153
154func (fs *ChrootHelper) Lstat(filename string) (os.FileInfo, error) {
155	fullpath, err := fs.underlyingPath(filename)
156	if err != nil {
157		return nil, err
158	}
159
160	return fs.underlying.(billy.Symlink).Lstat(fullpath)
161}
162
163func (fs *ChrootHelper) Symlink(target, link string) error {
164	target = filepath.FromSlash(target)
165
166	// only rewrite target if it's already absolute
167	if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) {
168		target = fs.Join(fs.Root(), target)
169		target = filepath.Clean(filepath.FromSlash(target))
170	}
171
172	link, err := fs.underlyingPath(link)
173	if err != nil {
174		return err
175	}
176
177	return fs.underlying.(billy.Symlink).Symlink(target, link)
178}
179
180func (fs *ChrootHelper) Readlink(link string) (string, error) {
181	fullpath, err := fs.underlyingPath(link)
182	if err != nil {
183		return "", err
184	}
185
186	target, err := fs.underlying.(billy.Symlink).Readlink(fullpath)
187	if err != nil {
188		return "", err
189	}
190
191	if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) {
192		return target, nil
193	}
194
195	target, err = filepath.Rel(fs.base, target)
196	if err != nil {
197		return "", err
198	}
199
200	return string(os.PathSeparator) + target, nil
201}
202
203func (fs *ChrootHelper) Chroot(path string) (billy.Filesystem, error) {
204	fullpath, err := fs.underlyingPath(path)
205	if err != nil {
206		return nil, err
207	}
208
209	return New(fs.underlying, fullpath), nil
210}
211
212func (fs *ChrootHelper) Root() string {
213	return fs.base
214}
215
216func (fs *ChrootHelper) Underlying() billy.Basic {
217	return fs.underlying
218}
219
220// Capabilities implements the Capable interface.
221func (fs *ChrootHelper) Capabilities() billy.Capability {
222	return billy.Capabilities(fs.underlying)
223}
224
225type file struct {
226	billy.File
227	name string
228}
229
230func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File {
231	filename = fs.Join(fs.Root(), filename)
232	filename, _ = filepath.Rel(fs.Root(), filename)
233
234	return &file{
235		File: f,
236		name: filename,
237	}
238}
239
240func (f *file) Name() string {
241	return f.name
242}