Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1944 lines (1797 sloc) 42 KB
/*
* This file is part of Jehanne.
*
* Copyright (C) 2017 Giacomo Tesio <giacomo@tesio.it>
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, version 3 of the License.
*
* Jehanne is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Jehanne. If not, see <http://www.gnu.org/licenses/>.
*/
/* This is an helper for programs using libposix.
*
* It simulates the kernel management of signal related stuffs, like
* - signal delivery: it translates signals to notes and send them
* - signal masks: it discards ignored signals and defer blocked ones
* - process groups
*
* It represent a single login session.
* It starts a new program in a new namespace providing /dev/posix/,
* to serve applications based on libposix.
* It translate notes received into signals for the foreground process
* group.
*/
#include <u.h>
#include <lib9.h>
#include <envvars.h>
#include <9P2000.h>
#include <posix.h>
#include "internal.h"
extern int *__libposix_devsignal;
/* Type defs */
typedef struct Process Process;
typedef enum ProcessFlags ProcessFlags;
typedef struct ProcessGroup ProcessGroup;
typedef struct Signal Signal;
typedef struct SignalList SignalList;
typedef struct Waiter Waiter;
typedef struct Fid Fid;
/* Data structures */
enum ProcessFlags
{
SessionLeader = 1<<0,
GroupLeader = 1<<1,
NannyProcess = 1<<2, /* waits for a child and produce SIGCHLD */
CalledExec = 1<<3, /* do not free on close */
Detached = 1<<4,
};
struct ProcessInfos /* for actual processes */
{
PosixSignalMask waited;
SignalList* signals;
};
struct NannyInfos /* for SIGCHLD generators */
{
int ppid;
int cpid;
Process* child;
};
struct Process
{
int pid;
int children;
int noteid;
ProcessFlags flags;
Process* parent;
ProcessGroup* group;
PosixSignalMask blocked;
PosixSignalMask ignored;
union{
struct ProcessInfos;
struct NannyInfos; /* if flags & NannyProcess */
};
Process* next;
};
struct Waiter
{
uint16_t tag;
Fid* owner;
Signal* signal;
Waiter* next;
};
struct ProcessGroup
{
int pgid;
int noteid;
int processes;
ProcessGroup* next;
};
struct Fid
{
Qid qid;
int16_t opened; /* -1 when not open */
int fd;
Process* process; /* if any */
Fid *next;
};
/* Global variables */
char notebuf[ERRMAX];
int fspid; // filesystem id
int sid; // session leader id
int debugging = -1;
char *debug_prefix;
int debug_prefix_len;
char *user;
char *data;
extern void (*__assert)(char*);
ProcessGroup* foreground;
ProcessGroup* groups;
Process* processes;
Process* session_leader;
Waiter* waiting_process;
/* Utilities (DEBUG, devproc and so on) */
#define DEBUG(...) if (debugging != -1)debug(__VA_ARGS__)
static void
debug(const char *fmt, ...)
{
va_list arg;
if (debug_prefix == nil){
debug_prefix = smprint("posix.%s.%d[%d]: ", user, sid, fspid);
debug_prefix_len = strlen(debug_prefix);
}
write(debugging, debug_prefix, debug_prefix_len);
va_start(arg, fmt);
vfprint(debugging, fmt, arg);
va_end(arg);
}
static void
usage(void)
{
fprint(2, "usage: %s [-d dbgfile] [-p pid | cmd [args...]]\n", argv0);
exits("usage");
}
/* Signals */
struct Signal
{
int refs;
PosixSignalInfo info;
char note[];
};
struct SignalList
{
Signal* signal;
SignalList* next;
};
static Signal*
signal_create(PosixSignalInfo *siginfo)
{
int i;
Signal *signal;
__libposix_signal_to_note(siginfo, notebuf, sizeof(notebuf));
i = strlen(notebuf);
signal = malloc(sizeof(Signal)+i+1);
signal->refs = 1;
memmove(&signal->info, siginfo, sizeof(PosixSignalInfo));
memmove(signal->note, notebuf, i);
signal->note[i] = '\0';
return signal;
}
static void
signal_free(Signal* signal)
{
assert(signal != nil && signal->refs > 0);
if(--signal->refs == 0)
free(signal);
}
/* Interface to devproc */
static int
proc_noteid(int pid)
{
long n;
char buf[32];
sprint(buf, "/proc/%d/noteid", pid);
n = remove(buf);
DEBUG("proc_noteid(%d) = %lld\n", pid, n);
if(n == -1)
return -1;
return (int)n;
}
static int
proc_set_noteid(int pid, int noteid)
{
int n, f;
char buf[32];
assert(pid != 0);
assert(noteid != 0);
sprint(buf, "/proc/%d/noteid", pid);
f = open(buf, OWRITE);
if(f < 0)
return 0;
n = sprint(buf, "%d", noteid);
n = write(f, buf, n);
close(f);
if(n < 0)
return 0;
return 1;
}
static int
proc_ppid(int pid)
{
long n;
char buf[32];
sprint(buf, "/proc/%d/ppid", pid);
n = remove(buf);
DEBUG("proc_ppid(%d) = %lld\n", pid, n);
if(n == -1)
return -1;
return (int)n;
}
static int
proc_dispatch_signal(int target, Signal *signal)
{
DEBUG("proc_dispatch_signal(%d, '%s')\n", target, signal->note);
if(postnote(PNPROC, target, signal->note) < 0)
return -1;
if(signal->info.si_signo == PosixSIGCONT)
__libposix_send_control_msg(target, "start");
return 1;
}
static int
proc_convert_signal(int target, Signal *signal)
{
char *note;
DEBUG("proc_convert_signal(%d, '%s')\n", target, signal->note);
switch(signal->info.si_signo){
case PosixSIGCONT:
__libposix_send_control_msg(target, "start");
return 1;
case PosixSIGTSTP:
case PosixSIGTTIN:
case PosixSIGTTOU:
case PosixSIGSTOP:
__libposix_send_control_msg(target, "stop");
return 1;
case PosixSIGCHLD:
/* simply don't send notes that are not expected */
return 1;
case PosixSIGHUP:
note = "hangup";
break;
case PosixSIGALRM:
note = "alarm";
break;
case PosixSIGINT:
note = "interrupt";
break;
default:
note = signal->note;
break;
}
if(postnote(PNPROC, target, note) < 0)
return -1;
return 1;
}
/* ProcessGroups */
static int process_handle_signal(Process *target, Signal *signal);
static void process_free(Process *target);
static void
group_remove(ProcessGroup* group, Process* p)
{
ProcessGroup **e, *g;
assert(group == p->group);
p->group = nil;
if(--group->processes == 0){
e = &groups;
while(g = *e){
if(group == g){
*e = group->next;
if(foreground == group)
foreground = nil;
free(group);
return;
}
e = &g->next;
}
DEBUG("group_remove(%d, %d) not found\n", group->pgid, p->pid);
sysfatal("group_remove: group %d not found", group->pgid);
}
DEBUG("group_remove(%d, %d): done\n", group->pgid, p->pid);
}
static int
group_add(ProcessGroup* group, Process* p)
{
assert(group != p->group);
if(!proc_set_noteid(p->pid, group->noteid)){
DEBUG("group_add(%d, %d): cannot set noteid %d\n", group->pgid, p->pid, group->noteid);
return 0;
}
group_remove(p->group, p);
p->group = group;
p->noteid = group->noteid;
++group->processes;
DEBUG("group_add(%d, %d): done\n", group->pgid, p->pid);
return 1;
}
static ProcessGroup*
group_create(Process *leader)
{
ProcessGroup* g;
assert(leader != nil);
if(leader->flags & SessionLeader)
return nil; /* operation not permitted */
if(leader->group && leader->group->pgid == leader->pid)
return leader->group; /* nothing to do */
g = mallocz(sizeof(ProcessGroup), 1);
if(g == nil)
return nil;
DEBUG("group_create(%d)\n", leader->pid);
leader->flags |= GroupLeader;
g->pgid = leader->pid;
g->noteid = proc_noteid(leader->pid);
g->processes = 1;
if(leader->group)
group_remove(leader->group, leader);
leader->group = g;
if((leader->flags&NannyProcess) && leader->child != nil){
group_add(g, leader->child);
}
g->next = groups;
groups = g;
DEBUG("group_create(%d): done\n", leader->pid);
return g;
}
static void
group_handle_signal(ProcessGroup *group, Signal *signal)
{
Process *p, *tmp, **e;
assert(group != nil && signal != nil);
DEBUG("group_handle_signal(%d, %d): dispatching\n", group->pgid, signal->info.si_signo);
e = &processes;
while(p = *e){
if(p->group == group && !(p->flags & NannyProcess))
if(process_handle_signal(p, signal) == -1
&&((p->flags & CalledExec)||(p->flags & Detached))){
/* the signal dispatching (aka postnote) failed
* and the process called exec or setsid.
* Let assume that the process exited.
*/
tmp = p->next;
DEBUG("group_handle_signal(%d, %d): removing (probably) dead process %d\n", group->pgid, signal->info.si_signo, p->pid);
process_free(p);
*e = tmp;
continue;
}
e = &p->next;
}
DEBUG("group_handle_signal(%d, %d): done\n", group->pgid, signal->info.si_signo);
}
static int
group_foreground(Process *reqprocess, int pgid)
{
static PosixSignalInfo siginfo;
ProcessGroup *g;
Signal *s;
if(reqprocess->group == foreground
||(reqprocess->blocked&SIGNAL_MASK(PosixSIGTTOU))
||(reqprocess->ignored&SIGNAL_MASK(PosixSIGTTOU))){
g = groups;
while(g != nil && g->pgid != pgid)
g = g->next;
if(g == nil)
return 0; /* group not found */
foreground = g;
return 1;
}
siginfo.si_signo = PosixSIGTTOU;
s = signal_create(&siginfo);
group_handle_signal(reqprocess->group, s);
signal_free(s);
return 0;
}
static ProcessGroup*
group_find(int gid)
{
ProcessGroup* g;
g = groups;
while(g && g->pgid != gid)
g = g->next;
if(g && g->pgid == gid)
return g;
return nil;
}
/* Processes */
static Process*
process_find(int pid)
{
Process* p;
p = processes;
while(p && p->pid < pid)
p = p->next;
if(p && p->pid == pid)
return p;
return nil;
}
static Process*
process_create(int pid)
{
int ppid, tmp;
Process *n, *p, *t, **e;
n = mallocz(sizeof(Process), 1);
if(n == nil)
return nil;
n->pid = pid;
ppid = proc_ppid(pid);
e = &processes;
while((p = *e) && p->pid < pid){
if(p->pid == ppid){
n->parent = p;
n->group = p->group;
++n->group->processes;
}
e = &p->next;
}
if(p != nil && p->pid == pid){
free(n);
return nil;
}
if(n->parent == nil && session_leader != nil){
tmp = ppid;
while(tmp > sid){
tmp = proc_ppid(tmp);
t = process_find(tmp);
if(t != nil){
n->blocked = t->blocked;
n->group = t->group;
++n->group->processes;
break;
}
}
} else if(session_leader != nil){
/* libposix must ensure that the creation of a process
* here occurs before the exit of the parent process
* (so fork must be wait for the child to speak with us)
*/
++n->parent->children;
if(n->parent->flags & NannyProcess){
assert(n->child == nil); /* one nanny for one child */
n->parent->cpid = pid;
n->parent->child = n;
}
n->blocked = n->parent->blocked;
n->ignored = n->parent->ignored;
++n->group->processes;
} else {
session_leader = n;
foreground = group_create(session_leader);
session_leader->flags |= SessionLeader;
}
n->noteid = proc_noteid(pid);
n->next = p;
*e = n;
if(n->flags & SessionLeader){
DEBUG("process_create(%d): session leader\n", pid);
} else if(n->parent == nil){
DEBUG("process_create(%d): ppid %d (NOT POSIX), gid %d\n", pid, ppid, n->group ? n->group->pgid : -1);
} else {
DEBUG("process_create(%d): ppid %d, gid %d\n", pid, n->parent->pid, n->group->pgid);
}
return n;
}
static Process*
process_create_nanny(int pid)
{
Process *p = process_create(pid);
if(p == nil)
return nil;
assert(p->parent != nil);
p->flags |= NannyProcess;
p->ppid = p->parent->pid;
return p;
}
static void
process_free_nannies(Process *parent, Process **list)
{
Process *p;
char buf[256];
int fd;
while(parent->children > 0 && (p = *list)){
if(p->parent == parent && (p->flags & NannyProcess)){
*list = p->next;
DEBUG("process_free_nannies(%d): killing pid %d\n", parent->pid, p->pid);
snprint(buf, sizeof(buf), "/proc/%d/ctl", p->pid);
fd = open(buf, OWRITE);
if(fd < 0){
DEBUG("process_free_nannies(%d): cannot open '%s': %r\n", parent, buf);
} else {
write(fd, "kill", 5);
}
close(fd);
if(p->child)
p->child->parent = nil;
free(p);
--parent->children;
} else {
list = &p->next;
}
}
}
static void
process_free(Process *target)
{
Process *p, *c, **e;
Signal *s;
PosixSignalInfo siginfo;
if(target == session_leader){
if(foreground != nil){
memset(&siginfo, 0, sizeof(PosixSignalInfo));
siginfo.si_signo = PosixSIGHUP;
s = signal_create(&siginfo);
group_handle_signal(foreground, s);
signal_free(s);
}
session_leader = nil;
}
e = &processes;
while(p = *e){
if(p == target){
*e = p->next;
DEBUG("process_free(%d): gid %d\n", p->pid, p->group->pgid);
if(p->children > 0 && !(p->flags&NannyProcess)){
/* remove all sigchild proxies */
process_free_nannies(p, e);
}
if(p->children > 0){
c = *e;
while(c && p->children > 0){
if(c->parent == p){
c->parent = nil;
--p->children;
}
c = c->next;
}
assert(p->children == 0);
}
if(p->parent){
--p->parent->children;
if(p->parent->flags&NannyProcess)
p->parent->child = nil;
}
free(p);
return;
}
e = &p->next;
}
DEBUG("process_free(%d): not found!\n", target->pid);
}
static int
process_handle_signal(Process *target, Signal *signal)
{
int tmp;
Waiter *wp;
SignalList *l;
assert(target != nil && signal != nil);
if(target->flags & NannyProcess){
/* target is a proxy */
if(signal->info.si_pid == target->cpid){
/* this is a message from the child, to its parent */
if(target->parent){
signal->info.si_pid = target->pid;
tmp = process_handle_signal(target->parent, signal);
signal->info.si_pid = target->cpid;
return tmp;
}
return 1;
}
if(signal->info.si_pid == target->ppid){
/* this is a message from the parent, to its child */
if(target->child){
signal->info.si_pid = target->pid;
tmp = process_handle_signal(target->child, signal);
signal->info.si_pid = target->ppid;
return tmp;
}
return 1;
}
/* this message is neither from the parent nor from the child
* but since only the parent can give away target pid
* as its own child pid, we assume the signal is for
* the child.
*/
if(target->child)
return process_handle_signal(target->child, signal);
}
PosixSignalMask sigflag = SIGNAL_MASK(signal->info.si_signo);
if(target->ignored & sigflag)
return 1;
if(target->waited & sigflag){
/* the process is waiting this signal reading */
wp = waiting_process;
while(wp && wp->owner->process != target)
wp = wp->next;
assert(wp != nil && wp->owner->process == target);
wp->signal = signal;
++signal->refs;
return 0;
}
if(target->blocked & sigflag){
l = malloc(sizeof(SignalList));
if(nil == l)
return 1;
l->signal = signal;
l->next = target->signals;
target->signals = l;
++signal->refs;
return 0;
}
if(target->flags & CalledExec)
return proc_convert_signal(target->pid, signal);
return proc_dispatch_signal(target->pid, signal);
}
void
process_block_signals(Process *p, PosixSignalMask signals)
{
SignalList *s, **es;
PosixSignalMask old;
assert(p != nil);
old = p->blocked;
if(old == signals){
DEBUG("process_block_signals(%d, %ullb): nothing to do\n", p->pid, signals);
return;
}
p->blocked = signals;
es = &p->signals;
while(s = *es){
if((signals & SIGNAL_MASK(s->signal->info.si_signo)) == 0){
*es = s->next;
DEBUG("process_block_signals(%d, %ullb): dispatching '%s'\n", p->pid, signals, s->signal->note);
process_handle_signal(p, s->signal);
signal_free(s->signal);
free(s);
continue;
}
es = &s->next;
}
DEBUG("process_block_signals(%d, %ullb): done\n", p->pid, signals);
}
void
process_enable_signal(Process *p, PosixSignalMask signalmask)
{
PosixSignalMask old;
assert(p != nil);
old = p->ignored;
if(old & signalmask){
p->ignored &= ~signalmask;
DEBUG("process_enable_signal(%d, %ullb): done\n", p->pid, signalmask);
} else {
DEBUG("process_enable_signal(%d, %ullb): nothing to do\n", p->pid, signalmask);
}
}
void
process_ignore_signal(Process *p, PosixSignalMask signalmask)
{
SignalList *s, **es;
PosixSignalMask old;
assert(p != nil);
old = p->ignored;
if(old & signalmask){
DEBUG("ignore_signals(%d, %ullb): nothing to do\n", p->pid, signalmask);
return;
}
p->ignored |= signalmask;
es = &p->signals;
while(s = *es){
if(signalmask & SIGNAL_MASK(s->signal->info.si_signo)){
*es = s->next;
DEBUG("ignore_signals(%d, %ullb): discarded %d\n", p->pid, signalmask, s->signal->info.si_signo);
signal_free(s->signal);
free(s);
continue;
}
es = &s->next;
}
DEBUG("ignore_signals(%d, %ullb): done\n", p->pid, signalmask);
}
static Signal*
process_find_pending_signal(Process *p, PosixSignalMask mask)
{
SignalList *s, **e;
Signal *found;
assert(p != nil);
e = &p->signals;
while(s = *e){
if(SIGNAL_MASK(s->signal->info.si_signo) & mask){
*e = s->next;
found = s->signal;
free(s);
return found;
}
}
return nil;
}
static PosixSignalMask
process_pending_signals(Process *p)
{
PosixSignalMask pending = 0;
SignalList *s;
assert(p != nil);
s = p->signals;
while(s){
pending |= SIGNAL_MASK(s->signal->info.si_signo);
s = s->next;
}
return pending;
}
/* File System */
typedef union
{
long value;
PosixHelperRequest request;
} LongConverter;
typedef union
{
int value;
char bytes[sizeof(int)];
} IntConverter;
enum
{
Maxfdata = 8192,
Miniosize = IOHDRSZ+sizeof(PosixSignalInfo),
Maxiosize = IOHDRSZ+Maxfdata,
};
typedef enum
{
Initializing,
Mounted,
Unmounted, /* fsserve() loop while status < Unmounted */
} Status;
Status status;
enum {
Qroot,
Qposix,
Nqid,
Qcontrol,
Qsignals,
Qnanny,
Nhqid,
};
static struct Qtab {
char *name;
int mode;
int type;
int length;
} qtab[Nhqid] = {
"/",
DMDIR|0555,
QTDIR,
0,
"posix", /* create files on fork */
DMDIR|0755,
QTDIR,
0,
"",
0,
0,
0,
"control",
0222, /* write to send signals */
0,
0,
"signals", /* read/write to control signal management */
0666,
0,
0,
"nanny", /* write to send SIGCHLD */
0222,
0,
0,
};
/* linked list of known fids
*
* NOTE: we don't free() Fids, because there's no appropriate point
* in 9P2000 to do that, except the Tclunk of the attach fid,
* that in our case corresponds to shutdown
* (the kernel is our single client, we are doomed to trust it)
*/
#define ISCLOSED(f) (f != nil && f->opened == -1)
static Fid *fids;
static Fid **ftail;
static Fid *external; /* attach fid of the mount() */
static Fid *control_fid;
static Fid*
fid_create(int fd, Qid qid)
{
Fid *fid;
fid = (Fid*)malloc(sizeof(Fid));
if(fid){
fid->fd = fd;
fid->qid = qid;
fid->opened = -1;
fid->process = nil;
fid->next = nil;
*ftail = fid;
ftail = &fid->next;
}
return fid;
}
static Fid*
fid_find(int fd)
{
Fid *fid;
fid = fids;
while(fid != nil && fid->fd != fd)
fid = fid->next;
return fid;
}
/* 9p message handlers */
static int
fillstat(uint64_t path, Dir *d)
{
struct Qtab *t;
memset(d, 0, sizeof(Dir));
d->uid = user;
d->gid = user;
d->muid = user;
d->qid = (Qid){path, 0, 0};
d->atime = time(0);
t = qtab + path;
d->name = t->name;
d->qid.type = t->type;
d->mode = t->mode;
d->length = t->length;
return 1;
}
static int
rootread(Fid *fid, uint8_t *buf, long off, int cnt, int blen)
{
int m, n;
long i, pos;
Dir d;
n = 0;
pos = 0;
for (i = 1; i < Nqid; i++){
fillstat(i, &d);
m = convD2M(&d, &buf[n], blen-n);
if(off <= pos){
if(m <= BIT16SZ || m > cnt)
break;
n += m;
cnt -= m;
}
pos += m;
}
return n;
}
static int
fs_error(Fcall *rep, char *err)
{
DEBUG("fs_error %#p: %s\n", __builtin_return_address(0), err);
rep->type = Rerror;
rep->ename = err;
return 1;
}
#define fs_eperm(req, rep) fs_error(rep, "permission denied")
#define fs_enotfound(req, rep) fs_error(rep, "file does not exist")
static int
fs_attach(Fcall *req, Fcall *rep)
{
char *spec;
Fid *f;
spec = req->aname;
if(spec && spec[0]){
return fs_error(rep, "bad attach specifier");
}
f = fid_find(req->fid);
if(f == nil)
f = fid_create(req->fid, (Qid){Qroot, 0, QTDIR});
if(f == nil){
return fs_error(rep, "out of memory");
}
external = f;
status = Mounted;
rep->type = Rattach;
rep->qid = f->qid;
return 1;
}
static int
fs_auth(Fcall *req, Fcall *rep)
{
return fs_error(rep, "authentication not required");
}
static int
fs_version(Fcall *req, Fcall *rep)
{
if(req->msize < Miniosize){
return fs_error(rep, "message size too small");
}
rep->type = Rversion;
rep->msize = req->msize;
if(*req->version == 0 || strncmp(req->version, "9P2000", 6) == 0)
rep->version = "9P2000";
else
rep->version = "unknown";
return 1;
}
static int
fs_flush(Fcall *req, Fcall *rep)
{
Fid *f;
Waiter **e, *s;
e = &waiting_process;
while(s = *e){
if(s->tag == req->oldtag){
*e = s->next;
f = s->owner;
f->process->waited = 0;
if(s->signal != nil){
process_handle_signal(f->process, s->signal);
signal_free(s->signal);
}
free(s);
break;
}
e = &s->next;
}
rep->type = Rflush;
return 1;
}
static int
fs_walk(Fcall *req, Fcall *rep)
{
Fid *f, *n;
Qid q;
f = fid_find(req->fid);
if(f == nil)
return fs_error(rep, "bad fid");
if(f->opened != -1)
return fs_error(rep, "fid in use");
if(req->nwname > 0 && f->qid.type != QTDIR)
goto WalkInNonDirectory;
if(req->fid == req->newfid){
n = f;
} else {
n = fid_find(req->newfid);
if(n == nil)
n = fid_create(req->newfid, q);
else if(n->opened != -1)
return fs_error(rep, "newfid already in use");
if(n == nil)
return fs_error(rep, "out of memory");
}
if(req->nwname == 0){
n->qid = f->qid;
rep->type = Rwalk;
rep->nwqid = 0;
return 1;
}
// TODO handle '..'
switch(f->qid.path){
case Qroot:
switch(req->nwname){
case 1:
if(strcmp(qtab[Qposix].name, req->wname[0]) != 0)
goto FileNotExists;
rep->wqid[0] = (Qid){Qposix, 0, QTDIR};
break;
case 2:
if(strcmp(qtab[Qposix].name, req->wname[0]) != 0)
goto FileNotExists;
if(control_fid != nil
|| strcmp(qtab[Qcontrol].name, req->wname[1]) != 0)
goto FileNotExists;
rep->wqid[0] = (Qid){Qposix, 0, QTDIR};
rep->wqid[1] = (Qid){Qcontrol, 0, 0};
break;
default:
goto WalkInNonDirectory;
}
break;
case Qposix:
if(req->nwname > 1)
goto WalkInNonDirectory;
if(strcmp("..", req->wname[0]) == 0){
rep->wqid[0] = (Qid){Qroot, 0, QTDIR};
break;
}
if(strcmp(qtab[Qcontrol].name, req->wname[0]) != 0)
goto FileNotExists;
rep->wqid[0] = (Qid){Qcontrol, 0, 0};
break;
default:
goto WalkInNonDirectory;
}
n->qid = rep->wqid[req->nwname-1];
rep->type = Rwalk;
rep->nwqid = req->nwname;
return 1;
WalkInNonDirectory:
return fs_error(rep, "walk in non directory");
FileNotExists:
return fs_error(rep, "file does not exist");
}
static int
fs_create(Fcall *req, Fcall *rep)
{
static int need[4] = {
4, /* NP_OREAD */
2, /* NP_OWRITE */
6, /* NP_ORDWR */
1 /* NP_OEXEC */
};
struct Qtab *t;
Fid *f;
int n;
if(req->mode&NP_OZEROES)
return fs_error(rep, "invalid 9P2000 open mode");
f = fid_find(req->fid);
if(f == nil)
return fs_error(rep, "bad fid");
if(f->qid.path != Qposix)
return fs_eperm(req, rep);
assert(f->process == nil);
if(strcmp("signals", req->name) == 0){
t = qtab + Qsignals;
n = need[req->mode & 3];
if((n & (t->mode>>6)) != n)
return fs_eperm(req, rep);
n = (int)req->perm;
if(n == sid){
/* this is the session leader */
f->process = session_leader;
foreground = session_leader->group;
} else {
f->process = process_find(n);
if(f->process){
if(f->process->flags & CalledExec){
f->process->flags &= ~CalledExec;
} else {
f->process = nil;
return fs_eperm(req, rep);
}
} else {
f->process = process_create(n);
}
}
f->qid = (Qid){Qsignals, 0, 0};
} else if(strcmp("nanny", req->name) == 0){
t = qtab + Qnanny;
n = need[req->mode & 3];
if((n & (t->mode>>6)) != n)
return fs_eperm(req, rep);
f->process = process_create_nanny((int)req->perm);
f->qid = (Qid){Qnanny, 0, 0};
}
if(f->process == nil)
return fs_eperm(req, rep);
f->opened = req->mode;
/* both processes and nannies share the same iounit */
rep->type = Rcreate;
rep->qid = f->qid;
rep->iounit = sizeof(PosixSignalInfo);
return 1;
}
static int
fs_open(Fcall *req, Fcall *rep)
{
static int need[4] = {
4, /* NP_OREAD */
2, /* NP_OWRITE */
6, /* NP_ORDWR */
1 /* NP_OEXEC */
};
struct Qtab *t;
Fid *f;
int n;
if(req->mode&NP_OZEROES)
return fs_error(rep, "invalid 9P2000 open mode");
f = fid_find(req->fid);
if(f == nil)
return fs_error(rep, "bad fid");
if(f->opened != -1)
return fs_error(rep, "already open");
t = qtab + f->qid.path;
n = need[req->mode & 3];
if((n & (t->mode>>6)) != n)
return fs_eperm(req, rep);
rep->iounit = 0;
switch(f->qid.path){
case Qnanny:
case Qsignals:
return fs_enotfound(req, rep);
case Qcontrol:
if(control_fid) /* can be opened only once */
return fs_enotfound(req, rep);
control_fid = f;
rep->iounit = sizeof(PosixSignalInfo);
break;
case Qroot:
case Qposix:
break;
default:
return fs_enotfound(req, rep);
}
f->opened = req->mode;
rep->type = Ropen;
rep->qid = f->qid;
return 1;
}
static int
fs_read(Fcall *req, Fcall *rep)
{
Fid *f;
Signal *signal;
Waiter *waiter;
if(req->count < 0)
return fs_error(rep, "bad read/write count");
f = fid_find(req->fid);
if(f == nil){
return fs_error(rep, "bad fid");
}
if(ISCLOSED(f) || f->opened == NP_OWRITE){
return fs_error(rep, "i/o error");
}
rep->type = Rread;
switch(f->qid.path){
case Qroot:
if(req->count == 0){
rep->count = 0;
rep->data = nil;
return 1;
}
rep->count = rootread(f, (uint8_t*)data, req->offset, req->count, Maxfdata);
rep->data = data + req->offset;
return 1;
case Qposix:
/* posix/ is always empty */
rep->count = 0;
rep->data = nil;
return 1;
case Qsignals:
assert(f->process != nil);
if(req->count != sizeof(PosixSignalInfo))
return fs_error(rep, "i/o error");
signal = process_find_pending_signal(f->process, req->offset);
if(signal != nil){
memmove(data, &signal->info, sizeof(PosixSignalInfo));
rep->data = data;
rep->count = sizeof(PosixSignalInfo);
signal_free(signal);
f->process->waited = 0;
return 1;
}
waiter = malloc(sizeof(Waiter));
if(waiter == nil)
return fs_error(rep, "i/o error");
f->process->waited = req->offset;
waiter->tag = req->tag;
waiter->owner = f;
waiter->signal = nil;
waiter->next = waiting_process;
waiting_process = waiter;
return 0;
default:
return fs_error(rep, "i/o error");
}
}
static int
fs_write(Fcall *req, Fcall *rep)
{
/* write are always sincronous */
LongConverter offset;
union
{
int group;
PosixSignalMask mask;
PosixSignalInfo signal;
char raw[sizeof(PosixSignalInfo)/sizeof(char)];
} buffer;
Fid *f;
Process *p;
ProcessGroup *g;
Signal *s;
if(req->count < 0)
return fs_error(rep, "bad read/write count");
f = fid_find(req->fid);
if(f == nil)
return fs_error(rep, "bad fid");
if(ISCLOSED(f) || f->opened == NP_OREAD)
return fs_error(rep, "i/o error");
rep->count = 0;
offset.value = req->offset;
switch(f->qid.path){
case Qcontrol:
switch(offset.request.command){
case PHSignalForeground:
if(req->count != sizeof(PosixSignalInfo)){
return fs_error(rep, "cannot send signal to foreground: invalid siginfo");
}
if(foreground != nil){
memmove(buffer.raw, req->data, sizeof(PosixSignalInfo));
s = signal_create(&buffer.signal);
group_handle_signal(foreground, s);
signal_free(s);
} else {
DEBUG("fs_write: signal foreground: nothing to do (no foreground process).\n");
}
break;
case PHSignalProcess:
goto SendSignalToProcess;
default:
DEBUG("fs_write: ignoring unknown Qcontrol command %d\n", offset.request.command);
return fs_error(rep, "i/o error");
}
break;
case Qnanny:
if(f->process == nil || !(f->process->flags & NannyProcess))
return fs_error(rep, "i/o error");
switch(offset.request.command){
case PHProcessExited:
/* this is for Nannies only, only for their child */
if(f->process->cpid != offset.request.target)
return fs_error(rep, "i/o error");
p = f->process->child;
if(p != nil)
process_free(p);
break;
case PHSignalProcess:
goto SendSignalToProcess;
default:
DEBUG("fs_write: ignoring unknown Qnanny command %d\n", offset.request.command);
return fs_error(rep, "i/o error");
}
break;
case Qsignals:
/* here we handle commands */
if(f->process == nil || (f->process->flags & NannyProcess))
return fs_error(rep, "i/o error");
switch(offset.request.command){
case PHCallingExec:
if(f->process->flags & CalledExec)
return fs_error(rep, "i/o error");
f->process->flags |= CalledExec;
break;
case PHSetProcessGroup:
if(offset.request.target == 0)
p = f->process;
else {
p = process_find(offset.request.target);
if(p == nil)
return fs_error(rep, "cannot set process group: unknown process");
if(p != f->process
&& p->parent != f->process
&& p->parent != nil && (p->parent->flags&NannyProcess) && p->parent->parent != f->process)
return fs_eperm(req, rep);
}
if(p->flags&SessionLeader)
return fs_eperm(req, rep);
g = nil;
if(req->count == 0){
g = group_create(p);
} else if(req->count == sizeof(int)){
memmove(buffer.raw, req->data, sizeof(int));
if(buffer.group < 0)
return fs_error(rep, "cannot set process group: invalid group");
if(buffer.group == 0 || buffer.group == p->pid)
g = group_create(p);
else
g = group_find(buffer.group);
} else
return fs_error(rep, "cannot set process group: invalid group");
if(g == nil)
return fs_eperm(req, rep);
if(p->group != g)
group_add(g, p);
break;
case PHIgnoreSignal:
if(req->count != sizeof(PosixSignalMask)){
return fs_error(rep, "cannot set ignore mask: invalid mask");
}
memmove(buffer.raw, req->data, sizeof(PosixSignalMask));
process_ignore_signal(f->process, buffer.mask);
break;
case PHEnableSignal:
if(req->count != sizeof(PosixSignalMask)){
return fs_error(rep, "cannot set ignore mask: invalid mask");
}
memmove(buffer.raw, req->data, sizeof(PosixSignalMask));
process_enable_signal(f->process, buffer.mask);
break;
case PHBlockSignals:
if(req->count != sizeof(PosixSignalMask)){
return fs_error(rep, "cannot set block mask: invalid mask");
}
memmove(buffer.raw, req->data, sizeof(PosixSignalMask));
process_block_signals(f->process, buffer.mask);
break;
case PHSignalProcess:
SendSignalToProcess:
if(req->count != sizeof(PosixSignalInfo)){
return fs_error(rep, "cannot send signal to proces: invalid siginfo");
}
memmove(buffer.raw, req->data, sizeof(PosixSignalInfo));
s = signal_create(&buffer.signal);
p = process_find(offset.request.target);
if(p != nil){
if(process_handle_signal(p, s) == -1){
process_free(p);
}
} else {
proc_convert_signal(offset.request.target, s);
}
signal_free(s);
break;
case PHSignalGroup:
if(req->count != sizeof(PosixSignalInfo)){
return fs_error(rep, "cannot send signal to group: invalid siginfo");
}
memmove(buffer.raw, req->data, sizeof(PosixSignalInfo));
s = signal_create(&buffer.signal);
g = group_find(offset.request.target);
if(g != nil)
group_handle_signal(g, s);
signal_free(s);
break;
case PHGetProcMask:
rep->count = f->process->blocked;
break;
case PHGetSessionId:
if(offset.request.target)
p = process_find(offset.request.target);
else
p = f->process;
if(p != nil)
rep->count = sid;
else
return fs_eperm(req, rep);
break;
case PHGetProcessGroupId:
if(offset.request.target)
p = process_find(offset.request.target);
else
p = f->process;
if(p == nil){
// TODO: lookup the group by noteid
return fs_eperm(req, rep);
}
rep->count = p->group->pgid;
break;
case PHSetForegroundGroup:
group_foreground(f->process, offset.request.target);
break;
case PHGetForegroundGroup:
if(foreground)
rep->count = foreground->pgid;
else
rep->count = 2147483647; // max_int;
break;
case PHDetachSession:
if((f->process->flags & SessionLeader)
|| (f->process->flags & GroupLeader)
|| (f->process->flags & NannyProcess)
|| (f->process->flags & Detached))
return fs_eperm(req, rep);
if(f->process->children > 0){
/* we cannot free the process since some
* of its children are still in this
* session.
*/
f->process->flags |= Detached;
} else {
process_free(f->process);
f->process = nil;
}
break;
case PHGetPendingSignals:
rep->count = process_pending_signals(f->process);
break;
case PHSignalForeground: /* only in Qcontrol */
case PHWaitSignals: /* read */
return fs_error(rep, "i/o error");
default:
DEBUG("fs_write: ignoring unknown Qprocess command %d\n", offset.request.command);
return fs_error(rep, "i/o error");
}
break;
default:
return fs_error(rep, "i/o error");
}
rep->type = Rwrite;
return 1;
}
static int
fs_clunk(Fcall *req, Fcall *rep)
{
Fid *f;
PosixSignalInfo siginfo;
Signal *signal;
f = fid_find(req->fid);
if(f != nil){
if(f == external){
DEBUG("fs_serve: external clients gone\n");
status = Unmounted;
} else if(f->qid.path == Qsignals && f->process != nil){
/* f->process might be nil for detached processes */
if(f->process == session_leader && foreground != nil){
// see https://unix.stackexchange.com/questions/407448/
siginfo.si_signo = PosixSIGHUP;
siginfo.si_pid = sid;
signal = signal_create(&siginfo);
group_handle_signal(foreground, signal);
signal_free(signal);
}
if(!(f->process->flags & CalledExec)){
if(f->process == session_leader)
session_leader = nil;
process_free(f->process);
}
f->process = nil;
} else if(f->qid.path == Qcontrol && session_leader != nil){
assert(f->process == nil);
// see https://unix.stackexchange.com/questions/407448/
siginfo.si_signo = PosixSIGHUP;
siginfo.si_pid = sid;
signal = signal_create(&siginfo);
if(process_handle_signal(session_leader, signal) == -1
&&((session_leader->flags & CalledExec)||(session_leader->flags & Detached))){
/* the signal dispatching (aka postnote) failed
* and the session_leader called exec.
* Let assume that the process exited.
*/
process_free(session_leader);
session_leader = nil;
if(foreground)
group_handle_signal(foreground, signal);
}
signal_free(signal);
control_fid = nil;
} else if(f->qid.path == Qnanny) {
assert(f->process != nil);
process_free(f->process);
f->process = nil;
}
f->opened = -1;
}
rep->type = Rclunk;
return 1;
}
static int
fs_stat(Fcall *req, Fcall *rep)
{
Dir d;
Fid *f;
static uint8_t mdata[Maxiosize];
f = fid_find(req->fid);
if(f == nil || f->qid.path >= Nqid){
return fs_error(rep, "bad fid");
}
fillstat(f->qid.path, &d);
rep->type = Rstat;
rep->nstat = convD2M(&d, mdata, Maxiosize);
rep->stat = mdata;
return 1;
}
static int
fs_not_implemented(Fcall *req, Fcall *rep)
{
return fs_eperm(req, rep);
}
static int (*fcalls[])(Fcall *, Fcall *) = {
[Tversion] fs_version,
[Tauth] fs_auth,
[Tattach] fs_attach,
[Tflush] fs_flush,
[Twalk] fs_walk,
[Topen] fs_open,
[Tcreate] fs_create,
[Tread] fs_read,
[Twrite] fs_write,
[Tclunk] fs_clunk,
[Tremove] fs_not_implemented,
[Tstat] fs_stat,
[Twstat] fs_not_implemented,
};
static int
note_forward(void *v, char *s)
{
PosixSignalInfo siginfo;
DEBUG("%d: noted: %s\n", *__libposix_pid, s);
if(strncmp("sys:", s, 4) == 0)
return 0; // this is for us...
// otherwhise it's for the foreground process
memset(&siginfo, 0, sizeof(PosixSignalInfo));
if(!__libposix_note_to_signal(s, &siginfo)){
DEBUG("unable to forward '%s'", s);
return 1;
}
__libposix_sighelper_signal(PHSignalForeground, 0, &siginfo);
return 1;
}
static void
traceassert(char*a)
{
char buf[256];
snprint(buf, sizeof(buf), "assert failed: %s, %#p %#p\n", a,
__builtin_return_address(2),
__builtin_return_address(3)
);
DEBUG(buf);
exits(buf);
}
void
enabledebug(const char *file)
{
if (debugging < 0) {
if((debugging = ocreate(file, OCEXEC|OWRITE, 0666)) < 0)
sysfatal("ocreate(%s) %r", file);
__assert = traceassert;
fmtinstall('F', fcallfmt);
}
}
static int
readmessage(int fd, Fcall *req)
{
int n;
n = read9pmsg(fd, data, Maxiosize);
if(n > 0)
if(convM2S((uint8_t*)data, n, req) == 0){
DEBUG("readmessage: convM2S returns 0\n");
return -1;
} else {
DEBUG("fs_serve: <-%F\n", req);
return 1;
}
if(n < 0){
DEBUG("readmessage: read9pmsg: %r\n");
return -1;
}
return 0;
}
static int
sendmessage(int fd, Fcall *rep)
{
int n;
static uint8_t repdata[Maxiosize];
n = convS2M(rep, repdata, Maxiosize);
if(n == 0) {
DEBUG("sendmessage: convS2M error\n");
return 0;
}
if(write(fd, repdata, n) != n) {
DEBUG("sendmessage: write\n");
return 0;
}
DEBUG("fs_serve: ->%F\n", rep);
return 1;
}
void
fs_serve(int connection)
{
int r, w, syncrep;
Fcall rep;
Fcall *req;
Waiter *wp, **e;
process_create(sid);
req = malloc(sizeof(Fcall)+Maxfdata+((strlen(user)+1)*4));
if(req == nil)
sysfatal("out of memory");
data = malloc(Maxfdata);
if(data == nil)
sysfatal("out of memory");
ftail = &fids;
status = Initializing;
DEBUG("started\n");
do
{
DEBUG("wait for a new request\n");
if((r = readmessage(connection, req)) <= 0){
DEBUG("readmessage returns %d\n", r);
goto FSLoopExit;
}
rep.tag = req->tag;
if(req->type < Tversion || req->type > Twstat)
syncrep = fs_error(&rep, "bad fcall type");
else
syncrep = (*fcalls[req->type])(req, &rep);
if(syncrep){
if((w = sendmessage(connection, &rep)) <= 0){
DEBUG("sendmessage returns %d\n", w);
goto FSLoopExit;
}
}
/* Send available replies to processes waiting in reads */
e = &waiting_process;
while(wp = *e){
if(wp->signal){
rep.type = Rread;
rep.tag = wp->tag;
rep.fid = wp->owner->fd;
rep.count = sizeof(PosixSignalInfo);
rep.data = (char*)&wp->signal->info;
if((w = sendmessage(connection, &rep)) <= 0){
DEBUG("sendmessage for waiting process returns %d\n", w);
goto FSLoopExit;
}
*e = wp->next;
wp->owner->process->waited = 0;
signal_free(wp->signal);
free(wp);
continue;
}
e = &wp->next;
}
/* We can exit (properly) only when the following conditions hold
*
* - the kernel decided that nobody need us anymore
* (status == Unmounted, see fs_clunk and fs_attach)
*
* Thus we exit when the kernel decides that nobody will
* need our services (aka, all the children sharing the
* mountpoint that we serve have exited).
*
* (AND obviously if an unexpected error occurred)
*/
}
while(status < Unmounted);
FSLoopExit:
if(r < 0){
DEBUG("error: fs_serve: readmessage");
sysfatal("fs_serve: readmessage");
}
if(w < 0){
DEBUG("error: fs_serve: sendmessage");
sysfatal("fs_serve: sendmessage");
}
close(connection);
DEBUG("close(%d)\n", connection);
DEBUG("shut down\n");
}
/* Command line / Controller */
static void
tty_from_cons(int fd, int mode)
{
int tmp;
char buf[256];
if(sys_fd2path(fd, buf, sizeof(buf)) < 0)
sysfatal("fd2path: %d", fd);
tmp = strlen(buf);
if(tmp < 9 || strcmp(buf+tmp-9, "/dev/cons") != 0)
return;
tmp = open("/dev/tty", mode);
dup(tmp, fd);
close(tmp);
}
static void
unmount_dev(void)
{
char name[256];
snprint(name, sizeof(name), "#s/posixly.%s.%d", user, sid);
unmount(name, "/dev");
remove(name);
}
static void
post_mount(int fd)
{
/* we want the mount point to be unmount() on session detach,
* so it must have a deterministic name: "#s/posixly.glenda.123"
* is way better than "#|/data".
*/
int f;
char name[256], buf[32];
snprint(name, sizeof(name), "#s/posixly.%s.%d", user, sid);
f = create(name, OWRITE, 0600);
if(f < 0)
sysfatal("create(%s)", name);
sprint(buf, "%d", fd);
if(write(f, buf, strlen(buf)) != strlen(buf))
sysfatal("write(%s)", name);
close(f);
close(fd);
f = open(name, ORDWR);
if(f < 0)
sysfatal("open(%s)", name);
if(mount(f, -1, "/dev", MBEFORE, "", '9') == -1)
sysfatal("mount: %r");
}
static void
open_control_fd(void)
{
while((*__libposix_devsignal = open("/dev/posix/control", OWRITE)) < 0)
sleep(250);
}
void
main(int argc, char *argv[])
{
int p[2], i, sidprovided, fsrun, leaderrun, controlpid;
static PosixSignalInfo sighup;
int devsignal;
sidprovided = 0;
ARGBEGIN{
case 'd':
enabledebug(EARGF(usage()));
break;
case 'p':
sidprovided = 1;
sid = atoi(EARGF(usage()));
break;
default:
usage();
break;
}ARGEND;
if(sid == 0 && argc < 1)
usage();
rfork(RFFDG);
controlpid = getpid();
__libposix_pid = &controlpid;
user = strdup(getuser());
if(sid == 0){
rfork(RFREND|RFNAMEG);
if(access("/dev/tty", AWRITE|AREAD) == 0){
/* replace /dev/cons with /dev/tty */
tty_from_cons(0, OREAD);
tty_from_cons(1, OWRITE);
tty_from_cons(2, ORDWR);
if((i = open("/dev/consctl", OWRITE)) > 0){
write(i, "winchon", 7);
close(i);
}
}
/* fork session leader */
switch(sid = rfork(RFPROC|RFNOTEG|RFFDG)){
case -1:
sysfatal("rfork");
case 0:
while(rendezvous(main, (void*)0x1) == ((void*)~0))
sleep(250);
close(debugging);
jehanne_pexec(strdup(argv[0]), argv);
exits("exec");
default:
break;
}
}
pipe(p);
switch(fspid = rfork(RFPROC|RFMEM|RFCENVG|RFNOTEG|RFNAMEG|RFNOMNT|RFFDG|RFREND)){
case -1:
sysfatal("rfork");
case 0:
close(0);
close(1);
close(2);
close(p[0]);
fspid = getpid();
fs_serve(p[1]);
exits(nil);
default:
break;
}
close(0);
close(1);
close(2);
close(p[1]);
post_mount(p[0]);
__libposix_devsignal = &devsignal;
open_control_fd();
rfork(RFCNAMEG);
if(!atnotify(note_forward, 1)){
fprint(2, "atnotify: %r\n");
exits("atnotify");
}
if(!sidprovided){
/* let the session leader start */
while(rendezvous(main, (void*)0x2) == ((void*)~0))
sleep(250);
/* if we created the session leader, we will wait for it */
leaderrun = 1;
}
/* We wait for fspid because it will be alive until any process
* will be in the namespace providing /dev/posix/.
* Indeed until there are process in such namespace we
* want to forward notes/signals to their foreground group.
*
* Also we wait for sid because if the session leader exits
* we have to send SIGHUP to the foreground group
*/
fsrun = 1;
while(fsrun || leaderrun){
i = waitpid();
if(i == fspid){
DEBUG("file system exited\n");
fsrun = 0;
leaderrun = 0; /* no need to wait for the leader */
} else if(i == sid){
DEBUG("session leader exited\n");
sighup.si_signo = PosixSIGHUP;
__libposix_sighelper_signal(PHSignalForeground, 0, &sighup);
close(devsignal);
unmount_dev();
leaderrun = 0;
}
}
exits(nil);
}