]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/split/split.c
bhnd(9): Fix a few mandoc related issues
[FreeBSD/FreeBSD.git] / usr.bin / split / split.c
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
36 static 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
42 static 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
68 static off_t     bytecnt;               /* Byte count to split on. */
69 static off_t     chunks = 0;            /* Chunks count to split into. */
70 static long      numlines;              /* Line count to split on. */
71 static int       file_open;             /* If a file open. */
72 static int       ifd = -1, ofd = -1;    /* Input/output file descriptors. */
73 static char      bfr[MAXBSIZE];         /* I/O buffer. */
74 static char      fname[MAXPATHLEN];     /* File name prefix. */
75 static regex_t   rgx;
76 static int       pflag;
77 static bool      dflag;
78 static long      sufflen = 2;           /* File name suffix length. */
79
80 static void newfile(void);
81 static void split1(void);
82 static void split2(void);
83 static void split3(void);
84 static void usage(void);
85
86 int
87 main(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  */
203 static void
204 split1(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  */
264 static void
265 split2(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
295 writeit:
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  */
316 static void
317 split3(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  */
341 static void
342 newfile(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
395 static void
396 usage(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 }