podman

Форк
0
/
oci_conmon_attach_common.go 
316 строк · 9.0 Кб
1
//go:build !remote && (linux || freebsd)
2

3
package libpod
4

5
import (
6
	"context"
7
	"errors"
8
	"fmt"
9
	"io"
10
	"net"
11
	"os"
12
	"path/filepath"
13
	"syscall"
14

15
	"github.com/containers/common/pkg/config"
16
	"github.com/containers/common/pkg/detach"
17
	"github.com/containers/common/pkg/resize"
18
	"github.com/containers/podman/v5/libpod/define"
19
	"github.com/containers/podman/v5/pkg/errorhandling"
20
	"github.com/moby/term"
21
	"github.com/sirupsen/logrus"
22
	"golang.org/x/sys/unix"
23
)
24

25
/* Sync with stdpipe_t in conmon.c */
26
const (
27
	AttachPipeStdin  = 1
28
	AttachPipeStdout = 2
29
	AttachPipeStderr = 3
30
)
31

32
// Attach to the given container.
33
// Does not check if state is appropriate.
34
// started is only required if startContainer is true.
35
func (r *ConmonOCIRuntime) Attach(c *Container, params *AttachOptions) error {
36
	passthrough := c.LogDriver() == define.PassthroughLogging || c.LogDriver() == define.PassthroughTTYLogging
37

38
	if params == nil || params.Streams == nil {
39
		return fmt.Errorf("must provide parameters to Attach: %w", define.ErrInternal)
40
	}
41

42
	if !params.Streams.AttachOutput && !params.Streams.AttachError && !params.Streams.AttachInput && !passthrough {
43
		return fmt.Errorf("must provide at least one stream to attach to: %w", define.ErrInvalidArg)
44
	}
45
	if params.Start && params.Started == nil {
46
		return fmt.Errorf("started chan not passed when startContainer set: %w", define.ErrInternal)
47
	}
48

49
	keys := config.DefaultDetachKeys
50
	if params.DetachKeys != nil {
51
		keys = *params.DetachKeys
52
	}
53

54
	detachKeys, err := processDetachKeys(keys)
55
	if err != nil {
56
		return err
57
	}
58

59
	var conn *net.UnixConn
60
	if !passthrough {
61
		logrus.Debugf("Attaching to container %s", c.ID())
62

63
		// If we have a resize, do it.
64
		if params.InitialSize != nil {
65
			if err := r.AttachResize(c, *params.InitialSize); err != nil {
66
				return err
67
			}
68
		}
69

70
		attachSock, err := c.AttachSocketPath()
71
		if err != nil {
72
			return err
73
		}
74

75
		conn, err = openUnixSocket(attachSock)
76
		if err != nil {
77
			return fmt.Errorf("failed to connect to container's attach socket: %v: %w", attachSock, err)
78
		}
79
		defer func() {
80
			if err := conn.Close(); err != nil {
81
				logrus.Errorf("unable to close socket: %q", err)
82
			}
83
		}()
84
	}
85

86
	// If starting was requested, start the container and notify when that's
87
	// done.
88
	if params.Start {
89
		if err := c.start(context.TODO()); err != nil {
90
			return err
91
		}
92
		params.Started <- true
93
	}
94

95
	if passthrough {
96
		return nil
97
	}
98

99
	receiveStdoutError, stdinDone := setupStdioChannels(params.Streams, conn, detachKeys)
100
	if params.AttachReady != nil {
101
		params.AttachReady <- true
102
	}
103
	return readStdio(conn, params.Streams, receiveStdoutError, stdinDone)
104
}
105

