github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/runtime/engines/oci/process.go (about) 1 // Copyright (c) 2018, 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 oci 7 8 import ( 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net" 14 "os" 15 osexec "os/exec" 16 "os/signal" 17 "path/filepath" 18 "strconv" 19 "strings" 20 "syscall" 21 22 "github.com/sylabs/singularity/pkg/util/copy" 23 24 "github.com/kr/pty" 25 26 specs "github.com/opencontainers/runtime-spec/specs-go" 27 "github.com/sylabs/singularity/pkg/ociruntime" 28 "github.com/sylabs/singularity/pkg/util/rlimit" 29 "github.com/sylabs/singularity/pkg/util/unix" 30 31 "github.com/sylabs/singularity/internal/pkg/instance" 32 "github.com/sylabs/singularity/internal/pkg/util/exec" 33 34 "github.com/sylabs/singularity/internal/pkg/security" 35 "github.com/sylabs/singularity/internal/pkg/sylog" 36 ) 37 38 func setRlimit(rlimits []specs.POSIXRlimit) error { 39 var resources []string 40 41 for _, rl := range rlimits { 42 if err := rlimit.Set(rl.Type, rl.Soft, rl.Hard); err != nil { 43 return err 44 } 45 for _, t := range resources { 46 if t == rl.Type { 47 return fmt.Errorf("%s was already set", t) 48 } 49 } 50 resources = append(resources, rl.Type) 51 } 52 53 return nil 54 } 55 56 func (engine *EngineOperations) emptyProcess(masterConn net.Conn) error { 57 // pause process on next read 58 if _, err := masterConn.Write([]byte("t")); err != nil { 59 return fmt.Errorf("failed to pause process: %s", err) 60 } 61 62 // block on read start given 63 data := make([]byte, 1) 64 if _, err := masterConn.Read(data); err != nil { 65 return fmt.Errorf("failed to receive ack from master: %s", err) 66 } 67 68 var status syscall.WaitStatus 69 signals := make(chan os.Signal, 1) 70 signal.Notify(signals, syscall.SIGCHLD, syscall.SIGINT, syscall.SIGTERM) 71 72 if err := security.Configure(&engine.EngineConfig.OciConfig.Spec); err != nil { 73 return fmt.Errorf("failed to apply security configuration: %s", err) 74 } 75 76 masterConn.Close() 77 78 for { 79 s := <-signals 80 switch s { 81 case syscall.SIGCHLD: 82 for { 83 if pid, _ := syscall.Wait4(-1, &status, syscall.WNOHANG, nil); pid <= 0 { 84 break 85 } 86 } 87 case syscall.SIGINT, syscall.SIGTERM: 88 os.Exit(0) 89 } 90 } 91 } 92 93 // StartProcess starts the process 94 func (engine *EngineOperations) StartProcess(masterConn net.Conn) error { 95 cwd := engine.EngineConfig.OciConfig.Process.Cwd 96 97 if cwd == "" { 98 cwd = "/" 99 } 100 101 if !filepath.IsAbs(cwd) { 102 return fmt.Errorf("cwd property must be an absolute path") 103 } 104 105 if err := os.Chdir(cwd); err != nil { 106 return fmt.Errorf("can't enter in current working directory: %s", err) 107 } 108 109 if err := setRlimit(engine.EngineConfig.OciConfig.Process.Rlimits); err != nil { 110 return err 111 } 112 113 if engine.EngineConfig.EmptyProcess { 114 return engine.emptyProcess(masterConn) 115 } 116 117 args := engine.EngineConfig.OciConfig.Process.Args 118 env := engine.EngineConfig.OciConfig.Process.Env 119 120 for _, e := range engine.EngineConfig.OciConfig.Process.Env { 121 if strings.HasPrefix(e, "PATH=") { 122 os.Setenv("PATH", e[5:]) 123 } 124 } 125 126 bpath, err := osexec.LookPath(args[0]) 127 if err != nil { 128 return fmt.Errorf("%s", err) 129 } 130 args[0] = bpath 131 132 if engine.EngineConfig.MasterPts != -1 { 133 slaveFd := engine.EngineConfig.SlavePts 134 if err := syscall.Dup3(slaveFd, int(os.Stdin.Fd()), 0); err != nil { 135 return err 136 } 137 if err := syscall.Dup3(slaveFd, int(os.Stdout.Fd()), 0); err != nil { 138 return err 139 } 140 if err := syscall.Dup3(slaveFd, int(os.Stderr.Fd()), 0); err != nil { 141 return err 142 } 143 if err := syscall.Close(engine.EngineConfig.MasterPts); err != nil { 144 return err 145 } 146 if err := syscall.Close(slaveFd); err != nil { 147 return err 148 } 149 if _, err := syscall.Setsid(); err != nil { 150 return err 151 } 152 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, os.Stdin.Fd(), uintptr(syscall.TIOCSCTTY), 1); err != 0 { 153 return fmt.Errorf("failed to set crontrolling terminal: %s", err.Error()) 154 } 155 } else if engine.EngineConfig.OutputStreams[1] != -1 { 156 if err := syscall.Dup3(engine.EngineConfig.OutputStreams[1], int(os.Stdout.Fd()), 0); err != nil { 157 return err 158 } 159 if err := syscall.Close(engine.EngineConfig.OutputStreams[1]); err != nil { 160 return err 161 } 162 if err := syscall.Close(engine.EngineConfig.OutputStreams[0]); err != nil { 163 return err 164 } 165 166 if err := syscall.Dup3(engine.EngineConfig.ErrorStreams[1], int(os.Stderr.Fd()), 0); err != nil { 167 return err 168 } 169 if err := syscall.Close(engine.EngineConfig.ErrorStreams[1]); err != nil { 170 return err 171 } 172 if err := syscall.Close(engine.EngineConfig.ErrorStreams[0]); err != nil { 173 return err 174 } 175 176 if err := syscall.Dup3(engine.EngineConfig.InputStreams[1], int(os.Stdin.Fd()), 0); err != nil { 177 return err 178 } 179 if err := syscall.Close(engine.EngineConfig.InputStreams[1]); err != nil { 180 return err 181 } 182 if err := syscall.Close(engine.EngineConfig.InputStreams[0]); err != nil { 183 return err 184 } 185 } 186 187 // trigger pre-start process 188 if _, err := masterConn.Write([]byte("t")); err != nil { 189 return fmt.Errorf("failed to pause process: %s", err) 190 } 191 if !engine.EngineConfig.Exec { 192 // block on read start given 193 data := make([]byte, 1) 194 if _, err := masterConn.Read(data); err != nil { 195 return fmt.Errorf("failed to receive start signal: %s", err) 196 } 197 } 198 199 if err := security.Configure(&engine.EngineConfig.OciConfig.Spec); err != nil { 200 return fmt.Errorf("failed to apply security configuration: %s", err) 201 } 202 203 err = syscall.Exec(args[0], args, env) 204 return fmt.Errorf("exec %s failed: %s", args[0], err) 205 } 206 207 // PreStartProcess will be executed in master context 208 func (engine *EngineOperations) PreStartProcess(pid int, masterConn net.Conn, fatalChan chan error) error { 209 if engine.EngineConfig.Exec { 210 return nil 211 } 212 213 file, err := instance.Get(engine.CommonConfig.ContainerID, instance.OciSubDir) 214 if err != nil { 215 return err 216 } 217 engine.EngineConfig.State.AttachSocket = filepath.Join(filepath.Dir(file.Path), "attach.sock") 218 219 attach, err := unix.CreateSocket(engine.EngineConfig.State.AttachSocket) 220 if err != nil { 221 return err 222 } 223 224 engine.EngineConfig.State.ControlSocket = filepath.Join(filepath.Dir(file.Path), "control.sock") 225 226 control, err := unix.CreateSocket(engine.EngineConfig.State.ControlSocket) 227 if err != nil { 228 return err 229 } 230 231 logPath := engine.EngineConfig.GetLogPath() 232 if logPath == "" { 233 containerID := engine.CommonConfig.ContainerID 234 dir, err := instance.GetDirPrivileged(containerID, instance.OciSubDir) 235 if err != nil { 236 return err 237 } 238 logPath = filepath.Join(dir, containerID+".log") 239 } 240 241 format := engine.EngineConfig.GetLogFormat() 242 formatter, ok := instance.LogFormats[format] 243 if !ok { 244 return fmt.Errorf("log format %s is not supported", format) 245 } 246 247 logger, err := instance.NewLogger(logPath, formatter) 248 if err != nil { 249 return err 250 } 251 252 pidFile := engine.EngineConfig.GetPidFile() 253 if pidFile != "" { 254 if err := ioutil.WriteFile(pidFile, []byte(strconv.Itoa(pid)), 0644); err != nil { 255 return err 256 } 257 } 258 259 if err := engine.updateState(ociruntime.Created); err != nil { 260 return err 261 } 262 263 start := make(chan bool, 1) 264 265 go engine.handleControl(masterConn, attach, control, logger, start, fatalChan) 266 267 hooks := engine.EngineConfig.OciConfig.Hooks 268 if hooks != nil { 269 for _, h := range hooks.Prestart { 270 if err := exec.Hook(&h, &engine.EngineConfig.State.State); err != nil { 271 return err 272 } 273 } 274 } 275 276 // detach process 277 syscall.Kill(os.Getppid(), syscall.SIGUSR1) 278 279 // block until start event received 280 <-start 281 close(start) 282 283 return nil 284 } 285 286 // PostStartProcess will execute code in master context after execution of container 287 // process, typically to write instance state/config files or execute post start OCI hook 288 func (engine *EngineOperations) PostStartProcess(pid int) error { 289 if err := engine.updateState(ociruntime.Running); err != nil { 290 return err 291 } 292 hooks := engine.EngineConfig.OciConfig.Hooks 293 if hooks != nil { 294 for _, h := range hooks.Poststart { 295 if err := exec.Hook(&h, &engine.EngineConfig.State.State); err != nil { 296 sylog.Warningf("%s", err) 297 } 298 } 299 } 300 return nil 301 } 302 303 func (engine *EngineOperations) handleStream(l net.Listener, logger *instance.Logger, fatalChan chan error) { 304 var stdout io.ReadWriteCloser 305 var stderr io.ReadCloser 306 var stdin io.WriteCloser 307 var outputWriters *copy.MultiWriter 308 var errorWriters *copy.MultiWriter 309 var inputWriters *copy.MultiWriter 310 var tbuf *copy.TerminalBuffer 311 312 hasTerminal := engine.EngineConfig.OciConfig.Process.Terminal 313 314 inputWriters = ©.MultiWriter{} 315 outputWriters = ©.MultiWriter{} 316 outputWriters.Add(logger.NewWriter("stdout", true)) 317 318 if hasTerminal { 319 stdout = os.NewFile(uintptr(engine.EngineConfig.MasterPts), "stream-master-pts") 320 tbuf = copy.NewTerminalBuffer() 321 outputWriters.Add(tbuf) 322 inputWriters.Add(stdout) 323 } else { 324 outputStream := os.NewFile(uintptr(engine.EngineConfig.OutputStreams[0]), "stdout-stream") 325 errorStream := os.NewFile(uintptr(engine.EngineConfig.ErrorStreams[0]), "error-stream") 326 inputStream := os.NewFile(uintptr(engine.EngineConfig.InputStreams[0]), "input-stream") 327 stdout = outputStream 328 stderr = errorStream 329 stdin = inputStream 330 outputWriters.Add(os.Stdout) 331 inputWriters.Add(stdin) 332 } 333 334 if stderr != nil { 335 errorWriters = ©.MultiWriter{} 336 errorWriters.Add(logger.NewWriter("stderr", true)) 337 errorWriters.Add(os.Stderr) 338 } 339 340 go func() { 341 for { 342 c, err := l.Accept() 343 if err != nil { 344 fatalChan <- err 345 return 346 } 347 348 go func() { 349 outputWriters.Add(c) 350 if stderr != nil { 351 errorWriters.Add(c) 352 } 353 354 if tbuf != nil { 355 c.Write(tbuf.Line()) 356 } 357 358 io.Copy(inputWriters, c) 359 360 outputWriters.Del(c) 361 if stderr != nil { 362 errorWriters.Del(c) 363 } 364 c.Close() 365 }() 366 } 367 }() 368 369 go func() { 370 io.Copy(outputWriters, stdout) 371 stdout.Close() 372 }() 373 374 if stderr != nil { 375 go func() { 376 io.Copy(errorWriters, stderr) 377 stderr.Close() 378 }() 379 } 380 if stdin != nil { 381 go func() { 382 io.Copy(inputWriters, os.Stdin) 383 stdin.Close() 384 }() 385 } 386 } 387 388 func (engine *EngineOperations) handleControl(masterConn net.Conn, attach net.Listener, control net.Listener, logger *instance.Logger, start chan bool, fatalChan chan error) { 389 var master *os.File 390 started := false 391 392 if engine.EngineConfig.OciConfig.Process.Terminal { 393 master = os.NewFile(uintptr(engine.EngineConfig.MasterPts), "control-master-pts") 394 } 395 396 for { 397 c, err := control.Accept() 398 if err != nil { 399 fatalChan <- err 400 return 401 } 402 dec := json.NewDecoder(c) 403 ctrl := &ociruntime.Control{} 404 if err := dec.Decode(ctrl); err != nil { 405 fatalChan <- err 406 return 407 } 408 409 if ctrl.StartContainer && !started { 410 started = true 411 412 engine.handleStream(attach, logger, fatalChan) 413 414 // since container process block on read, send it an 415 // ACK so when it will receive data, the container 416 // process will be executed 417 if _, err := masterConn.Write([]byte("s")); err != nil { 418 fatalChan <- fmt.Errorf("failed to send ACK to start process: %s", err) 419 return 420 } 421 422 // send start event 423 start <- true 424 425 // wait status update 426 engine.waitStatusUpdate() 427 } 428 if ctrl.ConsoleSize != nil && master != nil { 429 size := &pty.Winsize{ 430 Cols: uint16(ctrl.ConsoleSize.Width), 431 Rows: uint16(ctrl.ConsoleSize.Height), 432 } 433 if err := pty.Setsize(master, size); err != nil { 434 fatalChan <- err 435 return 436 } 437 } 438 if ctrl.ReopenLog { 439 logger.ReOpenFile() 440 } 441 if ctrl.Pause { 442 if err := engine.EngineConfig.Cgroups.Pause(); err != nil { 443 fatalChan <- err 444 return 445 } 446 if err := engine.updateState(ociruntime.Paused); err != nil { 447 fatalChan <- err 448 return 449 } 450 } 451 if ctrl.Resume { 452 if err := engine.updateState(ociruntime.Running); err != nil { 453 fatalChan <- err 454 return 455 } 456 if err := engine.EngineConfig.Cgroups.Resume(); err != nil { 457 fatalChan <- err 458 return 459 } 460 } 461 462 c.Close() 463 } 464 }