Plan 9 from Bell Labs’s /usr/web/sources/contrib/blstuart/θfs/cons.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * Copyright (c) 2013, Coraid, Inc.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Coraid nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL CORAID BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include <stdio.h>
#include "dat.h"

enum {
	CMallow,
	CMblockuse,
	CMcheckalloc,
	CMcstat,
	CMdisallow,
	CMdumpusers,
	CMfixfamilies,
	CMfixpaths,
	CMhalt,
	CMhelp,
	CMhstat,
	CMinituid,
	CMlcreate,
	CMlls,
	CMlmeta,
	CMlrm,
	CMmpred,
	CMmprint,
	CMmstat,
	CMnewroot,
	CMnfsdebug,
	CMp2q,
	CMp9debug,
	CMphash,
	CMpmeta,
	CMq2m,
	CMqmeta,
	CMrecovermeta,
	CMrevert,
	CMrmp,
	CMrootallow,
	CMrootdisallow,
	CMsetmeta,
	CMsetmstruct,
	CMsetqhash,
	CMsnap,
	CMsuper,
	CMsync,
};

enum {
	SecPerDay = 24 * 60 * 60,
};

static void θconsread(Req *);
static void θconswrite(Req *);

static Srv θconssrv = {
	.start = θstart,
	.read = θconsread,
	.write = θconswrite,
};

static int snapid;
static Channel *snaptrigger;
static Ioproc *consio;
static int pfd[2];
static Cmdtab ctab[] = {
	{CMallow, "allow", 1},
	{CMblockuse, "blockuse", 2},
	{CMcheckalloc, "checkalloc", 1},
	{CMcstat, "cstat", 1},
	{CMdisallow, "disallow", 1},
	{CMdumpusers, "dumpusers", 1},
	{CMfixfamilies, "fixfamilies", 1},
	{CMfixpaths, "fixpaths", 1},
	{CMhalt, "halt", 1},
	{CMhelp, "help", 1},
	{CMhstat, "hstat", 1},
	{CMinituid, "inituid", 1},
	{CMlcreate, "lcreate", 3},
	{CMlls, "lls", 1},
	{CMlmeta, "lmeta", 2},
	{CMlrm, "lrm", 2},
	{CMmpred, "mpred", 2},
	{CMmprint, "mprint", 2},
	{CMmstat, "mstat", 1},
	{CMnewroot, "newroot", 2},
	{CMnfsdebug, "nfsdebug", 0},
	{CMp2q, "p2q", 2},
	{CMp9debug, "p9debug", 2},
	{CMphash, "phash", 2},
	{CMpmeta, "pmeta", 2},
	{CMq2m, "q2m", 2},
	{CMqmeta, "qmeta", 2},
	{CMrecovermeta, "recovermeta", 1},
	{CMrevert, "revert", 2},
	{CMrmp, "rmp", 2},
	{CMrootallow, "rootallow", 1},
	{CMrootdisallow, "rootdisallow", 1},
	{CMsetmeta, "setmeta", 5},
	{CMsetmstruct, "setmstruct", 6},
	{CMsetqhash, "setqhash", 3},
	{CMsnap, "snap", 1},
	{CMsuper, "super", 1},
	{CMsync, "sync", 1},
};

extern int chatty9p;

int allow;
int rootallow;

static void
showhelp(void)
{
	int i;

	for(i = 0; i < nelem(ctab); ++i)
		fprint(pfd[1], "%-15s %d\n", ctab[i].cmd, ctab[i].narg);
}

static void
lcreate(char *aoeid, uvlong size)
{
	Qid nqid;
	uvlong meta, dirblk, now, nblk, pperb;
	int sperb;
	int aoemajor, aoeminor;

	sperb = BlkSize / 512;
	pperb = BlkSize / 8;
	nblk = (size + sperb - 1) / sperb;
	if(nblk + 3 >= super.nfree) {
		fprint(pfd[1], "Not enough space\n");
		return;
	}
	sscanf(aoeid, "%d.%d", &aoemajor, &aoeminor);
	nqid.path = ((uvlong)TLun << 60) | (aoemajor << 8) | aoeminor;
	nqid.vers = 0;
	nqid.type = QTFILE;
	meta = q2m(-1, nqid.path, 1);
	if(meta == 0) {
		fprint(pfd[1], "Creation failure\n");
		return;
	}
	setmetaint(meta, "aoemajor", nil, aoemajor);
	setmetaint(meta, "aoeminor", nil, aoeminor);
	setmetaint(meta, "qpath", nil, nqid.path);
	setmetaint(meta, "qvers", nil, nqid.vers);
	setmetaint(meta, "qtype", nil, nqid.type);
	now = nsec();
	setmetaint(meta, "ctime", nil, now);
	setmetaint(meta, "length", nil, size << 9);
	dirblk = allocblock();
	if(dirblk != 0) {
		cbclean(dirblk);
		cbwrite(dirblk);
		brelease(dirblk);
	}
	if(nblk <= pperb)
		setmetaint(meta, "index", nil, dirblk);
	else if(nblk <= pperb * pperb)
		setmetaint(meta, "indirect", nil, dirblk);
	else
		setmetaint(meta, "dblindir", nil, dirblk);
	setmetaint(meta, "nextlun", nil, super.firstlun);
	setqhash(nqid.path, meta);
	super.firstlun = nqid.path;
	savesuper();
	starttarget(aoemajor, aoeminor, size);
	resetmeta();
	csync();
	fprint(pfd[1], "Created %d.%d with qid %ulld\n", aoemajor, aoeminor, nqid.path);
}

static char llsbuf[1024];

static char *
lls(void)
{
	char *p, *e;
	uvlong x;
	uvlong qpath, meta, length;
	int aoemajor, aoeminor;

	p = llsbuf;
	e = llsbuf + nelem(llsbuf);
	p = seprint(p, e, "Luns:\n");
	for(qpath = super.firstlun; qpath; ) {
		meta = q2m(-1, qpath, 0);
		if(meta == 0) {
			seprint(p, e, "no metadata for %ulld\n", qpath);
			return llsbuf;
		}
		getmetaint(-1, meta, "aoemajor", &x);
		aoemajor = x;
		getmetaint(-1, meta, "aoeminor", &x);
		aoeminor = x;
		getmetaint(-1, meta, "length", &x);
		length = x;
		p = seprint(p, e, "%d.%d %ulld\n", aoemajor, aoeminor, length);
		getmetaint(-1, meta, "nextlun", &qpath);
	}
	return llsbuf;
}

static void
lmeta(char *aoeid)
{
	uvlong qpath;
	int aoemajor, aoeminor;

	sscanf(aoeid, "%d.%d", &aoemajor, &aoeminor);
	qpath = ((uvlong)TLun << 60) | (aoemajor << 8) | aoeminor;
	fprint(pfd[1], "metadata for %d.%d:\n", aoemajor, aoeminor);
	prmeta(pfd[1], qpath);
}

static void
lrm(char *aoeid)
{
	uvlong qpath, meta, nextlun, qt, mt;
	int aoemajor, aoeminor;

	sscanf(aoeid, "%d.%d", &aoemajor, &aoeminor);
	qpath = ((uvlong)TLun << 60) | (aoemajor << 8) | aoeminor;
	meta = q2m(-1, qpath, 0);
	if(meta == 0) {
		fprint(pfd[1], "Not found\n");
		return;
	}
	freedata(meta);
	getmetaint(-1, meta, "nextlun", &nextlun);
	if(super.firstlun == qpath) {
		super.firstlun = nextlun;
		savesuper();
	}
	else {
		qt = super.firstlun;
		while(1) {
			mt = q2m(-1, qt, 0);
			if(mt == 0) {
				fprint(pfd[1], "Missing metadata in LUN set\n");
				goto bail;
			}
			getmetaint(-1, mt, "nextlun", &qt);
			if(qt == qpath)
				break;
		}
		setmetaint(mt, "nextlun", nil, nextlun);
	}
bail:
	rmq(qpath, meta);
	freeblock(meta);
	rmtarget(aoemajor, aoeminor);
	resetmeta();
	csync();
}

