4 * Very simple 'cp' implementation with progressbar and resume.
5 * Usefull with sshfs/httpfs and other network filesystems.
8 * use another thread for progressbar instead of sigalarm -> blocked read
11 #define _FILE_OFFSET_BITS 64
22 #include <sys/types.h>
27 #define DEL_LINE "\e[2K"
28 #define INV_ON "\e[7m"
29 #define INV_OFF "\e[27m"
33 #define BAR_WIDTH (1<<BAR_BITS)
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)
57 size_t resumed
, xfered
, total
;
58 int step
, peak
, accum
;
59 int avg_speed
, cur_speed
;
72 display_t display_opt
= DEFAULT_DISPLAY
;
74 static inline void pexit(char *s
)
80 static inline void sexit(const char *fn
, const char *s
)
82 fprintf(stderr
, "%s: %s\n", fn
, s
);
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
)
102 static inline void remove_last(struct grow
*g
, int c
)
105 while (len
&& g
->s
[len
-1] == c
) {
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
)
118 static char buffer
[128];
120 while (val
>= 1000 && *(c
+1)) {
121 val
/= (double) 1024;
124 snprintf(buffer
, 128, "%3.1lf %c", val
, *c
);
128 static char *show_time(int sec
)
130 static char buffer
[128];
132 snprintf(buffer
, 128, "%3d:%02d", sec
/60, sec
%60);
136 static void show_band(struct bar
*b
)
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];
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);
167 for (i
=0; i
<PROG_WIDTH
; i
++) {
177 void update_stats(struct bar
*b
, size_t accum
)
181 /* we transfered some more */
184 /* set speed in sec */
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 */
193 for (i
=1; i
<BAR_WIDTH
; i
++)
194 peak
= MAX(b
->xfer
[i
], peak
);
201 void show_stats(struct bar
*b
)
206 avg
= (double) b
->avg_speed
/ (double) MIN(AVG_SECS
, b
->step
);
207 cur
= (double) b
->cur_speed
/ (double) MIN(CUR_SECS
, b
->step
);
211 if (b
->display
& D_BAND
)
213 if (b
->display
& D_PROG
)
215 if (b
->display
& D_ETA
) {
217 eta
= (double) (b
->total
- b
->resumed
- b
->xfered
) / avg
;
218 printf(" ETA %s", show_time(eta
));
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
);
238 static void update(int sig
)
240 struct bar
*b
= &progress
;
241 time_t now
= time(NULL
), steps
=0;
246 steps
= MAX(1, now
- b
->last
);
247 accum_one
= (b
->accum
+ steps
- 1) / steps
;
249 update_stats(b
, accum_one
);
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
;
263 b
->last
= time(NULL
);
264 signal(SIGALRM
, update
);
268 static inline void fini_bar(struct bar
*b
)
270 signal(SIGALRM
, SIG_IGN
);
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
)
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;
287 dbg("total size %llu, resume from %lld\n", src_size
, dst_size
);
289 if (dst_size
>= src_size
) {
290 dbg("Already done.\n");
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");
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");
310 init_bar(&progress
, basename(src
), src_size
, dst_size
);
313 ret
= read(sfd
, buffer
, BS
);
315 if (errno
== EAGAIN
) {
319 perror("Reading input file");
323 if (dst_pos
== src_size
|| !src_size
) {
328 dbg("Unexpected end of source.\n");
333 wrt
= write(dfd
, buffer
, ret
);
335 if (errno
== EAGAIN
) {
339 perror("Writing output file");
343 perror("Too few bytes written");
350 update_bar(&progress
, wrt
);
354 static int mkdirp(const char *dir
, int mode
)
356 char buffer
[PATH_MAX
];
363 for (; *p
== '/'; p
++);
373 len
= MIN(len
, PATH_MAX
-1);
374 memcpy(buffer
, dir
, len
);
377 ret
= mkdir(buffer
, mode
);
378 if (ret
&& errno
!= EEXIST
)
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
)
395 d
= opendir(grow_cstr(src
));
397 perror(grow_cstr(src
));
400 while ((ent
= readdir(d
))) {
401 if (!strcmp(ent
->d_name
, ".") || !strcmp(ent
->d_name
, ".."))
404 optr
= grow_get_ptr(src
);
405 nptr
= grow_get_ptr(dst
);
408 grow_puts(src
, ent
->d_name
);
409 copy(src
, dst
, conf
);
411 grow_set_ptr(src
, optr
);
412 grow_set_ptr(dst
, nptr
);
419 static int copy(struct grow
*srcg
, struct grow
*dstg
, struct config
*conf
)
422 int sfd
, dfd
, found
, ret
=-1;
425 src
= grow_cstr(srcg
);
426 dst
= grow_cstr(dstg
);
428 if (stat(src
, &sb
) < 0) {
433 if (sb
.st_mode
& S_IFREG
&& sb
.st_size
<= 0) {
439 if (sb
.st_mode
& S_IFDIR
) {
443 if (!conf
->recursive
) {
444 serror(src
, "Ommiting directory");
449 optr
= grow_get_ptr(dstg
);
451 grow_puts(dstg
, basename(src
));
453 if (mkdirp(grow_cstr(dstg
), 0777))
456 ret
= copydir(srcg
, dstg
, conf
);
458 grow_set_ptr(dstg
, optr
);
463 if (!(sb
.st_mode
& S_IFREG
)) {
464 serror(src
, "Not a regular file");
468 sfd
= open(src
, O_RDONLY
);
475 found
= !stat(dst
, &db
);
477 if (db
.st_mode
& S_IFDIR
) {
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");
488 if (!(db
.st_mode
& S_IFREG
)) {
489 serror(dst
, "Destination not a regular file");
494 serror(src
, "File exists");
498 dfd
= open(dst
, O_WRONLY
);
504 ret
= do_copy(src
, sfd
, dfd
, sb
.st_size
, db
.st_size
);
507 } else if (conf
->force_dir
) {
508 serror(dst
, "Destination is not a directory");
512 dfd
= open(dst
, O_CREAT
|O_WRONLY
, 0666);
518 ret
= do_copy(src
, sfd
, dfd
, sb
.st_size
, 0);
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");
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
;
543 memset(&conf
, 0, sizeof(conf
));
544 for (i
=1; i
<argc
; i
++) {
545 if (argv
[i
][0] == '-') {
551 display_opt
= D_BAND
| D_SPEED
| D_SIZE
;
554 display_opt
^= D_BAND
| D_PROG
;
557 display_opt
^= D_ETA
;
560 display_opt
^= D_NAME
;
569 fprintf(stderr
, "Unknown opt %c\n", argv
[i
][1]);
583 for (i
=1; i
<dstidx
; i
++) {
584 if (argv
[i
][0] == '-')
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
);