106
// Attach to the given container's exec session.
107
//
108
// attachFd and startFd must be open file descriptors. attachFd must be the
109
// output side of the fd and is used for two things:
110
//
111
//  1. conmon will first send a nonce value across the pipe indicating it has
112
//     set up its side of the console socket this ensures attachToExec gets all of
113
//     the output of the called process.
114
//
115
//  2. conmon will then send the exit code of the exec process, or an error in the exec session.
116
//
117
// startFd must be the input side of the fd.
118
//
119
// newSize resizes the tty to this size before the process is started, must be
120
// nil if the exec session has no tty
121
//
122
// conmon will wait to start the exec session until the parent process has set up the console socket.
123
//
124
// Once attachToExec successfully attaches to the console socket, the child
125
// conmon process responsible for calling runtime exec will read from the
126
// output side of start fd, thus learning to start the child process.
127
//
128
// Thus, the order goes as follow:
129
// 1. conmon parent process sets up its console socket. sends on attachFd
130
// 2. attachToExec attaches to the console socket after reading on attachFd and resizes the tty
131
// 3. child waits on startFd for attachToExec to attach to said console socket
132
// 4. attachToExec sends on startFd, signalling it has attached to the socket and child is ready to go
133
// 5. child receives on startFd, runs the runtime exec command
134
// attachToExec is responsible for closing startFd and attachFd
135
func (c *Container) attachToExec(streams *define.AttachStreams, keys *string, sessionID string, startFd, attachFd *os.File, newSize *resize.TerminalSize) error {
136
	if !streams.AttachOutput && !streams.AttachError && !streams.AttachInput {
137
		return fmt.Errorf("must provide at least one stream to attach to: %w", define.ErrInvalidArg)
138
	}
139
	if startFd == nil || attachFd == nil {
140
		return fmt.Errorf("start sync pipe and attach sync pipe must be defined for exec attach: %w", define.ErrInvalidArg)
141
	}
142

143
	defer errorhandling.CloseQuiet(startFd)
144
	defer errorhandling.CloseQuiet(attachFd)
145

146
	detachString := config.DefaultDetachKeys
147
	if keys != nil {
148
		detachString = *keys
149
	}
150
	detachKeys, err := processDetachKeys(detachString)
151
	if err != nil {
152
		return err
153
	}
154

155
	logrus.Debugf("Attaching to container %s exec session %s", c.ID(), sessionID)
156

157
	// set up the socket path, such that it is the correct length and location for exec
158
	sockPath, err := c.execAttachSocketPath(sessionID)
159
	if err != nil {
160
		return err
161
	}
162

163
	// 2: read from attachFd that the parent process has set up the console socket
164
	if _, err := readConmonPipeData(c.ociRuntime.Name(), attachFd, ""); err != nil {
165
		return err
166
	}
167

168
	// resize before we start the container process
169
	if newSize != nil {
170
		err = c.ociRuntime.ExecAttachResize(c, sessionID, *newSize)
171
		if err != nil {
172
			logrus.Warnf("Resize failed: %v", err)
173
		}
174
	}
175

176
	// 2: then attach
177
	conn, err := openUnixSocket(sockPath)
178
	if err != nil {
179
		return fmt.Errorf("failed to connect to container's attach socket: %v: %w", sockPath, err)
180
	}
181
	defer func() {
182
		if err := conn.Close(); err != nil {
183
			logrus.Errorf("Unable to close socket: %q", err)
184
		}
185
	}()
186

187
	// start listening on stdio of the process
188
	receiveStdoutError, stdinDone := setupStdioChannels(streams, conn, detachKeys)
189

190
	// 4: send start message to child
191
	if err := writeConmonPipeData(startFd); err != nil {
192
		return err
193
	}
194

195
	return readStdio(conn, streams, receiveStdoutError, stdinDone)
196
}
197

198
func processDetachKeys(keys string) ([]byte, error) {
199
	// Check the validity of the provided keys first
200
	if len(keys) == 0 {
201
		return []byte{}, nil
202
	}
203
	detachKeys, err := term.ToBytes(keys)
204
	if err != nil {
205
		return nil, fmt.Errorf("invalid detach keys: %w", err)
206
	}
207
	return detachKeys, nil
208
}
209

