2005-08-08 14:50:13 +02:00
|
|
|
#include "u.h"
|
|
|
|
#include "lib.h"
|
|
|
|
#include "dat.h"
|
|
|
|
#include "fns.h"
|
|
|
|
#include "error.h"
|
|
|
|
|
|
|
|
#include "keyboard.h"
|
|
|
|
|
2006-05-21 19:02:05 +02:00
|
|
|
void (*consdebug)(void) = 0;
|
|
|
|
void (*screenputs)(char*, int) = 0;
|
2005-08-08 14:50:13 +02:00
|
|
|
|
|
|
|
Queue* kbdq; /* unprocessed console input */
|
|
|
|
Queue* lineq; /* processed console input */
|
|
|
|
Queue* serialoq; /* serial console output */
|
|
|
|
Queue* kprintoq; /* console output, for /dev/kprint */
|
2005-11-07 18:34:39 +01:00
|
|
|
long kprintinuse; /* test and set whether /dev/kprint is open */
|
2005-08-08 14:50:13 +02:00
|
|
|
int iprintscreenputs = 0;
|
|
|
|
|
|
|
|
int panicking;
|
|
|
|
|
|
|
|
struct
|
|
|
|
{
|
|
|
|
int exiting;
|
|
|
|
int machs;
|
|
|
|
} active;
|
|
|
|
|
|
|
|
static struct
|
|
|
|
{
|
|
|
|
QLock lk;
|
|
|
|
|
|
|
|
int raw; /* true if we shouldn't process input */
|
|
|
|
int ctl; /* number of opens to the control file */
|
|
|
|
int x; /* index into line */
|
|
|
|
char line[1024]; /* current input line */
|
|
|
|
|
|
|
|
int count;
|
|
|
|
int ctlpoff;
|
|
|
|
|
|
|
|
/* a place to save up characters at interrupt time before dumping them in the queue */
|
|
|
|
Lock lockputc;
|
|
|
|
char istage[1024];
|
|
|
|
char *iw;
|
|
|
|
char *ir;
|
|
|
|
char *ie;
|
|
|
|
} kbd = {
|
|
|
|
{ 0 },
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
{ 0 },
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
{ 0 },
|
|
|
|
{ 0 },
|
|
|
|
kbd.istage,
|
|
|
|
kbd.istage,
|
|
|
|
kbd.istage + sizeof(kbd.istage),
|
|
|
|
};
|
|
|
|
|
|
|
|
char *sysname;
|
|
|
|
vlong fasthz;
|
|
|
|
|
|
|
|
static int readtime(ulong, char*, int);
|
|
|
|
static int readbintime(char*, int);
|
|
|
|
static int writetime(char*, int);
|
|
|
|
static int writebintime(char*, int);
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
CMreboot,
|
|
|
|
CMpanic,
|
|
|
|
};
|
|
|
|
|
|
|
|
Cmdtab rebootmsg[] =
|
|
|
|
{
|
|
|
|
CMreboot, "reboot", 0,
|
|
|
|
CMpanic, "panic", 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
int
|
|
|
|
return0(void *v)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
printinit(void)
|
|
|
|
{
|
2006-05-21 19:02:05 +02:00
|
|
|
lineq = qopen(2*1024, 0, 0, nil);
|
2005-08-08 14:50:13 +02:00
|
|
|
if(lineq == nil)
|
|
|
|
panic("printinit");
|
|
|
|
qnoblock(lineq, 1);
|
|
|
|
|
|
|
|
kbdq = qopen(4*1024, 0, 0, 0);
|
|
|
|
if(kbdq == nil)
|
|
|
|
panic("kbdinit");
|
|
|
|
qnoblock(kbdq, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
consactive(void)
|
|
|
|
{
|
|
|
|
if(serialoq)
|
|
|
|
return qlen(serialoq) > 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
prflush(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
ulong now;
|
|
|
|
|
|
|
|
now = m->ticks;
|
|
|
|
while(consactive())
|
|
|
|
if(m->ticks - now >= HZ)
|
|
|
|
break;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Print a string on the console. Convert \n to \r\n for serial
|
|
|
|
* line consoles. Locking of the queues is left up to the screen
|
|
|
|
* or uart code. Multi-line messages to serial consoles may get
|
|
|
|
* interspersed with other messages.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
putstrn0(char *str, int n, int usewrite)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* if someone is reading /dev/kprint,
|
|
|
|
* put the message there.
|
|
|
|
* if not and there's an attached bit mapped display,
|
|
|
|
* put the message there.
|
|
|
|
*
|
|
|
|
* if there's a serial line being used as a console,
|
|
|
|
* put the message there.
|
|
|
|
*/
|
|
|
|
if(kprintoq != nil && !qisclosed(kprintoq)){
|
|
|
|
if(usewrite)
|
|
|
|
qwrite(kprintoq, str, n);
|
|
|
|
else
|
|
|
|
qiwrite(kprintoq, str, n);
|
2006-05-24 17:24:14 +02:00
|
|
|
}else if(screenputs != 0)
|
2005-08-08 14:50:13 +02:00
|
|
|
screenputs(str, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
putstrn(char *str, int n)
|
|
|
|
{
|
|
|
|
putstrn0(str, n, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int noprint;
|
|
|
|
|
|
|
|
int
|
|
|
|
print(char *fmt, ...)
|
|
|
|
{
|
|
|
|
int n;
|
|
|
|
va_list arg;
|
|
|
|
char buf[PRINTSIZE];
|
|
|
|
|
|
|
|
if(noprint)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
va_start(arg, fmt);
|
|
|
|
n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
|
|
|
|
va_end(arg);
|
|
|
|
putstrn(buf, n);
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
panic(char *fmt, ...)
|
|
|
|
{
|
|
|
|
int n;
|
|
|
|
va_list arg;
|
|
|
|
char buf[PRINTSIZE];
|
|
|
|
|
|
|
|
kprintoq = nil; /* don't try to write to /dev/kprint */
|
|
|
|
|
|
|
|
if(panicking)
|
|
|
|
for(;;);
|
|
|
|
panicking = 1;
|
|
|
|
|
|
|
|
splhi();
|
|
|
|
strcpy(buf, "panic: ");
|
|
|
|
va_start(arg, fmt);
|
|
|
|
n = vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg) - buf;
|
|
|
|
va_end(arg);
|
|
|
|
buf[n] = '\n';
|
|
|
|
uartputs(buf, n+1);
|
|
|
|
if(consdebug)
|
|
|
|
(*consdebug)();
|
|
|
|
spllo();
|
|
|
|
prflush();
|
|
|
|
putstrn(buf, n+1);
|
|
|
|
dumpstack();
|
|
|
|
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
pprint(char *fmt, ...)
|
|
|
|
{
|
|
|
|
int n;
|
|
|
|
Chan *c;
|
|
|
|
va_list arg;
|
|
|
|
char buf[2*PRINTSIZE];
|
|
|
|
|
|
|
|
if(up == nil || up->fgrp == nil)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
c = up->fgrp->fd[2];
|
|
|
|
if(c==0 || (c->mode!=OWRITE && c->mode!=ORDWR))
|
|
|
|
return 0;
|
|
|
|
n = sprint(buf, "%s %lud: ", up->text, up->pid);
|
|
|
|
va_start(arg, fmt);
|
|
|
|
n = vseprint(buf+n, buf+sizeof(buf), fmt, arg) - buf;
|
|
|
|
va_end(arg);
|
|
|
|
|
|
|
|
if(waserror())
|
|
|
|
return 0;
|
|
|
|
devtab[c->type]->write(c, buf, n, c->offset);
|
|
|
|
poperror();
|
|
|
|
|
|
|
|
lock(&c->ref.lk);
|
|
|
|
c->offset += n;
|
|
|
|
unlock(&c->ref.lk);
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
echoscreen(char *buf, int n)
|
|
|
|
{
|
|
|
|
char *e, *p;
|
|
|
|
char ebuf[128];
|
|
|
|
int x;
|
|
|
|
|
|
|
|
p = ebuf;
|
|
|
|
e = ebuf + sizeof(ebuf) - 4;
|
|
|
|
while(n-- > 0){
|
|
|
|
if(p >= e){
|
|
|
|
screenputs(ebuf, p - ebuf);
|
|
|
|
p = ebuf;
|
|
|
|
}
|
|
|
|
x = *buf++;
|
|
|
|
if(x == 0x15){
|
|
|
|
*p++ = '^';
|
|
|
|
*p++ = 'U';
|
|
|
|
*p++ = '\n';
|
|
|
|
} else
|
|
|
|
*p++ = x;
|
|
|
|
}
|
|
|
|
if(p != ebuf)
|
|
|
|
screenputs(ebuf, p - ebuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
echoserialoq(char *buf, int n)
|
|
|
|
{
|
|
|
|
char *e, *p;
|
|
|
|
char ebuf[128];
|
|
|
|
int x;
|
|
|
|
|
|
|
|
p = ebuf;
|
|
|
|
e = ebuf + sizeof(ebuf) - 4;
|
|
|
|
while(n-- > 0){
|
|
|
|
if(p >= e){
|
|
|
|
qiwrite(serialoq, ebuf, p - ebuf);
|
|
|
|
p = ebuf;
|
|
|
|
}
|
|
|
|
x = *buf++;
|
|
|
|
if(x == '\n'){
|
|
|
|
*p++ = '\r';
|
|
|
|
*p++ = '\n';
|
|
|
|
} else if(x == 0x15){
|
|
|
|
*p++ = '^';
|
|
|
|
*p++ = 'U';
|
|
|
|
*p++ = '\n';
|
|
|
|
} else
|
|
|
|
*p++ = x;
|
|
|
|
}
|
|
|
|
if(p != ebuf)
|
|
|
|
qiwrite(serialoq, ebuf, p - ebuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
echo(char *buf, int n)
|
|
|
|
{
|
2005-11-07 18:13:41 +01:00
|
|
|
static int ctrlt;
|
2005-08-08 14:50:13 +02:00
|
|
|
int x;
|
|
|
|
char *e, *p;
|
|
|
|
|
|
|
|
e = buf+n;
|
|
|
|
for(p = buf; p < e; p++){
|
|
|
|
switch(*p){
|
|
|
|
case 0x10: /* ^P */
|
|
|
|
if(cpuserver && !kbd.ctlpoff){
|
|
|
|
active.exiting = 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x14: /* ^T */
|
|
|
|
ctrlt++;
|
|
|
|
if(ctrlt > 2)
|
|
|
|
ctrlt = 2;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ctrlt != 2)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* ^T escapes */
|
|
|
|
ctrlt = 0;
|
|
|
|
switch(*p){
|
|
|
|
case 'S':
|
|
|
|
x = splhi();
|
|
|
|
dumpstack();
|
|
|
|
procdump();
|
|
|
|
splx(x);
|
|
|
|
return;
|
|
|
|
case 's':
|
|
|
|
dumpstack();
|
|
|
|
return;
|
|
|
|
case 'x':
|
|
|
|
xsummary();
|
|
|
|
ixsummary();
|
|
|
|
mallocsummary();
|
|
|
|
pagersummary();
|
|
|
|
return;
|
|
|
|
case 'd':
|
2006-05-21 19:02:05 +02:00
|
|
|
if(consdebug == 0)
|
2005-08-08 14:50:13 +02:00
|
|
|
consdebug = rdb;
|
|
|
|
else
|
2006-05-21 19:02:05 +02:00
|
|
|
consdebug = 0;
|
2005-08-08 14:50:13 +02:00
|
|
|
print("consdebug now 0x%p\n", consdebug);
|
|
|
|
return;
|
|
|
|
case 'D':
|
2006-05-21 19:02:05 +02:00
|
|
|
if(consdebug == 0)
|
2005-08-08 14:50:13 +02:00
|
|
|
consdebug = rdb;
|
|
|
|
consdebug();
|
|
|
|
return;
|
|
|
|
case 'p':
|
|
|
|
x = spllo();
|
|
|
|
procdump();
|
|
|
|
splx(x);
|
|
|
|
return;
|
|
|
|
case 'q':
|
|
|
|
scheddump();
|
|
|
|
return;
|
|
|
|
case 'k':
|
|
|
|
killbig();
|
|
|
|
return;
|
|
|
|
case 'r':
|
|
|
|
exit(0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
qproduce(kbdq, buf, n);
|
|
|
|
if(kbd.raw)
|
|
|
|
return;
|
2006-05-21 19:02:05 +02:00
|
|
|
if(screenputs != 0)
|
2005-08-08 14:50:13 +02:00
|
|
|
echoscreen(buf, n);
|
|
|
|
if(serialoq)
|
|
|
|
echoserialoq(buf, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called by a uart interrupt for console input.
|
|
|
|
*
|
|
|
|
* turn '\r' into '\n' before putting it into the queue.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
kbdcr2nl(Queue *q, int ch)
|
|
|
|
{
|
|
|
|
char *next;
|
|
|
|
|
|
|
|
USED(q);
|
|
|
|
ilock(&kbd.lockputc); /* just a mutex */
|
|
|
|
if(ch == '\r' && !kbd.raw)
|
|
|
|
ch = '\n';
|
|
|
|
next = kbd.iw+1;
|
|
|
|
if(next >= kbd.ie)
|
|
|
|
next = kbd.istage;
|
|
|
|
if(next != kbd.ir){
|
|
|
|
*kbd.iw = ch;
|
|
|
|
kbd.iw = next;
|
|
|
|
}
|
|
|
|
iunlock(&kbd.lockputc);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static
|
|
|
|
void
|
|
|
|
_kbdputc(int c)
|
|
|
|
{
|
|
|
|
Rune r;
|
|
|
|
char buf[UTFmax];
|
|
|
|
int n;
|
|
|
|
|
|
|
|
r = c;
|
|
|
|
n = runetochar(buf, &r);
|
|
|
|
if(n == 0)
|
|
|
|
return;
|
|
|
|
echo(buf, n);
|
|
|
|
// kbd.c = r;
|
|
|
|
// qproduce(kbdq, buf, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* _kbdputc, but with compose translation */
|
|
|
|
int
|
|
|
|
kbdputc(Queue *q, int c)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
static int collecting, nk;
|
|
|
|
static Rune kc[5];
|
|
|
|
|
|
|
|
if(c == Kalt){
|
|
|
|
collecting = 1;
|
|
|
|
nk = 0;
|
2005-10-31 16:30:23 +01:00
|
|
|
return 0;
|
2005-08-08 14:50:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if(!collecting){
|
|
|
|
_kbdputc(c);
|
2005-10-31 16:30:23 +01:00
|
|
|
return 0;
|
2005-08-08 14:50:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
kc[nk++] = c;
|
|
|
|
c = latin1(kc, nk);
|
|
|
|
if(c < -1) /* need more keystrokes */
|
2005-11-02 16:20:27 +01:00
|
|
|
return 0;
|
2005-08-08 14:50:13 +02:00
|
|
|
if(c != -1) /* valid sequence */
|
|
|
|
_kbdputc(c);
|
|
|
|
else
|
|
|
|
for(i=0; i<nk; i++)
|
|
|
|
_kbdputc(kc[i]);
|
|
|
|
nk = 0;
|
|
|
|
collecting = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum{
|
|
|
|
Qdir,
|
|
|
|
Qbintime,
|
|
|
|
Qcons,
|
|
|
|
Qconsctl,
|
|
|
|
Qcpunote,
|
|
|
|
Qcputime,
|
|
|
|
Qdrivers,
|
|
|
|
Qkprint,
|
|
|
|
Qhostdomain,
|
|
|
|
Qhostowner,
|
|
|
|
Qnull,
|
|
|
|
Qosversion,
|
|
|
|
Qpgrpid,
|
|
|
|
Qpid,
|
|
|
|
Qppid,
|
|
|
|
Qrandom,
|
|
|
|
Qreboot,
|
|
|
|
Qsecstore,
|
2006-05-22 01:32:55 +02:00
|
|
|
Qshowfile,
|
2005-08-08 14:50:13 +02:00
|
|
|
Qsnarf,
|
|
|
|
Qswap,
|
|
|
|
Qsysname,
|
|
|
|
Qsysstat,
|
|
|
|
Qtime,
|
|
|
|
Quser,
|
|
|
|
Qzero,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
VLNUMSIZE= 22,
|
|
|
|
};
|
|
|
|
|
|
|
|
static Dirtab consdir[]={
|
|
|
|
".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
|
|
|
|
"bintime", {Qbintime}, 24, 0664,
|
|
|
|
"cons", {Qcons}, 0, 0660,
|
|
|
|
"consctl", {Qconsctl}, 0, 0220,
|
|
|
|
"cpunote", {Qcpunote}, 0, 0444,
|
|
|
|
"cputime", {Qcputime}, 6*NUMSIZE, 0444,
|
|
|
|
"drivers", {Qdrivers}, 0, 0444,
|
|
|
|
"hostdomain", {Qhostdomain}, DOMLEN, 0664,
|
|
|
|
"hostowner", {Qhostowner}, 0, 0664,
|
|
|
|
"kprint", {Qkprint, 0, QTEXCL}, 0, DMEXCL|0440,
|
|
|
|
"null", {Qnull}, 0, 0666,
|
|
|
|
"osversion", {Qosversion}, 0, 0444,
|
|
|
|
"pgrpid", {Qpgrpid}, NUMSIZE, 0444,
|
|
|
|
"pid", {Qpid}, NUMSIZE, 0444,
|
|
|
|
"ppid", {Qppid}, NUMSIZE, 0444,
|
|
|
|
"random", {Qrandom}, 0, 0444,
|
|
|
|
"reboot", {Qreboot}, 0, 0664,
|
|
|
|
"secstore", {Qsecstore}, 0, 0666,
|
2006-05-22 01:32:55 +02:00
|
|
|
"showfile", {Qshowfile}, 0, 0220,
|
2005-08-08 14:50:13 +02:00
|
|
|
"snarf", {Qsnarf}, 0, 0666,
|
|
|
|
"swap", {Qswap}, 0, 0664,
|
|
|
|
"sysname", {Qsysname}, 0, 0664,
|
|
|
|
"sysstat", {Qsysstat}, 0, 0666,
|
|
|
|
"time", {Qtime}, NUMSIZE+3*VLNUMSIZE, 0664,
|
|
|
|
"user", {Quser}, 0, 0666,
|
|
|
|
"zero", {Qzero}, 0, 0444,
|
|
|
|
};
|
|
|
|
|
|
|
|
char secstorebuf[65536];
|
|
|
|
Dirtab *secstoretab = &consdir[Qsecstore];
|
|
|
|
Dirtab *snarftab = &consdir[Qsnarf];
|
|
|
|
|
|
|
|
int
|
|
|
|
readnum(ulong off, char *buf, ulong n, ulong val, int size)
|
|
|
|
{
|
|
|
|
char tmp[64];
|
|
|
|
|
|
|
|
snprint(tmp, sizeof(tmp), "%*.0lud", size-1, val);
|
|
|
|
tmp[size-1] = ' ';
|
|
|
|
if(off >= size)
|
|
|
|
return 0;
|
|
|
|
if(off+n > size)
|
|
|
|
n = size-off;
|
|
|
|
memmove(buf, tmp+off, n);
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
readstr(ulong off, char *buf, ulong n, char *str)
|
|
|
|
{
|
|
|
|
int size;
|
|
|
|
|
|
|
|
size = strlen(str);
|
|
|
|
if(off >= size)
|
|
|
|
return 0;
|
|
|
|
if(off+n > size)
|
|
|
|
n = size-off;
|
|
|
|
memmove(buf, str+off, n);
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
consinit(void)
|
|
|
|
{
|
|
|
|
todinit();
|
|
|
|
randominit();
|
|
|
|
/*
|
|
|
|
* at 115200 baud, the 1024 char buffer takes 56 ms to process,
|
|
|
|
* processing it every 22 ms should be fine
|
|
|
|
*/
|
|
|
|
/* addclock0link(kbdputcclock, 22); */
|
|
|
|
}
|
|
|
|
|
|
|
|
static Chan*
|
|
|
|
consattach(char *spec)
|
|
|
|
{
|
|
|
|
return devattach('c', spec);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Walkqid*
|
|
|
|
conswalk(Chan *c, Chan *nc, char **name, int nname)
|
|
|
|
{
|
|
|
|
return devwalk(c, nc, name,nname, consdir, nelem(consdir), devgen);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
consstat(Chan *c, uchar *dp, int n)
|
|
|
|
{
|
|
|
|
return devstat(c, dp, n, consdir, nelem(consdir), devgen);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Chan*
|
|
|
|
consopen(Chan *c, int omode)
|
|
|
|
{
|
|
|
|
c->aux = nil;
|
|
|
|
c = devopen(c, omode, consdir, nelem(consdir), devgen);
|
|
|
|
switch((ulong)c->qid.path){
|
|
|
|
case Qconsctl:
|
|
|
|
qlock(&kbd.lk);
|
|
|
|
kbd.ctl++;
|
|
|
|
qunlock(&kbd.lk);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qkprint:
|
|
|
|
if(tas(&kprintinuse) != 0){
|
|
|
|
c->flag &= ~COPEN;
|
|
|
|
error(Einuse);
|
|
|
|
}
|
|
|
|
if(kprintoq == nil){
|
|
|
|
kprintoq = qopen(8*1024, Qcoalesce, 0, 0);
|
|
|
|
if(kprintoq == nil){
|
|
|
|
c->flag &= ~COPEN;
|
|
|
|
error(Enomem);
|
|
|
|
}
|
|
|
|
qnoblock(kprintoq, 1);
|
|
|
|
}else
|
|
|
|
qreopen(kprintoq);
|
|
|
|
c->iounit = qiomaxatomic;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qsecstore:
|
|
|
|
if(omode == ORDWR)
|
|
|
|
error(Eperm);
|
|
|
|
if(omode != OREAD)
|
|
|
|
memset(secstorebuf, 0, sizeof secstorebuf);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qsnarf:
|
|
|
|
if(omode == ORDWR)
|
|
|
|
error(Eperm);
|
|
|
|
if(omode == OREAD)
|
|
|
|
c->aux = strdup("");
|
|
|
|
else
|
|
|
|
c->aux = mallocz(SnarfSize, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
consclose(Chan *c)
|
|
|
|
{
|
|
|
|
switch((ulong)c->qid.path){
|
|
|
|
/* last close of control file turns off raw */
|
|
|
|
case Qconsctl:
|
|
|
|
if(c->flag&COPEN){
|
|
|
|
qlock(&kbd.lk);
|
|
|
|
if(--kbd.ctl == 0)
|
|
|
|
kbd.raw = 0;
|
|
|
|
qunlock(&kbd.lk);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* close of kprint allows other opens */
|
|
|
|
case Qkprint:
|
|
|
|
if(c->flag & COPEN){
|
|
|
|
kprintinuse = 0;
|
|
|
|
qhangup(kprintoq, nil);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qsnarf:
|
|
|
|
if(c->mode == OWRITE)
|
|
|
|
clipwrite(c->aux);
|
|
|
|
free(c->aux);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static long
|
|
|
|
consread(Chan *c, void *buf, long n, vlong off)
|
|
|
|
{
|
2005-11-07 18:13:41 +01:00
|
|
|
char *b;
|
2005-08-08 14:50:13 +02:00
|
|
|
char tmp[128]; /* must be >= 6*NUMSIZE */
|
|
|
|
char *cbuf = buf;
|
2005-11-07 18:13:41 +01:00
|
|
|
int ch, i, eol;
|
2005-08-08 14:50:13 +02:00
|
|
|
vlong offset = off;
|
|
|
|
|
|
|
|
if(n <= 0)
|
|
|
|
return n;
|
|
|
|
switch((ulong)c->qid.path){
|
|
|
|
case Qdir:
|
|
|
|
return devdirread(c, buf, n, consdir, nelem(consdir), devgen);
|
|
|
|
|
|
|
|
case Qcons:
|
|
|
|
qlock(&kbd.lk);
|
|
|
|
if(waserror()) {
|
|
|
|
qunlock(&kbd.lk);
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
if(kbd.raw) {
|
|
|
|
if(qcanread(lineq))
|
|
|
|
n = qread(lineq, buf, n);
|
|
|
|
else {
|
|
|
|
/* read as much as possible */
|
|
|
|
do {
|
|
|
|
i = qread(kbdq, cbuf, n);
|
|
|
|
cbuf += i;
|
|
|
|
n -= i;
|
|
|
|
} while (n>0 && qcanread(kbdq));
|
|
|
|
n = cbuf - (char*)buf;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
while(!qcanread(lineq)) {
|
|
|
|
qread(kbdq, &kbd.line[kbd.x], 1);
|
|
|
|
ch = kbd.line[kbd.x];
|
|
|
|
eol = 0;
|
|
|
|
switch(ch){
|
|
|
|
case '\b':
|
|
|
|
if(kbd.x)
|
|
|
|
kbd.x--;
|
|
|
|
break;
|
|
|
|
case 0x15:
|
|
|
|
kbd.x = 0;
|
|
|
|
break;
|
|
|
|
case '\n':
|
|
|
|
case 0x04:
|
|
|
|
eol = 1;
|
|
|
|
default:
|
|
|
|
kbd.line[kbd.x++] = ch;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(kbd.x == sizeof(kbd.line) || eol){
|
|
|
|
if(ch == 0x04)
|
|
|
|
kbd.x--;
|
|
|
|
qwrite(lineq, kbd.line, kbd.x);
|
|
|
|
kbd.x = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
n = qread(lineq, buf, n);
|
|
|
|
}
|
|
|
|
qunlock(&kbd.lk);
|
|
|
|
poperror();
|
|
|
|
return n;
|
|
|
|
|
|
|
|
case Qcpunote:
|
|
|
|
sleep(&up->sleep, return0, nil);
|
|
|
|
|
|
|
|
case Qcputime:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case Qkprint:
|
|
|
|
return qread(kprintoq, buf, n);
|
|
|
|
|
|
|
|
case Qpgrpid:
|
|
|
|
return readnum((ulong)offset, buf, n, up->pgrp->pgrpid, NUMSIZE);
|
|
|
|
|
|
|
|
case Qpid:
|
|
|
|
return readnum((ulong)offset, buf, n, up->pid, NUMSIZE);
|
|
|
|
|
|
|
|
case Qppid:
|
|
|
|
return readnum((ulong)offset, buf, n, up->parentpid, NUMSIZE);
|
|
|
|
|
|
|
|
case Qtime:
|
|
|
|
return readtime((ulong)offset, buf, n);
|
|
|
|
|
|
|
|
case Qbintime:
|
|
|
|
return readbintime(buf, n);
|
|
|
|
|
|
|
|
case Qhostowner:
|
|
|
|
return readstr((ulong)offset, buf, n, eve);
|
|
|
|
|
|
|
|
case Qhostdomain:
|
|
|
|
return readstr((ulong)offset, buf, n, hostdomain);
|
|
|
|
|
|
|
|
case Quser:
|
|
|
|
return readstr((ulong)offset, buf, n, up->user);
|
|
|
|
|
|
|
|
case Qnull:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case Qsnarf:
|
|
|
|
if(offset == 0){
|
|
|
|
free(c->aux);
|
|
|
|
c->aux = clipread();
|
|
|
|
}
|
|
|
|
if(c->aux == nil)
|
|
|
|
return 0;
|
|
|
|
return readstr(offset, buf, n, c->aux);
|
|
|
|
|
|
|
|
case Qsecstore:
|
|
|
|
return readstr(offset, buf, n, secstorebuf);
|
|
|
|
|
|
|
|
case Qsysstat:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case Qswap:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case Qsysname:
|
|
|
|
if(sysname == nil)
|
|
|
|
return 0;
|
|
|
|
return readstr((ulong)offset, buf, n, sysname);
|
|
|
|
|
|
|
|
case Qrandom:
|
|
|
|
return randomread(buf, n);
|
|
|
|
|
|
|
|
case Qdrivers:
|
|
|
|
b = malloc(READSTR);
|
|
|
|
if(b == nil)
|
|
|
|
error(Enomem);
|
|
|
|
n = 0;
|
|
|
|
for(i = 0; devtab[i] != nil; i++)
|
|
|
|
n += snprint(b+n, READSTR-n, "#%C %s\n", devtab[i]->dc, devtab[i]->name);
|
|
|
|
if(waserror()){
|
|
|
|
free(b);
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
n = readstr((ulong)offset, buf, n, b);
|
|
|
|
free(b);
|
|
|
|
poperror();
|
|
|
|
return n;
|
|
|
|
|
|
|
|
case Qzero:
|
|
|
|
memset(buf, 0, n);
|
|
|
|
return n;
|
|
|
|
|
|
|
|
case Qosversion:
|
|
|
|
snprint(tmp, sizeof tmp, "2000");
|
|
|
|
n = readstr((ulong)offset, buf, n, tmp);
|
|
|
|
return n;
|
|
|
|
|
|
|
|
default:
|
|
|
|
print("consread 0x%llux\n", c->qid.path);
|
|
|
|
error(Egreg);
|
|
|
|
}
|
|
|
|
return -1; /* never reached */
|
|
|
|
}
|
|
|
|
|
|
|
|
static long
|
|
|
|
conswrite(Chan *c, void *va, long n, vlong off)
|
|
|
|
{
|
|
|
|
char buf[256];
|
|
|
|
long l, bp;
|
|
|
|
char *a = va;
|
2005-11-07 18:13:41 +01:00
|
|
|
int fd;
|
2005-08-08 14:50:13 +02:00
|
|
|
Chan *swc;
|
|
|
|
ulong offset = off;
|
|
|
|
Cmdbuf *cb;
|
|
|
|
Cmdtab *ct;
|
|
|
|
|
|
|
|
switch((ulong)c->qid.path){
|
|
|
|
case Qcons:
|
|
|
|
/*
|
|
|
|
* Can't page fault in putstrn, so copy the data locally.
|
|
|
|
*/
|
|
|
|
l = n;
|
|
|
|
while(l > 0){
|
|
|
|
bp = l;
|
|
|
|
if(bp > sizeof buf)
|
|
|
|
bp = sizeof buf;
|
|
|
|
memmove(buf, a, bp);
|
|
|
|
putstrn0(buf, bp, 1);
|
|
|
|
a += bp;
|
|
|
|
l -= bp;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qconsctl:
|
|
|
|
if(n >= sizeof(buf))
|
|
|
|
n = sizeof(buf)-1;
|
|
|
|
strncpy(buf, a, n);
|
|
|
|
buf[n] = 0;
|
|
|
|
for(a = buf; a;){
|
|
|
|
if(strncmp(a, "rawon", 5) == 0){
|
|
|
|
qlock(&kbd.lk);
|
|
|
|
if(kbd.x){
|
|
|
|
qwrite(kbdq, kbd.line, kbd.x);
|
|
|
|
kbd.x = 0;
|
|
|
|
}
|
|
|
|
kbd.raw = 1;
|
|
|
|
qunlock(&kbd.lk);
|
|
|
|
} else if(strncmp(a, "rawoff", 6) == 0){
|
|
|
|
qlock(&kbd.lk);
|
|
|
|
kbd.raw = 0;
|
|
|
|
kbd.x = 0;
|
|
|
|
qunlock(&kbd.lk);
|
|
|
|
} else if(strncmp(a, "ctlpon", 6) == 0){
|
|
|
|
kbd.ctlpoff = 0;
|
|
|
|
} else if(strncmp(a, "ctlpoff", 7) == 0){
|
|
|
|
kbd.ctlpoff = 1;
|
|
|
|
}
|
2005-11-07 18:13:41 +01:00
|
|
|
if((a = strchr(a, ' ')))
|
2005-08-08 14:50:13 +02:00
|
|
|
a++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qtime:
|
|
|
|
if(!iseve())
|
|
|
|
error(Eperm);
|
|
|
|
return writetime(a, n);
|
|
|
|
|
|
|
|
case Qbintime:
|
|
|
|
if(!iseve())
|
|
|
|
error(Eperm);
|
|
|
|
return writebintime(a, n);
|
|
|
|
|
|
|
|
case Qhostowner:
|
|
|
|
return hostownerwrite(a, n);
|
|
|
|
|
|
|
|
case Qhostdomain:
|
|
|
|
return hostdomainwrite(a, n);
|
|
|
|
|
|
|
|
case Quser:
|
|
|
|
return userwrite(a, n);
|
|
|
|
|
|
|
|
case Qnull:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qreboot:
|
|
|
|
if(!iseve())
|
|
|
|
error(Eperm);
|
|
|
|
cb = parsecmd(a, n);
|
|
|
|
|
|
|
|
if(waserror()) {
|
|
|
|
free(cb);
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
ct = lookupcmd(cb, rebootmsg, nelem(rebootmsg));
|
|
|
|
switch(ct->index) {
|
|
|
|
case CMreboot:
|
|
|
|
rebootcmd(cb->nf-1, cb->f+1);
|
|
|
|
break;
|
|
|
|
case CMpanic:
|
|
|
|
panic("/dev/reboot");
|
|
|
|
}
|
|
|
|
poperror();
|
|
|
|
free(cb);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qsecstore:
|
|
|
|
if(offset >= sizeof secstorebuf || offset+n+1 >= sizeof secstorebuf)
|
|
|
|
error(Etoobig);
|
|
|
|
secstoretab->qid.vers++;
|
|
|
|
memmove(secstorebuf+offset, va, n);
|
|
|
|
return n;
|
|
|
|
|
2006-05-22 01:32:55 +02:00
|
|
|
case Qshowfile:
|
|
|
|
return showfilewrite(a, n);
|
|
|
|
|
2005-08-08 14:50:13 +02:00
|
|
|
case Qsnarf:
|
|
|
|
if(offset >= SnarfSize || offset+n >= SnarfSize)
|
|
|
|
error(Etoobig);
|
|
|
|
snarftab->qid.vers++;
|
|
|
|
memmove((uchar*)c->aux+offset, va, n);
|
|
|
|
return n;
|
|
|
|
|
|
|
|
case Qsysstat:
|
|
|
|
n = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qswap:
|
|
|
|
if(n >= sizeof buf)
|
|
|
|
error(Egreg);
|
|
|
|
memmove(buf, va, n); /* so we can NUL-terminate */
|
|
|
|
buf[n] = 0;
|
|
|
|
/* start a pager if not already started */
|
|
|
|
if(strncmp(buf, "start", 5) == 0){
|
|
|
|
kickpager();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(cpuserver && !iseve())
|
|
|
|
error(Eperm);
|
|
|
|
if(buf[0]<'0' || '9'<buf[0])
|
|
|
|
error(Ebadarg);
|
|
|
|
fd = strtoul(buf, 0, 0);
|
|
|
|
swc = fdtochan(fd, -1, 1, 1);
|
|
|
|
setswapchan(swc);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qsysname:
|
|
|
|
if(offset != 0)
|
|
|
|
error(Ebadarg);
|
|
|
|
if(n <= 0 || n >= sizeof buf)
|
|
|
|
error(Ebadarg);
|
|
|
|
strncpy(buf, a, n);
|
|
|
|
buf[n] = 0;
|
|
|
|
if(buf[n-1] == '\n')
|
|
|
|
buf[n-1] = 0;
|
|
|
|
kstrdup(&sysname, buf);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
print("conswrite: 0x%llux\n", c->qid.path);
|
|
|
|
error(Egreg);
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
Dev consdevtab = {
|
|
|
|
'c',
|
|
|
|
"cons",
|
|
|
|
|
|
|
|
devreset,
|
|
|
|
consinit,
|
|
|
|
devshutdown,
|
|
|
|
consattach,
|
|
|
|
conswalk,
|
|
|
|
consstat,
|
|
|
|
consopen,
|
|
|
|
devcreate,
|
|
|
|
consclose,
|
|
|
|
consread,
|
|
|
|
devbread,
|
|
|
|
conswrite,
|
|
|
|
devbwrite,
|
|
|
|
devremove,
|
|
|
|
devwstat,
|
|
|
|
};
|
|
|
|
|
2005-10-31 16:30:23 +01:00
|
|
|
static uvlong uvorder = (uvlong) 0x0001020304050607ULL;
|
2005-08-08 14:50:13 +02:00
|
|
|
|
|
|
|
static uchar*
|
|
|
|
le2vlong(vlong *to, uchar *f)
|
|
|
|
{
|
|
|
|
uchar *t, *o;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
t = (uchar*)to;
|
|
|
|
o = (uchar*)&uvorder;
|
|
|
|
for(i = 0; i < sizeof(vlong); i++)
|
|
|
|
t[o[i]] = f[i];
|
|
|
|
return f+sizeof(vlong);
|
|
|
|
}
|
|
|
|
|
|
|
|
static uchar*
|
|
|
|
vlong2le(uchar *t, vlong from)
|
|
|
|
{
|
|
|
|
uchar *f, *o;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
f = (uchar*)&from;
|
|
|
|
o = (uchar*)&uvorder;
|
|
|
|
for(i = 0; i < sizeof(vlong); i++)
|
|
|
|
t[i] = f[o[i]];
|
|
|
|
return t+sizeof(vlong);
|
|
|
|
}
|
|
|
|
|
|
|
|
static long order = 0x00010203;
|
|
|
|
|
|
|
|
static uchar*
|
|
|
|
le2long(long *to, uchar *f)
|
|
|
|
{
|
|
|
|
uchar *t, *o;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
t = (uchar*)to;
|
|
|
|
o = (uchar*)ℴ
|
|
|
|
for(i = 0; i < sizeof(long); i++)
|
|
|
|
t[o[i]] = f[i];
|
|
|
|
return f+sizeof(long);
|
|
|
|
}
|
|
|
|
|
2005-11-07 18:13:41 +01:00
|
|
|
/*
|
2005-08-08 14:50:13 +02:00
|
|
|
static uchar*
|
|
|
|
long2le(uchar *t, long from)
|
|
|
|
{
|
|
|
|
uchar *f, *o;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
f = (uchar*)&from;
|
|
|
|
o = (uchar*)ℴ
|
|
|
|
for(i = 0; i < sizeof(long); i++)
|
|
|
|
t[i] = f[o[i]];
|
|
|
|
return t+sizeof(long);
|
|
|
|
}
|
2005-11-07 18:13:41 +01:00
|
|
|
*/
|
2005-08-08 14:50:13 +02:00
|
|
|
|
|
|
|
char *Ebadtimectl = "bad time control";
|
|
|
|
|
|
|
|
/*
|
|
|
|
* like the old #c/time but with added info. Return
|
|
|
|
*
|
|
|
|
* secs nanosecs fastticks fasthz
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
readtime(ulong off, char *buf, int n)
|
|
|
|
{
|
|
|
|
vlong nsec, ticks;
|
|
|
|
long sec;
|
|
|
|
char str[7*NUMSIZE];
|
|
|
|
|
|
|
|
nsec = todget(&ticks);
|
|
|
|
if(fasthz == (vlong)0)
|
|
|
|
fastticks((uvlong*)&fasthz);
|
|
|
|
sec = nsec/((uvlong) 1000000000);
|
|
|
|
snprint(str, sizeof(str), "%*.0lud %*.0llud %*.0llud %*.0llud ",
|
|
|
|
NUMSIZE-1, sec,
|
|
|
|
VLNUMSIZE-1, nsec,
|
|
|
|
VLNUMSIZE-1, ticks,
|
|
|
|
VLNUMSIZE-1, fasthz);
|
|
|
|
return readstr(off, buf, n, str);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set the time in seconds
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
writetime(char *buf, int n)
|
|
|
|
{
|
|
|
|
char b[13];
|
|
|
|
long i;
|
|
|
|
vlong now;
|
|
|
|
|
|
|
|
if(n >= sizeof(b))
|
|
|
|
error(Ebadtimectl);
|
|
|
|
strncpy(b, buf, n);
|
|
|
|
b[n] = 0;
|
|
|
|
i = strtol(b, 0, 0);
|
|
|
|
if(i <= 0)
|
|
|
|
error(Ebadtimectl);
|
|
|
|
now = i*((vlong) 1000000000);
|
|
|
|
todset(now, 0, 0);
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* read binary time info. all numbers are little endian.
|
|
|
|
* ticks and nsec are syncronized.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
readbintime(char *buf, int n)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
vlong nsec, ticks;
|
|
|
|
uchar *b = (uchar*)buf;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
if(fasthz == (vlong)0)
|
|
|
|
fastticks((uvlong*)&fasthz);
|
|
|
|
nsec = todget(&ticks);
|
|
|
|
if(n >= 3*sizeof(uvlong)){
|
|
|
|
vlong2le(b+2*sizeof(uvlong), fasthz);
|
|
|
|
i += sizeof(uvlong);
|
|
|
|
}
|
|
|
|
if(n >= 2*sizeof(uvlong)){
|
|
|
|
vlong2le(b+sizeof(uvlong), ticks);
|
|
|
|
i += sizeof(uvlong);
|
|
|
|
}
|
|
|
|
if(n >= 8){
|
|
|
|
vlong2le(b, nsec);
|
|
|
|
i += sizeof(vlong);
|
|
|
|
}
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set any of the following
|
|
|
|
* - time in nsec
|
|
|
|
* - nsec trim applied over some seconds
|
|
|
|
* - clock frequency
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
writebintime(char *buf, int n)
|
|
|
|
{
|
|
|
|
uchar *p;
|
|
|
|
vlong delta;
|
|
|
|
long period;
|
|
|
|
|
|
|
|
n--;
|
|
|
|
p = (uchar*)buf + 1;
|
|
|
|
switch(*buf){
|
|
|
|
case 'n':
|
|
|
|
if(n < sizeof(vlong))
|
|
|
|
error(Ebadtimectl);
|
|
|
|
le2vlong(&delta, p);
|
|
|
|
todset(delta, 0, 0);
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
if(n < sizeof(vlong)+sizeof(long))
|
|
|
|
error(Ebadtimectl);
|
|
|
|
p = le2vlong(&delta, p);
|
|
|
|
le2long(&period, p);
|
|
|
|
todset(-1, delta, period);
|
|
|
|
break;
|
|
|
|
case 'f':
|
|
|
|
if(n < sizeof(uvlong))
|
|
|
|
error(Ebadtimectl);
|
|
|
|
le2vlong(&fasthz, p);
|
|
|
|
todsetfreq(fasthz);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-11-04 16:36:40 +01:00
|
|
|
int
|
|
|
|
iprint(char *fmt, ...)
|
|
|
|
{
|
|
|
|
int n, s;
|
|
|
|
va_list arg;
|
|
|
|
char buf[PRINTSIZE];
|
|
|
|
|
|
|
|
s = splhi();
|
|
|
|
va_start(arg, fmt);
|
|
|
|
n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
|
|
|
|
va_end(arg);
|
2006-05-24 17:24:14 +02:00
|
|
|
if(screenputs != 0 && iprintscreenputs)
|
2005-11-04 16:36:40 +01:00
|
|
|
screenputs(buf, n);
|
|
|
|
#undef write
|
|
|
|
write(2, buf, n);
|
|
|
|
splx(s);
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|