1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22 #include "tool_setup.h"
23 #include "tool_operate.h"
24 #include "tool_progress.h"
25 #include "tool_util.h"
26 
27 #define ENABLE_CURLX_PRINTF
28 /* use our own printf() functions */
29 #include "curlx.h"
30 
31 /* The point of this function would be to return a string of the input data,
32    but never longer than 5 columns (+ one zero byte).
33    Add suffix k, M, G when suitable... */
max5data(curl_off_t bytes,char * max5)34 static char *max5data(curl_off_t bytes, char *max5)
35 {
36 #define ONE_KILOBYTE  CURL_OFF_T_C(1024)
37 #define ONE_MEGABYTE (CURL_OFF_T_C(1024) * ONE_KILOBYTE)
38 #define ONE_GIGABYTE (CURL_OFF_T_C(1024) * ONE_MEGABYTE)
39 #define ONE_TERABYTE (CURL_OFF_T_C(1024) * ONE_GIGABYTE)
40 #define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE)
41 
42   if(bytes < CURL_OFF_T_C(100000))
43     msnprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes);
44 
45   else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE)
46     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k", bytes/ONE_KILOBYTE);
47 
48   else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE)
49     /* 'XX.XM' is good as long as we're less than 100 megs */
50     msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0"
51               CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE,
52               (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) );
53 
54 #if (SIZEOF_CURL_OFF_T > 4)
55 
56   else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE)
57     /* 'XXXXM' is good until we're at 10000MB or above */
58     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE);
59 
60   else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE)
61     /* 10000 MB - 100 GB, we show it as XX.XG */
62     msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0"
63               CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE,
64               (bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) );
65 
66   else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE)
67     /* up to 10000GB, display without decimal: XXXXG */
68     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE);
69 
70   else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE)
71     /* up to 10000TB, display without decimal: XXXXT */
72     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "T", bytes/ONE_TERABYTE);
73 
74   else
75     /* up to 10000PB, display without decimal: XXXXP */
76     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "P", bytes/ONE_PETABYTE);
77 
78     /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number
79        can hold, but our data type is signed so 8192PB will be the maximum. */
80 
81 #else
82 
83   else
84     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE);
85 
86 #endif
87 
88   return max5;
89 }
90 
xferinfo_cb(void * clientp,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)91 int xferinfo_cb(void *clientp,
92                 curl_off_t dltotal,
93                 curl_off_t dlnow,
94                 curl_off_t ultotal,
95                 curl_off_t ulnow)
96 {
97   struct per_transfer *per = clientp;
98   struct OperationConfig *config = per->config;
99   per->dltotal = dltotal;
100   per->dlnow = dlnow;
101   per->ultotal = ultotal;
102   per->ulnow = ulnow;
103 
104   if(per->abort)
105     return 1;
106 
107   if(config->readbusy) {
108     config->readbusy = FALSE;
109     curl_easy_pause(per->curl, CURLPAUSE_CONT);
110   }
111 
112   return 0;
113 }
114 
115 /* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero
116    byte) */
time2str(char * r,curl_off_t seconds)117 static void time2str(char *r, curl_off_t seconds)
118 {
119   curl_off_t h;
120   if(seconds <= 0) {
121     strcpy(r, "--:--:--");
122     return;
123   }
124   h = seconds / CURL_OFF_T_C(3600);
125   if(h <= CURL_OFF_T_C(99)) {
126     curl_off_t m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60);
127     curl_off_t s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60));
128     msnprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T ":%02" CURL_FORMAT_CURL_OFF_T
129               ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s);
130   }
131   else {
132     /* this equals to more than 99 hours, switch to a more suitable output
133        format to fit within the limits. */
134     curl_off_t d = seconds / CURL_OFF_T_C(86400);
135     h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600);
136     if(d <= CURL_OFF_T_C(999))
137       msnprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T
138                 "d %02" CURL_FORMAT_CURL_OFF_T "h", d, h);
139     else
140       msnprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d", d);
141   }
142 }
143 
144 static curl_off_t all_dltotal = 0;
145 static curl_off_t all_ultotal = 0;
146 static curl_off_t all_dlalready = 0;
147 static curl_off_t all_ulalready = 0;
148 
149 curl_off_t all_xfers = 0;   /* current total */
150 
151 struct speedcount {
152   curl_off_t dl;
153   curl_off_t ul;
154   struct timeval stamp;
155 };
156 #define SPEEDCNT 10
157 static unsigned int speedindex;
158 static bool indexwrapped;
159 static struct speedcount speedstore[SPEEDCNT];
160 
161 /*
162   |DL% UL%  Dled  Uled  Xfers  Live   Qd Total     Current  Left    Speed
163   |  6 --   9.9G     0     2     2     0  0:00:40  0:00:02  0:00:37 4087M
164 */
progress_meter(struct GlobalConfig * global,struct timeval * start,bool final)165 bool progress_meter(struct GlobalConfig *global,
166                     struct timeval *start,
167                     bool final)
168 {
169   static struct timeval stamp;
170   static bool header = FALSE;
171   struct timeval now;
172   long diff;
173 
174   if(global->noprogress)
175     return FALSE;
176 
177   now = tvnow();
178   diff = tvdiff(now, stamp);
179 
180   if(!header) {
181     header = TRUE;
182     fputs("DL% UL%  Dled  Uled  Xfers  Live   Qd "
183           "Total     Current  Left    Speed\n",
184           global->errors);
185   }
186   if(final || (diff > 500)) {
187     char time_left[10];
188     char time_total[10];
189     char time_spent[10];
190     char buffer[3][6];
191     curl_off_t spent = tvdiff(now, *start)/1000;
192     char dlpercen[4]="--";
193     char ulpercen[4]="--";
194     struct per_transfer *per;
195     curl_off_t all_dlnow = 0;
196     curl_off_t all_ulnow = 0;
197     bool dlknown = TRUE;
198     bool ulknown = TRUE;
199     curl_off_t all_running = 0; /* in progress */
200     curl_off_t all_queued = 0;  /* pending */
201     curl_off_t speed = 0;
202     unsigned int i;
203     stamp = now;
204 
205     /* first add the amounts of the already completed transfers */
206     all_dlnow += all_dlalready;
207     all_ulnow += all_ulalready;
208 
209     for(per = transfers; per; per = per->next) {
210       all_dlnow += per->dlnow;
211       all_ulnow += per->ulnow;
212       if(!per->dltotal)
213         dlknown = FALSE;
214       else if(!per->dltotal_added) {
215         /* only add this amount once */
216         all_dltotal += per->dltotal;
217         per->dltotal_added = TRUE;
218       }
219       if(!per->ultotal)
220         ulknown = FALSE;
221       else if(!per->ultotal_added) {
222         /* only add this amount once */
223         all_ultotal += per->ultotal;
224         per->ultotal_added = TRUE;
225       }
226       if(!per->added)
227         all_queued++;
228       else
229         all_running++;
230     }
231     if(dlknown && all_dltotal)
232       /* TODO: handle integer overflow */
233       msnprintf(dlpercen, sizeof(dlpercen), "%3" CURL_FORMAT_CURL_OFF_T,
234                 all_dlnow * 100 / all_dltotal);
235     if(ulknown && all_ultotal)
236       /* TODO: handle integer overflow */
237       msnprintf(ulpercen, sizeof(ulpercen), "%3" CURL_FORMAT_CURL_OFF_T,
238                 all_ulnow * 100 / all_ultotal);
239 
240     /* get the transfer speed, the higher of the two */
241 
242     i = speedindex;
243     speedstore[i].dl = all_dlnow;
244     speedstore[i].ul = all_ulnow;
245     speedstore[i].stamp = now;
246     if(++speedindex >= SPEEDCNT) {
247       indexwrapped = TRUE;
248       speedindex = 0;
249     }
250 
251     {
252       long deltams;
253       curl_off_t dl;
254       curl_off_t ul;
255       curl_off_t dls;
256       curl_off_t uls;
257       if(indexwrapped) {
258         /* 'speedindex' is the oldest stored data */
259         deltams = tvdiff(now, speedstore[speedindex].stamp);
260         dl = all_dlnow - speedstore[speedindex].dl;
261         ul = all_ulnow - speedstore[speedindex].ul;
262       }
263       else {
264         /* since the beginning */
265         deltams = tvdiff(now, *start);
266         dl = all_dlnow;
267         ul = all_ulnow;
268       }
269       dls = (curl_off_t)((double)dl / ((double)deltams/1000.0));
270       uls = (curl_off_t)((double)ul / ((double)deltams/1000.0));
271       speed = dls > uls ? dls : uls;
272     }
273 
274 
275     if(dlknown && speed) {
276       curl_off_t est = all_dltotal / speed;
277       curl_off_t left = (all_dltotal - all_dlnow) / speed;
278       time2str(time_left, left);
279       time2str(time_total, est);
280     }
281     else {
282       time2str(time_left, 0);
283       time2str(time_total, 0);
284     }
285     time2str(time_spent, spent);
286 
287     fprintf(global->errors,
288             "\r"
289             "%-3s " /* percent downloaded */
290             "%-3s " /* percent uploaded */
291             "%s " /* Dled */
292             "%s " /* Uled */
293             "%5" CURL_FORMAT_CURL_OFF_T " " /* Xfers */
294             "%5" CURL_FORMAT_CURL_OFF_T " " /* Live */
295             "%5" CURL_FORMAT_CURL_OFF_T " " /* Queued */
296             "%s "  /* Total time */
297             "%s "  /* Current time */
298             "%s "  /* Time left */
299             "%s "  /* Speed */
300             "%5s" /* final newline */,
301 
302             dlpercen,  /* 3 letters */
303             ulpercen,  /* 3 letters */
304             max5data(all_dlnow, buffer[0]),
305             max5data(all_ulnow, buffer[1]),
306             all_xfers,
307             all_running,
308             all_queued,
309             time_total,
310             time_spent,
311             time_left,
312             max5data(speed, buffer[2]), /* speed */
313             final ? "\n" :"");
314     return TRUE;
315   }
316   return FALSE;
317 }
318 
progress_finalize(struct per_transfer * per)319 void progress_finalize(struct per_transfer *per)
320 {
321   /* get the numbers before this transfer goes away */
322   all_dlalready += per->dlnow;
323   all_ulalready += per->ulnow;
324   if(!per->dltotal_added) {
325     all_dltotal += per->dltotal;
326     per->dltotal_added = TRUE;
327   }
328   if(!per->ultotal_added) {
329     all_ultotal += per->ultotal;
330     per->ultotal_added = TRUE;
331   }
332 }
333