1 // ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ 2 // 3 // Copyright (c) 2001-2003, OpenBeOS 4 // 5 // This software is part of the OpenBeOS 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