1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 2002-2011 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 /*
22  * text method
23  *
24  * Glenn Fowler
25  * AT&T Research
26  */
27 
28 static const char usage[] =
29 "[+DESCRIPTION?The \bdss\b text method describes newline-terminated"
30 "	field-delimited text file data. The method schema is a \bscanf\b(3)"
31 "	style format string with embedded field names of the form"
32 "	\b%(\b\afield\a\b)\b\aformat-char\a \adelimiter\a ...]"
33 "[+?Use the \bdss\b \bflat\b method for generic record-oriented flat "
34     "file data.]"
35 "[+EXAMPLES]{"
36 "	[+dss -x text::::\b\"%(name)s::%(passwd::Encrypted\\ password.)s::%(uid)d::%(gid)d::%(comment)s::%(home)s::%(shell)s\"\\ 'passwd==\"\"&&uid==0'\\ "
37 "	/etc/passwd?Prints \b/etc/passwd\b entries with uid==0 and no"
38 "	password.]"
39 "}"
40 "\n"
41 "\n--method=text[,option...]\n"
42 "\n"
43 ;
44 
45 #include <dsslib.h>
46 #include <tm.h>
47 
48 typedef uint32_t Ipaddr_t;
49 
50 struct Text_s; typedef struct Text_s Text_t;
51 
52 struct Text_s				/* Dssmeth_t.data		*/
53 {
54 	char*		format;
55 	Dt_t*		dict;
56 	int		vars;
57 	char		name[1];
58 };
59 
60 static char		null[1];
61 
62 extern Dsslib_t		dss_lib_text;
63 
64 /*
65  * text identf
66  */
67 
68 static int
textident(Dssfile_t * file,void * buf,size_t n,Dssdisc_t * disc)69 textident(Dssfile_t* file, void* buf, size_t n, Dssdisc_t* disc)
70 {
71 	return 1;
72 }
73 
74 /*
75  * text fopenf
76  */
77 
78 static int
textfopen(Dssfile_t * file,Dssdisc_t * disc)79 textfopen(Dssfile_t* file, Dssdisc_t* disc)
80 {
81 	if (!(file->data = vmnewof(file->dss->vm, 0, Cxvalue_t, ((Text_t*)file->dss->meth->data)->vars, 0)))
82 	{
83 		if (disc->errorf)
84 			(*disc->errorf)(NiL, disc, ERROR_SYSTEM|2, "out of space");
85 		return -1;
86 	}
87 	return 0;
88 }
89 
90 /*
91  * text fclosef
92  */
93 
94 static int
textfclose(Dssfile_t * file,Dssdisc_t * disc)95 textfclose(Dssfile_t* file, Dssdisc_t* disc)
96 {
97 	if (!file || !file->data)
98 		return -1;
99 	vmfree(file->dss->vm, file->data);
100 	return 0;
101 }
102 
103 /*
104  * get one string token into p
105  */
106 
107 static char*
lextok(register char * s,register int c,Cxstring_t * p)108 lextok(register char* s, register int c, Cxstring_t* p)
109 {
110 	register char*	t;
111 	register int	q;
112 	char*		b;
113 	char*		u;
114 
115 	b = s;
116 	q = 0;
117 	t = 0;
118 	for (;;)
119 	{
120 		if (!*s)
121 		{
122 			if (!q)
123 			{
124 				if (c && c != ' ')
125 				{
126 					s = b;
127 					b = null;
128 					break;
129 				}
130 			}
131 			if (t)
132 				*t = 0;
133 			break;
134 		}
135 		else if (*s == '\\')
136 		{
137 			u = s;
138 			if (!*++s)
139 				continue;
140 			if (b == u)
141 				b = s;
142 			else if (!t)
143 				t = u;
144 		}
145 		else if (q)
146 		{
147 			if (*s == q)
148 			{
149 				q = 0;
150 				if (!t)
151 					t = s;
152 				s++;
153 				continue;
154 			}
155 			else if (*s == '\r')
156 				*s = 0;
157 		}
158 		else if (*s == '"' || *s == '\'')
159 		{
160 			q = *s++;
161 			if (b == (s - 1))
162 				b = s;
163 			else if (!t)
164 				t = s - 1;
165 			continue;
166 		}
167 		else if (*s == c || c == ' ' && *s == '\t')
168 		{
169 			*s++ = 0;
170 			if (t)
171 				*t = 0;
172 			if (c == ' ')
173 				while (*s == ' ' || *s == '\t')
174 					s++;
175 			break;
176 		}
177 		if (t)
178 			*t++ = *s;
179 		s++;
180 	}
181 	p->data = b;
182 	p->size = *b ? (s - b) : 0;
183 	return s;
184 }
185 
186 /*
187  * text readf
188  */
189 
190 static int
textread(register Dssfile_t * file,Dssrecord_t * record,Dssdisc_t * disc)191 textread(register Dssfile_t* file, Dssrecord_t* record, Dssdisc_t* disc)
192 {
193 	register Cxvalue_t*	data = (Cxvalue_t*)file->data;
194 	register Text_t*	text = (Text_t*)file->dss->meth->data;
195 	register char*		s;
196 	register char*		f;
197 	register int		c;
198 	char*			t;
199 	int			num;
200 	int			q;
201 	Ipaddr_t		a;
202 
203 	if (!(s = sfgetr(file->io, '\n', 1)))
204 		return 0;
205 	num = 0;
206 	f = text->format;
207 	for (;;)
208 	{
209 		switch (c = *f++)
210 		{
211 		case 0:
212 			break;
213 		case ' ':
214 			while (*s == ' ' || *s == '\t')
215 				s++;
216 			break;
217 		case '%':
218 			switch (c = *f++)
219 			{
220 			case 'h':
221 			case 'l':
222 				q = c;
223 				c = *f++;
224 				break;
225 			default:
226 				q = 0;
227 				break;
228 			}
229 			switch (c)
230 			{
231 			case 0:
232 				f--;
233 				continue;
234 			case '%':
235 				if (*s++ != c)
236 					s = null;
237 				continue;
238 			case 'c':
239 				if (data[num].number = *s)
240 					s++;
241 				num++;
242 				break;
243 			case 'd':
244 				c = 10;
245 				goto number;
246 			case 'i':
247 				if (!*s)
248 					data[num].number = 0;
249 				else
250 				{
251 					strtoip4(s, &t, &a, NiL);
252 					data[num].number = a;
253 					s = t;
254 				}
255 				num++;
256 				break;
257 			case 'n':
258 			case 'u':
259 				c = 0;
260 				goto number;
261 			case 'o':
262 				c = 8;
263 				goto number;
264 			case 'x':
265 				c = 16;
266 			number:
267 				if (!*s)
268 					data[num].number = 0;
269 				else
270 				{
271 					data[num].number = strtol(s, &t, c);
272 					s = t;
273 				}
274 				num++;
275 				break;
276 			case 'f':
277 			case 'g':
278 				if (!*s)
279 					data[num].number = 0;
280 				else
281 				{
282 					data[num].number = strtod(s, &t);
283 					s = t;
284 				}
285 				num++;
286 				break;
287 			case 's':
288 				if (q = *f)
289 					f++;
290 				if (!*s)
291 				{
292 					data[num].string.data = null;
293 					data[num].string.size = 0;
294 				}
295 				else
296 					s = lextok(s, q, &data[num].string);
297 				num++;
298 				break;
299 			case 't':
300 				if (!*s)
301 					data[num].number = 0;
302 				else
303 				{
304 					data[num].number = tmdate(s, &t, NiL);
305 					if (*t && *t != *f && *t != '\n')
306 						data[num].number = strtol(s, &t, 0);
307 					s = t;
308 				}
309 				num++;
310 				break;
311 			}
312 			continue;
313 		case '\n':
314 			break;
315 		default:
316 			if (*s++ != c)
317 				s = null;
318 			continue;
319 		}
320 		break;
321 	}
322 	record->data = data;
323 	return 1;
324 }
325 
326 /*
327  * text writef
328  */
329 
330 static int
textwrite(Dssfile_t * file,Dssrecord_t * record,Dssdisc_t * disc)331 textwrite(Dssfile_t* file, Dssrecord_t* record, Dssdisc_t* disc)
332 {
333 	register Text_t*	text = (Text_t*)file->dss->meth->data;
334 	Cxvalue_t*		data = (Cxvalue_t*)record->data;
335 	register char*		f;
336 	register int		c;
337 	int			num;
338 
339 	num = 0;
340 	f = text->format;
341 	for (;;)
342 	{
343 		switch (c = *f++)
344 		{
345 		case 0:
346 			break;
347 		case ' ':
348 			sfputc(file->io, ' ');
349 			break;
350 		case '%':
351 			switch (c = *f++)
352 			{
353 			case 'h':
354 			case 'l':
355 				c = *f++;
356 				break;
357 			}
358 			switch (c)
359 			{
360 			case 0:
361 				f--;
362 				continue;
363 			case '%':
364 				sfputc(file->io, '%');
365 				continue;
366 			case 'c':
367 				sfputc(file->io, (int)data[num].number);
368 				num++;
369 				break;
370 			case 'd':
371 			case 'n':
372 			case 'u':
373 				sfprintf(file->io, "%d", (long)data[num].number);
374 				num++;
375 				break;
376 			case 'i':
377 				sfprintf(file->io, "%s", fmtip4((Ipaddr_t)data[num].number, -1));
378 				num++;
379 				break;
380 			case 'f':
381 			case 'g':
382 				sfprintf(file->io, "%Lg", data[num].number);
383 				num++;
384 				break;
385 			case 'o':
386 				sfprintf(file->io, "%o", (long)data[num].number);
387 				num++;
388 				break;
389 			case 'x':
390 				sfprintf(file->io, "%x", (long)data[num].number);
391 				num++;
392 				break;
393 			case 's':
394 				sfprintf(file->io, "%-.*s", data[num].string.size, data[num].string.data);
395 				num++;
396 				break;
397 			case 't':
398 				sfprintf(file->io, "%s", fmttime("%K", (time_t)data[num].number));
399 				num++;
400 				break;
401 			}
402 			continue;
403 		case '\n':
404 			break;
405 		default:
406 			sfputc(file->io, c);
407 			continue;
408 		}
409 		break;
410 	}
411 	sfputc(file->io, '\n');
412 	return 0;
413 }
414 
415 static Dssformat_t text_format =
416 {
417 	"text",
418 	"text format (2010-05-28)",
419 	CXH,
420 	textident,
421 	textfopen,
422 	textread,
423 	textwrite,
424 	0,
425 	textfclose,
426 	0,
427 	0,
428 	0
429 };
430 
431 static int
op_get(Cx_t * cx,Cxinstruction_t * pc,Cxoperand_t * r,Cxoperand_t * a,Cxoperand_t * b,void * data,Cxdisc_t * disc)432 op_get(Cx_t* cx, Cxinstruction_t* pc, Cxoperand_t* r, Cxoperand_t* a, Cxoperand_t* b, void* data, Cxdisc_t* disc)
433 {
434 	r->value = ((Cxvalue_t*)DSSDATA(data))[((Cxvariable_t*)pc->data.variable)->index];
435 	return 0;
436 }
437 
438 static Cxcallout_t local_callouts[] =
439 {
440 CXC(CX_GET, "void", "void", op_get, 0)
441 };
442 
443 /*
444  * methf
445  */
446 
447 static Dssmeth_t*
textmeth(const char * name,const char * options,const char * schema,Dssdisc_t * disc,Dssmeth_t * ometh)448 textmeth(const char* name, const char* options, const char* schema, Dssdisc_t* disc, Dssmeth_t* ometh)
449 {
450 	register Text_t*	text;
451 	register Dssmeth_t*	meth;
452 	register Cxvariable_t*	var;
453 	register char*		s;
454 	register char*		t;
455 	register char*		f;
456 	register int		c;
457 	char*			d;
458 	int			p;
459 	int			index;
460 
461 	if (options)
462 	{
463 		if (dssoptlib(ometh->cx->buf, &dss_lib_text, usage, disc))
464 			goto drop;
465 		s = sfstruse(ometh->cx->buf);
466 		for (;;)
467 		{
468 			switch (optstr(options, s))
469 			{
470 			case '?':
471 				if (disc->errorf)
472 					(*disc->errorf)(NiL, disc, ERROR_USAGE|4, "%s", opt_info.arg);
473 				goto drop;
474 			case ':':
475 				if (disc->errorf)
476 					(*disc->errorf)(NiL, disc, 2, "%s", opt_info.arg);
477 				goto drop;
478 			}
479 			break;
480 		}
481 	}
482 	if (!schema || !*schema)
483 		return ometh;
484 	if (!(meth = newof(0, Dssmeth_t, 1, sizeof(Text_t) + strlen(name) + 2 * strlen(schema) + 2)))
485 	{
486 		free(meth);
487 		if (disc->errorf)
488 			(*disc->errorf)(NiL, disc, 2, "out of space");
489 		return 0;
490 	}
491 	*meth = *ometh;
492 	meth->data = text = (Text_t*)(meth + 1);
493 	text->format = strcopy(text->name, name) + 1;
494 	index = 0;
495 	s = (char*)schema;
496 	f = text->format;
497 	for (;;)
498 	{
499 		switch (c = *s++)
500 		{
501 		case 0:
502 			break;
503 		case '%':
504 			*f++ = '%';
505 			var = 0;
506 			switch (c = *s++)
507 			{
508 			case 0:
509 				goto invalid;
510 			case 'h': case 'l': case 'L':
511 			case '+': case '-': case '.': case '_':
512 			case '0': case '1': case '2': case '3': case '4':
513 			case '5': case '6': case '7': case '8': case '9':
514 				continue;
515 			case '%':
516 				*f++ = '%';
517 				continue;
518 			case '(':
519 				t = f;
520 				d = 0;
521 				p = 1;
522 				for (;;)
523 				{
524 					switch (c = *s++)
525 					{
526 					case 0:
527 						goto invalid;
528 					case '(':
529 						p++;
530 						*t++ = c;
531 						continue;
532 					case ')':
533 						if (!--p)
534 							break;
535 						*t++ = c;
536 						continue;
537 					case ':':
538 						if (d)
539 							*t++ = c;
540 						else
541 						{
542 							*t++ = 0;
543 							d = t;
544 						}
545 						continue;
546 					default:
547 						*t++ = c;
548 						continue;
549 					}
550 					break;
551 				}
552 				*t = 0;
553 				if (dtmatch(meth->cx->variables, f))
554 				{
555 					if (disc->errorf)
556 						(*disc->errorf)(NiL, disc, 2, "%s: duplicate field", f);
557 					goto drop;
558 				}
559 				if (!(var = newof(0, Cxvariable_t, 1, t - f + 1)))
560 				{
561 					if (disc->errorf)
562 						(*disc->errorf)(NiL, disc, 2, "out of space");
563 					goto drop;
564 				}
565 				var->index = index;
566 				t = strcopy((char*)(var->name = (char*)(var + 1)), f);
567 				if (d)
568 					var->description = strcpy(t + 1, d);
569 				break;
570 			}
571 			for (;;)
572 			{
573 				switch (c = *s++)
574 				{
575 				case 0:
576 					goto invalid;
577 				case 'h': case 'l': case 'L':
578 				case '+': case '-': case '.': case '_':
579 				case '0': case '1': case '2': case '3': case '4':
580 				case '5': case '6': case '7': case '8': case '9':
581 					continue;
582 				}
583 				break;
584 			}
585 			if (var)
586 			{
587 				switch (c)
588 				{
589 				case 'd':
590 				case 'f':
591 				case 'g':
592 				case 'n':
593 				case 'o':
594 				case 'u':
595 				case 'x':
596 					var->type = (Cxtype_t*)"number";
597 					break;
598 				case 'i':
599 					var->type = (Cxtype_t*)"ipaddr_t";
600 					break;
601 				case 's':
602 					var->type = (Cxtype_t*)"string";
603 					break;
604 				case 't':
605 					var->type = (Cxtype_t*)"time_t";
606 					break;
607 				default:
608 					if (disc->errorf)
609 						(*disc->errorf)(NiL, disc, 2, "%c: invalid field format >>>%s", c, s - 1);
610 					goto drop;
611 				}
612 				if (cxaddvariable(meth->cx, var, disc))
613 					goto drop;
614 			}
615 			index++;
616 			*f++ = c;
617 			continue;
618 		case ' ':
619 		case '\t':
620 		case '\n':
621 			if (f == text->format || *(f - 1) != ' ')
622 				*f++ = ' ';
623 			continue;
624 		default:
625 			*f++ = c;
626 			continue;
627 		}
628 		break;
629 	}
630 	if (!(text->vars = index))
631 		goto invalid;
632 	*f = 0;
633 	dtinsert(meth->formats, &text_format);
634 	for (c = 0; c < elementsof(local_callouts); c++)
635 		if (cxaddcallout(meth->cx, &local_callouts[c], disc))
636 			return 0;
637 	return meth;
638  invalid:
639 	if (disc->errorf)
640 		(*disc->errorf)(NiL, disc, ERROR_SYSTEM|2, "%s: invalid schema", schema);
641  drop:
642 	free(meth);
643 	return 0;
644 }
645 
646 /*
647  * openf
648  */
649 
650 static int
textopen(Dss_t * dss,Dssdisc_t * disc)651 textopen(Dss_t* dss, Dssdisc_t* disc)
652 {
653 	return dss->meth->data ? 0 : -1;
654 }
655 
656 static Dssmeth_t	method =
657 {
658 	"text",
659 	"Newline-terminated field-delimited text file; the method schema is"
660 	" a scanf(3) like format string with embedded field names of the form:"
661 	" %(field1)format-char delimiter ...",
662 	CXH,
663 	textmeth,
664 	textopen,
665 	0,
666 	0
667 };
668 
669 Dsslib_t		dss_lib_text =
670 {
671 	"text",
672 	"text method"
673 	"[-1ls5Pp0?\n@(#)$Id: dss text method (AT&T Research) 2002-12-17 $\n]"
674 	USAGE_LICENSE,
675 	CXH,
676 	0,
677 	&method,
678 };
679