]> git.saurik.com Git - apt.git/blob - apt-pkg/install-progress.cc
prevent C++ locale number formatting in text APIs (try 3)
[apt.git] / apt-pkg / install-progress.cc
1 #include <config.h>
2
3 #include <apt-pkg/configuration.h>
4 #include <apt-pkg/fileutl.h>
5 #include <apt-pkg/strutl.h>
6 #include <apt-pkg/install-progress.h>
7
8 #include <signal.h>
9 #include <unistd.h>
10 #include <iostream>
11 #include <vector>
12 #include <sys/ioctl.h>
13 #include <fcntl.h>
14 #include <algorithm>
15 #include <stdio.h>
16 #include <sstream>
17 #include <cmath>
18
19 #include <apti18n.h>
20
21 namespace APT {
22 namespace Progress {
23
24 PackageManager::PackageManager() : d(NULL), percentage(0.0), last_reported_progress(-1) {}
25 PackageManager::~PackageManager() {}
26
27 /* Return a APT::Progress::PackageManager based on the global
28 * apt configuration (i.e. APT::Status-Fd and APT::Status-deb822-Fd)
29 */
30 PackageManager* PackageManagerProgressFactory()
31 {
32 // select the right progress
33 int status_fd = _config->FindI("APT::Status-Fd", -1);
34 int status_deb822_fd = _config->FindI("APT::Status-deb822-Fd", -1);
35
36 APT::Progress::PackageManager *progress = NULL;
37 if (status_deb822_fd > 0)
38 progress = new APT::Progress::PackageManagerProgressDeb822Fd(
39 status_deb822_fd);
40 else if (status_fd > 0)
41 progress = new APT::Progress::PackageManagerProgressFd(status_fd);
42 else if(_config->FindB("Dpkg::Progress-Fancy", false) == true)
43 progress = new APT::Progress::PackageManagerFancy();
44 else if (_config->FindB("Dpkg::Progress",
45 _config->FindB("DpkgPM::Progress", false)) == true)
46 progress = new APT::Progress::PackageManagerText();
47 else
48 progress = new APT::Progress::PackageManager();
49 return progress;
50 }
51
52 bool PackageManager::StatusChanged(std::string /*PackageName*/,
53 unsigned int StepsDone,
54 unsigned int TotalSteps,
55 std::string /*HumanReadableAction*/)
56 {
57 int reporting_steps = _config->FindI("DpkgPM::Reporting-Steps", 1);
58 percentage = StepsDone/(float)TotalSteps * 100.0;
59 strprintf(progress_str, _("Progress: [%3i%%]"), (int)percentage);
60
61 if(percentage < (last_reported_progress + reporting_steps))
62 return false;
63
64 return true;
65 }
66
67 PackageManagerProgressFd::PackageManagerProgressFd(int progress_fd)
68 : d(NULL), StepsDone(0), StepsTotal(1)
69 {
70 OutStatusFd = progress_fd;
71 }
72 PackageManagerProgressFd::~PackageManagerProgressFd() {}
73
74 void PackageManagerProgressFd::WriteToStatusFd(std::string s)
75 {
76 if(OutStatusFd <= 0)
77 return;
78 FileFd::Write(OutStatusFd, s.c_str(), s.size());
79 }
80
81 static std::string GetProgressFdString(char const * const status,
82 char const * const pkg, unsigned long long Done,
83 unsigned long long Total, char const * const msg)
84 {
85 float const progress{Done / static_cast<float>(Total) * 100};
86 std::ostringstream str;
87 str.imbue(std::locale("C.UTF-8"));
88 str.precision(4);
89 str << status << ':' << pkg << ':' << std::fixed << progress << ':' << msg << '\n';
90 return str.str();
91 }
92
93 void PackageManagerProgressFd::StartDpkg()
94 {
95 if(OutStatusFd <= 0)
96 return;
97
98 // FIXME: use SetCloseExec here once it taught about throwing
99 // exceptions instead of doing _exit(100) on failure
100 fcntl(OutStatusFd,F_SETFD,FD_CLOEXEC);
101
102 // send status information that we are about to fork dpkg
103 WriteToStatusFd(GetProgressFdString("pmstatus", "dpkg-exec", StepsDone, StepsTotal, _("Running dpkg")));
104 }
105
106 APT_CONST void PackageManagerProgressFd::Stop()
107 {
108 }
109
110 void PackageManagerProgressFd::Error(std::string PackageName,
111 unsigned int StepsDone,
112 unsigned int TotalSteps,
113 std::string ErrorMessage)
114 {
115 WriteToStatusFd(GetProgressFdString("pmerror", PackageName.c_str(),
116 StepsDone, TotalSteps, ErrorMessage.c_str()));
117 }
118
119 void PackageManagerProgressFd::ConffilePrompt(std::string PackageName,
120 unsigned int StepsDone,
121 unsigned int TotalSteps,
122 std::string ConfMessage)
123 {
124 WriteToStatusFd(GetProgressFdString("pmconffile", PackageName.c_str(),
125 StepsDone, TotalSteps, ConfMessage.c_str()));
126 }
127
128
129 bool PackageManagerProgressFd::StatusChanged(std::string PackageName,
130 unsigned int xStepsDone,
131 unsigned int xTotalSteps,
132 std::string pkg_action)
133 {
134 StepsDone = xStepsDone;
135 StepsTotal = xTotalSteps;
136
137 WriteToStatusFd(GetProgressFdString("pmstatus", StringSplit(PackageName, ":")[0].c_str(),
138 StepsDone, StepsTotal, pkg_action.c_str()));
139
140 if(_config->FindB("Debug::APT::Progress::PackageManagerFd", false) == true)
141 std::cerr << "progress: " << PackageName << " " << xStepsDone
142 << " " << xTotalSteps << " " << pkg_action
143 << std::endl;
144
145
146 return true;
147 }
148
149
150 PackageManagerProgressDeb822Fd::PackageManagerProgressDeb822Fd(int progress_fd)
151 : d(NULL), StepsDone(0), StepsTotal(1)
152 {
153 OutStatusFd = progress_fd;
154 }
155 PackageManagerProgressDeb822Fd::~PackageManagerProgressDeb822Fd() {}
156
157 void PackageManagerProgressDeb822Fd::WriteToStatusFd(std::string s)
158 {
159 FileFd::Write(OutStatusFd, s.c_str(), s.size());
160 }
161
162 static std::string GetProgressDeb822String(char const * const status,
163 char const * const pkg, unsigned long long Done,
164 unsigned long long Total, char const * const msg)
165 {
166 float const progress{Done / static_cast<float>(Total) * 100};
167 std::ostringstream str;
168 str.imbue(std::locale("C.UTF-8"));
169 str.precision(4);
170 str << "Status: " << status << '\n';
171 if (pkg != nullptr)
172 str << "Package: " << pkg << '\n';
173 str << "Percent: " << std::fixed << progress << '\n'
174 << "Message: " << msg << "\n\n";
175 return str.str();
176 }
177
178 void PackageManagerProgressDeb822Fd::StartDpkg()
179 {
180 // FIXME: use SetCloseExec here once it taught about throwing
181 // exceptions instead of doing _exit(100) on failure
182 fcntl(OutStatusFd,F_SETFD,FD_CLOEXEC);
183
184 WriteToStatusFd(GetProgressDeb822String("progress", nullptr, StepsDone, StepsTotal, _("Running dpkg")));
185 }
186
187 APT_CONST void PackageManagerProgressDeb822Fd::Stop()
188 {
189 }
190
191 void PackageManagerProgressDeb822Fd::Error(std::string PackageName,
192 unsigned int StepsDone,
193 unsigned int TotalSteps,
194 std::string ErrorMessage)
195 {
196 WriteToStatusFd(GetProgressDeb822String("Error", PackageName.c_str(), StepsDone, TotalSteps, ErrorMessage.c_str()));
197 }
198
199 void PackageManagerProgressDeb822Fd::ConffilePrompt(std::string PackageName,
200 unsigned int StepsDone,
201 unsigned int TotalSteps,
202 std::string ConfMessage)
203 {
204 WriteToStatusFd(GetProgressDeb822String("ConfFile", PackageName.c_str(), StepsDone, TotalSteps, ConfMessage.c_str()));
205 }
206
207
208 bool PackageManagerProgressDeb822Fd::StatusChanged(std::string PackageName,
209 unsigned int xStepsDone,
210 unsigned int xTotalSteps,
211 std::string message)
212 {
213 StepsDone = xStepsDone;
214 StepsTotal = xTotalSteps;
215
216 WriteToStatusFd(GetProgressDeb822String("progress", PackageName.c_str(), StepsDone, StepsTotal, message.c_str()));
217 return true;
218 }
219
220
221 PackageManagerFancy::PackageManagerFancy()
222 : d(NULL), child_pty(-1)
223 {
224 // setup terminal size
225 old_SIGWINCH = signal(SIGWINCH, PackageManagerFancy::staticSIGWINCH);
226 instances.push_back(this);
227 }
228 std::vector<PackageManagerFancy*> PackageManagerFancy::instances;
229
230 PackageManagerFancy::~PackageManagerFancy()
231 {
232 instances.erase(find(instances.begin(), instances.end(), this));
233 signal(SIGWINCH, old_SIGWINCH);
234 }
235
236 void PackageManagerFancy::staticSIGWINCH(int signum)
237 {
238 std::vector<PackageManagerFancy *>::const_iterator I;
239 for(I = instances.begin(); I != instances.end(); ++I)
240 (*I)->HandleSIGWINCH(signum);
241 }
242
243 PackageManagerFancy::TermSize
244 PackageManagerFancy::GetTerminalSize()
245 {
246 struct winsize win;
247 PackageManagerFancy::TermSize s = { 0, 0 };
248
249 // FIXME: get from "child_pty" instead?
250 if(ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&win) != 0)
251 return s;
252
253 if(_config->FindB("Debug::InstallProgress::Fancy", false) == true)
254 std::cerr << "GetTerminalSize: " << win.ws_row << " x " << win.ws_col << std::endl;
255
256 s.rows = win.ws_row;
257 s.columns = win.ws_col;
258 return s;
259 }
260
261 void PackageManagerFancy::SetupTerminalScrollArea(int nr_rows)
262 {
263 if(_config->FindB("Debug::InstallProgress::Fancy", false) == true)
264 std::cerr << "SetupTerminalScrollArea: " << nr_rows << std::endl;
265
266 if (unlikely(nr_rows <= 1))
267 return;
268
269 // scroll down a bit to avoid visual glitch when the screen
270 // area shrinks by one row
271 std::cout << "\n";
272
273 // save cursor
274 std::cout << "\0337";
275
276 // set scroll region (this will place the cursor in the top left)
277 std::cout << "\033[0;" << std::to_string(nr_rows - 1) << "r";
278
279 // restore cursor but ensure its inside the scrolling area
280 std::cout << "\0338";
281 static const char *move_cursor_up = "\033[1A";
282 std::cout << move_cursor_up;
283
284 // ensure its flushed
285 std::flush(std::cout);
286
287 // setup tty size to ensure xterm/linux console are working properly too
288 // see bug #731738
289 struct winsize win;
290 if (ioctl(child_pty, TIOCGWINSZ, (char *)&win) != -1)
291 {
292 win.ws_row = nr_rows - 1;
293 ioctl(child_pty, TIOCSWINSZ, (char *)&win);
294 }
295 }
296
297 void PackageManagerFancy::HandleSIGWINCH(int)
298 {
299 int const nr_terminal_rows = GetTerminalSize().rows;
300 SetupTerminalScrollArea(nr_terminal_rows);
301 DrawStatusLine();
302 }
303
304 void PackageManagerFancy::Start(int a_child_pty)
305 {
306 child_pty = a_child_pty;
307 int const nr_terminal_rows = GetTerminalSize().rows;
308 SetupTerminalScrollArea(nr_terminal_rows);
309 }
310
311 void PackageManagerFancy::Stop()
312 {
313 int const nr_terminal_rows = GetTerminalSize().rows;
314 if (nr_terminal_rows > 0)
315 {
316 SetupTerminalScrollArea(nr_terminal_rows + 1);
317
318 // override the progress line (sledgehammer)
319 static const char* clear_screen_below_cursor = "\033[J";
320 std::cout << clear_screen_below_cursor;
321 std::flush(std::cout);
322 }
323 child_pty = -1;
324 }
325
326 std::string
327 PackageManagerFancy::GetTextProgressStr(float Percent, int OutputSize)
328 {
329 std::string output;
330 if (unlikely(OutputSize < 3))
331 return output;
332
333 int const BarSize = OutputSize - 2; // bar without the leading "[" and trailing "]"
334 int const BarDone = std::max(0, std::min(BarSize, static_cast<int>(std::floor(Percent * BarSize))));
335 output.append("[");
336 std::fill_n(std::fill_n(std::back_inserter(output), BarDone, '#'), BarSize - BarDone, '.');
337 output.append("]");
338 return output;
339 }
340
341 bool PackageManagerFancy::StatusChanged(std::string PackageName,
342 unsigned int StepsDone,
343 unsigned int TotalSteps,
344 std::string HumanReadableAction)
345 {
346 if (!PackageManager::StatusChanged(PackageName, StepsDone, TotalSteps,
347 HumanReadableAction))
348 return false;
349
350 return DrawStatusLine();
351 }
352 bool PackageManagerFancy::DrawStatusLine()
353 {
354 PackageManagerFancy::TermSize const size = GetTerminalSize();
355 if (unlikely(size.rows < 1 || size.columns < 1))
356 return false;
357
358 static std::string save_cursor = "\0337";
359 static std::string restore_cursor = "\0338";
360
361 // green
362 static std::string set_bg_color = DeQuoteString(
363 _config->Find("Dpkg::Progress-Fancy::Progress-fg", "%1b[42m"));
364 // black
365 static std::string set_fg_color = DeQuoteString(
366 _config->Find("Dpkg::Progress-Fancy::Progress-bg", "%1b[30m"));
367
368 static std::string restore_bg = "\033[49m";
369 static std::string restore_fg = "\033[39m";
370
371 std::cout << save_cursor
372 // move cursor position to last row
373 << "\033[" << std::to_string(size.rows) << ";0f"
374 << set_bg_color
375 << set_fg_color
376 << progress_str
377 << restore_bg
378 << restore_fg;
379 std::flush(std::cout);
380
381 // draw text progress bar
382 if (_config->FindB("Dpkg::Progress-Fancy::Progress-Bar", true))
383 {
384 int padding = 4;
385 float progressbar_size = size.columns - padding - progress_str.size();
386 float current_percent = percentage / 100.0;
387 std::cout << " "
388 << GetTextProgressStr(current_percent, progressbar_size)
389 << " ";
390 std::flush(std::cout);
391 }
392
393 // restore
394 std::cout << restore_cursor;
395 std::flush(std::cout);
396
397 last_reported_progress = percentage;
398
399 return true;
400 }
401
402 bool PackageManagerText::StatusChanged(std::string PackageName,
403 unsigned int StepsDone,
404 unsigned int TotalSteps,
405 std::string HumanReadableAction)
406 {
407 if (!PackageManager::StatusChanged(PackageName, StepsDone, TotalSteps, HumanReadableAction))
408 return false;
409
410 std::cout << progress_str << "\r\n";
411 std::flush(std::cout);
412
413 last_reported_progress = percentage;
414
415 return true;
416 }
417
418 PackageManagerText::PackageManagerText() : PackageManager(), d(NULL) {}
419 PackageManagerText::~PackageManagerText() {}
420
421
422
423
424 } // namespace progress
425 } // namespace apt