mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
Add pseudo terminals.
This is a compatible ABI change riding on the previous commit's bump.
This commit is contained in:
parent
6ef5a5cee3
commit
b38c84852c
10 changed files with 960 additions and 3 deletions
|
@ -134,6 +134,7 @@ poll.o \
|
|||
process.o \
|
||||
psctl.o \
|
||||
ptable.o \
|
||||
pty.o \
|
||||
random.o \
|
||||
refcount.o \
|
||||
registers.o \
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
833
kernel/pty.cpp
Normal 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
75
kernel/pty.h
Normal 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
|
|
@ -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,
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
29
libc/termios/mkpty.c
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue