1 /* ncdu - NCurses Disk Usage
2
3 Copyright (c) 2007-2021 Yoran Heling
4
5 Permission is hereby granted, free of charge, to any person obtaining
6 a copy of this software and associated documentation files (the
7 "Software"), to deal in the Software without restriction, including
8 without limitation the rights to use, copy, modify, merge, publish,
9 distribute, sublicense, and/or sell copies of the Software, and to
10 permit persons to whom the Software is furnished to do so, subject to
11 the following conditions:
12
13 The above copyright notice and this permission notice shall be included
14 in all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24 */
25
26 #include "util.h"
27
28 #include <string.h>
29 #include <stdlib.h>
30 #include <ncurses.h>
31 #include <stdarg.h>
32 #include <unistd.h>
33 #ifdef HAVE_LOCALE_H
34 #include <locale.h>
35 #endif
36
37 int uic_theme;
38 int winrows, wincols;
39 int subwinr, subwinc;
40 int si;
41 static char thou_sep;
42
43
cropstr(const char * from,int s)44 char *cropstr(const char *from, int s) {
45 static char dat[4096];
46 int i, j, o = strlen(from);
47 if(o < s) {
48 strcpy(dat, from);
49 return dat;
50 }
51 j=s/2-3;
52 for(i=0; i<j; i++)
53 dat[i] = from[i];
54 dat[i] = '.';
55 dat[++i] = '.';
56 dat[++i] = '.';
57 j=o-s;
58 while(++i<s)
59 dat[i] = from[j+i];
60 dat[s] = '\0';
61 return dat;
62 }
63
64
formatsize(int64_t from,const char ** unit)65 float formatsize(int64_t from, const char **unit) {
66 float r = from;
67 if (si) {
68 if(r < 1000.0f) { *unit = " B"; }
69 else if(r < 1e6f) { *unit = "KB"; r/=1e3f; }
70 else if(r < 1e9f) { *unit = "MB"; r/=1e6f; }
71 else if(r < 1e12f){ *unit = "GB"; r/=1e9f; }
72 else if(r < 1e15f){ *unit = "TB"; r/=1e12f; }
73 else if(r < 1e18f){ *unit = "PB"; r/=1e15f; }
74 else { *unit = "EB"; r/=1e18f; }
75 }
76 else {
77 if(r < 1000.0f) { *unit = " B"; }
78 else if(r < 1023e3f) { *unit = "KiB"; r/=1024.0f; }
79 else if(r < 1023e6f) { *unit = "MiB"; r/=1048576.0f; }
80 else if(r < 1023e9f) { *unit = "GiB"; r/=1073741824.0f; }
81 else if(r < 1023e12f){ *unit = "TiB"; r/=1099511627776.0f; }
82 else if(r < 1023e15f){ *unit = "PiB"; r/=1125899906842624.0f; }
83 else { *unit = "EiB"; r/=1152921504606846976.0f; }
84 }
85 return r;
86 }
87
88
printsize(enum ui_coltype t,int64_t from)89 void printsize(enum ui_coltype t, int64_t from) {
90 const char *unit;
91 float r = formatsize(from, &unit);
92 uic_set(t == UIC_HD ? UIC_NUM_HD : t == UIC_SEL ? UIC_NUM_SEL : UIC_NUM);
93 printw("%5.1f", r);
94 addchc(t, ' ');
95 addstrc(t, unit);
96 }
97
98
fullsize(int64_t from)99 char *fullsize(int64_t from) {
100 static char dat[26]; /* max: 9.223.372.036.854.775.807 (= 2^63-1) */
101 char tmp[26];
102 int64_t n = from;
103 int i, j;
104
105 /* the K&R method - more portable than sprintf with %lld */
106 i = 0;
107 do {
108 tmp[i++] = n % 10 + '0';
109 } while((n /= 10) > 0);
110 tmp[i] = '\0';
111
112 /* reverse and add thousand separators */
113 j = 0;
114 while(i--) {
115 dat[j++] = tmp[i];
116 if(i != 0 && i%3 == 0)
117 dat[j++] = thou_sep;
118 }
119 dat[j] = '\0';
120
121 return dat;
122 }
123
124
fmtmode(unsigned short mode)125 char *fmtmode(unsigned short mode) {
126 static char buf[11];
127 unsigned short ft = mode & S_IFMT;
128 buf[0] = ft == S_IFDIR ? 'd'
129 : ft == S_IFREG ? '-'
130 : ft == S_IFLNK ? 'l'
131 : ft == S_IFIFO ? 'p'
132 : ft == S_IFSOCK ? 's'
133 : ft == S_IFCHR ? 'c'
134 : ft == S_IFBLK ? 'b' : '?';
135 buf[1] = mode & 0400 ? 'r' : '-';
136 buf[2] = mode & 0200 ? 'w' : '-';
137 buf[3] = mode &04000 ? 's' : mode & 0100 ? 'x' : '-';
138 buf[4] = mode & 0040 ? 'r' : '-';
139 buf[5] = mode & 0020 ? 'w' : '-';
140 buf[6] = mode &02000 ? 's' : mode & 0010 ? 'x' : '-';
141 buf[7] = mode & 0004 ? 'r' : '-';
142 buf[8] = mode & 0002 ? 'w' : '-';
143 buf[9] = mode &01000 ? (S_ISDIR(mode) ? 't' : 'T') : mode & 0001 ? 'x' : '-';
144 buf[10] = 0;
145 return buf;
146 }
147
148
read_locale()149 void read_locale() {
150 thou_sep = '.';
151 #ifdef HAVE_LOCALE_H
152 setlocale(LC_ALL, "");
153 char *locale_thou_sep = localeconv()->thousands_sep;
154 if(locale_thou_sep && 1 == strlen(locale_thou_sep))
155 thou_sep = locale_thou_sep[0];
156 #endif
157 }
158
159
ncresize(int minrows,int mincols)160 int ncresize(int minrows, int mincols) {
161 int ch;
162
163 getmaxyx(stdscr, winrows, wincols);
164 while((minrows && winrows < minrows) || (mincols && wincols < mincols)) {
165 erase();
166 mvaddstr(0, 0, "Warning: terminal too small,");
167 mvaddstr(1, 1, "please either resize your terminal,");
168 mvaddstr(2, 1, "press i to ignore, or press q to quit.");
169 refresh();
170 nodelay(stdscr, 0);
171 ch = getch();
172 getmaxyx(stdscr, winrows, wincols);
173 if(ch == 'q') {
174 erase();
175 refresh();
176 endwin();
177 exit(0);
178 }
179 if(ch == 'i')
180 return 1;
181 }
182 erase();
183 return 0;
184 }
185
186
nccreate(int height,int width,const char * title)187 void nccreate(int height, int width, const char *title) {
188 int i;
189
190 uic_set(UIC_DEFAULT);
191 subwinr = winrows/2-height/2;
192 subwinc = wincols/2-width/2;
193
194 /* clear window */
195 for(i=0; i<height; i++)
196 mvhline(subwinr+i, subwinc, ' ', width);
197
198 /* box() only works around curses windows, so create our own */
199 move(subwinr, subwinc);
200 addch(ACS_ULCORNER);
201 for(i=0; i<width-2; i++)
202 addch(ACS_HLINE);
203 addch(ACS_URCORNER);
204
205 move(subwinr+height-1, subwinc);
206 addch(ACS_LLCORNER);
207 for(i=0; i<width-2; i++)
208 addch(ACS_HLINE);
209 addch(ACS_LRCORNER);
210
211 mvvline(subwinr+1, subwinc, ACS_VLINE, height-2);
212 mvvline(subwinr+1, subwinc+width-1, ACS_VLINE, height-2);
213
214 /* title */
215 uic_set(UIC_BOX_TITLE);
216 mvaddstr(subwinr, subwinc+4, title);
217 uic_set(UIC_DEFAULT);
218 }
219
220
ncprint(int r,int c,const char * fmt,...)221 void ncprint(int r, int c, const char *fmt, ...) {
222 va_list arg;
223 va_start(arg, fmt);
224 move(subwinr+r, subwinc+c);
225 vw_printw(stdscr, fmt, arg);
226 va_end(arg);
227 }
228
229
nctab(int c,int sel,int num,const char * str)230 void nctab(int c, int sel, int num, const char *str) {
231 uic_set(sel ? UIC_KEY_HD : UIC_KEY);
232 ncprint(0, c, "%d", num);
233 uic_set(sel ? UIC_HD : UIC_DEFAULT);
234 addch(':');
235 addstr(str);
236 uic_set(UIC_DEFAULT);
237 }
238
239
240 static int colors[] = {
241 #define C(name, ...) 0,
242 UI_COLORS
243 #undef C
244 0
245 };
246 static int lastcolor = 0;
247
248
249 static const struct {
250 short fg, bg;
251 int attr;
252 } color_defs[] = {
253 #define C(name, off_fg, off_bg, off_a, dark_fg, dark_bg, dark_a) \
254 {off_fg, off_bg, off_a}, \
255 {dark_fg, dark_bg, dark_a},
256 UI_COLORS
257 #undef C
258 {0,0,0}
259 };
260
uic_init()261 void uic_init() {
262 size_t i, j;
263
264 start_color();
265 use_default_colors();
266 for(i=0; i<sizeof(colors)/sizeof(*colors)-1; i++) {
267 j = i*2 + uic_theme;
268 init_pair(i+1, color_defs[j].fg, color_defs[j].bg);
269 colors[i] = color_defs[j].attr | COLOR_PAIR(i+1);
270 }
271 }
272
uic_set(enum ui_coltype c)273 void uic_set(enum ui_coltype c) {
274 attroff(lastcolor);
275 lastcolor = colors[(int)c];
276 attron(lastcolor);
277 }
278
279
280
281 /* removes item from the hlnk circular linked list and size counts of the parents */
freedir_hlnk(struct dir * d)282 static void freedir_hlnk(struct dir *d) {
283 struct dir *t, *par, *pt;
284 int i;
285
286 if(!(d->flags & FF_HLNKC))
287 return;
288
289 /* remove size from parents.
290 * This works the same as with adding: only the parents in which THIS is the
291 * only occurrence of the hard link will be modified, if the same file still
292 * exists within the parent it shouldn't get removed from the count.
293 * XXX: Same note as for dir_mem.c / hlink_check():
294 * this is probably not the most efficient algorithm */
295 for(i=1,par=d->parent; i&∥ par=par->parent) {
296 if(d->hlnk)
297 for(t=d->hlnk; i&&t!=d; t=t->hlnk)
298 for(pt=t->parent; i&&pt; pt=pt->parent)
299 if(pt==par)
300 i=0;
301 if(i) {
302 par->size = adds64(par->size, -d->size);
303 par->asize = adds64(par->size, -d->asize);
304 }
305 }
306
307 /* remove from hlnk */
308 if(d->hlnk) {
309 for(t=d->hlnk; t->hlnk!=d; t=t->hlnk)
310 ;
311 t->hlnk = d->hlnk;
312 }
313 }
314
315
freedir_rec(struct dir * dr)316 static void freedir_rec(struct dir *dr) {
317 struct dir *tmp, *tmp2;
318 tmp2 = dr;
319 while((tmp = tmp2) != NULL) {
320 freedir_hlnk(tmp);
321 /* remove item */
322 if(tmp->sub) freedir_rec(tmp->sub);
323 tmp2 = tmp->next;
324 free(tmp);
325 }
326 }
327
328
freedir(struct dir * dr)329 void freedir(struct dir *dr) {
330 if(!dr)
331 return;
332
333 /* free dr->sub recursively */
334 if(dr->sub)
335 freedir_rec(dr->sub);
336
337 /* update references */
338 if(dr->parent && dr->parent->sub == dr)
339 dr->parent->sub = dr->next;
340 if(dr->prev)
341 dr->prev->next = dr->next;
342 if(dr->next)
343 dr->next->prev = dr->prev;
344
345 freedir_hlnk(dr);
346
347 /* update sizes of parent directories if this isn't a hard link.
348 * If this is a hard link, freedir_hlnk() would have done so already
349 *
350 * mtime is 0 here because recalculating the maximum at every parent
351 * dir is expensive, but might be good feature to add later if desired */
352 addparentstats(dr->parent, dr->flags & FF_HLNKC ? 0 : -dr->size, dr->flags & FF_HLNKC ? 0 : -dr->asize, 0, -(dr->items+1));
353
354 free(dr);
355 }
356
357
getpath(struct dir * cur)358 const char *getpath(struct dir *cur) {
359 static char *dat;
360 static int datl = 0;
361 struct dir *d, **list;
362 int c, i;
363
364 if(!cur->name[0])
365 return "/";
366
367 c = i = 1;
368 for(d=cur; d!=NULL; d=d->parent) {
369 i += strlen(d->name)+1;
370 c++;
371 }
372
373 if(datl == 0) {
374 datl = i;
375 dat = xmalloc(i);
376 } else if(datl < i) {
377 datl = i;
378 dat = xrealloc(dat, i);
379 }
380 list = xmalloc(c*sizeof(struct dir *));
381
382 c = 0;
383 for(d=cur; d!=NULL; d=d->parent)
384 list[c++] = d;
385
386 dat[0] = '\0';
387 while(c--) {
388 if(list[c]->parent)
389 strcat(dat, "/");
390 strcat(dat, list[c]->name);
391 }
392 free(list);
393 return dat;
394 }
395
396
getroot(struct dir * d)397 struct dir *getroot(struct dir *d) {
398 while(d && d->parent)
399 d = d->parent;
400 return d;
401 }
402
403
addparentstats(struct dir * d,int64_t size,int64_t asize,uint64_t mtime,int items)404 void addparentstats(struct dir *d, int64_t size, int64_t asize, uint64_t mtime, int items) {
405 struct dir_ext *e;
406 while(d) {
407 d->size = adds64(d->size, size);
408 d->asize = adds64(d->asize, asize);
409 d->items += items;
410 if (d->flags & FF_EXT) {
411 e = dir_ext_ptr(d);
412 e->mtime = (e->mtime > mtime) ? e->mtime : mtime;
413 }
414 d = d->parent;
415 }
416 }
417
418
419 /* Apparently we can just resume drawing after endwin() and ncurses will pick
420 * up where it left. Probably not very portable... */
421 #define oom_msg "\nOut of memory, press enter to try again or Ctrl-C to give up.\n"
422 #define wrap_oom(f) \
423 void *ptr;\
424 char buf[128];\
425 while((ptr = f) == NULL) {\
426 close_nc();\
427 write(2, oom_msg, sizeof(oom_msg));\
428 read(0, buf, sizeof(buf));\
429 }\
430 return ptr;
431
xmalloc(size_t size)432 void *xmalloc(size_t size) { wrap_oom(malloc(size)) }
xcalloc(size_t n,size_t size)433 void *xcalloc(size_t n, size_t size) { wrap_oom(calloc(n, size)) }
xrealloc(void * mem,size_t size)434 void *xrealloc(void *mem, size_t size) { wrap_oom(realloc(mem, size)) }
435