mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
834 lines
21 KiB
C++
834 lines
21 KiB
C++
|
/*
|
||
|
* 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
|