Sync after every write.
[ccp.git] / ccp.c
1 /**
2  * ccp.c - a cp replacement with progress bar
3  *
4  * @version $Id: ccp.c 769 2008-04-26 10:53:11Z bombe $
5  */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <sys/ioctl.h>
12 #include <termios.h>
13 #include <unistd.h>
14 #include <errno.h>
15 #include <string.h>
16 #include <signal.h>
17 #include <sys/timeb.h>
18 #include <time.h>
19
20 #define false 0
21 #define true !false
22
23 int redrawProgressBar = false;
24
25 void handleWindowResize(int signalNumber) {
26         signal(signalNumber, SIG_IGN);
27
28         redrawProgressBar = true;
29
30         signal(signalNumber, handleWindowResize);
31 }
32
33 int getColumns() {
34         struct winsize terminalSize;
35         int error;
36
37         error = ioctl(1, TIOCGWINSZ, &terminalSize);
38         
39         if (!error) {
40                 return terminalSize.ws_col;
41         }
42         return 80;
43 }
44
45 char *extractFilename(char *pathName) {
46         char* result;
47
48         result = strrchr(pathName, '/');
49         if (result) {
50                 return result + 1;
51         }
52         return pathName;
53 }
54
55 int scaleProgress(char* fileName, double progress) {
56         int columns;
57
58         columns = getColumns() - 3 - strlen(fileName);
59         if (columns < 2) {
60                 columns = 2;
61         }
62
63         return (int) (columns * progress);
64 }
65
66 void printProgress(char* fileName, int currentProgress) {
67         char* shortFileName;
68         int totalColumns;
69         int columns;
70         int i;
71
72         shortFileName = fileName;
73         totalColumns = getColumns();
74         columns = totalColumns - 3 - strlen(fileName);
75
76         if (columns < 2) {
77                 shortFileName = fileName + (strlen(fileName) - (totalColumns - 8));
78                 columns = 2;
79         }
80
81         if (shortFileName != fileName) {
82                 printf("...%s", shortFileName);
83         } else {
84                 printf("%s", fileName);
85         }
86         printf(" [");
87         for (i = 0; i < columns; i++) {
88                 printf((i < currentProgress) ? "*" : ".");
89         }
90         printf("]\r");
91         fflush(stdout);
92 }
93
94 int copyFileToFile(char* sourceFile, char* destFile) {
95         FILE* inputFile;
96         FILE* outputFile;
97         char* fileName;
98         long long fileSize;
99         size_t read;
100         size_t toWrite;
101         long long writtenTotal = 0;
102         size_t written;
103         int readSize;
104         int bufferSize;
105         void* buffer;
106         double progress;
107         int currentProgress = 0;
108         int lastProgress = -1;
109         struct timeb startTime, endTime;
110         long startTimeMillis, endTimeMillis, spent;
111
112         inputFile = fopen(sourceFile, "rb");
113         if (!inputFile) {
114                 fprintf(stderr, "can not open \"%s\" for reading!\n", sourceFile);
115                 return false;
116         }
117
118         outputFile = fopen(destFile, "wb");
119         if (!outputFile) {
120                 fprintf(stderr, "can not open \"%s\" for writing!\n", destFile);
121                 fclose(inputFile);
122                 return false;
123         }
124
125         fseek(inputFile, 0, SEEK_END);
126         fileSize = ftell(inputFile);
127         fseek(inputFile, 0, SEEK_SET);
128
129         fileName = extractFilename(sourceFile);
130
131         bufferSize = 1 << 20;
132         readSize = bufferSize;
133         buffer = (void*) malloc(bufferSize);
134
135         while (!feof(inputFile)) {
136                 ftime(&startTime);
137                 read = fread(buffer, 1, readSize, inputFile);
138                 if (read) {
139                         toWrite = read;
140                         do {
141                                 written = fwrite(buffer + (read - toWrite), 1, toWrite, outputFile);
142                                 if (!written) {
143                                         if (ferror(outputFile)) {
144                                                 fprintf(stderr, "\nerror writing to \"%s\"!\n", destFile);
145                                                 fclose(inputFile);
146                                                 fclose(outputFile);
147                                                 return false;
148                                         }
149                                 }
150                                 toWrite -= written;
151                         } while (toWrite);
152                         fsync(fileno(outputFile));
153                         ftime(&endTime);
154                         writtenTotal += written;
155                         progress = (double) writtenTotal / fileSize;
156                         currentProgress = scaleProgress(fileName, progress);
157                         if ((currentProgress != lastProgress) || (redrawProgressBar)) {
158                                 printProgress(fileName, currentProgress);
159                                 lastProgress = currentProgress;
160                                 redrawProgressBar = false;
161                         }
162                         endTimeMillis = endTime.time * 1000 + endTime.millitm;
163                         startTimeMillis = startTime.time * 1000 + startTime.millitm;
164                         spent = endTimeMillis - startTimeMillis;
165                         if (spent >= 200) {
166                                 if (readSize > 2047) {
167                                         readSize >>= 1;
168                                 }
169                         } else if (spent <= 50) {
170                                 if (readSize < (1 << 19)) {
171                                         readSize <<= 1;
172                                 }
173                         }
174                 } else {
175                         if (ferror(inputFile)) {
176                                 fprintf(stderr, "\nerror reading from \"%s\"!\n", sourceFile);
177                                 fclose(inputFile);
178                                 fclose(outputFile);
179                                 return false;
180                         }
181                 }
182         }
183         printf("\n");
184         fflush(stdout);
185         fclose(inputFile);
186         fclose(outputFile);
187
188         return true;
189 }
190
191 int copyFileToDirectory(char *sourceFile, char* destDirectory) {
192         char* fileName;
193         char* destFile;
194         int copyResult;
195
196         fileName = extractFilename(sourceFile);
197         destFile = (char*) calloc(strlen(destDirectory) + 1 + strlen(fileName) + 1, 1);
198         strcat(destFile, destDirectory);
199         strcat(destFile, "/");
200         strcat(destFile, fileName);
201
202         copyResult = copyFileToFile(sourceFile, destFile);
203
204         free(destFile);
205         return copyResult;
206 }
207
208 void printHelp() {
209         printf("Syntax: ccp SRC [SRC [...]] DEST\n\n");
210         printf("SRC must be a file. If DEST is a directory, an arbitrary amount of SRC\n");
211         printf("may be given. If DEST is an existing file or does not exist at all,\n");
212         printf("only one SRC is allowed.\n");
213 }
214
215 int isWritableFile(char *pathName) {
216         struct stat fileStat;
217         int statError;
218         FILE* testFd;
219
220         statError = stat(pathName, &fileStat);
221         if (statError == -1) {
222                 if (errno == ENOENT) {
223                         testFd = fopen(pathName, "w");
224                         if (testFd) {
225                                 fclose(testFd);
226                                 return true;
227                         }
228                 }
229                 return false;
230         }
231         if (S_ISREG(fileStat.st_mode)) {
232                 testFd = fopen(pathName, "ab+");
233                 if (testFd) {
234                         fclose(testFd);
235                         return true;
236                 }
237         }
238         return false;
239 }
240
241 int isDirectory(char *pathName) {
242         struct stat fileStat;
243         int statError;
244
245         statError = stat(pathName, &fileStat);
246         if (statError == -1) {
247                 return false;
248         }
249         return S_ISDIR(fileStat.st_mode);
250 }
251
252 int checkForDirectoryOrNonExistingFile(char* pathName) {
253         return isDirectory(pathName) || isWritableFile(pathName);
254 }
255
256 int main(int argc, char** argv) {
257         char* lastArgument;
258         char* binaryName;
259         int destinationIsDirectory = false;
260         int fileIndex;
261         int moveFiles = false;
262
263         if (argc < 3) {
264                 printHelp();
265                 return 1;
266         }
267         lastArgument = argv[argc - 1];
268         if (!isDirectory(lastArgument)) {
269                 if (isWritableFile(lastArgument)) {
270                         if (argc != 3) {
271                                 printHelp();
272                                 return 1;
273                         }
274                 } else {
275                         fprintf(stderr, "\"%s\" is not a writable file.\n", lastArgument);
276                         return 2;
277                 }
278         } else {
279                 destinationIsDirectory = true;
280         }
281
282         binaryName = extractFilename(argv[0]);
283         if (!strcmp(binaryName, "cmv")) {
284                 moveFiles = true;
285         }
286
287         signal(SIGWINCH, handleWindowResize);
288         for (fileIndex = 1; fileIndex < (argc - 1); fileIndex++) {
289                 if (destinationIsDirectory) {
290                         if (copyFileToDirectory(argv[fileIndex], lastArgument) && moveFiles) {
291                                 unlink(argv[fileIndex]);
292                         }
293                 } else {
294                         if (copyFileToFile(argv[fileIndex], lastArgument) && moveFiles) {
295                                 unlink(argv[fileIndex]);
296                         }
297                 }
298         }
299
300         return 0;
301 }