github.com/apptainer/singularity@v3.1.1+incompatible/internal/app/starter/master.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 starter 7 8 import ( 9 "fmt" 10 "io" 11 "net" 12 "os" 13 "os/signal" 14 "runtime" 15 "syscall" 16 "time" 17 "unsafe" 18 19 "github.com/sylabs/singularity/internal/pkg/runtime/engines" 20 "github.com/sylabs/singularity/internal/pkg/sylog" 21 ) 22 23 // Master initializes a runtime engine and runs it 24 func Master(rpcSocket, masterSocket int, isInstance bool, containerPid int, engine *engines.Engine) { 25 var fatal error 26 var status syscall.WaitStatus 27 28 fatalChan := make(chan error, 1) 29 ppid := os.Getppid() 30 31 go func() { 32 comm := os.NewFile(uintptr(rpcSocket), "socket") 33 rpcConn, err := net.FileConn(comm) 34 comm.Close() 35 if err != nil { 36 fatalChan <- fmt.Errorf("failed to copy unix socket descriptor: %s", err) 37 return 38 } 39 40 runtime.LockOSThread() 41 err = engine.CreateContainer(containerPid, rpcConn) 42 if err != nil { 43 fatalChan <- fmt.Errorf("container creation failed: %s", err) 44 } else { 45 rpcConn.Close() 46 } 47 48 runtime.Goexit() 49 }() 50 51 go func() { 52 data := make([]byte, 1) 53 comm := os.NewFile(uintptr(masterSocket), "master-socket") 54 conn, err := net.FileConn(comm) 55 comm.Close() 56 if err != nil { 57 fatalChan <- fmt.Errorf("failed to create master connection: %s", err) 58 } 59 defer conn.Close() 60 61 // special path for engines which needs to stop before executing 62 // container process 63 if obj, ok := engine.EngineOperations.(interface { 64 PreStartProcess(int, net.Conn, chan error) error 65 }); ok { 66 n, err := conn.Read(data) 67 if (err != nil && err != io.EOF) || n == 0 || data[0] == 'f' { 68 if isInstance && os.Getppid() == ppid { 69 syscall.Kill(ppid, syscall.SIGUSR2) 70 } 71 return 72 } 73 if err := obj.PreStartProcess(containerPid, conn, fatalChan); err != nil { 74 fatalChan <- fmt.Errorf("pre start process failed: %s", err) 75 return 76 } 77 } 78 // wait container process execution, any error different from EOF 79 // or any data send over master connection at this point means an 80 // error occured in StartProcess, just return by waiting error 81 n, err := conn.Read(data) 82 if (err != nil && err != io.EOF) || n > 0 { 83 return 84 } 85 86 err = engine.PostStartProcess(containerPid) 87 if err != nil { 88 if isInstance && os.Getppid() == ppid { 89 syscall.Kill(ppid, syscall.SIGUSR2) 90 } 91 fatalChan <- fmt.Errorf("post start process failed: %s", err) 92 return 93 } 94 if isInstance { 95 // sleep a bit to see if child exit 96 time.Sleep(100 * time.Millisecond) 97 if os.Getppid() == ppid { 98 syscall.Kill(ppid, syscall.SIGUSR1) 99 } 100 } 101 }() 102 103 go func() { 104 var err error 105 106 // catch all signals 107 signals := make(chan os.Signal, 1) 108 signal.Notify(signals) 109 110 status, err = engine.MonitorContainer(containerPid, signals) 111 fatalChan <- err 112 }() 113 114 fatal = <-fatalChan 115 116 runtime.LockOSThread() 117 if err := engine.CleanupContainer(fatal, status); err != nil { 118 sylog.Errorf("container cleanup failed: %s", err) 119 } 120 runtime.UnlockOSThread() 121 122 if !isInstance { 123 pgrp := syscall.Getpgrp() 124 tcpgrp := 0 125 126 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, 1, uintptr(syscall.TIOCGPGRP), uintptr(unsafe.Pointer(&tcpgrp))); err == 0 { 127 if tcpgrp > 0 && pgrp != tcpgrp { 128 signal.Ignore(syscall.SIGTTOU) 129 130 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, 1, uintptr(syscall.TIOCSPGRP), uintptr(unsafe.Pointer(&pgrp))); err != 0 { 131 sylog.Errorf("failed to set crontrolling terminal group: %s", err.Error()) 132 } 133 } 134 } 135 } 136 137 if fatal != nil { 138 if isInstance { 139 if os.Getppid() == ppid { 140 syscall.Kill(ppid, syscall.SIGUSR2) 141 } 142 } 143 syscall.Kill(containerPid, syscall.SIGKILL) 144 sylog.Fatalf("%s", fatal) 145 } 146 147 if status.Signaled() { 148 sylog.Debugf("Child exited due to signal %d", status.Signal()) 149 if isInstance && os.Getppid() == ppid { 150 syscall.Kill(ppid, syscall.SIGUSR2) 151 } 152 os.Exit(128 + int(status.Signal())) 153 } else if status.Exited() { 154 sylog.Debugf("Child exited with exit status %d", status.ExitStatus()) 155 if isInstance { 156 if status.ExitStatus() != 0 { 157 if os.Getppid() == ppid { 158 syscall.Kill(ppid, syscall.SIGUSR2) 159 sylog.Fatalf("failed to spawn instance") 160 } 161 } 162 if os.Getppid() == ppid { 163 syscall.Kill(ppid, syscall.SIGUSR1) 164 } 165 } 166 os.Exit(status.ExitStatus()) 167 } 168 }