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