file.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. package ops
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "path"
  8. "runtime"
  9. "sort"
  10. "sync"
  11. "github.com/moby/buildkit/cache"
  12. "github.com/moby/buildkit/cache/metadata"
  13. "github.com/moby/buildkit/session"
  14. "github.com/moby/buildkit/solver"
  15. "github.com/moby/buildkit/solver/llbsolver"
  16. "github.com/moby/buildkit/solver/llbsolver/errdefs"
  17. "github.com/moby/buildkit/solver/llbsolver/file"
  18. "github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
  19. "github.com/moby/buildkit/solver/pb"
  20. "github.com/moby/buildkit/util/flightcontrol"
  21. "github.com/moby/buildkit/worker"
  22. digest "github.com/opencontainers/go-digest"
  23. "github.com/pkg/errors"
  24. "golang.org/x/sync/errgroup"
  25. )
  26. const fileCacheType = "buildkit.file.v0"
  27. type fileOp struct {
  28. op *pb.FileOp
  29. md *metadata.Store
  30. w worker.Worker
  31. solver *FileOpSolver
  32. numInputs int
  33. }
  34. func NewFileOp(v solver.Vertex, op *pb.Op_File, cm cache.Manager, md *metadata.Store, w worker.Worker) (solver.Op, error) {
  35. if err := llbsolver.ValidateOp(&pb.Op{Op: op}); err != nil {
  36. return nil, err
  37. }
  38. return &fileOp{
  39. op: op.File,
  40. md: md,
  41. numInputs: len(v.Inputs()),
  42. w: w,
  43. solver: NewFileOpSolver(w, &file.Backend{}, file.NewRefManager(cm)),
  44. }, nil
  45. }
  46. func (f *fileOp) CacheMap(ctx context.Context, g session.Group, index int) (*solver.CacheMap, bool, error) {
  47. selectors := map[int]map[llbsolver.Selector]struct{}{}
  48. invalidSelectors := map[int]struct{}{}
  49. actions := make([][]byte, 0, len(f.op.Actions))
  50. markInvalid := func(idx pb.InputIndex) {
  51. if idx != -1 {
  52. invalidSelectors[int(idx)] = struct{}{}
  53. }
  54. }
  55. indexes := make([][]int, 0, len(f.op.Actions))
  56. for _, action := range f.op.Actions {
  57. var dt []byte
  58. var err error
  59. switch a := action.Action.(type) {
  60. case *pb.FileAction_Mkdir:
  61. p := *a.Mkdir
  62. markInvalid(action.Input)
  63. processOwner(p.Owner, selectors)
  64. dt, err = json.Marshal(p)
  65. if err != nil {
  66. return nil, false, err
  67. }
  68. case *pb.FileAction_Mkfile:
  69. p := *a.Mkfile
  70. markInvalid(action.Input)
  71. processOwner(p.Owner, selectors)
  72. dt, err = json.Marshal(p)
  73. if err != nil {
  74. return nil, false, err
  75. }
  76. case *pb.FileAction_Rm:
  77. p := *a.Rm
  78. markInvalid(action.Input)
  79. dt, err = json.Marshal(p)
  80. if err != nil {
  81. return nil, false, err
  82. }
  83. case *pb.FileAction_Copy:
  84. p := *a.Copy
  85. markInvalid(action.Input)
  86. processOwner(p.Owner, selectors)
  87. if action.SecondaryInput != -1 && int(action.SecondaryInput) < f.numInputs {
  88. addSelector(selectors, int(action.SecondaryInput), p.Src, p.AllowWildcard, p.FollowSymlink)
  89. p.Src = path.Base(p.Src)
  90. }
  91. dt, err = json.Marshal(p)
  92. if err != nil {
  93. return nil, false, err
  94. }
  95. }
  96. actions = append(actions, dt)
  97. indexes = append(indexes, []int{int(action.Input), int(action.SecondaryInput), int(action.Output)})
  98. }
  99. if isDefaultIndexes(indexes) {
  100. indexes = nil
  101. }
  102. dt, err := json.Marshal(struct {
  103. Type string
  104. Actions [][]byte
  105. Indexes [][]int `json:"indexes,omitempty"`
  106. }{
  107. Type: fileCacheType,
  108. Actions: actions,
  109. Indexes: indexes,
  110. })
  111. if err != nil {
  112. return nil, false, err
  113. }
  114. cm := &solver.CacheMap{
  115. Digest: digest.FromBytes(dt),
  116. Deps: make([]struct {
  117. Selector digest.Digest
  118. ComputeDigestFunc solver.ResultBasedCacheFunc
  119. PreprocessFunc solver.PreprocessFunc
  120. }, f.numInputs),
  121. }
  122. for idx, m := range selectors {
  123. if _, ok := invalidSelectors[idx]; ok {
  124. continue
  125. }
  126. dgsts := make([][]byte, 0, len(m))
  127. for k := range m {
  128. dgsts = append(dgsts, []byte(k.Path))
  129. }
  130. sort.Slice(dgsts, func(i, j int) bool {
  131. return bytes.Compare(dgsts[i], dgsts[j]) > 0
  132. })
  133. cm.Deps[idx].Selector = digest.FromBytes(bytes.Join(dgsts, []byte{0}))
  134. cm.Deps[idx].ComputeDigestFunc = llbsolver.NewContentHashFunc(dedupeSelectors(m))
  135. }
  136. for idx := range cm.Deps {
  137. cm.Deps[idx].PreprocessFunc = llbsolver.UnlazyResultFunc
  138. }
  139. return cm, true, nil
  140. }
  141. func (f *fileOp) Exec(ctx context.Context, g session.Group, inputs []solver.Result) ([]solver.Result, error) {
  142. inpRefs := make([]fileoptypes.Ref, 0, len(inputs))
  143. for _, inp := range inputs {
  144. workerRef, ok := inp.Sys().(*worker.WorkerRef)
  145. if !ok {
  146. return nil, errors.Errorf("invalid reference for exec %T", inp.Sys())
  147. }
  148. inpRefs = append(inpRefs, workerRef.ImmutableRef)
  149. }
  150. outs, err := f.solver.Solve(ctx, inpRefs, f.op.Actions, g)
  151. if err != nil {
  152. return nil, err
  153. }
  154. outResults := make([]solver.Result, 0, len(outs))
  155. for _, out := range outs {
  156. outResults = append(outResults, worker.NewWorkerRefResult(out.(cache.ImmutableRef), f.w))
  157. }
  158. return outResults, nil
  159. }
  160. func addSelector(m map[int]map[llbsolver.Selector]struct{}, idx int, sel string, wildcard, followLinks bool) {
  161. mm, ok := m[idx]
  162. if !ok {
  163. mm = map[llbsolver.Selector]struct{}{}
  164. m[idx] = mm
  165. }
  166. s := llbsolver.Selector{Path: sel}
  167. if wildcard && containsWildcards(sel) {
  168. s.Wildcard = true
  169. }
  170. if followLinks {
  171. s.FollowLinks = true
  172. }
  173. mm[s] = struct{}{}
  174. }
  175. func containsWildcards(name string) bool {
  176. isWindows := runtime.GOOS == "windows"
  177. for i := 0; i < len(name); i++ {
  178. ch := name[i]
  179. if ch == '\\' && !isWindows {
  180. i++
  181. } else if ch == '*' || ch == '?' || ch == '[' {
  182. return true
  183. }
  184. }
  185. return false
  186. }
  187. func dedupeSelectors(m map[llbsolver.Selector]struct{}) []llbsolver.Selector {
  188. paths := make([]string, 0, len(m))
  189. pathsFollow := make([]string, 0, len(m))
  190. for sel := range m {
  191. if !sel.Wildcard {
  192. if sel.FollowLinks {
  193. pathsFollow = append(pathsFollow, sel.Path)
  194. } else {
  195. paths = append(paths, sel.Path)
  196. }
  197. }
  198. }
  199. paths = dedupePaths(paths)
  200. pathsFollow = dedupePaths(pathsFollow)
  201. selectors := make([]llbsolver.Selector, 0, len(m))
  202. for _, p := range paths {
  203. selectors = append(selectors, llbsolver.Selector{Path: p})
  204. }
  205. for _, p := range pathsFollow {
  206. selectors = append(selectors, llbsolver.Selector{Path: p, FollowLinks: true})
  207. }
  208. for sel := range m {
  209. if sel.Wildcard {
  210. selectors = append(selectors, sel)
  211. }
  212. }
  213. sort.Slice(selectors, func(i, j int) bool {
  214. return selectors[i].Path < selectors[j].Path
  215. })
  216. return selectors
  217. }
  218. func processOwner(chopt *pb.ChownOpt, selectors map[int]map[llbsolver.Selector]struct{}) error {
  219. if chopt == nil {
  220. return nil
  221. }
  222. if chopt.User != nil {
  223. if u, ok := chopt.User.User.(*pb.UserOpt_ByName); ok {
  224. if u.ByName.Input < 0 {
  225. return errors.Errorf("invalid user index %d", u.ByName.Input)
  226. }
  227. addSelector(selectors, int(u.ByName.Input), "/etc/passwd", false, true)
  228. }
  229. }
  230. if chopt.Group != nil {
  231. if u, ok := chopt.Group.User.(*pb.UserOpt_ByName); ok {
  232. if u.ByName.Input < 0 {
  233. return errors.Errorf("invalid user index %d", u.ByName.Input)
  234. }
  235. addSelector(selectors, int(u.ByName.Input), "/etc/group", false, true)
  236. }
  237. }
  238. return nil
  239. }
  240. func NewFileOpSolver(w worker.Worker, b fileoptypes.Backend, r fileoptypes.RefManager) *FileOpSolver {
  241. return &FileOpSolver{
  242. w: w,
  243. b: b,
  244. r: r,
  245. outs: map[int]int{},
  246. ins: map[int]input{},
  247. }
  248. }
  249. type FileOpSolver struct {
  250. w worker.Worker
  251. b fileoptypes.Backend
  252. r fileoptypes.RefManager
  253. mu sync.Mutex
  254. outs map[int]int
  255. ins map[int]input
  256. g flightcontrol.Group
  257. }
  258. type input struct {
  259. requiresCommit bool
  260. mount fileoptypes.Mount
  261. ref fileoptypes.Ref
  262. }
  263. func (s *FileOpSolver) Solve(ctx context.Context, inputs []fileoptypes.Ref, actions []*pb.FileAction, g session.Group) ([]fileoptypes.Ref, error) {
  264. for i, a := range actions {
  265. if int(a.Input) < -1 || int(a.Input) >= len(inputs)+len(actions) {
  266. return nil, errors.Errorf("invalid input index %d, %d provided", a.Input, len(inputs)+len(actions))
  267. }
  268. if int(a.SecondaryInput) < -1 || int(a.SecondaryInput) >= len(inputs)+len(actions) {
  269. return nil, errors.Errorf("invalid secondary input index %d, %d provided", a.Input, len(inputs))
  270. }
  271. inp, ok := s.ins[int(a.Input)]
  272. if ok {
  273. inp.requiresCommit = true
  274. }
  275. s.ins[int(a.Input)] = inp
  276. inp, ok = s.ins[int(a.SecondaryInput)]
  277. if ok {
  278. inp.requiresCommit = true
  279. }
  280. s.ins[int(a.SecondaryInput)] = inp
  281. if a.Output != -1 {
  282. if _, ok := s.outs[int(a.Output)]; ok {
  283. return nil, errors.Errorf("duplicate output %d", a.Output)
  284. }
  285. idx := len(inputs) + i
  286. s.outs[int(a.Output)] = idx
  287. s.ins[idx] = input{requiresCommit: true}
  288. }
  289. }
  290. if len(s.outs) == 0 {
  291. return nil, errors.Errorf("no outputs specified")
  292. }
  293. for i := 0; i < len(s.outs); i++ {
  294. if _, ok := s.outs[i]; !ok {
  295. return nil, errors.Errorf("missing output index %d", i)
  296. }
  297. }
  298. defer func() {
  299. for _, in := range s.ins {
  300. if in.ref == nil && in.mount != nil {
  301. in.mount.Release(context.TODO())
  302. }
  303. }
  304. }()
  305. outs := make([]fileoptypes.Ref, len(s.outs))
  306. eg, ctx := errgroup.WithContext(ctx)
  307. for i, idx := range s.outs {
  308. func(i, idx int) {
  309. eg.Go(func() error {
  310. if err := s.validate(idx, inputs, actions, nil); err != nil {
  311. return err
  312. }
  313. inp, err := s.getInput(ctx, idx, inputs, actions, g)
  314. if err != nil {
  315. return errdefs.WithFileActionError(err, idx-len(inputs))
  316. }
  317. outs[i] = inp.ref
  318. return nil
  319. })
  320. }(i, idx)
  321. }
  322. if err := eg.Wait(); err != nil {
  323. for _, r := range outs {
  324. if r != nil {
  325. r.Release(context.TODO())
  326. }
  327. }
  328. return nil, err
  329. }
  330. return outs, nil
  331. }
  332. func (s *FileOpSolver) validate(idx int, inputs []fileoptypes.Ref, actions []*pb.FileAction, loaded []int) error {
  333. for _, check := range loaded {
  334. if idx == check {
  335. return errors.Errorf("loop from index %d", idx)
  336. }
  337. }
  338. if idx < len(inputs) {
  339. return nil
  340. }
  341. loaded = append(loaded, idx)
  342. action := actions[idx-len(inputs)]
  343. for _, inp := range []int{int(action.Input), int(action.SecondaryInput)} {
  344. if err := s.validate(inp, inputs, actions, loaded); err != nil {
  345. return err
  346. }
  347. }
  348. return nil
  349. }
  350. func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptypes.Ref, actions []*pb.FileAction, g session.Group) (input, error) {
  351. inp, err := s.g.Do(ctx, fmt.Sprintf("inp-%d", idx), func(ctx context.Context) (_ interface{}, err error) {
  352. s.mu.Lock()
  353. inp := s.ins[idx]
  354. s.mu.Unlock()
  355. if inp.mount != nil || inp.ref != nil {
  356. return inp, nil
  357. }
  358. if idx < len(inputs) {
  359. inp.ref = inputs[idx]
  360. s.mu.Lock()
  361. s.ins[idx] = inp
  362. s.mu.Unlock()
  363. return inp, nil
  364. }
  365. var inpMount, inpMountSecondary fileoptypes.Mount
  366. var toRelease []fileoptypes.Mount
  367. action := actions[idx-len(inputs)]
  368. defer func() {
  369. if err != nil && inpMount != nil {
  370. inputRes := make([]solver.Result, len(inputs))
  371. for i, input := range inputs {
  372. inputRes[i] = worker.NewWorkerRefResult(input.(cache.ImmutableRef), s.w)
  373. }
  374. outputRes := make([]solver.Result, len(actions))
  375. // Commit the mutable for the primary input of the failed action.
  376. if !inpMount.Readonly() {
  377. ref, cerr := s.r.Commit(ctx, inpMount)
  378. if cerr == nil {
  379. outputRes[idx-len(inputs)] = worker.NewWorkerRefResult(ref.(cache.ImmutableRef), s.w)
  380. }
  381. }
  382. // If the action has a secondary input, commit it and set the ref on
  383. // the output results.
  384. if inpMountSecondary != nil && !inpMountSecondary.Readonly() {
  385. ref2, cerr := s.r.Commit(ctx, inpMountSecondary)
  386. if cerr == nil {
  387. outputRes[int(action.SecondaryInput)-len(inputs)] = worker.NewWorkerRefResult(ref2.(cache.ImmutableRef), s.w)
  388. }
  389. }
  390. err = errdefs.WithExecError(err, inputRes, outputRes)
  391. }
  392. for _, m := range toRelease {
  393. m.Release(context.TODO())
  394. }
  395. }()
  396. loadInput := func(ctx context.Context) func() error {
  397. return func() error {
  398. inp, err := s.getInput(ctx, int(action.Input), inputs, actions, g)
  399. if err != nil {
  400. return err
  401. }
  402. if inp.ref != nil {
  403. m, err := s.r.Prepare(ctx, inp.ref, false, g)
  404. if err != nil {
  405. return err
  406. }
  407. inpMount = m
  408. return nil
  409. }
  410. inpMount = inp.mount
  411. return nil
  412. }
  413. }
  414. loadSecondaryInput := func(ctx context.Context) func() error {
  415. return func() error {
  416. inp, err := s.getInput(ctx, int(action.SecondaryInput), inputs, actions, g)
  417. if err != nil {
  418. return err
  419. }
  420. if inp.ref != nil {
  421. m, err := s.r.Prepare(ctx, inp.ref, true, g)
  422. if err != nil {
  423. return err
  424. }
  425. inpMountSecondary = m
  426. toRelease = append(toRelease, m)
  427. return nil
  428. }
  429. inpMountSecondary = inp.mount
  430. return nil
  431. }
  432. }
  433. loadUser := func(ctx context.Context, uopt *pb.UserOpt) (fileoptypes.Mount, error) {
  434. if uopt == nil {
  435. return nil, nil
  436. }
  437. switch u := uopt.User.(type) {
  438. case *pb.UserOpt_ByName:
  439. var m fileoptypes.Mount
  440. if u.ByName.Input < 0 {
  441. return nil, errors.Errorf("invalid user index: %d", u.ByName.Input)
  442. }
  443. inp, err := s.getInput(ctx, int(u.ByName.Input), inputs, actions, g)
  444. if err != nil {
  445. return nil, err
  446. }
  447. if inp.ref != nil {
  448. mm, err := s.r.Prepare(ctx, inp.ref, true, g)
  449. if err != nil {
  450. return nil, err
  451. }
  452. toRelease = append(toRelease, mm)
  453. m = mm
  454. } else {
  455. m = inp.mount
  456. }
  457. return m, nil
  458. default:
  459. return nil, nil
  460. }
  461. }
  462. loadOwner := func(ctx context.Context, chopt *pb.ChownOpt) (fileoptypes.Mount, fileoptypes.Mount, error) {
  463. if chopt == nil {
  464. return nil, nil, nil
  465. }
  466. um, err := loadUser(ctx, chopt.User)
  467. if err != nil {
  468. return nil, nil, err
  469. }
  470. gm, err := loadUser(ctx, chopt.Group)
  471. if err != nil {
  472. return nil, nil, err
  473. }
  474. return um, gm, nil
  475. }
  476. if action.Input != -1 && action.SecondaryInput != -1 {
  477. eg, ctx := errgroup.WithContext(ctx)
  478. eg.Go(loadInput(ctx))
  479. eg.Go(loadSecondaryInput(ctx))
  480. if err := eg.Wait(); err != nil {
  481. return nil, err
  482. }
  483. } else {
  484. if action.Input != -1 {
  485. if err := loadInput(ctx)(); err != nil {
  486. return nil, err
  487. }
  488. }
  489. if action.SecondaryInput != -1 {
  490. if err := loadSecondaryInput(ctx)(); err != nil {
  491. return nil, err
  492. }
  493. }
  494. }
  495. if inpMount == nil {
  496. m, err := s.r.Prepare(ctx, nil, false, g)
  497. if err != nil {
  498. return nil, err
  499. }
  500. inpMount = m
  501. }
  502. switch a := action.Action.(type) {
  503. case *pb.FileAction_Mkdir:
  504. user, group, err := loadOwner(ctx, a.Mkdir.Owner)
  505. if err != nil {
  506. return nil, err
  507. }
  508. if err := s.b.Mkdir(ctx, inpMount, user, group, *a.Mkdir); err != nil {
  509. return nil, err
  510. }
  511. case *pb.FileAction_Mkfile:
  512. user, group, err := loadOwner(ctx, a.Mkfile.Owner)
  513. if err != nil {
  514. return nil, err
  515. }
  516. if err := s.b.Mkfile(ctx, inpMount, user, group, *a.Mkfile); err != nil {
  517. return nil, err
  518. }
  519. case *pb.FileAction_Rm:
  520. if err := s.b.Rm(ctx, inpMount, *a.Rm); err != nil {
  521. return nil, err
  522. }
  523. case *pb.FileAction_Copy:
  524. if inpMountSecondary == nil {
  525. m, err := s.r.Prepare(ctx, nil, true, g)
  526. if err != nil {
  527. return nil, err
  528. }
  529. inpMountSecondary = m
  530. }
  531. user, group, err := loadOwner(ctx, a.Copy.Owner)
  532. if err != nil {
  533. return nil, err
  534. }
  535. if err := s.b.Copy(ctx, inpMountSecondary, inpMount, user, group, *a.Copy); err != nil {
  536. return nil, err
  537. }
  538. default:
  539. return nil, errors.Errorf("invalid action type %T", action.Action)
  540. }
  541. if inp.requiresCommit {
  542. ref, err := s.r.Commit(ctx, inpMount)
  543. if err != nil {
  544. return nil, err
  545. }
  546. inp.ref = ref
  547. } else {
  548. inp.mount = inpMount
  549. }
  550. s.mu.Lock()
  551. s.ins[idx] = inp
  552. s.mu.Unlock()
  553. return inp, nil
  554. })
  555. if err != nil {
  556. return input{}, err
  557. }
  558. return inp.(input), err
  559. }
  560. func isDefaultIndexes(idxs [][]int) bool {
  561. // Older version of checksum did not contain indexes for actions resulting in possibility for a wrong cache match.
  562. // We detect the most common pattern for indexes and maintain old checksum for that case to minimize cache misses on upgrade.
  563. // If a future change causes braking changes in instruction cache consider removing this exception.
  564. if len(idxs) == 0 {
  565. return false
  566. }
  567. for i, idx := range idxs {
  568. if len(idx) != 3 {
  569. return false
  570. }
  571. // input for first action is first input
  572. if i == 0 && idx[0] != 0 {
  573. return false
  574. }
  575. // input for other actions is previous action
  576. if i != 0 && idx[0] != len(idxs)+(i-1) {
  577. return false
  578. }
  579. // secondary input is second input or -1
  580. if idx[1] != -1 && idx[1] != 1 {
  581. return false
  582. }
  583. // last action creates output
  584. if i == len(idxs)-1 && idx[2] != 0 {
  585. return false
  586. }
  587. // other actions do not create an output
  588. if i != len(idxs)-1 && idx[2] != -1 {
  589. return false
  590. }
  591. }
  592. return true
  593. }