Add pseudo terminals.

This is a compatible ABI change riding on the previous commit's bump.
This commit is contained in:
Jonas 'Sortie' Termansen 2016-11-15 21:14:56 +01:00
parent 6ef5a5cee3
commit b38c84852c
10 changed files with 960 additions and 3 deletions

View File

@ -134,6 +134,7 @@ poll.o \
process.o \
psctl.o \
ptable.o \
pty.o \
random.o \
refcount.o \
registers.o \

View File

@ -116,6 +116,7 @@ off_t sys_lseek(int, off_t, int);
int sys_memstat(size_t*, size_t*);
int sys_mkdirat(int, const char*, mode_t);
int sys_mkpartition(int, off_t, off_t, int);
int sys_mkpty(int*, int*, int);
void* sys_mmap_wrapper(struct mmap_request*);
int sys_mprotect(const void*, size_t, int);
int sys_munmap(void*, size_t);

View File

@ -173,7 +173,7 @@
#define SYSCALL_UNMOUNTAT 150
#define SYSCALL_FSM_MOUNTAT 151
#define SYSCALL_CLOSEFROM 152
#define SYSCALL_RESERVED1 153
#define SYSCALL_MKPTY 153
#define SYSCALL_PSCTL 154
#define SYSCALL_TCDRAIN 155
#define SYSCALL_TCFLOW 156

View File

@ -85,6 +85,7 @@
#include "multiboot.h"
#include "net/fs.h"
#include "poll.h"
#include "pty.h"
#include "uart.h"
#include "vga.h"
@ -492,6 +493,21 @@ static void BootThread(void* /*user*/)
if ( LinkInodeInDir(&ctx, slashdev, "tty", tty) != 0 )
Panic("Unable to link /dev/tty to kernel terminal factory.");
// Register the psuedo terminal filesystem as /dev/pts.
pts = Ref<PTS>(new PTS(0755, 0, 0));
if ( !pts )
Panic("Could not allocate a psuedo terminal filesystem");
if ( slashdev->mkdir(&ctx, "pts", 0755) < 0 )
Panic("Could not mkdir /dev/pts");
Ref<Descriptor> ptsdir =
slashdev->open(&ctx, "pts", O_DIRECTORY | O_READ | O_WRITE);
if ( !ptsdir )
Panic("Could not open /dev/pts");
if ( !mtable->AddMount(ptsdir->ino, ptsdir->dev, pts, true) )
Panic("Could not mount pseudo terminal filesystem on /dev/pts");
if ( slashdev->symlink(&ctx, "pts/ptmx", "ptmx") < 0 )
Panic("Could not symlink /dev/ptmx -> pts/ptmx");
// Register the kernel terminal as /dev/tty1.
Ref<Inode> tty1(new LogTerminal(slashdev->dev, 0666, 0, 0,
keyboard, kblayout, "tty1"));

833
kernel/pty.cpp Normal file
View File

