Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/root/sys/src/cmd/tags/tagfs.c

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


#include <u.h>
#include <libc.h>
#include <bio.h>
#include <fcall.h>
#include <thread.h>
#include <auth.h>
#include <9p.h>
#include <ctype.h>
#include "trie.h"
#include "util.h"
#include "query.h"

typedef struct Query Query;

struct Query{
	char*	text;
	Texpr*	expr;	// non-nil after query write completed
};

Trie*	trie;
File*	ctlf;
char*	tfname;
char*	ttfname;

static void
fscreate(Req* r)
{
	File*	file;
	Query*	q;
	char*	name;
	char*	uid;
	int	mode;
	File*	f;

	file = r->fid->file;
	name = r->ifcall.name;
	uid = r->fid->uid;
	mode = r->fid->file->mode & 0x777 & r->ifcall.perm;
	mode |= (r->ifcall.perm & ~0x777);
	if(mode&DMDIR){
		respond(r, "queries cannot be directories");
		return;
	}
	if(f = createfile(file, name, uid, mode, nil)){
		q = emalloc9p(sizeof *q);
		q->text = estrdup9p("");
		q->expr = nil;
		f->aux = q;
		closefile(r->fid->file);
		r->fid->file = f;
		r->ofcall.qid = f->qid;
		respond(r, nil);
	} else
		responderror(r);
}

void
tag(Trie* t, char* s, uvlong qid)
{
	int	l;
	char*	q;
	Rune	r;
	int	nc;

	l = strlen(s);
	// don't use and and two char words as tags
	// don't use uttlerly long words, probably not tags.
	if(l < 3 || l > 40)
		return;

	// don't use as tags things other than alphanumeric.
	for(q = s; *q != 0; ){
		nc = chartorune(&r, q);
		if(!isalpharune(r) && !isdigit(r))
			return;
		q += nc;
	}
	trieput(t, s, qid);
}

static void
ctlwrite(Req* r)
{
	char	buf[1024];
	char*	toks[512];
	int	fd;
	Biobuf	bout;
	int	ntoks;
	long	count;
	uvlong	qid;
	int	i;

	count = r->ifcall.count;
	if(count > sizeof(buf)-1)
		count=sizeof(buf)-1;
	r->ofcall.count = count;
	memmove(buf, r->ifcall.data, count);
	buf[count] = 0;
	ntoks = tokenize(buf, toks, nelem(toks));
	if(ntoks < 1){
		respond(r, "null ctl");
		return;
	}
	if(strcmp(toks[0], "tag") == 0){
		if(ntoks < 3){
			respond(r, "ctl usage: tag qid tag...");
			return;
		}
		qid = strtoull(toks[1], nil, 16);
		for(i = 2; i < ntoks; i++)
			tag(trie, toks[i], qid);
	} else if(strcmp(toks[0], "sync") == 0){
		fd = create(ttfname, OWRITE, 0664);
		if(fd < 0){
			responderror(r);
			return;
		}
		Binit(&bout, fd, OWRITE);
		if(wrtrie(&bout, trie) < 0){
			close(fd);
			remove(ttfname);
			responderror(r);
			return;
		}
		Bterm(&bout);
		close(fd);
		if(rename(tfname, ttfname) < 0){
			responderror(r);
			remove(ttfname);
			return;
		}
	} else {
		respond(r, "bad ctl request");
		return;
	}
	respond(r, nil);
}

static void
fswrite(Req* r)
{
	Query*	q;
	File*	f;
	long	count;
	char*	ntext;
	int	l;

	if(r->fid->qid.type&QTDIR){
		respond(r, "bug: write on dir");
		return;
	}
	f = r->fid->file;
	if(f == ctlf){
		ctlwrite(r);
		return;
	}
	count = r->ifcall.count;
	r->ofcall.count = count;
	q = f->aux;
	if(q->expr != nil){
		// a previous query was made. start another.
		freeexpr(q->expr);
		free(q->text);
		q->expr = nil;
		q->text = nil;
	}
	/* append text to query text, ignore offset.
	 */
	l = strlen(q->text);
	ntext = emalloc9p(l + count + 1);
	strcpy(ntext, q->text);
	memmove(ntext+l, r->ifcall.data, count);
	ntext[l+count]=0;
	free(q->text);
	q->text = ntext;
	respond(r, nil);
}

static void
ctlread(Req* r)
{
	char	buf[4096];
	char*	s;
	int	i;

	s = seprint(buf, buf+sizeof(buf), "trie %s\n", tfname);
	s = seprint(s, buf+sizeof(buf), "prefixes %ld\n", ntries);
	s = seprint(s, buf+sizeof(buf), "tags %ld\n", nvaltries);
	s = seprint(s, buf+sizeof(buf), "max entry %ld\n", maxvals);
	if(trie->nents > 0){
		s = seprint(s, buf+sizeof(buf), "%d root runes [", trie->nents);
		for(i = 0; i < trie->nents; i++)
			s = seprint(s, buf+sizeof(buf), "%C", trie->ents[i].r);
		s = seprint(s, buf+sizeof(buf), "]\n");
	}
	if(roott.nents > 0){
		s = seprint(s, buf+sizeof(buf), "%d used runes [", roott.nents);
		for(i = 0; i < roott.nents; i++)
			s = seprint(s, buf+sizeof(buf), "%C", roott.ents[i].r);
		seprint(s, buf+sizeof(buf), "]\n");
	}
	readstr(r, buf);
	respond(r, nil);
}

