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