mirror of
				https://github.com/containers/podman.git
				synced 2025-10-31 01:50:50 +08:00 
			
		
		
		
	 898a8ad285
			
		
	
	898a8ad285
	
	
	
		
			
			Update containers common to the latest HEAD. Some bug fixes in libimage forced us to have a clearer separation between ordinary images and manifest lists. Hence, when looking up manifest lists without recursing into any of their instances, we need to use `LookupManifestList()`. Also account for some other changes in c/common (e.g., the changed order in the security labels). Further vendor the latest HEAD from Buildah which is required to get the bud tests to pass. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
		
			
				
	
	
		
			522 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			522 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package hcs
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"io"
 | |
| 	"sync"
 | |
| 	"syscall"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/Microsoft/hcsshim/internal/log"
 | |
| 	"github.com/Microsoft/hcsshim/internal/oc"
 | |
| 	"github.com/Microsoft/hcsshim/internal/vmcompute"
 | |
| 	"go.opencensus.io/trace"
 | |
| )
 | |
| 
 | |
| // ContainerError is an error encountered in HCS
 | |
| type Process struct {
 | |
| 	handleLock     sync.RWMutex
 | |
| 	handle         vmcompute.HcsProcess
 | |
| 	processID      int
 | |
| 	system         *System
 | |
| 	hasCachedStdio bool
 | |
| 	stdioLock      sync.Mutex
 | |
| 	stdin          io.WriteCloser
 | |
| 	stdout         io.ReadCloser
 | |
| 	stderr         io.ReadCloser
 | |
| 	callbackNumber uintptr
 | |
| 
 | |
| 	closedWaitOnce sync.Once
 | |
| 	waitBlock      chan struct{}
 | |
| 	exitCode       int
 | |
| 	waitError      error
 | |
| }
 | |
| 
 | |