static void
newroot(char *name)
{
	Qid rootqid;
	char *me, *path;
	uvlong meta;
	vlong now;

	path = smprint("/%s", name);
	rootqid.path = p2q(-1, path, 1);
	meta = q2m(-1, rootqid.path, 1);
	setmetastr(meta, "name", nil, path, 0);
	rootqid.vers = 0;
	rootqid.type = QTDIR;
	setmetaint(meta, "qpath", nil, rootqid.path);
	setmetaint(meta, "qvers", nil, rootqid.vers);
	setmetaint(meta, "qtype", nil, rootqid.type);
	setmetaint(meta, "mode", nil, DMDIR | 0775);
	now = nsec();
	setmetaint(meta, "atime", nil, now);
	setmetaint(meta, "mtime", nil, now);
	setmetaint(meta, "length", nil, 0);
	me = getuser();
	setmetastr(meta, "uid", nil, me, 0);
	setmetastr(meta, "gid", nil, me, 0);
	setmetastr(meta, "muid", nil, me, 0);
	setmetaint(meta, "child", nil, 0);
	setqhash(rootqid.path, meta);
	savesuper();
	free(path);
}

static char *
dosnap(void)
{
	Qid qid;
	Tm *today;
	char *me;
	uvlong meta, now, dqid, dmeta, yqid, ymeta, x;
	int fd, seq, n;
	char path[128], sname[32];

	dqid = p2q(-1, "/dump", 0);
	if(dqid == 0)
		return "no dump";
	dmeta = q2m(-1, dqid, 0);
	snprint(path, 127, "%s/ctl", ddir);
	fd = open(path, ORDWR);
	if(fd < 0)
		return "no snap";
	today = localtime(time(0));
	snprint(path, 127, "/dump/%04d", today->year + 1900);
	seq = 0;
	yqid = p2q(-1, path, 0);
	if(yqid == 0) {
		qid.path = p2q(-1, path, 1);
		yqid = qid.path;
		qid.vers = 0;
		qid.type = QTDIR;
		ymeta = q2m(-1, qid.path, 1);
		snprint(path, 127, "%04d", today->year + 1900);
		setmetastr(ymeta, "name", nil, path, 0);
		setmetaint(ymeta, "qpath", nil, qid.path);
		setmetaint(ymeta, "qvers", nil, qid.vers);
		setmetaint(ymeta, "qtype", nil, qid.type);
		setmetaint(ymeta, "mode", nil, DMDIR | 0775);
		setmetaint(ymeta, "parent", nil, dqid);
		now = nsec();
		setmetaint(ymeta, "atime", nil, now);
		setmetaint(ymeta, "mtime", nil, now);
		setmetaint(ymeta, "length", nil, 0);
		me = getuser();
		setmetastr(ymeta, "uid", nil, me, 0);
		setmetastr(ymeta, "gid", nil, me, 0);
		setmetastr(ymeta, "muid", nil, me, 0);
		getmetaint(-1, dmeta, "child", &x);
		setmetaint(ymeta, "sib", nil, x);
		setmetaint(dmeta, "child", nil, yqid);
		setmetaint(ymeta, "child", nil, 0);
		setqhash(qid.path, ymeta);
		savesuper();
		snprint(path, 127, "/dump/%04d/%02d%02d", today->year + 1900, today->mon+1, today->mday);
	}
	else {
		snprint(path, 127, "/dump/%04d/%02d%02d", today->year + 1900, today->mon+1, today->mday);
		if(p2q(-1, path, 0) != 0) {
			for(seq = 1; seq < 10; ++seq) {
				snprint(path, 127, "/dump/%04d/%02d%02d%d",
					today->year + 1900, today->mon+1, today->mday, seq);
				if(p2q(-1, path, 0) == 0)
					break;
			}
			if(seq >= 10) {
				close(fd);
				return "too many snaps";
			}
		}
		ymeta = q2m(-1, yqid, 0);
	}
	qid.path = p2q(-1, path, 1);
	qid.vers = 0;
	qid.type = QTDIR;
	meta = q2m(-1, qid.path, 1);
	if(seq == 0) {
		snprint(path, 127, "%02d%02d", today->mon+1, today->mday);
		snprint(sname, 31, "%s.%04d%02d%02d",
			dname, today->year + 1900, today->mon+1, today->mday);
	}
	else {
		snprint(path, 127, "%02d%02d%d", today->mon+1, today->mday, seq);
		snprint(sname, 31, "%s.%04d%02d%02d%d",
			dname, today->year + 1900, today->mon+1, today->mday, seq);
	}
	resetmeta();
	csync();
	n = fprint(fd, "snap %s %s", dname, sname);
	close(fd);
	if(n < 0)
		return (char *)(~0);
	setmetastr(meta, "name", nil, path, 0);
	setmetaint(meta, "qpath", nil, qid.path);
	setmetaint(meta, "qvers", nil, qid.vers);
	setmetaint(meta, "qtype", nil, qid.type);
	setmetaint(meta, "mode", nil, DMDIR | 0775);
	setmetaint(meta, "parent", nil, yqid);
	now = nsec();
	setmetaint(meta, "atime", nil, now);
	setmetaint(meta, "mtime", nil, now);
	setmetaint(meta, "length", nil, 0);
	me = getuser();
	setmetastr(meta, "uid", nil, me, 0);
	setmetastr(meta, "gid", nil, me, 0);
	setmetastr(meta, "muid", nil, me, 0);
	getmetaint(-1, ymeta, "child", &x);
	setmetaint(meta, "sib", nil, x);
	setmetaint(ymeta, "child", nil, qid.path);
	setmetastr(meta, "snap", nil, sname, 0);
	setqhash(qid.path, meta);
	savesuper();
	return nil;
}