static void
fsread(Req* r)
{
	Query*	q;
	char**	toks;
	int	ntoks;
	int	atoks;
	char*	s;
	int	pos;
	File*	f;

	if(r->fid->qid.type&QTDIR){
		respond(r, "bug: write on dir");
		return;
	}
	f = r->fid->file;
	if(f == ctlf){
		ctlread(r);
		return;
	}
	q = f->aux;

	/* The first read process the query.
	 * Further reads just retrieve more data,
	 * if any.
	 */
	if(q->expr == nil){
		atoks = 512;
		toks = emalloc9p(atoks*sizeof(char*));
		do {
			s = estrdup9p(q->text);
			ntoks = tokenize(s, toks, atoks);
			if(ntoks == atoks){
				atoks += 512;
				toks = erealloc9p(toks, atoks*sizeof(char*));
				free(s);
			}
		}while(ntoks == atoks);
		pos = 0;
		if(chatty9p)
			fprint(2, "compiling %s (%d toks)\n", q->text, ntoks);
		q->expr = parseexpr(ntoks, toks, &pos);
		if(q->expr == nil){
			free(s);
			free(toks);
			respond(r, "syntax error");
			return;
		}
		if(chatty9p)
			fprint(2, "evaluating %s (%d toks)\n", q->text, ntoks);
		evalexpr(trie, q->expr);
		free(s);
		free(toks);
		free(q->text);
		/* smprintexprval limits the total output to
		 * at most 64K of text
		 */
		q->text = smprintexprval(q->expr);
		if(chatty9p)
			fprint(2, "result is [%s]\n", q->text);
	}
	/* After the query is process,
	 * q->text holds the reply.
	 */
	readstr(r,  q->text);
	respond(r, nil);
}

static void
fsclunk(Fid* fid)
{
	Query*	q;
	File*	f;

	f = fid->file;
	if(f == nil)
		return;
	q = f->aux;
	if(q == nil)
		return;
	if(q->expr != nil){
		/* the query was already made, destroy the file.
		 *
		 * We must incref the file because
		 * removefile assumes that we hold the
		 * reference given to it, and we do not.
		 * We just want the file removed from the tree.
		 */
		free(q->text);
		freeexpr(q->expr);
		free(q);
		f->aux = nil;
		incref(f);
		removefile(f);
	}
}

static Srv sfs=
{
	.create	=	fscreate,
	.read	=	fsread,
	.write	=	fswrite,
	.destroyfid = 	fsclunk,
};

char*
srvname(char* triefname)
{
	char*	s;
	char*	r;

	s = strrchr(triefname, '/');
	if(s != nil)
		s++;
	else
		s = triefname;
	s = estrdup(s);
	r = strstr(s, ".trie.db");
	if(r != nil)
		strcpy(r, ".tagfs");
	else
		s = smprint("%s.tagfs", s);
	return s;
}

void
usage(void)
{
	fprint(2, "usage: %s [-abcD] [-s srv] [-m mnt] trie\n", argv0);
	exits("usage");
}

void
main(int argc, char* argv[])
{
	char*	mnt;
	char*	srv;
	int	mflag;
	Biobuf*	b;
	char*	user;

	srv = nil;
	mnt = nil;
	mflag = MREPL|MCREATE;
	ARGBEGIN{
	case 'a':
		mflag = MAFTER|MCREATE;
		break;
	case 'b':
		mflag = MBEFORE|MCREATE;
		break;
	case 'c':
		mflag = MREPL|MCREATE;
		break;
	case 's':
		srv = EARGF(usage());
		break;
	case 'm':
		mnt = EARGF(usage());
		break;
	case 'D':
		debug = 1;
		chatty9p++;
		break;
	default:
		usage();
	}ARGEND;
	if(argc != 1)
		usage();

	tfname = argv[0];
	ttfname = smprint("%s.new", tfname);
	b = Bopen(tfname, OREAD);
	if(b == nil)
		sysfatal("%s: %r", tfname);
	trie = rdtrie(b);
	Bterm(b);
	if(trie == nil)
		sysfatal("%s: %r", tfname);

	if(srv == nil && mnt == nil){
		mnt = "/mnt/tags";
		srv = srvname(tfname);
	}
	if(!chatty9p)
		rfork(RFNOTEG);
	sfs.tree =  alloctree(nil, nil, DMDIR|0777, nil);
	user = getuser();
	ctlf = createfile(sfs.tree->root, "ctl", user, 0666, nil);
	postmountsrv(&sfs, srv, mnt, mflag);
	exits(nil);
}

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.