mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Port devpts to VFS2.
PiperOrigin-RevId: 308164359
This commit is contained in:
committed by
gVisor bot
parent
eccae0f77d
commit
696feaf10c
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user