1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package v2
18
19import (
20	"context"
21	"fmt"
22	"io"
23	"net"
24	"os"
25	"sync"
26	"time"
27
28	"github.com/containerd/containerd/namespaces"
29	"github.com/pkg/errors"
30)
31
32type deferredPipeConnection struct {
33	ctx context.Context
34
35	wg   sync.WaitGroup
36	once sync.Once
37
38	c      net.Conn
39	conerr error
40}
41
42func (dpc *deferredPipeConnection) Read(p []byte) (n int, err error) {
43	if dpc.c == nil {
44		dpc.wg.Wait()
45		if dpc.c == nil {
46			return 0, dpc.conerr
47		}
48	}
49	return dpc.c.Read(p)
50}
51func (dpc *deferredPipeConnection) Close() error {
52	var err error
53	dpc.once.Do(func() {
54		dpc.wg.Wait()
55		if dpc.c != nil {
56			err = dpc.c.Close()
57		} else if dpc.conerr != nil {
58			err = dpc.conerr
59		}
60	})
61	return err
62}
63
64// openShimLog on Windows acts as the client of the log pipe. In this way the
65// containerd daemon can reconnect to the shim log stream if it is restarted.
66func openShimLog(ctx context.Context, bundle *Bundle, dialer func(string, time.Duration) (net.Conn, error)) (io.ReadCloser, error) {
67	ns, err := namespaces.NamespaceRequired(ctx)
68	if err != nil {
69		return nil, err
70	}
71	dpc := &deferredPipeConnection{
72		ctx: ctx,
73	}
74	dpc.wg.Add(1)
75	go func() {
76		c, conerr := dialer(
77			fmt.Sprintf("\\\\.\\pipe\\containerd-shim-%s-%s-log", ns, bundle.ID),
78			time.Second*10,
79		)
80		if conerr != nil {
81			dpc.conerr = errors.Wrap(conerr, "failed to connect to shim log")
82		}
83		dpc.c = c
84		dpc.wg.Done()
85	}()
86	return dpc, nil
87}
88
89func checkCopyShimLogError(ctx context.Context, err error) error {
90	// When using a multi-container shim the 2nd to Nth container in the
91	// shim will not have a separate log pipe. Ignore the failure log
92	// message here when the shim connect times out.
93	if errors.Is(err, os.ErrNotExist) {
94		return nil
95	}
96	return err
97}
98