1/*
2 * Copyright (c) 1989, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Case Larsen.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 4. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#ifndef lint
34static const char copyright[] =
35"@(#) Copyright (c) 1989, 1993\n\
36	The Regents of the University of California.  All rights reserved.\n";
37#endif /* not lint */
38
39#ifndef lint
40#if 0
41static char sccsid[] = "@(#)uniq.c	8.3 (Berkeley) 5/4/95";
42#endif
43static const char rcsid[] =
44  "$FreeBSD$";
45#endif /* not lint */
46
47#include <ctype.h>
48#include <err.h>
49#include <limits.h>
50#include <locale.h>
51#include <stdint.h>
52#define _WITH_GETLINE
53#include <stdio.h>
54#include <stdlib.h>
55#include <string.h>
56#include <unistd.h>
57#include <wchar.h>
58#include <wctype.h>
59
60int cflag, dflag, uflag, iflag;
61int numchars, numfields, repeats;
62
63FILE	*file(const char *, const char *);
64wchar_t	*convert(const char *);
65int	 inlcmp(const char *, const char *);
66void	 show(FILE *, const char *);
67wchar_t	*skip(wchar_t *);
68void	 obsolete(char *[]);
69static void	 usage(void);
70
71int
72main (int argc, char *argv[])
73{
74	wchar_t *tprev, *tthis;
75	FILE *ifp, *ofp;
76	int ch, comp;
77	size_t prevbuflen, thisbuflen, b1;
78	char *prevline, *thisline, *p;
79	const char *ifn;
80
81	(void) setlocale(LC_ALL, "");
82
83	obsolete(argv);
84	while ((ch = getopt(argc, argv, "cdif:s:u")) != -1)
85		switch (ch) {
86		case 'c':
87			cflag = 1;
88			break;
89		case 'd':
90			dflag = 1;
91			break;
92		case 'i':
93			iflag = 1;
94			break;
95		case 'f':
96			numfields = strtol(optarg, &p, 10);
97			if (numfields < 0 || *p)
98				errx(1, "illegal field skip value: %s", optarg);
99			break;
100		case 's':
101			numchars = strtol(optarg, &p, 10);
102			if (numchars < 0 || *p)
103				errx(1, "illegal character skip value: %s", optarg);
104			break;
105		case 'u':
106			uflag = 1;
107			break;
108		case '?':
109		default:
110			usage();
111		}
112
113	argc -= optind;
114	argv += optind;
115
116	/* If no flags are set, default is -d -u. */
117	if (cflag) {
118		if (dflag || uflag)
119			usage();
120	} else if (!dflag && !uflag)
121		dflag = uflag = 1;
122
123	if (argc > 2)
124		usage();
125
126	ifp = stdin;
127	ifn = "stdin";
128	ofp = stdout;
129	if (argc > 0 && strcmp(argv[0], "-") != 0)
130		ifp = file(ifn = argv[0], "r");
131	if (argc > 1)
132		ofp = file(argv[1], "w");
133
134	prevbuflen = thisbuflen = 0;
135	prevline = thisline = NULL;
136
137	if (getline(&prevline, &prevbuflen, ifp) < 0) {
138		if (ferror(ifp))
139			err(1, "%s", ifn);
140		exit(0);
141	}
142	tprev = convert(prevline);
143
144	if (!cflag && uflag && dflag)
145		show(ofp, prevline);
146
147	tthis = NULL;
148	while (getline(&thisline, &thisbuflen, ifp) >= 0) {
149		if (tthis != NULL)
150			free(tthis);
151		tthis = convert(thisline);
152
153		if (tthis == NULL && tprev == NULL)
154			comp = inlcmp(thisline, prevline);
155		else if (tthis == NULL || tprev == NULL)
156			comp = 1;
157		else
158			comp = wcscoll(tthis, tprev);
159
160		if (comp) {
161			/* If different, print; set previous to new value. */
162			if (cflag || !dflag || !uflag)
163				show(ofp, prevline);
164			p = prevline;
165			b1 = prevbuflen;
166			prevline = thisline;
167			prevbuflen = thisbuflen;
168			if (tprev != NULL)
169				free(tprev);
170			tprev = tthis;
171			if (!cflag && uflag && dflag)
172				show(ofp, prevline);
173			thisline = p;
174			thisbuflen = b1;
175			tthis = NULL;
176			repeats = 0;
177		} else
178			++repeats;
179	}
180	if (ferror(ifp))
181		err(1, "%s", ifn);
182	if (cflag || !dflag || !uflag)
183		show(ofp, prevline);
184	exit(0);
185}
186
187wchar_t *
188convert(const char *str)
189{
190	size_t n;
191	wchar_t *buf, *ret, *p;
192
193	if ((n = mbstowcs(NULL, str, 0)) == (size_t)-1)
194		return (NULL);
195	if (SIZE_MAX / sizeof(*buf) < n + 1)
196		errx(1, "conversion buffer length overflow");
197	if ((buf = malloc((n + 1) * sizeof(*buf))) == NULL)
198		err(1, "malloc");
199	if (mbstowcs(buf, str, n + 1) != n)
200		errx(1, "internal mbstowcs() error");
201	/* The last line may not end with \n. */
202	if (n > 0 && buf[n - 1] == L'\n')
203		buf[n - 1] = L'\0';
204
205	/* If requested get the chosen fields + character offsets. */
206	if (numfields || numchars) {
207		if ((ret = wcsdup(skip(buf))) == NULL)
208			err(1, "wcsdup");
209		free(buf);
210	} else
211		ret = buf;
212
213	if (iflag) {
214		for (p = ret; *p != L'\0'; p++)
215			*p = towlower(*p);
216	}
217
218	return (ret);
219}
220
221int
222inlcmp(const char *s1, const char *s2)
223{
224	int c1, c2;
225
226	while (*s1 == *s2++)
227		if (*s1++ == '\0')
228			return (0);
229	c1 = (unsigned char)*s1;
230	c2 = (unsigned char)*(s2 - 1);
231	/* The last line may not end with \n. */
232	if (c1 == '\n')
233		c1 = '\0';
234	if (c2 == '\n')
235		c2 = '\0';
236	return (c1 - c2);
237}
238
239/*
240 * show --
241 *	Output a line depending on the flags and number of repetitions
242 *	of the line.
243 */
244void
245show(FILE *ofp, const char *str)
246{
247
248	if (cflag)
249		(void)fprintf(ofp, "%4d %s", repeats + 1, str);
250	if ((dflag && repeats) || (uflag && !repeats))
251		(void)fprintf(ofp, "%s", str);
252}
253
254wchar_t *
255skip(wchar_t *str)
256{
257	int nchars, nfields;
258
259	for (nfields = 0; *str != L'\0' && nfields++ != numfields; ) {
260		while (iswblank(*str))
261			str++;
262		while (*str != L'\0' && !iswblank(*str))
263			str++;
264	}
265	for (nchars = numchars; nchars-- && *str != L'\0'; ++str)
266		;
267	return(str);
268}
269
270FILE *
271file(const char *name, const char *mode)
272{
273	FILE *fp;
274
275	if ((fp = fopen(name, mode)) == NULL)
276		err(1, "%s", name);
277	return(fp);
278}
279
280void
281obsolete(char *argv[])
282{
283	int len;
284	char *ap, *p, *start;
285
286	while ((ap = *++argv)) {
287		/* Return if "--" or not an option of any form. */
288		if (ap[0] != '-') {
289			if (ap[0] != '+')
290				return;
291		} else if (ap[1] == '-')
292			return;
293		if (!isdigit((unsigned char)ap[1]))
294			continue;
295		/*
296		 * Digit signifies an old-style option.  Malloc space for dash,
297		 * new option and argument.
298		 */
299		len = strlen(ap);
300		if ((start = p = malloc(len + 3)) == NULL)
301			err(1, "malloc");
302		*p++ = '-';
303		*p++ = ap[0] == '+' ? 's' : 'f';
304		(void)strcpy(p, ap + 1);
305		*argv = start;
306	}
307}
308
309static void
310usage(void)
311{
312	(void)fprintf(stderr,
313"usage: uniq [-c | -d | -u] [-i] [-f fields] [-s chars] [input [output]]\n");
314	exit(1);
315}
316