func testCodepage(t *testing.T, name string, inReader, outReader func(io.Reader) io.Reader) { data := make([]byte, 256) for i := range data { data[i] = byte(i) } inr := inReader(bytes.NewBuffer(data)) r, err := charset.NewReader(name, inr) if err != nil { t.Fatalf("cannot make reader for charset %q: %v", name, err) } outr := outReader(r) r = outr var outbuf bytes.Buffer w, err := charset.NewWriter(name, &outbuf) if err != nil { t.Fatalf("cannot make writer for charset %q: %v", name, err) } _, err = io.Copy(w, r) if err != nil { t.Fatalf("copy failed: %v", err) } err = w.Close() if err != nil { t.Fatalf("close failed: %v", err) } if len(outbuf.Bytes()) != len(data) { t.Fatalf("short result of roundtrip, charset %q, readers %T, %T; expected 256, got %d", name, inr, outr, len(outbuf.Bytes())) } for i, x := range outbuf.Bytes() { if data[i] != x { t.Fatalf("charset %q, round trip expected %d, got %d", name, i, data[i]) } } }
func ExampleNewWriter() { buf := new(bytes.Buffer) w, err := charset.NewWriter("latin1", buf) if err != nil { log.Fatal(err) } fmt.Fprintf(w, "£5 for Peppé") w.Close() fmt.Printf("%q\n", buf.Bytes()) // Output: "\xa35 for Pepp\xe9" }
// NewPTY() is a newer version base on pty package, this opens from /dev/ptmx // and find the slave tty from the /dev/pts folder automatically func NewPTY() (*PTY, error) { pty, tty, err := pty.Open() if err != nil { return nil, err } masterEncoded, err := charset.NewWriter("ISO-8859-1", pty) if err != nil { return nil, err } return &PTY{ Master: pty, Slave: tty, No: 0, MasterEncoded: masterEncoded, }, nil }
func main() { flag.Usage = func() { fmt.Fprintf(os.Stderr, "usage: tcs [-l] [-v] [charset]\n") fmt.Fprintf(os.Stderr, "\ttcs [-f charset] [-t charset] [file]\n") } flag.Parse() if *listFlag { cs := "" switch flag.NArg() { case 1: cs = flag.Arg(0) case 0: default: flag.Usage() } listCharsets(*verboseFlag, cs) return } var f *os.File switch flag.NArg() { case 0: f = os.Stdin case 1: var err error f, err = os.Open(flag.Arg(0)) if err != nil { fatalf("cannot open %q: %v", err) } } r, err := charset.NewReader(*fromCharset, f) if err != nil { fatalf("cannot translate from %q: %v", *fromCharset, err) } w, err := charset.NewWriter(*toCharset, os.Stdout) if err != nil { fatalf("cannot translate to %q: ", err) } _, err = io.Copy(w, r) if err != nil { fatalf("%v", err) } }
func New(ptsPath string) (*PTY, error) { pty, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) if err != nil { return nil, fmt.Errorf("open pty %s", err) } sname, err := ptsname(pty) if err != nil { return nil, fmt.Errorf("ptsname %s", err) } err = grantpt(pty) if err != nil { return nil, fmt.Errorf("grantpt %s", err) } err = unlockpt(pty) if err != nil { return nil, fmt.Errorf("unlockpt %s", err) } tty, err := os.OpenFile(sname, os.O_RDWR, 0) if err != nil { return nil, fmt.Errorf("open tty %s", err) } masterEncoded, err := charset.NewWriter("ISO-8859-1", pty) if err != nil { return nil, fmt.Errorf("charset %s", err) } return &PTY{ Master: pty, Slave: tty, No: 0, MasterEncoded: masterEncoded, }, nil }
func New(ptsPath string) (*PTY, error) { // open master master, err := os.OpenFile(ptsPath+"/ptmx", os.O_RDWR, 0) if err != nil { return nil, fmt.Errorf("open pty %s", err) } pty := &PTY{Master: master} // unlock slave var unlock int32 if err := pty.Ioctl(syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&unlock))); err != nil { return nil, fmt.Errorf("failed to unlock pty %s", err) } // find out slave name var ptyno uint32 if err := pty.Ioctl(syscall.TIOCGPTN, uintptr(unsafe.Pointer(&ptyno))); err != nil { return nil, fmt.Errorf("failed to get ptyno %s", err) } pty.No = int(ptyno) // open slave slave, err := os.OpenFile(ptsPath+"/"+strconv.Itoa(pty.No), os.O_RDWR|syscall.O_NOCTTY, 0) if err != nil { return nil, fmt.Errorf("open tty %s", err) } pty.Slave = slave // apply proper encoding masterEncoded, err := charset.NewWriter("ISO-8859-1", master) if err != nil { return nil, fmt.Errorf("charset %s", err) } pty.MasterEncoded = masterEncoded return pty, nil }
// Connect connects to an existing Container by spawning a new process and // attaching to it. func (d *Docker) Connect(r *kite.Request) (interface{}, error) { var params struct { // The ID of the container. This needs to be created and started before // we can use Connect. ID string // Cmd contains the command which is executed and passed to the docker // exec api. If empty "bash" is used. Cmd string SizeX, SizeY int Remote Remote } if err := r.Args.One().Unmarshal(¶ms); err != nil { return nil, err } if params.ID == "" { return nil, errors.New("missing arg: container ID is empty") } cmd := []string{"bash"} if params.Cmd != "" { cmd = strings.Fields(params.Cmd) } createOpts := dockerclient.CreateExecOptions{ Container: params.ID, Tty: true, Cmd: cmd, // we attach to anything, it's used in the same was as with `docker // exec` AttachStdout: true, AttachStderr: true, AttachStdin: true, } // now we create a new Exec instance. It will return us an exec ID which // will be used to start the created exec instance d.log.Info("Creating exec instance") ex, err := d.client.CreateExec(createOpts) if err != nil { return nil, err } // these pipes are important as we are acting as a proxy between the // Browser (client side) and Docker Deamon. For example, every input coming // from the client side is being written into inWritePipe and this input is // then read by docker exec via the inReadPipe. inReadPipe, inWritePipe := io.Pipe() outReadPipe, outWritePipe := io.Pipe() opts := dockerclient.StartExecOptions{ Detach: false, Tty: true, OutputStream: outWritePipe, ErrorStream: outWritePipe, // this is ok, that's how tty works InputStream: inReadPipe, } // Control characters needs to be in ISO-8859 charset, so be sure that // UTF-8 writes are translated to this charset, for more info: // http://en.wikipedia.org/wiki/Control_character controlSequence, err := charset.NewWriter("ISO-8859-1", inWritePipe) if err != nil { return nil, err } errCh := make(chan error) closeCh := make(chan bool) server := &Server{ Session: ex.ID, remote: params.Remote, out: outReadPipe, in: inWritePipe, controlSequence: controlSequence, closeChan: closeCh, client: d.client, } go func() { d.log.Info("Starting exec instance '%s'", ex.ID) err := d.client.StartExec(ex.ID, opts) errCh <- err // call the remote function that we ended the session server.remote.SessionEnded.Call() }() go func() { select { case err := <-errCh: if err != nil { d.log.Error("startExec error: ", err) } case <-closeCh: // once we close them the underlying hijack process in docker // client package will end too, which will close the underlying // connection once it's finished/returned. inReadPipe.CloseWithError(errors.New("user closed the session")) inWritePipe.CloseWithError(errors.New("user closed the session")) outReadPipe.CloseWithError(errors.New("user closed the session")) outWritePipe.CloseWithError(errors.New("user closed the session")) } }() var once sync.Once // Read the STDOUT from shell process and send to the connected client. // https://github.com/koding/koding/commit/50cbd3609af93334150f7951dae49a23f71078f6 go func() { buf := make([]byte, (1<<12)-utf8.UTFMax, 1<<12) for { n, err := server.out.Read(buf) for n < cap(buf)-1 { r, _ := utf8.DecodeLastRune(buf[:n]) if r != utf8.RuneError { break } server.out.Read(buf[n : n+1]) n++ } // we need to set it for the first time. Because "StartExec" is // called in a goroutine and it's blocking there is now way we know // when it's ready. Therefore we set the size only once when get an // output. After that the client side is setting the TTY size with // the Server.SetSize method, that is called everytime the client // side sends a size command to us. once.Do(func() { // Y is height, X is width err = d.client.ResizeExecTTY(ex.ID, int(params.SizeY), int(params.SizeX)) if err != nil { fmt.Println("error resizing", err) } }) server.remote.Output.Call(string(filterInvalidUTF8(buf[:n]))) if err != nil { break } } d.log.Debug("Breaking out of for loop") }() d.log.Debug("Returning server") return server, nil }