1package file
2
3import (
4	"context"
5	"io/ioutil"
6	"log"
7	"os"
8	"path/filepath"
9	"strings"
10	"time"
11
12	"github.com/containerd/continuity/fs"
13	"github.com/docker/docker/pkg/idtools"
14	"github.com/moby/buildkit/snapshot"
15	"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
16	"github.com/moby/buildkit/solver/pb"
17	"github.com/pkg/errors"
18	copy "github.com/tonistiigi/fsutil/copy"
19)
20
21func timestampToTime(ts int64) *time.Time {
22	if ts == -1 {
23		return nil
24	}
25	tm := time.Unix(ts/1e9, ts%1e9)
26	return &tm
27}
28
29func mapUserToChowner(user *copy.User, idmap *idtools.IdentityMapping) (copy.Chowner, error) {
30	if user == nil {
31		return func(old *copy.User) (*copy.User, error) {
32			if old == nil {
33				if idmap == nil {
34					return nil, nil
35				}
36				old = &copy.User{} // root
37				// non-nil old is already mapped
38				if idmap != nil {
39					identity, err := idmap.ToHost(idtools.Identity{
40						UID: old.UID,
41						GID: old.GID,
42					})
43					if err != nil {
44						return nil, err
45					}
46					return &copy.User{UID: identity.UID, GID: identity.GID}, nil
47				}
48			}
49			return old, nil
50		}, nil
51	}
52	u := *user
53	if idmap != nil {
54		identity, err := idmap.ToHost(idtools.Identity{
55			UID: user.UID,
56			GID: user.GID,
57		})
58		if err != nil {
59			return nil, err
60		}
61		u.UID = identity.UID
62		u.GID = identity.GID
63	}
64	return func(*copy.User) (*copy.User, error) {
65		return &u, nil
66	}, nil
67}
68
69func mkdir(ctx context.Context, d string, action pb.FileActionMkDir, user *copy.User, idmap *idtools.IdentityMapping) error {
70	p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
71	if err != nil {
72		return err
73	}
74
75	ch, err := mapUserToChowner(user, idmap)
76	if err != nil {
77		return err
78	}
79
80	if action.MakeParents {
81		if err := copy.MkdirAll(p, os.FileMode(action.Mode)&0777, ch, timestampToTime(action.Timestamp)); err != nil {
82			return err
83		}
84	} else {
85		if err := os.Mkdir(p, os.FileMode(action.Mode)&0777); err != nil {
86			if errors.Is(err, os.ErrExist) {
87				return nil
88			}
89			return err
90		}
91		if err := copy.Chown(p, nil, ch); err != nil {
92			return err
93		}
94		if err := copy.Utimes(p, timestampToTime(action.Timestamp)); err != nil {
95			return err
96		}
97	}
98
99	return nil
100}
101
102func mkfile(ctx context.Context, d string, action pb.FileActionMkFile, user *copy.User, idmap *idtools.IdentityMapping) error {
103	p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
104	if err != nil {
105		return err
106	}
107
108	ch, err := mapUserToChowner(user, idmap)
109	if err != nil {
110		return err
111	}
112
113	if err := ioutil.WriteFile(p, action.Data, os.FileMode(action.Mode)&0777); err != nil {
114		return err
115	}
116
117	if err := copy.Chown(p, nil, ch); err != nil {
118		return err
119	}
120
121	if err := copy.Utimes(p, timestampToTime(action.Timestamp)); err != nil {
122		return err
123	}
124
125	return nil
126}
127
128func rm(ctx context.Context, d string, action pb.FileActionRm) error {
129	if action.AllowWildcard {
130		src := cleanPath(action.Path)
131		m, err := copy.ResolveWildcards(d, src, false)
132		if err != nil {
133			return err
134		}
135
136		for _, s := range m {
137			if err := rmPath(d, s, action.AllowNotFound); err != nil {
138				return err
139			}
140		}
141
142		return nil
143	}
144
145	return rmPath(d, action.Path, action.AllowNotFound)
146}
147
148func rmPath(root, src string, allowNotFound bool) error {
149	p, err := fs.RootPath(root, filepath.Join(filepath.Join("/", src)))
150	if err != nil {
151		return err
152	}
153
154	if err := os.RemoveAll(p); err != nil {
155		if errors.Is(err, os.ErrNotExist) && allowNotFound {
156			return nil
157		}
158		return err
159	}
160
161	return nil
162}
163
164func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy, u *copy.User, idmap *idtools.IdentityMapping) error {
165	srcPath := cleanPath(action.Src)
166	destPath := cleanPath(action.Dest)
167
168	if !action.CreateDestPath {
169		p, err := fs.RootPath(dest, filepath.Join(filepath.Join("/", action.Dest)))
170		if err != nil {
171			return err
172		}
173		if _, err := os.Lstat(filepath.Dir(p)); err != nil {
174			return errors.Wrapf(err, "failed to stat %s", action.Dest)
175		}
176	}
177
178	xattrErrorHandler := func(dst, src, key string, err error) error {
179		log.Println(err)
180		return nil
181	}
182
183	ch, err := mapUserToChowner(u, idmap)
184	if err != nil {
185		return err
186	}
187
188	opt := []copy.Opt{
189		func(ci *copy.CopyInfo) {
190			ci.Chown = ch
191			ci.Utime = timestampToTime(action.Timestamp)
192			if m := int(action.Mode); m != -1 {
193				ci.Mode = &m
194			}
195			ci.CopyDirContents = action.DirCopyContents
196			ci.FollowLinks = action.FollowSymlink
197		},
198		copy.WithXAttrErrorHandler(xattrErrorHandler),
199	}
200
201	if !action.AllowWildcard {
202		if action.AttemptUnpackDockerCompatibility {
203			if ok, err := unpack(ctx, src, srcPath, dest, destPath, ch, timestampToTime(action.Timestamp)); err != nil {
204				return err
205			} else if ok {
206				return nil
207			}
208		}
209		return copy.Copy(ctx, src, srcPath, dest, destPath, opt...)
210	}
211
212	m, err := copy.ResolveWildcards(src, srcPath, action.FollowSymlink)
213	if err != nil {
214		return err
215	}
216
217	if len(m) == 0 {
218		if action.AllowEmptyWildcard {
219			return nil
220		}
221		return errors.Errorf("%s not found", srcPath)
222	}
223
224	for _, s := range m {
225		if action.AttemptUnpackDockerCompatibility {
226			if ok, err := unpack(ctx, src, s, dest, destPath, ch, timestampToTime(action.Timestamp)); err != nil {
227				return err
228			} else if ok {
229				continue
230			}
231		}
232		if err := copy.Copy(ctx, src, s, dest, destPath, opt...); err != nil {
233			return err
234		}
235	}
236
237	return nil
238}
239
240func cleanPath(s string) string {
241	s2 := filepath.Join("/", s)
242	if strings.HasSuffix(s, "/.") {
243		if s2 != "/" {
244			s2 += "/"
245		}
246		s2 += "."
247	} else if strings.HasSuffix(s, "/") && s2 != "/" {
248		s2 += "/"
249	}
250	return s2
251}
252
253type Backend struct {
254}
255
256func (fb *Backend) Mkdir(ctx context.Context, m, user, group fileoptypes.Mount, action pb.FileActionMkDir) error {
257	mnt, ok := m.(*Mount)
258	if !ok {
259		return errors.Errorf("invalid mount type %T", m)
260	}
261
262	lm := snapshot.LocalMounter(mnt.m)
263	dir, err := lm.Mount()
264	if err != nil {
265		return err
266	}
267	defer lm.Unmount()
268
269	u, err := readUser(action.Owner, user, group)
270	if err != nil {
271		return err
272	}
273
274	return mkdir(ctx, dir, action, u, mnt.m.IdentityMapping())
275}
276
277func (fb *Backend) Mkfile(ctx context.Context, m, user, group fileoptypes.Mount, action pb.FileActionMkFile) error {
278	mnt, ok := m.(*Mount)
279	if !ok {
280		return errors.Errorf("invalid mount type %T", m)
281	}
282
283	lm := snapshot.LocalMounter(mnt.m)
284	dir, err := lm.Mount()
285	if err != nil {
286		return err
287	}
288	defer lm.Unmount()
289
290	u, err := readUser(action.Owner, user, group)
291	if err != nil {
292		return err
293	}
294
295	return mkfile(ctx, dir, action, u, mnt.m.IdentityMapping())
296}
297
298func (fb *Backend) Rm(ctx context.Context, m fileoptypes.Mount, action pb.FileActionRm) error {
299	mnt, ok := m.(*Mount)
300	if !ok {
301		return errors.Errorf("invalid mount type %T", m)
302	}
303
304	lm := snapshot.LocalMounter(mnt.m)
305	dir, err := lm.Mount()
306	if err != nil {
307		return err
308	}
309	defer lm.Unmount()
310
311	return rm(ctx, dir, action)
312}
313
314func (fb *Backend) Copy(ctx context.Context, m1, m2, user, group fileoptypes.Mount, action pb.FileActionCopy) error {
315	mnt1, ok := m1.(*Mount)
316	if !ok {
317		return errors.Errorf("invalid mount type %T", m1)
318	}
319	mnt2, ok := m2.(*Mount)
320	if !ok {
321		return errors.Errorf("invalid mount type %T", m2)
322	}
323
324	lm := snapshot.LocalMounter(mnt1.m)
325	src, err := lm.Mount()
326	if err != nil {
327		return err
328	}
329	defer lm.Unmount()
330
331	lm2 := snapshot.LocalMounter(mnt2.m)
332	dest, err := lm2.Mount()
333	if err != nil {
334		return err
335	}
336	defer lm2.Unmount()
337
338	u, err := readUser(action.Owner, user, group)
339	if err != nil {
340		return err
341	}
342
343	return docopy(ctx, src, dest, action, u, mnt2.m.IdentityMapping())
344}
345