golang - exec.Command start a process and get pid - linux

I wrote a function to run process, this is my code:
func execCmd(user User, command string) (*exec.Cmd, error) {
cmd := exec.Command(os.Getenv("SHELL"), "-c", command)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(user.UID), Gid: uint32(user.GID)}
if err := cmd.Start(); err != nil {
return nil, err
}
return cmd, nil
}
I used cmd.Process.Pid to get the pid of the process after the process has started.
It works well with a command like /tmp/test but returns an unexpected pid with the command /tmp/test > /tmp/test.log. The command returns 1 less than the actual pid (actual pid - 1). I want to get the pid /tmp/test and after use this pid kill /tmp/test.
For example: cmd.Process.pid = 10667, but ps -ef | grep /tmp/test shows that the pid equals 10668
Thanks.

Related

Access docker container in interactive shell using Golang

I am trying to access interactive shell of a running docker container using Golang.
Here is what I tried.
package main
import (
"fmt"
"log"
"os"
"os/exec"
)
func main() {
prg := "docker"
arg1 := "exec"
arg2 := "-ti"
arg3 := "df43f9a0d5c4"
arg4 := "bash"
cmd := exec.Command(prg, arg1, arg2, arg3, arg4)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("[Command] %s\n", cmd.String())
log.Printf("Running command and waiting for it to finish...")
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Command finished with error %s\n", cmd.String())
}
AND here is output with error:
> [Command] /usr/bin/docker exec -ti df43f9a0d5c4 bash 2022/07/28
> 19:21:02 Running command and waiting for it to finish... the input
> device is not a TTY 2022/07/28 19:21:02 exit status 1 exit status 1
Note: Interactive shell of running docker container works fine when executing this command directly on the shell
You are passing -t, telling docker exec to allocate a pseudoterminal for the exec session within the container.
But you're not setting cmd.Stdin to anything, so the cmd.Stdin is nil. The documentation says
// If Stdin is nil, the process reads from the null device (os.DevNull).
The input isn't a terminal so that's why you get
the input device is not a TTY
You say
Note: Interactive shell of running docker container works fine when executing this command directly on the shell
Because when you run it directly in the shell, the standard input is a terminal.
Try this:
cmd.Stdin = os.Stdin

How to implement the mount with golang

Can I use go to implement the mount function in Linux? Mount a path transferred from the foreground to a local path?such as
add_iptables "${shared_file_path}"
if [[ "x$domain" == "xnoDomain" ]]
then
expect > /dev/null 2>&1 <<EOF
set timeout 1
//
spawn /usr/bin/mount -t cifs -o nodev,nosuid,noexec,username=${user_name} ${shared_file_path} ${local_path}
expect {
"Passwor*:" {send "${local_pws}\n"}
}
expect eof
catch wait result
exit [lindex \$result 3]
EOF
else
expect > /dev/null 2>&1 <<EOF
set timeout 1
spawn /usr/bin/mount -t cifs -o nodev,nosuid,noexec,domain=${domain},username=${user_name} ${shared_file_path} ${local_path}
expect {
"Passwor*:" {send "${local_pws}\n"}
}
expect eof
catch wait result
exit [lindex \$result 3]
EOF
You could use Go to wrap a system call to pretty much anything you want.
For instance, in nanobox-io/nanobox with util/provider/dockermachine_mount_windows.go (extract of a larger function):
// ensure cifs/samba utilities are installed
cmd = []string{"sh", "-c", setupCifsUtilsScript()}
if b, err := Run(cmd); err != nil {
lumber.Debug("cifs output: %s", b)
return fmt.Errorf("cifs:%s", err.Error())
}
// mount!
// mount -t cifs -o sec=ntlmssp,username=USER,password=PASSWORD,uid=1000,gid=1000 //192.168.99.1/<path to app> /<vm location>
source := fmt.Sprintf("//192.168.99.1/nanobox-%s", appID)
// mfsymlinks,
config, _ := models.LoadConfig()
additionalOptions := config.NetfsMountOpts
// since the mount command inserts the user into the command string with
// single quotes, we need to escape any single quotes from the real
// username. As the command will be running in bash, the actual escape
// sequence is a bit tricky. Each ' will be replaced with '"'"'.
escapedUser := strings.Replace(user, "'", "'\"'\"'", -1)
opts := fmt.Sprintf("nodev,sec=ntlmssp,user='%s',password='%s',uid=1000,gid=1000", escapedUser, pass)
if additionalOptions != "" {
opts = fmt.Sprintf("%s,%s", additionalOptions, opts)
}
cmd = []string{
"sudo",
"/bin/mount",
"-t",
"cifs",
"-o",
opts,
source,
host,
}
lumber.Debug("cifs mount cmd: %v", cmd)
if b, err := Run(cmd); err != nil {
lumber.Debug("mount output: %s", b)
return fmt.Errorf("mount: output: %s err:%s", b, err.Error())
}

How to restart itself in Go daemon process?