static char *
revert(char *snap)
{
	char *path, *p;
	int fd, n;

	path = smprint("%s/ctl", ddir);
	fd = open(path, ORDWR);
	free(path);
	if(fd < 0)
		return (char *)(~0);
	p = strchr(snap, '/');
	if(p)
		path = smprint("%s.%.*s%s", dname, (int)(p - snap), snap, p + 1);
	else
		path = smprint("%s.%s", dname, snap);
	n = fprint(fd, "revert %s %s", dname, path);
	free(path);
	close(fd);
	resetmeta();
	resetcache();
	if(n < 0)
		return (char *)(~0);
	return nil;
}

static void
doshutdown(void)
{
	shutdown = 1;
	threadkill(snapid);
	haltaoe();
	haltnfs();
	halt9p();
	haltfree();
	haltcache();
	threadkillgrp(threadgetgrp());
}

void
docons(void *x)
{
	Cmdbuf *cb;
	Cmdtab *ct;
	char *s;
	char buf[256];
	uvlong vl;
	int n;

	USED(x);
	while(1) {
		fprint(pfd[1], "> ");
		n = ioread(consio, pfd[1], buf, 255);
		if(n <= 0)
			return;
		buf[n] = 0;
		cb = parsecmd(buf, n);
		if(cb == nil) {
			fprint(pfd[1], "Unparsable command %s\n", buf);
			continue;
		}
		if(cb->nf == 0)
			continue;
		ct = lookupcmd(cb, ctab, nelem(ctab));
		if(ct == nil) {
			fprint(pfd[1], "%s: %r\n", buf);
			continue;
		}
		switch(ct->index) {
		case CMallow:
			allow = 1;
			break;
		case CMblockuse:
			blockuse(pfd[1], strtoull(cb->f[1], nil, 0));
			break;
		case CMcheckalloc:
			checkalloc(pfd[1]);
			break;
		case CMcstat:
			fprint(pfd[1], "%s", prcstat());
			break;
		case CMdisallow:
			allow = 0;
			break;
		case CMdumpusers:
			dumpusers(pfd[1]);
			break;
		case CMfixfamilies:
			fixfamilies(pfd[1]);
			break;
		case CMfixpaths:
			fixpaths(pfd[1]);
			break;
		case CMhalt:
			doshutdown();
			return;
		case CMhelp:
			showhelp();
			break;
		case CMhstat:
			fprint(pfd[1], "%s", prhstat());
			break;
		case CMinituid:
			inituid();
			break;
		case CMlcreate:
			lcreate(cb->f[1], strtoull(cb->f[2], nil, 10));
			break;
		case CMlls:
			fprint(pfd[1], "%s", lls());
			break;
		case CMlmeta:
			lmeta(cb->f[1]);
			break;
		case CMlrm:
			lrm(cb->f[1]);
			break;
		case CMmpred:
			mpred(pfd[1], strtoull(cb->f[1], nil, 0));
			break;
		case CMmprint:
			mprint(pfd[1], strtoull(cb->f[1], nil, 0));
			break;
		case CMmstat:
			fprint(pfd[1], "%s", prmstat());
			break;
		case CMnewroot:
			newroot(cb->f[1]);
			break;
		case CMnfsdebug:
			if(cb->nf < 2)
				fprint(pfd[1], "%d\n", debugnfs);
			else
				debugnfs = atoi(cb->f[1]);
			break;
		case CMp2q:
			vl = p2q(-1, cb->f[1], 0);
			fprint(pfd[1], "%ulld\n", vl);
			break;
		case CMp9debug:
			chatty9p = atoi(cb->f[1]);
			break;
		case CMphash:
			showphash(pfd[1], cb->f[1]);
			break;
		case CMpmeta:
			fprint(pfd[1], "metadata for %s\n", cb->f[1]);
			prmeta(pfd[1], p2q(-1, cb->f[1], 0));
			break;
		case CMq2m:
			vl = q2m(-1, strtoull(cb->f[1], nil, 10), 0);
			fprint(pfd[1], "%ulld\n", vl);
			break;
		case CMqmeta:
			fprint(pfd[1], "metadata for %s\n", cb->f[1]);
			prmeta(pfd[1], strtoull(cb->f[1], nil, 10));
			break;
		case CMrecovermeta:
			recovermeta(pfd[1]);
			break;
		case CMrevert:
			s = revert(cb->f[1]);
			if(s == (char *)(~0))
				fprint(pfd[1], "%r\n");
			else if(s)
				fprint(pfd[1], "%s\n", s);
			break;
		case CMrmp:
			rmp(cb->f[1]);
			break;
		case CMrootallow:
			rootallow = 1;
			break;
		case CMrootdisallow:
			rootallow = 0;
			break;
		case CMsetmeta:
			vl = q2m(-1, strtoull(cb->f[1], nil, 10), 0);
			if(cb->f[2][0] == 's')
				setmetastr(vl, cb->f[3], nil, cb->f[4], 0);
			else
				setmetaint(vl, cb->f[3], nil, strtoull(cb->f[4], nil, 0));
			break;
		case CMsetmstruct:
			vl = strtoull(cb->f[1], nil, 0);
			setmstruct(vl, strtoull(cb->f[2], nil, 0), cb->f[3], atoi(cb->f[4]), strtoull(cb->f[5], nil, 0));
			break;
		case CMsetqhash:
			setqhash(strtoull(cb->f[1], nil, 0), strtoull(cb->f[2], nil, 0));
			break;
		case CMsnap:
			s = dosnap();
			if(s == (char *)(~0))
				fprint(pfd[1], "%r\n");
			else if(s)
				fprint(pfd[1], "%s\n", s);
			break;
		case CMsuper:
			fprint(pfd[1], "%s", prsuper());
			break;
		case CMsync:
			resetmeta();
			csync();
			break;
		}
	}
}

