Files
Shailend ChandandgVisor bot b943164ed6 Make every user of a tty also refcount
A tty inode was kept alive by a single ownership chain: the stdio file
description's reference on the import-time dentry. Everything else that used
the inode referred to it uncounted: a file description opened via /dev/tty
pins only the devtmpfs dentry, and the controlling-terminal association
(ThreadGroup.tty) is a bare pointer.

PiperOrigin-RevId: 962479941
2026-08-10 18:19:56 -07:00

309 lines
8.5 KiB
Go

// 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 host
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/unimpl"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/usermem"
)
// TTYFileDescription implements vfs.FileDescriptionImpl for a host file
// descriptor that wraps a TTY FD.
//
// It implements kernel.TTYOperations.
//
// +stateify savable
type TTYFileDescription struct {
fileDescription
}
// Release implements vfs.FileDescriptionImpl.Release.
//
// It drops the inode reference taken in inode.OpenTTY.
func (t *TTYFileDescription) Release(ctx context.Context) {
t.inode.DecRef(ctx)
}
// TTY returns the kernel.TTY.
func (t *TTYFileDescription) TTY() *kernel.TTY {
return t.inode.tty
}
// ThreadGroup returns the kernel.ThreadGroup associated with this tty.
func (t *TTYFileDescription) ThreadGroup() *kernel.ThreadGroup {
return t.inode.tty.ThreadGroup()
}
// PRead implements vfs.FileDescriptionImpl.PRead.
//
// Reading from a TTY is only allowed for foreground process groups. Background
// process groups will either get EIO or a SIGTTIN.
func (t *TTYFileDescription) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
// Are we allowed to do the read?
// drivers/tty/n_tty.c:n_tty_read()=>job_control()=>tty_check_change().
if err := t.TTY().CheckChange(ctx, linux.SIGTTIN); err != nil {
return 0, err
}
// Do the read.
return t.fileDescription.PRead(ctx, dst, offset, opts)
}
// Read implements vfs.FileDescriptionImpl.Read.
//
// Reading from a TTY is only allowed for foreground process groups. Background
// process groups will either get EIO or a SIGTTIN.
func (t *TTYFileDescription) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
// Are we allowed to do the read?
// drivers/tty/n_tty.c:n_tty_read()=>job_control()=>tty_check_change().
if err := t.TTY().CheckChange(ctx, linux.SIGTTIN); err != nil {
return 0, err
}
// Do the read.
return t.fileDescription.Read(ctx, dst, opts)
}
// PWrite implements vfs.FileDescriptionImpl.PWrite.
func (t *TTYFileDescription) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
t.inode.termiosMu.Lock()
defer t.inode.termiosMu.Unlock()
// Check whether TOSTOP is enabled. This corresponds to the check in
// drivers/tty/n_tty.c:n_tty_write().
if t.inode.termios.LEnabled(linux.TOSTOP) {
if err := t.TTY().CheckChange(ctx, linux.SIGTTOU); err != nil {
return 0, err
}
}
return t.fileDescription.PWrite(ctx, src, offset, opts)
}
// Write implements vfs.FileDescriptionImpl.Write.
func (t *TTYFileDescription) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
t.inode.termiosMu.Lock()
defer t.inode.termiosMu.Unlock()
// Check whether TOSTOP is enabled. This corresponds to the check in
// drivers/tty/n_tty.c:n_tty_write().
if t.inode.termios.LEnabled(linux.TOSTOP) {
if err := t.TTY().CheckChange(ctx, linux.SIGTTOU); err != nil {
return 0, err
}
}
return t.fileDescription.Write(ctx, src, opts)
}
// Ioctl implements vfs.FileDescriptionImpl.Ioctl.
func (t *TTYFileDescription) Ioctl(ctx context.Context, io usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error) {
task := kernel.TaskFromContext(ctx)
if task == nil {
return 0, linuxerr.ENOTTY
}
// Ignore arg[0]. This is the real FD:
fd := t.inode.hostFD
ioctl := args[1].Uint64()
switch ioctl {
case linux.FIONREAD:
v, err := ioctlFionread(fd)
if err != nil {
return 0, err
}
var buf [4]byte
hostarch.ByteOrder.PutUint32(buf[:], v)
_, err = io.CopyOut(ctx, args[2].Pointer(), buf[:], usermem.IOOpts{})
return 0, err
case linux.TCGETS:
termios, err := ioctlGetTermios(fd)
if err != nil {
return 0, err
}
_, err = termios.CopyOut(task, args[2].Pointer())
return 0, err
case linux.TCGETS2:
termios, err := ioctlGetTermios2(fd)
if err != nil {
return 0, err
}
_, err = termios.CopyOut(task, args[2].Pointer())
return 0, err
case linux.TCSETS, linux.TCSETSW, linux.TCSETSF:
t.inode.termiosMu.Lock()
defer t.inode.termiosMu.Unlock()
if err := t.inode.tty.CheckChange(ctx, linux.SIGTTOU); err != nil {
return 0, err
}
var termios linux.Termios
if _, err := termios.CopyIn(task, args[2].Pointer()); err != nil {
return 0, err
}
err := ioctlSetTermios(fd, ioctl, &termios)
if err == nil {
t.inode.termios.FromTermios(termios)
}
return 0, err
case linux.TCSETS2, linux.TCSETSW2, linux.TCSETSF2:
t.inode.termiosMu.Lock()
defer t.inode.termiosMu.Unlock()
if err := t.inode.tty.CheckChange(ctx, linux.SIGTTOU); err != nil {
return 0, err
}
var termios linux.KernelTermios
if _, err := termios.CopyIn(task, args[2].Pointer()); err != nil {
return 0, err
}
err := ioctlSetTermios2(fd, ioctl, &termios)
if err == nil {
t.inode.termios = termios
}
return 0, err
case linux.TIOCGPGRP:
// Args: pid_t *argp
// When successful, equivalent to *argp = tcgetpgrp(fd).
// Get the process group ID of the foreground process group on this
// terminal.
pidns := kernel.PIDNamespaceFromContext(ctx)
if pidns == nil {
return 0, linuxerr.ENOTTY
}
tg := t.ThreadGroup()
if tg == nil {
return 0, linuxerr.ENOTTY
}
fgpg, err := tg.ForegroundProcessGroup(t.TTY())
if err != nil {
return 0, err
}
// Map the ProcessGroup into a ProcessGroupID in the task's PID namespace.
pgID := primitive.Int32(pidns.IDOfProcessGroup(fgpg))
_, err = pgID.CopyOut(task, args[2].Pointer())
return 0, err
case linux.TIOCSPGRP:
// Args: const pid_t *argp
// Equivalent to tcsetpgrp(fd, *argp).
// Set the foreground process group ID of this terminal.
var pgIDP primitive.Int32
if _, err := pgIDP.CopyIn(task, args[2].Pointer()); err != nil {
return 0, err
}
pgID := kernel.ProcessGroupID(pgIDP)
tg := t.ThreadGroup()
if tg == nil {
return 0, linuxerr.ENOTTY
}
if err := tg.SetForegroundProcessGroupID(ctx, t.TTY(), pgID); err != nil {
return 0, err
}
return 0, nil
case linux.TIOCGWINSZ:
// Args: struct winsize *argp
// Get window size.
winsize, err := ioctlGetWinsize(fd)
if err != nil {
return 0, err
}
_, err = winsize.CopyOut(task, args[2].Pointer())
return 0, err
case linux.TIOCSWINSZ:
// Args: const struct winsize *argp
// Set window size.
// Unlike setting the termios, any process group (even background ones) can
// set the winsize.
var winsize linux.Winsize
if _, err := winsize.CopyIn(task, args[2].Pointer()); err != nil {
return 0, err
}
oldSize, err := ioctlGetWinsize(fd)
if err != nil {
return 0, err
}
if err := ioctlSetWinsize(fd, &winsize); err != nil {
return 0, err
}
if *oldSize != winsize {
t.TTY().SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGWINCH))
}
return 0, nil
case linux.TIOCSCTTY:
// Make the given terminal the controlling terminal of the
// calling process.
steal := args[2].Int() == 1
return 0, task.ThreadGroup().SetControllingTTY(ctx, t.TTY(), steal, t.vfsfd.IsReadable())
case linux.TIOCNOTTY:
// Release this process's controlling terminal.
return 0, task.ThreadGroup().ReleaseControllingTTY(ctx, t.TTY())
// Unimplemented commands.
case 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, sysno)
fallthrough
default:
return 0, linuxerr.ENOTTY
}
}