210
func registerResizeFunc(r <-chan resize.TerminalSize, bundlePath string) {
211
	resize.HandleResizing(r, func(size resize.TerminalSize) {
212
		controlPath := filepath.Join(bundlePath, "ctl")
213
		controlFile, err := os.OpenFile(controlPath, unix.O_WRONLY, 0)
214
		if err != nil {
215
			logrus.Debugf("Could not open ctl file: %v", err)
216
			return
217
		}
218
		defer controlFile.Close()
219

220
		logrus.Debugf("Received a resize event: %+v", size)
221
		if _, err = fmt.Fprintf(controlFile, "%d %d %d\n", 1, size.Height, size.Width); err != nil {
222
			logrus.Warnf("Failed to write to control file to resize terminal: %v", err)
223
		}
224
	})
225
}
226

227
func setupStdioChannels(streams *define.AttachStreams, conn *net.UnixConn, detachKeys []byte) (chan error, chan error) {
228
	receiveStdoutError := make(chan error)
229
	go func() {
230
		receiveStdoutError <- redirectResponseToOutputStreams(streams.OutputStream, streams.ErrorStream, streams.AttachOutput, streams.AttachError, conn)
231
	}()
232

233
	stdinDone := make(chan error)
234
	go func() {
235
		var err error
236
		if streams.AttachInput {
237
			_, err = detach.Copy(conn, streams.InputStream, detachKeys)
238
		}
239
		stdinDone <- err
240
	}()
241

242
	return receiveStdoutError, stdinDone
243
}
244

245
func redirectResponseToOutputStreams(outputStream, errorStream io.Writer, writeOutput, writeError bool, conn io.Reader) error {
246
	var err error
247
	buf := make([]byte, 8192+1) /* Sync with conmon STDIO_BUF_SIZE */
248
	for {
249
		nr, er := conn.Read(buf)
250
		if nr > 0 {
251
			var dst io.Writer
252
			var doWrite bool
253
			switch buf[0] {
254
			case AttachPipeStdout:
255
				dst = outputStream
256
				doWrite = writeOutput
257
			case AttachPipeStderr:
258
				dst = errorStream
259
				doWrite = writeError
260
			default:
261
				logrus.Infof("Received unexpected attach type %+d", buf[0])
262
			}
263
			if dst == nil {
264
				return errors.New("output destination cannot be nil")
265
			}
266

267
			if doWrite {
268
				nw, ew := dst.Write(buf[1:nr])
269
				if ew != nil {
270
					err = ew
271
					break
272
				}
273
				if nr != nw+1 {
274
					err = io.ErrShortWrite
275
					break
276
				}
277
			}
278
		}
279
		if errors.Is(er, io.EOF) || errors.Is(er, syscall.ECONNRESET) {
280
			break
281
		}
282
		if er != nil {
283
			err = er
284
			break
285
		}
286
	}
287
	return err
288
}
289

290
func readStdio(conn *net.UnixConn, streams *define.AttachStreams, receiveStdoutError, stdinDone chan error) error {
291
	var err error
292
	select {
293
	case err = <-receiveStdoutError:
294
		if err := socketCloseWrite(conn); err != nil {
295
			logrus.Errorf("Failed to close stdin: %v", err)
296
		}
297
		return err
298
	case err = <-stdinDone:
299
		if err == define.ErrDetach {
300
			if err := socketCloseWrite(conn); err != nil {
301
				logrus.Errorf("Failed to close stdin: %v", err)
302
			}
303
			return err
304
		}
305
		if err == nil {
306
			// copy stdin is done, close it
307
			if connErr := socketCloseWrite(conn); connErr != nil {
308
				logrus.Errorf("Unable to close conn: %v", connErr)
309
			}
310
		}
311
		if streams.AttachOutput || streams.AttachError {
312
			return <-receiveStdoutError
313
		}
314
	}
315
	return nil
316
}
317

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.