@ -0,0 +1,833 @@
/*
* Copyright (c) 2015, 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* pty.cpp
* Pseudoterminals.
*/
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <sortix/fcntl.h>
#include <sortix/ioctl.h>
#include <sortix/poll.h>
#include <sortix/stat.h>
#include <sortix/termmode.h>
#include <sortix/winsize.h>
#include <sortix/kernel/copy.h>
#include <sortix/kernel/descriptor.h>
#include <sortix/kernel/dtable.h>
#include <sortix/kernel/inode.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/poll.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/signal.h>
#include <sortix/kernel/syscall.h>
#include <sortix/kernel/vnode.h>
#include "pty.h"
#include "tty.h"
#define ULONG_BIT (sizeof(unsigned long) * CHAR_BIT)
#define PTY_LIMIT (1024 * 1024)
namespace Sortix {
struct PTS::Entry
{
char name[10 + 1];
ino_t ino;
Ref<Inode> inode;
};
Ref<PTS> pts;
static kthread_mutex_t ptynum_lock = KTHREAD_MUTEX_INITIALIZER;
static unsigned long* ptynum_bitmap = NULL;
static size_t ptynum_bitmap_words = 0;
static size_t ptynum_none_below = 0;
static int AllocatePTYNumber()
{
ScopedLock lock(&ptynum_lock);
for ( size_t i = ptynum_none_below/ULONG_BIT; i < ptynum_bitmap_words; i++ )
{
unsigned long word = ptynum_bitmap[i];
if ( word == ULONG_MAX )
continue;
for ( size_t n = 0; n < ULONG_BIT; n++ )
{
unsigned long mask = 1UL << n;
if ( word & mask )
continue;
ptynum_bitmap[i] = word | mask;
size_t result = i * ULONG_MAX + n;
if ( PTY_LIMIT < result || INT_MAX < result )
{
ptynum_none_below = result;
return errno = EMFILE, -1;
}
ptynum_none_below = result + 1;
return result;
}
}
size_t new_words = 2 * ptynum_bitmap_words;
if ( !new_words )
new_words = 4;
if ( PTY_LIMIT / ULONG_BIT < new_words )
new_words = PTY_LIMIT / ULONG_BIT;
if ( new_words <= ptynum_bitmap_words )
return errno = EMFILE, -1;
unsigned long* new_bitmap = (unsigned long*)
reallocarray(ptynum_bitmap, new_words, sizeof(unsigned long));
if ( !new_bitmap )
return -1;
for ( size_t i = ptynum_bitmap_words; i < new_words; i++ )
new_bitmap[i] = 0;
size_t result = ptynum_bitmap_words * ULONG_BIT;
new_bitmap[ptynum_bitmap_words] |= 1UL << 0;
ptynum_bitmap = new_bitmap;
ptynum_bitmap_words = new_words;
ptynum_none_below = result + 1;
return result;
}
static void FreePTYNumber(int ptynum)
{
assert(0 <= ptynum);
ScopedLock lock(&ptynum_lock);
assert((size_t) ptynum < ptynum_bitmap_words * ULONG_BIT);
size_t word = ptynum / ULONG_BIT;
size_t bit = ptynum % ULONG_BIT;
unsigned long mask = 1UL << bit;
assert(ptynum_bitmap[word] & mask);
ptynum_bitmap[word] &= ~mask;
if ( (size_t) ptynum < ptynum_none_below )
ptynum_none_below = ptynum;
}
static ssize_t common_tcgetblob(ioctx_t* ctx,
const char* name,
void* buffer,
size_t count)
{
if ( !name )
{
static const char index[] = "device-path\0filesystem-type\0";
size_t index_size = sizeof(index) - 1;
if ( buffer && count < index_size )
return errno = ERANGE, -1;
if ( buffer && !ctx->copy_to_dest(buffer, &index, index_size) )
return -1;
return (ssize_t) index_size;
}
else if ( !strcmp(name, "device-path") )
{
const char* data = "none";
size_t size = strlen(data);
if ( buffer && count < size )
return errno = ERANGE, -1;
if ( buffer && !ctx->copy_to_dest(buffer, data, size) )
return -1;
return (ssize_t) size;
}
else if ( !strcmp(name, "filesystem-type") )
{
const char* data = "pts";
size_t size = strlen(data);
if ( buffer && count < size )
return errno = ERANGE, -1;
if ( buffer && !ctx->copy_to_dest(buffer, data, size) )
return -1;
return (ssize_t) size;
}
else
return errno = ENOENT, -1;
}
int common_statvfs(ioctx_t* ctx, struct statvfs* stvfs, dev_t dev)
{
struct statvfs retstvfs;
memset(&retstvfs, 0, sizeof(retstvfs));
retstvfs.f_bsize = 0;
retstvfs.f_frsize = 0;
retstvfs.f_blocks = 0;
retstvfs.f_bfree = 0;
retstvfs.f_bavail = 0;
retstvfs.f_files = 0;
retstvfs.f_ffree = 0;
retstvfs.f_favail = 0;
retstvfs.f_fsid = dev;
retstvfs.f_flag = ST_NOSUID;
retstvfs.f_namemax = 10; /* ceil(log(INT_MAX)/log(10)) */
if ( !ctx->copy_to_dest(stvfs, &retstvfs, sizeof(retstvfs)) )
return -1;
return 0;
}
class PTMX : public AbstractInode
{
public:
PTMX(dev_t dev, ino_t ino, mode_t mode, uid_t owner, gid_t group);
virtual ~PTMX();
public:
virtual Ref<Inode> factory(ioctx_t* ctx, const char* filename, int flags,
mode_t mode);
};
PTS::PTS(mode_t mode, uid_t owner, gid_t group)
{
inode_type = INODE_TYPE_DIR;
dev = (dev_t) this;
ino = 0;
stat_gid = owner;
stat_gid = group;
type = S_IFDIR;
stat_mode = (mode & S_SETABLE) | type;
dirlock = KTHREAD_MUTEX_INITIALIZER;
entries = NULL;
entries_count = 0;
entries_allocated = 0;
}
PTS::~PTS()
{
}
bool PTS::ContainsFile(const char* name) // dirlock held
{
if ( !strcmp(name, ".") || !strcmp(name, "..") || !strcmp(name, "ptmx") )
return true;
for ( size_t i = 0; i < entries_count; i++ )
if ( !strcmp(entries[i].name, name) )
return true;
return false;
}
ssize_t PTS::readdirents(ioctx_t* ctx, struct dirent* dirent, size_t size,
off_t start)
{
static const char* const names[3] = { ".", "..", "ptmx" };
static const ino_t inos[3] = { 0, 0, 1 };
static const unsigned char dtypes[3] = { DT_DIR, DT_DIR, DT_CHR };
struct dirent retdirent;
memset(&retdirent, 0, sizeof(retdirent));
retdirent.d_dev = dev;
const char* name;
ino_t ino;
unsigned char dtype;
ScopedLock lock(&dirlock);
if ( start < 3 )
{
name = names[start];
ino = inos[start];
dtype = dtypes[start];
}
else
{
start -= 3;
if ( (uintmax_t) entries_count <= (uintmax_t) start )
return 0;
name = entries[start].name;
ino = entries[start].ino;
dtype = DT_CHR;
}
size_t namelen = strlen(name);
retdirent.d_reclen = sizeof(*dirent) + namelen + 1;
retdirent.d_namlen = namelen;
retdirent.d_ino = ino;
retdirent.d_type = dtype;
if ( !ctx->copy_to_dest(dirent, &retdirent, sizeof(retdirent)) )
return -1;
if ( size < retdirent.d_reclen )
return errno = ERANGE, -1;
if ( !ctx->copy_to_dest(dirent->d_name, name, namelen+1) )
return -1;
return (ssize_t) retdirent.d_reclen;
}
Ref<Inode> PTS::open(ioctx_t* /*ctx*/, const char* filename, int flags,
mode_t /*mode*/)
{
ScopedLock lock(&dirlock);
if ( ContainsFile(filename) )
{
Ref<Inode> result;
if ( (flags & O_CREATE) && (flags & O_EXCL) )
return errno = EEXIST, Ref<Inode>(NULL);
if ( !strcmp(filename, ".") || !strcmp(filename, "..") )
result = Ref<Inode>(this);
else if ( !strcmp(filename, "ptmx") )
return Ref<Inode>(new PTMX(dev, 1, 0666, 0, 0));
else
{
for ( size_t i = 0; !result && i < entries_count; i++ )
if ( !strcmp(filename, entries[i].name) )
result = entries[i].inode;
}
if ( result )
{
if ( (flags & O_CREATE) && (flags & O_EXCL) )
return errno = EEXIST, Ref<Inode>(NULL);
return result;
}
}
if ( !(flags & O_CREATE) )
return errno = ENOENT, Ref<Inode>(NULL);
return errno = EPERM, Ref<Inode>(NULL);
}
int PTS::mkdir(ioctx_t* /*ctx*/, const char* filename, mode_t /*mode*/)
{
ScopedLock lock(&dirlock);
if ( ContainsFile(filename) )
return errno = EEXIST, -1;
return errno = EPERM, -1;
}
int PTS::link(ioctx_t* /*ctx*/, const char* filename, Ref<Inode> /*node*/)
{
ScopedLock lock(&dirlock);
if ( ContainsFile(filename) )
return errno = EEXIST, -1;
return errno = EPERM, -1;
}
int PTS::link_raw(ioctx_t* /*ctx*/, const char* filename, Ref<Inode> /*node*/)
{
ScopedLock lock(&dirlock);
if ( ContainsFile(filename) )
return errno = EEXIST, -1;
return errno = EPERM, -1;
}
int PTS::unlink(ioctx_t* /*ctx*/, const char* filename)
{
ScopedLock lock(&dirlock);
if ( !ContainsFile(filename) )
return errno = ENOENT, -1;
return errno = EPERM, -1;
}
int PTS::unlink_raw(ioctx_t* /*ctx*/, const char* filename)
{
ScopedLock lock(&dirlock);
if ( !ContainsFile(filename) )
return errno = ENOENT, -1;
return errno = EPERM, -1;
}
int PTS::rmdir(ioctx_t* /*ctx*/, const char* filename)
{
ScopedLock lock(&dirlock);
if ( !ContainsFile(filename) )
return errno = ENOENT, -1;
return errno = EPERM, -1;
}
int PTS::rmdir_me(ioctx_t* /*ctx*/)
{
return errno = EPERM, -1;
}
int PTS::symlink(ioctx_t* /*ctx*/, const char* /*oldname*/,
const char* filename)
{
ScopedLock lock(&dirlock);
if ( ContainsFile(filename) )
return errno = EEXIST, -1;
return errno = EPERM, -1;
}
int PTS::rename_here(ioctx_t* /*ctx*/, Ref<Inode> /*from*/,
const char* /*oldname*/, const char* /*newname*/)
{
return errno = EPERM, -1;
}
ssize_t PTS::tcgetblob(ioctx_t* ctx, const char* name, void* buffer,
size_t count)
{
return common_tcgetblob(ctx, name, buffer, count);
}
int PTS::statvfs(ioctx_t* ctx, struct statvfs* stvfs)
{
return common_statvfs(ctx, stvfs, dev);
}
bool PTS::RegisterPTY(Ref<Inode> pty, int ptynum)
{
ino_t ino;
if ( __builtin_add_overflow(ptynum, 2, &ino) )
return errno = EMFILE, false;
ScopedLock lock(&dirlock);
if ( entries_count == entries_allocated )
{
size_t new_allocated = 2 * entries_allocated;
if ( !new_allocated)
new_allocated = 16;
struct Entry* new_entries = new Entry[new_allocated];
if ( !new_entries )
return false;
for ( size_t i = 0; i < entries_count; i++ )
{
new_entries[i] = entries[i];
entries[i].inode.Reset();
}
delete[] entries;
entries = new_entries;
entries_allocated = new_allocated;
}
struct Entry* entry = &entries[entries_count++];
snprintf(entry->name, sizeof(entry->name), "%i", ptynum);
entry->ino = ino;
entry->inode = pty;
return true;
}
void PTS::UnregisterPTY(int ptynum)
{
ino_t ino = (ino_t) 2 + (ino_t) ptynum;
ScopedLock lock(&dirlock);
bool found = false;
for ( size_t i = 0; i < entries_count; i++ )
{
if ( entries[i].ino == ino )
{
entries[i].inode.Reset();
if ( i + 1 != entries_count )
{
entries[i] = entries[entries_count-1];
entries[entries_count-1].inode.Reset();
}
entries_count--;
found = true;
break;
}
}
assert(found);
if ( 16 < entries_allocated && entries_count <= entries_allocated / 4 )
{
size_t new_allocated = entries_allocated / 2;
struct Entry* new_entries = new Entry[new_allocated];
if ( !new_entries )
return;
for ( size_t i = 0; i < entries_count; i++ )
{
new_entries[i] = entries[i];
entries[i].inode.Reset();
}
delete[] entries;
entries = new_entries;
entries_allocated = new_allocated;
}
}
class PTY : public TTY
{
public:
PTY(dev_t dev, ino_t ino, mode_t mode, uid_t owner, gid_t group,
int ptynum);
virtual ~PTY();
public:
virtual int sync(ioctx_t* ctx);
virtual int ioctl(ioctx_t* ctx, int cmd, uintptr_t arg);
public:
ssize_t master_read(ioctx_t* ctx, uint8_t* buf, size_t count);
ssize_t master_write(ioctx_t* ctx, const uint8_t* buf, size_t count);
int master_poll(ioctx_t* ctx, PollNode* node);
int master_ioctl(ioctx_t* ctx, int cmd, uintptr_t arg);
protected:
virtual void tty_output(const unsigned char* buffer, size_t length);
private:
short PollMasterEventStatus();
private:
PollChannel master_poll_channel;
struct winsize ws;
kthread_cond_t output_ready_cond;
kthread_cond_t output_possible_cond;
size_t output_offset;
size_t output_used;
static const size_t output_size = 4096;
uint8_t output[output_size];
int ptynum;
};
PTY::PTY(dev_t dev, ino_t ino, mode_t mode, uid_t owner, gid_t group,
int ptynum) : TTY(dev, ino, mode, owner, group, "")
{
tio.c_cflag |= CREAD;
output_ready_cond = KTHREAD_COND_INITIALIZER;
output_possible_cond = KTHREAD_COND_INITIALIZER;
output_offset = 0;
output_used = 0;
memset(&ws, 0, sizeof(ws));
this->ptynum = ptynum;
snprintf(ttyname, sizeof(ttyname), "pts/%i", ptynum);
}
PTY::~PTY()
{
FreePTYNumber(ptynum);
}
ssize_t PTY::master_read(ioctx_t* ctx, uint8_t* buf, size_t count)
{
ScopedLockSignal lock(&termlock);
if ( !lock.IsAcquired() )
return errno = EINTR, -1;
while ( !output_used )
{
if ( ctx->dflags & O_NONBLOCK )
return errno = EWOULDBLOCK, -1;
if ( !kthread_cond_wait_signal(&output_ready_cond, &termlock) )
return errno = EINTR, -1;
}
size_t sofar = 0;
while ( output_used && sofar < count )
{
size_t limit = output_size - output_offset;
size_t possible = limit < output_used ? limit : output_used;
size_t amount = count < possible ? count : possible;
if ( !ctx->copy_to_dest(buf + sofar, output + output_offset, amount) )
return sofar ? (ssize_t) sofar : -1;
output_used -= amount;
output_offset += amount;
if ( output_offset == output_size )
output_offset = 0;
sofar += amount;
kthread_cond_signal(&output_possible_cond);
}
return (ssize_t) sofar;
}
// TODO: Have this be non-blocking and have master_poll_channel signal POLLOUT
// only when it won't block.
ssize_t PTY::master_write(ioctx_t* ctx, const uint8_t* buf, size_t count)
{
ScopedLockSignal lock(&termlock);
if ( !lock.IsAcquired() )
return errno = EINTR, -1;
size_t sofar = 0;
while ( sofar < count )
{
uint8_t input[1024];
size_t amount = count < sizeof(input) ? count : sizeof(input);
if ( !ctx->copy_from_src(input, buf + sofar, amount) )
return sofar ? (ssize_t) sofar : -1;
for ( size_t i = 0; i < amount; i++ )
{
if ( Signal::IsPending() )
return sofar ? (ssize_t) sofar : (errno = EINTR, -1);
ProcessByte(input[i]);
}
sofar += amount;
}
return (ssize_t) sofar;
}
// TODO: This function can deadlock if data is written using master_write, with
// tty ECHO on, then it echos the input through this function, but the
// output buffer is full, so it blocks. But there only was a single thread
// using the pty, which did a write, and now is waiting for itself to
// read. Either this is a broken usage of pty's and you must have a
// dedicated read thread, or need a dedicated kernel thread for each pty
// that buffers up a large amount of input, then processes it at its own
// pace.
// TODO: Alternatively the master is supposed to use non-blocking writes and
// check for pending input as well. This function needs to be changed to
// allow non-blocking input as well, needs an ioctx and ability to do
// partial work.
void PTY::tty_output(const unsigned char* buffer, size_t length) // termlock held
{
while ( length )
{
while ( output_used == output_size )
if ( !kthread_cond_wait_signal(&output_possible_cond, &termlock) )
return; // TODO: Data loss?
size_t offset = output_offset + output_used;
if ( output_size <= offset )
offset -= output_size;
size_t left = output_size - output_used;
size_t end = offset + left;
if ( output_size < end )
end = output_size;
size_t possible = end - offset;
size_t amount = length < possible ? length : possible;
memcpy(output + offset, buffer, amount);
buffer += amount;
length -= amount;
output_used += amount;
kthread_cond_signal(&output_ready_cond);
master_poll_channel.Signal(POLLIN | POLLRDNORM);
}
}
short PTY::PollMasterEventStatus()
{
short status = 0;
if ( output_used )
status |= POLLIN | POLLRDNORM;
if ( true /* can always write */ )
status |= POLLOUT | POLLWRNORM;
return status;
}
int PTY::master_poll(ioctx_t* /*ctx*/, PollNode* node)
{
ScopedLock lock(&termlock);
short ret_status = PollMasterEventStatus() & node->events;
if ( ret_status )
{
node->master->revents |= ret_status;
return 0;
}
master_poll_channel.Register(node);
return errno = EAGAIN, -1;
}
int PTY::sync(ioctx_t* /*ctx*/)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
return 0;
}
int PTY::ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( cmd == TIOCGWINSZ )
{
struct winsize* user_ws = (struct winsize*) arg;
if ( !ctx->copy_to_dest(user_ws, &ws, sizeof(ws)) )
return -1;
return 0;
}
else if ( cmd == TIOCGPTN )
{
int* arg_ptr = (int*) arg;
if ( !ctx->copy_to_dest(arg_ptr, &ptynum, sizeof(ptynum)) )
return -1;
return 0;
}
lock.Reset();
return TTY::ioctl(ctx, cmd, arg);
}
int PTY::master_ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
{
if ( cmd == TIOCSWINSZ )
{
ScopedLock lock(&termlock);
const struct winsize* user_ws = (const struct winsize*) arg;
if ( !ctx->copy_from_src(&ws, user_ws, sizeof(ws)) )
return -1;
return 0;
}
return ioctl(ctx, cmd, arg);
}
class MasterNode : public AbstractInode
{
public:
MasterNode(uid_t owner, gid_t group, mode_t mode, Ref<PTY> pty, int ptynum);
virtual ~MasterNode();
virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count);
virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count);
virtual int poll(ioctx_t* ctx, PollNode* node);
virtual int ioctl(ioctx_t* ctx, int cmd, uintptr_t arg);
public:
Ref<PTY> pty;
int ptynum;
};
MasterNode::MasterNode(uid_t owner, gid_t group, mode_t mode, Ref<PTY> pty,
int ptynum) : pty(pty)
{
inode_type = INODE_TYPE_TTY;
this->dev = (dev_t) this;
this->ino = (ino_t) this;
this->stat_uid = owner;
this->stat_gid = group;
this->type = S_IFCHR;
this->stat_mode = (mode & S_SETABLE) | this->type;
this->ptynum = ptynum;
}
MasterNode::~MasterNode()
{
pts->UnregisterPTY(ptynum);
pty->hup();
}
ssize_t MasterNode::read(ioctx_t* ctx, uint8_t* buf, size_t count)
{
return pty->master_read(ctx, buf, count);
}
ssize_t MasterNode::write(ioctx_t* ctx, const uint8_t* buf, size_t count)
{
return pty->master_write(ctx, buf, count);
}
int MasterNode::poll(ioctx_t* ctx, PollNode* node)
{
return pty->master_poll(ctx, node);
}
int MasterNode::ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
{
return pty->master_ioctl(ctx, cmd, arg);
}
PTMX::PTMX(dev_t dev, ino_t ino, mode_t mode, uid_t owner, gid_t group)
{
inode_type = INODE_TYPE_TTY;
this->dev = dev;
this->ino = ino;
this->type = S_IFFACTORY | S_IFFACTORY_NOSTAT;
this->stat_mode = (mode & S_SETABLE) | S_IFCHR;
this->stat_uid = owner;
this->stat_gid = group;
}
PTMX::~PTMX()
{
}
Ref<Inode> PTMX::factory(ioctx_t* ctx, const char* filename, int flags,
mode_t mode)
{
(void) ctx;
(void) filename;
(void) flags;
(void) mode;
Process* process = CurrentProcess();
uid_t uid = process->uid;
uid_t gid = process->gid;
mode_t new_mode = 0620;
int ptynum = AllocatePTYNumber();
if ( ptynum < 0 )
return Ref<Inode>(NULL);
Ref<PTY> slave_inode(new PTY(pts->dev, 2 + (ino_t) ptynum, mode, uid, gid,
ptynum));
if ( !slave_inode )
return FreePTYNumber(ptynum), Ref<Inode>(NULL);
if ( !pts->RegisterPTY(slave_inode, ptynum) )
return Ref<Inode>(NULL);
Ref<MasterNode> master_inode(new MasterNode(uid, gid, new_mode, slave_inode,
ptynum));
if ( !master_inode )
{
pts->UnregisterPTY(ptynum);
return Ref<Inode>(NULL);
}
return master_inode;
}
int sys_mkpty(int* master_fd_user, int* slave_fd_user, int flags)
{
int fdflags = 0;
if ( flags & O_CLOEXEC ) fdflags |= FD_CLOEXEC;
if ( flags & O_CLOFORK ) fdflags |= FD_CLOFORK;
flags &= ~(O_CLOEXEC | O_CLOFORK);
if ( flags & ~(O_NONBLOCK) )
return errno = EINVAL, -1;
Process* process = CurrentProcess();
uid_t uid = process->uid;
uid_t gid = process->gid;
mode_t mode = 0620;
int ptynum = AllocatePTYNumber();
if ( ptynum < 0 )
return -1;
Ref<PTY> slave_inode(new PTY(pts->dev, 2 + ptynum, mode, uid, gid, ptynum));
if ( !slave_inode )
return FreePTYNumber(ptynum), -1;
if ( !pts->RegisterPTY(slave_inode, ptynum) )
return -1;
Ref<MasterNode> master_inode(new MasterNode(uid, gid, mode, slave_inode,
ptynum));
if ( !master_inode )
{
pts->UnregisterPTY(ptynum);
return -1;
}
Ref<Vnode> master_vnode(new Vnode(master_inode, Ref<Vnode>(NULL), 0, 0));
Ref<Vnode> slave_vnode(new Vnode(slave_inode, Ref<Vnode>(NULL), 0, 0));
master_inode.Reset();
slave_inode.Reset();
if ( !master_vnode || !slave_vnode )
return -1;
Ref<Descriptor> master_desc(
new Descriptor(master_vnode, O_READ | O_WRITE | flags));
Ref<Descriptor> slave_desc(
new Descriptor(slave_vnode, O_READ | O_WRITE | flags));
master_vnode.Reset();
slave_vnode.Reset();
if ( !master_desc || !slave_desc )
return -1;
Ref<DescriptorTable> dtable = process->GetDTable();
int master_fd = dtable->Allocate(master_desc, fdflags);
int slave_fd = dtable->Allocate(slave_desc, fdflags);
master_desc.Reset();
slave_desc.Reset();
if ( master_fd < 0 || slave_fd < 0 )
{
if ( 0 < master_fd )
dtable->Free(master_fd);
if ( 0 < master_fd )
dtable->Free(slave_fd);
return -1;
}
dtable.Reset();
if ( !CopyToUser(master_fd_user, &master_fd, sizeof(int)) ||
!CopyToUser(slave_fd_user, &slave_fd, sizeof(int)) )
return -1;
return 0;
}
} // namespace Sortix