static void
θconsread(Req *r)
{
	char *s;

	s = smprint("%s\n%s\n%s\n%s\n%s", prsuper(), prcstat(), prmstat(), prhstat(), lls());
	readstr(r, s);
	free(s);
	respond(r, nil);
}

static void
θconswrite(Req *r)
{
	Cmdbuf *cb;
	Cmdtab *ct;
	char *s;

	s = nil;
	cb = parsecmd(r->ifcall.data, r->ifcall.count);
	if(cb == nil) {
		respond(r, "unparsable command");
		return;
	}
	if(cb->nf == 0) {
		respond(r, nil);
		return;
	}
	ct = lookupcmd(cb, ctab, nelem(ctab));
	if(ct == nil) {
		respond(r, r->error);
		return;
	}
	switch(ct->index) {
	case CMallow:
		allow = 1;
		break;
	case CMcheckalloc:
		checkalloc(pfd[1]);
		break;
	case CMdisallow:
		allow = 0;
		break;
	case CMinituid:
		inituid();
		break;
	case CMlcreate:
		lcreate(cb->f[1], strtoull(cb->f[2], nil, 10));
		break;
	case CMlrm:
		lrm(cb->f[1]);
		break;
	case CMnewroot:
		newroot(cb->f[1]);
		break;
	case CMp9debug:
		chatty9p = atoi(cb->f[1]);
		break;
	case CMrevert:
		s = revert(cb->f[1]);
		break;
	case CMrmp:
		rmp(cb->f[1]);
		break;
	case CMrootallow:
		rootallow = 1;
		break;
	case CMrootdisallow:
		rootallow = 0;
		break;
	case CMsetmstruct:
		setmstruct(strtoull(cb->f[1], nil, 0), strtoull(cb->f[2], nil, 0), cb->f[3], atoi(cb->f[4]), strtoull(cb->f[5], nil, 0));
		break;
	case CMsetqhash:
		setqhash(strtoull(cb->f[1], nil, 0), strtoull(cb->f[2], nil, 0));
		break;
	case CMsnap:
		s = dosnap();
		break;
	case CMsync:
		resetmeta();
		csync();
		break;
	default:
		s = "unsupported ctl command";
		break;
	}
	if(s == (char *)(~0))
		respond(r, r->error);
	else
		respond(r, s);
}

