1 /*
2 * ProFTPD - ftptop: a utility for monitoring proftpd sessions
3 * Copyright (c) 2000-2002 TJ Saunders <tj@castaglia.org>
4 * Copyright (c) 2003-2020 The ProFTPD Project team
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19 *
20 * As a special exemption, TJ Saunders and other respective copyright holders
21 * give permission to link this program with OpenSSL, and distribute the
22 * resulting executable, without including the source code for OpenSSL in the
23 * source distribution.
24 */
25
26 /* Shows who is online via proftpd, in a manner similar to top. Uses the
27 * scoreboard files.
28 */
29
30 #define FTPTOP_VERSION "ftptop/1.0"
31
32 #include "utils.h"
33
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <time.h>
37
38 #if defined(PR_USE_NLS) && defined(HAVE_LOCALE_H)
39 # include <locale.h>
40 #endif
41
42 static const char *program = "ftptop";
43
44 /* ncurses is preferred...*/
45
46 #if defined(HAVE_NCURSES_H) && \
47 ((defined(HAVE_LIBNCURSES) && defined(PR_USE_NCURSES) || \
48 (defined(HAVE_LIBNCURSESW) && defined(PR_USE_NCURSESW))))
49 # define HAVE_NCURSES 1
50 # include <ncurses.h>
51 #elif defined(HAVE_CURSES_H) && defined(HAVE_LIBCURSES) && \
52 defined(PR_USE_CURSES)
53 # define HAVE_CURSES 1
54 /* Sigh...portability. It seems that Solaris' curses.h (at least for 2.8)
55 * steps on wide-character macros, generating compiler warnings. This, then
56 * is just a hack to silence the compiler.
57 */
58 # ifdef SOLARIS2
59 # define __lint
60 # endif
61 # include <curses.h>
62 #endif
63
64 #if defined(HAVE_NCURSES) || defined(HAVE_CURSES)
65
66 /* Display options */
67
68 /* These are for displaying "PID S USER CLIENT SERVER TIME COMMAND" */
69 #define FTPTOP_REG_HEADER_FMT "%-5s %s %-8s %-20s %-15s %-4s %-*s\n"
70 #define FTPTOP_REG_DISPLAY_FMT "%-5u %s %-*.*s %-*.*s %-15s %-6.6s %4s %-*.*s\n"
71
72 /* These are for displaying tranfer data: "PID S USER CLIENT KB/s %DONE" */
73 #define FTPTOP_XFER_HEADER_FMT "%-5s %s %-8s %-44s %-10s %-*s\n"
74 #define FTPTOP_XFER_DISPLAY_FMT "%-5u %s %-*.*s %-*.*s %-10.2f %-*.*s\n"
75
76 #define FTPTOP_REG_ARG_MIN_SIZE 20
77 #define FTPTOP_XFER_DONE_MIN_SIZE 6
78 #define FTPTOP_REG_ARG_SIZE \
79 (COLS - (80 - FTPTOP_REG_ARG_MIN_SIZE) < FTPTOP_REG_ARG_MIN_SIZE ? \
80 FTPTOP_REG_ARG_MIN_SIZE : COLS - (80 - FTPTOP_REG_ARG_MIN_SIZE))
81 #define FTPTOP_XFER_DONE_SIZE \
82 (COLS - (80 - FTPTOP_XFER_DONE_MIN_SIZE) < FTPTOP_XFER_DONE_MIN_SIZE ? \
83 FTPTOP_XFER_DONE_MIN_SIZE : COLS - (80 - FTPTOP_XFER_DONE_MIN_SIZE))
84
85 #define FTPTOP_SHOW_DOWNLOAD 0x0001
86 #define FTPTOP_SHOW_UPLOAD 0x0002
87 #define FTPTOP_SHOW_IDLE 0x0004
88 #define FTPTOP_SHOW_AUTH 0x0008
89 #define FTPTOP_SHOW_REG \
90 (FTPTOP_SHOW_DOWNLOAD|FTPTOP_SHOW_UPLOAD|FTPTOP_SHOW_IDLE)
91 #define FTPTOP_SHOW_RATES 0x0010
92
93 static int delay = 2;
94 static unsigned int display_mode = FTPTOP_SHOW_REG;
95 static int batch_mode = FALSE;
96 static unsigned int max_iterations = 0;
97
98 static char *config_filename = PR_CONFIG_FILE_PATH;
99
100 /* Scoreboard variables */
101 static time_t ftp_uptime = 0;
102 static unsigned int ftp_nsessions = 0;
103 static unsigned int ftp_nuploads = 0;
104 static unsigned int ftp_ndownloads = 0;
105 static unsigned int ftp_nidles = 0;
106
107 static char *server_name = NULL;
108 static char **ftp_sessions = NULL;
109 static unsigned int chunklen = 3;
110
111 /* necessary prototypes */
112 static void scoreboard_close(void);
113 static int scoreboard_open(void);
114
115 static void show_version(void);
116 static const char *show_ftpd_uptime(void);
117 static void usage(void);
118
clear_counters(void)119 static void clear_counters(void) {
120
121 if (ftp_sessions != NULL &&
122 ftp_nsessions > 0) {
123 register unsigned int i = 0;
124
125 for (i = 0; i < ftp_nsessions; i++) {
126 free(ftp_sessions[i]);
127 }
128 free(ftp_sessions);
129 ftp_sessions = NULL;
130 }
131
132 /* Reset the session counters. */
133 ftp_nsessions = 0;
134 ftp_nuploads = 0;
135 ftp_ndownloads = 0;
136 ftp_nidles = 0;
137 }
138
finish(int signo)139 static void finish(int signo) {
140 endwin();
141 exit(0);
142 }
143
calc_percent_done(off_t size,off_t done)144 static char *calc_percent_done(off_t size, off_t done) {
145 static char sbuf[32];
146
147 memset(sbuf, '\0', sizeof(sbuf));
148
149 if (done == 0) {
150 util_sstrncpy(sbuf, "0", sizeof(sbuf));
151
152 } else if (size == 0) {
153 util_sstrncpy(sbuf, "Inf", sizeof(sbuf));
154
155 } else if (done >= size) {
156 util_sstrncpy(sbuf, "100", sizeof(sbuf));
157
158 } else {
159 snprintf(sbuf, sizeof(sbuf), "%.0f",
160 ((double) done / (double) size) * 100.0);
161 sbuf[sizeof(sbuf)-1] = '\0';
162 }
163
164 return sbuf;
165 }
166
167 /* Given a NUL-terminated string -- possibly UTF8-encoded -- and a maximum
168 * CHARACTER length, return the number of bytes in the string which can fit in
169 * that max length without truncating a character. This is needed since UTF8
170 * characters are variable-width.
171 */
str_getscreenlen(const char * str,size_t max_chars)172 static int str_getscreenlen(const char *str, size_t max_chars) {
173 #ifdef PR_USE_NLS
174 register unsigned int i = 0;
175 int nbytes = 0, nchars = 0;
176
177 while (str[i] > 0 &&
178 i < max_chars) {
179 ascii:
180 i++;
181 nbytes++;
182 nchars++;
183 }
184
185 while (str[i] &&
186 (size_t) nchars < max_chars) {
187 size_t len;
188
189 if (str[i] > 0) {
190 goto ascii;
191 }
192
193 len = 0;
194
195 switch (str[i] & 0xF0) {
196 case 0xE0:
197 len = 3;
198 break;
199
200 case 0xF0:
201 len = 4;
202 break;
203
204 default:
205 len = 2;
206 break;
207 }
208
209 /* Increment the index with the given length, but increment the
210 * character count only one.
211 */
212
213 i += len;
214 nbytes += len;
215 nchars++;
216 }
217
218 return nbytes;
219 #else
220 /* No UTF8 support in this proftpd build; just return the max characters. */
221 return (int) max_chars;
222 #endif /* !PR_USE_NLS */
223 }
224
225 /* Borrowed from ftpwho.c */
show_time(time_t * i)226 static const char *show_time(time_t *i) {
227 time_t now = time(NULL);
228 unsigned long l;
229 static char buf[32];
230
231 if (i == NULL ||
232 *i == 0) {
233 return "-";
234 }
235
236 memset(buf, '\0', sizeof(buf));
237 l = now - *i;
238
239 if (l < 3600) {
240 snprintf(buf, sizeof(buf), "%lum%lus", (l / 60), (l % 60));
241
242 } else {
243 snprintf(buf, sizeof(buf), "%luh%lum", (l / 3600),
244 ((l - (l / 3600) * 3600) / 60));
245 }
246
247 return buf;
248 }
249
check_scoreboard_file(void)250 static int check_scoreboard_file(void) {
251 struct stat sbuf;
252
253 if (stat(util_get_scoreboard(), &sbuf) < 0)
254 return -1;
255
256 return 0;
257 }
258
show_ftpd_uptime(void)259 static const char *show_ftpd_uptime(void) {
260 static char buf[128] = {'\0'};
261 time_t uptime_secs = time(NULL) - ftp_uptime;
262 int upminutes, uphours, updays;
263 int pos = 0;
264
265 if (!ftp_uptime)
266 return "";
267
268 memset(buf, '\0', sizeof(buf));
269 pos += snprintf(buf, sizeof(buf)-1, "%s", ", up for ");
270
271 updays = (int) uptime_secs / (60 * 60 * 24);
272
273 if (updays) {
274 pos += snprintf(buf + pos, sizeof(buf) - pos, "%d day%s, ", updays,
275 (updays != 1) ? "s" : "");
276 }
277
278 upminutes = (int) uptime_secs / 60;
279
280 uphours = upminutes / 60;
281 uphours = uphours % 24;
282
283 upminutes = upminutes % 60;
284
285 if (uphours) {
286 snprintf(buf + pos, sizeof(buf) - pos, "%2d hr%s %02d min", uphours,
287 (uphours != 1) ? "s" : "", upminutes);
288
289 } else {
290 snprintf(buf + pos, sizeof(buf) - pos, "%d min", upminutes);
291 }
292
293 return buf;
294 }
295
process_opts(int argc,char * argv[])296 static void process_opts(int argc, char *argv[]) {
297 int optc = 0;
298 const char *prgopts = "AabDS:d:f:hIin:UV";
299
300 while ((optc = getopt(argc, argv, prgopts)) != -1) {
301 switch (optc) {
302 case 'A':
303 display_mode = 0U;
304 display_mode |= FTPTOP_SHOW_AUTH;
305 break;
306
307 case 'a':
308 display_mode &= ~FTPTOP_SHOW_AUTH;
309 break;
310
311 case 'b':
312 batch_mode = TRUE;
313 break;
314
315 case 'D':
316 display_mode = 0U;
317 display_mode |= FTPTOP_SHOW_DOWNLOAD;
318 break;
319
320 case 'd':
321 delay = atoi(optarg);
322
323 if (delay < 0) {
324 fprintf(stderr, "%s: negative delay illegal: %d\n", program,
325 delay);
326 exit(1);
327 }
328
329 if (delay > 15) {
330 fprintf(stderr, "%s: delay of 0-15 seconds only supported\n",
331 program);
332 exit(1);
333 }
334
335 break;
336
337 case 'f':
338 if (util_set_scoreboard(optarg) < 0) {
339 fprintf(stderr, "%s: unable to use scoreboard '%s': %s\n",
340 program, optarg, strerror(errno));
341 exit(1);
342 }
343 break;
344
345 case 'h':
346 usage();
347 break;
348
349 case 'I':
350 display_mode = 0U;
351 display_mode |= FTPTOP_SHOW_IDLE;
352 break;
353
354 case 'i':
355 display_mode &= ~FTPTOP_SHOW_IDLE;
356 break;
357
358 case 'n': {
359 int count;
360
361 count = atoi(optarg);
362 if (count <= 0) {
363 fprintf(stderr, "%s: bad iterations argument '%s'\n", program,
364 optarg);
365 exit(1);
366 }
367
368 max_iterations = count;
369 break;
370 }
371
372 case 'S':
373 if (server_name != NULL) {
374 free(server_name);
375 server_name = NULL;
376 }
377
378 server_name = strdup(optarg);
379 break;
380
381 case 'U':
382 display_mode = 0U;
383 display_mode |= FTPTOP_SHOW_UPLOAD;
384 break;
385
386 case 'V':
387 show_version();
388 break;
389
390 case '?':
391 break;
392
393 default:
394 break;
395 }
396 }
397
398 /* First attempt to check the supplied/default scoreboard path. If this is
399 * incorrect, try the config file kludge.
400 */
401 if (check_scoreboard_file() < 0) {
402 char *path;
403
404 path = util_scan_config(config_filename, "ScoreboardFile");
405 if (path != NULL) {
406 util_set_scoreboard(path);
407 free(path);
408 }
409
410 if (check_scoreboard_file() < 0) {
411 fprintf(stderr, "%s: %s\n", util_get_scoreboard(), strerror(errno));
412 fprintf(stderr, "(Perhaps you need to specify the ScoreboardFile with -f, or change\n");
413 fprintf(stderr," the compile-time default directory?)\n");
414 exit(1);
415 }
416 }
417 }
418
read_scoreboard(void)419 static void read_scoreboard(void) {
420
421 /* NOTE: this buffer should probably be limited to the maximum window
422 * width, as it is used for display purposes.
423 */
424 static char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
425 pr_scoreboard_entry_t *score = NULL;
426
427 ftp_sessions = calloc(chunklen, sizeof(char *));
428 if (ftp_sessions == NULL) {
429 exit(1);
430 }
431
432 if (scoreboard_open() < 0) {
433 return;
434 }
435
436 /* Iterate through the scoreboard. */
437 while ((score = util_scoreboard_entry_read()) != NULL) {
438
439 /* Default status: "A" for "authenticating" */
440 char *status = "A";
441
442 /* If a ServerName was given, skip unless the scoreboard entry matches. */
443 if (server_name != NULL &&
444 strcmp(server_name, score->sce_server_label) != 0) {
445 continue;
446 }
447
448 /* Clear the buffer for this run. */
449 memset(buf, '\0', sizeof(buf));
450
451 /* Has the user authenticated yet? */
452 if (strcmp(score->sce_user, "(none)") != 0) {
453
454 /* Determine the status symbol to display. */
455 if (strcmp(score->sce_cmd, "idle") == 0) {
456 status = "I";
457 ftp_nidles++;
458
459 if (display_mode != FTPTOP_SHOW_RATES &&
460 !(display_mode & FTPTOP_SHOW_IDLE)) {
461 continue;
462 }
463
464 } else if (strcmp(score->sce_cmd, "RETR") == 0 ||
465 strcmp(score->sce_cmd, "READ") == 0 ||
466 strcmp(score->sce_cmd, "scp download") == 0) {
467 status = "D";
468 ftp_ndownloads++;
469
470 if (display_mode != FTPTOP_SHOW_RATES &&
471 !(display_mode & FTPTOP_SHOW_DOWNLOAD)) {
472 continue;
473 }
474
475 } else if (strcmp(score->sce_cmd, "STOR") == 0 ||
476 strcmp(score->sce_cmd, "APPE") == 0 ||
477 strcmp(score->sce_cmd, "STOU") == 0 ||
478 strcmp(score->sce_cmd, "WRITE") == 0 ||
479 strcmp(score->sce_cmd, "scp upload") == 0) {
480 status = "U";
481 ftp_nuploads++;
482
483 if (display_mode != FTPTOP_SHOW_RATES &&
484 !(display_mode & FTPTOP_SHOW_UPLOAD)) {
485 continue;
486 }
487
488 } else if (strcmp(score->sce_cmd, "LIST") == 0 ||
489 strcmp(score->sce_cmd, "NLST") == 0 ||
490 strcmp(score->sce_cmd, "MLST") == 0 ||
491 strcmp(score->sce_cmd, "MLSD") == 0 ||
492 strcmp(score->sce_cmd, "READDIR") == 0) {
493 status = "L";
494 }
495
496 } else {
497 status = "A";
498
499 /* Overwrite the "command", for display purposes */
500 util_sstrncpy(score->sce_cmd, "(authenticating)", sizeof(score->sce_cmd));
501 }
502
503 if (display_mode != FTPTOP_SHOW_RATES) {
504 int user_namelen, client_namelen, cmd_arglen;
505
506 user_namelen = str_getscreenlen(score->sce_user, 8);
507 client_namelen = str_getscreenlen(score->sce_client_name, 20);
508 cmd_arglen = str_getscreenlen(score->sce_cmd_arg, FTPTOP_REG_ARG_SIZE);
509
510 snprintf(buf, sizeof(buf), FTPTOP_REG_DISPLAY_FMT,
511 (unsigned int) score->sce_pid, status,
512 user_namelen, user_namelen, score->sce_user,
513 client_namelen, client_namelen, score->sce_client_name,
514 score->sce_server_addr,
515 show_time(&score->sce_begin_session), score->sce_cmd,
516 cmd_arglen, cmd_arglen, score->sce_cmd_arg);
517 buf[sizeof(buf)-1] = '\0';
518
519 } else {
520 int user_namelen, client_namelen;
521
522 user_namelen = str_getscreenlen(score->sce_user, 8);
523 client_namelen = str_getscreenlen(score->sce_client_name, 44);
524
525 /* Skip sessions unless they are actually transferring data */
526 if (*status != 'U' &&
527 *status != 'D') {
528 continue;
529 }
530
531 snprintf(buf, sizeof(buf), FTPTOP_XFER_DISPLAY_FMT,
532 (unsigned int) score->sce_pid, status,
533 user_namelen, user_namelen, score->sce_user,
534 client_namelen, client_namelen, score->sce_client_name,
535 (score->sce_xfer_len / 1024.0) / (score->sce_xfer_elapsed / 1000),
536 FTPTOP_XFER_DONE_SIZE, FTPTOP_XFER_DONE_SIZE,
537 *status == 'D' ?
538 calc_percent_done(score->sce_xfer_size, score->sce_xfer_done) :
539 "(n/a)");
540 buf[sizeof(buf)-1] = '\0';
541 }
542
543 /* Make sure there is enough memory allocated in the session list.
544 * Allocate more if needed.
545 */
546 if (ftp_nsessions > 0 &&
547 ftp_nsessions % chunklen == 0) {
548 ftp_sessions = realloc(ftp_sessions,
549 (ftp_nsessions + chunklen) * sizeof(char *));
550
551 if (ftp_sessions == NULL) {
552 exit(1);
553 }
554 }
555
556 ftp_sessions[ftp_nsessions] = calloc(1, strlen(buf) + 1);
557 if (ftp_sessions[ftp_nsessions] == NULL) {
558 exit(1);
559 }
560
561 util_sstrncpy(ftp_sessions[ftp_nsessions++], buf, strlen(buf) + 1);
562 }
563
564 scoreboard_close();
565 }
566
scoreboard_close(void)567 static void scoreboard_close(void) {
568 util_close_scoreboard();
569 }
570
scoreboard_open(void)571 static int scoreboard_open(void) {
572 int res = 0;
573
574 res = util_open_scoreboard(O_RDONLY);
575 if (res < 0) {
576 switch (res) {
577 case UTIL_SCORE_ERR_BAD_MAGIC:
578 fprintf(stderr, "%s: error opening scoreboard: bad/corrupted file\n",
579 program);
580 return res;
581
582 case UTIL_SCORE_ERR_OLDER_VERSION:
583 fprintf(stderr, "%s: error opening scoreboard: bad version (too old)\n",
584 program);
585 return res;
586
587 case UTIL_SCORE_ERR_NEWER_VERSION:
588 fprintf(stderr, "%s: error opening scoreboard: bad version (too new)\n",
589 program);
590 return res;
591
592 default:
593 fprintf(stderr, "%s: error opening scoreboard: %s\n",
594 program, strerror(errno));
595 return res;
596 }
597 }
598
599 ftp_uptime = util_scoreboard_get_daemon_uptime();
600
601 return 0;
602 }
603
show_sessions(int use_attributes)604 static void show_sessions(int use_attributes) {
605 time_t now;
606 #if defined(HAVE_CTIME_R)
607 char now_str[32];
608 #else
609 char *now_str = NULL;
610 #endif /* HAVE_CTIME_R */
611 const char *uptime_str = NULL;
612
613 clear_counters();
614 read_scoreboard();
615
616 time(&now);
617
618 #if defined(HAVE_CTIME_R)
619 memset(now_str, '\0', sizeof(now_str));
620 (void) ctime_r(&now, now_str);
621 #else
622 now_str = ctime(&now);
623 #endif /* HAVE_CTIME_R */
624
625 /* Trim ctime(3)'s trailing newline. */
626 now_str[strlen(now_str)-1] = '\0';
627
628 uptime_str = show_ftpd_uptime();
629
630 if (use_attributes) {
631 wclear(stdscr);
632 move(0, 0);
633 attron(A_BOLD);
634 }
635
636 printw(FTPTOP_VERSION ": %s%s\n", now_str, uptime_str);
637 printw("%u Total FTP Sessions: %u downloading, %u uploading, %u idle\n",
638 ftp_nsessions, ftp_ndownloads, ftp_nuploads, ftp_nidles);
639
640 if (use_attributes) {
641 attroff(A_BOLD);
642 }
643
644 printw("\n");
645
646 if (use_attributes) {
647 attron(A_REVERSE);
648 }
649
650 if (display_mode != FTPTOP_SHOW_RATES) {
651 printw(FTPTOP_REG_HEADER_FMT, "PID", "S", "USER", "CLIENT", "SERVER",
652 "TIME", FTPTOP_REG_ARG_SIZE, "COMMAND");
653
654 } else {
655 printw(FTPTOP_XFER_HEADER_FMT, "PID", "S", "USER", "CLIENT", "KB/s", FTPTOP_XFER_DONE_SIZE, "%DONE");
656 }
657
658 if (use_attributes) {
659 attroff(A_REVERSE);
660 }
661
662 /* Write out the scoreboard entries. */
663 if (ftp_sessions != NULL &&
664 ftp_nsessions > 0) {
665 register unsigned int i = 0;
666
667 for (i = 0; i < ftp_nsessions; i++) {
668 printw("%s", ftp_sessions[i]);
669 }
670 }
671
672 wrefresh(stdscr);
673 }
674
toggle_mode(void)675 static void toggle_mode(void) {
676 static unsigned int cached_mode = 0;
677
678 if (cached_mode == 0)
679 cached_mode = display_mode;
680
681 if (display_mode != FTPTOP_SHOW_RATES) {
682 display_mode = FTPTOP_SHOW_RATES;
683
684 } else {
685 display_mode = cached_mode;
686 }
687 }
688
show_version(void)689 static void show_version(void) {
690 fprintf(stdout, FTPTOP_VERSION "\n");
691 exit(0);
692 }
693
usage(void)694 static void usage(void) {
695 fprintf(stdout, "usage: ftptop [options]\n\n");
696 fprintf(stdout, "\t-A \t\tshow only authenticating sessions\n");
697 fprintf(stdout, "\t-a \t\tignores authenticating connections when listing\n");
698 fprintf(stdout, "\t-b \t\tbatch mode\n");
699 fprintf(stdout, "\t-D \t\tshow only downloading sessions\n");
700 fprintf(stdout, "\t-d <num>\t\trefresh delay in seconds\n");
701 fprintf(stdout, "\t-f \t\tconfigures the ScoreboardFile to use\n");
702 fprintf(stdout, "\t-h \t\tdisplays this message\n");
703 fprintf(stdout, "\t-I \t\tshow only idle connections\n");
704 fprintf(stdout, "\t-i \t\tignores idle connections\n");
705 fprintf(stdout, "\t-n <num>\t\tnumber of iterations\n");
706 fprintf(stdout, "\t-S \t\tshow only sessions for this ServerName\n");
707 fprintf(stdout, "\t-U \t\tshow only uploading sessions\n");
708 fprintf(stdout, "\t-V \t\tshows version\n");
709 fprintf(stdout, "\n");
710 fprintf(stdout, " Use the 't' key to toggle between \"regular\" and \"transfer speed\"\n");
711 fprintf(stdout, " display modes. Use the 'q' key to quit.\n\n");
712 exit(0);
713 }
714
verify_scoreboard_file(void)715 static void verify_scoreboard_file(void) {
716 struct stat sbuf;
717
718 if (stat(util_get_scoreboard(), &sbuf) < 0) {
719 fprintf(stderr, "%s: unable to stat '%s': %s\n", program,
720 util_get_scoreboard(), strerror(errno));
721 exit(1);
722 }
723 }
724
main(int argc,char * argv[])725 int main(int argc, char *argv[]) {
726 unsigned int iteration_count = 0;
727
728 /* Process command line options. */
729 process_opts(argc, argv);
730
731 /* Verify that the scoreboard file is usable. */
732 verify_scoreboard_file();
733
734 /* Install signal handlers. */
735 signal(SIGINT, finish);
736 signal(SIGTERM, finish);
737
738 #if defined(PR_USE_NLS) && defined(HAVE_LOCALE_H)
739 (void) setlocale(LC_ALL, "");
740 #endif
741
742 /* Initialize the display. */
743 initscr();
744 if (batch_mode == TRUE) {
745 scrollok(stdscr, TRUE);
746
747 } else {
748 cbreak();
749 }
750 noecho();
751 #if !defined(HAVE_NCURSES)
752 nodelay(stdscr, TRUE);
753 #endif /* HAVE_NCURSES */
754 curs_set(0);
755
756 /* Paint the initial display. */
757 show_sessions(batch_mode == FALSE);
758
759 /* Loop endlessly. */
760 while (TRUE) {
761 if (batch_mode == TRUE) {
762 sleep(delay);
763
764 } else {
765 int c = -1;
766
767 #if defined(HAVE_NCURSES)
768 (void) halfdelay(delay * 10);
769 #else
770 sleep(delay);
771 #endif /* HAVE_NCURSES */
772
773 c = getch();
774 if (c != -1) {
775 if (tolower(c) == 'q') {
776 break;
777 }
778
779 if (tolower(c) == 't') {
780 toggle_mode();
781 }
782 }
783 }
784
785 show_sessions(batch_mode == FALSE);
786
787 if (batch_mode == TRUE) {
788 /* Only worry about the number of iterations if a limit has been
789 * set via `-n`.
790 */
791 if (max_iterations > 0) {
792 iteration_count++;
793
794 if (iteration_count >= max_iterations) {
795 break;
796 }
797 }
798 }
799 }
800
801 /* done */
802 finish(0);
803 return 0;
804 }
805
806 #else /* defined(HAVE_CURSES) || defined(HAVE_NCURSES) */
807
808 #include <stdio.h>
809
main(int argc,char * argv[])810 int main(int argc, char *argv[]) {
811 fprintf(stdout, "%s: no curses or ncurses library on this system\n", program);
812 return 1;
813 }
814
815 #endif /* defined(HAVE_CURSES) || defined(HAVE_NCURSES) */
816