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=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