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 "global.h"
27 
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <errno.h>
32 
33 #include <unistd.h>
34 #include <sys/time.h>
35 
36 #include <yopt.h>
37 
38 
39 int pstate;
40 int read_only = 0;
41 long update_delay = 100;
42 int cachedir_tags = 0;
43 int extended_info = 0;
44 int follow_symlinks = 0;
45 int follow_firmlinks = 1;
46 int confirm_quit = 0;
47 
48 static int min_rows = 17, min_cols = 60;
49 static int ncurses_init = 0;
50 static int ncurses_tty = 0; /* Explicitly open /dev/tty instead of using stdio */
51 static long lastupdate = 999;
52 
53 
screen_draw(void)54 static void screen_draw(void) {
55   switch(pstate) {
56     case ST_CALC:   dir_draw();    break;
57     case ST_BROWSE: browse_draw(); break;
58     case ST_HELP:   help_draw();   break;
59     case ST_SHELL:  shell_draw();  break;
60     case ST_DEL:    delete_draw(); break;
61     case ST_QUIT:   quit_draw();   break;
62   }
63 }
64 
65 
66 /* wait:
67  *  -1: non-blocking, always draw screen
68  *   0: blocking wait for input and always draw screen
69  *   1: non-blocking, draw screen only if a configured delay has passed or after keypress
70  */
input_handle(int wait)71 int input_handle(int wait) {
72   int ch;
73   struct timeval tv;
74 
75   if(wait != 1)
76     screen_draw();
77   else {
78     gettimeofday(&tv, NULL);
79     tv.tv_usec = (1000*(tv.tv_sec % 1000) + (tv.tv_usec / 1000)) / update_delay;
80     if(lastupdate != tv.tv_usec) {
81       screen_draw();
82       lastupdate = tv.tv_usec;
83     }
84   }
85 
86   /* No actual input handling is done if ncurses hasn't been initialized yet. */
87   if(!ncurses_init)
88     return wait == 0 ? 1 : 0;
89 
90   nodelay(stdscr, wait?1:0);
91   errno = 0;
92   while((ch = getch()) != ERR) {
93     if(ch == KEY_RESIZE) {
94       if(ncresize(min_rows, min_cols))
95         min_rows = min_cols = 0;
96       /* ncresize() may change nodelay state, make sure to revert it. */
97       nodelay(stdscr, wait?1:0);
98       screen_draw();
99       continue;
100     }
101     switch(pstate) {
102       case ST_CALC:   return dir_key(ch);
103       case ST_BROWSE: return browse_key(ch);
104       case ST_HELP:   return help_key(ch);
105       case ST_DEL:    return delete_key(ch);
106       case ST_QUIT:   return quit_key(ch);
107     }
108     screen_draw();
109   }
110   if(errno == EPIPE || errno == EBADF || errno == EIO)
111       return 1;
112   return 0;
113 }
114 
115 
116 /* parse command line */
argv_parse(int argc,char ** argv)117 static void argv_parse(int argc, char **argv) {
118   yopt_t yopt;
119   int v;
120   char *val;
121   char *export = NULL;
122   char *import = NULL;
123   char *dir = NULL;
124 
125   static yopt_opt_t opts[] = {
126     { 'h', 0, "-h,-?,--help" },
127     { 'q', 0, "-q" },
128     { 'v', 0, "-v,-V,--version" },
129     { 'x', 0, "-x" },
130     { 'e', 0, "-e" },
131     { 'r', 0, "-r" },
132     { 'o', 1, "-o" },
133     { 'f', 1, "-f" },
134     { '0', 0, "-0" },
135     { '1', 0, "-1" },
136     { '2', 0, "-2" },
137     {  1,  1, "--exclude" },
138     { 'X', 1, "-X,--exclude-from" },
139     { 'L', 0, "-L,--follow-symlinks" },
140     { 'C', 0, "--exclude-caches" },
141     {  2,  0, "--exclude-kernfs" },
142     {  3,  0, "--follow-firmlinks" }, /* undocumented, this behavior is the current default */
143     {  4,  0, "--exclude-firmlinks" },
144     { 's', 0, "--si" },
145     { 'Q', 0, "--confirm-quit" },
146     { 'c', 1, "--color" },
147     {0,0,NULL}
148   };
149 
150   dir_ui = -1;
151   si = 0;
152 
153   yopt_init(&yopt, argc, argv, opts);
154   while((v = yopt_next(&yopt, &val)) != -1) {
155     switch(v) {
156     case  0 : dir = val; break;
157     case 'h':
158       printf("ncdu <options> <directory>\n\n");
159       printf("  -h,--help                  This help message\n");
160       printf("  -q                         Quiet mode, refresh interval 2 seconds\n");
161       printf("  -v,-V,--version            Print version\n");
162       printf("  -x                         Same filesystem\n");
163       printf("  -e                         Enable extended information\n");
164       printf("  -r                         Read only\n");
165       printf("  -o FILE                    Export scanned directory to FILE\n");
166       printf("  -f FILE                    Import scanned directory from FILE\n");
167       printf("  -0,-1,-2                   UI to use when scanning (0=none,2=full ncurses)\n");
168       printf("  --si                       Use base 10 (SI) prefixes instead of base 2\n");
169       printf("  --exclude PATTERN          Exclude files that match PATTERN\n");
170       printf("  -X, --exclude-from FILE    Exclude files that match any pattern in FILE\n");
171       printf("  -L, --follow-symlinks      Follow symbolic links (excluding directories)\n");
172       printf("  --exclude-caches           Exclude directories containing CACHEDIR.TAG\n");
173 #if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS
174       printf("  --exclude-kernfs           Exclude Linux pseudo filesystems (procfs,sysfs,cgroup,...)\n");
175 #endif
176 #if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH
177       printf("  --exclude-firmlinks        Exclude firmlinks on macOS\n");
178 #endif
179       printf("  --confirm-quit             Confirm quitting ncdu\n");
180       printf("  --color SCHEME             Set color scheme (off/dark)\n");
181       exit(0);
182     case 'q': update_delay = 2000; break;
183     case 'v':
184       printf("ncdu %s\n", PACKAGE_VERSION);
185       exit(0);
186     case 'x': dir_scan_smfs = 1; break;
187     case 'e': extended_info = 1; break;
188     case 'r': read_only++; break;
189     case 's': si = 1; break;
190     case 'o': export = val; break;
191     case 'f': import = val; break;
192     case '0': dir_ui = 0; break;
193     case '1': dir_ui = 1; break;
194     case '2': dir_ui = 2; break;
195     case 'Q': confirm_quit = 1; break;
196     case  1 : exclude_add(val); break; /* --exclude */
197     case 'X':
198       if(exclude_addfile(val)) {
199         fprintf(stderr, "Can't open %s: %s\n", val, strerror(errno));
200         exit(1);
201       }
202       break;
203     case 'L': follow_symlinks = 1; break;
204     case 'C':
205       cachedir_tags = 1;
206       break;
207 
208     case  2 : /* --exclude-kernfs */
209 #if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS
210       exclude_kernfs = 1; break;
211 #else
212       fprintf(stderr, "This feature is not supported on your platform\n");
213       exit(1);
214 #endif
215     case  3 : /* --follow-firmlinks */
216 #if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH
217       follow_firmlinks = 1; break;
218 #else
219       fprintf(stderr, "This feature is not supported on your platform\n");
220       exit(1);
221 #endif
222     case  4 : /* --exclude-firmlinks */
223 #if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH
224       follow_firmlinks = 0; break;
225 #else
226       fprintf(stderr, "This feature is not supported on your platform\n");
227       exit(1);
228 #endif
229     case 'c':
230       if(strcmp(val, "off") == 0)  { uic_theme = 0; }
231       else if(strcmp(val, "dark") == 0) { uic_theme = 1; }
232       else {
233         fprintf(stderr, "Unknown --color option: %s\n", val);
234         exit(1);
235       }
236       break;
237     case -2:
238       fprintf(stderr, "ncdu: %s.\n", val);
239       exit(1);
240     }
241   }
242 
243   if(export) {
244     if(dir_export_init(export)) {
245       fprintf(stderr, "Can't open %s: %s\n", export, strerror(errno));
246       exit(1);
247     }
248     if(strcmp(export, "-") == 0)
249       ncurses_tty = 1;
250   } else
251     dir_mem_init(NULL);
252 
253   if(import) {
254     if(dir_import_init(import)) {
255       fprintf(stderr, "Can't open %s: %s\n", import, strerror(errno));
256       exit(1);
257     }
258     if(strcmp(import, "-") == 0)
259       ncurses_tty = 1;
260   } else
261     dir_scan_init(dir ? dir : ".");
262 
263   /* Use the single-line scan feedback by default when exporting to file, no
264    * feedback when exporting to stdout. */
265   if(dir_ui == -1)
266     dir_ui = export && strcmp(export, "-") == 0 ? 0 : export ? 1 : 2;
267 }
268 
269 
270 /* Initializes ncurses only when not done yet. */
init_nc(void)271 static void init_nc(void) {
272   int ok = 0;
273   FILE *tty;
274   SCREEN *term;
275 
276   if(ncurses_init)
277     return;
278   ncurses_init = 1;
279 
280   if(ncurses_tty) {
281     tty = fopen("/dev/tty", "r+");
282     if(!tty) {
283       fprintf(stderr, "Error opening /dev/tty: %s\n", strerror(errno));
284       exit(1);
285     }
286     term = newterm(NULL, tty, tty);
287     if(term)
288       set_term(term);
289     ok = !!term;
290   } else {
291     /* Make sure the user doesn't accidentally pipe in data to ncdu's standard
292      * input without using "-f -". An annoying input sequence could result in
293      * the deletion of your files, which we want to prevent at all costs. */
294     if(!isatty(0)) {
295       fprintf(stderr, "Standard input is not a TTY. Did you mean to import a file using '-f -'?\n");
296       exit(1);
297     }
298     ok = !!initscr();
299   }
300 
301   if(!ok) {
302     fprintf(stderr, "Error while initializing ncurses.\n");
303     exit(1);
304   }
305 
306   uic_init();
307   cbreak();
308   noecho();
309   curs_set(0);
310   keypad(stdscr, TRUE);
311   if(ncresize(min_rows, min_cols))
312     min_rows = min_cols = 0;
313 }
314 
315 
close_nc()316 void close_nc() {
317   if(ncurses_init) {
318     erase();
319     refresh();
320     endwin();
321   }
322 }
323 
324 
325 /* main program */
main(int argc,char ** argv)326 int main(int argc, char **argv) {
327   read_locale();
328   argv_parse(argc, argv);
329 
330   if(dir_ui == 2)
331     init_nc();
332 
333   while(1) {
334     /* We may need to initialize/clean up the screen when switching from the
335      * (sometimes non-ncurses) CALC state to something else. */
336     if(pstate != ST_CALC) {
337       if(dir_ui == 1)
338         fputc('\n', stderr);
339       init_nc();
340     }
341 
342     if(pstate == ST_CALC) {
343       if(dir_process()) {
344         if(dir_ui == 1)
345           fputc('\n', stderr);
346         break;
347       }
348     } else if(pstate == ST_DEL)
349       delete_process();
350     else if(input_handle(0))
351       break;
352   }
353 
354   close_nc();
355   exclude_clear();
356 
357   return 0;
358 }
359 
360