join.c revision 216370
174011Sjhb/*-
268685Sjhb * Copyright (c) 1991, 1993, 1994
368685Sjhb *	The Regents of the University of California.  All rights reserved.
468685Sjhb *
568685Sjhb * This code is derived from software contributed to Berkeley by
668685Sjhb * Steve Hayman of the Computer Science Department, Indiana University,
768685Sjhb * Michiro Hikida and David Goodenough.
868685Sjhb *
968685Sjhb * Redistribution and use in source and binary forms, with or without
1068685Sjhb * modification, are permitted provided that the following conditions
1168685Sjhb * are met:
1268685Sjhb * 1. Redistributions of source code must retain the above copyright
1368685Sjhb *    notice, this list of conditions and the following disclaimer.
1468685Sjhb * 2. Redistributions in binary form must reproduce the above copyright
1568685Sjhb *    notice, this list of conditions and the following disclaimer in the
1668685Sjhb *    documentation and/or other materials provided with the distribution.
1768685Sjhb * 4. Neither the name of the University nor the names of its contributors
1868685Sjhb *    may be used to endorse or promote products derived from this software
1968685Sjhb *    without specific prior written permission.
2068685Sjhb *
2168685Sjhb * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2268685Sjhb * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2368685Sjhb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2468685Sjhb * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2568685Sjhb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26254617Sjkim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27206622Suqs * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2868685Sjhb * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2968685Sjhb * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3068685Sjhb * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3168685Sjhb * SUCH DAMAGE.
3268685Sjhb */
33150628Sjhb
3468685Sjhb#ifndef lint
3568685Sjhbstatic const char copyright[] =
3668685Sjhb"@(#) Copyright (c) 1991, 1993, 1994\n\
3768685Sjhb	The Regents of the University of California.  All rights reserved.\n";
3868685Sjhb#endif /* not lint */
3968685Sjhb
4068685Sjhb#ifndef lint
4184306Sru#if 0
4284306Srustatic char sccsid[] = "@(#)join.c	8.6 (Berkeley) 5/4/95";
4368685Sjhb#endif
4489462Sru#endif /* not lint */
4568685Sjhb#include <sys/cdefs.h>
4689462Sru__FBSDID("$FreeBSD: head/usr.bin/join/join.c 216370 2010-12-11 08:32:16Z joel $");
4768685Sjhb
4889462Sru#include <sys/param.h>
4989462Sru
5089462Sru#include <err.h>
5189462Sru#include <errno.h>
5268685Sjhb#include <limits.h>
5389462Sru#include <locale.h>
54150628Sjhb#include <stdio.h>
55150628Sjhb#include <stdlib.h>
5689462Sru#include <string.h>
5789462Sru#include <unistd.h>
5889462Sru#include <wchar.h>
5968685Sjhb
6089462Sru/*
6168685Sjhb * There's a structure per input file which encapsulates the state of the
6289462Sru * file.  We repeatedly read lines from each file until we've read in all
6368685Sjhb * the consecutive lines from the file with a common join field.  Then we
6489462Sru * compare the set of lines with an equivalent set from the other file.
65254617Sjkim */
66254617Sjkimtypedef struct {
67254617Sjkim	char *line;		/* line */
68254617Sjkim	u_long linealloc;	/* line allocated count */
6968685Sjhb	char **fields;		/* line field(s) */
7068685Sjhb	u_long fieldcnt;	/* line field(s) count */
7168685Sjhb	u_long fieldalloc;	/* line field(s) allocated count */
7268685Sjhb} LINE;
7368685Sjhb
7468685Sjhbtypedef struct {
7589192Sru	FILE *fp;		/* file descriptor */
76115440Shmp	u_long joinf;		/* join field (-1, -2, -j) */
7768685Sjhb	int unpair;		/* output unpairable lines (-a) */
7868685Sjhb	u_long number;		/* 1 for file 1, 2 for file 2 */
7989192Sru
8089192Sru	LINE *set;		/* set of lines with same field */
8189192Sru	int pushbool;		/* if pushback is set */
8268685Sjhb	u_long pushback;	/* line on the stack */
8389192Sru	u_long setcnt;		/* set count */
8468685Sjhb	u_long setalloc;	/* set allocated count */
8589192Sru} INPUT;
8668685SjhbINPUT input1 = { NULL, 0, 0, 1, NULL, 0, 0, 0, 0 },
8789192Sru      input2 = { NULL, 0, 0, 2, NULL, 0, 0, 0, 0 };
8868685Sjhb
8989192Srutypedef struct {
9068685Sjhb	u_long	filenum;	/* file number */
9168685Sjhb	u_long	fieldno;	/* field number */
9268685Sjhb} OLIST;
9368685SjhbOLIST *olist;			/* output field list */
9468685Sjhbu_long olistcnt;		/* output field list count */
9587999Sjakeu_long olistalloc;		/* output field allocated count */
9689192Sru
9789192Sruint joinout = 1;		/* show lines with matched join fields (-v) */
9889192Sruint needsep;			/* need separator character */
9989192Sruint spans = 1;			/* span multiple delimiters (-t) */
10089192Sruchar *empty;			/* empty field replacement string (-e) */
10187999Sjakestatic wchar_t default_tabchar[] = L" \t";
10289192Sruwchar_t *tabchar = default_tabchar;/* delimiter characters (-t) */
10387999Sjake
10489192Sruint  cmp(LINE *, u_long, LINE *, u_long);
10587999Sjakevoid fieldarg(char *);
10689192Sruvoid joinlines(INPUT *, INPUT *);
10787999Sjakeint  mbscoll(const char *, const char *);
10887999Sjakechar *mbssep(char **, const wchar_t *);
10987999Sjakevoid obsolete(char **);
11087999Sjakevoid outfield(LINE *, u_long, int);
11187999Sjakevoid outoneline(INPUT *, LINE *);
11268685Sjhbvoid outtwoline(INPUT *, LINE *, INPUT *, LINE *);
11389192Sruvoid slurp(INPUT *);
11468685Sjhbwchar_t *towcs(const char *);
11568685Sjhbvoid usage(void);
11668685Sjhb
11789192Sruint
11868685Sjhbmain(int argc, char *argv[])
11968685Sjhb{
12068685Sjhb	INPUT *F1, *F2;
12168685Sjhb	int aflag, ch, cval, vflag;
12268685Sjhb	char *end;
12389192Sru
12468685Sjhb	setlocale(LC_ALL, "");
12568685Sjhb
12668685Sjhb	F1 = &input1;
12768685Sjhb	F2 = &input2;
12868685Sjhb
12968685Sjhb	aflag = vflag = 0;
13068685Sjhb	obsolete(argv);
13168685Sjhb	while ((ch = getopt(argc, argv, "\01a:e:j:1:2:o:t:v:")) != -1) {
13289192Sru		switch (ch) {
13368685Sjhb		case '\01':		/* See comment in obsolete(). */
134115440Shmp			aflag = 1;
13568685Sjhb			F1->unpair = F2->unpair = 1;
13668685Sjhb			break;
13768685Sjhb		case '1':
13868685Sjhb			if ((F1->joinf = strtol(optarg, &end, 10)) < 1)
13968685Sjhb				errx(1, "-1 option field number less than 1");
14068685Sjhb			if (*end)
14168685Sjhb				errx(1, "illegal field number -- %s", optarg);
14268685Sjhb			--F1->joinf;
14368685Sjhb			break;
14468685Sjhb		case '2':
14568685Sjhb			if ((F2->joinf = strtol(optarg, &end, 10)) < 1)
14689192Sru				errx(1, "-2 option field number less than 1");
14768685Sjhb			if (*end)
148115440Shmp				errx(1, "illegal field number -- %s", optarg);
14968685Sjhb			--F2->joinf;
15068685Sjhb			break;
15168685Sjhb		case 'a':
15268685Sjhb			aflag = 1;
15368685Sjhb			switch(strtol(optarg, &end, 10)) {
15468685Sjhb			case 1:
15568685Sjhb				F1->unpair = 1;
15668685Sjhb				break;
15768685Sjhb			case 2:
15868685Sjhb				F2->unpair = 1;
15968685Sjhb				break;
16068685Sjhb			default:
16168685Sjhb				errx(1, "-a option file number not 1 or 2");
16268685Sjhb				break;
16368685Sjhb			}
16479687Sschweikh			if (*end)
16568685Sjhb				errx(1, "illegal file number -- %s", optarg);
16668685Sjhb			break;
16768685Sjhb		case 'e':
16868685Sjhb			empty = optarg;
16968685Sjhb			break;
17068685Sjhb		case 'j':
17168685Sjhb			if ((F1->joinf = F2->joinf =
17268685Sjhb			    strtol(optarg, &end, 10)) < 1)
17368685Sjhb				errx(1, "-j option field number less than 1");
17468685Sjhb			if (*end)
17568685Sjhb				errx(1, "illegal field number -- %s", optarg);
17668685Sjhb			--F1->joinf;
17768685Sjhb			--F2->joinf;
17868685Sjhb			break;
17989192Sru		case 'o':
18089192Sru			fieldarg(optarg);
18168685Sjhb			break;
18268685Sjhb		case 't':
18389192Sru			spans = 0;
18489192Sru			if (mbrtowc(&tabchar[0], optarg, MB_LEN_MAX, NULL) !=
18568685Sjhb			    strlen(optarg))
18668685Sjhb				errx(1, "illegal tab character specification");
18789192Sru			tabchar[1] = L'\0';
18889192Sru			break;
18968685Sjhb		case 'v':
19068685Sjhb			vflag = 1;
191254617Sjkim			joinout = 0;
19268685Sjhb			switch (strtol(optarg, &end, 10)) {
193254617Sjkim			case 1:
19468685Sjhb				F1->unpair = 1;
19568685Sjhb				break;
19668685Sjhb			case 2:
19768685Sjhb				F2->unpair = 1;
19868685Sjhb				break;
19989192Sru			default:
20089192Sru				errx(1, "-v option file number not 1 or 2");
20189192Sru				break;
20289192Sru			}
20389192Sru			if (*end)
20489192Sru				errx(1, "illegal file number -- %s", optarg);
20568685Sjhb			break;
206150628Sjhb		case '?':
207150628Sjhb		default:
208150628Sjhb			usage();
209150628Sjhb		}
210254617Sjkim	}
211150628Sjhb	argc -= optind;
212150628Sjhb	argv += optind;
213150628Sjhb
214150628Sjhb	if (aflag && vflag)
215150628Sjhb		errx(1, "the -a and -v options are mutually exclusive");
216150628Sjhb
217177276Spjd	if (argc != 2)
218177276Spjd		usage();
219150628Sjhb
220150628Sjhb	/* Open the files; "-" means stdin. */
221150628Sjhb	if (!strcmp(*argv, "-"))
222150628Sjhb		F1->fp = stdin;
223254617Sjkim	else if ((F1->fp = fopen(*argv, "r")) == NULL)
22489192Sru		err(1, "%s", *argv);
225254617Sjkim	++argv;
22668685Sjhb	if (!strcmp(*argv, "-"))
22768685Sjhb		F2->fp = stdin;
22868685Sjhb	else if ((F2->fp = fopen(*argv, "r")) == NULL)
22968685Sjhb		err(1, "%s", *argv);
23068685Sjhb	if (F1->fp == stdin && F2->fp == stdin)
231192536Sjhb		errx(1, "only one input file may be stdin");
23268685Sjhb
233254617Sjkim	slurp(F1);
23489192Sru	slurp(F2);
235254617Sjkim	while (F1->setcnt && F2->setcnt) {
236254617Sjkim		cval = cmp(F1->set, F1->joinf, F2->set, F2->joinf);
237254617Sjkim		if (cval == 0) {
23868685Sjhb			/* Oh joy, oh rapture, oh beauty divine! */
23968685Sjhb			if (joinout)
24068685Sjhb				joinlines(F1, F2);
24168685Sjhb			slurp(F1);
24268685Sjhb			slurp(F2);
24389192Sru		} else if (cval < 0) {
24489192Sru			/* File 1 takes the lead... */
24589192Sru			if (F1->unpair)
24689192Sru				joinlines(F1, NULL);
24789192Sru			slurp(F1);
24889192Sru		} else {
24989192Sru			/* File 2 takes the lead... */
250254617Sjkim			if (F2->unpair)
25168685Sjhb				joinlines(F2, NULL);
25289192Sru			slurp(F2);
25389192Sru		}
25468685Sjhb	}
25568685Sjhb
25689192Sru	/*
25789192Sru	 * Now that one of the files is used up, optionally output any
25868685Sjhb	 * remaining lines from the other file.
25968685Sjhb	 */
26089192Sru	if (F1->unpair)
26189192Sru		while (F1->setcnt) {
26268685Sjhb			joinlines(F1, NULL);
26368685Sjhb			slurp(F1);
26468685Sjhb		}
26568685Sjhb	if (F2->unpair)
26668685Sjhb		while (F2->setcnt) {
26768685Sjhb			joinlines(F2, NULL);
268192536Sjhb			slurp(F2);
269254617Sjkim		}
270254617Sjkim	exit(0);
271254617Sjkim}
272254617Sjkim
273254617Sjkimvoid
274254617Sjkimslurp(INPUT *F)
275254617Sjkim{
276254617Sjkim	LINE *lp, *lastlp, tmp;
277254617Sjkim	size_t len;
278254617Sjkim	int cnt;
279254617Sjkim	char *bp, *fieldp;
280254617Sjkim
281254617Sjkim	/*
282254617Sjkim	 * Read all of the lines from an input file that have the same
283254617Sjkim	 * join field.
284254617Sjkim	 */
285254617Sjkim	F->setcnt = 0;
286254617Sjkim	for (lastlp = NULL;; ++F->setcnt) {
287254617Sjkim		/*
288254617Sjkim		 * If we're out of space to hold line structures, allocate
289254617Sjkim		 * more.  Initialize the structure so that we know that this
290254617Sjkim		 * is new space.
291254617Sjkim		 */
292254617Sjkim		if (F->setcnt == F->setalloc) {
293254617Sjkim			cnt = F->setalloc;
294254617Sjkim			F->setalloc += 50;
295254617Sjkim			if ((F->set = realloc(F->set,
296254617Sjkim			    F->setalloc * sizeof(LINE))) == NULL)
297254617Sjkim				err(1, NULL);
298254617Sjkim			memset(F->set + cnt, 0, 50 * sizeof(LINE));
299254617Sjkim
300254617Sjkim			/* re-set lastlp in case it moved */
301254617Sjkim			if (lastlp != NULL)
302254617Sjkim				lastlp = &F->set[F->setcnt - 1];
303254617Sjkim		}
304254617Sjkim
305254617Sjkim		/*
306254617Sjkim		 * Get any pushed back line, else get the next line.  Allocate
30768685Sjhb		 * space as necessary.  If taking the line from the stack swap
30889192Sru		 * the two structures so that we don't lose space allocated to
30968685Sjhb		 * either structure.  This could be avoided by doing another
310150628Sjhb		 * level of indirection, but it's probably okay as is.
311150628Sjhb		 */
312150628Sjhb		lp = &F->set[F->setcnt];
313150628Sjhb		if (F->setcnt)
314150628Sjhb			lastlp = &F->set[F->setcnt - 1];
31568685Sjhb		if (F->pushbool) {
31689192Sru			tmp = F->set[F->setcnt];
31768685Sjhb			F->set[F->setcnt] = F->set[F->pushback];
318254617Sjkim			F->set[F->pushback] = tmp;
31989192Sru			F->pushbool = 0;
320150628Sjhb			continue;
321150628Sjhb		}
322254617Sjkim		if ((bp = fgetln(F->fp, &len)) == NULL)
32368685Sjhb			return;
324254617Sjkim		if (lp->linealloc <= len + 1) {
325254617Sjkim			lp->linealloc += MAX(100, len + 1 - lp->linealloc);
326254617Sjkim			if ((lp->line =
327254617Sjkim			    realloc(lp->line, lp->linealloc)) == NULL)
328254617Sjkim				err(1, NULL);
32968685Sjhb		}
33068685Sjhb		memmove(lp->line, bp, len);
33168685Sjhb
33268685Sjhb		/* Replace trailing newline, if it exists. */
33368685Sjhb		if (bp[len - 1] == '\n')
33468685Sjhb			lp->line[len - 1] = '\0';
33568685Sjhb		else
33668685Sjhb			lp->line[len] = '\0';
33768685Sjhb		bp = lp->line;
33889192Sru
33968685Sjhb		/* Split the line into fields, allocate space as necessary. */
34089192Sru		lp->fieldcnt = 0;
34168685Sjhb		while ((fieldp = mbssep(&bp, tabchar)) != NULL) {
34268685Sjhb			if (spans && *fieldp == '\0')
343150628Sjhb				continue;
34468685Sjhb			if (lp->fieldcnt == lp->fieldalloc) {
345150628Sjhb				lp->fieldalloc += 50;
34668685Sjhb				if ((lp->fields = realloc(lp->fields,
34768685Sjhb				    lp->fieldalloc * sizeof(char *))) == NULL)
348150628Sjhb					err(1, NULL);
349150628Sjhb			}
350150628Sjhb			lp->fields[lp->fieldcnt++] = fieldp;
35168685Sjhb		}
352150628Sjhb
353150628Sjhb		/* See if the join field value has changed. */
35468685Sjhb		if (lastlp != NULL && cmp(lp, F->joinf, lastlp, F->joinf)) {
35568685Sjhb			F->pushbool = 1;
35668685Sjhb			F->pushback = F->setcnt;
35768685Sjhb			break;
35868685Sjhb		}
35968685Sjhb	}
36068685Sjhb}
36168685Sjhb
36268685Sjhbchar *
36368685Sjhbmbssep(char **stringp, const wchar_t *delim)
36468685Sjhb{
36568685Sjhb	char *s, *tok;
36668685Sjhb	const wchar_t *spanp;
36768685Sjhb	wchar_t c, sc;
36868685Sjhb	size_t n;
36968685Sjhb
37089192Sru	if ((s = *stringp) == NULL)
37189192Sru		return (NULL);
37289192Sru	for (tok = s;;) {
37389192Sru		n = mbrtowc(&c, s, MB_LEN_MAX, NULL);
37489192Sru		if (n == (size_t)-1 || n == (size_t)-2)
37589192Sru			errc(1, EILSEQ, NULL);	/* XXX */
37668685Sjhb		s += n;
37768685Sjhb		spanp = delim;
37868685Sjhb		do {
37968685Sjhb			if ((sc = *spanp++) == c) {
38068685Sjhb				if (c == 0)
38168685Sjhb					s = NULL;
38268685Sjhb				else
38368685Sjhb					s[-n] = '\0';
38489192Sru				*stringp = s;
38589192Sru				return (tok);
38689192Sru			}
38789192Sru		} while (sc != 0);
38889192Sru	}
38989192Sru}
390150628Sjhb
39189192Sruint
39268685Sjhbcmp(LINE *lp1, u_long fieldno1, LINE *lp2, u_long fieldno2)
39368685Sjhb{
39468685Sjhb	if (lp1->fieldcnt <= fieldno1)
395150628Sjhb		return (lp2->fieldcnt <= fieldno2 ? 0 : 1);
396150628Sjhb	if (lp2->fieldcnt <= fieldno2)
397150628Sjhb		return (-1);
398150628Sjhb	return (mbscoll(lp1->fields[fieldno1], lp2->fields[fieldno2]));
399254617Sjkim}
400254617Sjkim
401254617Sjkimint
402254617Sjkimmbscoll(const char *s1, const char *s2)
403254617Sjkim{
404254617Sjkim	wchar_t *w1, *w2;
405	int ret;
406
407	if (MB_CUR_MAX == 1)
408		return (strcoll(s1, s2));
409	if ((w1 = towcs(s1)) == NULL || (w2 = towcs(s2)) == NULL)
410		err(1, NULL);	/* XXX */
411	ret = wcscoll(w1, w2);
412	free(w1);
413	free(w2);
414	return (ret);
415}
416
417wchar_t *
418towcs(const char *s)
419{
420	wchar_t *wcs;
421	size_t n;
422
423	if ((n = mbsrtowcs(NULL, &s, 0, NULL)) == (size_t)-1)
424		return (NULL);
425	if ((wcs = malloc((n + 1) * sizeof(*wcs))) == NULL)
426		return (NULL);
427	mbsrtowcs(wcs, &s, n + 1, NULL);
428	return (wcs);
429}
430
431void
432joinlines(INPUT *F1, INPUT *F2)
433{
434	u_long cnt1, cnt2;
435
436	/*
437	 * Output the results of a join comparison.  The output may be from
438	 * either file 1 or file 2 (in which case the first argument is the
439	 * file from which to output) or from both.
440	 */
441	if (F2 == NULL) {
442		for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
443			outoneline(F1, &F1->set[cnt1]);
444		return;
445	}
446	for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
447		for (cnt2 = 0; cnt2 < F2->setcnt; ++cnt2)
448			outtwoline(F1, &F1->set[cnt1], F2, &F2->set[cnt2]);
449}
450
451void
452outoneline(INPUT *F, LINE *lp)
453{
454	u_long cnt;
455
456	/*
457	 * Output a single line from one of the files, according to the
458	 * join rules.  This happens when we are writing unmatched single
459	 * lines.  Output empty fields in the right places.
460	 */
461	if (olist)
462		for (cnt = 0; cnt < olistcnt; ++cnt) {
463			if (olist[cnt].filenum == (unsigned)F->number)
464				outfield(lp, olist[cnt].fieldno, 0);
465			else if (olist[cnt].filenum == 0)
466				outfield(lp, F->joinf, 0);
467			else
468				outfield(lp, 0, 1);
469		}
470	else
471		for (cnt = 0; cnt < lp->fieldcnt; ++cnt)
472			outfield(lp, cnt, 0);
473	(void)printf("\n");
474	if (ferror(stdout))
475		err(1, "stdout");
476	needsep = 0;
477}
478
479void
480outtwoline(INPUT *F1, LINE *lp1, INPUT *F2, LINE *lp2)
481{
482	u_long cnt;
483
484	/* Output a pair of lines according to the join list (if any). */
485	if (olist)
486		for (cnt = 0; cnt < olistcnt; ++cnt)
487			if (olist[cnt].filenum == 0) {
488				if (lp1->fieldcnt >= F1->joinf)
489					outfield(lp1, F1->joinf, 0);
490				else
491					outfield(lp2, F2->joinf, 0);
492			} else if (olist[cnt].filenum == 1)
493				outfield(lp1, olist[cnt].fieldno, 0);
494			else /* if (olist[cnt].filenum == 2) */
495				outfield(lp2, olist[cnt].fieldno, 0);
496	else {
497		/*
498		 * Output the join field, then the remaining fields from F1
499		 * and F2.
500		 */
501		outfield(lp1, F1->joinf, 0);
502		for (cnt = 0; cnt < lp1->fieldcnt; ++cnt)
503			if (F1->joinf != cnt)
504				outfield(lp1, cnt, 0);
505		for (cnt = 0; cnt < lp2->fieldcnt; ++cnt)
506			if (F2->joinf != cnt)
507				outfield(lp2, cnt, 0);
508	}
509	(void)printf("\n");
510	if (ferror(stdout))
511		err(1, "stdout");
512	needsep = 0;
513}
514
515void
516outfield(LINE *lp, u_long fieldno, int out_empty)
517{
518	if (needsep++)
519		(void)printf("%lc", *tabchar);
520	if (!ferror(stdout)) {
521		if (lp->fieldcnt <= fieldno || out_empty) {
522			if (empty != NULL)
523				(void)printf("%s", empty);
524		} else {
525			if (*lp->fields[fieldno] == '\0')
526				return;
527			(void)printf("%s", lp->fields[fieldno]);
528		}
529	}
530	if (ferror(stdout))
531		err(1, "stdout");
532}
533
534/*
535 * Convert an output list argument "2.1, 1.3, 2.4" into an array of output
536 * fields.
537 */
538void
539fieldarg(char *option)
540{
541	u_long fieldno, filenum;
542	char *end, *token;
543
544	while ((token = strsep(&option, ", \t")) != NULL) {
545		if (*token == '\0')
546			continue;
547		if (token[0] == '0')
548			filenum = fieldno = 0;
549		else if ((token[0] == '1' || token[0] == '2') &&
550		    token[1] == '.') {
551			filenum = token[0] - '0';
552			fieldno = strtol(token + 2, &end, 10);
553			if (*end)
554				errx(1, "malformed -o option field");
555			if (fieldno == 0)
556				errx(1, "field numbers are 1 based");
557			--fieldno;
558		} else
559			errx(1, "malformed -o option field");
560		if (olistcnt == olistalloc) {
561			olistalloc += 50;
562			if ((olist = realloc(olist,
563			    olistalloc * sizeof(OLIST))) == NULL)
564				err(1, NULL);
565		}
566		olist[olistcnt].filenum = filenum;
567		olist[olistcnt].fieldno = fieldno;
568		++olistcnt;
569	}
570}
571
572void
573obsolete(char **argv)
574{
575	size_t len;
576	char **p, *ap, *t;
577
578	while ((ap = *++argv) != NULL) {
579		/* Return if "--". */
580		if (ap[0] == '-' && ap[1] == '-')
581			return;
582		/* skip if not an option */
583		if (ap[0] != '-')
584			continue;
585		switch (ap[1]) {
586		case 'a':
587			/*
588			 * The original join allowed "-a", which meant the
589			 * same as -a1 plus -a2.  POSIX 1003.2, Draft 11.2
590			 * only specifies this as "-a 1" and "a -2", so we
591			 * have to use another option flag, one that is
592			 * unlikely to ever be used or accidentally entered
593			 * on the command line.  (Well, we could reallocate
594			 * the argv array, but that hardly seems worthwhile.)
595			 */
596			if (ap[2] == '\0' && (argv[1] == NULL ||
597			    (strcmp(argv[1], "1") != 0 &&
598			    strcmp(argv[1], "2") != 0))) {
599				ap[1] = '\01';
600				warnx("-a option used without an argument; "
601				    "reverting to historical behavior");
602			}
603			break;
604		case 'j':
605			/*
606			 * The original join allowed "-j[12] arg" and "-j arg".
607			 * Convert the former to "-[12] arg".  Don't convert
608			 * the latter since getopt(3) can handle it.
609			 */
610			switch(ap[2]) {
611			case '1':
612				if (ap[3] != '\0')
613					goto jbad;
614				ap[1] = '1';
615				ap[2] = '\0';
616				break;
617			case '2':
618				if (ap[3] != '\0')
619					goto jbad;
620				ap[1] = '2';
621				ap[2] = '\0';
622				break;
623			case '\0':
624				break;
625			default:
626jbad:				errx(1, "illegal option -- %s", ap);
627				usage();
628			}
629			break;
630		case 'o':
631			/*
632			 * The original join allowed "-o arg arg".
633			 * Convert to "-o arg -o arg".
634			 */
635			if (ap[2] != '\0')
636				break;
637			for (p = argv + 2; *p; ++p) {
638				if (p[0][0] == '0' || ((p[0][0] != '1' &&
639				    p[0][0] != '2') || p[0][1] != '.'))
640					break;
641				len = strlen(*p);
642				if (len - 2 != strspn(*p + 2, "0123456789"))
643					break;
644				if ((t = malloc(len + 3)) == NULL)
645					err(1, NULL);
646				t[0] = '-';
647				t[1] = 'o';
648				memmove(t + 2, *p, len + 1);
649				*p = t;
650			}
651			argv = p - 1;
652			break;
653		}
654	}
655}
656
657void
658usage(void)
659{
660	(void)fprintf(stderr, "%s %s\n%s\n",
661	    "usage: join [-a fileno | -v fileno ] [-e string] [-1 field]",
662	    "[-2 field]",
663		"            [-o list] [-t char] file1 file2");
664	exit(1);
665}
666