packages/pty_linux.go (93 lines of code) (raw):
// Copyright 2019 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package packages
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"sync"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
func ioctl(fd, req, arg uintptr) (err error) {
_, _, e1 := unix.Syscall(unix.SYS_IOCTL, fd, req, arg)
if e1 != 0 {
err = syscall.Errno(e1)
}
return
}
// This is used for anytime we need to parse YUM output.
// See https://bugzilla.redhat.com/show_bug.cgi?id=584525#c21
// TODO: We should probably look into a thin python shim we can
// interact with that the utilizes the yum libraries.
func runWithPty(cmd *exec.Cmd) ([]byte, []byte, error) {
// Much of this logic was taken from, without the CGO stuff:
// https://golang.org/src/os/signal/signal_cgo_test.go
pty, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
defer pty.Close()
// grantpt doesn't appear to be required anymore.
// unlockpt
var i int
if err := ioctl(pty.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&i))); err != nil {
return nil, nil, fmt.Errorf("error from ioctl TIOCSPTLCK: %v", err)
}
// ptsname
var u uint32
if err := ioctl(pty.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&u))); err != nil {
return nil, nil, fmt.Errorf("error from ioctl TIOCGPTN: %v", err)
}
path := filepath.Join("/dev/pts", strconv.Itoa(int(u)))
tty, err := os.OpenFile(path, os.O_RDWR|syscall.O_NOCTTY, 0)
if err != nil {
return nil, nil, err
}
defer tty.Close()
if err := unix.IoctlSetWinsize(int(pty.Fd()), syscall.TIOCSWINSZ, &unix.Winsize{Row: 1, Col: 500}); err != nil {
return nil, nil, fmt.Errorf("error from IoctlSetWinsize: %v", err)
}
var stderr bytes.Buffer
cmd.Stdin = tty
cmd.Stdout = tty
cmd.Stderr = &stderr
cmd.SysProcAttr = &syscall.SysProcAttr{
Setctty: true,
Setsid: true,
Ctty: 0, // Stdin of the child process
}
var stdout bytes.Buffer
var retErr error
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
input := bufio.NewReader(pty)
for {
b, err := input.ReadBytes('\n')
if err != nil {
if perr, ok := err.(*os.PathError); ok {
err = perr.Err
}
if err != io.EOF && err != syscall.EIO {
retErr = err
}
return
}
if _, err := stdout.Write(b); err != nil {
retErr = err
return
}
}
}()
cmdErr := cmd.Run()
if err := tty.Close(); err != nil {
return stdout.Bytes(), stderr.Bytes(), err
}
wg.Wait()
if cmdErr != nil {
// Yum returns non-zero exit values on non-error conditions.
// Errors which are *not* in this category indicate failure.
if _, ok := cmdErr.(*exec.ExitError); !ok {
return stdout.Bytes(), stderr.Bytes(), cmdErr
}
}
// Exit code 0 means no updates, 1 probably means there are but we just didn't install them.
if cmdErr == nil {
return nil, nil, nil
}
return stdout.Bytes(), stderr.Bytes(), retErr
}