├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── integration ├── assets │ └── spawn_orphaned.sh ├── docker.go ├── hook_server.go ├── integration_test.go └── utils.go ├── iowire ├── iowire.go └── iowire_test.go ├── logrotate ├── logrotate.go └── logrotate_test.go ├── main.go ├── notifier └── notifier.go ├── port ├── port.go └── port_test.go ├── process.go ├── procfs ├── README.md ├── assets │ ├── README.md │ └── proc │ │ ├── 1 │ │ └── status │ │ ├── 9 │ │ ├── fd │ │ │ ├── 0 │ │ │ ├── 1 │ │ │ ├── 2 │ │ │ └── 3 │ │ └── status │ │ ├── 12 │ │ └── status │ │ ├── 14 │ │ └── status │ │ ├── dir │ │ └── .gitkeep │ │ ├── junk │ │ ├── net │ │ ├── tcp │ │ ├── tcp6 │ │ ├── udp │ │ └── udp6 │ │ └── symlinktargets │ │ ├── socket:[84336181] │ │ ├── targ0 │ │ ├── targ1 │ │ └── targ2 ├── doc.go ├── net.go ├── net_test.go ├── proc.go ├── proc_test.go ├── procfs.go └── procfs_test.go ├── signals.go ├── utils.go └── vendor.sh /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | dock 3 | release 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gliderlabs/alpine:3.3 2 | 3 | RUN apk add --no-cache bash make go git gcc musl-dev python 4 | 5 | # create workspace 6 | ENV GOPATH=/go 7 | RUN mkdir -p /go/src/github.com/robinmonjo/dock 8 | ADD . /go/src/github.com/robinmonjo/dock 9 | WORKDIR /go/src/github.com/robinmonjo/dock 10 | 11 | # build the app 12 | RUN IN_CONTAINER=true make && mv dock /usr/local/bin -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Robin Monjo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CWD:=$(shell pwd) 2 | GO:=GO15VENDOREXPERIMENT=1 go 3 | VERSION:=0.8 4 | IMAGE_NAME=robinmonjo/alpine-dock:dev 5 | 6 | build: 7 | ifeq ($(IN_CONTAINER), true) 8 | $(GO) build -ldflags="-X main.version=$(VERSION)" 9 | else 10 | docker build -t $(IMAGE_NAME) . 11 | endif 12 | 13 | binary: 14 | $(GO) build -ldflags="-X main.version=$(VERSION)" 15 | 16 | test: build 17 | ifeq ($(IN_CONTAINER), true) 18 | bash -c 'cd port && $(GO) test' 19 | bash -c 'cd logrotate && $(GO) test' 20 | bash -c 'cd iowire && $(GO) test' 21 | bash -c 'cd procfs && $(GO) test' 22 | else 23 | docker run -it -w "/go/src/github.com/robinmonjo/dock" -e IN_CONTAINER=true $(IMAGE_NAME) bash -c 'make test' 24 | endif 25 | 26 | integration: build 27 | TEST_IMAGE=$(IMAGE_NAME) bash -c 'cd integration && $(GO) test' 28 | 29 | release: 30 | mkdir -p release 31 | GOOS=linux $(GO) build -ldflags="-X main.version=$(VERSION)" -o release/dock 32 | cd release && tar -zcf dock-v$(VERSION).tgz dock 33 | rm release/dock 34 | 35 | vendor: 36 | bash vendor.sh -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dock 2 | 3 | `dock` is a "micro init system" for linux containers 4 | 5 | ## Installation 6 | 7 | ````bash 8 | curl -sL https://github.com/robinmonjo/dock/releases/download/v0.8/dock-v0.8.tgz | tar -C /usr/local/bin -zxf - 9 | ```` 10 | 11 | This will place the latest `dock` binary in `/usr/local/bin` 12 | 13 | ## Inspirations / motivations 14 | 15 | - [Yelp dumn-init](https://github.com/Yelp/dumb-init) 16 | - [Phusion baseimage docker](https://github.com/phusion/baseimage-docker) 17 | 18 | ## How signals are handled 19 | 20 | `dock` acts as the PID 1 in the container. It forwards every signals to it's child process. When a child process dies, `dock` : 21 | 22 | 1. detect one of its child died 23 | 2. send SIGTERM to all processes remaining in its process tree 24 | 3. call `wait4` until no more children exist 25 | 26 | Step 3 may block if some processes do not respond to the SIGTERM in step 2. If this is the case, a SIGKILL is sent after the SIGTERM (within a 5 seconds timeout) 27 | 28 | ## Why `dock` ? 29 | 30 | `dock` is written in Go and has no dependency. The binary can simply be added into a linux container image. It also provides some useful features : 31 | 32 | - can call a web hook when process state changes (`starting`, `running` and `crashed`) 33 | - can say a process started only when a given port is bound (if you start a web server, you may want to know when this one is ready to accept connections). Think container rotation during a deployment process 34 | - smart stdin / stdout (see the `--io` flag for more information) 35 | - can provide log rotation (see `--log-rotate` flag for more information) 36 | - authoritarian signal transmission (see `--thug` flag for more information) 37 | 38 | Note: `dock` may be used outside of a container, directly on a linux system 39 | 40 | ## Usage 41 | 42 | `dock [OPTIONS] command` 43 | 44 | #### `--io` 45 | 46 | Allows to redirect process stdin / stdout: 47 | 48 | - `dock bash` run bash with current stdin and stdout 49 | - `dock --io file:///var/run/process.log server.go` redirect stdout to the given file. Stdin stay unchanged 50 | - `dock --io tcp://192.168.1.9:2567 bash` make stdin and stdout go over a tcp connection 51 | - `dock --io tls://192.168.1.9:2657 bash` make stdin and stdout go over a tls connection 52 | 53 | Every URL scheme supported by Go's `net.Dial` are supported by `dock` 54 | 55 | #### `--web-hook` 56 | 57 | If specified, `dock` performs a HTTP PUT request with a JSON payload that contains information about the process and its environment: 58 | 59 | ````json 60 | { 61 | "ps": { 62 | "status": "running", 63 | "net_interfaces": [ 64 | { 65 | "name": "lo", 66 | "ipv4": "127.0.0.1", 67 | "ipv6": "::1" 68 | }, 69 | { 70 | "name":"eth0", 71 | "ipv4":"172.17.0.2", 72 | "ipv6":"fe80::42:acff:fe11:2" 73 | } 74 | ] 75 | } 76 | } 77 | ```` 78 | 79 | where `status` may be: `starting`, `running` or `crashed`. Note that if `--bind-port` flag is used, the `running` status is sent only once the given port is bound by one of `dock` children processes. 80 | 81 | This payload will evolve to carry more useful information in the future. 82 | 83 | #### `--bind-port` 84 | 85 | Port `dock`'s child process is expected to bind. Port may be bound by any processes in the container. See `--strict-port-binding` for more control. 86 | 87 | #### `--strict-port-binding` 88 | 89 | If `--bind-port` is specified, this flag will ensure that the process is considered running only if the binder is a descendant process of `dock`. This is not really useful in container environment since dock will have PID 1 (hence any port in the container will be bound by a descendant). Be careful while using this flag (TODO: explain why) 90 | 91 | 92 | #### `--log-rotate` 93 | 94 | If given `--io` is a file, specifying `--log-rotate X` perform a log rotation every X hours: 95 | 96 | - archive (gzip) the current log file by prepending a timestamp (in the stdout file directory) 97 | - empty the current log file 98 | - keep at most 5 log archives 99 | 100 | #### `--stdout-prefix` 101 | 102 | Add a prefix to stdout lines. Format: `prefix[:]` where color may be white, green, blue, magenta, yellow, cyan or red 103 | 104 | #### `--thug` 105 | 106 | When you stop a docker container, a SIGTERM is sent to the process running it. The docker daemon then wait for a certain delay and if the container still exists, it will kill the process (using SIGKILL). This happens a lot with process ran as PID 1 inside a linux container, since the [kernel will treat PID 1 specially](http://lwn.net/Articles/532748/). 107 | 108 | With `dock` this behavior happens less frequently since `dock` runs as PID 1. However some program block or ignore some signals. For example, `sh` ignores the SIGTERM signal. Using `docker stop` on a container running `sh` will force the docker engine to kill the process. This may be frustrating. 109 | 110 | The `--thug` flag allows to translate a stopping signal (SIGINT, SIGQUIT, SIGTERM) into a SIGKILL **if** the stopping signal is blocked or ignored by `dock`'s child process 111 | 112 | ## Working on `dock` 113 | 114 | - use the Makefile and Dockerfile :) 115 | - use the `-d` flag for verbosity 116 | - the `utils.go` file contains some nice function to inspect and debug 117 | 118 | # TODOs 119 | 120 | - [why this ?](https://github.com/gliderlabs/docker-alpine/issues/143) 121 | - more tests ! 122 | 123 | ## Known issue (not blocking) 124 | 125 | - process is not stopped if interactive and over the network and the connection is closed 126 | 127 | -------------------------------------------------------------------------------- /integration/assets/spawn_orphaned.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #bash script used to test port watcher 4 | 5 | function launch_nc { 6 | # the pid that binds $PORT should be the grand son of this script pid 7 | echo "spawning" 8 | nc -l 3000 9 | } 10 | 11 | function launch_nc2 { 12 | echo "spawning2" 13 | nc -l 3001 14 | } 15 | 16 | function prepare_nc { 17 | #spawn an other child process 18 | launch_nc & 19 | launch_nc2 & 20 | } 21 | 22 | #spawn a child process 23 | prepare_nc & 24 | -------------------------------------------------------------------------------- /integration/docker.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | 9 | "github.com/kr/pty" 10 | ) 11 | 12 | // struct to play with the docker binary 13 | type docker struct { 14 | path string 15 | ps *os.Process 16 | stdout []byte 17 | stderr []byte 18 | } 19 | 20 | func newDocker() *docker { 21 | return &docker{ 22 | path: "docker", //docker must be in the path 23 | } 24 | } 25 | 26 | func (d *docker) start(usePty bool, args ...string) error { 27 | cmd := exec.Command(d.path, args...) 28 | 29 | stdout, _ := cmd.StdoutPipe() 30 | stderr, _ := cmd.StderrPipe() 31 | 32 | var err error 33 | 34 | if usePty { 35 | var f *os.File 36 | f, err = pty.Start(cmd) 37 | stdout, stderr = f, f 38 | defer f.Close() 39 | } else { 40 | err = cmd.Start() 41 | } 42 | 43 | if err != nil { 44 | return err 45 | } 46 | 47 | d.ps = cmd.Process 48 | d.stdout, _ = ioutil.ReadAll(stdout) 49 | d.stderr, _ = ioutil.ReadAll(stderr) 50 | 51 | return cmd.Wait() 52 | } 53 | 54 | func (d *docker) debugInfo() string { 55 | return fmt.Sprintf("stdout: %s\nstderr: %s", string(d.stdout), string(d.stderr)) 56 | } 57 | -------------------------------------------------------------------------------- /integration/hook_server.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/robinmonjo/dock/notifier" 9 | ) 10 | 11 | type hookServer struct { 12 | c chan notifier.PsStatus 13 | t *testing.T 14 | } 15 | 16 | func (s *hookServer) start() (host, port string) { 17 | host = "192.168.99.1" //WARNING, made to work with docker machine and virtualbox on a mac 18 | port = "8080" 19 | 20 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 21 | decoder := json.NewDecoder(r.Body) 22 | 23 | var payload notifier.HookPayload 24 | 25 | if err := decoder.Decode(&payload); err != nil { 26 | s.t.Fatal(err) 27 | } 28 | 29 | s.c <- payload.Ps.Status 30 | }) 31 | 32 | go func() { 33 | if err := http.ListenAndServe(":"+port, nil); err != nil { 34 | s.t.Fatal(err) 35 | } 36 | }() 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /integration/integration_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/robinmonjo/dock/notifier" 9 | ) 10 | 11 | var ( 12 | testImage string //must match with what is in the Makefile 13 | 14 | server *hookServer 15 | serverURL string 16 | ) 17 | 18 | func init() { 19 | testImage = os.Getenv("TEST_IMAGE") 20 | 21 | server = &hookServer{} 22 | h, p := server.start() 23 | serverURL = fmt.Sprintf("http://%s:%s", h, p) 24 | } 25 | 26 | func TestSimpleCommand(t *testing.T) { 27 | fmt.Println("testing simple command") 28 | d := newDocker() 29 | if err := d.start(true, "run", testImage, "dock", "--debug", "ls"); err != nil { 30 | fmt.Println(d.debugInfo()) 31 | t.Fatal(err) 32 | } 33 | } 34 | 35 | func TestOrphanProcessReaping(t *testing.T) { 36 | fmt.Println("testing orphan process reaping") 37 | d := newDocker() 38 | // using --debug, dock will have a 999 exit code if more than one process exists when exiting 39 | if err := d.start(true, "run", testImage, "dock", "--debug", "bash", "/go/src/github.com/robinmonjo/dock/integration/assets/spawn_orphaned.sh"); err != nil { 40 | fmt.Println(d.debugInfo()) 41 | t.Fatal(err) 42 | } 43 | } 44 | 45 | func TestWebHook(t *testing.T) { 46 | fmt.Println("testing web hook call") 47 | c := make(chan notifier.PsStatus, 3) 48 | 49 | server.c = c 50 | server.t = t 51 | 52 | d := newDocker() 53 | 54 | if err := d.start(false, "run", testImage, "dock", "--debug", "--web-hook", serverURL, "ls"); err != nil { 55 | fmt.Println(d.debugInfo()) 56 | t.Fatal(err) 57 | } 58 | 59 | for _, status := range []notifier.PsStatus{notifier.StatusStarting, notifier.StatusRunning, notifier.StatusCrashed} { 60 | s := <-server.c 61 | if s != status { 62 | t.Fatalf("expected status %q, got %q", notifier.StatusStarting, status) 63 | } 64 | } 65 | } 66 | 67 | func TestPortBindingHook(t *testing.T) { 68 | fmt.Println("testing process is not considered running if specified port is not bound") 69 | c := make(chan notifier.PsStatus, 3) 70 | 71 | server.c = c 72 | server.t = t 73 | port := "9999" 74 | 75 | d := newDocker() 76 | 77 | if err := d.start(false, "run", testImage, "dock", "--debug", "--web-hook", serverURL, "--bind-port", port, "ls"); err != nil { 78 | fmt.Println(d.debugInfo()) 79 | t.Fatal(err) 80 | } 81 | // ls will never bind the port, should never see the "running status" 82 | for _, status := range []notifier.PsStatus{notifier.StatusStarting, notifier.StatusCrashed} { 83 | s := <-server.c 84 | if s != status { 85 | t.Fatalf("expected status %q, got %q", notifier.StatusStarting, status) 86 | } 87 | } 88 | } 89 | 90 | func TestPortBinding(t *testing.T) { 91 | fmt.Println("testing web hook with port binding") 92 | c := make(chan notifier.PsStatus, 3) 93 | 94 | server.c = c 95 | server.t = t 96 | 97 | d := newDocker() 98 | port := "9999" 99 | name := "dock-test-container" 100 | 101 | if err := d.start(true, "run", "-d", "--name", name, testImage, "dock", "--debug", "--web-hook", serverURL, "--bind-port", port, "--strict-port-binding", "--thug", "python", "-m", "SimpleHTTPServer", port); err != nil { 102 | fmt.Println(d.debugInfo()) 103 | t.Fatal(err) 104 | } 105 | 106 | defer d.start(false, "rm", name) 107 | 108 | s := <-server.c 109 | if s != notifier.StatusStarting { 110 | t.Fatalf("expected status %q, got %q", notifier.StatusStarting, s) 111 | } 112 | 113 | s = <-server.c 114 | if s != notifier.StatusRunning { 115 | t.Fatalf("expected status %q, got %q", notifier.StatusRunning, s) 116 | } 117 | 118 | if err := d.start(false, "stop", name); err != nil { 119 | t.Fatal(err) 120 | } 121 | s = <-server.c 122 | if s != notifier.StatusCrashed { 123 | t.Fatalf("expected status %q, got %q", notifier.StatusCrashed, s) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /integration/utils.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "github.com/robinmonjo/dock/notifier" 10 | ) 11 | 12 | func statusFromHookBody(body io.Reader, t *testing.T) notifier.PsStatus { 13 | var payload notifier.HookPayload 14 | 15 | content, _ := ioutil.ReadAll(body) 16 | if err := json.Unmarshal(content, &payload); err != nil { 17 | t.Fatal(err) 18 | } 19 | return payload.Ps.Status 20 | } 21 | -------------------------------------------------------------------------------- /iowire/iowire.go: -------------------------------------------------------------------------------- 1 | package iowire 2 | 3 | import ( 4 | "crypto/tls" 5 | "io" 6 | "net" 7 | "net/url" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | "github.com/docker/docker/pkg/term" 13 | ) 14 | 15 | const ( 16 | DIAL_TIMEOUT = 5 * time.Second 17 | ) 18 | 19 | type Color uint8 20 | 21 | const ( 22 | White = iota 23 | Green 24 | Blue 25 | Magenta 26 | Yellow 27 | Cyan 28 | Red 29 | NoColor 30 | ) 31 | 32 | type Wire struct { 33 | URL *url.URL 34 | prefix []byte 35 | Input io.Reader 36 | Output io.Writer 37 | CloseCh chan bool 38 | } 39 | 40 | func NewWire(uri string) (*Wire, error) { 41 | u, err := url.Parse(uri) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | wire := &Wire{URL: u} 47 | 48 | path := u.Host + u.Path 49 | 50 | if u.Scheme == "" && path != "" { 51 | u.Scheme = "file" 52 | } 53 | 54 | switch u.Scheme { 55 | case "": 56 | wire.Input = os.Stdin //use standard input, output 57 | wire.Output = os.Stdout 58 | case "file": 59 | f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) 60 | if err != nil { 61 | return nil, err 62 | } 63 | wire.Output = f 64 | wire.Input = os.Stdin 65 | 66 | case "ssl": 67 | fallthrough 68 | case "tls": 69 | tcpConn, err := net.DialTimeout("tcp", path, DIAL_TIMEOUT) 70 | if err != nil { 71 | return nil, err 72 | } 73 | config := &tls.Config{InsecureSkipVerify: true} 74 | conn := tls.Client(tcpConn, config) 75 | wire.Input = conn 76 | wire.Output = conn 77 | 78 | default: 79 | conn, err := net.DialTimeout(u.Scheme, path, DIAL_TIMEOUT) 80 | if err != nil { 81 | return nil, err 82 | } 83 | wire.Input = conn 84 | wire.Output = conn 85 | } 86 | 87 | wire.CloseCh = make(chan bool, 10) 88 | 89 | return wire, nil 90 | } 91 | 92 | func (wire *Wire) SetPrefix(prefix string, color Color) { 93 | if color == NoColor { 94 | wire.prefix = []byte(prefix) 95 | } else { 96 | wire.prefix = []byte(escapeCode(color) + prefix + resetEscapeCode()) 97 | } 98 | wire.Write(wire.prefix) //flush first prefix 99 | } 100 | 101 | //tell whether or not the stream is interactive 102 | func (wire *Wire) Interactive() bool { 103 | _, isConnIn := wire.Input.(net.Conn) 104 | _, isConnOut := wire.Output.(net.Conn) 105 | if isConnIn && isConnOut { 106 | return true //assume connection stream are interactive 107 | } 108 | 109 | return wire.Terminal() 110 | } 111 | 112 | func (wire *Wire) Terminal() bool { 113 | _, isTerminalIn := term.GetFdInfo(wire.Input) 114 | _, isTerminalOut := term.GetFdInfo(wire.Output) 115 | return isTerminalIn && isTerminalOut 116 | } 117 | 118 | func (wire *Wire) Write(p []byte) (int, error) { 119 | if len(wire.prefix) == 0 || !strings.HasSuffix(string(p), "\n") { 120 | return wire.Output.Write(p) 121 | } 122 | 123 | //will write a line, write the prefix for next line 124 | n, err := wire.Output.Write(append(p, wire.prefix...)) 125 | 126 | //the caller will except the write to be len(p), so if we have a prefix, it will confused it. 127 | //That is why we need to check for an ErrShortWrite error and return n = n - len(prefix) 128 | 129 | if err != nil && err != io.ErrShortWrite { 130 | return n, err 131 | } 132 | return n - len(wire.prefix), err 133 | } 134 | 135 | func (wire *Wire) Read(p []byte) (int, error) { 136 | if wire.Input == nil { 137 | return 0, nil 138 | } 139 | return wire.Input.Read(p) 140 | } 141 | 142 | func (wire *Wire) Close() { 143 | for _, i := range []interface{}{wire.Input, wire.Output} { 144 | if c, ok := i.(io.ReadCloser); ok { 145 | c.Close() 146 | } 147 | } 148 | wire.CloseCh <- true 149 | } 150 | 151 | func MapColor(c string) Color { 152 | switch c { 153 | case "red": 154 | return Red 155 | case "green": 156 | return Green 157 | case "blue": 158 | return Blue 159 | case "yellow": 160 | return Yellow 161 | case "magenta": 162 | return Magenta 163 | case "cyan": 164 | return Cyan 165 | case "white": 166 | return White 167 | default: 168 | return NoColor 169 | } 170 | } 171 | 172 | func escapeCode(color Color) string { 173 | switch color { 174 | case Red: 175 | return "\x1b[31;1m" 176 | case Green: 177 | return "\x1b[32;1m" 178 | case Blue: 179 | return "\x1b[34;1m" 180 | case Yellow: 181 | return "\x1b[33;1m" 182 | case Magenta: 183 | return "\x1b[35;1m" 184 | case Cyan: 185 | return "\x1b[36;1m" 186 | case White: 187 | return "\x1b[37;1m" 188 | case NoColor: 189 | fallthrough 190 | default: 191 | return "" 192 | } 193 | } 194 | 195 | func resetEscapeCode() string { 196 | return "\x1b[0m" 197 | } 198 | -------------------------------------------------------------------------------- /iowire/iowire_test.go: -------------------------------------------------------------------------------- 1 | package iowire 2 | 3 | import ( 4 | "bufio" 5 | "io/ioutil" 6 | "net" 7 | "os" 8 | "sync" 9 | "testing" 10 | ) 11 | 12 | func Test_remoteWire(t *testing.T) { 13 | //create a simple tcp server 14 | ln, err := net.Listen("tcp", ":9998") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | defer ln.Close() 19 | 20 | var wg sync.WaitGroup 21 | wg.Add(1) 22 | go func() { 23 | conn, err := ln.Accept() 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | message, err := bufio.NewReader(conn).ReadString('\n') 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | conn.Write([]byte(message)) 33 | wg.Done() 34 | }() 35 | 36 | //create a stream on this server 37 | wire, err := NewWire("tcp://127.0.0.1:9998") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | mess := []byte("foo bar\n") 43 | if _, err := wire.Write(mess); err != nil { 44 | t.Fatal(err) 45 | } 46 | wg.Wait() 47 | received, err := bufio.NewReader(wire).ReadString('\n') 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | if string(mess) != received { 53 | t.Fatal("expected to receive %s, got %s", string(mess), received) 54 | } 55 | 56 | //make sure close channel is called 57 | wg.Add(1) 58 | go func() { 59 | <-wire.CloseCh 60 | wg.Done() 61 | }() 62 | 63 | wire.Close() 64 | 65 | wg.Wait() 66 | } 67 | 68 | func Test_fileWire(t *testing.T) { 69 | wire, err := NewWire("file:///tmp/dock_test.log") 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | wire.SetPrefix("prefix ", NoColor) 74 | 75 | wire.Write([]byte("foo bar")) 76 | wire.Close() 77 | 78 | content, err := ioutil.ReadFile("/tmp/dock_test.log") 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | os.Remove("/tmp/dock_test.log") 83 | if string(content) != "prefix foo bar" { 84 | t.Fatalf("expecting \"prefix foo bar\" got \"%s\"", string(content)) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /logrotate/logrotate.go: -------------------------------------------------------------------------------- 1 | package logrotate 2 | 3 | import ( 4 | "bufio" 5 | "compress/gzip" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path" 10 | "path/filepath" 11 | "strings" 12 | "time" 13 | 14 | log "github.com/Sirupsen/logrus" 15 | ) 16 | 17 | var ( 18 | rotationDelay = 6 * time.Hour //6 hours by default 19 | archiveRetainCount = 5 //number of log archive to keep 20 | ) 21 | 22 | type Rotator struct { 23 | LogFile string 24 | RotationDelay time.Duration 25 | ArchiveRetainCount int 26 | ticker *time.Ticker 27 | 28 | workingDir string 29 | watching bool 30 | } 31 | 32 | func NewRotator(logFile string) *Rotator { 33 | dir := filepath.Dir(logFile) 34 | return &Rotator{LogFile: logFile, workingDir: dir, RotationDelay: rotationDelay, ArchiveRetainCount: archiveRetainCount} 35 | } 36 | 37 | // gzipLogFile gzip r current log file by copying it and empty current log file once done. 38 | // it returns the path of the archived file or an error 39 | func (r *Rotator) gzipLogFile() (string, error) { 40 | //gzip the current log file into a new file 41 | t := time.Now().Local() 42 | archive, err := os.Create(fmt.Sprintf("%s_%s.gz", r.LogFile, t.Format("20060102150405"))) 43 | if err != nil { 44 | return "", err 45 | } 46 | 47 | defer archive.Close() 48 | 49 | gzipWriter := gzip.NewWriter(archive) 50 | defer gzipWriter.Close() 51 | 52 | logFile, err := os.Open(r.LogFile) 53 | if err != nil { 54 | return "", err 55 | } 56 | 57 | bufReader := bufio.NewReader(logFile) 58 | if _, err := bufReader.WriteTo(gzipWriter); err != nil { 59 | return "", err 60 | } 61 | if err := gzipWriter.Flush(); err != nil { 62 | return "", err 63 | } 64 | 65 | //empty the current log file 66 | return archive.Name(), ioutil.WriteFile(r.LogFile, []byte(""), 0600) 67 | } 68 | 69 | // keep ArchiveRetainCount on disk and supress others () 70 | func (r *Rotator) cleanupOldArchives() error { 71 | archives, err := r.relatedGZFiles() 72 | if err != nil { 73 | return err 74 | } 75 | // oldest archives are at the start of the array 76 | for i := len(archives) - 1; i >= r.ArchiveRetainCount; i-- { 77 | os.Remove(archives[i]) 78 | } 79 | return nil 80 | } 81 | 82 | // relatedGZFiles looks for file that start with r.LogPath and end with .gz in r.LogPath directory 83 | // returns a list of path sorted !! 84 | func (r *Rotator) relatedGZFiles() ([]string, error) { 85 | files, err := ioutil.ReadDir(r.workingDir) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | relatedFiles := []string{} 91 | for _, f := range files { 92 | fName := f.Name() 93 | if strings.HasPrefix(fName, filepath.Base(r.LogFile)) && filepath.Ext(fName) == ".gz" { 94 | relatedFiles = append(relatedFiles, path.Join(r.workingDir, fName)) 95 | } 96 | } 97 | return relatedFiles, nil 98 | } 99 | 100 | func (r *Rotator) StartWatching() { 101 | r.watching = true 102 | r.ticker = time.NewTicker(r.RotationDelay) 103 | for { 104 | <-r.ticker.C 105 | if !r.watching { 106 | return 107 | } 108 | log.Debug("Log rotation: rotating") 109 | if _, err := r.gzipLogFile(); err != nil { 110 | log.Errorf("Log rotation: %v", err) 111 | } 112 | if err := r.cleanupOldArchives(); err != nil { 113 | log.Errorf("Log rotation: %v", err) 114 | } 115 | } 116 | } 117 | 118 | func (r *Rotator) StopWatching() { 119 | if r.watching { 120 | r.ticker.Stop() 121 | } 122 | r.watching = false 123 | } 124 | -------------------------------------------------------------------------------- /logrotate/logrotate_test.go: -------------------------------------------------------------------------------- 1 | package logrotate 2 | 3 | import ( 4 | "compress/gzip" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func Test_gzipLogFile(t *testing.T) { 14 | var ( 15 | logFileContent = "foo bar foo bar foo bar foo bar\n foo bar foo bar foo bar foo bar foo bar" 16 | ) 17 | 18 | logFile, err := ioutil.TempFile("", "") 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | defer func() { 23 | logFile.Close() 24 | os.Remove(logFile.Name()) 25 | }() 26 | 27 | if err := ioutil.WriteFile(logFile.Name(), []byte(logFileContent), 0600); err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | r := NewRotator(logFile.Name()) 32 | archive, err := r.gzipLogFile() 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | //logFile must be empty 38 | cont, err := ioutil.ReadAll(logFile) 39 | if string(cont) != "" { 40 | t.Fatalf("log file not empty: %s", string(cont)) 41 | } 42 | 43 | //archive must contains logFileContent 44 | archiveFile, err := os.Open(archive) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | defer func() { 49 | archiveFile.Close() 50 | os.Remove(archiveFile.Name()) 51 | }() 52 | 53 | gzipReader, err := gzip.NewReader(archiveFile) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | defer gzipReader.Close() 58 | 59 | archiveContent, err := ioutil.ReadAll(gzipReader) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | if string(archiveContent) != logFileContent { 64 | t.Fatalf("archive content doesn't match original content: %s", string(archiveContent)) 65 | } 66 | } 67 | 68 | func Test_cleanupOldArchives(t *testing.T) { 69 | nbArchives := 10 70 | 71 | r := NewRotator(path.Join(os.TempDir(), "logfile.log")) 72 | 73 | archives := []string{} 74 | for i := 0; i < nbArchives; i++ { 75 | archives = append(archives, fmt.Sprintf("%s/logfile.log_2006010215040%d.gz", os.TempDir(), i)) 76 | } 77 | 78 | //create archive files 79 | for _, a := range archives { 80 | if _, err := os.Create(a); err != nil { 81 | t.Fatal(err) 82 | } 83 | defer os.Remove(a) 84 | } 85 | 86 | foundArchives, err := r.relatedGZFiles() 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | if len(foundArchives) != nbArchives { 91 | t.Fatalf("excepted to find %d archives, found %d", nbArchives, len(foundArchives)) 92 | } 93 | 94 | //cleanup 95 | if err := r.cleanupOldArchives(); err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | //should only be 100 | foundArchives, err = r.relatedGZFiles() 101 | if len(foundArchives) != r.ArchiveRetainCount { 102 | t.Fatalf("expected to find %d archives, got %d", r.ArchiveRetainCount, len(foundArchives)) 103 | } 104 | } 105 | 106 | func Test_StartWatching(t *testing.T) { 107 | logFile, err := ioutil.TempFile("", "psdock_") 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | defer func() { 112 | logFile.Close() 113 | os.Remove(logFile.Name()) 114 | }() 115 | 116 | r := NewRotator(logFile.Name()) 117 | r.RotationDelay = 230 * time.Millisecond 118 | r.ArchiveRetainCount = 1 119 | 120 | //create a routine that continuously write on the file 121 | go func() { 122 | for { 123 | if _, err := logFile.Write([]byte("foo bar\n")); err != nil { 124 | t.Fatal(err) 125 | } 126 | time.Sleep(100 * time.Millisecond) 127 | } 128 | }() 129 | 130 | go r.StartWatching() 131 | 132 | time.Sleep(1 * time.Second) 133 | 134 | r.StopWatching() 135 | 136 | //only one archive should be left 137 | foundArchives, err := r.relatedGZFiles() 138 | if err != nil { 139 | t.Fatal(err) 140 | } 141 | 142 | if len(foundArchives) != 1 { 143 | t.Fatalf("expected one archive, got %d", len(foundArchives)) 144 | } 145 | os.Remove(foundArchives[0]) 146 | } 147 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | "github.com/codegangsta/cli" 10 | "github.com/robinmonjo/dock/notifier" 11 | "github.com/robinmonjo/dock/port" 12 | "github.com/robinmonjo/dock/procfs" 13 | 14 | "github.com/robinmonjo/dock/iowire" 15 | "github.com/robinmonjo/dock/logrotate" 16 | ) 17 | 18 | var ( 19 | version string //injected by the makefile 20 | ) 21 | 22 | func main() { 23 | app := cli.NewApp() 24 | app.Name = "dock" 25 | app.Version = fmt.Sprintf("v%s", version) 26 | app.Author = "Robin Monjo" 27 | app.Email = "robinmonjo@gmail.com" 28 | app.Usage = "micro init system for containers" 29 | 30 | app.Flags = []cli.Flag{ 31 | cli.StringFlag{Name: "io", Usage: "smart stdin / stdout (see README for more info)"}, 32 | cli.StringFlag{Name: "web-hook", Usage: "hook where process status changes should be notified"}, 33 | cli.StringFlag{Name: "bind-port", Usage: "port the process is expected to bind"}, 34 | cli.BoolFlag{Name: "strict-port-binding", Usage: "when bind-port is specified, ensure binding PID is a descendant of dock (see doc for more info)"}, 35 | cli.IntFlag{Name: "log-rotate", Usage: "duration in hour when stdoud should rotate (if `--io` is a file)"}, 36 | cli.StringFlag{Name: "stdout-prefix", Usage: "add a prefix to stdout lines (format: :)"}, 37 | cli.BoolFlag{Name: "debug, d", Usage: "run with verbose output (for developpers)"}, 38 | cli.BoolFlag{Name: "thug", Usage: "translate stopping signals in SIGKILL if process ignore or block the signal"}, 39 | } 40 | 41 | app.Action = func(c *cli.Context) { 42 | 43 | if c.Bool("debug") { 44 | log.SetLevel(log.DebugLevel) 45 | } 46 | 47 | exit, err := start(c) 48 | if err != nil { 49 | log.Error(err) 50 | } 51 | log.Debugf("exit status: %d", exit) 52 | os.Exit(exit) 53 | } 54 | 55 | if err := app.Run(os.Args); err != nil { 56 | log.Fatal(err) 57 | } 58 | } 59 | 60 | func start(c *cli.Context) (int, error) { 61 | if len(c.Args()) == 0 { 62 | cli.ShowAppHelp(c) 63 | return 0, nil 64 | } 65 | 66 | log.Debugf("dock pid: %d", os.Getpid()) 67 | 68 | wire, err := iowire.NewWire(c.String("io")) 69 | if err != nil { 70 | return 1, err 71 | } 72 | defer wire.Close() 73 | 74 | wire.SetPrefix(parsePrefixArg(c.String("stdout-prefix"))) 75 | 76 | process := &process{ 77 | argv: c.Args(), 78 | wire: wire, 79 | } 80 | defer process.cleanup() 81 | 82 | sh := newSignalsHandler() 83 | sh.authority = c.Bool("thug") 84 | 85 | wh := c.String("web-hook") 86 | notifier.WebHook = wh 87 | 88 | processStateChanged(notifier.StatusStarting) 89 | defer processStateChanged(notifier.StatusCrashed) 90 | 91 | if err := process.start(); err != nil { 92 | return exitStatusFromError(err), err 93 | } 94 | 95 | log.Debugf("process pid: %d", process.pid()) 96 | 97 | // log rotation is specified and if stdout redirecto to a file 98 | if c.Int("log-rotate") > 0 && wire.URL.Scheme == "file" { 99 | r := logrotate.NewRotator(wire.URL.Host + wire.URL.Path) 100 | r.RotationDelay = time.Duration(c.Int("log-rotate")) * time.Hour 101 | go r.StartWatching() 102 | defer r.StopWatching() 103 | } 104 | 105 | // watch ports 106 | go func() { 107 | bindPort := c.String("bind-port") 108 | if bindPort != "" { 109 | waitPortBinding(bindPort, c.Bool("strict-port-binding")) 110 | } else { 111 | processStateChanged(notifier.StatusRunning) 112 | } 113 | }() 114 | 115 | exit := sh.forward(process) //blocking call 116 | 117 | if c.Bool("debug") { 118 | //assert, at this point only 1 process should be running, self 119 | i, err := procfs.CountRunningProcs() 120 | if err != nil { 121 | log.Error(err) 122 | } else { 123 | if i != 1 { 124 | exit = 999 125 | } 126 | } 127 | } 128 | return exit, nil 129 | } 130 | 131 | func processStateChanged(state notifier.PsStatus) { 132 | log.Debugf("process state: %q", state) 133 | if notifier.WebHook != "" { 134 | notifier.NotifyHook(state) 135 | } 136 | } 137 | 138 | func waitPortBinding(watchedPort string, strictBinding bool) { 139 | for { 140 | p := procfs.Self() 141 | 142 | pids := []int{} 143 | 144 | if strictBinding { 145 | descendants, err := p.Descendants() 146 | if err != nil { 147 | log.Error(err) 148 | break 149 | } 150 | 151 | for _, p := range descendants { 152 | pids = append(pids, p.Pid) 153 | } 154 | log.Debug(pids) 155 | } 156 | 157 | binderPid, err := port.IsPortBound(watchedPort, pids) 158 | if err != nil { 159 | log.Error(err) 160 | break 161 | } 162 | log.Debug(binderPid) 163 | if binderPid != -1 { 164 | log.Debugf("port %s binded by pid %d (used strict check: %v)", watchedPort, binderPid, strictBinding) 165 | processStateChanged(notifier.StatusRunning) 166 | break 167 | } 168 | time.Sleep(200 * time.Millisecond) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /notifier/notifier.go: -------------------------------------------------------------------------------- 1 | package notifier 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "fmt" 8 | "net" 9 | "net/http" 10 | "net/url" 11 | 12 | log "github.com/Sirupsen/logrus" 13 | ) 14 | 15 | type PsStatus string 16 | 17 | const ( 18 | StatusStarting PsStatus = "starting" 19 | StatusRunning PsStatus = "running" 20 | StatusCrashed PsStatus = "crashed" 21 | ) 22 | 23 | var WebHook string 24 | 25 | type Ps struct { 26 | Status PsStatus `json:"status"` 27 | NetInterfaces []*NetInterface `json:"net_interfaces"` 28 | } 29 | 30 | type NetInterface struct { 31 | Name string `json:"name"` 32 | IPv4 string `json:"ipv4,omitempty"` 33 | IPv6 string `json:"ipv6,omitempty"` 34 | } 35 | 36 | type HookPayload struct { 37 | Ps *Ps `json:"ps"` 38 | } 39 | 40 | func NotifyHook(status PsStatus) error { 41 | payload := &HookPayload{ 42 | &Ps{ 43 | Status: status, 44 | NetInterfaces: netInterfaces(), 45 | }, 46 | } 47 | 48 | body, err := json.Marshal(payload) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | req, err := http.NewRequest("PUT", WebHook, bytes.NewBuffer(body)) 54 | req.Header.Set("Content-Type", "application/json") 55 | 56 | u, err := url.Parse(WebHook) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | var client *http.Client 62 | 63 | if u.Scheme == "https" { 64 | tr := &http.Transport{ 65 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 66 | } 67 | client = &http.Client{Transport: tr} 68 | } else { 69 | client = &http.Client{} 70 | } 71 | 72 | resp, err := client.Do(req) 73 | if err != nil { 74 | return err 75 | } 76 | defer resp.Body.Close() 77 | 78 | if resp.StatusCode < 200 || resp.StatusCode > 299 { 79 | return fmt.Errorf("bad status code expected 200 .. 299 got %d", resp.Status) 80 | } 81 | return nil 82 | } 83 | 84 | func netInterfaces() (netInterfaces []*NetInterface) { 85 | ifaces, err := net.Interfaces() 86 | if err != nil { 87 | log.Error(err) 88 | return 89 | } 90 | 91 | for _, iface := range ifaces { 92 | addrs, err := iface.Addrs() 93 | if err != nil { 94 | log.Error(err) 95 | continue 96 | } 97 | 98 | netInterface := &NetInterface{ 99 | Name: iface.Name, 100 | } 101 | 102 | for _, addr := range addrs { 103 | var ip net.IP 104 | switch v := addr.(type) { 105 | case *net.IPNet: 106 | ip = v.IP 107 | case *net.IPAddr: 108 | ip = v.IP 109 | } 110 | 111 | if ip.To4() != nil { 112 | netInterface.IPv4 = ip.String() 113 | } else { 114 | netInterface.IPv6 = ip.String() 115 | } 116 | } 117 | netInterfaces = append(netInterfaces, netInterface) 118 | } 119 | return 120 | } 121 | -------------------------------------------------------------------------------- /port/port.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "os" 5 | "sort" 6 | 7 | "github.com/robinmonjo/dock/procfs" 8 | ) 9 | 10 | // check whether the port is bound by one of the given PID. Return the PID of the process, 0 if no pids list specified or -1 if port is not bound 11 | func IsPortBound(port string, pids []int) (int, error) { 12 | sockets, err := procfs.ReadNet() 13 | if err != nil { 14 | return -1, err 15 | } 16 | 17 | if len(pids) == 0 { 18 | //no PIDs specified, just check if the port is bound 19 | for _, s := range sockets { 20 | if s.LocalPort == port { 21 | return 0, nil //port is bound but we don't care about the PID 22 | } 23 | } 24 | return -1, nil 25 | } 26 | 27 | sort.Sort(procfs.Sockets(sockets)) //sort output by inode for faster search 28 | 29 | for _, pid := range pids { 30 | p := &procfs.Proc{ 31 | Pid: pid, 32 | } 33 | 34 | // get back all file descriptors associated to this PID 35 | fds, err := p.Fds() 36 | if err != nil { 37 | if !os.IsPermission(err) { 38 | return -1, err 39 | } 40 | } 41 | 42 | // get back inodes 43 | inodes := []string{} 44 | 45 | for _, fd := range fds { 46 | inode := fd.SocketInode() 47 | if inode != "" { 48 | inodes = append(inodes, inode) 49 | } 50 | } 51 | 52 | for _, inode := range inodes { 53 | if s := procfs.Sockets(sockets).Find(inode); s != nil { 54 | if s.LocalPort == port { 55 | return p.Pid, nil 56 | } 57 | } 58 | } 59 | } 60 | return -1, nil 61 | } 62 | -------------------------------------------------------------------------------- /port/port_test.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestIsPortBound(t *testing.T) { 11 | port := "8080" 12 | go func() { 13 | err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | }() 18 | 19 | maxTry := 10 20 | for i := 0; i < maxTry; i++ { 21 | pid, err := IsPortBound(port, []int{os.Getpid()}) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | if pid == -1 { 26 | continue //port not bound yet 27 | } 28 | //port bound 29 | if pid != os.Getpid() { 30 | t.Fatal("expect port to be bound by %d, got %d", os.Getpid(), pid) 31 | } else { 32 | return // ok 33 | } 34 | } 35 | t.Fatal("port never bound :(") 36 | } 37 | 38 | func TestIsPortBoundNonStrict(t *testing.T) { 39 | port := "8081" 40 | go func() { 41 | err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | }() 46 | 47 | maxTry := 10 48 | for i := 0; i < maxTry; i++ { 49 | pid, err := IsPortBound(port, []int{}) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | if pid == -1 { 54 | continue //port not bound yet 55 | } 56 | //port bound 57 | if pid != 0 { 58 | t.Fatal("expect port to be bound by %d, got %d", 0, pid) 59 | } else { 60 | return // ok 61 | } 62 | } 63 | t.Fatal("port never bound :(") 64 | } 65 | -------------------------------------------------------------------------------- /process.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "syscall" 9 | 10 | "github.com/docker/docker/pkg/term" 11 | "github.com/kr/pty" 12 | "github.com/robinmonjo/dock/iowire" 13 | ) 14 | 15 | type process struct { 16 | argv []string //argv[0] must be the path 17 | cmd *exec.Cmd 18 | wire *iowire.Wire 19 | pty *os.File 20 | termState *termState 21 | } 22 | 23 | type termState struct { 24 | state *term.State 25 | fd uintptr //file descriptor associated with the state 26 | } 27 | 28 | func (p *process) start() error { 29 | n := len(p.argv) 30 | if n == 0 { 31 | return fmt.Errorf("argv can't be empty") 32 | } 33 | 34 | path, err := exec.LookPath(p.argv[0]) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | var args []string 40 | if n > 1 { 41 | args = p.argv[1:n] 42 | } 43 | 44 | p.cmd = exec.Command(path, args...) 45 | 46 | p.cmd.SysProcAttr = &syscall.SysProcAttr{ 47 | Pdeathsig: syscall.SIGTERM, 48 | } 49 | 50 | if p.wire.Interactive() { 51 | go func() { 52 | <-p.wire.CloseCh 53 | //if interactive and stream closed, send a sigterm to the process 54 | p.signal(syscall.SIGTERM) 55 | }() 56 | return p.startInteractive() 57 | } else { 58 | return p.startNonInteractive() 59 | } 60 | } 61 | 62 | func (p *process) startNonInteractive() error { 63 | p.cmd.Stdin = p.wire 64 | p.cmd.Stdout = p.wire 65 | p.cmd.Stderr = p.wire 66 | 67 | return p.cmd.Start() 68 | } 69 | 70 | func (p *process) startInteractive() error { 71 | f, err := pty.Start(p.cmd) 72 | if err != nil { 73 | return err 74 | } 75 | p.pty = f 76 | 77 | if p.wire.Input == os.Stdin { 78 | // the current terminal shall pass everything to the console, make it ignores ctrl+C etc ... 79 | // this is done by making the terminal raw. The state is saved to reset user's terminal settings 80 | // when dock exits 81 | state, err := term.SetRawTerminal(os.Stdin.Fd()) 82 | if err != nil { 83 | return err 84 | } 85 | p.termState = &termState{ 86 | state: state, 87 | fd: os.Stdin.Fd(), 88 | } 89 | } else { 90 | // wire.Input is a socket (tcp, tls ...). Obvioulsy, we can't set the remote user's terminal in raw mode, however we can at least 91 | // disable echo on the console 92 | state, err := term.SaveState(p.pty.Fd()) 93 | if err != nil { 94 | return err 95 | } 96 | if err := term.DisableEcho(p.pty.Fd(), state); err != nil { 97 | return err 98 | } 99 | p.termState = &termState{ 100 | state: state, 101 | fd: p.pty.Fd(), 102 | } 103 | } 104 | 105 | p.resizePty() 106 | go io.Copy(p.wire, f) 107 | go io.Copy(f, p.wire) 108 | return nil 109 | } 110 | 111 | func (p *process) wait() error { 112 | return p.cmd.Wait() 113 | } 114 | 115 | func (p *process) cleanup() { 116 | p.wire.Close() 117 | if p.pty != nil { 118 | p.pty.Close() 119 | } 120 | if p.termState != nil { 121 | term.RestoreTerminal(p.termState.fd, p.termState.state) 122 | } 123 | } 124 | 125 | func (p *process) pid() int { 126 | return p.cmd.Process.Pid 127 | } 128 | 129 | func (p *process) signal(sig os.Signal) error { 130 | return p.cmd.Process.Signal(sig) 131 | } 132 | 133 | func (p *process) resizePty() error { 134 | if p.pty == nil { 135 | return nil 136 | } 137 | ws, err := term.GetWinsize(os.Stdin.Fd()) 138 | if err != nil { 139 | return err 140 | } 141 | return term.SetWinsize(p.pty.Fd(), ws) 142 | } 143 | -------------------------------------------------------------------------------- /procfs/README.md: -------------------------------------------------------------------------------- 1 | # procfs 2 | 3 | Package `procfs` provides some information that could be retrieved by scanning the `/proc` file system on linux. It's entirely native and requires no dependencies. 4 | 5 | See `doc.go` for source code of a simple tool that simulates the `ps` utility. It also provides informations about TCP and UDP ports bound by a process 6 | 7 | 8 | -------------------------------------------------------------------------------- /procfs/assets/README.md: -------------------------------------------------------------------------------- 1 | `proc` folder simulate a `/proc` folder that can be found in traditional linux. Used for testing 2 | -------------------------------------------------------------------------------- /procfs/assets/proc/1/status: -------------------------------------------------------------------------------- 1 | Name: bash 2 | State: S (sleeping) 3 | Tgid: 9 4 | Ngid: 0 5 | Pid: 1 6 | PPid: 0 7 | TracerPid: 0 8 | Uid: 0 0 0 0 9 | Gid: 0 0 0 0 10 | FDSize: 256 11 | Groups: 12 | VmPeak: 18240 kB 13 | VmSize: 18176 kB 14 | VmLck: 0 kB 15 | VmPin: 0 kB 16 | VmHWM: 3296 kB 17 | VmRSS: 3296 kB 18 | VmData: 356 kB 19 | VmStk: 136 kB 20 | VmExe: 956 kB 21 | VmLib: 2288 kB 22 | VmPTE: 56 kB 23 | VmPMD: 12 kB 24 | VmSwap: 0 kB 25 | Threads: 1 26 | SigQ: 0/7902 27 | SigPnd: 0000000000000000 28 | ShdPnd: 0000000000000000 29 | SigBlk: 0000000000010000 30 | SigIgn: 0000000000380004 31 | SigCgt: 000000004b817efb 32 | CapInh: 00000000a80425fb 33 | CapPrm: 00000000a80425fb 34 | CapEff: 00000000a80425fb 35 | CapBnd: 00000000a80425fb 36 | Seccomp: 0 37 | Cpus_allowed: 3 38 | Cpus_allowed_list: 0-1 39 | Mems_allowed: 1 40 | Mems_allowed_list: 0 41 | voluntary_ctxt_switches: 73 42 | nonvoluntary_ctxt_switches: 64 43 | -------------------------------------------------------------------------------- /procfs/assets/proc/12/status: -------------------------------------------------------------------------------- 1 | Name: nc 2 | State: S (sleeping) 3 | Tgid: 24193 4 | Pid: 12 5 | PPid: 9 6 | TracerPid: 0 7 | Uid: 1000 1000 1000 1000 8 | Gid: 1000 1000 1000 1000 9 | FDSize: 256 10 | Groups: 4 20 24 25 29 30 44 46 110 111 1000 11 | VmPeak: 14396 kB 12 | VmSize: 14396 kB 13 | VmLck: 0 kB 14 | VmPin: 0 kB 15 | VmHWM: 516 kB 16 | VmRSS: 516 kB 17 | VmData: 716 kB 18 | VmStk: 136 kB 19 | VmExe: 28 kB 20 | VmLib: 3216 kB 21 | VmPTE: 48 kB 22 | VmSwap: 0 kB 23 | Threads: 1 24 | SigQ: 8/119992 25 | SigPnd: 0000000000000000 26 | ShdPnd: 0000000000000000 27 | SigBlk: 0000000000000000 28 | SigIgn: 0000000000000000 29 | SigCgt: 0000000180000000 30 | CapInh: 0000000000000000 31 | CapPrm: 0000000000000000 32 | CapEff: 0000000000000000 33 | CapBnd: 0000001fffffffff 34 | Seccomp: 0 35 | Cpus_allowed: f 36 | Cpus_allowed_list: 0-3 37 | Mems_allowed: 00000000,00000001 38 | Mems_allowed_list: 0 39 | voluntary_ctxt_switches: 3 40 | nonvoluntary_ctxt_switches: 1 41 | -------------------------------------------------------------------------------- /procfs/assets/proc/14/status: -------------------------------------------------------------------------------- 1 | Name: nc 2 | State: S (sleeping) 3 | Tgid: 24193 4 | Pid: 14 5 | PPid: 9 6 | TracerPid: 0 7 | Uid: 1000 1000 1000 1000 8 | Gid: 1000 1000 1000 1000 9 | FDSize: 256 10 | Groups: 4 20 24 25 29 30 44 46 110 111 1000 11 | VmPeak: 14396 kB 12 | VmSize: 14396 kB 13 | VmLck: 0 kB 14 | VmPin: 0 kB 15 | VmHWM: 516 kB 16 | VmRSS: 516 kB 17 | VmData: 716 kB 18 | VmStk: 136 kB 19 | VmExe: 28 kB 20 | VmLib: 3216 kB 21 | VmPTE: 48 kB 22 | VmSwap: 0 kB 23 | Threads: 1 24 | SigQ: 8/119992 25 | SigPnd: 0000000000000000 26 | ShdPnd: 0000000000000000 27 | SigBlk: 0000000000000000 28 | SigIgn: 0000000000000000 29 | SigCgt: 0000000180000000 30 | CapInh: 0000000000000000 31 | CapPrm: 0000000000000000 32 | CapEff: 0000000000000000 33 | CapBnd: 0000001fffffffff 34 | Seccomp: 0 35 | Cpus_allowed: f 36 | Cpus_allowed_list: 0-3 37 | Mems_allowed: 00000000,00000001 38 | Mems_allowed_list: 0 39 | voluntary_ctxt_switches: 3 40 | nonvoluntary_ctxt_switches: 1 41 | -------------------------------------------------------------------------------- /procfs/assets/proc/9/fd/0: -------------------------------------------------------------------------------- 1 | ../../symlinktargets/targ0 -------------------------------------------------------------------------------- /procfs/assets/proc/9/fd/1: -------------------------------------------------------------------------------- 1 | ../../symlinktargets/targ1 -------------------------------------------------------------------------------- /procfs/assets/proc/9/fd/2: -------------------------------------------------------------------------------- 1 | ../../symlinktargets/targ2 -------------------------------------------------------------------------------- /procfs/assets/proc/9/fd/3: -------------------------------------------------------------------------------- 1 | ../../symlinktargets/socket:[84336181] -------------------------------------------------------------------------------- /procfs/assets/proc/9/status: -------------------------------------------------------------------------------- 1 | Name: nc 2 | State: S (sleeping) 3 | Tgid: 24193 4 | Pid: 9 5 | PPid: 1 6 | TracerPid: 0 7 | Uid: 1000 1000 1000 1000 8 | Gid: 1000 1000 1000 1000 9 | FDSize: 256 10 | Groups: 4 20 24 25 29 30 44 46 110 111 1000 11 | VmPeak: 14396 kB 12 | VmSize: 14396 kB 13 | VmLck: 0 kB 14 | VmPin: 0 kB 15 | VmHWM: 516 kB 16 | VmRSS: 516 kB 17 | VmData: 716 kB 18 | VmStk: 136 kB 19 | VmExe: 28 kB 20 | VmLib: 3216 kB 21 | VmPTE: 48 kB 22 | VmSwap: 0 kB 23 | Threads: 1 24 | SigQ: 8/119992 25 | SigPnd: 0000000000000000 26 | ShdPnd: 0000000000000000 27 | SigBlk: 0000000000000000 28 | SigIgn: 0000000000000000 29 | SigCgt: 0000000180000000 30 | CapInh: 0000000000000000 31 | CapPrm: 0000000000000000 32 | CapEff: 0000000000000000 33 | CapBnd: 0000001fffffffff 34 | Seccomp: 0 35 | Cpus_allowed: f 36 | Cpus_allowed_list: 0-3 37 | Mems_allowed: 00000000,00000001 38 | Mems_allowed_list: 0 39 | voluntary_ctxt_switches: 3 40 | nonvoluntary_ctxt_switches: 1 41 | -------------------------------------------------------------------------------- /procfs/assets/proc/dir/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinmonjo/dock/5901d367a6ae3d36b2272e81ab83d9227cf8f8de/procfs/assets/proc/dir/.gitkeep -------------------------------------------------------------------------------- /procfs/assets/proc/junk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinmonjo/dock/5901d367a6ae3d36b2272e81ab83d9227cf8f8de/procfs/assets/proc/junk -------------------------------------------------------------------------------- /procfs/assets/proc/net/tcp: -------------------------------------------------------------------------------- 1 | sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 2 | 0: 00000000:270F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 84336181 1 0000000000000000 100 0 0 10 0 3 | 1: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 549078 1 0000000000000000 100 0 0 10 0 4 | 2: 0103000A:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 18047 1 0000000000000000 100 0 0 10 0 5 | 3: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 8930 1 0000000000000000 100 0 0 10 0 6 | 4: 8EA6EE0A:0050 6C8B480A:6B98 01 00000000:00000000 00:00000000 00000000 0 0 84340179 1 0000000000000000 20 0 0 10 97 7 | 5: 8EA6EE0A:0050 8CC9210A:78CF 06 00000000:00000000 03:000004B2 00000000 0 0 0 3 0000000000000000 8 | 6: 8EA6EE0A:0050 6C8B480A:6BA4 01 00000000:00000000 00:00000000 00000000 0 0 84340305 1 0000000000000000 20 4 27 10 97 9 | 7: 8EA6EE0A:0050 8CC9210A:78BF 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 10 | 8: 8EA6EE0A:0050 6C8B480A:6B8B 06 00000000:00000000 03:0000069B 00000000 0 0 0 3 0000000000000000 11 | 9: 8EA6EE0A:0050 EE13230A:2AB3 01 00000000:00000000 00:00000000 00000000 0 0 84340172 1 0000000000000000 20 0 0 10 19 12 | 10: 8EA6EE0A:0050 BD80480A:7C46 01 00000000:00000000 00:00000000 00000000 0 0 84339878 1 0000000000000000 21 0 0 10 -1 13 | 11: 0103000A:882C 0A03000A:125C 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 14 | 12: 8EA6EE0A:0050 BD80480A:7C35 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 15 | 13: 8EA6EE0A:0050 AA90680A:A278 01 00000000:00000000 00:00000000 00000000 0 0 84340182 1 0000000000000000 21 0 0 10 18 16 | 14: 8EA6EE0A:0050 D428D10A:584F 01 00000000:00000000 00:00000000 00000000 0 0 84340157 1 0000000000000000 20 0 0 10 16 17 | 15: 8EA6EE0A:0050 AA90680A:A264 01 00000000:00000000 00:00000000 00000000 0 0 84339099 1 0000000000000000 21 0 0 10 18 18 | 16: 8EA6EE0A:0050 BD80480A:7C49 06 00000000:00000000 03:00000CBE 00000000 0 0 0 3 0000000000000000 19 | 17: 8EA6EE0A:0050 FBA3210A:166A 06 00000000:00000000 03:00000D05 00000000 0 0 0 3 0000000000000000 20 | 18: 8EA6EE0A:0050 6C8B480A:6B87 01 00000000:00000000 00:00000000 00000000 0 0 84339854 1 0000000000000000 20 0 0 10 97 21 | 19: 8EA6EE0A:0050 6C8B480A:6B7D 06 00000000:00000000 03:00000186 00000000 0 0 0 3 0000000000000000 22 | 20: 0103000A:E099 1203000A:0ED8 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 23 | 21: 8EA6EE0A:0050 EE13230A:2AB1 06 00000000:00000000 03:00000CC7 00000000 0 0 0 3 0000000000000000 24 | 22: 8EA6EE0A:0050 EE13230A:2AB6 01 00000000:00000000 00:00000000 00000000 0 0 84340174 1 0000000000000000 20 0 0 10 19 25 | 23: 8EA6EE0A:0050 8CC9210A:78AC 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 26 | 24: 8EA6EE0A:0050 6C8B480A:6B9C 01 00000000:00000000 00:00000000 00000000 0 0 84340209 1 0000000000000000 20 0 0 10 97 27 | 25: 8EA6EE0A:0050 AA90680A:A26D 01 00000000:00000000 00:00000000 00000000 0 0 84340136 1 0000000000000000 21 0 0 10 18 28 | 26: 8EA6EE0A:0050 FBA3210A:1663 06 00000000:00000000 03:000006CE 00000000 0 0 0 3 0000000000000000 29 | 27: 8EA6EE0A:0050 6C8B480A:6B6A 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 30 | 28: 8EA6EE0A:0050 FBA3210A:166D 06 00000000:00000000 03:00001307 00000000 0 0 0 3 0000000000000000 31 | 29: 8EA6EE0A:0050 FBA3210A:1672 01 00000000:00000000 00:00000000 00000000 0 0 84336125 1 0000000000000000 20 0 0 10 55 32 | 30: 8EA6EE0A:0050 FBA3210A:1664 01 00000000:00000000 00:00000000 00000000 0 0 84338977 1 0000000000000000 20 0 0 10 55 33 | 31: 8EA6EE0A:0050 6C8B480A:6B9A 06 00000000:00000000 03:00000F8D 00000000 0 0 0 3 0000000000000000 34 | 32: 8EA6EE0A:0050 8CC9210A:78BC 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 35 | 33: 8EA6EE0A:0050 6C8B480A:6B80 06 00000000:00000000 03:0000041E 00000000 0 0 0 3 0000000000000000 36 | 34: 8EA6EE0A:0050 6C8B480A:6B9D 06 00000000:00000000 03:00001288 00000000 0 0 0 3 0000000000000000 37 | 35: 8EA6EE0A:0050 6C8B480A:6B75 06 00000000:00000000 03:00001518 00000000 0 0 0 3 0000000000000000 38 | 36: 8EA6EE0A:9FA9 95A41F32:01BB 06 00000000:00000000 03:000016F8 00000000 0 0 0 3 0000000000000000 39 | 37: 8EA6EE0A:0050 8CC9210A:78D0 01 00000000:00000000 00:00000000 00000000 0 0 84339880 1 0000000000000000 20 0 0 10 72 40 | 38: 8EA6EE0A:0050 FBA3210A:1671 06 00000000:00000000 03:00001286 00000000 0 0 0 3 0000000000000000 41 | 39: 8EA6EE0A:0050 6C8B480A:6B81 01 00000000:00000000 00:00000000 00000000 0 0 84335907 1 0000000000000000 20 0 0 10 97 42 | 40: 8EA6EE0A:0050 8CC9210A:78DC 06 00000000:00000000 03:00000C66 00000000 0 0 0 3 0000000000000000 43 | 41: 8EA6EE0A:0050 28C64A0A:83CD 01 00000000:00000000 00:00000000 00000000 0 0 84338193 1 0000000000000000 20 0 0 10 68 44 | 42: 8EA6EE0A:0050 6C8B480A:6BB0 01 00000000:00000000 00:00000000 00000000 0 0 84340382 1 0000000000000000 20 0 0 10 97 45 | 43: 8EA6EE0A:0050 BD80480A:7C43 06 00000000:00000000 03:00000106 00000000 0 0 0 3 0000000000000000 46 | 44: 8EA6EE0A:0050 8CC9210A:78D3 06 00000000:00000000 03:00000A36 00000000 0 0 0 3 0000000000000000 47 | 45: 8EA6EE0A:0050 FBA3210A:164A 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 48 | 46: 8EA6EE0A:0050 EE13230A:2AB4 01 00000000:00000000 00:00000000 00000000 0 0 84340173 1 0000000000000000 20 0 0 10 19 49 | 47: 8EA6EE0A:0050 FBA3210A:1654 06 00000000:00000000 03:0000003E 00000000 0 0 0 3 0000000000000000 50 | 48: 8EA6EE0A:0050 6C8B480A:6B9B 01 00000000:00000000 00:00000000 00000000 0 0 84340198 1 0000000000000000 20 0 0 10 97 51 | 49: 8EA6EE0A:0050 6C8B480A:6BB3 01 00000000:00000000 00:00000000 00000000 0 0 84339265 1 0000000000000000 20 0 0 10 97 52 | 50: 8EA6EE0A:0050 6C8B480A:6BA3 01 00000000:00000000 00:00000000 00000000 0 0 84340293 1 0000000000000000 20 0 0 10 97 53 | 51: 8EA6EE0A:0050 D428D10A:583B 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 54 | 52: 8EA6EE0A:0050 6C8B480A:6B83 06 00000000:00000000 03:000003D4 00000000 0 0 0 3 0000000000000000 55 | 53: 8EA6EE0A:0050 8CC9210A:78F6 01 00000000:00000000 00:00000000 00000000 0 0 84340351 1 0000000000000000 21 0 0 10 72 56 | 54: 8EA6EE0A:C46D 94A41F32:01BB 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 57 | 55: 8EA6EE0A:0050 FBA3210A:1652 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 58 | 56: 0103000A:B610 0B03000A:14B4 06 00000000:00000000 03:00000A72 00000000 0 0 0 3 0000000000000000 59 | 57: 0103000A:A934 2103000A:0C1D 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 60 | 58: 8EA6EE0A:0050 6C8B480A:6B82 06 00000000:00000000 03:00000394 00000000 0 0 0 3 0000000000000000 61 | 59: 8EA6EE0A:0050 28C64A0A:83C9 06 00000000:00000000 03:00001036 00000000 0 0 0 3 0000000000000000 62 | 60: 0103000A:E35F 1203000A:0ED8 06 00000000:00000000 03:00000B11 00000000 0 0 0 3 0000000000000000 63 | 61: 8EA6EE0A:0050 FBA3210A:1643 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 64 | 62: 8EA6EE0A:0050 8CC9210A:78E5 01 00000000:00000000 00:00000000 00000000 0 0 84336104 1 0000000000000000 20 0 0 10 72 65 | 63: 0103000A:B8AC 1A03000A:0C1C 06 00000000:00000000 03:000016CE 00000000 0 0 0 3 0000000000000000 66 | 64: 8EA6EE0A:0050 AA90680A:A265 01 00000000:00000000 00:00000000 00000000 0 0 84339100 1 0000000000000000 21 0 0 10 18 67 | 65: 8EA6EE0A:0050 FBA3210A:1674 01 00000000:00000000 00:00000000 00000000 0 0 84339197 1 0000000000000000 20 4 10 10 55 68 | 66: 8EA6EE0A:0050 EE13230A:2AC3 01 00000000:00000000 00:00000000 00000000 0 0 84339248 1 0000000000000000 20 4 21 10 19 69 | 67: 8EA6EE0A:0050 6C8B480A:6BA2 01 00000000:00000000 00:00000000 00000000 0 0 84340292 1 0000000000000000 20 0 0 10 97 70 | 68: 8EA6EE0A:0050 28C64A0A:83CC 01 00000000:00000000 00:00000000 00000000 0 0 84338192 1 0000000000000000 20 0 0 10 68 71 | 69: 8EA6EE0A:0050 8CC9210A:78EE 01 00000000:00000000 00:00000000 00000000 0 0 84340311 1 0000000000000000 20 0 0 10 72 72 | 70: 8EA6EE0A:0050 AA90680A:A26C 01 00000000:00000000 00:00000000 00000000 0 0 84340135 1 0000000000000000 21 0 0 10 18 73 | 71: 8EA6EE0A:0050 8CC9210A:78D1 01 00000000:00000000 00:00000000 00000000 0 0 84339882 1 0000000000000000 20 0 0 10 72 74 | 72: 8EA6EE0A:0050 6C8B480A:6B99 06 00000000:00000000 03:00001251 00000000 0 0 0 3 0000000000000000 75 | 73: 8EA6EE0A:0050 8CC9210A:78B7 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 76 | 74: 8EA6EE0A:0050 AA90680A:A25D 06 00000000:00000000 03:0000029E 00000000 0 0 0 3 0000000000000000 77 | 75: 8EA6EE0A:0050 6C8B480A:6B86 01 00000000:00000000 00:00000000 00000000 0 0 84339844 1 0000000000000000 20 0 0 10 97 78 | 76: 8EA6EE0A:0050 FBA3210A:165F 01 00000000:00000000 00:00000000 00000000 0 0 84338955 1 0000000000000000 20 0 0 10 55 79 | 77: 8EA6EE0A:0050 EE13230A:2AAA 01 00000000:00000000 00:00000000 00000000 0 0 84339087 1 0000000000000000 20 0 0 10 19 80 | 78: 8EA6EE0A:0050 FBA3210A:1642 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 81 | 79: 8EA6EE0A:0050 8CC9210A:78E6 01 00000000:00000000 00:00000000 00000000 0 0 84336116 1 0000000000000000 20 0 0 10 72 82 | 80: 8EA6EE0A:0050 AA90680A:A260 06 00000000:00000000 03:00000CAF 00000000 0 0 0 3 0000000000000000 83 | 81: 8EA6EE0A:0050 FBA3210A:163E 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 84 | 82: 8EA6EE0A:0050 8CC9210A:78C0 06 00000000:00000000 03:000006A3 00000000 0 0 0 3 0000000000000000 85 | 83: 8EA6EE0A:0050 6C8B480A:6B5C 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 86 | 84: 8EA6EE0A:0050 FBA3210A:167C 01 00000000:00000000 00:00000000 00000000 0 0 84338265 1 0000000000000000 20 0 0 10 55 87 | 85: 8EA6EE0A:0050 AA90680A:A268 01 00000000:00000000 00:00000000 00000000 0 0 84340133 1 0000000000000000 21 0 0 10 18 88 | 86: 8EA6EE0A:0050 EE13230A:2AB7 01 00000000:00000000 00:00000000 00000000 0 0 84340175 1 0000000000000000 20 0 0 10 19 89 | 87: 8EA6EE0A:0050 6C8B480A:6B5D 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 90 | 88: 8EA6EE0A:0050 6C8B480A:6B97 01 00000000:00000000 00:00000000 00000000 0 0 84340176 1 0000000000000000 20 0 0 10 97 91 | 89: 8EA6EE0A:0050 D428D10A:584B 06 00000000:00000000 03:0000037D 00000000 0 0 0 3 0000000000000000 92 | 90: 0103000A:B75D 1A03000A:0C1C 06 00000000:00000000 03:0000081B 00000000 0 0 0 3 0000000000000000 93 | 91: 8EA6EE0A:0050 AA90680A:A274 06 00000000:00000000 03:00000E56 00000000 0 0 0 3 0000000000000000 94 | 92: 8EA6EE0A:0050 FBA3210A:166C 01 00000000:00000000 00:00000000 00000000 0 0 84339111 1 0000000000000000 20 0 0 10 55 95 | 93: 8EA6EE0A:0050 6C8B480A:6B6C 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 96 | 94: 8EA6EE0A:0050 28C64A0A:83C7 06 00000000:00000000 03:0000047E 00000000 0 0 0 3 0000000000000000 97 | 95: 0103000A:E084 1203000A:0ED8 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 98 | 96: 8EA6EE0A:0050 D428D10A:5851 06 00000000:00000000 03:00000F35 00000000 0 0 0 3 0000000000000000 99 | 97: 8EA6EE0A:0050 AA90680A:A269 01 00000000:00000000 00:00000000 00000000 0 0 84340134 1 0000000000000000 21 0 0 10 18 100 | 98: 8EA6EE0A:0050 6C8B480A:6BB2 01 00000000:00000000 00:00000000 00000000 0 0 84336177 1 0000000000000000 20 4 23 10 97 101 | 99: 8EA6EE0A:0050 EE13230A:2AA6 06 00000000:00000000 03:00000C44 00000000 0 0 0 3 0000000000000000 102 | 100: 8EA6EE0A:0050 6C8B480A:6B90 06 00000000:00000000 03:00000C8A 00000000 0 0 0 3 0000000000000000 103 | 101: 8EA6EE0A:0050 FBA3210A:166B 01 00000000:00000000 00:00000000 00000000 0 0 84339103 1 0000000000000000 20 0 0 10 55 104 | 102: 8EA6EE0A:0050 6C8B480A:6B8D 06 00000000:00000000 03:00000BE5 00000000 0 0 0 3 0000000000000000 105 | 103: 8EA6EE0A:0050 6C8B480A:6B5E 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 106 | 104: 8EA6EE0A:0050 BD80480A:7C57 01 00000000:00000000 00:00000000 00000000 0 0 84340451 1 0000000000000000 21 4 21 10 -1 107 | 105: 8EA6EE0A:0050 8CC9210A:78ED 01 00000000:00000000 00:00000000 00000000 0 0 84340309 1 0000000000000000 20 0 0 10 72 108 | 106: 8EA6EE0A:0050 6C8B480A:6B74 06 00000000:00000000 03:000002CA 00000000 0 0 0 3 0000000000000000 109 | 107: 8EA6EE0A:0050 6C8B480A:6B88 01 00000000:00000000 00:00000000 00000000 0 0 84339857 1 0000000000000000 20 0 0 10 97 110 | 108: 8EA6EE0A:0050 EE13230A:2A9C 06 00000000:00000000 03:0000010F 00000000 0 0 0 3 0000000000000000 111 | 109: 8EA6EE0A:0050 8CC9210A:78DA 01 00000000:00000000 00:00000000 00000000 0 0 84339960 1 0000000000000000 20 0 0 10 72 112 | 110: 8EA6EE0A:0050 FBA3210A:1668 01 00000000:00000000 00:00000000 00000000 0 0 84340028 1 0000000000000000 20 0 0 10 55 113 | 111: 8EA6EE0A:0050 8CC9210A:78B0 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 114 | 112: 8EA6EE0A:0050 6C8B480A:6B8F 01 00000000:00000000 00:00000000 00000000 0 0 84339003 1 0000000000000000 20 0 0 10 97 115 | 113: 8EA6EE0A:0050 BD80480A:7C32 06 00000000:00000000 03:0000047E 00000000 0 0 0 3 0000000000000000 116 | 114: 8EA6EE0A:0050 28C64A0A:83CE 01 00000000:00000000 00:00000000 00000000 0 0 84336126 1 0000000000000000 20 0 0 10 68 117 | 115: 8EA6EE0A:0050 EE13230A:2A9E 06 00000000:00000000 03:00000C3D 00000000 0 0 0 3 0000000000000000 118 | 116: 8EA6EE0A:0050 FBA3210A:1665 06 00000000:00000000 03:00000CF6 00000000 0 0 0 3 0000000000000000 119 | 117: 8EA6EE0A:0050 EE13230A:2AA5 01 00000000:00000000 00:00000000 00000000 0 0 84339084 1 0000000000000000 20 0 0 10 19 120 | 118: 8EA6EE0A:0016 4A4FC3D4:EF74 01 00000000:00000000 02:000084E6 00000000 0 0 84054686 2 0000000000000000 28 4 25 10 126 121 | 119: 8EA6EE0A:0050 6C8B480A:6BB1 01 00000000:00000000 00:00000000 00000000 0 0 84336174 1 0000000000000000 20 4 29 10 97 122 | 120: 8EA6EE0A:0050 28C64A0A:83B9 06 00000000:00000000 03:00001118 00000000 0 0 0 3 0000000000000000 123 | 121: 8EA6EE0A:0050 6C8B480A:6BAA 01 00000000:00000000 00:00000000 00000000 0 0 84338262 1 0000000000000000 20 0 0 10 97 124 | 122: 8EA6EE0A:0050 EE13230A:2AA9 01 00000000:00000000 00:00000000 00000000 0 0 84339086 1 0000000000000000 20 0 0 10 19 125 | 123: 8EA6EE0A:0050 8CC9210A:78D9 01 00000000:00000000 00:00000000 00000000 0 0 84339959 1 0000000000000000 20 0 0 10 72 126 | 124: 8EA6EE0A:0050 6C8B480A:6B7F 06 00000000:00000000 03:00000334 00000000 0 0 0 3 0000000000000000 127 | 125: 8EA6EE0A:0050 EE13230A:2AA1 01 00000000:00000000 00:00000000 00000000 0 0 84336050 1 0000000000000000 20 0 0 10 19 128 | 126: 8EA6EE0A:0050 8CC9210A:78E2 06 00000000:00000000 03:0000128B 00000000 0 0 0 3 0000000000000000 129 | 127: 8EA6EE0A:0050 28C64A0A:83CA 01 00000000:00000000 00:00000000 00000000 0 0 84340217 1 0000000000000000 20 0 0 10 68 130 | 128: 8EA6EE0A:0050 8CC9210A:78E3 06 00000000:00000000 03:0000106A 00000000 0 0 0 3 0000000000000000 131 | 129: 8EA6EE0A:0050 8CC9210A:78EA 06 00000000:00000000 03:00001549 00000000 0 0 0 3 0000000000000000 132 | 130: 8EA6EE0A:0050 EE13230A:2A91 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 133 | 131: 8EA6EE0A:0050 AA90680A:A253 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 134 | 132: 8EA6EE0A:0050 8CC9210A:78DB 01 00000000:00000000 00:00000000 00000000 0 0 84336048 1 0000000000000000 20 0 0 10 72 135 | 133: 8EA6EE0A:0050 EE13230A:2AA2 01 00000000:00000000 00:00000000 00000000 0 0 84336051 1 0000000000000000 20 0 0 10 19 136 | 134: 8EA6EE0A:0050 6C8B480A:6B8C 06 00000000:00000000 03:00000A10 00000000 0 0 0 3 0000000000000000 137 | 135: 0103000A:B7F6 1A03000A:0C1C 06 00000000:00000000 03:00000DEC 00000000 0 0 0 3 0000000000000000 138 | 136: 8EA6EE0A:0050 28C64A0A:83CF 01 00000000:00000000 00:00000000 00000000 0 0 84336127 1 0000000000000000 20 0 0 10 68 139 | 137: 8EA6EE0A:0050 FBA3210A:1670 01 00000000:00000000 00:00000000 00000000 0 0 84336089 1 0000000000000000 20 0 0 10 55 140 | 138: 8EA6EE0A:0050 FBA3210A:165E 06 00000000:00000000 03:00000722 00000000 0 0 0 3 0000000000000000 141 | 139: 8EA6EE0A:0050 FBA3210A:1669 01 00000000:00000000 00:00000000 00000000 0 0 84340075 1 0000000000000000 20 0 0 10 55 142 | 140: 8EA6EE0A:0050 6C8B480A:6B96 06 00000000:00000000 03:00000E72 00000000 0 0 0 3 0000000000000000 143 | 141: 8EA6EE0A:0050 6C8B480A:6BAF 01 00000000:00000000 00:00000000 00000000 0 0 84336155 1 0000000000000000 20 0 0 10 97 144 | 142: 8EA6EE0A:0050 6C8B480A:6BA0 01 00000000:00000000 00:00000000 00000000 0 0 84338213 1 0000000000000000 20 0 0 10 97 145 | 143: 0103000A:8B82 0A03000A:125C 06 00000000:00000000 03:0000104B 00000000 0 0 0 3 0000000000000000 146 | 144: 8EA6EE0A:0050 FBA3210A:1666 01 00000000:00000000 00:00000000 00000000 0 0 84339934 1 0000000000000000 20 0 0 10 55 147 | 145: 8EA6EE0A:0050 8CC9210A:78E4 01 00000000:00000000 00:00000000 00000000 0 0 84336103 1 0000000000000000 20 0 0 10 72 148 | 146: 8EA6EE0A:0050 AA90680A:A277 01 00000000:00000000 00:00000000 00000000 0 0 84340181 1 0000000000000000 21 0 0 10 18 149 | 147: 8EA6EE0A:0050 6C8B480A:6B9F 06 00000000:00000000 03:000012FB 00000000 0 0 0 3 0000000000000000 150 | 148: 8EA6EE0A:0050 28C64A0A:83B8 06 00000000:00000000 03:00000000 00000000 0 0 0 3 0000000000000000 151 | 149: 8EA6EE0A:0016 4A4FC3D4:F10C 01 00002090:00000000 01:00000015 00000000 0 0 84227396 3 0000000000000000 26 4 11 10 -1 152 | 150: 8EA6EE0A:0050 8CC9210A:78EC 01 00000000:00000000 00:00000000 00000000 0 0 84340307 1 0000000000000000 20 0 0 10 72 153 | 151: 8EA6EE0A:0050 8CC9210A:78D2 01 00000000:00000000 00:00000000 00000000 0 0 84339915 1 0000000000000000 20 0 0 10 72 154 | 152: 8EA6EE0A:0050 28C64A0A:83CB 01 00000000:00000000 00:00000000 00000000 0 0 84340218 1 0000000000000000 20 0 0 10 68 155 | 153: 8EA6EE0A:0050 8CC9210A:78E0 01 00000000:00000000 00:00000000 00000000 0 0 84336055 1 0000000000000000 20 0 0 10 72 156 | -------------------------------------------------------------------------------- /procfs/assets/proc/net/tcp6: -------------------------------------------------------------------------------- 1 | sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 2 | 0: 000080FE00000000FF2C2940121285FE:0035 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 107 0 21593 1 0000000000000000 100 0 0 10 -1 3 | 1: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 8932 1 0000000000000000 100 0 0 10 -1 4 | -------------------------------------------------------------------------------- /procfs/assets/proc/net/udp: -------------------------------------------------------------------------------- 1 | sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops 2 | 3834: 0103000A:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 18046 2 0000000000000000 0 3 | 3848: 00000000:0043 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 18035 2 0000000000000000 0 4 | 3849: 00000000:0044 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 8536 2 0000000000000000 0 5 | 3904: 0103000A:007B 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 20888 2 0000000000000000 0 6 | 3904: 8EA6EE0A:007B 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 20887 2 0000000000000000 0 7 | 3904: 0100007F:007B 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 20886 2 0000000000000000 0 8 | 3904: 00000000:007B 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 20879 2 0000000000000000 0 9 | -------------------------------------------------------------------------------- /procfs/assets/proc/net/udp6: -------------------------------------------------------------------------------- 1 | sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops 2 | 3834: 000080FE00000000FF2C2940121285FE:0035 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 107 0 21592 2 0000000000000000 0 3 | 3904: 000080FE00000000FF821A00B7980DFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 83617731 2 0000000000000000 0 4 | 3904: 000080FE00000000FF363EE0B1A89DFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 83426619 2 0000000000000000 0 5 | 3904: 000080FE00000000FFF58AF4BB63CFFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 83422513 2 0000000000000000 0 6 | 3904: 000080FE00000000FF242A94153F24FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 83145337 2 0000000000000000 0 7 | 3904: 000080FE00000000FFA6CF5C00E0DAFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 83145336 2 0000000000000000 0 8 | 3904: 000080FE00000000FFFB8F7421F53BFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 83139783 2 0000000000000000 0 9 | 3904: 000080FE00000000FF2A88984D652DFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 83091222 2 0000000000000000 0 10 | 3904: 000080FE00000000FF9274703CD418FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 82953995 2 0000000000000000 0 11 | 3904: 000080FE00000000FF721164B608D8FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 82279800 2 0000000000000000 0 12 | 3904: 000080FE00000000FF73877CFEB35EFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 82279799 2 0000000000000000 0 13 | 3904: 000080FE00000000FFD9C050E066D1FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 82279798 2 0000000000000000 0 14 | 3904: 000080FE00000000FF15B1EC3595BEFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 82279797 2 0000000000000000 0 15 | 3904: 000080FE00000000FF60D7F81A7CBAFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 82279796 2 0000000000000000 0 16 | 3904: 000080FE00000000FF0FE450D62A81FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 82262073 2 0000000000000000 0 17 | 3904: 000080FE00000000FF963DF48AEC97FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 82262072 2 0000000000000000 0 18 | 3904: 000080FE00000000FFBEAA4010CB5FFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 78803083 2 0000000000000000 0 19 | 3904: 000080FE00000000FF54E4FCBBBD4FFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 78017185 2 0000000000000000 0 20 | 3904: 000080FE00000000FF3E09287CAD9DFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 77912357 2 0000000000000000 0 21 | 3904: 000080FE00000000FF89DF98046D1DFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 76069070 2 0000000000000000 0 22 | 3904: 000080FE00000000FFE1E1987F4909FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 75451544 2 0000000000000000 0 23 | 3904: 000080FE00000000FFBE54A4BED628FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 75402208 2 0000000000000000 0 24 | 3904: 000080FE00000000FF77B6DC4CCD34FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 75326810 2 0000000000000000 0 25 | 3904: 000080FE00000000FF8482946E899CFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 62989957 2 0000000000000000 0 26 | 3904: 000080FE00000000FFF66DD409749BFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 62989956 2 0000000000000000 0 27 | 3904: 000080FE00000000FF4699D89434E0FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 61720434 2 0000000000000000 0 28 | 3904: 000080FE00000000FFBE64B0246FF4FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 61604666 2 0000000000000000 0 29 | 3904: 000080FE00000000FF89B0ACBCBD8DFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 39284758 2 0000000000000000 0 30 | 3904: 000080FE00000000FF6BBBDC7B0657FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 32318090 2 0000000000000000 0 31 | 3904: 000080FE00000000FF76BD408B0CCAFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 32314672 2 0000000000000000 0 32 | 3904: 000080FE00000000FF8A46B426F308FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 30036847 2 0000000000000000 0 33 | 3904: 000080FE00000000FF04265CB467F4FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 29944616 2 0000000000000000 0 34 | 3904: 000080FE00000000FFD263A8514287FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 28988307 2 0000000000000000 0 35 | 3904: 000080FE00000000FF79AEFCB104D5FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 28716572 2 0000000000000000 0 36 | 3904: 000080FE00000000FF90AE7895AE58FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 28716571 2 0000000000000000 0 37 | 3904: 000080FE00000000FFDD5D8879C942FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 28716570 2 0000000000000000 0 38 | 3904: 000080FE00000000FF3A1EE0381154FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 28716569 2 0000000000000000 0 39 | 3904: 000080FE00000000FF37CAF84B814DFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 28716568 2 0000000000000000 0 40 | 3904: 000080FE00000000FF4085580B7A58FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 28716567 2 0000000000000000 0 41 | 3904: 000080FE00000000FFD43D6C995B06FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 27167802 2 0000000000000000 0 42 | 3904: 000080FE00000000FF4C6ABCBDBE20FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 27035458 2 0000000000000000 0 43 | 3904: 000080FE00000000FF5E7BC4321394FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 26990957 2 0000000000000000 0 44 | 3904: 000080FE00000000FFB9F4CC99867DFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25535887 2 0000000000000000 0 45 | 3904: 000080FE00000000FF39E84CA540AAFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25462321 2 0000000000000000 0 46 | 3904: 000080FE00000000FF5237C8FF9663FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25460507 2 0000000000000000 0 47 | 3904: 000080FE00000000FF3F30FC08418BFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25434211 2 0000000000000000 0 48 | 3904: 000080FE00000000FFF2347CB37C3EFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25432066 2 0000000000000000 0 49 | 3904: 000080FE00000000FF4F224423DCE7FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25361083 2 0000000000000000 0 50 | 3904: 000080FE00000000FF34CCAC712D0CFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25354324 2 0000000000000000 0 51 | 3904: 000080FE00000000FFA7B4B08FCE3CFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25346507 2 0000000000000000 0 52 | 3904: 000080FE00000000FFDE5F247482B4FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25346506 2 0000000000000000 0 53 | 3904: 000080FE00000000FF9D95944C86ACFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25340320 2 0000000000000000 0 54 | 3904: 000080FE00000000FF8663786FED94FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25326172 2 0000000000000000 0 55 | 3904: 000080FE00000000FF6E240CB97DAEFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25310765 2 0000000000000000 0 56 | 3904: 000080FE00000000FFA3515449CF3DFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25280479 2 0000000000000000 0 57 | 3904: 000080FE00000000FF655D347ABB42FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25248358 2 0000000000000000 0 58 | 3904: 000080FE00000000FF1461483B03EDFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25238905 2 0000000000000000 0 59 | 3904: 000080FE00000000FF1DE574F23E7FFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25238904 2 0000000000000000 0 60 | 3904: 000080FE00000000FF56A2789D2819FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 25228175 2 0000000000000000 0 61 | 3904: 000080FE00000000FF6AFA303C21A7FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 24661826 2 0000000000000000 0 62 | 3904: 000080FE00000000FF6C42E48E1020FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 24659839 2 0000000000000000 0 63 | 3904: 000080FE00000000FFD56B14D29CC7FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 24550038 2 0000000000000000 0 64 | 3904: 000080FE00000000FFAC6B706772A1FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 3309761 2 0000000000000000 0 65 | 3904: 000080FE00000000FF29D400D291C1FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 3303032 2 0000000000000000 0 66 | 3904: 000080FE00000000FF6373D4FC19D5FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 677632 2 0000000000000000 0 67 | 3904: 000080FE00000000FF72E50C6F52C3FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 58596 2 0000000000000000 0 68 | 3904: 000080FE00000000FF768B8065759FFE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 58593 2 0000000000000000 0 69 | 3904: 000080FE00000000FFE927485EC974FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 58586 2 0000000000000000 0 70 | 3904: 000080FE00000000FF7003E800E804FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 58584 2 0000000000000000 0 71 | 3904: 000080FE00000000FFAFC00CDE8587FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 109 0 58580 2 0000000000000000 0 72 | 3904: 000080FE00000000FF3B311080A509FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 20895 2 0000000000000000 0 73 | 3904: 000080FE00000000FF2C2940121285FE:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 20894 2 0000000000000000 0 74 | 3904: 00000000000000000000000001000000:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 20889 2 0000000000000000 0 75 | 3904: 00000000000000000000000000000000:007B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 20880 2 0000000000000000 0 76 | -------------------------------------------------------------------------------- /procfs/assets/proc/symlinktargets/socket:[84336181]: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinmonjo/dock/5901d367a6ae3d36b2272e81ab83d9227cf8f8de/procfs/assets/proc/symlinktargets/socket:[84336181] -------------------------------------------------------------------------------- /procfs/assets/proc/symlinktargets/targ0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinmonjo/dock/5901d367a6ae3d36b2272e81ab83d9227cf8f8de/procfs/assets/proc/symlinktargets/targ0 -------------------------------------------------------------------------------- /procfs/assets/proc/symlinktargets/targ1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinmonjo/dock/5901d367a6ae3d36b2272e81ab83d9227cf8f8de/procfs/assets/proc/symlinktargets/targ1 -------------------------------------------------------------------------------- /procfs/assets/proc/symlinktargets/targ2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinmonjo/dock/5901d367a6ae3d36b2272e81ab83d9227cf8f8de/procfs/assets/proc/symlinktargets/targ2 -------------------------------------------------------------------------------- /procfs/doc.go: -------------------------------------------------------------------------------- 1 | // Package procfs provides primitives for interacting with the linux proc 2 | // pseudo file system 3 | 4 | package procfs 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "sort" 10 | "strings" 11 | ) 12 | 13 | //sample ps like tool 14 | 15 | func main() { 16 | 17 | showSockets := true // display or not UDP / TCP sockets attached to a process 18 | allUsers := true // display process of all users 19 | 20 | uid := os.Getuid() 21 | 22 | var ( 23 | sockets []*Socket 24 | err error 25 | ) 26 | 27 | if showSockets { 28 | sockets, err = ReadNet() 29 | if err != nil { 30 | panic(err) 31 | } 32 | sort.Sort(Sockets(sockets)) //sort output by inode for faster search 33 | } 34 | 35 | WalkProcs(func(p *Proc) (bool, error) { 36 | 37 | st, err := p.Status() 38 | if err != nil { 39 | if os.IsNotExist(err) { 40 | return true, nil 41 | } 42 | return false, err 43 | } 44 | 45 | if !allUsers && st.Uid != uid { 46 | return true, nil 47 | } 48 | 49 | //get back user 50 | user, err := st.User() 51 | if err != nil { 52 | return false, err 53 | } 54 | 55 | //get back process name 56 | n := st.Name 57 | args, err := p.CmdLine() 58 | if err != nil { 59 | return false, err 60 | } 61 | if args != nil { 62 | n = strings.Join(args, " ") 63 | } 64 | 65 | //print basic infos 66 | fmt.Printf("%s %d %d %v", user.Username, p.Pid, st.PPid, n) 67 | 68 | if showSockets { 69 | //get back port bound by the process 70 | if err := printSockets(p, sockets); err != nil { 71 | return false, err 72 | } 73 | } 74 | 75 | fmt.Printf("\n") 76 | return true, nil 77 | }) 78 | } 79 | 80 | func printSockets(p *Proc, sockets []*Socket) error { 81 | fds, err := p.Fds() 82 | if err != nil { 83 | if !os.IsPermission(err) { 84 | return err 85 | } 86 | } 87 | 88 | inodes := []string{} 89 | 90 | for _, fd := range fds { 91 | inode := fd.SocketInode() 92 | if inode != "" { 93 | inodes = append(inodes, inode) 94 | } 95 | } 96 | 97 | str := []string{} 98 | for _, inode := range inodes { 99 | if s := Sockets(sockets).Find(inode); s != nil { 100 | str = append(str, fmt.Sprintf("%s %v %s", s.Protocol, s.LocalIP, s.LocalPort)) 101 | } 102 | } 103 | fmt.Printf(" %s", strings.Join(str, ", ")) 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /procfs/net.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | import ( 4 | "bufio" 5 | "encoding/hex" 6 | "net" 7 | "os" 8 | "path/filepath" 9 | "sort" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | const ( 16 | localAddrCol = 1 17 | remoteAddrCol = 2 18 | inodeCol = 9 19 | ) 20 | 21 | var protocols = []string{"tcp", "tcp6", "udp", "udp6"} 22 | 23 | type Socket struct { 24 | Protocol string 25 | LocalIP net.IP 26 | LocalPort string 27 | RemoteIP net.IP 28 | RemotePort string 29 | Inode string 30 | } 31 | 32 | func ReadNet() ([]*Socket, error) { 33 | var ( 34 | sockets = []*Socket{} 35 | err error 36 | mutex = &sync.Mutex{} 37 | wg sync.WaitGroup 38 | ) 39 | 40 | wg.Add(len(protocols)) 41 | for _, proto := range protocols { 42 | go func(p string) { 43 | s, e := parseNetFile(p) 44 | mutex.Lock() 45 | if e != nil { 46 | err = e 47 | } else { 48 | sockets = append(sockets, s...) 49 | } 50 | mutex.Unlock() 51 | wg.Done() 52 | }(proto) 53 | } 54 | 55 | wg.Wait() 56 | return sockets, err 57 | } 58 | 59 | func parseNetFile(protocol string) ([]*Socket, error) { 60 | f, err := os.Open(filepath.Join(Mountpoint, "net", protocol)) 61 | if err != nil { 62 | return nil, err 63 | } 64 | defer f.Close() 65 | 66 | sockets := []*Socket{} 67 | 68 | scanner := bufio.NewScanner(f) 69 | scanner.Scan() //flush file header 70 | 71 | for scanner.Scan() { 72 | sock := processLine(scanner.Text(), protocol) 73 | sockets = append(sockets, sock) 74 | } 75 | 76 | return sockets, scanner.Err() 77 | } 78 | 79 | func processLine(line, protocol string) *Socket { 80 | columns := strings.Fields(line) 81 | 82 | s := &Socket{ 83 | Protocol: protocol, 84 | } 85 | 86 | for i, c := range columns { 87 | switch i { 88 | case localAddrCol: 89 | addr := strings.Split(c, ":") 90 | s.LocalIP = hexStringToIP(addr[0]) 91 | s.LocalPort = hexStringToDecimalPort(addr[1]) 92 | case remoteAddrCol: 93 | addr := strings.Split(c, ":") 94 | s.RemoteIP = hexStringToIP(addr[0]) 95 | s.RemotePort = hexStringToDecimalPort(addr[1]) 96 | case inodeCol: 97 | s.Inode = c 98 | } 99 | } 100 | 101 | return s 102 | } 103 | 104 | //sort warppers 105 | type Sockets []*Socket 106 | 107 | func (sockets Sockets) Len() int { return len(sockets) } 108 | func (sockets Sockets) Swap(i, j int) { sockets[i], sockets[j] = sockets[j], sockets[i] } 109 | func (sockets Sockets) Less(i, j int) bool { return sockets[i].Inode < sockets[j].Inode } 110 | 111 | func (sockets Sockets) Find(inode string) *Socket { 112 | i := sort.Search(len(sockets), func(i int) bool { 113 | return sockets[i].Inode >= inode 114 | }) 115 | if i < len(sockets) && sockets[i].Inode == inode { 116 | return sockets[i] 117 | } 118 | return nil 119 | } 120 | 121 | //utilities 122 | func hexStringToIP(str string) net.IP { 123 | b, _ := hex.DecodeString(str) 124 | return net.IP(b) 125 | } 126 | 127 | func hexStringToDecimalPort(str string) string { 128 | p, _ := strconv.ParseInt(str, 16, 32) 129 | return strconv.Itoa(int(p)) 130 | } 131 | -------------------------------------------------------------------------------- /procfs/net_test.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestReadNet(t *testing.T) { 8 | Mountpoint = "./assets/proc" 9 | sockets, err := ReadNet() 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | 14 | if len(sockets) != 237 { 15 | t.Fatalf("expected 237 sockets, got %d", len(sockets)) 16 | } 17 | 18 | //first tcp is supposed to bin port 9999 and have inode 84336181 19 | //sockets are ordered by protocols 20 | for _, s := range sockets { 21 | if s.Protocol == "tcp" { 22 | if s.LocalPort != "9999" { 23 | t.Fatalf("expected first tcp sockets to bind port 9999, got %s", s.LocalPort) 24 | } 25 | if s.Inode != "84336181" { 26 | t.Fatalf("expected first tcp sockets to have inode 84336181, got %s", s.Inode) 27 | } 28 | break 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /procfs/proc.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | import ( 4 | "bufio" 5 | "encoding/hex" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "os/user" 10 | "path/filepath" 11 | "regexp" 12 | "strconv" 13 | "strings" 14 | "syscall" 15 | ) 16 | 17 | const ( 18 | statusName = "Name" 19 | statusPPid = "PPid" 20 | statusState = "State" 21 | statusUid = "Uid" 22 | statusSigBlk = "SigBlk" 23 | statusSigIgn = "SigIgn" 24 | statusSigCgt = "SigCgt" 25 | 26 | socketLinkRegex = `socket:\[(\d+)\]` 27 | ) 28 | 29 | // Proc provides information about a running process 30 | type Proc struct { 31 | // Process ID 32 | Pid int 33 | } 34 | 35 | // Self returns a Proc struct for the current process 36 | func Self() *Proc { 37 | return &Proc{ 38 | Pid: os.Getpid(), 39 | } 40 | } 41 | 42 | // ProcStatus store data about the process status, as found in /procfs/$PID/status 43 | type ProcStatus struct { 44 | Name string 45 | PPid int 46 | State string 47 | Uid int 48 | SigBlk []syscall.Signal 49 | SigIgn []syscall.Signal 50 | SigCgt []syscall.Signal 51 | } 52 | 53 | //file descriptors are symlinks 54 | type Fd struct { 55 | Source string 56 | Target string 57 | } 58 | 59 | func (p *Proc) dir() string { 60 | return fmt.Sprintf("%s/%d", Mountpoint, p.Pid) 61 | } 62 | 63 | //return ProcStatus of the process 64 | func (p *Proc) Status() (*ProcStatus, error) { 65 | f, err := os.Open(filepath.Join(p.dir(), "status")) 66 | if err != nil { 67 | return nil, err 68 | } 69 | defer f.Close() 70 | 71 | s := &ProcStatus{} 72 | 73 | scanner := bufio.NewScanner(f) 74 | 75 | for scanner.Scan() { 76 | records := strings.SplitN(scanner.Text(), ":", 2) 77 | key, value := records[0], strings.TrimSpace(records[1]) 78 | 79 | switch key { 80 | case statusName: 81 | s.Name = value 82 | case statusPPid: 83 | s.PPid, _ = strconv.Atoi(value) 84 | case statusState: 85 | s.State = value 86 | case statusUid: 87 | s.Uid, _ = strconv.Atoi(strings.Fields(value)[0]) 88 | case statusSigBlk: 89 | s.SigBlk = decodeSigMask(value) 90 | case statusSigIgn: 91 | s.SigIgn = decodeSigMask(value) 92 | case statusSigCgt: 93 | s.SigCgt = decodeSigMask(value) 94 | } 95 | } 96 | 97 | return s, scanner.Err() 98 | } 99 | 100 | //return all process's direct children 101 | func (p *Proc) Children() ([]*Proc, error) { 102 | children := []*Proc{} 103 | err := WalkProcs(func(process *Proc) (bool, error) { 104 | if process.Pid == p.Pid { //myself 105 | return true, nil 106 | } 107 | status, err := process.Status() 108 | if err != nil { 109 | return false, err 110 | } 111 | 112 | if status.PPid == p.Pid { 113 | children = append(children, process) 114 | } 115 | return true, nil 116 | }) 117 | if err != nil { 118 | return nil, err 119 | } 120 | return children, nil 121 | } 122 | 123 | // return process's descendants (children, grand children ...) 124 | func (p *Proc) Descendants() ([]*Proc, error) { 125 | descendants := []*Proc{p} 126 | cursor := 0 127 | 128 | for { 129 | if cursor >= len(descendants) { 130 | break 131 | } 132 | 133 | cp := descendants[cursor] 134 | children, err := cp.Children() 135 | if err != nil { 136 | return nil, err 137 | } 138 | descendants = append(descendants, children...) 139 | cursor++ 140 | } 141 | 142 | return descendants[1:], nil //remove self from the descendants 143 | } 144 | 145 | // returns a list of file descriptors as if /proc/$PID/fd 146 | func (p *Proc) Fds() ([]*Fd, error) { 147 | d, err := os.Open(filepath.Join(p.dir(), "fd")) 148 | if err != nil { 149 | return nil, err 150 | } 151 | defer d.Close() 152 | 153 | names, err := d.Readdirnames(-1) 154 | if err != nil { 155 | return nil, err 156 | } 157 | fds := []*Fd{} 158 | for _, name := range names { 159 | targ, err := os.Readlink(filepath.Join(d.Name(), name)) 160 | if err != nil { 161 | return nil, err 162 | } 163 | 164 | fds = append(fds, &Fd{ 165 | Source: name, 166 | Target: targ, 167 | }) 168 | } 169 | return fds, nil 170 | } 171 | 172 | // return process command line (i.e: ls -al) 173 | func (p *Proc) CmdLine() ([]string, error) { 174 | b, err := ioutil.ReadFile(filepath.Join(p.dir(), "cmdline")) 175 | if err != nil { 176 | return nil, err 177 | } 178 | 179 | if len(b) < 1 { 180 | return nil, nil 181 | } 182 | 183 | return strings.Split(string(b[:len(b)-1]), string(byte(0))), nil 184 | } 185 | 186 | // returns process owner 187 | func (status *ProcStatus) User() (*user.User, error) { 188 | return user.LookupId(strconv.Itoa(status.Uid)) 189 | } 190 | 191 | func (fd *Fd) SocketInode() string { 192 | matches := regexp.MustCompile(socketLinkRegex).FindStringSubmatch(filepath.Base(fd.Target)) 193 | if matches == nil { 194 | return "" 195 | } 196 | return matches[1] 197 | } 198 | 199 | //implementation of signal mask decoding 200 | //ref: http://jeff66ruan.github.io/blog/2014/03/31/sigpnd-sigblk-sigign-sigcgt-in-proc-status-file/ 201 | func decodeSigMask(maskStr string) []syscall.Signal { 202 | b, _ := hex.DecodeString(maskStr) 203 | //interested in the 32 right bits of the mask 204 | mask := int32(b[4])<<24 | int32(b[5])<<16 | int32(b[6])<<8 | int32(b[7]) 205 | 206 | var signals []syscall.Signal 207 | 208 | for i := 0; i < 32; i++ { 209 | submask := int32(1 << uint(i)) 210 | if mask&submask > 0 { 211 | signals = append(signals, syscall.Signal(i+1)) 212 | } 213 | } 214 | 215 | return signals 216 | } 217 | -------------------------------------------------------------------------------- /procfs/proc_test.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestDecodeSigMask(t *testing.T) { 10 | masks := []string{"fffffffe7ffbfeff", "00000000280b2603", "0000000000000000"} 11 | expected := [][]int{ 12 | []int{1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, 13 | []int{1, 2, 10, 11, 14, 17, 18, 20, 28, 30}, 14 | []int{}, 15 | } 16 | 17 | for i, mask := range masks { 18 | signals := decodeSigMask(mask) 19 | 20 | exp := expected[i] 21 | if len(exp) != len(signals) { 22 | t.Fatalf("expected %d signals got %d for mask %s", len(exp), len(signals), mask) 23 | } 24 | 25 | for j, sig := range signals { 26 | if int(sig) != exp[j] { 27 | t.Fatalf("expected sig %d, got sig number %d", sig, exp[j]) 28 | } 29 | } 30 | } 31 | } 32 | 33 | func TestParseStatusFile(t *testing.T) { 34 | Mountpoint = "./assets/proc" 35 | p := &Proc{ 36 | Pid: 1, 37 | } 38 | 39 | ps, err := p.Status() 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | if ps.Name != "bash" { 45 | t.Fatalf("expected Name bash, got %q", ps.Name) 46 | } 47 | if ps.PPid != 0 { 48 | t.Fatalf("expected PPid 0, got %d", ps.PPid) 49 | } 50 | if ps.Uid != 0 { 51 | t.Fatalf("expected Uid 0, got %d", ps.Uid) 52 | } 53 | if ps.State != "S (sleeping)" { 54 | t.Fatalf("expected State S (sleeping), got %q", ps.State) 55 | } 56 | if len(ps.SigBlk) != 1 { 57 | t.Fatalf("expected 1 signal blocked, got %d", len(ps.SigBlk)) 58 | } 59 | if len(ps.SigIgn) != 4 { 60 | t.Fatalf("expected 4 signals ignored, got %d", len(ps.SigIgn)) 61 | } 62 | if len(ps.SigCgt) != 19 { 63 | t.Fatalf("expected 20 signals blocked, got %d", len(ps.SigCgt)) 64 | } 65 | } 66 | 67 | func TestFds(t *testing.T) { 68 | Mountpoint = "./assets/proc" 69 | p := &Proc{ 70 | Pid: 9, 71 | } 72 | fds, err := p.Fds() 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | 77 | for i := 0; i < 3; i++ { 78 | fd := fds[i] 79 | //expected ../../symlinktargets/targ 80 | expectedSource := fmt.Sprintf("%d", i) 81 | if fd.Source != expectedSource { 82 | t.Fatalf("expected source to be %q, got %q", expectedSource, fd.Source) 83 | } 84 | 85 | expectedTarget := fmt.Sprintf("../../symlinktargets/targ%d", i) 86 | if fd.Target != expectedTarget { 87 | t.Fatalf("expected target to be %q, got %q", expectedTarget, fd.Target) 88 | } 89 | } 90 | 91 | inode := fds[0].SocketInode() 92 | if inode != "" { 93 | t.Fatal("first file descriptor shouldn't be a socket") 94 | } 95 | 96 | inode = fds[3].SocketInode() 97 | if inode != "84336181" { 98 | t.Fatalf("expected inode to be 84336181, got %q", inode) 99 | } 100 | } 101 | 102 | func TestChildren(t *testing.T) { 103 | Mountpoint = "./assets/proc" 104 | p := &Proc{ 105 | Pid: 9, 106 | } 107 | children, err := p.Children() 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | expected := []*Proc{ 112 | &Proc{Pid: 12}, 113 | &Proc{Pid: 14}, 114 | } 115 | if !reflect.DeepEqual(children, expected) { 116 | t.Fatalf("expected processes %#v, got %#v", expected, children) 117 | } 118 | } 119 | 120 | func TestNoChild(t *testing.T) { 121 | Mountpoint = "./assets/proc" 122 | p := &Proc{ 123 | Pid: 12, 124 | } 125 | children, err := p.Children() 126 | if err != nil { 127 | t.Fatal(err) 128 | } 129 | if len(children) != 0 { 130 | t.Fatal("pid 12 should have no children") 131 | } 132 | } 133 | 134 | func TestDescendants(t *testing.T) { 135 | Mountpoint = "./assets/proc" 136 | p := &Proc{ 137 | Pid: 1, 138 | } 139 | descendants, err := p.Descendants() 140 | if err != nil { 141 | t.Fatal(err) 142 | } 143 | 144 | expected := []*Proc{ 145 | &Proc{Pid: 9}, 146 | &Proc{Pid: 12}, 147 | &Proc{Pid: 14}, 148 | } 149 | if !reflect.DeepEqual(descendants, expected) { 150 | t.Fatalf("expected processes %#v, got %#v", expected, descendants) 151 | } 152 | } 153 | 154 | func TestNoDescendant(t *testing.T) { 155 | Mountpoint = "./assets/proc" 156 | p := &Proc{ 157 | Pid: 14, 158 | } 159 | descendants, err := p.Descendants() 160 | if err != nil { 161 | t.Fatal(err) 162 | } 163 | if len(descendants) != 0 { 164 | t.Fatal("pid 14 should have no descendants") 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /procfs/procfs.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | ) 7 | 8 | // DefaultMountpoint define the default mount point of the proc file system 9 | const DefaultMountpoint = "/proc" 10 | 11 | // MountPoint is the path of the proc file system mount point. 12 | // Default to DefaultMountPoint 13 | var Mountpoint = DefaultMountpoint 14 | 15 | // CountRunningProces return the number of running processes or an error if any 16 | func CountRunningProcs() (int, error) { 17 | cpt := 0 18 | err := WalkProcs(func(process *Proc) (bool, error) { 19 | cpt++ 20 | return true, nil 21 | }) 22 | return cpt, err 23 | } 24 | 25 | // WalkFunc WalkFunc is the type of the function called for each process visited by WalkProcs. 26 | // The process argument contains the current process. If the function return false or an error, 27 | // the WalkProcs func stop, and returns the eventual error 28 | type WalkFunc func(process *Proc) (bool, error) 29 | 30 | // WalkProcs walks all the processes and call walk on each process 31 | func WalkProcs(walk WalkFunc) error { 32 | d, err := os.Open(Mountpoint) 33 | if err != nil { 34 | return err 35 | } 36 | defer d.Close() 37 | names, err := d.Readdirnames(-1) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | for _, name := range names { 43 | if pid, err := strconv.Atoi(name); err == nil { 44 | loop, err := walk(&Proc{ 45 | Pid: pid, 46 | }) 47 | if err != nil { 48 | return err 49 | } 50 | if !loop { 51 | return nil 52 | } 53 | } 54 | } 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /procfs/procfs_test.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCountRunningProcs(t *testing.T) { 8 | Mountpoint = "./assets/proc" 9 | c, err := CountRunningProcs() 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | if c != 4 { 14 | t.Fatalf("expected 2 running processes, got %d", c) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /signals.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | "time" 8 | 9 | log "github.com/Sirupsen/logrus" 10 | "github.com/robinmonjo/dock/procfs" 11 | ) 12 | 13 | // resources: - https://github.com/opencontainers/runc/blob/master/signals.go 14 | // - https://github.com/Yelp/dumb-init/blob/master/dumb-init.c 15 | // - https://github.com/phusion/baseimage-docker/blob/master/image/bin/my_init 16 | 17 | const ( 18 | signalBufferSize = 2048 19 | killTimeout = 5 20 | ) 21 | 22 | type signalsHandler struct { 23 | signals chan os.Signal 24 | authority bool 25 | } 26 | 27 | func newSignalsHandler() *signalsHandler { 28 | s := make(chan os.Signal, signalBufferSize) 29 | signal.Notify(s) 30 | 31 | return &signalsHandler{ 32 | signals: s, 33 | } 34 | } 35 | 36 | func (h *signalsHandler) forward(p *process) int { 37 | 38 | pid1 := p.pid() 39 | 40 | for s := range h.signals { 41 | log.Debugf("signal: %q", s) 42 | 43 | switch s { 44 | case syscall.SIGWINCH: 45 | p.resizePty() 46 | 47 | case syscall.SIGCHLD: 48 | //child process died, dock will exit 49 | //sending sigterm to every remaining processes before calling wait4 50 | if err := signalAllDescendants(syscall.SIGTERM); err != nil { 51 | log.Debugf("failed to send sigterm signal: %v", err) 52 | } 53 | 54 | go func() { 55 | <-time.After(killTimeout * time.Second) 56 | log.Debugf("kill timed out") 57 | if err := signalAllDescendants(syscall.SIGKILL); err != nil { 58 | log.Debugf("failed to send sigkill signal: %v", err) 59 | } 60 | }() 61 | 62 | //waiting for all processes to die 63 | log.Debug("reaping all children") 64 | exits, err := reap() 65 | log.Debug("children reaped") 66 | if err != nil { 67 | log.Error(err) 68 | } 69 | 70 | for _, e := range exits { 71 | if e.pid == pid1 { 72 | p.wait() 73 | return e.status 74 | } 75 | } 76 | 77 | case syscall.SIGINT: 78 | fallthrough 79 | case syscall.SIGTERM: 80 | fallthrough 81 | case syscall.SIGQUIT: 82 | //stopping signals 83 | sigToForward := s 84 | 85 | if h.authority { 86 | blocked, err := isSignalBlocked(pid1, s) 87 | if err != nil { 88 | log.Error(err) 89 | goto forward 90 | } 91 | ignored, err := isSignalIgnored(pid1, s) 92 | if err != nil { 93 | log.Error(err) 94 | goto forward 95 | } 96 | if blocked || ignored { 97 | sigToForward = os.Signal(syscall.SIGKILL) 98 | } 99 | } 100 | 101 | forward: 102 | if err := p.signal(sigToForward); err != nil { 103 | log.Error(err) 104 | } 105 | 106 | default: 107 | 108 | //simply forward the signal to the process 109 | if err := p.signal(s); err != nil { 110 | log.Error(err) 111 | } 112 | } 113 | } 114 | 115 | panic("-- this line should never been executed --") 116 | } 117 | 118 | // exit models a process exit status with the pid and exit status. 119 | type exit struct { 120 | pid int 121 | status int 122 | } 123 | 124 | func reap() (exits []exit, err error) { 125 | var ( 126 | ws syscall.WaitStatus 127 | rus syscall.Rusage 128 | ) 129 | for { 130 | pid, err := syscall.Wait4(-1, &ws, 0, &rus) 131 | if err != nil { 132 | if err == syscall.ECHILD || err == syscall.ESRCH { 133 | return exits, nil 134 | } 135 | return nil, err 136 | } 137 | if pid <= 0 { 138 | return exits, nil 139 | } 140 | log.Debugf("process with PID %d died", pid) 141 | exits = append(exits, exit{ 142 | pid: pid, 143 | status: exitStatus(ws), 144 | }) 145 | } 146 | } 147 | 148 | // Send the given signal to every processes except for the PID 1 149 | func signalAllDescendants(sig syscall.Signal) error { 150 | self := procfs.Self() 151 | pses, err := self.Descendants() 152 | if err != nil { 153 | return err 154 | } 155 | for _, ps := range pses { 156 | err = syscall.Kill(ps.Pid, sig) 157 | } 158 | return err 159 | } 160 | 161 | // tell if given pid blocks the given signal 162 | func isSignalBlocked(pid int, s os.Signal) (bool, error) { 163 | status, err := procStatus(pid) 164 | if err != nil { 165 | return false, err 166 | } 167 | return include(status.SigBlk, s), nil 168 | } 169 | 170 | // tell if the given pid ignore the given signal 171 | func isSignalIgnored(pid int, s os.Signal) (bool, error) { 172 | status, err := procStatus(pid) 173 | if err != nil { 174 | return false, err 175 | } 176 | return include(status.SigIgn, s), nil 177 | } 178 | 179 | func procStatus(pid int) (*procfs.ProcStatus, error) { 180 | p := &procfs.Proc{ 181 | Pid: pid, 182 | } 183 | return p.Status() 184 | } 185 | 186 | func include(signals []syscall.Signal, s os.Signal) bool { 187 | if sig, ok := s.(syscall.Signal); ok { 188 | for _, s := range signals { 189 | if s == sig { 190 | return true 191 | } 192 | } 193 | return false 194 | } 195 | return false 196 | } 197 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os/exec" 5 | "strings" 6 | "syscall" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | "github.com/robinmonjo/dock/iowire" 10 | "github.com/robinmonjo/dock/procfs" 11 | ) 12 | 13 | const exitSignalOffset = 128 14 | 15 | // ExitStatus returns the correct exit status for a process based on if it 16 | // was signaled or existed cleanly. 17 | func exitStatus(status syscall.WaitStatus) int { 18 | if status.Signaled() { 19 | return exitSignalOffset + int(status.Signal()) 20 | } 21 | return status.ExitStatus() 22 | } 23 | 24 | func exitStatusFromError(err error) int { 25 | if msg, ok := err.(*exec.ExitError); ok { 26 | return msg.Sys().(syscall.WaitStatus).ExitStatus() 27 | } 28 | return -1 29 | } 30 | 31 | // Print the current process tree 32 | func printProcessTree() { 33 | procfs.WalkProcs(func(p *procfs.Proc) (bool, error) { 34 | status, err := p.Status() 35 | if err != nil { 36 | log.Printf("%d", p.Pid) 37 | } else { 38 | args, err := p.CmdLine() 39 | if err != nil { 40 | args = []string{status.Name} 41 | } 42 | log.Printf("%d\t%d\t%s\t%s", p.Pid, status.PPid, status.State, strings.Join(args, " ")) 43 | } 44 | return true, nil 45 | }) 46 | } 47 | 48 | // prefix args have the following format: --prefix some-prefix[:blue] 49 | func parsePrefixArg(prefix string) (string, iowire.Color) { 50 | comps := strings.Split(prefix, ":") 51 | if len(comps) == 1 { 52 | return comps[0], iowire.NoColor 53 | } 54 | return comps[0], iowire.MapColor(comps[len(comps)-1]) 55 | } 56 | -------------------------------------------------------------------------------- /vendor.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | git_clone() { 6 | pkg=$1 7 | rev=$2 8 | 9 | pkg_url=https://$pkg 10 | target_dir=vendor/$pkg 11 | 12 | echo -n "$pkg @ $rev: " 13 | 14 | if [ -d $target_dir ]; then 15 | echo -n 'rm old, ' 16 | rm -fr $target_dir 17 | fi 18 | 19 | echo -n 'clone, ' 20 | git clone --quiet --no-checkout $pkg_url $target_dir 21 | ( cd $target_dir && git reset --quiet --hard $rev ) 22 | 23 | echo -n 'rm VCS, ' 24 | ( cd $target_dir && rm -rf .{git,hg} ) 25 | 26 | echo done 27 | } 28 | 29 | git_clone github.com/codegangsta/cli v1.2.0 30 | git_clone github.com/Sirupsen/logrus v0.8.6 31 | git_clone github.com/kr/pty 32 | 33 | svn export https://github.com/docker/docker/trunk/pkg/term vendor/github.com/docker/docker/pkg/term --non-interactive --trust-server-cert --------------------------------------------------------------------------------