I use go-daemon library to fork process and run it in background. And I need to restart the daemon process after update performed from within http handler.
The handler code is
func httpUpdate(w http.ResponseWriter, req *http.Request) {
if !isPost(req.Method) {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
if checkAuth(req) != 200 {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
log.Println("INFO: Update request, checking for update...")
var err = doUpdate(UPDATE_URL, nil, false)
if !isError(err) {
log.Println("INFO: Update successful, exit")
var system = RealSystem{}
system.Run(fmt.Sprintf("(sleep 0.3s && %s start &> /test/extra.log)&disown", appFilename()))
system.Exit(0)
return
}
w.WriteHeader(http.StatusNoContent)
}
doUpdate() returns nil if successfully replaced the executable file. RealSystem is just wrapper for exec.Command and os.Exit(). appFilename() is the executable file name. The command to start app is /path/to/app start.
I see that new process starts, but executing Context::Reborn() fails with EOF error. Looks like some intrinsic pipes used as implementation details fail with EOF (may be...).
What would be the reason? Or may be there is a better way of doing that?
For now everything happens inside docker container in the "context" of e2e test if it matters. I spent hours trying to make it work but with no success.
I assume you mean restarting the currently running Go binary. You can use a syscall for unix-based systems, and use an exec.Command for Windows.
func RestartSelf() error {
self, err := osext.Executable()
if err != nil {
return err
}
args := os.Args
env := os.Environ()
// Windows does not support exec syscall.
if runtime.GOOS == "windows" {
cmd := exec.Command(self, args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = env
err := cmd.Run()
if err == nil {
os.Exit(0)
}
return err
}
return syscall.Exec(self, args, env)
}
The issue is specific to the library. Spawn new self instance from within child process is not a problem for the system, but for that library.
To achieve this it's necessary to execute something like that.
Note the _GO_DAEMON=0 variable set to zero. This makes library follow parent control flow.
var cmd = exec.Command("bash", "-c", fmt.Sprintf("sleep 0.5s; _GO_DAEMON=0 %s start", appFilename()))
var err = cmd.Start()
Also it was necessary to make small changes to the original library. Here is the fork.

using curl with commands in go

I'm using Go with command to execute curl which works as expected
curl := exec.Command("curl", "https://services.odata.org/V3/northwind/northwind.svc/")
out, err := curl.Output()
if err != nil {
fmt.Println("erorr" , err)
return
}
fmt.Println(out)
Now I want to use some placeholders like
curl -O http://quiet-waters-1228.herokuapp.com/assets/image.jpg
but now I need to get the url for command
e.g. if I run in bash mytool url I got the url value
`curl -O $(mytool url)`
The problem is that we need to execute the command in the code and I'm not sure how to pass it
curl := exec.Command("curl", "curl -O $(url)")
out, err := curl.Output()
if err != nil {
fmt.Println("erorr" , err)
return
}
fmt.Println(out)
In os package you have slice of strings which contains all arguments passed by shell to your program.
os.Args 0th value, i.e., first element is going to be name of the command itself.
If your tool command is mytool, os.Args[0] contains mytool.
Rest are going to be the arguments, which are passed by shell.
package main
import (
"log"
"os"
"os/exec"
)
func main() {
if len(os.Args) < 2 {
// If argument is not provided quit
log.Fatalln("url not provided")
}
url := os.Args[1] // URL
cmd := exec.Command("curl", "-O", url)
cmd.Run()
}
You can also download multiple URLs concurrently,
var wg *sync.WaitGroup
func main() {
urls := os.Args[1:]
wg = new(sync.WaitGroup)
wg.Add(len(urls))
for _, url := range urls {
go download(url)
}
wg.Wait()
}
func download(url string) {
defer wg.Done()
cmd := exec.Command("curl", "-O", url)
cmd.Run()
}

Go: strange results when using strings with exec.Command

I have a Go function that processes Linux CLI commands and their arguments:
func cmd(cmd string, args ...string) ([]byte, error) {
path, err := exec.Command("/usr/bin/which", cmd).Output()
if err != nil {
return []byte(""), err
}
response, err := exec.Command(string(path), args...).Output()
if err != nil {
response = []byte("Unknown")
}
return response, err
}
Which is called by the following:
func main() {
uname, err := cmd("uname", "-a")
fmt.Println(string(uname))
}
The "which" command returns the correct path to the binary but when it tries to run the second exec command with a dynamic path the return is always:
fork/exec /usr/bin/uname
: no such file or directory
exit status 1
Yet if the second exec command is hardcoded, everything works as expected and prints the uname:
response, err := exec.Command("/usr/bin/uname", args...).Output()
Am I missing something about how exec and strings behave?
Thanks
The which command prints a newline following the name of the executable. The path variable is set to "/usr/bin/uname\n". There is no executable with this path. The extra newline is visible in the error message (the newline just before the ":").
Trim the newline suffix to get the correct name of the executable:
response, err := exec.Command(strings.TrimSuffix(string(path), "\n"), args...).Output()

Resources