75
kernel/pty.h Normal file
View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* pty.h
* Pseudoterminals.
*/
#ifndef SORTIX_PTY_H
#define SORTIX_PTY_H
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/inode.h>
namespace Sortix {
class PTS : public AbstractInode
{
struct Entry;
public:
PTS(mode_t mode, uid_t owner, gid_t group);
virtual ~PTS();
public:
virtual ssize_t readdirents(ioctx_t* ctx, struct dirent* dirent,
size_t size, off_t start);
virtual Ref<Inode> open(ioctx_t* ctx, const char* filename, int flags,
mode_t mode);
virtual int mkdir(ioctx_t* ctx, const char* filename, mode_t mode);
virtual int link(ioctx_t* ctx, const char* filename, Ref<Inode> node);
virtual int link_raw(ioctx_t* ctx, const char* filename, Ref<Inode> node);
virtual int unlink(ioctx_t* ctx, const char* filename);
virtual int unlink_raw(ioctx_t* ctx, const char* filename);
virtual int rmdir(ioctx_t* ctx, const char* filename);
virtual int rmdir_me(ioctx_t* ctx);
virtual int symlink(ioctx_t* ctx, const char* oldname,
const char* filename);
virtual int rename_here(ioctx_t* ctx, Ref<Inode> from, const char* oldname,
const char* newname);
virtual ssize_t tcgetblob(ioctx_t* ctx, const char* name, void* buffer,
size_t count);
virtual int statvfs(ioctx_t* ctx, struct statvfs* stvfs);
public:
bool RegisterPTY(Ref<Inode> pty, int ptynum);
void UnregisterPTY(int ptynum);
private:
bool ContainsFile(const char* name);
private:
kthread_mutex_t dirlock;
struct Entry* entries;
size_t entries_count;
size_t entries_allocated;
};
extern Ref<PTS> pts;
} // namespace Sortix
#endif