static void
mysrvproc(void *a)
{
	Srv *s;
	int data;

	s = a;
	data = s->infd;
	srv(s);
	close(data);
	threadexits(nil);
}

static void
snapthread(void *)
{
	while(1) {
		recvul(snaptrigger);
		if(shutdown)
			break;
		dosnap();
	}
	threadexits(nil);
}

static void
snapproc(void *)
{
//	Tm *now;
	ulong cursec, waitsec;

	sleep(300*1000);	/* Give sometime to get the clock set before looking at tod */
	while(1) {
		/*
		 * We'd like to get the time zone correction here, but
		 * it's doesn't play nice with the threading.  I'll come
		 * back to this later.
		 */
//		now = localtime(time(nil));
//		cursec = (now->hour * 60 + now->min) * 60 + now->sec;
		cursec = time(nil) % SecPerDay;
		waitsec = (super.snaptime + SecPerDay - cursec) % SecPerDay;
		if(waitsec < 60)
			waitsec = SecPerDay;
		sleep(waitsec*1000);
		sendul(snaptrigger, 1);
		if(shutdown)
			break;
	}
	threadexits(nil);
}

void
initcons(int postcons)
{
	char *me;
	int cfd[2];

	if(postcons) {
		consio = ioproc();
		me = getuser();
		θconssrv.tree = alloctree(me, me, 0555, nil);
		createfile(θconssrv.tree->root, "θfsctl", me, 0664, nil);
		if(pipe(cfd) < 0)
			sysfatal("pipe: %r");
		θconssrv.infd = θconssrv.outfd = cfd[1];
		conspost(cfd, pfd);
		threadcreate(mysrvproc, &θconssrv, 32 * 1024);
	}
	snaptrigger = chancreate(sizeof(ulong), 2);
	threadcreate(snapthread, nil, 8192);
	snapid = proccreate(snapproc, nil, 1024);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.