| func newProcess(process vmcompute.HcsProcess, processID int, computeSystem *System) *Process {
 | |
| 	return &Process{
 | |
| 		handle:    process,
 | |
| 		processID: processID,
 | |
| 		system:    computeSystem,
 | |
| 		waitBlock: make(chan struct{}),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type processModifyRequest struct {
 | |
| 	Operation   string
 | |
| 	ConsoleSize *consoleSize `json:",omitempty"`
 | |
| 	CloseHandle *closeHandle `json:",omitempty"`
 | |
| }
 | |
| 
 | |
| type consoleSize struct {
 | |
| 	Height uint16
 | |
| 	Width  uint16
 | |
| }
 | |
| 
 | |
| type closeHandle struct {
 | |
| 	Handle string
 | |
| }
 | |
| 
 | |
| type processStatus struct {
 | |
| 	ProcessID      uint32
 | |
| 	Exited         bool
 | |
| 	ExitCode       uint32
 | |
| 	LastWaitResult int32
 | |
| }
 | |
| 
 | |
| const stdIn string = "StdIn"
 | |
| 
 | |
| const (
 | |
| 	modifyConsoleSize string = "ConsoleSize"
 | |
| 	modifyCloseHandle string = "CloseHandle"
 | |
| )
 | |
| 
 | |
| // Pid returns the process ID of the process within the container.
 | |
| func (process *Process) Pid() int {
 | |
| 	return process.processID
 | |
| }
 | |
| 
 | |
| // SystemID returns the ID of the process's compute system.
 | |
| func (process *Process) SystemID() string {
 | |
| 	return process.system.ID()
 | |
| }
 | |
| 
 | |
| func (process *Process) processSignalResult(ctx context.Context, err error) (bool, error) {
 | |
| 	switch err {
 | |
| 	case nil:
 | |
| 		return true, nil
 | |
| 	case ErrVmcomputeOperationInvalidState, ErrComputeSystemDoesNotExist, ErrElementNotFound:
 | |
| 		select {
 | |
| 		case <-process.waitBlock:
 | |
| 			// The process exit notification has already arrived.
 | |
| 		default:
 | |
| 			// The process should be gone, but we have not received the notification.
 | |
| 			// After a second, force unblock the process wait to work around a possible
 | |
| 			// deadlock in the HCS.
 | |
| 			go func() {
 | |
| 				time.Sleep(time.Second)
 | |
| 				process.closedWaitOnce.Do(func() {
 | |
| 					log.G(ctx).WithError(err).Warn("force unblocking process waits")
 | |
| 					process.exitCode = -1
 | |
| 					process.waitError = err
 | |
| 					close(process.waitBlock)
 | |
| 				})
 | |
| 			}()
 | |
| 		}
 | |
| 		return false, nil
 | |
| 	default:
 | |
| 		return false, err
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Signal signals the process with `options`.
 | |
| //
 | |
| // For LCOW `guestrequest.SignalProcessOptionsLCOW`.
 | |
| //
 | |
| // For WCOW `guestrequest.SignalProcessOptionsWCOW`.
 | |
| func (process *Process) Signal(ctx context.Context, options interface{}) (bool, error) {
 | |
| 	process.handleLock.RLock()
 | |
| 	defer process.handleLock.RUnlock()
 | |
| 
 | |
| 	operation := "hcs::Process::Signal"
 | |
| 
 | |
| 	if process.handle == 0 {
 | |
| 		return false, makeProcessError(process, operation, ErrAlreadyClosed, nil)
 | |
| 	}
 | |
| 
 | |
| 	optionsb, err := json.Marshal(options)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 
 | |
| 	resultJSON, err := vmcompute.HcsSignalProcess(ctx, process.handle, string(optionsb))
 | |
| 	events := processHcsResult(ctx, resultJSON)
 | |
| 	delivered, err := process.processSignalResult(ctx, err)
 | |
| 	if err != nil {
 | |
| 		err = makeProcessError(process, operation, err, events)
 | |
| 	}
 | |
| 	return delivered, err
 | |
| }
 | |
| 
 | |
| // Kill signals the process to terminate but does not wait for it to finish terminating.
 | |
| func (process *Process) Kill(ctx context.Context) (bool, error) {
 | |
| 	process.handleLock.RLock()
 | |
| 	defer process.handleLock.RUnlock()
 | |
| 
 | |
| 	operation := "hcs::Process::Kill"
 | |
| 
 | |
| 	if process.handle == 0 {
 | |
| 		return false, makeProcessError(process, operation, ErrAlreadyClosed, nil)
 | |
| 	}
 | |
| 
 | |
| 	resultJSON, err := vmcompute.HcsTerminateProcess(ctx, process.handle)
 | |
| 	events := processHcsResult(ctx, resultJSON)
 | |
| 	delivered, err := process.processSignalResult(ctx, err)
 | |
| 	if err != nil {
 | |
| 		err = makeProcessError(process, operation, err, events)
 | |
| 	}
 | |
| 	return delivered, err
 | |
| }
 | |
| 
 | |
| // waitBackground waits for the process exit notification. Once received sets
 | |
| // `process.waitError` (if any) and unblocks all `Wait` calls.
 | |
| //
 | |
| // This MUST be called exactly once per `process.handle` but `Wait` is safe to
 | |
| // call multiple times.
 | |
| func (process *Process) waitBackground() {
 | |
| 	operation := "hcs::Process::waitBackground"
 | |
| 	ctx, span := trace.StartSpan(context.Background(), operation)
 | |
| 	defer span.End()
 | |
| 	span.AddAttributes(
 | |
| 		trace.StringAttribute("cid", process.SystemID()),
 | |
| 		trace.Int64Attribute("pid", int64(process.processID)))
 | |
| 
 | |
| 	var (
 | |
| 		err            error
 | |
| 		exitCode       = -1
 | |
| 		propertiesJSON string
 | |
| 		resultJSON     string
 | |
| 	)
 | |
| 
 | |
| 	err = waitForNotification(ctx, process.callbackNumber, hcsNotificationProcessExited, nil)
 | |
| 	if err != nil {
 | |
| 		err = makeProcessError(process, operation, err, nil)
 | |
| 		log.G(ctx).WithError(err).Error("failed wait")
 | |
| 	} else {
 | |
| 		process.handleLock.RLock()
 | |
| 		defer process.handleLock.RUnlock()
 | |
| 
 | |
| 		// Make sure we didnt race with Close() here
 | |
| 		if process.handle != 0 {
 | |
| 			propertiesJSON, resultJSON, err = vmcompute.HcsGetProcessProperties(ctx, process.handle)
 | |
| 			events := processHcsResult(ctx, resultJSON)
 | |
| 			if err != nil {
 | |
| 				err = makeProcessError(process, operation, err, events) //nolint:ineffassign
 | |
| 			} else {
 | |
| 				properties := &processStatus{}
 | |
| 				err = json.Unmarshal([]byte(propertiesJSON), properties)
 | |
| 				if err != nil {
 | |
| 					err = makeProcessError(process, operation, err, nil) //nolint:ineffassign
 | |
| 				} else {
 | |
| 					if properties.LastWaitResult != 0 {
 | |
| 						log.G(ctx).WithField("wait-result", properties.LastWaitResult).Warning("non-zero last wait result")
 | |
| 					} else {
 | |
| 						exitCode = int(properties.ExitCode)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	log.G(ctx).WithField("exitCode", exitCode).Debug("process exited")
 | |
| 
 | |
| 	process.closedWaitOnce.Do(func() {
 | |
| 		process.exitCode = exitCode
 | |
| 		process.waitError = err
 | |
| 		close(process.waitBlock)
 | |
| 	})
 | |
| 	oc.SetSpanStatus(span, err)
 | |
| }
 | |
| 
 | |
| // Wait waits for the process to exit. If the process has already exited returns
 | |
| // the pervious error (if any).
 | |
| func (process *Process) Wait() error {
 | |
| 	<-process.waitBlock
 | |
| 	return process.waitError
 | |
| }
 | |
| 
 | |
| // ResizeConsole resizes the console of the process.
 | |
| func (process *Process) ResizeConsole(ctx context.Context, width, height uint16) error {
 | |
| 	process.handleLock.RLock()
 | |
| 	defer process.handleLock.RUnlock()
 | |
| 
 | |
| 	operation := "hcs::Process::ResizeConsole"
 | |
| 
 | |
| 	if process.handle == 0 {
 | |
| 		return makeProcessError(process, operation, ErrAlreadyClosed, nil)
 | |
| 	}
 | |
| 
 | |
| 	modifyRequest := processModifyRequest{
 | |
| 		Operation: modifyConsoleSize,
 | |
| 		ConsoleSize: &consoleSize{
 | |
| 			Height: height,
 | |
| 			Width:  width,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	modifyRequestb, err := json.Marshal(modifyRequest)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	resultJSON, err := vmcompute.HcsModifyProcess(ctx, process.handle, string(modifyRequestb))
 | |
| 	events := processHcsResult(ctx, resultJSON)
 | |
| 	if err != nil {
 | |
| 		return makeProcessError(process, operation, err, events)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // ExitCode returns the exit code of the process. The process must have
 | |
| // already terminated.
 | |
| func (process *Process) ExitCode() (int, error) {
 | |
| 	select {
 | |
| 	case <-process.waitBlock:
 | |
| 		if process.waitError != nil {
 | |
| 			return -1, process.waitError
 | |
| 		}
 | |
| 		return process.exitCode, nil
 | |
| 	default:
 | |
| 		return -1, makeProcessError(process, "hcs::Process::ExitCode", ErrInvalidProcessState, nil)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // StdioLegacy returns the stdin, stdout, and stderr pipes, respectively. Closing
 | |
| // these pipes does not close the underlying pipes. Once returned, these pipes
 | |
| // are the responsibility of the caller to close.
 | |
| func (process *Process) StdioLegacy() (_ io.WriteCloser, _ io.ReadCloser, _ io.ReadCloser, err error) {
 | |
| 	operation := "hcs::Process::StdioLegacy"
 | |
| 	ctx, span := trace.StartSpan(context.Background(), operation)
 | |
| 	defer span.End()
 | |
| 	defer func() { oc.SetSpanStatus(span, err) }()
 | |
| 	span.AddAttributes(
 | |
| 		trace.StringAttribute("cid", process.SystemID()),
 | |
| 		trace.Int64Attribute("pid", int64(process.processID)))
 | |
| 
 | |
| 	process.handleLock.RLock()
 | |
| 	defer process.handleLock.RUnlock()
 | |
| 
 | |
| 	if process.handle == 0 {
 | |
| 		return nil, nil, nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
 | |
| 	}
 | |
| 
 | |
| 	process.stdioLock.Lock()
 | |
| 	defer process.stdioLock.Unlock()
 | |
| 	if process.hasCachedStdio {
 | |
| 		stdin, stdout, stderr := process.stdin, process.stdout, process.stderr
 | |
| 		process.stdin, process.stdout, process.stderr = nil, nil, nil
 | |
| 		process.hasCachedStdio = false
 | |
| 		return stdin, stdout, stderr, nil
 | |
| 	}
 | |
| 
 | |
| 	processInfo, resultJSON, err := vmcompute.HcsGetProcessInfo(ctx, process.handle)
 | |
| 	events := processHcsResult(ctx, resultJSON)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, nil, makeProcessError(process, operation, err, events)
 | |
| 	}
 | |
| 
 | |
| 	pipes, err := makeOpenFiles([]syscall.Handle{processInfo.StdInput, processInfo.StdOutput, processInfo.StdError})
 | |
| 	if err != nil {
 | |
| 		return nil, nil, nil, makeProcessError(process, operation, err, nil)
 | |
| 	}
 | |
| 
 | |
| 	return pipes[0], pipes[1], pipes[2], nil
 | |
| }
 | |
| 
 | |
| // Stdio returns the stdin, stdout, and stderr pipes, respectively.
 | |
| // To close them, close the process handle.
 | |
| func (process *Process) Stdio() (stdin io.Writer, stdout, stderr io.Reader) {
 | |
| 	process.stdioLock.Lock()
 | |
| 	defer process.stdioLock.Unlock()
 | |
| 	return process.stdin, process.stdout, process.stderr
 | |
| }
 | |
| 
 | |
| // CloseStdin closes the write side of the stdin pipe so that the process is
 | |
| // notified on the read side that there is no more data in stdin.
 | |
| func (process *Process) CloseStdin(ctx context.Context) error {
 | |
| 	process.handleLock.RLock()
 | |
| 	defer process.handleLock.RUnlock()
 | |
| 
 | |
| 	operation := "hcs::Process::CloseStdin"
 | |
| 
 | |
| 	if process.handle == 0 {
 | |
| 		return makeProcessError(process, operation, ErrAlreadyClosed, nil)
 | |
| 	}
 | |
| 
 | |
| 	modifyRequest := processModifyRequest{
 | |
| 		Operation: modifyCloseHandle,
 | |
| 		CloseHandle: &closeHandle{
 | |
| 			Handle: stdIn,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	modifyRequestb, err := json.Marshal(modifyRequest)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	resultJSON, err := vmcompute.HcsModifyProcess(ctx, process.handle, string(modifyRequestb))
 | |
| 	events := processHcsResult(ctx, resultJSON)
 | |
| 	if err != nil {
 | |
| 		return makeProcessError(process, operation, err, events)
 | |
| 	}
 | |
| 
 | |
| 	process.stdioLock.Lock()
 | |
| 	if process.stdin != nil {
 | |
| 		process.stdin.Close()
 | |
| 		process.stdin = nil
 | |
| 	}
 | |
| 	process.stdioLock.Unlock()
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (process *Process) CloseStdout(ctx context.Context) (err error) {
 | |
| 	ctx, span := trace.StartSpan(ctx, "hcs::Process::CloseStdout") //nolint:ineffassign,staticcheck
 | |
| 	defer span.End()
 | |
| 	defer func() { oc.SetSpanStatus(span, err) }()
 | |
| 	span.AddAttributes(
 | |
| 		trace.StringAttribute("cid", process.SystemID()),
 | |
| 		trace.Int64Attribute("pid", int64(process.processID)))
 | |
| 
 | |
| 	process.handleLock.Lock()
 | |
| 	defer process.handleLock.Unlock()
 | |
| 
 | |
| 	if process.handle == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	process.stdioLock.Lock()
 | |
| 	defer process.stdioLock.Unlock()
 | |
| 	if process.stdout != nil {
 | |
| 		process.stdout.Close()
 | |
| 		process.stdout = nil
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (process *Process) CloseStderr(ctx context.Context) (err error) {
 | |
| 	ctx, span := trace.StartSpan(ctx, "hcs::Process::CloseStderr") //nolint:ineffassign,staticcheck
 | |
| 	defer span.End()
 | |
| 	defer func() { oc.SetSpanStatus(span, err) }()
 | |
| 	span.AddAttributes(
 | |
| 		trace.StringAttribute("cid", process.SystemID()),
 | |
| 		trace.Int64Attribute("pid", int64(process.processID)))
 | |
| 
 | |
| 	process.handleLock.Lock()
 | |
| 	defer process.handleLock.Unlock()
 | |
| 
 | |
| 	if process.handle == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	process.stdioLock.Lock()
 | |
| 	defer process.stdioLock.Unlock()
 | |
| 	if process.stderr != nil {
 | |
| 		process.stderr.Close()
 | |
| 		process.stderr = nil
 | |
| 
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Close cleans up any state associated with the process but does not kill
 | |
| // or wait on it.
 | |
| func (process *Process) Close() (err error) {
 | |
| 	operation := "hcs::Process::Close"
 | |
| 	ctx, span := trace.StartSpan(context.Background(), operation)
 | |
| 	defer span.End()
 | |
| 	defer func() { oc.SetSpanStatus(span, err) }()
 | |
| 	span.AddAttributes(
 | |
| 		trace.StringAttribute("cid", process.SystemID()),
 | |
| 		trace.Int64Attribute("pid", int64(process.processID)))
 | |
| 
 | |
| 	process.handleLock.Lock()
 | |
| 	defer process.handleLock.Unlock()
 | |
| 
 | |
| 	// Don't double free this
 | |
| 	if process.handle == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	process.stdioLock.Lock()
 | |
| 	if process.stdin != nil {
 | |
| 		process.stdin.Close()
 | |
| 		process.stdin = nil
 | |
| 	}
 | |
| 	if process.stdout != nil {
 | |
| 		process.stdout.Close()
 | |
| 		process.stdout = nil
 | |
| 	}
 | |
| 	if process.stderr != nil {
 | |
| 		process.stderr.Close()
 | |
| 		process.stderr = nil
 | |
| 	}
 | |
| 	process.stdioLock.Unlock()
 | |
| 
 | |
| 	if err = process.unregisterCallback(ctx); err != nil {
 | |
| 		return makeProcessError(process, operation, err, nil)
 | |
| 	}
 | |
| 
 | |
| 	if err = vmcompute.HcsCloseProcess(ctx, process.handle); err != nil {
 | |
| 		return makeProcessError(process, operation, err, nil)
 | |
| 	}
 | |
| 
 | |
| 	process.handle = 0
 | |
| 	process.closedWaitOnce.Do(func() {
 | |
| 		process.exitCode = -1
 | |
| 		process.waitError = ErrAlreadyClosed
 | |
| 		close(process.waitBlock)
 | |
| 	})
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (process *Process) registerCallback(ctx context.Context) error {
 | |
| 	callbackContext := ¬ificationWatcherContext{
 | |
| 		channels:  newProcessChannels(),
 | |
| 		systemID:  process.SystemID(),
 | |
| 		processID: process.processID,
 | |
| 	}
 | |
| 
 | |
| 	callbackMapLock.Lock()
 | |
| 	callbackNumber := nextCallback
 | |
| 	nextCallback++
 | |
| 	callbackMap[callbackNumber] = callbackContext
 | |
| 	callbackMapLock.Unlock()
 | |
| 
 | |
| 	callbackHandle, err := vmcompute.HcsRegisterProcessCallback(ctx, process.handle, notificationWatcherCallback, callbackNumber)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	callbackContext.handle = callbackHandle
 | |
| 	process.callbackNumber = callbackNumber
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (process *Process) unregisterCallback(ctx context.Context) error {
 | |
| 	callbackNumber := process.callbackNumber
 | |
| 
 | |
| 	callbackMapLock.RLock()
 | |
| 	callbackContext := callbackMap[callbackNumber]
 | |
| 	callbackMapLock.RUnlock()
 | |
| 
 | |
| 	if callbackContext == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	handle := callbackContext.handle
 | |
| 
 | |
| 	if handle == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// vmcompute.HcsUnregisterProcessCallback has its own synchronization to
 | |
| 	// wait for all callbacks to complete. We must NOT hold the callbackMapLock.
 | |
| 	err := vmcompute.HcsUnregisterProcessCallback(ctx, handle)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	closeChannels(callbackContext.channels)
 | |
| 
 | |
| 	callbackMapLock.Lock()
 | |
| 	delete(callbackMap, callbackNumber)
 | |
| 	callbackMapLock.Unlock()
 | |
| 
 | |
| 	handle = 0 //nolint:ineffassign
 | |
| 
 | |
| 	return nil
 | |
| }
 |