简体   繁体   中英

Golang exec process and to disown it

I am trying to fork processes with my daemon, and trying to disown them in case of my daemon crashes. Regular os/exec is high-level, therefore I went for syscall.ForkExec and produced the following code:

package main

import (
    "fmt"
    "os"
    "os/exec"
    "syscall"
    "time"
)

func main() {

    cmd := "myproc"
    binary, lookErr := exec.LookPath(cmd)
    if lookErr != nil {
        panic(lookErr)
    }
    fmt.Println(binary)

    os.Remove("/tmp/stdin")
    os.Remove("/tmp/stdout")
    os.Remove("/tmp/stderr")

    fstdin, err1 := os.Create("/tmp/stdin")
    fstdout, err2 := os.Create("/tmp/stdout")
    fstderr, err3 := os.Create("/tmp/stderr")
    if err1 != nil || err2 != nil || err3 != nil {
        fmt.Println(err1, err2, err3)
        panic("WOW")
    }

    argv := []string{"hi"}
    procAttr := syscall.ProcAttr{
        Dir:   "/tmp",
        Files: []uintptr{fstdin.Fd(), fstdout.Fd(), fstderr.Fd()},
        Env:   []string{"VAR1=ABC123"},
        Sys: &syscall.SysProcAttr{
            Foreground: false,
        },
    }

    pid, err := syscall.ForkExec(binary, argv, &procAttr)
    fmt.Println("Spawned proc", pid, err)

    time.Sleep(time.Second * 100)
}

I have also made a simple application that sleeps and prints hello world and put it to path.

#include <stdio.h>

int main(){
    while(1){
        printf("hello world");
        fflush(stdout);
        usleep(300000);
        }
}

It works, however, the process is not send to background as I expected, my go process still owns the child. The SysProcAttr has the following values in Linux:

type SysProcAttr struct {
    Chroot      string         // Chroot.
    Credential  *Credential    // Credential.
    Ptrace      bool           // Enable tracing.
    Setsid      bool           // Create session.
    Setpgid     bool           // Set process group ID to Pgid, or, if Pgid == 0, to new pid.
    Setctty     bool           // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
    Noctty      bool           // Detach fd 0 from controlling terminal
    Ctty        int            // Controlling TTY fd
    Foreground  bool           // Place child's process group in foreground. (Implies Setpgid. Uses Ctty as fd of controlling TTY)
    Pgid        int            // Child's process group ID if Setpgid.
    Pdeathsig   Signal         // Signal that the process will get when its parent dies (Linux only)
    Cloneflags  uintptr        // Flags for clone calls (Linux only)
    UidMappings []SysProcIDMap // User ID mappings for user namespaces.
    GidMappings []SysProcIDMap // Group ID mappings for user namespaces.
    // GidMappingsEnableSetgroups enabling setgroups syscall.
    // If false, then setgroups syscall will be disabled for the child process.
    // This parameter is no-op if GidMappings == nil. Otherwise for unprivileged
    // users this should be set to false for mappings work.
    GidMappingsEnableSetgroups bool
}

I also tried the following but It caused an error:

Sys: &syscall.SysProcAttr{
    Setsid:     true,
    Setctty:    true,
    Foreground: false,
 },

Spawned proc 0 inappropriate ioctl for device

Also the following:

Sys: &syscall.SysProcAttr{
    Setsid:     true,
    Setctty:    true,
    Foreground: false,
    Noctty:     true,
    Setpgid:    true,
},

Spawned proc 0 operation not permitted (with root privilleges)

What am I doing/assuming wrong? Note: Despite saying os/exec is high-level, I also tried the following, but it produced same results.

cs := exec.Command(binary)
cs.SysProcAttr = &syscall.SysProcAttr{
    Setctty: true,
}
err := cs.Run()
fmt.Println(err)

A process forked with Start() will continue even after its parent dies.

func forker() {
    cmd := exec.Command("sleep", "3")
    cmd.Start()
    time.Sleep(2 * time.Second)
    os.Exit(1)
}

Here the sleep process will happily live on for 3 seconds even if the parent process only lives for 2 seconds:

  $ forker &; while true; do ps -f; sleep 1; done

  UID   PID  PPID   C STIME   TTY           TIME CMD
  501 71423 69892   0  3:01PM ttys003    0:00.07 forker
  501 71433 71432   0  3:01PM ttys003    0:00.00 sleep 3

  UID   PID  PPID   C STIME   TTY           TIME CMD
  501 71423 69892   0  3:01PM ttys003    0:00.07 forker
  501 71433 71432   0  3:01PM ttys003    0:00.00 sleep 3

  UID   PID  PPID   C STIME   TTY           TIME CMD
  501 71433     1   0  3:01PM ttys003    0:00.00 sleep 3

Notice how the parent process ID ( PPID ) of the sleep process became 1 when the parent process 71432 exited. This means that the sleep process has been orphaned.

The Start() method should give you what you're looking for. My script to append a simple text file after my sample Go program terminated continued to run:

package main

import (
    "os/exec"
)

func main() {
    cmd := exec.Command("./appender.sh")
    cmd.Start()
}

It seems good strategy for immediately disowning is spawning os.Argv[0] process (self-spawn), then spawning target process (with Setsid: true), and then exiting first spawned process. This way gradchild process immediately gets ppid 1. Optionally first spawned process can communicate pid of granchild via stdout to parent before it exits.

Daemonization doesn't play well with go's goroutine scheduling, so the details get pretty gnarly. godaemon has a small implementation with good discussion and links to more good discussion, and there's also the more popular and more traditionally implemented go-daemon .

The ioctl errors you are getting almost certainly come from the use of Setctty .

Sys: &syscall.SysProcAttr{ Foreground: false, Setsid: true, },

should get you semantics like os/exec 's Start , where the child process is not immediately reparented, but will safely be hoisted if/when the parent dies.

The operation not permitted you're seeing is, I think, about the pipes in /tmp/ , from running under strace: openat(AT_FDCWD, "/tmp/stdin", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3 epoll_create1(EPOLL_CLOEXEC) = 4 epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted) epoll_ctl(4, EPOLL_CTL_DEL, 3, 0xc420039c24) = -1 EPERM (Operation not permitted) openat(AT_FDCWD, "/tmp/stdout", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 5 epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted) epoll_ctl(4, EPOLL_CTL_DEL, 5, 0xc420039c24) = -1 EPERM (Operation not permitted) openat(AT_FDCWD, "/tmp/stderr", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 6 epoll_ctl(4, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted) epoll_ctl(4, EPOLL_CTL_DEL, 6, 0xc420039c24) = -1 EPERM (Operation not permitted) openat(AT_FDCWD, "/tmp/stdin", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3 epoll_create1(EPOLL_CLOEXEC) = 4 epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted) epoll_ctl(4, EPOLL_CTL_DEL, 3, 0xc420039c24) = -1 EPERM (Operation not permitted) openat(AT_FDCWD, "/tmp/stdout", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 5 epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted) epoll_ctl(4, EPOLL_CTL_DEL, 5, 0xc420039c24) = -1 EPERM (Operation not permitted) openat(AT_FDCWD, "/tmp/stderr", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 6 epoll_ctl(4, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted) epoll_ctl(4, EPOLL_CTL_DEL, 6, 0xc420039c24) = -1 EPERM (Operation not permitted)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM