1 /* ui.cpp --
2 
3    This file is part of the UPX executable compressor.
4 
5    Copyright (C) 1996-2020 Markus Franz Xaver Johannes Oberhumer
6    Copyright (C) 1996-2020 Laszlo Molnar
7    All Rights Reserved.
8 
9    UPX and the UCL library are free software; you can redistribute them
10    and/or modify them under the terms of the GNU General Public License as
11    published by the Free Software Foundation; either version 2 of
12    the License, or (at your option) any later version.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; see the file COPYING.
21    If not, write to the Free Software Foundation, Inc.,
22    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 
24    Markus F.X.J. Oberhumer              Laszlo Molnar
25    <markus@oberhumer.com>               <ezerotven+github@gmail.com>
26  */
27 
28 #include "conf.h"
29 #include "file.h"
30 #include "ui.h"
31 #include "screen.h"
32 #include "packer.h"
33 
34 #if 1 && (USE_SCREEN)
35 #define UI_USE_SCREEN 1
36 #endif
37 
38 enum {
39     M_QUIET,    // nothing at all '-qqq'
40     M_INFO,     // print a one line info after compression '-qq'
41     M_MSG,      // print "compressing", then "\r" and M_INFO
42     M_CB_TERM,  // 1 line callback using stdout
43     M_CB_SCREEN // 2 line callback using screen
44 };
45 
46 struct UiPacker::State {
47     int mode;
48 
49     unsigned u_len;
50     unsigned step;
51     unsigned next_update;
52 
53     int pass;
54     int total_passes;
55 
56     // message stuff
57     char msg_buf[1 + 79 + 1];
58     int pos;               // last progress bar position
59     unsigned spin_counter; // for spinner
60 
61     int bar_pos;
62     int bar_len;
63     int pass_digits; // number of digits needed to print total_passes
64 
65 #if (UI_USE_SCREEN)
66     screen_t *screen;
67     int screen_init_done;
68     int b_cx, b_cy;
69     int s_cx, s_cy;
70     int s_fg, s_bg;
71     int c_fg;
72     int scroll_up;
73     int cursor_shape;
74 #else
75     void *screen;
76 #endif
77 };
78 
79 unsigned UiPacker::total_files = 0;
80 unsigned UiPacker::total_files_done = 0;
81 upx_uint64_t UiPacker::total_c_len = 0;
82 upx_uint64_t UiPacker::total_u_len = 0;
83 upx_uint64_t UiPacker::total_fc_len = 0;
84 upx_uint64_t UiPacker::total_fu_len = 0;
85 unsigned UiPacker::update_c_len = 0;
86 unsigned UiPacker::update_u_len = 0;
87 unsigned UiPacker::update_fc_len = 0;
88 unsigned UiPacker::update_fu_len = 0;
89 
90 /*************************************************************************
91 // constants
92 **************************************************************************/
93 
94 static const char header_line1[] = "        File size         Ratio      Format      Name\n";
95 static char header_line2[] = "   --------------------   ------   -----------   -----------\n";
96 
97 static char progress_filler[] = ".*[]";
98 
init_global_constants(void)99 static void init_global_constants(void) {
100 #if 0 && (ACC_OS_DOS16 || ACC_OS_DOS32)
101     // FIXME: should test codepage here
102 
103     static bool done = false;
104     if (done)
105         return;
106     done = true;
107 
108 #if 1 && (ACC_OS_DOS32) && defined(__DJGPP__)
109     /* check for Windows NT/2000/XP */
110     if (_get_dos_version(1) == 0x0532)
111         return;
112 #endif
113 
114     char *p;
115     for (p = header_line2; *p; p++)
116         if (*p == '-')
117             *p = '\xc4';
118 
119     //strcpy(progress_filler, "\x07\xb0[]");
120     //strcpy(progress_filler, "\x07\xb1[]");
121     strcpy(progress_filler, "\xf9\xfe[]");
122 #endif
123 }
124 
125 /*************************************************************************
126 //
127 **************************************************************************/
128 
mkline(upx_uint64_t fu_len,upx_uint64_t fc_len,upx_uint64_t u_len,upx_uint64_t c_len,const char * format_name,const char * filename,bool decompress=false)129 static const char *mkline(upx_uint64_t fu_len, upx_uint64_t fc_len, upx_uint64_t u_len,
130                           upx_uint64_t c_len, const char *format_name, const char *filename,
131                           bool decompress = false) {
132     static char buf[2048];
133     char r[7 + 1];
134     char fn[15 + 1];
135     const char *f;
136 
137     // Large ratios can happen because of overlays that are
138     // appended after a program is packed.
139     unsigned ratio = get_ratio(fu_len, fc_len);
140     if (ratio >= 1000 * 1000)
141         strcpy(r, "overlay");
142     else
143         upx_snprintf(r, sizeof(r), "%3u.%02u%%", ratio / 10000, (ratio % 10000) / 100);
144     if (decompress)
145         f = "%10lld <-%10lld  %7s %15s %s";
146     else
147         f = "%10lld ->%10lld  %7s %15s %s";
148     center_string(fn, sizeof(fn), format_name);
149     assert(strlen(fn) == 15);
150     upx_snprintf(buf, sizeof(buf), f, (long long) fu_len, (long long) fc_len, r, fn, filename);
151     UNUSED(u_len);
152     UNUSED(c_len);
153     return buf;
154 }
155 
156 /*************************************************************************
157 //
158 **************************************************************************/
159 
UiPacker(const Packer * p_)160 UiPacker::UiPacker(const Packer *p_) : ui_pass(0), ui_total_passes(0), p(p_), s(NULL) {
161     init_global_constants();
162 
163     cb.reset();
164 
165     s = new State;
166     memset(s, 0, sizeof(*s));
167     s->msg_buf[0] = '\r';
168 
169 #if defined(UI_USE_SCREEN)
170     // FIXME - ugly hack
171     s->screen = sobject_get_screen();
172 #endif
173 
174     if (opt->verbose < 0)
175         s->mode = M_QUIET;
176     else if (opt->verbose == 0 || !acc_isatty(STDOUT_FILENO))
177         s->mode = M_INFO;
178     else if (opt->verbose == 1 || opt->no_progress)
179         s->mode = M_MSG;
180     else if (s->screen == NULL)
181         s->mode = M_CB_TERM;
182     else
183         s->mode = M_CB_SCREEN;
184 }
185 
~UiPacker()186 UiPacker::~UiPacker() {
187     cb.reset();
188     delete s;
189     s = NULL;
190 }
191 
192 /*************************************************************************
193 // start callback
194 **************************************************************************/
195 
printInfo(int nl)196 void UiPacker::printInfo(int nl) {
197     if (opt->all_methods && s->total_passes > 1)
198         con_fprintf(stdout, "Compressing %s [%s]%s", p->fi->getName(), p->getName(),
199                     nl ? "\n" : "");
200     else {
201         char method_name[32 + 1];
202         set_method_name(method_name, sizeof(method_name), p->ph.method, p->ph.level);
203         con_fprintf(stdout, "Compressing %s [%s, %s]%s", p->fi->getName(), p->getName(),
204                     method_name, nl ? "\n" : "");
205     }
206 }
207 
startCallback(unsigned u_len,unsigned step,int pass,int total_passes)208 void UiPacker::startCallback(unsigned u_len, unsigned step, int pass, int total_passes) {
209     s->u_len = u_len;
210     s->step = step;
211     s->next_update = step;
212 
213     s->pass = pass;
214     s->total_passes = total_passes;
215     // printf("startCallback %d %d\n", s->pass, s->total_passes);
216 
217     s->bar_len = 64;
218     s->pos = -2;
219     s->spin_counter = 0;
220     s->bar_pos = 1; // because of the leading '\r'
221     s->pass_digits = 0;
222 
223     cb.reset();
224 
225     if (s->pass < 0) // no callback wanted
226         return;
227 
228     if (s->mode <= M_INFO)
229         return;
230     if (s->mode == M_MSG) {
231         if (pass <= 1) {
232             printInfo(0);
233             fflush(stdout);
234             printSetNl(2);
235         }
236         return;
237     }
238 
239     cb.nprogress = progress_callback;
240     cb.user = this; // parameter for static function UiPacker::progress_callback()
241 
242     if (s->mode == M_CB_TERM) {
243         const char *fname = fn_basename(p->fi->getName());
244         int l = (int) strlen(fname);
245         if (l > 0 && l <= 30) {
246             strcpy(&s->msg_buf[s->bar_pos], fname);
247             s->bar_pos += l;
248             s->msg_buf[s->bar_pos++] = ' ';
249             s->msg_buf[s->bar_pos++] = ' ';
250             s->bar_len -= l + 2;
251         }
252     }
253 
254     // set pass
255     if (total_passes > 1) {
256         int buflen, l;
257         do {
258             s->pass_digits++;
259             total_passes /= 10;
260         } while (total_passes > 0);
261         buflen = sizeof(s->msg_buf) - s->bar_pos;
262         l = upx_snprintf(&s->msg_buf[s->bar_pos], buflen, "%*d/%*d  ", s->pass_digits, s->pass,
263                          s->pass_digits, s->total_passes);
264         if (l > 0 && s->bar_len - l > 10) {
265             s->bar_len -= l;
266             s->bar_pos += l;
267         }
268     }
269 
270 #if (UI_USE_SCREEN)
271     if (s->mode == M_CB_SCREEN) {
272         if (!s->screen_init_done) {
273             s->screen_init_done = 1;
274             if (s->screen->hideCursor)
275                 s->cursor_shape = s->screen->hideCursor(s->screen);
276             s->s_fg = s->screen->getFg(s->screen);
277             s->s_bg = s->screen->getBg(s->screen);
278             s->screen->getCursor(s->screen, &s->s_cx, &s->s_cy);
279             s->scroll_up = s->screen->getScrollCounter(s->screen);
280             printInfo(1);
281             s->screen->getCursor(s->screen, &s->b_cx, &s->b_cy);
282             s->scroll_up = s->screen->getScrollCounter(s->screen) - s->scroll_up;
283         }
284     }
285 #endif /* UI_USE_SCREEN */
286 }
287 
288 // may only get called directly after startCallback()
firstCallback()289 void UiPacker::firstCallback() {
290     if (s->pos == -2)
291         doCallback(0, 0);
292 }
293 
294 // make sure we reach 100% in the progress bar
finalCallback(unsigned u_len,unsigned c_len)295 void UiPacker::finalCallback(unsigned u_len, unsigned c_len) {
296     s->next_update = u_len;
297     doCallback(u_len, c_len);
298 }
299 
300 /*************************************************************************
301 // end callback
302 **************************************************************************/
303 
endCallback()304 void UiPacker::endCallback() {
305     bool done = (s->total_passes <= 0 || s->pass >= s->total_passes);
306     endCallback(done);
307 }
308 
endCallback(bool done)309 void UiPacker::endCallback(bool done) {
310     if (s->pass < 0) // no callback wanted
311         return;
312 
313     if (s->mode == M_CB_TERM) {
314         if (done)
315             printClearLine(stdout);
316         else
317             printSetNl(2);
318     }
319 
320 // restore screen
321 #if (UI_USE_SCREEN)
322     if (s->mode == M_CB_SCREEN) {
323         if (done) {
324             int cx, cy, sy;
325             assert(s->screen_init_done);
326             s->screen_init_done = 0;
327             assert(s->s_cx == 0 && s->b_cx == 0);
328             s->screen->getCursor(s->screen, &cx, &cy);
329             sy = UPX_MAX(0, s->s_cy - s->scroll_up);
330             while (cy >= sy)
331                 s->screen->clearLine(s->screen, cy--);
332             s->screen->setCursor(s->screen, s->s_cx, sy);
333             s->screen->setFg(s->screen, s->s_fg);
334             s->screen->setBg(s->screen, s->s_bg);
335             if (s->cursor_shape > 0)
336                 s->screen->setCursorShape(s->screen, s->cursor_shape);
337         } else {
338             // not needed:
339             //   s->screen->clearLine(s->screen, s->b_cy);
340             //   s->screen->setCursor(s->screen, s->b_cx, s->b_cy);
341         }
342     }
343 #endif /* UI_USE_SCREEN */
344 
345     cb.reset();
346 #if 0
347     printf("callback: pass %d, step %6d, updates %6d\n",
348            s->pass, s->step, s->spin_counter);
349 #endif
350 }
351 
352 /*************************************************************************
353 // the callback
354 **************************************************************************/
355 
progress_callback(upx_callback_p cb,unsigned isize,unsigned osize)356 void __acc_cdecl UiPacker::progress_callback(upx_callback_p cb, unsigned isize, unsigned osize) {
357     // printf("%6d %6d %d\n", isize, osize, state);
358     UiPacker *self = (UiPacker *) cb->user;
359     self->doCallback(isize, osize);
360 }
361 
doCallback(unsigned isize,unsigned osize)362 void UiPacker::doCallback(unsigned isize, unsigned osize) {
363     int i;
364     static const char spinner[] = "|/-\\";
365 
366     if (s->pass < 0) // no callback wanted
367         return;
368 
369     if (s->u_len == 0 || isize > s->u_len)
370         return;
371     // check if we should update the display
372     if (s->step > 0 && isize > 0 && isize < s->u_len) {
373         if (isize < s->next_update)
374             return;
375         s->next_update += s->step;
376     }
377 
378     // compute progress position
379     int pos = -1;
380     if (isize >= s->u_len)
381         pos = s->bar_len;
382     else if (isize > 0) {
383         pos = get_ratio(s->u_len, isize) * s->bar_len / 1000000;
384         assert(pos >= 0);
385         assert(pos <= s->bar_len);
386     }
387 
388 #if 0
389     printf("%6d %6d %6d %6d %3d %3d\n", isize, osize, s->step, s->next_update, pos, s->pos);
390     return;
391 #endif
392 
393     if (pos < s->pos)
394         return;
395     if (pos < 0 && pos == s->pos)
396         return;
397 
398     // fill the progress bar
399     char *m = &s->msg_buf[s->bar_pos];
400     *m++ = progress_filler[2];
401     for (i = 0; i < s->bar_len; i++)
402         *m++ = progress_filler[i <= pos];
403     *m++ = progress_filler[3];
404 
405     // compute current compression ratio
406     unsigned ratio = 1000000;
407     if (osize > 0)
408         ratio = get_ratio(isize, osize);
409 
410     int buflen = (int) (&s->msg_buf[sizeof(s->msg_buf)] - m);
411     upx_snprintf(m, buflen, "  %3d.%1d%%  %c ", ratio / 10000, (ratio % 10000) / 1000,
412                  spinner[s->spin_counter & 3]);
413     assert(strlen(s->msg_buf) < 1 + 80);
414 
415     s->pos = pos;
416     s->spin_counter++;
417 
418     if (s->mode == M_CB_TERM) {
419         const char *msg = &s->msg_buf[0];
420         int fg = con_fg(stdout, FG_CYAN);
421         con_fprintf(stdout, "%s", msg); // avoid backslash interpretation
422         (void) con_fg(stdout, fg);
423         fflush(stdout);
424         printSetNl(1);
425         UNUSED(fg);
426         return;
427     }
428 
429 #if (UI_USE_SCREEN)
430     if (s->mode == M_CB_SCREEN) {
431         const char *msg = &s->msg_buf[1];
432 #if 0
433         s->screen->putString(s->screen,msg,s->b_cx,s->b_cy);
434 #else
435         // FIXME: this doesn't honor '--mono' etc.
436         int attr = FG_CYAN | s->s_bg;
437         s->screen->putStringAttr(s->screen, msg, attr, s->b_cx, s->b_cy);
438 #endif
439         s->screen->refresh(s->screen);
440     }
441 #endif /* UI_USE_SCREEN */
442 }
443 
444 /*************************************************************************
445 // pack
446 **************************************************************************/
447 
uiPackStart(const OutputFile * fo)448 void UiPacker::uiPackStart(const OutputFile *fo) {
449     total_files++;
450     UNUSED(fo);
451 }
452 
uiPackEnd(const OutputFile * fo)453 void UiPacker::uiPackEnd(const OutputFile *fo) {
454     uiUpdate(fo->st_size());
455 
456     if (s->mode == M_QUIET)
457         return;
458     if (s->mode == M_MSG) {
459         // We must put this here and not in endCallback() as we may
460         // have multiple passes.
461         printClearLine(stdout);
462     }
463 
464     const char *name = p->fi->getName();
465     if (opt->output_name)
466         name = opt->output_name;
467     else if (opt->to_stdout)
468         name = "<stdout>";
469     con_fprintf(stdout, "%s\n", mkline(p->ph.u_file_size, fo->st_size(), p->ph.u_len, p->ph.c_len,
470                                        p->getName(), fn_basename(name)));
471     printSetNl(0);
472 }
473 
uiPackTotal()474 void UiPacker::uiPackTotal() {
475     uiListTotal();
476     uiFooter("Packed");
477 }
478 
479 /*************************************************************************
480 // unpack
481 **************************************************************************/
482 
uiUnpackStart(const OutputFile * fo)483 void UiPacker::uiUnpackStart(const OutputFile *fo) {
484     total_files++;
485     UNUSED(fo);
486 }
487 
uiUnpackEnd(const OutputFile * fo)488 void UiPacker::uiUnpackEnd(const OutputFile *fo) {
489     uiUpdate(-1, fo->getBytesWritten());
490 
491     if (s->mode == M_QUIET)
492         return;
493 
494     const char *name = p->fi->getName();
495     if (opt->output_name)
496         name = opt->output_name;
497     else if (opt->to_stdout)
498         name = "<stdout>";
499     con_fprintf(stdout, "%s\n", mkline(fo->getBytesWritten(), p->file_size, p->ph.u_len,
500                                        p->ph.c_len, p->getName(), fn_basename(name), true));
501     printSetNl(0);
502 }
503 
uiUnpackTotal()504 void UiPacker::uiUnpackTotal() {
505     uiListTotal(true);
506     uiFooter("Unpacked");
507 }
508 
509 /*************************************************************************
510 // list
511 **************************************************************************/
512 
uiListStart()513 void UiPacker::uiListStart() { total_files++; }
514 
uiList()515 void UiPacker::uiList() {
516     const char *name = p->fi->getName();
517     con_fprintf(stdout, "%s\n", mkline(p->ph.u_file_size, p->file_size, p->ph.u_len, p->ph.c_len,
518                                        p->getName(), name));
519     printSetNl(0);
520 }
521 
uiListEnd()522 void UiPacker::uiListEnd() { uiUpdate(); }
523 
uiListTotal(bool decompress)524 void UiPacker::uiListTotal(bool decompress) {
525     if (opt->verbose >= 1 && total_files >= 2) {
526         char name[32];
527         upx_snprintf(name, sizeof(name), "[ %u file%s ]", total_files_done,
528                      total_files_done == 1 ? "" : "s");
529         con_fprintf(stdout, "%s%s\n", header_line2, mkline(total_fu_len, total_fc_len, total_u_len,
530                                                            total_c_len, "", name, decompress));
531         printSetNl(0);
532     }
533 }
534 
535 /*************************************************************************
536 // test
537 **************************************************************************/
538 
uiTestStart()539 void UiPacker::uiTestStart() {
540     total_files++;
541 
542     if (opt->verbose >= 1) {
543         con_fprintf(stdout, "testing %s ", p->fi->getName());
544         fflush(stdout);
545         printSetNl(1);
546     }
547 }
548 
uiTestEnd()549 void UiPacker::uiTestEnd() {
550     if (opt->verbose >= 1) {
551         con_fprintf(stdout, "[OK]\n");
552         fflush(stdout);
553         printSetNl(0);
554     }
555     uiUpdate();
556 }
557 
uiTestTotal()558 void UiPacker::uiTestTotal() { uiFooter("Tested"); }
559 
560 /*************************************************************************
561 // info
562 **************************************************************************/
563 
uiFileInfoStart()564 bool UiPacker::uiFileInfoStart() {
565 #if defined(_WIN32) // msvcrt
566 #define PRLLD "I64d"
567 #else
568 #define PRLLD "lld"
569 #endif
570     total_files++;
571 
572     int fg = con_fg(stdout, FG_CYAN);
573     con_fprintf(stdout, "%s [%s, %s]\n", p->fi->getName(), p->getFullName(opt), p->getName());
574     fg = con_fg(stdout, fg);
575     UNUSED(fg);
576     if (p->ph.c_len > 0) {
577         con_fprintf(stdout, "  %8" PRLLD " bytes", (long long) p->file_size);
578         con_fprintf(stdout, ", compressed by UPX %d, method %d, level %d, filter 0x%02x/0x%02x\n",
579                     p->ph.version, p->ph.method, p->ph.level, p->ph.filter, p->ph.filter_cto);
580         return false;
581     } else {
582         con_fprintf(stdout, "  %8" PRLLD " bytes", (long long) p->file_size);
583         con_fprintf(stdout, ", not compressed by UPX\n");
584         return true;
585     }
586 #undef PRLLD
587 }
588 
uiFileInfoEnd()589 void UiPacker::uiFileInfoEnd() { uiUpdate(); }
590 
uiFileInfoTotal()591 void UiPacker::uiFileInfoTotal() {}
592 
593 /*************************************************************************
594 // util
595 **************************************************************************/
596 
uiHeader()597 void UiPacker::uiHeader() {
598     static bool done = false;
599     if (done)
600         return;
601     done = true;
602     if (opt->cmd == CMD_TEST || opt->cmd == CMD_FILEINFO)
603         return;
604     if (opt->verbose >= 1) {
605         con_fprintf(stdout, "%s%s", header_line1, header_line2);
606     }
607 }
608 
uiFooter(const char * t)609 void UiPacker::uiFooter(const char *t) {
610     static bool done = false;
611     if (done)
612         return;
613     done = true;
614     if (opt->verbose >= 1) {
615         assert(total_files >= total_files_done);
616         unsigned n1 = total_files;
617         unsigned n2 = total_files_done;
618         unsigned n3 = total_files - total_files_done;
619         if (n3 == 0)
620             con_fprintf(stdout, "\n%s %u file%s.\n", t, n1, n1 == 1 ? "" : "s");
621         else
622             con_fprintf(stdout, "\n%s %u file%s: %u ok, %u error%s.\n", t, n1, n1 == 1 ? "" : "s",
623                         n2, n3, n3 == 1 ? "" : "s");
624     }
625 }
626 
uiUpdate(off_t fc_len,off_t fu_len)627 void UiPacker::uiUpdate(off_t fc_len, off_t fu_len) {
628     update_fc_len = (fc_len >= 0) ? fc_len : p->file_size;
629     update_fu_len = (fu_len >= 0) ? fu_len : p->ph.u_file_size;
630     update_c_len = p->ph.c_len;
631     update_u_len = p->ph.u_len;
632 }
633 
uiConfirmUpdate()634 void UiPacker::uiConfirmUpdate() {
635     total_files_done++;
636     total_fc_len += update_fc_len;
637     total_fu_len += update_fu_len;
638     total_c_len += update_c_len;
639     total_u_len += update_u_len;
640 }
641 
642 /* vim:set ts=4 sw=4 et: */
643