xref: /haiku/src/bin/chop.c (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
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 
52 void chop_file (int, char *, off_t);
53 void do_chop   (char *);
54 void usage     (void);
55 
56 
57 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
58 // globals
59 
60 #define BLOCKSIZE 64 * 1024        // file data is read in BLOCKSIZE blocks
61 static char Block[BLOCKSIZE];      // and stored in the global Block array
62 
63 static int KBytesPerChunk = 1400;  // determines size of output files
64 
65 
66 void
67 usage()
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 
76 int
77 main(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 
124 void
125 do_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 
177 void
178 chop_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