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  }