The file descriptor table now allows reserving room for multiple file descriptors without assigning their numbers. This functionality means any error conditions happen up front and the subsequent number assignment will never fail. This change uses the new functionality to fix troublesome error handling when allocating multiple file descriptors. One pty allocation error path was even wrong. There were subtle race conditions where one (kernel) thread may have allocated one file descriptor, and another thread spuciously replaces it with something else, and then the second file descriptor allocation failed in the first thread, and it closes the first file descriptor now pointing to a different file description. This case seems harmless but it's not a great class of bugs to exist in the first place. The new behavior means the file descriptions appear in the file descriptor table without fail and never needs to be cleaned up midway and is certainly immune to shenangians from other threads. Reviewed-by: Pedro Falcato <pedro.falcato@gmail.com>
831 lines
21 KiB
C++
831 lines
21 KiB
C++
/*
|
|
* Copyright (c) 2015, 2016, 2021 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 reservation = 0;
|
|
if ( !dtable->Reserve(2, &reservation) )
|
|
return -1;
|
|
int master_fd = dtable->Allocate(master_desc, fdflags, 0, &reservation);
|
|
int slave_fd = dtable->Allocate(slave_desc, fdflags, 0, &reservation);
|
|
assert(0 <= master_fd);
|
|
assert(0 <= slave_fd);
|
|
master_desc.Reset();
|
|
slave_desc.Reset();
|
|
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
|