fixnuta buga v remove_last (nesnizovala grow ptr, ktery tak ubiral znaky a nemenil...
[httpfs.git] / ncp.c
blob7332b26dd9b2cddb907dc7e4d92f0b7e1cb276f7
1 /*
2 * ncp - Neat CoPy
4 * Very simple 'cp' implementation with progressbar and resume.
5 * Usefull with sshfs/httpfs and other network filesystems.
6 *
7 * TODO
8 * use another thread for progressbar instead of sigalarm -> blocked read
9 */
11 #define _FILE_OFFSET_BITS 64
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <fcntl.h>
16 #include <errno.h>
17 #include <error.h>
18 #include <unistd.h>
19 #include <signal.h>
20 #include <time.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <dirent.h>
24 #include "debug.h"
25 #include "grow.h"
27 #define DEL_LINE "\e[2K"
28 #define INV_ON "\e[7m"
29 #define INV_OFF "\e[27m"
30 #define BS 4096
32 #define BAR_BITS 5
33 #define BAR_WIDTH (1<<BAR_BITS)
34 #define AVG_SECS 15
35 #define CUR_SECS 2
36 #define PROG_WIDTH 30ULL
38 #define step(b,n) ((b)->xfer[(b->step+(n))&((1<<BAR_BITS)-1)])
39 #define MAX(x,y) ((x)>(y)?(x):(y))
40 #define MIN(x,y) ((x)<(y)?(x):(y))
42 #define DEFAULT_DISPLAY (D_PROG | D_SPEED | D_SIZE | D_NAME)
44 typedef enum {
45 D_BAND = 1,
46 D_PROG = 2,
47 D_NAME = 4,
48 D_ETA = 8,
49 D_SPEED = 16,
50 D_SIZE = 32,
51 } display_t;
53 struct bar {
54 display_t display;
55 int xfer[BAR_WIDTH];
56 time_t start, last;
57 size_t resumed, xfered, total;
58 int step, peak, accum;
59 int avg_speed, cur_speed;
60 const char *filename;
63 struct config {
64 int force_dir;
65 int recursive;
66 int resume;
70 char buffer[BS];
71 struct bar progress;
72 display_t display_opt = DEFAULT_DISPLAY;
74 static inline void pexit(char *s)
76 perror(s);
77 exit(1);
80 static inline void sexit(const char *fn, const char *s)
82 fprintf(stderr, "%s: %s\n", fn, s);
83 exit(1);
86 static inline void serror(const char *fn, const char *s)
88 fprintf(stderr, "%s: %s\n", fn, s);
92 static inline const char *basename(const char *s)
94 char *p;
96 p = strrchr(s, '/');
97 if (!p)
98 return s;
99 return (p+1);
102 static inline void remove_last(struct grow *g, int c)
104 int len = g->ptr;
105 while (len && g->s[len-1] == c) {
106 g->s[len-1] = 0;
107 len--;
109 g->ptr = len;
112 /* we completely ignore the fact that alarms can be delayed or that race
113 * condition could occur */
115 static char *show_unit(double val)
117 char *c = "\0KMGT";
118 static char buffer[128];
120 while (val >= 1000 && *(c+1)) {
121 val /= (double) 1024;
122 c++;
124 snprintf(buffer, 128, "%3.1lf %c", val, *c);
125 return buffer;
128 static char *show_time(int sec)
130 static char buffer[128];
132 snprintf(buffer, 128, "%3d:%02d", sec/60, sec%60);
133 return buffer;
136 static void show_band(struct bar *b)
138 int i;
139 double peak;
141 if (!b->peak)
142 peak = 100;
143 else
144 peak = (double) (b->peak + 1);
146 for (i=1; i<=BAR_WIDTH; i++) {
147 int idx = 5.0 * (double) step(b, -i) / peak;
148 putchar(" _.oO"[idx]);
149 /* putchar(" _.-~"[idx]); */
154 static void show_prog(struct bar *b)
156 char buf[PROG_WIDTH], fmt[8];
157 int rev, perc, i;
159 rev = (int)((b->xfered + b->resumed) * PROG_WIDTH / b->total);
160 perc = (int)((b->xfered + b->resumed) * 100ULL / b->total);
161 snprintf(fmt, 8, "%3d%%", perc);
163 memset(buf, ' ', PROG_WIDTH);
164 memcpy(buf+PROG_WIDTH/2-2, fmt, 4);
166 printf("[" INV_ON);
167 for (i=0; i<PROG_WIDTH; i++) {
168 if (i == rev)
169 printf(INV_OFF);
170 putchar(buf[i]);
172 printf(INV_OFF "]");
177 void update_stats(struct bar *b, size_t accum)
179 int i, peak;
181 /* we transfered some more */
182 b->xfered += accum;
184 /* set speed in sec */
185 step(b,0) = accum;
187 /* update speed stats */
188 b->cur_speed += accum - step(b,-CUR_SECS);
189 b->avg_speed += accum - step(b,-AVG_SECS);
191 /* calculate peak in last BAR_WIDTH secs */
192 peak = b->xfer[0];
193 for (i=1; i<BAR_WIDTH; i++)
194 peak = MAX(b->xfer[i], peak);
195 b->peak = peak;
197 /* step forward */
198 b->step++;
201 void show_stats(struct bar *b)
203 double avg, cur;
204 int eta;
206 avg = (double) b->avg_speed / (double) MIN(AVG_SECS, b->step);
207 cur = (double) b->cur_speed / (double) MIN(CUR_SECS, b->step);
209 printf("\r");
211 if (b->display & D_BAND)
212 show_band(b);
213 if (b->display & D_PROG)
214 show_prog(b);
215 if (b->display & D_ETA) {
216 if (avg > 0) {
217 eta = (double) (b->total - b->resumed - b->xfered) / avg;
218 printf(" ETA %s", show_time(eta));
219 } else {
220 printf(" ETA --:--");
223 if (b->display & D_SPEED) {
224 printf(", %sB/s", show_unit(cur));
226 if (b->display & D_SIZE) {
227 printf(", %sB of", show_unit(b->xfered+b->resumed));
228 printf(" %sB", show_unit(b->total));
230 if (b->display & D_NAME) {
231 printf(", %s", b->filename);
234 printf("\e[K");
235 fflush(stdout);
238 static void update(int sig)
240 struct bar *b = &progress;
241 time_t now = time(NULL), steps=0;
242 size_t accum_one;
244 alarm(1);
246 steps = MAX(1, now - b->last);
247 accum_one = (b->accum + steps - 1) / steps;
248 while(steps--)
249 update_stats(b, accum_one);
250 b->last = now;
251 b->accum = 0;
252 show_stats(b);
255 static inline void init_bar(struct bar *b, const char *filename, size_t total , size_t resumed)
257 memset(b, 0, sizeof(*b));
258 b->filename = filename;
259 b->display = display_opt;
260 b->start = time(NULL);
261 b->resumed = resumed;
262 b->total = total;
263 b->last = time(NULL);
264 signal(SIGALRM, update);
265 alarm(1);
268 static inline void fini_bar(struct bar *b)
270 signal(SIGALRM, SIG_IGN);
271 update(0);
272 printf("\r%s -- %s\e[K\n", show_time(time(NULL)-b->start), b->filename);
275 static inline void update_bar(struct bar *b, int data)
277 b->accum += data;
281 static int do_copy(char *src, int sfd, int dfd, off_t src_size, off_t dst_size)
283 off_t src_pos=0, dst_pos=0;
284 int ret, wrt;
287 dbg("total size %llu, resume from %lld\n", src_size, dst_size);
289 if (dst_size >= src_size) {
290 dbg("Already done.\n");
291 return 0;
294 if (dst_size > 0) {
295 src_pos = lseek(sfd, dst_size, SEEK_SET);
296 if (src_pos < 0 || src_pos != dst_size) {
297 perror("Cannot seek in input file");
298 return -1;
301 dst_pos = lseek(dfd, dst_size, SEEK_SET);
302 if (dst_pos < 0 || dst_pos != dst_size) {
303 perror("Cannot seek in output file");
304 return -1;
307 dbg("Resuming.\n");
310 init_bar(&progress, basename(src), src_size, dst_size);
311 while (1) {
312 againread:
313 ret = read(sfd, buffer, BS);
314 if (ret < 0) {
315 if (errno == EAGAIN) {
316 dbg("Interrupted.");
317 goto againread;
319 perror("Reading input file");
320 return -1;
322 if (ret == 0) {
323 if (dst_pos == src_size || !src_size) {
324 fini_bar(&progress);
325 return 0;
327 fini_bar(&progress);
328 dbg("Unexpected end of source.\n");
329 return 1;
332 againwrite:
333 wrt = write(dfd, buffer, ret);
334 if (wrt < 0) {
335 if (errno == EAGAIN) {
336 dbg("Interrupted.");
337 goto againwrite;
339 perror("Writing output file");
340 return -1;
342 if (wrt < ret) {
343 perror("Too few bytes written");
344 return -1;
347 dst_pos += wrt;
348 src_pos += wrt;
350 update_bar(&progress, wrt);
354 static int mkdirp(const char *dir, int mode)
356 char buffer[PATH_MAX];
357 const char *p, *q;
358 int ret, len;
360 q = p = dir;
362 while (p) {
363 for (; *p == '/'; p++);
364 if (!*p)
365 break;
367 q = strchr(p, '/');
368 if (!q)
369 len = strlen(dir);
370 else
371 len = q-dir;
373 len = MIN(len, PATH_MAX-1);
374 memcpy(buffer, dir, len);
375 buffer[len] = 0;
377 ret = mkdir(buffer, mode);
378 if (ret && errno != EEXIST)
379 return -1;
381 p = q;
383 return 0;
387 static int copy(struct grow *src, struct grow *dst, struct config *conf);
389 static int copydir(struct grow *src, struct grow *dst, struct config *conf)
391 DIR *d;
392 struct dirent *ent;
393 size_t optr, nptr;
395 d = opendir(grow_cstr(src));
396 if (!d) {
397 perror(grow_cstr(src));
398 return 0;
400 while ((ent = readdir(d))) {
401 if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
402 continue;
404 optr = grow_get_ptr(src);
405 nptr = grow_get_ptr(dst);
407 grow_put(src, '/');
408 grow_puts(src, ent->d_name);
409 copy(src, dst, conf);
411 grow_set_ptr(src, optr);
412 grow_set_ptr(dst, nptr);
414 closedir(d);
415 return 0;
419 static int copy(struct grow *srcg, struct grow *dstg, struct config *conf)
421 struct stat sb, db;
422 int sfd, dfd, found, ret=-1;
423 char *src, *dst;
425 src = grow_cstr(srcg);
426 dst = grow_cstr(dstg);
428 if (stat(src, &sb) < 0) {
429 perror(src);
430 return -1;
433 if (sb.st_mode & S_IFREG && sb.st_size <= 0) {
434 /* httpfs trick */
435 sleep(1);
436 stat(src, &sb);
439 if (sb.st_mode & S_IFDIR) {
440 int ret;
441 size_t optr;
443 if (!conf->recursive) {
444 serror(src, "Ommiting directory");
445 return -1;
448 conf->force_dir++;
449 optr = grow_get_ptr(dstg);
450 grow_put(dstg, '/');
451 grow_puts(dstg, basename(src));
453 if (mkdirp(grow_cstr(dstg), 0777))
454 return -1;
456 ret = copydir(srcg, dstg, conf);
458 grow_set_ptr(dstg, optr);
459 conf->force_dir--;
460 return ret;
463 if (!(sb.st_mode & S_IFREG)) {
464 serror(src, "Not a regular file");
465 return -1;
468 sfd = open(src, O_RDONLY);
469 if (sfd < 0) {
470 perror(src);
471 return -1;
475 found = !stat(dst, &db);
476 if (found) {
477 if (db.st_mode & S_IFDIR) {
478 grow_put(dstg, '/');
479 grow_puts(dstg, basename(src));
480 dst = grow_cstr(dstg);
481 found = !stat(dst, &db);
482 } else if (conf->force_dir) {
483 serror(dst, "Destination is not a directory");
484 goto sfd_close;
487 if (found) {
488 if (!(db.st_mode & S_IFREG)) {
489 serror(dst, "Destination not a regular file");
490 goto sfd_close;
493 if (!conf->resume) {
494 serror(src, "File exists");
495 goto sfd_close;
498 dfd = open(dst, O_WRONLY);
499 if (dfd < 0) {
500 perror(dst);
501 goto sfd_close;
504 ret = do_copy(src, sfd, dfd, sb.st_size, db.st_size);
505 goto dfd_close;
507 } else if (conf->force_dir) {
508 serror(dst, "Destination is not a directory");
509 goto sfd_close;
512 dfd = open(dst, O_CREAT|O_WRONLY, 0666);
513 if (dfd < 0) {
514 perror(dst);
515 goto sfd_close;
518 ret = do_copy(src, sfd, dfd, sb.st_size, 0);
519 dfd_close:
520 close(dfd);
521 sfd_close:
522 close(sfd);
523 return ret;
526 static void usage(char *me)
528 printf("usage: %s [-b] [-c] [-e] SOURCE(s) DEST\n", me);
529 printf("\t-b show bandwidth bar\n\t-c resume downloads\n\t-e show ETA\n");
530 exit(1);
533 char source[PATH_MAX], destination[PATH_MAX];
535 /* TODO: overwrite, recursive */
536 int main(int argc, char *argv[])
538 int nfiles=0, dstidx=0;
539 struct grow src, dst;
540 struct config conf;
541 int i;
543 memset(&conf, 0, sizeof(conf));
544 for (i=1; i<argc; i++) {
545 if (argv[i][0] == '-') {
546 switch(argv[i][1]) {
547 case 'r':
548 conf.recursive = 1;
549 break;
550 case '0':
551 display_opt = D_BAND | D_SPEED | D_SIZE;
552 break;
553 case 'b':
554 display_opt ^= D_BAND | D_PROG;
555 break;
556 case 'e':
557 display_opt ^= D_ETA;
558 break;
559 case 'n':
560 display_opt ^= D_NAME;
561 break;
562 case 'c':
563 conf.resume = 1;
564 break;
565 case 'h':
566 usage(argv[0]);
567 break;
568 default:
569 fprintf(stderr, "Unknown opt %c\n", argv[i][1]);
570 break;
572 continue;
574 nfiles++;
575 dstidx=i;
578 if (nfiles < 2)
579 usage(argv[0]);
580 if (nfiles > 2)
581 conf.force_dir=1;
583 for (i=1; i<dstidx; i++) {
584 if (argv[i][0] == '-')
585 continue;
587 grow_init_static(&src, source, PATH_MAX);
588 grow_init_static(&dst, destination, PATH_MAX);
589 grow_puts(&src, argv[i]);
590 grow_puts(&dst, argv[dstidx]);
591 remove_last(&src, '/');
592 remove_last(&dst, '/');
594 copy(&src, &dst, &conf);
596 return 0;