github.com/apptainer/singularity@v3.1.1+incompatible/internal/app/singularity/oci_attach_linux.go (about) 1 // Copyright (c) 2018-2019, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 package singularity 7 8 import ( 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net" 14 "os" 15 osignal "os/signal" 16 "sync" 17 "syscall" 18 19 "github.com/kr/pty" 20 21 specs "github.com/opencontainers/runtime-spec/specs-go" 22 "github.com/sylabs/singularity/internal/pkg/runtime/engines/oci" 23 "github.com/sylabs/singularity/internal/pkg/sylog" 24 "github.com/sylabs/singularity/pkg/ociruntime" 25 "github.com/sylabs/singularity/pkg/util/unix" 26 "golang.org/x/crypto/ssh/terminal" 27 ) 28 29 func resize(controlSocket string, oversized bool) { 30 ctrl := &ociruntime.Control{} 31 ctrl.ConsoleSize = &specs.Box{} 32 33 c, err := unix.Dial(controlSocket) 34 if err != nil { 35 sylog.Errorf("failed to connect to control socket") 36 return 37 } 38 defer c.Close() 39 40 rows, cols, err := pty.Getsize(os.Stdin) 41 if err != nil { 42 sylog.Errorf("terminal resize error: %s", err) 43 return 44 } 45 46 ctrl.ConsoleSize.Height = uint(rows) 47 ctrl.ConsoleSize.Width = uint(cols) 48 49 if oversized { 50 ctrl.ConsoleSize.Height++ 51 ctrl.ConsoleSize.Width++ 52 } 53 54 enc := json.NewEncoder(c) 55 if err != nil { 56 sylog.Errorf("%s", err) 57 return 58 } 59 60 if err := enc.Encode(ctrl); err != nil { 61 sylog.Errorf("%s", err) 62 return 63 } 64 } 65 66 func attach(engineConfig *oci.EngineConfig, run bool) error { 67 var ostate *terminal.State 68 var conn net.Conn 69 var wg sync.WaitGroup 70 71 state := &engineConfig.State 72 73 if state.AttachSocket == "" { 74 return fmt.Errorf("attach socket not available, container state: %s", state.Status) 75 } 76 if state.ControlSocket == "" { 77 return fmt.Errorf("control socket not available, container state: %s", state.Status) 78 } 79 80 hasTerminal := engineConfig.OciConfig.Process.Terminal 81 if hasTerminal && !terminal.IsTerminal(0) { 82 return fmt.Errorf("attach requires a terminal when terminal config is set to true") 83 } 84 85 var err error 86 conn, err = unix.Dial(state.AttachSocket) 87 if err != nil { 88 return err 89 } 90 defer conn.Close() 91 92 if hasTerminal { 93 ostate, _ = terminal.MakeRaw(0) 94 resize(state.ControlSocket, true) 95 resize(state.ControlSocket, false) 96 } 97 98 wg.Add(1) 99 100 go func() { 101 // catch SIGWINCH signal for terminal resize 102 signals := make(chan os.Signal, 1) 103 pid := state.Pid 104 osignal.Notify(signals) 105 106 for { 107 s := <-signals 108 switch s { 109 case syscall.SIGWINCH: 110 if hasTerminal { 111 resize(state.ControlSocket, false) 112 } 113 default: 114 syscall.Kill(pid, s.(syscall.Signal)) 115 } 116 } 117 }() 118 119 if hasTerminal || !run { 120 // Pipe session to bash and visa-versa 121 go func() { 122 io.Copy(os.Stdout, conn) 123 wg.Done() 124 }() 125 go func() { 126 io.Copy(conn, os.Stdin) 127 }() 128 wg.Wait() 129 130 if hasTerminal { 131 fmt.Printf("\r") 132 return terminal.Restore(0, ostate) 133 } 134 return nil 135 } 136 137 io.Copy(ioutil.Discard, conn) 138 return nil 139 } 140 141 // OciAttach attaches console to a running container 142 func OciAttach(containerID string) error { 143 engineConfig, err := getEngineConfig(containerID) 144 if err != nil { 145 return err 146 } 147 if engineConfig.GetState().Status != ociruntime.Running { 148 return fmt.Errorf("could not attach to %s: not in running state", containerID) 149 } 150 151 defer exitContainer(containerID, false) 152 153 return attach(engineConfig, false) 154 }