1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1987, 1993, 1994
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD$");
34
35#ifndef lint
36static const char copyright[] =
37"@(#) Copyright (c) 1987, 1993, 1994\n\
38	The Regents of the University of California.  All rights reserved.\n";
39#endif
40
41#ifndef lint
42static const char sccsid[] = "@(#)split.c	8.2 (Berkeley) 4/16/94";
43#endif
44
45#include <sys/param.h>
46#include <sys/types.h>
47#include <sys/stat.h>
48
49#include <ctype.h>
50#include <err.h>
51#include <errno.h>
52#include <fcntl.h>
53#include <inttypes.h>
54#include <libutil.h>
55#include <limits.h>
56#include <locale.h>
57#include <stdbool.h>
58#include <stdint.h>
59#include <stdio.h>
60#include <stdlib.h>
61#include <string.h>
62#include <unistd.h>
63#include <regex.h>
64#include <sysexits.h>
65
66#define DEFLINE	1000			/* Default num lines per file. */
67
68static off_t	 bytecnt;		/* Byte count to split on. */
69static off_t	 chunks = 0;		/* Chunks count to split into. */
70static long	 numlines;		/* Line count to split on. */
71static int	 file_open;		/* If a file open. */
72static int	 ifd = -1, ofd = -1;	/* Input/output file descriptors. */
73static char	 bfr[MAXBSIZE];		/* I/O buffer. */
74static char	 fname[MAXPATHLEN];	/* File name prefix. */
75static regex_t	 rgx;
76static int	 pflag;
77static bool	 dflag;
78static long	 sufflen = 2;		/* File name suffix length. */
79
80static void newfile(void);
81static void split1(void);
82static void split2(void);
83static void split3(void);
84static void usage(void);
85
86int
87main(int argc, char **argv)
88{
89	int ch;
90	int error;
91	char *ep, *p;
92
93	setlocale(LC_ALL, "");
94
95	dflag = false;
96	while ((ch = getopt(argc, argv, "0123456789a:b:dl:n:p:")) != -1)
97		switch (ch) {
98		case '0': case '1': case '2': case '3': case '4':
99		case '5': case '6': case '7': case '8': case '9':
100			/*
101			 * Undocumented kludge: split was originally designed
102			 * to take a number after a dash.
103			 */
104			if (numlines == 0) {
105				p = argv[optind - 1];
106				if (p[0] == '-' && p[1] == ch && !p[2])
107					numlines = strtol(++p, &ep, 10);
108				else
109					numlines =
110					    strtol(argv[optind] + 1, &ep, 10);
111				if (numlines <= 0 || *ep)
112					errx(EX_USAGE,
113					    "%s: illegal line count", optarg);
114			}
115			break;
116		case 'a':		/* Suffix length */
117			if ((sufflen = strtol(optarg, &ep, 10)) <= 0 || *ep)
118				errx(EX_USAGE,
119				    "%s: illegal suffix length", optarg);
120			break;
121		case 'b':		/* Byte count. */
122			errno = 0;
123			error = expand_number(optarg, &bytecnt);
124			if (error == -1)
125				errx(EX_USAGE, "%s: offset too large", optarg);
126			break;
127		case 'd':		/* Decimal suffix */
128			dflag = true;
129			break;
130		case 'l':		/* Line count. */
131			if (numlines != 0)
132				usage();
133			if ((numlines = strtol(optarg, &ep, 10)) <= 0 || *ep)
134				errx(EX_USAGE,
135				    "%s: illegal line count", optarg);
136			break;
137		case 'n':		/* Chunks. */
138			if (!isdigit((unsigned char)optarg[0]) ||
139			    (chunks = (size_t)strtoul(optarg, &ep, 10)) == 0 ||
140			    *ep != '\0') {
141				errx(EX_USAGE, "%s: illegal number of chunks",
142				     optarg);
143			}
144			break;
145
146		case 'p':		/* pattern matching. */
147			if (regcomp(&rgx, optarg, REG_EXTENDED|REG_NOSUB) != 0)
148				errx(EX_USAGE, "%s: illegal regexp", optarg);
149			pflag = 1;
150			break;
151		default:
152			usage();
153		}
154	argv += optind;
155	argc -= optind;
156
157	if (*argv != NULL) {			/* Input file. */
158		if (strcmp(*argv, "-") == 0)
159			ifd = STDIN_FILENO;
160		else if ((ifd = open(*argv, O_RDONLY, 0)) < 0)
161			err(EX_NOINPUT, "%s", *argv);
162		++argv;
163	}
164	if (*argv != NULL)			/* File name prefix. */
165		if (strlcpy(fname, *argv++, sizeof(fname)) >= sizeof(fname))
166			errx(EX_USAGE, "file name prefix is too long");
167	if (*argv != NULL)
168		usage();
169
170	if (strlen(fname) + (unsigned long)sufflen >= sizeof(fname))
171		errx(EX_USAGE, "suffix is too long");
172	if (pflag && (numlines != 0 || bytecnt != 0 || chunks != 0))
173		usage();
174
175	if (numlines == 0)
176		numlines = DEFLINE;
177	else if (bytecnt != 0 || chunks != 0)
178		usage();
179
180	if (bytecnt && chunks)
181		usage();
182
183	if (ifd == -1)				/* Stdin by default. */
184		ifd = 0;
185
186	if (bytecnt) {
187		split1();
188		exit (0);
189	} else if (chunks) {
190		split3();
191		exit (0);
192	}
193	split2();
194	if (pflag)
195		regfree(&rgx);
196	exit(0);
197}
198
199/*
200 * split1 --
201 *	Split the input by bytes.
202 */
203static void
204split1(void)
205{
206	off_t bcnt;
207	char *C;
208	ssize_t dist, len;
209	int nfiles;
210
211	nfiles = 0;
212
213	for (bcnt = 0;;)
214		switch ((len = read(ifd, bfr, MAXBSIZE))) {
215		case 0:
216			exit(0);
217		case -1:
218			err(EX_IOERR, "read");
219			/* NOTREACHED */
220		default:
221			if (!file_open) {
222				if (!chunks || (nfiles < chunks)) {
223					newfile();
224					nfiles++;
225				}
226			}
227			if (bcnt + len >= bytecnt) {
228				dist = bytecnt - bcnt;
229				if (write(ofd, bfr, dist) != dist)
230					err(EX_IOERR, "write");
231				len -= dist;
232				for (C = bfr + dist; len >= bytecnt;
233				    len -= bytecnt, C += bytecnt) {
234					if (!chunks || (nfiles < chunks)) {
235					newfile();
236						nfiles++;
237					}
238					if (write(ofd,
239					    C, bytecnt) != bytecnt)
240						err(EX_IOERR, "write");
241				}
242				if (len != 0) {
243					if (!chunks || (nfiles < chunks)) {
244					newfile();
245						nfiles++;
246					}
247					if (write(ofd, C, len) != len)
248						err(EX_IOERR, "write");
249				} else
250					file_open = 0;
251				bcnt = len;
252			} else {
253				bcnt += len;
254				if (write(ofd, bfr, len) != len)
255					err(EX_IOERR, "write");
256			}
257		}
258}
259
260/*
261 * split2 --
262 *	Split the input by lines.
263 */
264static void
265split2(void)
266{
267	long lcnt = 0;
268	FILE *infp;
269
270	/* Stick a stream on top of input file descriptor */
271	if ((infp = fdopen(ifd, "r")) == NULL)
272		err(EX_NOINPUT, "fdopen");
273
274	/* Process input one line at a time */
275	while (fgets(bfr, sizeof(bfr), infp) != NULL) {
276		const int len = strlen(bfr);
277
278		/* If line is too long to deal with, just write it out */
279		if (bfr[len - 1] != '\n')
280			goto writeit;
281
282		/* Check if we need to start a new file */
283		if (pflag) {
284			regmatch_t pmatch;
285
286			pmatch.rm_so = 0;
287			pmatch.rm_eo = len - 1;
288			if (regexec(&rgx, bfr, 0, &pmatch, REG_STARTEND) == 0)
289				newfile();
290		} else if (lcnt++ == numlines) {
291			newfile();
292			lcnt = 1;
293		}
294
295writeit:
296		/* Open output file if needed */
297		if (!file_open)
298			newfile();
299
300		/* Write out line */
301		if (write(ofd, bfr, len) != len)
302			err(EX_IOERR, "write");
303	}
304
305	/* EOF or error? */
306	if (ferror(infp))
307		err(EX_IOERR, "read");
308	else
309		exit(0);
310}
311
312/*
313 * split3 --
314 *	Split the input into specified number of chunks
315 */
316static void
317split3(void)
318{
319	struct stat sb;
320
321	if (fstat(ifd, &sb) == -1) {
322		err(1, "stat");
323		/* NOTREACHED */
324	}
325
326	if (chunks > sb.st_size) {
327		errx(1, "can't split into more than %d files",
328		    (int)sb.st_size);
329		/* NOTREACHED */
330	}
331
332	bytecnt = sb.st_size / chunks;
333	split1();
334}
335
336
337/*
338 * newfile --
339 *	Open a new output file.
340 */
341static void
342newfile(void)
343{
344	long i, maxfiles, tfnum;
345	static long fnum;
346	static char *fpnt;
347	char beg, end;
348	int pattlen;
349
350	if (ofd == -1) {
351		if (fname[0] == '\0') {
352			fname[0] = 'x';
353			fpnt = fname + 1;
354		} else {
355			fpnt = fname + strlen(fname);
356		}
357		ofd = fileno(stdout);
358	}
359
360	if (dflag) {
361		beg = '0';
362		end = '9';
363	}
364	else {
365		beg = 'a';
366		end = 'z';
367	}
368	pattlen = end - beg + 1;
369
370	/* maxfiles = pattlen^sufflen, but don't use libm. */
371	for (maxfiles = 1, i = 0; i < sufflen; i++)
372		if (LONG_MAX / pattlen < maxfiles)
373			errx(EX_USAGE, "suffix is too long (max %ld)", i);
374		else
375			maxfiles *= pattlen;
376
377	if (fnum == maxfiles)
378		errx(EX_DATAERR, "too many files");
379
380	/* Generate suffix of sufflen letters */
381	tfnum = fnum;
382	i = sufflen - 1;
383	do {
384		fpnt[i] = tfnum % pattlen + beg;
385		tfnum /= pattlen;
386	} while (i-- > 0);
387	fpnt[sufflen] = '\0';
388
389	++fnum;
390	if (!freopen(fname, "w", stdout))
391		err(EX_IOERR, "%s", fname);
392	file_open = 1;
393}
394
395static void
396usage(void)
397{
398	(void)fprintf(stderr,
399"usage: split [-l line_count] [-a suffix_length] [file [prefix]]\n"
400"       split -b byte_count[K|k|M|m|G|g] [-a suffix_length] [file [prefix]]\n"
401"       split -n chunk_count [-a suffix_length] [file [prefix]]\n"
402"       split -p pattern [-a suffix_length] [file [prefix]]\n");
403	exit(EX_USAGE);
404}
405