View File

@ -187,7 +187,7 @@ void* syscall_list[SYSCALL_MAX_NUM + 1] =
[SYSCALL_UNMOUNTAT] = (void*) sys_unmountat,
[SYSCALL_FSM_MOUNTAT] = (void*) sys_fsm_mountat,
[SYSCALL_CLOSEFROM] = (void*) sys_closefrom,
[SYSCALL_RESERVED1] = (void*) sys_bad_syscall,
[SYSCALL_MKPTY] = (void*) sys_mkpty,
[SYSCALL_PSCTL] = (void*) sys_psctl,
[SYSCALL_TCDRAIN] = (void*) sys_tcdrain,
[SYSCALL_TCFLOW] = (void*) sys_tcflow,

View File

@ -608,6 +608,7 @@ sys/uio/writev.o \
sys/utsname/uname.o \
sys/wait/wait.o \
sys/wait/waitpid.o \
termios/mkpty.o \
termios/tcdrain.o \
termios/tcflow.o \
termios/tcflush.o \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2013, 2014, 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -69,6 +69,7 @@ int tcsetattr(int, int, const struct termios*);
/* Functions that are Sortix extensions. */
#if __USE_SORTIX
int mkpty(int*, int*, int);
ssize_t tcgetblob(int, const char*, void*, size_t);
ssize_t tcsetblob(int, const char*, const void*, size_t);
int tcgetwincurpos(int, struct wincurpos*);

29
libc/termios/mkpty.c Normal file
View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2015, 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* termios/mkpty.c
* Create and open a pseudoterminal.
*/
#include <sys/syscall.h>
#include <termios.h>
DEFN_SYSCALL3(int, sys_mkpty, SYSCALL_MKPTY, int*, int*, int);
int mkpty(int* master_fd, int* slave_fd, int flags)
{
return sys_mkpty(master_fd, slave_fd, flags);
}