How to implement the mount with golang - linux

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())
}

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

Answer to password shell prompt in golang

I am searching for a way to answer to a shell password prompt in golang.
like :
bussiere#kus:~/Workspace/rteest$ ./passwordtest.sh
Password :
I would like to enter the password automatically with my token in golang after launching a shell command / script ...
I've made some script that get a one time token with mfa if everything is ok (in golang). So i need to enter the tempory token to a linux password prompt.
I know that there is the expect command but i would like to compile my program to embed it and have minimal depency.
Thanks and regards
edit thks to #nevermore i've tried this (but it doesn't work) : https://play.golang.org/p/Ffm3q5h636
package main
import (
"os/exec"
"fmt"
"log"
"io"
)
func main() {
cmdb := "git"
args := "clone https://bb#gitlab.com/bb/fzgs.git"
cmd := exec.Command(cmdb, args)
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "QSRDFGHJfZERTYU")
}()
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
it gives me this :
2017/05/12 20:42:36 exit status 1
exit status 1
edit bis :
cmdb := "git"
args := "https://bb#gitlab.com/bb/fzgs.git"
cmd := exec.Command(cmdb, "clone", args)
it asks for the password :
Password for 'https://bb#gitlab.com':
The problem is that you are trying to send the password before Git asks you
to do so.
And the way Git asks for password depends on whether credential helper is set. If the credential helper is not set, or doesn't return a password, the user is asked for input.
Solution #1: Include Password into URI
In most cases (at least with Github and Bitbucket) the Git server accepts
credentials passed via HTTPS URI, e.g. https://user:password#domain.com/repo.git.
cmd := exec.Command("git", "clone", "https://bb:PASSWORD#gitlab.com/bb/fzgs.git")
env := os.Environ()
env = append(env, "GIT_ASKPASS=")
cmd.Env = env
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
Solution #2: Custom Credential Helper
Another way is to make a credential helper script, e.g.:
#!/bin/bash -
printf '%s\n' 'YOUR_PASSWORD'
and pass it via GIT_ASKPASS:
cmd := exec.Command("git", "clone", "https://bb#gitlab.com/bb/fzgs.git")
env := os.Environ()
env = append(env, "GIT_ASKPASS=/path/to/fzgs-askpass.sh")
cmd.Env = env
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
If a user is asked for a password in their shell, you can use golang to write to the io.Writer that is os.Stdin
Properly passing data on stdin to a command and receiving data from stdout of that command in golang
os.Stdin is a os.File created by
NewFile(uintptr(syscall.Stdin), "/dev/stdin")
And the password prompt will be reading from this file
You can execute ./passwordtest.sh as in your question, and then write the password to os.Stdin, and the bash script should accept the bytes written by golang as the password.
Alternatively is actually a method for this in the exec package for the Cmd type.
Use cmd to execute your shell script
Input the password using stdin, or cmd.StdinPip()
Read the shells output using cmd.CombinedOutput()
Cmd represents an external command being prepared or run.
https://golang.org/pkg/os/exec/#Cmd.StdinPipe
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
}()
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
StdinPipe returns a pipe that will be connected to the command's standard input when the command starts. The pipe will be closed automatically after Wait sees the command exit. A caller need only call Close to force the pipe to close sooner. For example, if the command being run will not exit until standard input is closed, the caller must close the pipe.
Also, your cmd arguments shouldn't combine clone and the url, try instead
cmd := exec.Command(cmdb, "clone", url)

golang - exec.Command start a process and get pid

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.

Bash trap unset from function

Namely, http://fvue.nl/wiki/Bash:_Error_handling#Set_ERR_trap_to_exit
Why is it necessary to set -o errtrace to make trap set/unset from a function call work?
#!/usr/bin/env bash
function trapit {
echo 'trapped in a box'
}
function setTrap {
trap 'trapit' ERR
}
function unsetTrap {
trap - ERR
}
function foo_init {
fooOldErrtrace=$(set +o | grep errtrace)
set -o errtrace
trap 'echo trapped' ERR # Set ERR trap
}
function foo_deinit {
trap - ERR # Reset ERR trap
eval $fooOldErrtrace # Restore `errtrace' setting
unset fooOldErrtrace # Delete global variable
}
# foo_init
setTrap
echo 'set'
false
echo 'unset'
#foo_deinit
unsetTrap
false
According to man bash(5) functions not inherits ERR trap without the errtrace flag turned on. I dont know why ERR trap cant be inherited by default, but... it is so for now :)
You can test this behaviour with my sample code:
#!/usr/bin/env bash
trapit () {
echo 'some error trapped'
}
doerr1 () {
echo 'I am the first err generator and i return error status to the callee'
return 1
}
doerr2 () {
echo 'I am the second err generator and i produce an error inside my code'
fgrep a /etc/motttd
return 0
}
[[ $1 ]] && set -o errtrace
trap trapit ERR
doerr1
doerr2
echo 'We will produce an exception in the main program...'
cat /etc/ftab | fgrep a
echo 'OK, thats done, you see it :)'
If you pass any parameter to this script, errtrace flag will be turned on and you will see that exception was "catched" when doerr2 tried to do something awful.

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