1 /* @(#)paste.c	1.22 21/08/20 Copyright 1985-2021 J. Schilling */
2 #include <schily/mconfig.h>
3 #ifndef lint
4 static	const char sccsid[] =
5 	"@(#)paste.c	1.22 21/08/20 Copyright 1985-2021 J. Schilling";
6 #endif
7 /*
8  *	Paste some files together
9  *
10  *	Copyright (c) 1985-2021 J. Schilling
11  */
12 /*
13  * The contents of this file are subject to the terms of the
14  * Common Development and Distribution License, Version 1.0 only
15  * (the "License").  You may not use this file except in compliance
16  * with the License.
17  *
18  * See the file CDDL.Schily.txt in this distribution for details.
19  * A copy of the CDDL is also available via the Internet at
20  * http://www.opensource.org/licenses/cddl1.txt
21  *
22  * When distributing Covered Code, include this CDDL HEADER in each
23  * file and include the License file CDDL.Schily.txt from this distribution.
24  */
25 
26 #include <schily/mconfig.h>
27 #include <schily/stdio.h>
28 #include <schily/stdlib.h>
29 #include <schily/unistd.h>	/* For sys/types.h to make off_t available */
30 #include <schily/standard.h>
31 #define	GT_COMERR		/* #define comerr gtcomerr */
32 #define	GT_ERROR		/* #define error gterror   */
33 #include <schily/schily.h>
34 #include <schily/nlsdefs.h>
35 
36 #define	MIN_LINELEN	4096		/* Min line size  */
37 #define	INCR_LINELEN	4096		/* Increment for line size */
38 #define	MAX_FILES 	(256-3)		/* Max # of files */
39 
40 LOCAL	BOOL	eofp[MAX_FILES];	/* Files that did hit EOF */
41 LOCAL	FILE	*filep[MAX_FILES] = {0}; /* Files to work on */
42 LOCAL	char	*delim;
43 LOCAL	int	delimlen;
44 LOCAL	int	empty;
45 
46 LOCAL	char	*line;			/* Output line */
47 LOCAL	size_t	linelen = MIN_LINELEN;
48 LOCAL	int	linesize = -1;
49 
50 LOCAL	void	usage	__PR((int exitcode));
51 EXPORT	int	main	__PR((int ac, char ** av));
52 LOCAL	void	paste	__PR((int n));
53 LOCAL	void	spaste	__PR((FILE *f));
54 LOCAL	int	parsedelim __PR((char *d));
55 
56 LOCAL void
usage(exitcode)57 usage(exitcode)
58 	int	exitcode;
59 {
60 	error("Usage:	paste [options] file1...filen\n");
61 	error("Options:\n");
62 	error("\td=list\t\tuse 'list' as delimiter instead of 'tab'.\n");
63 	error("\t-e\t\tdo not output empty lines.\n");
64 	error("\t-s\t\tpaste lines of one file instead of one line per file.\n");
65 	error("\twidth=#,w=#\tmaximum output linewidth (default infinite).\n");
66 	error("\t-help\t\tPrint this help.\n");
67 	error("\t-version\tPrint version information and exit.\n");
68 	exit(exitcode);
69 }
70 
71 EXPORT int
main(ac,av)72 main(ac, av)
73 	int	ac;
74 	char	*av[];
75 {
76 	int	i = 0;			/* Count # of input files */
77 
78 	char	*options = "help,version,d*,e,s,width#,w#";
79 	int	help	= 0;
80 	int	prvers	= 0;
81 	int	ser	= 0;
82 	int	cac;
83 	char	* const *cav;
84 
85 	save_args(ac, av);
86 
87 	(void) setlocale(LC_ALL, "");
88 
89 #ifdef  USE_NLS
90 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
91 #define	TEXT_DOMAIN "paste"	/* Use this only if it weren't */
92 #endif
93 	{ char	*dir;
94 	dir = searchfileinpath("share/locale", F_OK,
95 					SIP_ANY_FILE|SIP_NO_PATH, NULL);
96 	if (dir)
97 		(void) bindtextdomain(TEXT_DOMAIN, dir);
98 	else
99 #if defined(PROTOTYPES) && defined(INS_BASE)
100 	(void) bindtextdomain(TEXT_DOMAIN, INS_BASE "/share/locale");
101 #else
102 	(void) bindtextdomain(TEXT_DOMAIN, "/usr/share/locale");
103 #endif
104 	(void) textdomain(TEXT_DOMAIN);
105 	}
106 #endif 	/* USE_NLS */
107 
108 	cac	= --ac;
109 	cav	= ++av;
110 
111 	if (getallargs(&cac, &cav, options, &help, &prvers,
112 					&delim, &empty, &ser,
113 					&linesize, &linesize) < 0) {
114 		errmsgno(EX_BAD, "Bad flag: %s.\n", cav[0]);
115 		usage(EX_BAD);
116 	}
117 	if (help)
118 		usage(0);
119 
120 	if (prvers) {
121 		/* CSTYLED */
122 		gtprintf("Paste release %s (%s-%s-%s) Copyright (C) 1985-2021 %s\n",
123 				"1.22",
124 				HOST_CPU, HOST_VENDOR, HOST_OS,
125 				_("J�rg Schilling"));
126 		exit(0);
127 	}
128 
129 	if (delim) {
130 		delimlen = parsedelim(delim);
131 		if (delimlen == 0)
132 			comerrno(EX_BAD, "No delimiter.\n");
133 	} else {
134 		delim = "\t";
135 		delimlen = 1;
136 	}
137 	cac	= ac;
138 	cav	= av;
139 	for (; getfiles(&cac, &cav, options); cac--, cav++) {
140 		if (i >= MAX_FILES)
141 			comerrno(EX_BAD, "Cannot paste more than %d files.\n",
142 				MAX_FILES);
143 		if (streql(*cav, "-"))
144 			filep[i++] = stdin;
145 		else if ((filep[i++] = fileopen(*cav, "r")) == (FILE *)NULL)
146 			comerr("Cannot open '%s'.\n", *cav);
147 		if (ser)
148 			spaste(filep[i-1]);
149 	}
150 
151 	if (i == 0) {
152 		errmsgno(EX_BAD, "No files given.\n");
153 		usage(EX_BAD);
154 	}
155 	if (ser)
156 		return (0);
157 
158 	if (linesize > 0)
159 		linelen = linesize;
160 	if ((line = malloc(linelen+2)) == NULL)
161 		comerr("Cannot malloc space for line.\n");
162 
163 	paste(i);
164 	return (0);
165 }
166 
167 LOCAL void
paste(n)168 paste(n)
169 	int	n;		/* # of files to read from */
170 {
171 	int	k;
172 	register char	*lp;	/* pointer to line */
173 	register char	*ep;	/* pointer to end of line */
174 	register int	c;
175 	register FILE	*fp;
176 	register int	i;
177 
178 	for (i = 0; i < n; i++)
179 		eofp[i] = FALSE;
180 	ep = &line[linelen];
181 
182 	for (k = 0; k < n; ) {	/* Stop when all files hit EOF */
183 		lp = line;
184 		for (i = 0; i < n; i++) {
185 			if (!eofp[i]) {		/* No EOF on this file yet? */
186 				fp = filep[i];
187 			again:
188 				while ((c = getc(fp)) != EOF &&
189 				    c != '\n' && lp < ep)
190 					*lp++ = c;
191 
192 				if (lp >= ep) {
193 					char	*new = NULL;
194 					static	BOOL didwarn = FALSE;
195 
196 					if (linesize < 0) {
197 						/*
198 						 * Use dynamic line length,
199 						 */
200 						linelen += INCR_LINELEN;
201 						new = realloc(line,
202 						    linelen+2 + INCR_LINELEN);
203 					} else {
204 						didwarn = TRUE;
205 					}
206 					if (new == NULL) {
207 						if (!didwarn)
208 							errmsg(
209 					"Cannot realloc space for line.\n");
210 						didwarn = TRUE;
211 					} else {
212 						linelen += INCR_LINELEN;
213 						lp += new - line;
214 						ep = &new[linelen];
215 						line = new;
216 						goto again;
217 					}
218 					while ((c = getc(fp)) != EOF &&
219 					    c != '\n')
220 						;
221 				}
222 			} else {		/* Avoid EOFing twice	*/
223 				c = '\n';	/* so pretend eol.	*/
224 			}
225 
226 			if (c == EOF) {
227 				eofp[i] = EOF;
228 				k++;		/* One file less */
229 			}
230 
231 			if (i == (n-1)) {	/* Last file in line? */
232 				*lp++ = '\n';
233 				*lp   = '\0';
234 				break;		/* Quit loop to print line */
235 			} else {		/* Add field delimiter */
236 				c = delim[i%delimlen];
237 				if (c)
238 					*lp++ = c;
239 			}
240 		}
241 
242 		/*
243 		 * Print only if at least one file did have a line (k <  n).
244 		 * With -e, print only lines that have more than delimiters.
245 		 */
246 		if (lp-line > n || (!empty && k < n))
247 			filewrite(stdout, line, lp-line);
248 	}
249 }
250 
251 LOCAL void
spaste(f)252 spaste(f)
253 	FILE	*f;
254 {
255 	register int	len;
256 	register int	i;
257 	register int	c;
258 
259 	if ((len = fgetaline(f, &line, &linelen)) > 0) {
260 		for (i = 0; ; i++) {
261 			if (line[len-1] == '\n')
262 				len--;
263 			filewrite(stdout, line, len);
264 			if ((len = fgetaline(f, &line, &linelen)) <= 0)
265 				break;
266 			c = delim[i%delimlen];
267 			if (c)
268 				putchar(c);
269 		}
270 	}
271 	putchar('\n');
272 	fclose(f);
273 }
274 
275 LOCAL int
parsedelim(d)276 parsedelim(d)
277 	char	*d;
278 {
279 	int	len = 0;
280 	int	c;
281 static	char	del[MAX_FILES];
282 
283 	while (*d) {
284 		if ((c = *d++) != '\\') {
285 			del[len++] = c;
286 		} else {
287 			switch (c = *d++) {
288 
289 			case '0':
290 				c = 0;
291 				break;
292 			case 't':
293 				c = '\t';
294 				break;
295 			case 'n':
296 				c = '\n';
297 				break;
298 			}
299 			del[len++] = c;
300 		}
301 		if (len >= MAX_FILES)
302 			break;
303 	}
304 	delim = del;
305 	return (len);
306 }
307