Plan 9 from Bell Labs’s /usr/web/sources/contrib/fhs/dic.c

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


/*
 * Dictionary Server Protocol client (RFC2229)
 */
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ctype.h>

#pragma varargck type	"D" char*
#pragma varargck argpos	sendcmd 1
#pragma varargck argpos	simplecmd 1

typedef struct Resp Resp;
struct Resp {		/* Response */
	int		type;
	char	*line;
	int		code;
	char	msg[1024];	/* everything but response code */
	int		n;
	char	*word;
	char	*db;
	char	*dbdescr;
};

enum {
	Rstatus = 1,	/* status response */
	Rtext,			/* text response */
	EOT				/* End of Text marker */
};

Biobuf bin, bout;
char *addr;
char *database = "*";
char *strat = ".";
int domatch;
int showdb;
int showstrat;
int showserver;
int showinfo;

char*
dbquote(char *s)	/* double quote a string */
{
	char *q, *r, *t;
	
	/*
	 * max size occurs when escaping each char
	 * + 2 quotes surrounding it + null char
	 */
	q = malloc(2*strlen(s)+2+1);
	r = q;
	*r++ = '"';
	for(t = s; *t; t++)
		switch(*t){
		case '\\':
		case '"':
			*r++ = '\\';
			*r++ = *t;
			break;
		default:
			*r++ = *t;
		}
	*r++ = '"';
	*r = 0;
	return q;
}

int
Dfmt(Fmt *f)
{
	char *s, *q;
	int n;
	
	s = va_arg(f->args, char*);
	q = dbquote(s);
	n = fmtprint(f, "%s", q);
	free(q);
	return n;
}

/*
 * unquote a string quoted using quote char qc
 */
char*
unquote(char qc, char *s)
{
	char *t;
	
	*s++ = 0;		/* remove quote char */
	for(t = s; *t; t++){
		if(t[0] == '\\' && t[1] == '\\')
			t++;
		else if(t[0] == '\\' && t[1] == qc)
			t++;
		else if(t[0] == qc)
			break;
		*s++ = *t;
	}
	if(t[0] == 0 || t[1] == 0)
		t = nil;	/* end of input string */
	else
		t++;	/* after the quoted string */
	*s = 0;
	return t;
}

int
qtokenize(char *s, char **args, int maxargs)
{
	int i;
	
	if(*s == 0)
		return 0;
	for(i = 0; i < maxargs; i++){
		args[i] = s;
		if(*s == '"' || *s == '\''){
			args[i] = &s[1];
			s = unquote(*s, s);
			if(s == nil)
				return i+1;
			*s++ = 0;	/* skip space */
		}else{
			s = strchr(s, ' ');
			if(s == nil)
				return i+1;
			*s++ = 0;	/* skip space */
		}
	}
	return i;
}	

char*
getline(void)
{
	char *s;
	int n;
	
	s = Brdline(&bin, '\n');
	if(s != nil){
		n = Blinelen(&bin);
		s[n-2] = 0;
	}
	return s;
}

void
sendcmd(char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	Bvprint(&bout, fmt, args);
	va_end(args);
	Bprint(&bout, "\r\n");
	Bflush(&bout);
}

Resp*
getresp(void)
{
	static Resp response;
	Resp *r;
	char *arg[4], *line;
	int n;
	
	r = &response;
	memset(r, 0, sizeof(*r));
	
	r->line = line = getline();
	if(isdigit(line[0]) && isdigit(line[1]) && isdigit(line[2]))
		r->type = Rstatus;
	else{
		if(line[0] == '.' && line[1] == 0)
			r->type = EOT;
		else if(strncmp(line, "..", 3) == 0){
			r->type = Rtext;
			r->line[1] = 0;
		}else
			r->type = Rtext;
		return r;
	}
	
	/*
	 * Process status response
	 */
	utfecpy(r->msg, r->msg+sizeof(r->msg),
		&line[(line[3]==0) ? 3 : 4]);
	n = qtokenize(line, arg, nelem(arg));
	r->code = atoi(arg[0]);
	switch(r->code){
	case 110:
	case 111:
	case 150:
	case 152:
		if(n < 2)
			return r;
		r->n = atoi(arg[1]);
		break;
	case 151:
		if(n < 4)
			return r;
		r->word = arg[1];
		r->db = arg[2];
		r->dbdescr = arg[3];
		break;
	}
	return r;
}

