1/*
2 * Create a squashfs filesystem.  This is a highly compressed read only
3 * filesystem.
4 *
5 * Copyright (c) 2012, 2013, 2014
6 * Phillip Lougher <phillip@squashfs.org.uk>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2,
11 * or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 *
22 * progressbar.c
23 */
24
25#include <pthread.h>
26#include <sys/ioctl.h>
27#include <unistd.h>
28#include <signal.h>
29#include <sys/time.h>
30#include <stdio.h>
31#include <math.h>
32#include <stdarg.h>
33#include <errno.h>
34#include <stdlib.h>
35
36#include "error.h"
37
38#define FALSE 0
39#define TRUE 1
40
41/* flag whether progressbar display is enabled or not */
42int display_progress_bar = FALSE;
43
44/* flag whether the progress bar is temporarily disbled */
45int temp_disabled = FALSE;
46
47int rotate = 0;
48int cur_uncompressed = 0, estimated_uncompressed = 0;
49int columns;
50
51pthread_t progress_thread;
52pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
53
54
55static void sigwinch_handler()
56{
57	struct winsize winsize;
58
59	if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
60		if(isatty(STDOUT_FILENO))
61			ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
62				"columns\n");
63		columns = 80;
64	} else
65		columns = winsize.ws_col;
66}
67
68
69static void sigalrm_handler()
70{
71	rotate = (rotate + 1) % 4;
72}
73
74
75void inc_progress_bar()
76{
77	cur_uncompressed ++;
78}
79
80
81void dec_progress_bar(int count)
82{
83	cur_uncompressed -= count;
84}
85
86
87void progress_bar_size(int count)
88{
89	estimated_uncompressed += count;
90}
91
92
93static void progress_bar(long long current, long long max, int columns)
94{
95	char rotate_list[] = { '|', '/', '-', '\\' };
96	int max_digits, used, hashes, spaces;
97	static int tty = -1;
98
99	if(max == 0)
100		return;
101
102	max_digits = floor(log10(max)) + 1;
103	used = max_digits * 2 + 11;
104	hashes = (current * (columns - used)) / max;
105	spaces = columns - used - hashes;
106
107	if((current > max) || (columns - used < 0))
108		return;
109
110	if(tty == -1)
111		tty = isatty(STDOUT_FILENO);
112	if(!tty) {
113		static long long previous = -1;
114
115		/* Updating much more frequently than this results in huge
116		 * log files. */
117		if((current % 100) != 0 && current != max)
118			return;
119		/* Don't update just to rotate the spinner. */
120		if(current == previous)
121			return;
122		previous = current;
123	}
124
125	printf("\r[");
126
127	while (hashes --)
128		putchar('=');
129
130	putchar(rotate_list[rotate]);
131
132	while(spaces --)
133		putchar(' ');
134
135	printf("] %*lld/%*lld", max_digits, current, max_digits, max);
136	printf(" %3lld%%", current * 100 / max);
137	fflush(stdout);
138}
139
140
141void enable_progress_bar()
142{
143	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
144	pthread_mutex_lock(&progress_mutex);
145	if(display_progress_bar)
146		progress_bar(cur_uncompressed, estimated_uncompressed, columns);
147	temp_disabled = FALSE;
148	pthread_cleanup_pop(1);
149}
150
151
152void disable_progress_bar()
153{
154	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
155	pthread_mutex_lock(&progress_mutex);
156	if(display_progress_bar)
157		printf("\n");
158	temp_disabled = TRUE;
159	pthread_cleanup_pop(1);
160}
161
162
163void set_progressbar_state(int state)
164{
165	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
166	pthread_mutex_lock(&progress_mutex);
167	if(display_progress_bar != state) {
168		if(display_progress_bar && !temp_disabled) {
169			progress_bar(cur_uncompressed, estimated_uncompressed,
170				columns);
171			printf("\n");
172		}
173		display_progress_bar = state;
174	}
175	pthread_cleanup_pop(1);
176}
177
178
179void *progress_thrd(void *arg)
180{
181	struct timespec requested_time, remaining;
182	struct itimerval itimerval;
183	struct winsize winsize;
184
185	if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
186		if(isatty(STDOUT_FILENO))
187			ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
188				"columns\n");
189		columns = 80;
190	} else
191		columns = winsize.ws_col;
192	signal(SIGWINCH, sigwinch_handler);
193	signal(SIGALRM, sigalrm_handler);
194
195	itimerval.it_value.tv_sec = 0;
196	itimerval.it_value.tv_usec = 250000;
197	itimerval.it_interval.tv_sec = 0;
198	itimerval.it_interval.tv_usec = 250000;
199	setitimer(ITIMER_REAL, &itimerval, NULL);
200
201	requested_time.tv_sec = 0;
202	requested_time.tv_nsec = 250000000;
203
204	while(1) {
205		int res = nanosleep(&requested_time, &remaining);
206
207		if(res == -1 && errno != EINTR)
208			BAD_ERROR("nanosleep failed in progress thread\n");
209
210		pthread_mutex_lock(&progress_mutex);
211		if(display_progress_bar && !temp_disabled)
212			progress_bar(cur_uncompressed, estimated_uncompressed,
213				columns);
214		pthread_mutex_unlock(&progress_mutex);
215	}
216}
217
218
219void init_progress_bar()
220{
221	pthread_create(&progress_thread, NULL, progress_thrd, NULL);
222}
223
224
225void progressbar_error(char *fmt, ...)
226{
227	va_list ap;
228
229	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
230	pthread_mutex_lock(&progress_mutex);
231
232	if(display_progress_bar && !temp_disabled)
233		fprintf(stderr, "\n");
234
235	va_start(ap, fmt);
236	vfprintf(stderr, fmt, ap);
237	va_end(ap);
238
239	pthread_cleanup_pop(1);
240}
241
242
243void progressbar_info(char *fmt, ...)
244{
245	va_list ap;
246
247	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
248	pthread_mutex_lock(&progress_mutex);
249
250	if(display_progress_bar && !temp_disabled)
251		printf("\n");
252
253	va_start(ap, fmt);
254	vprintf(fmt, ap);
255	va_end(ap);
256
257	pthread_cleanup_pop(1);
258}
259
260