I am following : Containers From Scratch • Liz Rice • GOTO 2018
and test the source code lizrice/containers-from-scratch locally to learn containers.
But with code below , I am not able to fork a child process on Ubuntu 1804, below are my main.go
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
)
// go run main.go run <cmd> <args>
func main() {
switch os.Args[1] {
case "run":
run()
case "child":
child()
default:
panic("help")
}
}
func run() {
fmt.Printf("In <Run> Running %v \n", os.Args[2:])
cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args[2:]...)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS,
}
must(cmd.Run())
}
func child() {
fmt.Printf("In <child> Running %v \n", os.Args[2:])
cmd := exec.Command(os.Args[2], os.Args[3:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
syscall.Sethostname([]byte("container"))
must(cmd.Run())
}
func must(err error) {
if err != nil {
panic(err)
}
}
I tried to run it :
go run main1.go run /bin/bash
Error below occurred:
In <Run> Running [/bin/bash]
panic: fork/exec /proc/self/exe: operation not permitted
goroutine 1 [running]:
main.must(...)
/home/jia/cs_study_plan/docker/containers-from-scratch/main1.go:72
main.run()
/home/jia/cs_study_plan/docker/containers-from-scratch/main1.go:44 +0x27f
main.main()
/home/jia/cs_study_plan/docker/containers-from-scratch/main1.go:17 +0x4d
exit status 2
Any suggestion how can I fix this permission issue in my main.go ?
Thank you #Peter for suggestions.
I could run my app use 'go build' and 'sudo'
Here is what I do
1. go to directory of main1.go
2. go mod init main1 <it must be the same name as main1.go>
3. go mod tidy <it is optional step>
4. gp build
5. main1 is build in current directory
6. run command :
sudo ./main1 run /bin/bash
7. child forked successfully , as I can tell from my output
In <Run> Running [/bin/bash]
In <child> Running [/bin/bash]
ps
PID TTY TIME CMD
15774 pts/0 00:00:00 sudo
15775 pts/0 00:00:00 main1
15781 pts/0 00:00:00 exe
15786 pts/0 00:00:00 bash
16047 pts/0 00:00:00 ps
Also I tried to disable 'AppArmor' in my Ubunut , see if that is what blocks main1.go from fork child process
$sudo systemctl status apparmor
[sudo] password for jia:
● apparmor.service - AppArmor initialization
Loaded: loaded (/lib/systemd/system/apparmor.service; enabled; vendor preset: enabled)
Active: active (exited) since Thu 2022-06-09 08:31:21 CST; 2h 3min ago
Docs: man:apparmor(7)
http://wiki.apparmor.net/
Main PID: 660 (code=exited, status=0/SUCCESS)
Tasks: 0 (limit: 4915)
CGroup: /system.slice/apparmor.service
Jun 09 08:31:21 ub1804 apparmor[660]: * Starting AppArmor profiles
Jun 09 08:31:21 ub1804 apparmor[660]: Skipping profile in /etc/apparmor.d/disable: usr.bin.firefox
Jun 09 08:31:21 ub1804 apparmor[660]: Skipping profile in /etc/apparmor.d/disable: usr.sbin.rsyslogd
Jun 09 08:31:21 ub1804 systemd[1]: Starting AppArmor initialization...
Jun 09 08:31:21 ub1804 apparmor[660]: ...done.
Jun 09 08:31:21 ub1804 systemd[1]: Started AppArmor initialization.
$sudo systemctl stop apparmor
$go run main1.go run echo hello
In <Run> Running [echo hello]
panic: fork/exec /proc/self/exe: operation not permitted
NO luck , if anyone knows what stops my main1.go to fork child , please let me know , thanks in advance
Your problem appears because Ubuntu has sudo preinstalled so your user has restricted rights. According to proc documentation, it provides an interface for kernel data structures and represents a pseudo-filesystem. The /proc is mounted by the system itself as read-only, but you are trying to create a new process inside this file system which is read-only for your current user.
You can read more about it in the man https://man7.org/linux/man-pages/man5/proc.5.html
TL;DR: you need to run your program as a root.
Related
This question already has answers here:
Executing a Bash Script from Golang
(4 answers)
Closed 3 months ago.
I want to create a wrapper program that can wrapper whatever shell commands user provides, like:
./wrapper "cmd1 && cmd2"
In Python, I can call os.system("cmd1 && cmd2"). But Golang's exec.Command needs a list for command and args. Is there way in Golang to archive the same as Python's os.system()?
os/exec https://pkg.go.dev/os/exec
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("/usr/bin/bash", "-c", os.Args[1])
output, err := cmd.CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(output))
}
$ go run main.go "ls -alh && pwd"
total 4.0K
drwxr-xr-x 2 xxxx xxxx 120 Nov 14 11:12 .
drwxrwxrwt 17 root root 420 Nov 14 11:42 ..
-rw-r--r-- 1 xxxx xxxx 217 Nov 14 11:42 main.go
/tmp/stk
I am connecting to pod via client-Go and I want to get the properties of the file directory
func GetPodFiles(c *gin.Context) {
client, _ := Init.ClusterID(c)
path := c.DefaultQuery("path", "/")
cmd := []string{
"sh",
"-c",
fmt.Sprintf("ls -l %s", path),
}
config, _ := Init.ClusterCfg(c)
req := client.CoreV1().RESTClient().Post().
Resource("pods").
Name("nacos-0").
Namespace("default").SubResource("exec").Param("container", "nacos")
req.VersionedParams(
&v1.PodExecOptions{
Command: cmd,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
},
scheme.ParameterCodec,
)
var stdout, stderr bytes.Buffer
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
if err != nil {
response.FailWithMessage(response.InternalServerError, err.Error(), c)
return
}
err = exec.Stream(remotecommand.StreamOptions{
Stdin: nil,
Stdout: &stdout,
Stderr: &stderr,
})
if err != nil {
response.FailWithMessage(response.InternalServerError, "Error obtaining file", c)
return
}
fmt.Println(stdout.String())
}
Execution Result Output
total 0
lrwxrwxrwx 1 root root 7 Jun 1 2018 bin -> usr/bin
drwxr-xr-x 5 root root 360 Feb 16 16:39 dev
lrwxrwxrwx 1 root root 8 Jun 1 2018 sbin -> usr/sbin
drwxr-xr-x 2 root root 6 Apr 11 2018 srv
Expect the result
"data": [
{
"perm": "drwxr-xr-x",
"mod_time": "2022-03-02 15:02:15",
"kind": "d",
"name": "temp",
"size": ""
},
]
Is there a good way or a golang third-party library to handle it. Please let me know. Thank you
In a Kubernetes pod you can execute the stat linux command instead of ls command.
$ stat yourFileOrDirName
The output of this command by default is like this:
File: yourFileOrDirName
Size: 346 Blocks: 0 IO Block: 4096 directory
Device: 51h/82d Inode: 40431 Links: 1
Access: (0755/drwxr-xr-x) Uid: ( 1000/ username) Gid: ( 1000/ groupname)
Access: 2022-03-02 11:59:07.384821351 +0100
Modify: 2022-03-02 11:58:48.733821177 +0100
Change: 2022-03-02 11:58:48.733821177 +0100
Birth: 2021-12-21 11:12:05.571841723 +0100
But you can tweak its output like this:
$ stat --printf="%n,%A,%y,%s" yourFileOrDirName
where %n - file name, %A - permission bits and file type in human readable form, %y - time of last data modification human-readable, %s - total size, in bytes. You can also choose any character as a delimiter instead of comma.
the output will be:
yourFileOrDirName,drwxr-xr-x,2022-03-02 11:58:48.733821177 +0100,346
See more info about the stat command here.
After you get such output, I believe you can easily 'convert' it to json format if you really need it.
Furthermore, you can run the stat command like this:
$ stat --printf="{\"data\":[{\"name\":\"%n\",\"perm\":\"%A\",\"mod_time\":\"%y\",\"size\":\"%s\"}]}" yourFileOrDirName
Or as #mdaniel suggested, since the command does not contain any shell variables, nor a ', the cleaner command is:
stat --printf='{"data":[{"name":"%n","perm":"%A","mod_time":"%y","size":"%s"}]}' yourFileOrDirName
and get the DIY json output:
{"data":[{"name":"yourFileOrDirName","perm":"drwxrwxr-x","mod_time":"2022-02-04 15:17:27.000000000 +0000","size":"4096"}]}
So I've tried to execute a chain i.e. multiple commands on a Pod's container using client-go, and it seems to only work for some commands like ls.
Here is what I've tried:
req := client.CoreV1().RESTClient().Post().Resource("pods").Name(pod.Name).Namespace(pod.ObjectMeta.Namespace).SubResource("exec") // .Param("container", containerName)
scheme := runtime.NewScheme()
if err := _v1.AddToScheme(scheme); err != nil {
panic(err.Error())
}
parameterCodec := runtime.NewParameterCodec(scheme)
req.VersionedParams(&_v1.PodExecOptions{
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
Container: containerName,
Command: strings.Fields("/bin/sh -c " + command),
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
if err != nil {
panic(err.Error())
}
var stdout, stderr bytes.Buffer
err = exec.Stream(remotecommand.StreamOptions{
Stdin: nil,
Stdout: &stdout,
Stderr: &stderr,
Tty: false,
})
if err != nil {
panic(err.Error())
}
log.Printf("Output from pod: %v", stdout.String())
log.Printf("Error from pod: %v", stderr.String())
When the command variable is just a simple ls -l, I get the desired output. But when I try to do something like 'ls -l && echo hello' it produces an error command terminated with exit code 2.
It doesn't output anything if I only put echo hello. However, it does produce the desired output hello if I remove the Bourne Shell prefix /bin/sh -c and the Command attribute equals to string.Fields("echo hello"), but this approach doesn't let me chain commands.
All in all, what I am trying to do is to execute a chain of commands on a Pod's container.
The corev1.PodExecOptions.Command accept value of []string type.
req.VersionedParams(&_v1.PodExecOptions{
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
Container: containerName,
Command: cmds,
}, parameterCodec)
where, cmds can be:
cmds := []string{
"sh",
"-c",
"echo $HOME; ls -l && echo hello",
}
Output:
/root
total 68
drwxr-xr-x 2 root root 4096 Feb 24 00:00 bin
drwxr-xr-x 2 root root 4096 Feb 1 17:09 boot
drwxr-xr-x 2 root root 4096 Feb 24 00:00 mnt
drwxr-xr-x 2 root root 4096 Feb 24 00:00 opt
dr-xr-xr-x 396 root root 0 Mar 19 11:47 proc
drwx------ 2 root root 4096 Feb 24 00:00 root
.
.
hello
Explanation: Tim's answer
command: ["/bin/sh","-c"]
args: ["command one; command two && command three"]
The command ["/bin/sh", "-c"] says "run a shell, and execute the following instructions". The args are then passed as commands to the shell. In shell scripting a semicolon separates commands, and && conditionally runs the following command if the first succeed. In the above example, it always runs command one followed by command two, and only runs command three if command two succeeded.
N.B.:
For bash, it will be similar to something like below:
cmds := []string{
"bash",
"-c",
`export MYSQL_PWD=${MYSQL_ROOT_PASSWORD}
mysql -h localhost -nsLNE -e "select 1;" 2>/dev/null | grep -v "*"`,
},
I tried to run the following Go code:
package main
import (
"fmt"
"io/ioutil"
)
func main() {
items, err := ioutil.ReadDir("/dev/fd")
if err != nil {
panic(err)
}
fmt.Println(items)
}
I just get this error:
panic: lstat /dev/fd/4: bad file descriptor
goroutine 1 [running]:
main.main()
/Users/andy/Desktop/demo.go:11 +0xe8
exit status 2
The /dev/fd folder definitely exists, and there is a /dev/fd/4 inside there when I ls it.
$ ls -Al /dev/fd
total 9
crw--w---- 1 andy tty 16, 4 Jan 25 00:16 0
crw--w---- 1 andy tty 16, 4 Jan 25 00:16 1
crw--w---- 1 andy tty 16, 4 Jan 25 00:16 2
dr--r--r-- 3 root wheel 4419 Jan 23 20:42 3/
dr--r--r-- 1 root wheel 0 Jan 23 20:42 4/
What's going on? Why can't I read this directory? I'm trying to port the ls command to Go here, so I would like to be able to read this directory in order to produce similar output to ls.
EDIT: I am running everything as non-root user. The executable bit on /dev/fd is set.
$ ls -al /dev | grep fd
dr-xr-xr-x 1 root wheel 0 Jan 23 20:42 fd/
$ stat /dev/fd/4 # same result with -L flag
stat: /dev/fd/4: stat: Bad file descriptor
First, let's remember that /dev/fd is special. Its contents are different for every process (since they reflect the file descriptors of that process), so it doesn't really mean anything that ls can list it because its contents will be different for ls and your program.
Anyway, here's a slightly updated version of your program where instead of letting ioutil do things behind our back, we do it ourselves to see what's going on:
package main
import (
"fmt"
"time"
"os"
"log"
)
func main() {
d, err := os.Open("/dev/fd")
if err != nil {
log.Fatal(err)
}
names, err := d.Readdirnames(0)
if err != nil {
log.Fatal(err)
}
for _, n := range names {
n = "/dev/fd/" + n
fmt.Printf("file: %s\n", n)
_, err := os.Lstat(n)
if err != nil {
fmt.Printf("lstat error: %s - %v\n", n, err)
}
}
time.Sleep(time.Second * 200)
}
And then when run it gives me:
file: /dev/fd/0
file: /dev/fd/1
file: /dev/fd/2
file: /dev/fd/3
file: /dev/fd/4
lstat error: /dev/fd/4 - lstat /dev/fd/4: bad file descriptor
So this is indeed the same problem. I added the sleep at the end so that the process doesn't die so that we can debug which file descriptors it has. In another terminal:
$ lsof -p 7861
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
foo 7861 art cwd DIR 1,4 2272 731702 /Users/art/src/go/src
foo 7861 art txt REG 1,4 1450576 8591078117 /private/var/folders/m7/d614cd9x61s0l3thb7cf3rkh0000gn/T/go-build268777304/command-line-arguments/_obj/exe/foo
foo 7861 art txt REG 1,4 837248 8590944844 /usr/lib/dyld
foo 7861 art 0u CHR 16,4 0t8129 645 /dev/ttys004
foo 7861 art 1u CHR 16,4 0t8129 645 /dev/ttys004
foo 7861 art 2u CHR 16,4 0t8129 645 /dev/ttys004
foo 7861 art 3r DIR 37,7153808 0 316 /dev/fd
foo 7861 art 4u KQUEUE count=0, state=0x8
We can see that fd 4 is a KQUEUE. Kqueue file descriptors are used for managing events on OSX, similar to epoll on Linux if you know that mechanism.
It appears that OSX does not allow stat on kqueue file descriptors in /dev/fd which we can verify with:
$ cat > foo.c
#include <sys/types.h>
#include <sys/event.h>
#include <sys/stat.h>
#include <stdio.h>
#include <err.h>
int
main(int argc, char **argv)
{
int fd = kqueue();
char path[16];
struct stat st;
snprintf(path, sizeof(path), "/dev/fd/%d", fd);
if (lstat(path, &st) == -1)
err(1, "lstat");
return 0;
}
$ cc -o foo foo.c && ./foo
foo: lstat: Bad file descriptor
$
Go programs on OSX need a kqueue to handle various events (not exactly sure which, but probably signals, timers and various file events).
You have four options here: don't stat, ignore the error, don't touch /dev/fd because it's weird, convince Apple that this is a bug.
Assuming that you're running your code as a non-root user, I'd guess that the problem is that the directory doesn't have the execute bit set, which prevents chdir-ing to that directory (which ReadDir might do before attempting to read the contents).
I am trying to deploy a test golang app to Heroku:
My procfile looks like this:
web: todo
But when running my app I get the following error:
2017-04-10T15:24:07.128780+00:00 app[web.1]: panic: could not locate box "./static"
My main.go file contains:
package main
import (
"net/http"
"github.com/kitensei/go-todoist/server"
"github.com/GeertJohan/go.rice"
"os"
"log"
"fmt"
"path"
"strconv"
)
var boxPrefix = getenv("BOXPATH", "")
func main() {
dir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
fmt.Println("DIRECTORY CWD: " + dir)
ex, err := os.Executable()
if err != nil {
panic(err)
}
exPath := path.Dir(ex)
fmt.Println("EXECUTABLE PATH: " + exPath)
exists, err := exists(boxPrefix + "static")
if err != nil {
panic(err)
}
fmt.Println("CHECK IF (" +boxPrefix + "static) EXISTS: " + strconv.FormatBool(exists))
server.RegisterHandlers()
http.Handle("/", http.FileServer(rice.MustFindBox(boxPrefix + "static").HTTPBox()))
http.ListenAndServe(":8080", nil)
}
func getenv(key, fallback string) string {
value := os.Getenv(key)
if len(value) == 0 {
return fallback
}
return value
}
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
Can someone give me an hint ?
EDIT: Here the output when I try to find the CWD and set the box path accordingly
2017-04-10T17:23:57.000000+00:00 app[api]: Build succeeded
2017-04-10T17:24:06.341977+00:00 heroku[web.1]: Starting process with command `go-todoist`
2017-04-10T17:24:08.592486+00:00 app[web.1]: DIRECTORY CWD: /app
2017-04-10T17:24:08.595689+00:00 app[web.1]: panic: could not locate box "static"
2017-04-10T17:24:08.595691+00:00 app[web.1]:
2017-04-10T17:24:08.595692+00:00 app[web.1]: goroutine 1 [running]:
2017-04-10T17:24:08.595718+00:00 app[web.1]: github.com/kitensei/go-todoist/vendor/github.com/GeertJohan/go%2erice.MustFindBox(0x71e08d, 0x6, 0x0)
2017-04-10T17:24:08.595721+00:00 app[web.1]: /tmp/tmp.Pa1igbANNl/.go/src/github.com/kitensei/go-todoist/vendor/github.com/GeertJohan/go.rice/box.go:110 +0x94
2017-04-10T17:24:08.595724+00:00 app[web.1]: main.main()
2017-04-10T17:24:08.595741+00:00 app[web.1]: /tmp/tmp.Pa1igbANNl/.go/src/github.com/kitensei/go-todoist/main.go:21 +0x1a8
2017-04-10T17:24:08.660495+00:00 heroku[web.1]: Process exited with status 2
2017-04-10T17:24:08.685016+00:00 heroku[web.1]: State changed from starting to crashed
2017-04-10T17:24:08.686277+00:00 heroku[web.1]: State changed from crashed to starting
2017-04-10T17:24:09.023202+00:00 heroku[web.1]: Starting process with command `go-todoist`
2017-04-10T17:24:10.743837+00:00 app[web.1]: DIRECTORY CWD: /app
2017-04-10T17:24:10.746355+00:00 app[web.1]: panic: could not locate box "static"
2017-04-10T17:24:10.746357+00:00 app[web.1]:
2017-04-10T17:24:10.746360+00:00 app[web.1]: goroutine 1 [running]:
2017-04-10T17:24:10.746361+00:00 app[web.1]: github.com/kitensei/go-todoist/vendor/github.com/GeertJohan/go%2erice.MustFindBox(0x71e08d, 0x6, 0x0)
2017-04-10T17:24:10.746361+00:00 app[web.1]: /tmp/tmp.Pa1igbANNl/.go/src/github.com/kitensei/go-todoist/vendor/github.com/GeertJohan/go.rice/box.go:110 +0x94
2017-04-10T17:24:10.746363+00:00 app[web.1]: main.main()
2017-04-10T17:24:10.746367+00:00 app[web.1]: /tmp/tmp.Pa1igbANNl/.go/src/github.com/kitensei/go-todoist/main.go:21 +0x1a8
2017-04-10T17:24:10.791994+00:00 heroku[web.1]: Process exited with status 2
2017-04-10T17:24:10.817345+00:00 heroku[web.1]: State changed from starting to crashed
vagrant#precise64:/code/src/github.com/kitensei/go-todoist$ heroku run bash -a go-todoist-gpr
/usr/local/heroku/lib/heroku/jsplugin.rb:119: warning: Insecure world writable dir /code/bin in PATH, mode 040777
Running bash on ⬢ go-todoist-gpr... up, run.9334 (Free)
~ $ cd /app
~ $ pwd
/app
~ $ ls -lA
total 48
-rw------- 1 u52460 dyno 289 Apr 10 17:23 .gitignore
drwx------ 3 u52460 dyno 4096 Apr 10 17:24 .heroku
drwx------ 2 u52460 dyno 4096 Apr 10 17:24 .profile.d
-rw------- 1 u52460 dyno 15 Apr 10 17:24 Procfile
-rw------- 1 u52460 dyno 26 Apr 10 17:23 README.md
-rw------- 1 u52460 dyno 326 Apr 10 17:23 app.json
drwx------ 2 u52460 dyno 4096 Apr 10 17:24 bin
-rw------- 1 u52460 dyno 592 Apr 10 17:23 main.go
drwx------ 2 u52460 dyno 4096 Apr 10 17:23 server
drwx------ 3 u52460 dyno 4096 Apr 10 17:23 static
drwx------ 2 u52460 dyno 4096 Apr 10 17:23 task
drwx------ 3 u52460 dyno 4096 Apr 10 17:23 vendor
~ $
Trying to run the app on the Heroku bash:
~ $ go-todoist
DIRECTORY CWD: /app
EXECUTABLE PATH: /app/bin
CHECK IF (static) EXISTS: true
panic: could not locate box "static"
goroutine 1 [running]:
github.com/kitensei/go-todoist/vendor/github.com/GeertJohan/go%2erice.MustFindBox(0xc4200f0cf0, 0xb, 0x5)
/tmp/tmp.FmcU3dgw8Y/.go/src/github.com/kitensei/go-todoist/vendor/github.com/GeertJohan/go.rice/box.go:110 +0x94
main.main()
/tmp/tmp.FmcU3dgw8Y/.go/src/github.com/kitensei/go-todoist/main.go:34 +0x42c
The same with an absolute path:
~ $ go-todoist
DIRECTORY CWD: /app
EXECUTABLE PATH: /app/bin
CHECK IF (/app/static) EXISTS: true
panic: given name/path is absolute
goroutine 1 [running]:
github.com/kitensei/go-todoist/vendor/github.com/GeertJohan/go%2erice.MustFindBox(0xc4200f0cf0, 0xb, 0x5)
/tmp/tmp.FmcU3dgw8Y/.go/src/github.com/kitensei/go-todoist/vendor/github.com/GeertJohan/go.rice/box.go:110 +0x94
main.main()
/tmp/tmp.FmcU3dgw8Y/.go/src/github.com/kitensei/go-todoist/main.go:34 +0x42c