Port devpts to VFS2.

PiperOrigin-RevId: 308164359
This commit is contained in:
Nicolas Lacasse
2020-04-23 17:34:29 -07:00
committed by gVisor bot
parent eccae0f77d
commit 696feaf10c
16 changed files with 1570 additions and 5 deletions
+4
View File
@@ -27,6 +27,8 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
)
// LINT.IfChange
const (
// canonMaxBytes is the number of bytes that fit into a single line of
// terminal input in canonical mode. This corresponds to N_TTY_BUF_SIZE
@@ -443,3 +445,5 @@ func (l *lineDiscipline) peek(b []byte) int {
}
return size
}
// LINT.ThenChange(../../fsimpl/devpts/line_discipline.go)
+4
View File
@@ -26,6 +26,8 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
)
// LINT.IfChange
// masterInodeOperations are the fs.InodeOperations for the master end of the
// Terminal (ptmx file).
//
@@ -232,3 +234,5 @@ func maybeEmitUnimplementedEvent(ctx context.Context, cmd uint32) {
unimpl.EmitUnimplementedEvent(ctx)
}
}
// LINT.ThenChange(../../fsimpl/devpts/master.go)
+4
View File
@@ -25,6 +25,8 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
)
// LINT.IfChange
// waitBufMaxBytes is the maximum size of a wait buffer. It is based on
// TTYB_DEFAULT_MEM_LIMIT.
const waitBufMaxBytes = 131072
@@ -234,3 +236,5 @@ func (q *queue) waitBufAppend(b []byte) {
q.waitBuf = append(q.waitBuf, b)
q.waitBufLen += uint64(len(b))
}
// LINT.ThenChange(../../fsimpl/devpts/queue.go)
+4
View File
@@ -25,6 +25,8 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
)
// LINT.IfChange
// slaveInodeOperations are the fs.InodeOperations for the slave end of the
// Terminal (pts file).
//
@@ -172,3 +174,5 @@ func (sf *slaveFileOperations) Ioctl(ctx context.Context, _ *fs.File, io usermem
return 0, syserror.ENOTTY
}
}
// LINT.ThenChange(../../fsimpl/devpts/slave.go)
+4
View File
@@ -23,6 +23,8 @@ import (
"gvisor.dev/gvisor/pkg/usermem"
)
// LINT.IfChange
// Terminal is a pseudoterminal.
//
// +stateify savable
@@ -126,3 +128,5 @@ func (tm *Terminal) tty(isMaster bool) *kernel.TTY {
}
return tm.slaveKTTY
}
// LINT.ThenChange(../../fsimpl/devpts/terminal.go)
+43
View File
@@ -0,0 +1,43 @@
load("//tools:defs.bzl", "go_library", "go_test")
licenses(["notice"])
go_library(
name = "devpts",
srcs = [
"devpts.go",
"line_discipline.go",
"master.go",
"queue.go",
"slave.go",
"terminal.go",
],
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/safemem",
"//pkg/sentry/arch",
"//pkg/sentry/fsimpl/kernfs",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/unimpl",
"//pkg/sentry/vfs",
"//pkg/sync",
"//pkg/syserror",
"//pkg/usermem",
"//pkg/waiter",
],
)
go_test(
name = "devpts_test",
size = "small",
srcs = ["devpts_test.go"],
library = ":devpts",
deps = [
"//pkg/abi/linux",
"//pkg/sentry/contexttest",
"//pkg/usermem",
],
)
+207
View File
@@ -0,0 +1,207 @@
// Copyright 2020 The gVisor Authors.
//
// 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 devpts provides a filesystem implementation that behaves like
// devpts.
package devpts
import (
"fmt"
"math"
"sort"
"strconv"
"sync"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
// Name is the filesystem name.
const Name = "devpts"
// FilesystemType implements vfs.FilesystemType.
type FilesystemType struct{}
// Name implements vfs.FilesystemType.Name.
func (FilesystemType) Name() string {
return Name
}
var _ vfs.FilesystemType = (*FilesystemType)(nil)
// GetFilesystem implements vfs.FilesystemType.GetFilesystem.
func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
// No data allowed.
if opts.Data != "" {
return nil, nil, syserror.EINVAL
}
fs, root := fstype.newFilesystem(vfsObj, creds)
return fs.VFSFilesystem(), root.VFSDentry(), nil
}
// newFilesystem creates a new devpts filesystem with root directory and ptmx
// master inode. It returns the filesystem and root Dentry.
func (fstype FilesystemType) newFilesystem(vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials) (*kernfs.Filesystem, *kernfs.Dentry) {
fs := &kernfs.Filesystem{}
fs.Init(vfsObj, fstype)
// Construct the root directory. This is always inode id 1.
root := &rootInode{
slaves: make(map[uint32]*slaveInode),
}
root.InodeAttrs.Init(creds, 1, linux.ModeDirectory|0555)
root.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
root.dentry.Init(root)
// Construct the pts master inode and dentry. Linux always uses inode
// id 2 for ptmx. See fs/devpts/inode.c:mknod_ptmx.
master := &masterInode{
root: root,
}
master.InodeAttrs.Init(creds, 2, linux.ModeCharacterDevice|0666)
master.dentry.Init(master)
// Add the master as a child of the root.
links := root.OrderedChildren.Populate(&root.dentry, map[string]*kernfs.Dentry{
"ptmx": &master.dentry,
})
root.IncLinks(links)
return fs, &root.dentry
}
// rootInode is the root directory inode for the devpts mounts.
type rootInode struct {
kernfs.AlwaysValid
kernfs.InodeAttrs
kernfs.InodeDirectoryNoNewChildren
kernfs.InodeNotSymlink
kernfs.OrderedChildren
// Keep a reference to this inode's dentry.
dentry kernfs.Dentry
// master is the master pty inode. Immutable.
master *masterInode
// root is the root directory inode for this filesystem. Immutable.
root *rootInode
// mu protects the fields below.
mu sync.Mutex
// slaves maps pty ids to slave inodes.
slaves map[uint32]*slaveInode
// nextIdx is the next pty index to use. Must be accessed atomically.
//
// TODO(b/29356795): reuse indices when ptys are closed.
nextIdx uint32
}
var _ kernfs.Inode = (*rootInode)(nil)
// allocateTerminal creates a new Terminal and installs a pts node for it.
func (i *rootInode) allocateTerminal(creds *auth.Credentials) (*Terminal, error) {
i.mu.Lock()
defer i.mu.Unlock()
if i.nextIdx == math.MaxUint32 {
return nil, syserror.ENOMEM
}
idx := i.nextIdx
i.nextIdx++
// Sanity check that slave with idx does not exist.
if _, ok := i.slaves[idx]; ok {
panic(fmt.Sprintf("pty index collision; index %d already exists", idx))
}
// Create the new terminal and slave.
t := newTerminal(idx)
slave := &slaveInode{
root: i,
t: t,
}
// Linux always uses pty index + 3 as the inode id. See
// fs/devpts/inode.c:devpts_pty_new().
slave.InodeAttrs.Init(creds, uint64(idx+3), linux.ModeCharacterDevice|0600)
slave.dentry.Init(slave)
i.slaves[idx] = slave
return t, nil
}
// masterClose is called when the master end of t is closed.
func (i *rootInode) masterClose(t *Terminal) {
i.mu.Lock()
defer i.mu.Unlock()
// Sanity check that slave with idx exists.
if _, ok := i.slaves[t.n]; !ok {
panic(fmt.Sprintf("pty with index %d does not exist", t.n))
}
delete(i.slaves, t.n)
}
// Open implements kernfs.Inode.Open.
func (i *rootInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
fd := &kernfs.GenericDirectoryFD{}
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
return fd.VFSFileDescription(), nil
}
// Lookup implements kernfs.Inode.Lookup.
func (i *rootInode) Lookup(ctx context.Context, name string) (*vfs.Dentry, error) {
idx, err := strconv.ParseUint(name, 10, 32)
if err != nil {
return nil, syserror.ENOENT
}
i.mu.Lock()
defer i.mu.Unlock()
if si, ok := i.slaves[uint32(idx)]; ok {
si.dentry.IncRef()
return si.dentry.VFSDentry(), nil
}
return nil, syserror.ENOENT
}
// IterDirents implements kernfs.Inode.IterDirents.
func (i *rootInode) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback, offset, relOffset int64) (int64, error) {
i.mu.Lock()
defer i.mu.Unlock()
ids := make([]int, 0, len(i.slaves))
for id := range i.slaves {
ids = append(ids, int(id))
}
sort.Ints(ids)
for _, id := range ids[relOffset:] {
dirent := vfs.Dirent{
Name: strconv.FormatUint(uint64(id), 10),
Type: linux.DT_CHR,
Ino: i.slaves[uint32(id)].InodeAttrs.Ino(),
NextOff: offset + 1,
}
if err := cb.Handle(dirent); err != nil {
return offset, err
}
offset++
}
return offset, nil
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2018 The gVisor Authors.
//
// 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 devpts
import (
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/contexttest"
"gvisor.dev/gvisor/pkg/usermem"
)
func TestSimpleMasterToSlave(t *testing.T) {
ld := newLineDiscipline(linux.DefaultSlaveTermios)
ctx := contexttest.Context(t)
inBytes := []byte("hello, tty\n")
src := usermem.BytesIOSequence(inBytes)
outBytes := make([]byte, 32)
dst := usermem.BytesIOSequence(outBytes)
// Write to the input queue.
nw, err := ld.inputQueueWrite(ctx, src)
if err != nil {
t.Fatalf("error writing to input queue: %v", err)
}
if nw != int64(len(inBytes)) {
t.Fatalf("wrote wrong length: got %d, want %d", nw, len(inBytes))
}
// Read from the input queue.
nr, err := ld.inputQueueRead(ctx, dst)
if err != nil {
t.Fatalf("error reading from input queue: %v", err)
}
if nr != int64(len(inBytes)) {
t.Fatalf("read wrong length: got %d, want %d", nr, len(inBytes))
}
outStr := string(outBytes[:nr])
inStr := string(inBytes)
if outStr != inStr {
t.Fatalf("written and read strings do not match: got %q, want %q", outStr, inStr)
}
}
+449
View File
@@ -0,0 +1,449 @@
// Copyright 2018 The gVisor Authors.
//
// 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 devpts
import (
"bytes"
"unicode/utf8"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
// LINT.IfChange
const (
// canonMaxBytes is the number of bytes that fit into a single line of
// terminal input in canonical mode. This corresponds to N_TTY_BUF_SIZE
// in include/linux/tty.h.
canonMaxBytes = 4096
// nonCanonMaxBytes is the maximum number of bytes that can be read at
// a time in noncanonical mode.
nonCanonMaxBytes = canonMaxBytes - 1
spacesPerTab = 8
)
// lineDiscipline dictates how input and output are handled between the
// pseudoterminal (pty) master and slave. It can be configured to alter I/O,
// modify control characters (e.g. Ctrl-C for SIGINT), etc. The following man
// pages are good resources for how to affect the line discipline:
//
// * termios(3)
// * tty_ioctl(4)
//
// This file corresponds most closely to drivers/tty/n_tty.c.
//
// lineDiscipline has a simple structure but supports a multitude of options
// (see the above man pages). It consists of two queues of bytes: one from the
// terminal master to slave (the input queue) and one from slave to master (the
// output queue). When bytes are written to one end of the pty, the line
// discipline reads the bytes, modifies them or takes special action if
// required, and enqueues them to be read by the other end of the pty:
//
// input from terminal +-------------+ input to process (e.g. bash)
// +------------------------>| input queue |---------------------------+
// | (inputQueueWrite) +-------------+ (inputQueueRead) |
// | |
// | v
// masterFD slaveFD
// ^ |
// | |
// | output to terminal +--------------+ output from process |
// +------------------------| output queue |<--------------------------+
// (outputQueueRead) +--------------+ (outputQueueWrite)
//
// Lock order:
// termiosMu
// inQueue.mu
// outQueue.mu
//
// +stateify savable
type lineDiscipline struct {
// sizeMu protects size.
sizeMu sync.Mutex `state:"nosave"`
// size is the terminal size (width and height).
size linux.WindowSize
// inQueue is the input queue of the terminal.
inQueue queue
// outQueue is the output queue of the terminal.
outQueue queue
// termiosMu protects termios.
termiosMu sync.RWMutex `state:"nosave"`
// termios is the terminal configuration used by the lineDiscipline.
termios linux.KernelTermios
// column is the location in a row of the cursor. This is important for
// handling certain special characters like backspace.
column int
// masterWaiter is used to wait on the master end of the TTY.
masterWaiter waiter.Queue `state:"zerovalue"`
// slaveWaiter is used to wait on the slave end of the TTY.
slaveWaiter waiter.Queue `state:"zerovalue"`
}
func newLineDiscipline(termios linux.KernelTermios) *lineDiscipline {
ld := lineDiscipline{termios: termios}
ld.inQueue.transformer = &inputQueueTransformer{}
ld.outQueue.transformer = &outputQueueTransformer{}
return &ld
}
// getTermios gets the linux.Termios for the tty.
func (l *lineDiscipline) getTermios(ctx context.Context, io usermem.IO, args arch.SyscallArguments) (uintptr, error) {
l.termiosMu.RLock()
defer l.termiosMu.RUnlock()
// We must copy a Termios struct, not KernelTermios.
t := l.termios.ToTermios()
_, err := usermem.CopyObjectOut(ctx, io, args[2].Pointer(), t, usermem.IOOpts{
AddressSpaceActive: true,
})
return 0, err
}
// setTermios sets a linux.Termios for the tty.
func (l *lineDiscipline) setTermios(ctx context.Context, io usermem.IO, args arch.SyscallArguments) (uintptr, error) {
l.termiosMu.Lock()
defer l.termiosMu.Unlock()
oldCanonEnabled := l.termios.LEnabled(linux.ICANON)
// We must copy a Termios struct, not KernelTermios.
var t linux.Termios
_, err := usermem.CopyObjectIn(ctx, io, args[2].Pointer(), &t, usermem.IOOpts{
AddressSpaceActive: true,
})
l.termios.FromTermios(t)
// If canonical mode is turned off, move bytes from inQueue's wait
// buffer to its read buffer. Anything already in the read buffer is
// now readable.
if oldCanonEnabled && !l.termios.LEnabled(linux.ICANON) {
l.inQueue.mu.Lock()
l.inQueue.pushWaitBufLocked(l)
l.inQueue.readable = true
l.inQueue.mu.Unlock()
l.slaveWaiter.Notify(waiter.EventIn)
}
return 0, err
}
func (l *lineDiscipline) windowSize(ctx context.Context, io usermem.IO, args arch.SyscallArguments) error {
l.sizeMu.Lock()
defer l.sizeMu.Unlock()
_, err := usermem.CopyObjectOut(ctx, io, args[2].Pointer(), l.size, usermem.IOOpts{
AddressSpaceActive: true,
})
return err
}
func (l *lineDiscipline) setWindowSize(ctx context.Context, io usermem.IO, args arch.SyscallArguments) error {
l.sizeMu.Lock()
defer l.sizeMu.Unlock()
_, err := usermem.CopyObjectIn(ctx, io, args[2].Pointer(), &l.size, usermem.IOOpts{
AddressSpaceActive: true,
})
return err
}
func (l *lineDiscipline) masterReadiness() waiter.EventMask {
// We don't have to lock a termios because the default master termios
// is immutable.
return l.inQueue.writeReadiness(&linux.MasterTermios) | l.outQueue.readReadiness(&linux.MasterTermios)
}
func (l *lineDiscipline) slaveReadiness() waiter.EventMask {
l.termiosMu.RLock()
defer l.termiosMu.RUnlock()
return l.outQueue.writeReadiness(&l.termios) | l.inQueue.readReadiness(&l.termios)
}
func (l *lineDiscipline) inputQueueReadSize(ctx context.Context, io usermem.IO, args arch.SyscallArguments) error {
return l.inQueue.readableSize(ctx, io, args)
}
func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) {
l.termiosMu.RLock()
defer l.termiosMu.RUnlock()
n, pushed, err := l.inQueue.read(ctx, dst, l)
if err != nil {
return 0, err
}
if n > 0 {
l.masterWaiter.Notify(waiter.EventOut)
if pushed {
l.slaveWaiter.Notify(waiter.EventIn)
}
return n, nil
}
return 0, syserror.ErrWouldBlock
}
func (l *lineDiscipline) inputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) {
l.termiosMu.RLock()
defer l.termiosMu.RUnlock()
n, err := l.inQueue.write(ctx, src, l)
if err != nil {
return 0, err
}
if n > 0 {
l.slaveWaiter.Notify(waiter.EventIn)
return n, nil
}
return 0, syserror.ErrWouldBlock
}
func (l *lineDiscipline) outputQueueReadSize(ctx context.Context, io usermem.IO, args arch.SyscallArguments) error {
return l.outQueue.readableSize(ctx, io, args)
}
func (l *lineDiscipline) outputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) {
l.termiosMu.RLock()
defer l.termiosMu.RUnlock()
n, pushed, err := l.outQueue.read(ctx, dst, l)
if err != nil {
return 0, err
}
if n > 0 {
l.slaveWaiter.Notify(waiter.EventOut)
if pushed {
l.masterWaiter.Notify(waiter.EventIn)
}
return n, nil
}
return 0, syserror.ErrWouldBlock
}
func (l *lineDiscipline) outputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) {
l.termiosMu.RLock()
defer l.termiosMu.RUnlock()
n, err := l.outQueue.write(ctx, src, l)
if err != nil {
return 0, err
}
if n > 0 {
l.masterWaiter.Notify(waiter.EventIn)
return n, nil
}
return 0, syserror.ErrWouldBlock
}
// transformer is a helper interface to make it easier to stateify queue.
type transformer interface {
// transform functions require queue's mutex to be held.
transform(*lineDiscipline, *queue, []byte) int
}
// outputQueueTransformer implements transformer. It performs line discipline
// transformations on the output queue.
//
// +stateify savable
type outputQueueTransformer struct{}
// transform does output processing for one end of the pty. See
// drivers/tty/n_tty.c:do_output_char for an analogous kernel function.
//
// Preconditions:
// * l.termiosMu must be held for reading.
// * q.mu must be held.
func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) int {
// transformOutput is effectively always in noncanonical mode, as the
// master termios never has ICANON set.
if !l.termios.OEnabled(linux.OPOST) {
q.readBuf = append(q.readBuf, buf...)
if len(q.readBuf) > 0 {
q.readable = true
}
return len(buf)
}
var ret int
for len(buf) > 0 {
size := l.peek(buf)
cBytes := append([]byte{}, buf[:size]...)
ret += size
buf = buf[size:]
// We're guaranteed that cBytes has at least one element.
switch cBytes[0] {
case '\n':
if l.termios.OEnabled(linux.ONLRET) {
l.column = 0
}
if l.termios.OEnabled(linux.ONLCR) {
q.readBuf = append(q.readBuf, '\r', '\n')
continue
}
case '\r':
if l.termios.OEnabled(linux.ONOCR) && l.column == 0 {
continue
}
if l.termios.OEnabled(linux.OCRNL) {
cBytes[0] = '\n'
if l.termios.OEnabled(linux.ONLRET) {
l.column = 0
}
break
}
l.column = 0
case '\t':
spaces := spacesPerTab - l.column%spacesPerTab
if l.termios.OutputFlags&linux.TABDLY == linux.XTABS {
l.column += spaces
q.readBuf = append(q.readBuf, bytes.Repeat([]byte{' '}, spacesPerTab)...)
continue
}
l.column += spaces
case '\b':
if l.column > 0 {
l.column--
}
default:
l.column++
}
q.readBuf = append(q.readBuf, cBytes...)
}
if len(q.readBuf) > 0 {
q.readable = true
}
return ret
}
// inputQueueTransformer implements transformer. It performs line discipline
// transformations on the input queue.
//
// +stateify savable
type inputQueueTransformer struct{}
// transform does input processing for one end of the pty. Characters read are
// transformed according to flags set in the termios struct. See
// drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel
// function.
//
// Preconditions:
// * l.termiosMu must be held for reading.
// * q.mu must be held.
func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) int {
// If there's a line waiting to be read in canonical mode, don't write
// anything else to the read buffer.
if l.termios.LEnabled(linux.ICANON) && q.readable {
return 0
}
maxBytes := nonCanonMaxBytes
if l.termios.LEnabled(linux.ICANON) {
maxBytes = canonMaxBytes
}
var ret int
for len(buf) > 0 && len(q.readBuf) < canonMaxBytes {
size := l.peek(buf)
cBytes := append([]byte{}, buf[:size]...)
// We're guaranteed that cBytes has at least one element.
switch cBytes[0] {
case '\r':
if l.termios.IEnabled(linux.IGNCR) {
buf = buf[size:]
ret += size
continue
}
if l.termios.IEnabled(linux.ICRNL) {
cBytes[0] = '\n'
}
case '\n':
if l.termios.IEnabled(linux.INLCR) {
cBytes[0] = '\r'
}
}
// In canonical mode, we discard non-terminating characters
// after the first 4095.
if l.shouldDiscard(q, cBytes) {
buf = buf[size:]
ret += size
continue
}
// Stop if the buffer would be overfilled.
if len(q.readBuf)+size > maxBytes {
break
}
buf = buf[size:]
ret += size
// If we get EOF, make the buffer available for reading.
if l.termios.LEnabled(linux.ICANON) && l.termios.IsEOF(cBytes[0]) {
q.readable = true
break
}
q.readBuf = append(q.readBuf, cBytes...)
// Anything written to the readBuf will have to be echoed.
if l.termios.LEnabled(linux.ECHO) {
l.outQueue.writeBytes(cBytes, l)
l.masterWaiter.Notify(waiter.EventIn)
}
// If we finish a line, make it available for reading.
if l.termios.LEnabled(linux.ICANON) && l.termios.IsTerminating(cBytes) {
q.readable = true
break
}
}
// In noncanonical mode, everything is readable.
if !l.termios.LEnabled(linux.ICANON) && len(q.readBuf) > 0 {
q.readable = true
}
return ret
}
// shouldDiscard returns whether c should be discarded. In canonical mode, if
// too many bytes are enqueued, we keep reading input and discarding it until
// we find a terminating character. Signal/echo processing still occurs.
//
// Precondition:
// * l.termiosMu must be held for reading.
// * q.mu must be held.
func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool {
return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes)
}
// peek returns the size in bytes of the next character to process. As long as
// b isn't empty, peek returns a value of at least 1.
func (l *lineDiscipline) peek(b []byte) int {
size := 1
// If UTF-8 support is enabled, runes might be multiple bytes.
if l.termios.IEnabled(linux.IUTF8) {
_, size = utf8.DecodeRune(b)
}
return size
}
// LINT.ThenChange(../../fs/tty/line_discipline.go)
+226
View File
@@ -0,0 +1,226 @@
// Copyright 2020 The gVisor Authors.
//
// 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 devpts
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/unimpl"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
// LINT.IfChange
// masterInode is the inode for the master end of the Terminal.
type masterInode struct {
kernfs.InodeAttrs
kernfs.InodeNoopRefCount
kernfs.InodeNotDirectory
kernfs.InodeNotSymlink
// Keep a reference to this inode's dentry.
dentry kernfs.Dentry
// root is the devpts root inode.
root *rootInode
}
var _ kernfs.Inode = (*masterInode)(nil)
// Open implements kernfs.Inode.Open.
func (mi *masterInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
t, err := mi.root.allocateTerminal(rp.Credentials())
if err != nil {
return nil, err
}
mi.IncRef()
fd := &masterFileDescription{
inode: mi,
t: t,
}
if err := fd.vfsfd.Init(fd, opts.Flags, rp.Mount(), vfsd, &vfs.FileDescriptionOptions{}); err != nil {
mi.DecRef()
return nil, err
}
return &fd.vfsfd, nil
}
// Stat implements kernfs.Inode.Stat.
func (mi *masterInode) Stat(vfsfs *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) {
statx, err := mi.InodeAttrs.Stat(vfsfs, opts)
if err != nil {
return linux.Statx{}, err
}
statx.Blksize = 1024
statx.RdevMajor = linux.TTYAUX_MAJOR
statx.RdevMinor = linux.PTMX_MINOR
return statx, nil
}
// SetStat implements kernfs.Inode.SetStat
func (mi *masterInode) SetStat(ctx context.Context, vfsfs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
if opts.Stat.Mask&linux.STATX_SIZE != 0 {
return syserror.EINVAL
}
return mi.InodeAttrs.SetStat(ctx, vfsfs, creds, opts)
}
type masterFileDescription struct {
vfsfd vfs.FileDescription
vfs.FileDescriptionDefaultImpl
inode *masterInode
t *Terminal
}
var _ vfs.FileDescriptionImpl = (*masterFileDescription)(nil)
// Release implements vfs.FileDescriptionImpl.Release.
func (mfd *masterFileDescription) Release() {
mfd.inode.root.masterClose(mfd.t)
mfd.inode.DecRef()
}
// EventRegister implements waiter.Waitable.EventRegister.
func (mfd *masterFileDescription) EventRegister(e *waiter.Entry, mask waiter.EventMask) {
mfd.t.ld.masterWaiter.EventRegister(e, mask)
}
// EventUnregister implements waiter.Waitable.EventUnregister.
func (mfd *masterFileDescription) EventUnregister(e *waiter.Entry) {
mfd.t.ld.masterWaiter.EventUnregister(e)
}
// Readiness implements waiter.Waitable.Readiness.
func (mfd *masterFileDescription) Readiness(mask waiter.EventMask) waiter.EventMask {
return mfd.t.ld.masterReadiness()
}
// Read implements vfs.FileDescriptionImpl.Read.
func (mfd *masterFileDescription) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) {
return mfd.t.ld.outputQueueRead(ctx, dst)
}
// Write implements vfs.FileDescriptionImpl.Write.
func (mfd *masterFileDescription) Write(ctx context.Context, src usermem.IOSequence, _ vfs.WriteOptions) (int64, error) {
return mfd.t.ld.inputQueueWrite(ctx, src)
}
// Ioctl implements vfs.FileDescriptionImpl.Ioctl.
func (mfd *masterFileDescription) Ioctl(ctx context.Context, io usermem.IO, args arch.SyscallArguments) (uintptr, error) {
switch cmd := args[1].Uint(); cmd {
case linux.FIONREAD: // linux.FIONREAD == linux.TIOCINQ
// Get the number of bytes in the output queue read buffer.
return 0, mfd.t.ld.outputQueueReadSize(ctx, io, args)
case linux.TCGETS:
// N.B. TCGETS on the master actually returns the configuration
// of the slave end.
return mfd.t.ld.getTermios(ctx, io, args)
case linux.TCSETS:
// N.B. TCSETS on the master actually affects the configuration
// of the slave end.
return mfd.t.ld.setTermios(ctx, io, args)
case linux.TCSETSW:
// TODO(b/29356795): This should drain the output queue first.
return mfd.t.ld.setTermios(ctx, io, args)
case linux.TIOCGPTN:
_, err := usermem.CopyObjectOut(ctx, io, args[2].Pointer(), uint32(mfd.t.n), usermem.IOOpts{
AddressSpaceActive: true,
})
return 0, err
case linux.TIOCSPTLCK:
// TODO(b/29356795): Implement pty locking. For now just pretend we do.
return 0, nil
case linux.TIOCGWINSZ:
return 0, mfd.t.ld.windowSize(ctx, io, args)
case linux.TIOCSWINSZ:
return 0, mfd.t.ld.setWindowSize(ctx, io, args)
case linux.TIOCSCTTY:
// Make the given terminal the controlling terminal of the
// calling process.
return 0, mfd.t.setControllingTTY(ctx, io, args, true /* isMaster */)
case linux.TIOCNOTTY:
// Release this process's controlling terminal.
return 0, mfd.t.releaseControllingTTY(ctx, io, args, true /* isMaster */)
case linux.TIOCGPGRP:
// Get the foreground process group.
return mfd.t.foregroundProcessGroup(ctx, io, args, true /* isMaster */)
case linux.TIOCSPGRP:
// Set the foreground process group.
return mfd.t.setForegroundProcessGroup(ctx, io, args, true /* isMaster */)
default:
maybeEmitUnimplementedEvent(ctx, cmd)
return 0, syserror.ENOTTY
}
}
// SetStat implements vfs.FileDescriptionImpl.SetStat.
func (mfd *masterFileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
creds := auth.CredentialsFromContext(ctx)
fs := mfd.vfsfd.VirtualDentry().Mount().Filesystem()
return mfd.inode.SetStat(ctx, fs, creds, opts)
}
// Stat implements vfs.FileDescriptionImpl.Stat.
func (mfd *masterFileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
fs := mfd.vfsfd.VirtualDentry().Mount().Filesystem()
return mfd.inode.Stat(fs, opts)
}
// maybeEmitUnimplementedEvent emits unimplemented event if cmd is valid.
func maybeEmitUnimplementedEvent(ctx context.Context, cmd uint32) {
switch cmd {
case linux.TCGETS,
linux.TCSETS,
linux.TCSETSW,
linux.TCSETSF,
linux.TIOCGWINSZ,
linux.TIOCSWINSZ,
linux.TIOCSETD,
linux.TIOCSBRK,
linux.TIOCCBRK,
linux.TCSBRK,
linux.TCSBRKP,
linux.TIOCSTI,
linux.TIOCCONS,
linux.FIONBIO,
linux.TIOCEXCL,
linux.TIOCNXCL,
linux.TIOCGEXCL,
linux.TIOCGSID,
linux.TIOCGETD,
linux.TIOCVHANGUP,
linux.TIOCGDEV,
linux.TIOCMGET,
linux.TIOCMSET,
linux.TIOCMBIC,
linux.TIOCMBIS,
linux.TIOCGICOUNT,
linux.TCFLSH,
linux.TIOCSSERIAL,
linux.TIOCGPTPEER:
unimpl.EmitUnimplementedEvent(ctx)
}
}
// LINT.ThenChange(../../fs/tty/master.go)
+240
View File
@@ -0,0 +1,240 @@
// Copyright 2018 The gVisor Authors.
//
// 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 devpts
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
// LINT.IfChange
// waitBufMaxBytes is the maximum size of a wait buffer. It is based on
// TTYB_DEFAULT_MEM_LIMIT.
const waitBufMaxBytes = 131072
// queue represents one of the input or output queues between a pty master and
// slave. Bytes written to a queue are added to the read buffer until it is
// full, at which point they are written to the wait buffer. Bytes are
// processed (i.e. undergo termios transformations) as they are added to the
// read buffer. The read buffer is readable when its length is nonzero and
// readable is true.
//
// +stateify savable
type queue struct {
// mu protects everything in queue.
mu sync.Mutex `state:"nosave"`
// readBuf is buffer of data ready to be read when readable is true.
// This data has been processed.
readBuf []byte
// waitBuf contains data that can't fit into readBuf. It is put here
// until it can be loaded into the read buffer. waitBuf contains data
// that hasn't been processed.
waitBuf [][]byte
waitBufLen uint64
// readable indicates whether the read buffer can be read from. In
// canonical mode, there can be an unterminated line in the read buffer,
// so readable must be checked.
readable bool
// transform is the the queue's function for transforming bytes
// entering the queue. For example, transform might convert all '\r's
// entering the queue to '\n's.
transformer
}
// readReadiness returns whether q is ready to be read from.
func (q *queue) readReadiness(t *linux.KernelTermios) waiter.EventMask {
q.mu.Lock()
defer q.mu.Unlock()
if len(q.readBuf) > 0 && q.readable {
return waiter.EventIn
}
return waiter.EventMask(0)
}
// writeReadiness returns whether q is ready to be written to.
func (q *queue) writeReadiness(t *linux.KernelTermios) waiter.EventMask {
q.mu.Lock()
defer q.mu.Unlock()
if q.waitBufLen < waitBufMaxBytes {
return waiter.EventOut
}
return waiter.EventMask(0)
}
// readableSize writes the number of readable bytes to userspace.
func (q *queue) readableSize(ctx context.Context, io usermem.IO, args arch.SyscallArguments) error {
q.mu.Lock()
defer q.mu.Unlock()
var size int32
if q.readable {
size = int32(len(q.readBuf))
}
_, err := usermem.CopyObjectOut(ctx, io, args[2].Pointer(), size, usermem.IOOpts{
AddressSpaceActive: true,
})
return err
}
// read reads from q to userspace. It returns the number of bytes read as well
// as whether the read caused more readable data to become available (whether
// data was pushed from the wait buffer to the read buffer).
//
// Preconditions:
// * l.termiosMu must be held for reading.
func (q *queue) read(ctx context.Context, dst usermem.IOSequence, l *lineDiscipline) (int64, bool, error) {
q.mu.Lock()
defer q.mu.Unlock()
if !q.readable {
return 0, false, syserror.ErrWouldBlock
}
if dst.NumBytes() > canonMaxBytes {
dst = dst.TakeFirst(canonMaxBytes)
}
n, err := dst.CopyOutFrom(ctx, safemem.ReaderFunc(func(dst safemem.BlockSeq) (uint64, error) {
src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(q.readBuf))
n, err := safemem.CopySeq(dst, src)
if err != nil {
return 0, err
}
q.readBuf = q.readBuf[n:]
// If we read everything, this queue is no longer readable.
if len(q.readBuf) == 0 {
q.readable = false
}
return n, nil
}))
if err != nil {
return 0, false, err
}
// Move data from the queue's wait buffer to its read buffer.
nPushed := q.pushWaitBufLocked(l)
return int64(n), nPushed > 0, nil
}
// write writes to q from userspace.
//
// Preconditions:
// * l.termiosMu must be held for reading.
func (q *queue) write(ctx context.Context, src usermem.IOSequence, l *lineDiscipline) (int64, error) {
q.mu.Lock()
defer q.mu.Unlock()
// Copy data into the wait buffer.
n, err := src.CopyInTo(ctx, safemem.WriterFunc(func(src safemem.BlockSeq) (uint64, error) {
copyLen := src.NumBytes()
room := waitBufMaxBytes - q.waitBufLen
// If out of room, return EAGAIN.
if room == 0 && copyLen > 0 {
return 0, syserror.ErrWouldBlock
}
// Cap the size of the wait buffer.
if copyLen > room {
copyLen = room
src = src.TakeFirst64(room)
}
buf := make([]byte, copyLen)
// Copy the data into the wait buffer.
dst := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf))
n, err := safemem.CopySeq(dst, src)
if err != nil {
return 0, err
}
q.waitBufAppend(buf)
return n, nil
}))
if err != nil {
return 0, err
}
// Push data from the wait to the read buffer.
q.pushWaitBufLocked(l)
return n, nil
}
// writeBytes writes to q from b.
//
// Preconditions:
// * l.termiosMu must be held for reading.
func (q *queue) writeBytes(b []byte, l *lineDiscipline) {
q.mu.Lock()
defer q.mu.Unlock()
// Write to the wait buffer.
q.waitBufAppend(b)
q.pushWaitBufLocked(l)
}
// pushWaitBufLocked fills the queue's read buffer with data from the wait
// buffer.
//
// Preconditions:
// * l.termiosMu must be held for reading.
// * q.mu must be locked.
func (q *queue) pushWaitBufLocked(l *lineDiscipline) int {
if q.waitBufLen == 0 {
return 0
}
// Move data from the wait to the read buffer.
var total int
var i int
for i = 0; i < len(q.waitBuf); i++ {
n := q.transform(l, q, q.waitBuf[i])
total += n
if n != len(q.waitBuf[i]) {
// The read buffer filled up without consuming the
// entire buffer.
q.waitBuf[i] = q.waitBuf[i][n:]
break
}
}
// Update wait buffer based on consumed data.
q.waitBuf = q.waitBuf[i:]
q.waitBufLen -= uint64(total)
return total
}
// Precondition: q.mu must be locked.
func (q *queue) waitBufAppend(b []byte) {
q.waitBuf = append(q.waitBuf, b)
q.waitBufLen += uint64(len(b))
}
// LINT.ThenChange(../../fs/tty/queue.go)
+186
View File
@@ -0,0 +1,186 @@
// Copyright 2020 The gVisor Authors.
//
// 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 devpts
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
// LINT.IfChange
// slaveInode is the inode for the slave end of the Terminal.
type slaveInode struct {
kernfs.InodeAttrs
kernfs.InodeNoopRefCount
kernfs.InodeNotDirectory
kernfs.InodeNotSymlink
// Keep a reference to this inode's dentry.
dentry kernfs.Dentry
// root is the devpts root inode.
root *rootInode
// t is the connected Terminal.
t *Terminal
}
var _ kernfs.Inode = (*slaveInode)(nil)
// Open implements kernfs.Inode.Open.
func (si *slaveInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
si.IncRef()
fd := &slaveFileDescription{
inode: si,
}
if err := fd.vfsfd.Init(fd, opts.Flags, rp.Mount(), vfsd, &vfs.FileDescriptionOptions{}); err != nil {
si.DecRef()
return nil, err
}
return &fd.vfsfd, nil
}
// Valid implements kernfs.Inode.Valid.
func (si *slaveInode) Valid(context.Context) bool {
// Return valid if the slave still exists.
si.root.mu.Lock()
defer si.root.mu.Unlock()
_, ok := si.root.slaves[si.t.n]
return ok
}
// Stat implements kernfs.Inode.Stat.
func (si *slaveInode) Stat(vfsfs *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) {
statx, err := si.InodeAttrs.Stat(vfsfs, opts)
if err != nil {
return linux.Statx{}, err
}
statx.Blksize = 1024
statx.RdevMajor = linux.UNIX98_PTY_SLAVE_MAJOR
statx.RdevMinor = si.t.n
return statx, nil
}
// SetStat implements kernfs.Inode.SetStat
func (si *slaveInode) SetStat(ctx context.Context, vfsfs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
if opts.Stat.Mask&linux.STATX_SIZE != 0 {
return syserror.EINVAL
}
return si.InodeAttrs.SetStat(ctx, vfsfs, creds, opts)
}
type slaveFileDescription struct {
vfsfd vfs.FileDescription
vfs.FileDescriptionDefaultImpl
inode *slaveInode
}
var _ vfs.FileDescriptionImpl = (*slaveFileDescription)(nil)
// Release implements fs.FileOperations.Release.
func (sfd *slaveFileDescription) Release() {
sfd.inode.DecRef()
}
// EventRegister implements waiter.Waitable.EventRegister.
func (sfd *slaveFileDescription) EventRegister(e *waiter.Entry, mask waiter.EventMask) {
sfd.inode.t.ld.slaveWaiter.EventRegister(e, mask)
}
// EventUnregister implements waiter.Waitable.EventUnregister.
func (sfd *slaveFileDescription) EventUnregister(e *waiter.Entry) {
sfd.inode.t.ld.slaveWaiter.EventUnregister(e)
}
// Readiness implements waiter.Waitable.Readiness.
func (sfd *slaveFileDescription) Readiness(mask waiter.EventMask) waiter.EventMask {
return sfd.inode.t.ld.slaveReadiness()
}
// Read implements vfs.FileDescriptionImpl.Read.
func (sfd *slaveFileDescription) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) {
return sfd.inode.t.ld.inputQueueRead(ctx, dst)
}
// Write implements vfs.FileDescriptionImpl.Write.
func (sfd *slaveFileDescription) Write(ctx context.Context, src usermem.IOSequence, _ vfs.WriteOptions) (int64, error) {
return sfd.inode.t.ld.outputQueueWrite(ctx, src)
}
// Ioctl implements vfs.FileDescripionImpl.Ioctl.
func (sfd *slaveFileDescription) Ioctl(ctx context.Context, io usermem.IO, args arch.SyscallArguments) (uintptr, error) {
switch cmd := args[1].Uint(); cmd {
case linux.FIONREAD: // linux.FIONREAD == linux.TIOCINQ
// Get the number of bytes in the input queue read buffer.
return 0, sfd.inode.t.ld.inputQueueReadSize(ctx, io, args)
case linux.TCGETS:
return sfd.inode.t.ld.getTermios(ctx, io, args)
case linux.TCSETS:
return sfd.inode.t.ld.setTermios(ctx, io, args)
case linux.TCSETSW:
// TODO(b/29356795): This should drain the output queue first.
return sfd.inode.t.ld.setTermios(ctx, io, args)
case linux.TIOCGPTN:
_, err := usermem.CopyObjectOut(ctx, io, args[2].Pointer(), uint32(sfd.inode.t.n), usermem.IOOpts{
AddressSpaceActive: true,
})
return 0, err
case linux.TIOCGWINSZ:
return 0, sfd.inode.t.ld.windowSize(ctx, io, args)
case linux.TIOCSWINSZ:
return 0, sfd.inode.t.ld.setWindowSize(ctx, io, args)
case linux.TIOCSCTTY:
// Make the given terminal the controlling terminal of the
// calling process.
return 0, sfd.inode.t.setControllingTTY(ctx, io, args, false /* isMaster */)
case linux.TIOCNOTTY:
// Release this process's controlling terminal.
return 0, sfd.inode.t.releaseControllingTTY(ctx, io, args, false /* isMaster */)
case linux.TIOCGPGRP:
// Get the foreground process group.
return sfd.inode.t.foregroundProcessGroup(ctx, io, args, false /* isMaster */)
case linux.TIOCSPGRP:
// Set the foreground process group.
return sfd.inode.t.setForegroundProcessGroup(ctx, io, args, false /* isMaster */)
default:
maybeEmitUnimplementedEvent(ctx, cmd)
return 0, syserror.ENOTTY
}
}
// SetStat implements vfs.FileDescriptionImpl.SetStat.
func (sfd *slaveFileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
creds := auth.CredentialsFromContext(ctx)
fs := sfd.vfsfd.VirtualDentry().Mount().Filesystem()
return sfd.inode.SetStat(ctx, fs, creds, opts)
}
// Stat implements vfs.FileDescriptionImpl.Stat.
func (sfd *slaveFileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
fs := sfd.vfsfd.VirtualDentry().Mount().Filesystem()
return sfd.inode.Stat(fs, opts)
}
// LINT.ThenChange(../../fs/tty/slave.go)
+124
View File
@@ -0,0 +1,124 @@
// Copyright 2018 The gVisor Authors.
//
// 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 devpts
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/usermem"
)
// LINT.IfChanges
// Terminal is a pseudoterminal.
//
// +stateify savable
type Terminal struct {
// n is the terminal index. It is immutable.
n uint32
// ld is the line discipline of the terminal. It is immutable.
ld *lineDiscipline
// masterKTTY contains the controlling process of the master end of
// this terminal. This field is immutable.
masterKTTY *kernel.TTY
// slaveKTTY contains the controlling process of the slave end of this
// terminal. This field is immutable.
slaveKTTY *kernel.TTY
}
func newTerminal(n uint32) *Terminal {
termios := linux.DefaultSlaveTermios
t := Terminal{
n: n,
ld: newLineDiscipline(termios),
masterKTTY: &kernel.TTY{Index: n},
slaveKTTY: &kernel.TTY{Index: n},
}
return &t
}
// setControllingTTY makes tm the controlling terminal of the calling thread
// group.
func (tm *Terminal) setControllingTTY(ctx context.Context, io usermem.IO, args arch.SyscallArguments, isMaster bool) error {
task := kernel.TaskFromContext(ctx)
if task == nil {
panic("setControllingTTY must be called from a task context")
}
return task.ThreadGroup().SetControllingTTY(tm.tty(isMaster), args[2].Int())
}
// releaseControllingTTY removes tm as the controlling terminal of the calling
// thread group.
func (tm *Terminal) releaseControllingTTY(ctx context.Context, io usermem.IO, args arch.SyscallArguments, isMaster bool) error {
task := kernel.TaskFromContext(ctx)
if task == nil {
panic("releaseControllingTTY must be called from a task context")
}
return task.ThreadGroup().ReleaseControllingTTY(tm.tty(isMaster))
}
// foregroundProcessGroup gets the process group ID of tm's foreground process.
func (tm *Terminal) foregroundProcessGroup(ctx context.Context, io usermem.IO, args arch.SyscallArguments, isMaster bool) (uintptr, error) {
task := kernel.TaskFromContext(ctx)
if task == nil {
panic("foregroundProcessGroup must be called from a task context")
}
ret, err := task.ThreadGroup().ForegroundProcessGroup(tm.tty(isMaster))
if err != nil {
return 0, err
}
// Write it out to *arg.
_, err = usermem.CopyObjectOut(ctx, io, args[2].Pointer(), int32(ret), usermem.IOOpts{
AddressSpaceActive: true,
})
return 0, err
}
// foregroundProcessGroup sets tm's foreground process.
func (tm *Terminal) setForegroundProcessGroup(ctx context.Context, io usermem.IO, args arch.SyscallArguments, isMaster bool) (uintptr, error) {
task := kernel.TaskFromContext(ctx)
if task == nil {
panic("setForegroundProcessGroup must be called from a task context")
}
// Read in the process group ID.
var pgid int32
if _, err := usermem.CopyObjectIn(ctx, io, args[2].Pointer(), &pgid, usermem.IOOpts{
AddressSpaceActive: true,
}); err != nil {
return 0, err
}
ret, err := task.ThreadGroup().SetForegroundProcessGroup(tm.tty(isMaster), kernel.ProcessGroupID(pgid))
return uintptr(ret), err
}
func (tm *Terminal) tty(isMaster bool) *kernel.TTY {
if isMaster {
return tm.masterKTTY
}
return tm.slaveKTTY
}
// LINT.ThenChange(../../fs/tty/terminal.go)
+11 -2
View File
@@ -163,16 +163,25 @@ func (a *Accessor) CreateDeviceFile(ctx context.Context, pathname string, kind v
func (a *Accessor) UserspaceInit(ctx context.Context) error {
actx := a.wrapContext(ctx)
// systemd: src/shared/dev-setup.c:dev_setup()
// Initialize symlinks.
for _, symlink := range []struct {
source string
target string
}{
// /proc/kcore is not implemented.
// systemd: src/shared/dev-setup.c:dev_setup()
{source: "fd", target: "/proc/self/fd"},
{source: "stdin", target: "/proc/self/fd/0"},
{source: "stdout", target: "/proc/self/fd/1"},
{source: "stderr", target: "/proc/self/fd/2"},
// /proc/kcore is not implemented.
// Linux implements /dev/ptmx as a device node, but advises
// container implementations to create /dev/ptmx as a symlink
// to pts/ptmx (Documentation/filesystems/devpts.txt). Systemd
// follows this advice (src/nspawn/nspawn.c:setup_pts()), while
// LXC tries to create a bind mount and falls back to a symlink
// (src/lxc/conf.c:lxc_setup_devpts()).
{source: "ptmx", target: "pts/ptmx"},
} {
if err := a.vfsObj.SymlinkAt(actx, a.creds, a.pathOperationAt(symlink.source), symlink.target); err != nil {
return fmt.Errorf("failed to create symlink %q => %q: %v", symlink.source, symlink.target, err)
+1 -1
View File
@@ -391,7 +391,7 @@ func (fs *Filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vf
// O_NOFOLLOW have no effect here (they're handled by VFS by setting
// appropriate bits in rp), but are returned by
// FileDescriptionImpl.StatusFlags().
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_TRUNC | linux.O_DIRECTORY | linux.O_NOFOLLOW
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_TRUNC | linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NONBLOCK
ats := vfs.AccessTypesForOpenFlags(&opts)
// Do not create new file.
+7 -2
View File
@@ -216,6 +216,11 @@ func (a *InodeAttrs) Init(creds *auth.Credentials, ino uint64, mode linux.FileMo
atomic.StoreUint32(&a.nlink, nlink)
}
// Ino returns the inode id.
func (a *InodeAttrs) Ino() uint64 {
return atomic.LoadUint64(&a.ino)
}
// Mode implements Inode.Mode.
func (a *InodeAttrs) Mode() linux.FileMode {
return linux.FileMode(atomic.LoadUint32(&a.mode))
@@ -359,8 +364,8 @@ func (o *OrderedChildren) Destroy() {
// cache. Populate returns the number of directories inserted, which the caller
// may use to update the link count for the parent directory.
//
// Precondition: d.Impl() must be a kernfs Dentry. d must represent a directory
// inode. children must not contain any conflicting entries already in o.
// Precondition: d must represent a directory inode. children must not contain
// any conflicting entries already in o.
func (o *OrderedChildren) Populate(d *Dentry, children map[string]*Dentry) uint32 {
var links uint32
for name, child := range children {