void
printtext(void)
{
	Resp *r;

	for(;;){
		r = getresp();
		if(r->type == EOT)
			break;
		print("%s\n", r->line);
	}
}

void
simplecmd(char *fmt, ...)
{
	Resp *r;
	int e;
	va_list args;
	
	va_start(args, fmt);
	Bvprint(&bout, fmt, args);
	va_end(args);
	Bprint(&bout, "\r\n");
	Bflush(&bout);
	
	r = getresp();
	e = r->code/100;
	if(r->type == Rstatus && e != 5 && e != 4){
		printtext();
		getresp(); /* 250 */
	}else
		sysfatal("bad response: %s", r->msg);
}

void
match(char *db, char *strat, char *query)
{
	Resp *r;
	char *f[2];
	int n, e;
	
	doquote = needsrcquote;
	quotefmtinstall();

	sendcmd("MATCH %s %s %D", db, strat, query);
	r = getresp();
	if(r->type == Rstatus && r->code == 552){
		print("no match found\n");
		return;
	}
	e = r->code/100;
	if(r->type != Rstatus || e == 5 || e == 4)
		sysfatal("bad response: %s", r->msg);
	for(;;){
		r = getresp();
		if(r->type == EOT)
			break;
		n = qtokenize(r->line, f, nelem(f));
		if(n != 2)
			sysfatal("bad match output: %s", r->line);
		print("dic -d %s %q\n", f[0], f[1]);
	}
}

void
define(char *db, char *word)
{
	Resp *r;
	
	sendcmd("DEFINE %s %D", db, word);
	r = getresp();
	if(r->type == Rstatus && r->code == 552){ /* no match */
		match(database, strat, word);
		return;
	}
	if(r->type != Rstatus || r->code != 150)
		sysfatal("bad response: %s", r->msg);
	for(;;){
		r = getresp();
		if(r->type == Rstatus && r->code != 151)
			break;
		print("From %s [%s]:\n\n", r->dbdescr, r->db);
		printtext();
		print("\n");
	}
	if(r->code != 250)
		sysfatal("command failed: %s", r->msg);
}

void
usage(void)
{
	fprint(2, "usage: dic [-DISim] [-a addr] [-d db] [-s strategy] word\n");
	exits("usage");
}

void
main(int argc, char *argv[])
{
	int fd;
	Resp *r;
	
	fmtinstall('D', Dfmt);
	
	ARGBEGIN {
	case 'D':
		showdb++;
		break;
	case 'S':
		showstrat++;
		break;
	case 'I':
		showserver++;
		break;
	case 'i':
		showinfo++;
		break;
	case 'a':
		addr = EARGF(usage());
		break;
	case 'd':
		database = EARGF(usage());
		break;
	case 's':
		strat = EARGF(usage());
		/* fall through */
	case 'm':
		domatch++;
		break;
	default:
		usage();
	} ARGEND
	
	if(!showdb && !showstrat && !showserver && argc < 1)
		usage();
	
	if(addr)
		fd = dial(netmkaddr(addr, "tcp", "2628"), nil, nil, nil);
	else{
		fd = dial("tcp!localhost!2628", nil, nil, nil);
		if(fd < 0)
			fd = dial("tcp!dict.org!2628", nil, nil, nil);
	}
	if(fd < 0)
		sysfatal("dial: %r");
	
	Binit(&bin, fd, OREAD);
	Binit(&bout, fd, OWRITE);
	
	r = getresp();
	if(r->type != Rstatus || r->code != 220)
		sysfatal("server not usable: %s", r->msg);
	sendcmd("CLIENT plan9/dic");
	getresp();		/* ignore reply */
	
	if(showdb)
		simplecmd("SHOW DB");
	else if(showstrat)
		simplecmd("SHOW STRAT");
	else if(showserver)
		simplecmd("SHOW SERVER");
	else{
		if(domatch)
			match(database, strat, argv[0]);
		else if(showinfo)
			simplecmd("SHOW INFO %s", argv[0]);
		else
			define(database, argv[0]);
	}
	sendcmd("QUIT");
	
	Bterm(&bin);
	Bterm(&bout);
	close(fd);
	
	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.