main
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}