1// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
2//
3//  Copyright (c) 2001-2003, Haiku
4//
5//  This software is part of the Haiku distribution and is covered
6//  by the MIT License.
7//
8//
9//  File:        chop.c
10//  Author:      Daniel Reinhold (danielre@users.sf.net)
11//  Description: splits one file into a collection of smaller files
12//
13//  Notes:
14//  This program was written such that it would have identical output as from
15//  the original chop program included with BeOS R5. However, there are a few
16//  minor differences:
17//
18//  a) using "chop -n" (with no other args) crashes the original version,
19//     but not this one.
20//
21//  b) filenames are enclosed in single quotes here, but are not in the original.
22//     It is generally better to enquote filenames for error messages so that
23//     problems with the name (e.g extra space chars) can be more easily detected.
24//
25//  c) this version checks for validity of the input file (including file size)
26//     before there is any attempt to open it -- this changes the error output
27//     slightly from the original in some situations. It can also prevent some
28//     weirdness. For example, the original version will take a command such as:
29//
30//         chop /dev/ports/serial1
31//
32//     and attempt to open the device. If serial1 is unused, this will actually
33//     block while waiting for data from the serial port. This version will never
34//     encounter that because the device will be found to have size 0 which will
35//     abort immediately. Since the semantics of chop don't make sense for such
36//     devices as the source, this is really the better behavior (provided that
37//     anyone ever attempts to use such strange arguments, which is unlikely).
38//
39// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
40
41#include <OS.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <ctype.h>
46#include <errno.h>
47#include <fcntl.h>
48#include <unistd.h>
49#include <sys/stat.h>
50
51
52void chop_file (int, char *, off_t);
53void do_chop   (char *);
54void usage     (void);
55
56
57// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
58// globals
59
60#define BLOCKSIZE 64 * 1024        // file data is read in BLOCKSIZE blocks
61static char Block[BLOCKSIZE];      // and stored in the global Block array
62
63static int KBytesPerChunk = 1400;  // determines size of output files
64
65
66void
67usage()
68{
69	printf("Usage: chop [-n kbyte_per_chunk] file\n");
70	printf("Splits file into smaller files named file00, file01...\n");
71	printf("Default split size is 1400k\n");
72}
73
74
75
76int
77main(int argc, char *argv[])
78{
79	char *arg = NULL;
80	char *first;
81
82	if ((argc < 2) || (argc > 4)) {
83		usage();
84		return 0;
85	}
86
87	first = *++argv;
88
89	if (strcmp(first, "--help") == 0) {
90		usage();
91		return 0;
92	}
93
94	if (strcmp(first, "-n") == 0) {
95		if (--argc > 1) {
96			char *num = *++argv;
97
98			if (!isdigit(*num))
99				printf("-n option needs a numeric argument\n");
100			else {
101				int b = atoi(num);
102				KBytesPerChunk = (b < 1 ? 1 : b);
103
104				if (--argc > 1)
105					arg = *++argv;
106				else
107					printf("no file specified\n");
108			}
109		}
110		else
111			printf("-n option needs a numeric argument\n");
112	}
113	else
114		arg = first;
115
116	if (arg)
117		do_chop(arg);
118
119	putchar ('\n');
120	return 0;
121}
122
123
124void
125do_chop(char *fname)
126{
127	// do some checks for validity
128	// then call chop_file() to do the actual read/writes
129
130	struct stat e;
131	off_t  fsize;
132	int    fd;
133
134	// input file must exist
135	if (stat(fname, &e) == -1) {
136		fprintf(stderr, "'%s': no such file or directory\n", fname);
137		return;
138	}
139
140	// and it must be not be a directory
141	if (S_ISDIR(e.st_mode)) {
142		fprintf(stderr, "'%s' is a directory\n", fname);
143		return;
144	}
145
146	// needs to be big enough such that splitting it actually does something
147	fsize = e.st_size;
148	if (fsize < (KBytesPerChunk * 1024)) {
149		fprintf(stderr, "'%s': file is already small enough\n", fname);
150		return;
151	}
152
153	// also, don't chop up if chunk files are already present
154	{
155		char buf[256];
156
157		strcpy(buf, fname);
158		strcat(buf, "00");
159
160		if (stat(buf, &e) >= 0) {
161			fprintf(stderr, "'%s' already exists - aborting\n", buf);
162			return;
163		}
164	}
165
166	// finally! chop up the file
167	fd = open(fname, O_RDONLY);
168	if (fd < 0)
169		fprintf(stderr, "can't open '%s': %s\n", fname, strerror(errno));
170	else {
171		chop_file(fd, fname, fsize);
172		close(fd);
173	}
174}
175
176
177void
178chop_file(int fdin, char *fname, off_t fsize)
179{
180	const off_t chunk_size = KBytesPerChunk * 1024;  // max bytes written to any output file
181
182	bool open_next_file = true;  // when to open a new output file
183	char fnameN[256];            // name of the current output file (file01, file02, etc.)
184	int  index = 0;              // used to generate the next output file name
185	int  fdout = -1;             // output file descriptor
186
187	ssize_t got;                 // size of the current data block -- i.e. from the last read()
188	ssize_t put;                 // number of bytes just written   -- i.e. from the last write()
189	ssize_t needed;              // how many bytes we can safely write to the current output file
190	ssize_t avail;               // how many bytes we can safely grab from the current data block
191	off_t   curr_written = 0;    // number of bytes written to the current output file
192	off_t   total_written = 0;   // total bytes written out to all output files
193
194	char *beg = Block;  // pointer to the beginning of the block data to be written out
195	char *end = Block;  // end of the current block (init to beginning to force first block read)
196
197	printf("Chopping up %s into %d kbyte chunks\n", fname, KBytesPerChunk);
198
199	while (total_written < fsize) {
200		if (beg >= end) {
201			// read in another block
202			got = read(fdin, Block, BLOCKSIZE);
203			if (got <= 0)
204				break;
205
206			beg = Block;
207			end = Block + got - 1;
208		}
209
210		if (open_next_file) {
211			// start a new output file
212			sprintf(fnameN,  "%s%02d", fname, index++);
213
214			fdout = open(fnameN, O_WRONLY|O_CREAT);
215			if (fdout < 0) {
216				fprintf(stderr, "unable to create chunk file '%s': %s\n", fnameN, strerror(errno));
217				return;
218			}
219			curr_written = 0;
220			open_next_file = false;
221		}
222
223		needed = chunk_size - curr_written;
224		avail  = end - beg + 1;
225		if (needed > avail)
226			needed = avail;
227
228		if (needed > 0) {
229			put = write(fdout, beg, needed);
230			beg += put;
231
232			curr_written  += put;
233			total_written += put;
234		}
235
236		if (curr_written >= chunk_size) {
237			// the current output file is full
238			close(fdout);
239			open_next_file = true;
240		}
241	}
242
243	// close up the last output file if it's still open
244	if (!open_next_file)
245		close(fdout);
246}
247