| 548 | } |
| 549 | |
| 550 | func (m *Mount) mountWithHelper(helperBinary, typePrefix, target string) error { |
| 551 | // helperBinary: "mount.fuse3" |
| 552 | // target: "/foo/merged" |
| 553 | // m.Type: "fuse3.fuse-overlayfs" |
| 554 | // command: "mount.fuse3 overlay /foo/merged -o lowerdir=/foo/lower2:/foo/lower1,upperdir=/foo/upper,workdir=/foo/work -t fuse-overlayfs" |
| 555 | args := []string{m.Source, target} |
| 556 | for _, o := range m.Options { |
| 557 | args = append(args, "-o", o) |
| 558 | } |
| 559 | args = append(args, "-t", strings.TrimPrefix(m.Type, typePrefix)) |
| 560 | |
| 561 | infoBeforeMount, err := Lookup(target) |
| 562 | if err != nil { |
| 563 | return err |
| 564 | } |
| 565 | |
| 566 | // cmd.CombinedOutput() may intermittently return ECHILD because of our signal handling in shim. |
| 567 | // See #4387 and wait(2). |
| 568 | const retriesOnECHILD = 10 |
| 569 | for range retriesOnECHILD { |
| 570 | cmd := exec.Command(helperBinary, args...) |
| 571 | out, err := cmd.CombinedOutput() |
| 572 | if err == nil { |
| 573 | return nil |
| 574 | } |
| 575 | if !errors.Is(err, unix.ECHILD) { |
| 576 | return fmt.Errorf("mount helper [%s %v] failed: %q: %w", helperBinary, args, string(out), err) |
| 577 | } |
| 578 | // We got ECHILD, we are not sure whether the mount was successful. |
| 579 | // If the mount ID has changed, we are sure we got some new mount, but still not sure it is fully completed. |
| 580 | // So we attempt to unmount the new mount before retrying. |
| 581 | infoAfterMount, err := Lookup(target) |
| 582 | if err != nil { |
| 583 | return err |
| 584 | } |
| 585 | if infoAfterMount.ID != infoBeforeMount.ID { |
| 586 | _ = unmount(target, 0) |
| 587 | } |
| 588 | } |
| 589 | return fmt.Errorf("mount helper [%s %v] failed with ECHILD (retried %d times)", helperBinary, args, retriesOnECHILD) |
| 590 | } |