|
| 1 | +/* |
| 2 | + * Copyright 2016 SUSE LLC |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package main |
| 18 | + |
| 19 | +import ( |
| 20 | + "fmt" |
| 21 | + "io" |
| 22 | + "io/ioutil" |
| 23 | + "net" |
| 24 | + "os" |
| 25 | + "strings" |
| 26 | + |
| 27 | + "github.com/opencontainers/runc/libcontainer" |
| 28 | + "github.com/opencontainers/runc/libcontainer/utils" |
| 29 | + "github.com/urfave/cli" |
| 30 | +) |
| 31 | + |
| 32 | +// version will be populated by the Makefile, read from |
| 33 | +// VERSION file of the source code. |
| 34 | +var version = "" |
| 35 | + |
| 36 | +// gitCommit will be the hash that the binary was built from |
| 37 | +// and will be populated by the Makefile |
| 38 | +var gitCommit = "" |
| 39 | + |
| 40 | +const ( |
| 41 | + usage = `Open Container Initiative contrib/cmd/recvtty |
| 42 | +
|
| 43 | +recvtty is a reference implementation of a consumer of runC's --console-socket |
| 44 | +API. It has two main modes of operation: |
| 45 | +
|
| 46 | + * single: Only permit one terminal to be sent to the socket, which is |
| 47 | + then hooked up to the stdio of the recvtty process. This is useful |
| 48 | + for rudimentary shell management of a container. |
| 49 | +
|
| 50 | + * null: Permit as many terminals to be sent to the socket, but they |
| 51 | + are read to /dev/null. This is used for testing, and imitates the |
| 52 | + old runC API's --console=/dev/pts/ptmx hack which would allow for a |
| 53 | + similar trick. This is probably not what you want to use, unless |
| 54 | + you're doing something like our bats integration tests. |
| 55 | +
|
| 56 | +To use recvtty, just specify a socket path at which you want to receive |
| 57 | +terminals: |
| 58 | +
|
| 59 | + $ recvtty [--mode <single|null>] socket.sock |
| 60 | +` |
| 61 | +) |
| 62 | + |
| 63 | +func bail(err error) { |
| 64 | + fmt.Fprintf(os.Stderr, "[recvtty] fatal error: %v\n", err) |
| 65 | + os.Exit(1) |
| 66 | +} |
| 67 | + |
| 68 | +func handleSingle(path string) error { |
| 69 | + // Open a socket. |
| 70 | + ln, err := net.Listen("unix", path) |
| 71 | + if err != nil { |
| 72 | + return err |
| 73 | + } |
| 74 | + defer ln.Close() |
| 75 | + |
| 76 | + // We only accept a single connection, since we can only really have |
| 77 | + // one reader for os.Stdin. Plus this is all a PoC. |
| 78 | + conn, err := ln.Accept() |
| 79 | + if err != nil { |
| 80 | + return err |
| 81 | + } |
| 82 | + defer conn.Close() |
| 83 | + |
| 84 | + // Close ln, to allow for other instances to take over. |
| 85 | + ln.Close() |
| 86 | + |
| 87 | + // Get the fd of the connection. |
| 88 | + unixconn, ok := conn.(*net.UnixConn) |
| 89 | + if !ok { |
| 90 | + return fmt.Errorf("failed to cast to unixconn") |
| 91 | + } |
| 92 | + |
| 93 | + socket, err := unixconn.File() |
| 94 | + if err != nil { |
| 95 | + return err |
| 96 | + } |
| 97 | + defer socket.Close() |
| 98 | + |
| 99 | + // Get the master file descriptor from runC. |
| 100 | + master, err := utils.RecvFd(socket) |
| 101 | + if err != nil { |
| 102 | + return err |
| 103 | + } |
| 104 | + |
| 105 | + // Print the file descriptor tag. |
| 106 | + ti, err := libcontainer.GetTerminalInfo(master.Name()) |
| 107 | + if err != nil { |
| 108 | + return err |
| 109 | + } |
| 110 | + fmt.Printf("[recvtty] received masterfd: container '%s'\n", ti.ContainerID) |
| 111 | + |
| 112 | + // Copy from our stdio to the master fd. |
| 113 | + quitChan := make(chan struct{}) |
| 114 | + go func() { |
| 115 | + io.Copy(os.Stdout, master) |
| 116 | + quitChan <- struct{}{} |
| 117 | + }() |
| 118 | + go func() { |
| 119 | + io.Copy(master, os.Stdin) |
| 120 | + quitChan <- struct{}{} |
| 121 | + }() |
| 122 | + |
| 123 | + // Only close the master fd once we've stopped copying. |
| 124 | + <-quitChan |
| 125 | + master.Close() |
| 126 | + return nil |
| 127 | +} |
| 128 | + |
| 129 | +func handleNull(path string) error { |
| 130 | + // Open a socket. |
| 131 | + ln, err := net.Listen("unix", path) |
| 132 | + if err != nil { |
| 133 | + return err |
| 134 | + } |
| 135 | + defer ln.Close() |
| 136 | + |
| 137 | + // As opposed to handleSingle we accept as many connections as we get, but |
| 138 | + // we don't interact with Stdin at all (and we copy stdout to /dev/null). |
| 139 | + for { |
| 140 | + conn, err := ln.Accept() |
| 141 | + if err != nil { |
| 142 | + return err |
| 143 | + } |
| 144 | + go func(conn net.Conn) { |
| 145 | + // Don't leave references lying around. |
| 146 | + defer conn.Close() |
| 147 | + |
| 148 | + // Get the fd of the connection. |
| 149 | + unixconn, ok := conn.(*net.UnixConn) |
| 150 | + if !ok { |
| 151 | + return |
| 152 | + } |
| 153 | + |
| 154 | + socket, err := unixconn.File() |
| 155 | + if err != nil { |
| 156 | + return |
| 157 | + } |
| 158 | + defer socket.Close() |
| 159 | + |
| 160 | + // Get the master file descriptor from runC. |
| 161 | + master, err := utils.RecvFd(socket) |
| 162 | + if err != nil { |
| 163 | + return |
| 164 | + } |
| 165 | + |
| 166 | + // Print the file descriptor tag. |
| 167 | + ti, err := libcontainer.GetTerminalInfo(master.Name()) |
| 168 | + if err != nil { |
| 169 | + bail(err) |
| 170 | + } |
| 171 | + fmt.Printf("[recvtty] received masterfd: container '%s'\n", ti.ContainerID) |
| 172 | + |
| 173 | + // Just do a dumb copy to /dev/null. |
| 174 | + devnull, err := os.OpenFile("/dev/null", os.O_RDWR, 0) |
| 175 | + if err != nil { |
| 176 | + // TODO: Handle this nicely. |
| 177 | + return |
| 178 | + } |
| 179 | + |
| 180 | + io.Copy(devnull, master) |
| 181 | + devnull.Close() |
| 182 | + }(conn) |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +func main() { |
| 187 | + app := cli.NewApp() |
| 188 | + app.Name = "recvtty" |
| 189 | + app.Usage = usage |
| 190 | + |
| 191 | + // Set version to be the same as runC. |
| 192 | + var v []string |
| 193 | + if version != "" { |
| 194 | + v = append(v, version) |
| 195 | + } |
| 196 | + if gitCommit != "" { |
| 197 | + v = append(v, fmt.Sprintf("commit: %s", gitCommit)) |
| 198 | + } |
| 199 | + app.Version = strings.Join(v, "\n") |
| 200 | + |
| 201 | + // Set the flags. |
| 202 | + app.Flags = []cli.Flag{ |
| 203 | + cli.StringFlag{ |
| 204 | + Name: "mode, m", |
| 205 | + Value: "single", |
| 206 | + Usage: "Mode of operation (single or null)", |
| 207 | + }, |
| 208 | + cli.StringFlag{ |
| 209 | + Name: "pid-file", |
| 210 | + Value: "", |
| 211 | + Usage: "Path to write daemon process ID to", |
| 212 | + }, |
| 213 | + } |
| 214 | + |
| 215 | + app.Action = func(ctx *cli.Context) error { |
| 216 | + args := ctx.Args() |
| 217 | + if len(args) != 1 { |
| 218 | + return fmt.Errorf("need to specify a single socket path") |
| 219 | + } |
| 220 | + path := ctx.Args()[0] |
| 221 | + |
| 222 | + pidPath := ctx.String("pid-file") |
| 223 | + if pidPath != "" { |
| 224 | + pid := fmt.Sprintf("%d\n", os.Getpid()) |
| 225 | + if err := ioutil.WriteFile(pidPath, []byte(pid), 0644); err != nil { |
| 226 | + return err |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + switch ctx.String("mode") { |
| 231 | + case "single": |
| 232 | + if err := handleSingle(path); err != nil { |
| 233 | + return err |
| 234 | + } |
| 235 | + case "null": |
| 236 | + if err := handleNull(path); err != nil { |
| 237 | + return err |
| 238 | + } |
| 239 | + default: |
| 240 | + return fmt.Errorf("need to select a valid mode: %s", ctx.String("mode")) |
| 241 | + } |
| 242 | + return nil |
| 243 | + } |
| 244 | + if err := app.Run(os.Args); err != nil { |
| 245 | + bail(err) |
| 246 | + } |
| 247 | +} |
0 commit comments